Находите похожие тексты на основе обнаружения перефразирования

Мне интересно найти похожий контент (текст) на основе перефразирования. Как мне это сделать? Есть ли какие-то специальные инструменты, которые могут это сделать? Желательно на питоне.


person Sumukha TV    schedule 18.01.2014    source источник
comment
Должно ли сходство текста основываться на перефразировании или вы заинтересованы в выявлении похожего текста в целом?   -  person Harry    schedule 18.01.2014
comment
Моя конечная цель - найти похожие новостные статьи. Обратите внимание, я не имею в виду «связанные» новости. Мне нужны только похожие новости!   -  person Sumukha TV    schedule 20.01.2014


Ответы (3)


Я считаю, что инструмент, который вы ищете, - это скрытый семантический анализ.

Учитывая, что мой пост будет довольно длинным, я не буду вдаваться в подробности, объясняя теорию, лежащую в основе этого - если вы думаете, что это действительно то, что вы ищете, я рекомендую вам поискать его. Лучшее место для начала было бы здесь:

http://staff.scm.uws.edu.au/~lapark/lt.pdf

Таким образом, LSA пытается раскрыть скрытое / скрытое значение слов и фраз, основываясь на предположении, что похожие слова встречаются в аналогичных документах. Я буду использовать R, чтобы продемонстрировать, как это работает.

Я собираюсь настроить функцию, которая будет извлекать похожие документы на основе их скрытого значения:

# Setting up all the needed functions:

SemanticLink = function(text,expression,LSAS,n=length(text),Out="Text"){ 

  # Query Vector
  LookupPhrase = function(phrase,LSAS){ 
    lsatm = as.textmatrix(LSAS) 
    QV = function(phrase){ 
      q = query(phrase,rownames(lsatm)) 
      t(q)%*%LSAS$tk%*%diag(LSAS$sk) 
    } 

    q = QV(phrase) 
    qd = 0 

    for (i in 1:nrow(LSAS$dk)){ 
      qd[i] <- cosine(as.vector(q),as.vector(LSAS$dk[i,])) 
    }  
    qd  
  } 

  # Handling Synonyms
  Syns = function(word){   
    wl    =   gsub("(.*[[:space:]].*)","", 
                   gsub("^c\\(|[[:punct:]]+|^[[:space:]]+|[[:space:]]+$","", 
                        unlist(strsplit(PlainTextDocument(synonyms(word)),",")))) 
    wl = wl[wl!=""] 
    return(wl)  
  } 

  ex = unlist(strsplit(expression," "))
  for(i in seq(ex)){ex = c(ex,Syns(ex[i]))}
  ex = unique(wordStem(ex))

  cache = LookupPhrase(paste(ex,collapse=" "),LSAS) 

  if(Out=="Text"){return(text[which(match(cache,sort(cache,decreasing=T)[1:n])!="NA")])} 
  if(Out=="ValuesSorted"){return(sort(cache,decreasing=T)[1:n]) } 
  if(Out=="Index"){return(which(match(cache,sort(cache,decreasing=T)[1:n])!="NA"))} 
  if(Out=="ValuesUnsorted"){return(cache)} 

} 

Обратите внимание, что здесь мы используем синонимы при сборке нашего вектора запроса. Этот подход не идеален, потому что некоторые синонимы в библиотеке qdap в лучшем случае являются удаленными ... Это может помешать вашему поисковому запросу, поэтому для достижения более точных, но менее обобщаемых результатов вы можете просто избавиться от бит синонимов и вручную выберите все релевантные термины, составляющие вектор вашего запроса.

Давай попробуем. Я также буду использовать набор данных Конгресса США из пакета RTextTools:

library(tm)
library(RTextTools)
library(lsa)
library(data.table)
library(stringr)
library(qdap)

data(USCongress)

text = as.character(USCongress$text)

corp = Corpus(VectorSource(text)) 

parameters = list(minDocFreq        = 1, 
                  wordLengths       = c(2,Inf), 
                  tolower           = TRUE, 
                  stripWhitespace   = TRUE, 
                  removeNumbers     = TRUE, 
                  removePunctuation = TRUE, 
                  stemming          = TRUE, 
                  stopwords         = TRUE, 
                  tokenize          = NULL, 
                  weighting         = function(x) weightSMART(x,spec="ltn"))

tdm = TermDocumentMatrix(corp,control=parameters)
tdm.reduced = removeSparseTerms(tdm,0.999)

# setting up LSA space - this may take a little while...
td.mat = as.matrix(tdm.reduced) 
td.mat.lsa = lw_bintf(td.mat)*gw_idf(td.mat) # you can experiment with weightings here
lsaSpace = lsa(td.mat.lsa,dims=dimcalc_raw()) # you don't have to keep all dimensions
lsa.tm = as.textmatrix(lsaSpace)

l = 50 
exp = "support trade" 
SemanticLink(text,exp,n=5,lsaSpace,Out="Text") 

[1] "A bill to amend the Internal Revenue Code of 1986 to provide tax relief for small businesses, and for other purposes."                                                                       
[2] "A bill to authorize the Secretary of Transportation to issue a certificate of documentation with appropriate endorsement for employment in the coastwise trade for the vessel AJ."           
[3] "A bill to authorize the Secretary of Transportation to issue a certificate of documentation with appropriate endorsement for employment in the coastwise trade for the yacht EXCELLENCE III."
[4] "A bill to authorize the Secretary of Transportation to issue a certificate of documentation with appropriate endorsement for employment in the coastwise trade for the vessel M/V Adios."    
[5] "A bill to amend the Internal Revenue Code of 1986 to provide tax relief for small business, and for other purposes." 

Как вы можете видеть, хотя «поддержка торговли» может не отображаться как таковая в приведенном выше примере, функция извлекла набор документов, которые имеют отношение к запросу. Функция предназначена для поиска документов с семантическими связями, а не с точными совпадениями.

Мы также можем увидеть, насколько эти документы «близки» к вектору запроса, построив косинусные расстояния:

plot(1:l,SemanticLink(text,exp,lsaSpace,n=l,Out="ValuesSorted") 
     ,type="b",pch=16,col="blue",main=paste("Query Vector Proximity",exp,sep=" "), 
     xlab="observations",ylab="Cosine") 

У меня пока недостаточно репутации, чтобы продюсировать сюжет, извините.

Как видите, первые 2 записи более связаны с вектором запроса, чем остальные (хотя их около 5 особенно актуальны), хотя при чтении их у вас не было бы. Я бы сказал, что это эффект использования синонимов для построения векторов ваших запросов. Однако, игнорируя это, график позволяет нам узнать, сколько других документов отдаленно похожи на вектор запроса.


РЕДАКТИРОВАТЬ:


Совсем недавно мне пришлось решить проблему, которую вы пытаетесь решить, но указанная выше функция просто не сработала бы просто потому, что данные были ужасными - текст был коротким, его было очень мало и не так много тем были исследованы. Итак, чтобы найти подходящие записи в таких ситуациях, я разработал другую функцию, основанную исключительно на регулярных выражениях.

Вот оно:

HLS.Extract = function(pattern,text=active.text){


  require(qdap)
  require(tm)
  require(RTextTools)

  p = unlist(strsplit(pattern," "))
  p = unique(wordStem(p))
  p = gsub("(.*)i$","\\1y",p)

  Syns = function(word){   
    wl    =   gsub("(.*[[:space:]].*)","",      
                   gsub("^c\\(|[[:punct:]]+|^[[:space:]]+|[[:space:]]+$","",  
                        unlist(strsplit(PlainTextDocument(synonyms(word)),",")))) 
    wl = wl[wl!=""] 
    return(wl)     
  } 

  trim = function(x){

    temp_L  = nchar(x)
    if(temp_L < 5)                {N = 0}
    if(temp_L > 4 && temp_L < 8)  {N = 1}
    if(temp_L > 7 && temp_L < 10) {N = 2}
    if(temp_L > 9)                {N = 3}
    x = substr(x,0,nchar(x)-N)
    x = gsub("(.*)","\\1\\\\\\w\\*",x)

    return(x)
  }

  # SINGLE WORD SCENARIO

  if(length(p)<2){

    # EXACT
    p = trim(p)
    ndx_exact  = grep(p,text,ignore.case=T)
    text_exact = text[ndx_exact]

    # SEMANTIC
    p = unlist(strsplit(pattern," "))

    express  = new.exp = list()
    express  = c(p,Syns(p))
    p        = unique(wordStem(express))

    temp_exp = unlist(strsplit(express," "))
    temp.p = double(length(seq(temp_exp)))

    for(j in seq(temp_exp)){
      temp_exp[j] = trim(temp_exp[j])
    }

    rgxp   = paste(temp_exp,collapse="|")
    ndx_s  = grep(paste(temp_exp,collapse="|"),text,ignore.case=T,perl=T)
    text_s = as.character(text[ndx_s])

    f.object = list("ExactIndex"    = ndx_exact,
                    "SemanticIndex" = ndx_s,
                    "ExactText"     = text_exact,
                    "SemanticText"  = text_s)
  }

  # MORE THAN 2 WORDS

  if(length(p)>1){

    require(combinat)

    # EXACT
    for(j in seq(p)){p[j] = trim(p[j])}

    fp     = factorial(length(p))
    pmns   = permn(length(p))
    tmat   = matrix(0,fp,length(p))
    permut = double(fp)
    temp   = double(length(p))
    for(i in 1:fp){
      tmat[i,] = pmns[[i]]
    }

    for(i in 1:fp){
      for(j in seq(p)){
        temp[j] = paste(p[tmat[i,j]])
      }
      permut[i] = paste(temp,collapse=" ")
    }

    permut = gsub("[[:space:]]",
                  "[[:space:]]+([[:space:]]*\\\\w{,3}[[:space:]]+)*(\\\\w*[[:space:]]+)?([[:space:]]*\\\\w{,3}[[:space:]]+)*",permut)

    ndx_exact  = grep(paste(permut,collapse="|"),text)
    text_exact = as.character(text[ndx_exact])


    # SEMANTIC

    p = unlist(strsplit(pattern," "))
    express = list()
    charexp = permut = double(length(p))
    for(i in seq(p)){
      express[[i]] = c(p[i],Syns(p[i]))
      express[[i]] = unique(wordStem(express[[i]]))
      express[[i]] = gsub("(.*)i$","\\1y",express[[i]])
      for(j in seq(express[[i]])){
        express[[i]][j] = trim(express[[i]][j])
      }
      charexp[i] = paste(express[[i]],collapse="|")
    }

    charexp  = gsub("(.*)","\\(\\1\\)",charexp)
    charexpX = double(length(p))
    for(i in 1:fp){
      for(j in seq(p)){
        temp[j] = paste(charexp[tmat[i,j]])
      }
      permut[i] = paste(temp,collapse=
                          "[[:space:]]+([[:space:]]*\\w{,3}[[:space:]]+)*(\\w*[[:space:]]+)?([[:space:]]*\\w{,3}[[:space:]]+)*")
    }
    rgxp   = paste(permut,collapse="|")
    ndx_s  = grep(rgxp,text,ignore.case=T)
    text_s = as.character(text[ndx_s])

    temp.f = function(x){
      if(length(x)==0){x=0}
    }

    temp.f(ndx_exact);  temp.f(ndx_s)
    temp.f(text_exact); temp.f(text_s)

    f.object = list("ExactIndex"    = ndx_exact,
                    "SemanticIndex" = ndx_s,
                    "ExactText"     = text_exact,
                    "SemanticText"  = text_s,
                    "Synset"        = express)

  }
  return(f.object)
  cat(paste("Exact Matches:",length(ndx_exact),sep=""))
  cat(paste("\n"))
  cat(paste("Semantic Matches:",length(ndx_s),sep=""))
}

Пробуем:

HLS.Extract("buy house",
            c("we bought a new house",
              "I'm thinking about buying a new home",
              "purchasing a brand new house"))[["SemanticText"]]

$SemanticText
[1] "I'm thinking about buying a new home" "purchasing a brand new house"

Как видите, функция довольно гибкая. Это также подберет «покупку дома». Однако он не улавливал «мы купили новый дом», потому что «купил» - неправильный глагол - это то, что подхватило бы LSA.

Так что вы можете попробовать оба и посмотреть, какой из них работает лучше. Функция SemanticLink также требует тонны памяти, и когда у вас особенно большой корпус, вы не сможете его использовать.

Ваше здоровье

person IVR    schedule 27.01.2014
comment
Можно ли это сделать и для немецких текстов? - person Ray; 11.01.2021
comment
Я запустил HLS.Extract выше и застрял со следующим сообщением об ошибке: strsplit (PlainTextDocument (синонимы (слово)),,): несимвольный аргумент - person Ray; 11.01.2021
comment
Не могли бы вы проверить, совместим ли код с R 4.0.2? - person Ray; 11.01.2021
comment
Конечно, язык здесь не имеет значения. Этот код был написан 7 лет назад, и с тех пор многое изменилось. Он вполне мог быть несовместим с новой версией R. Кроме того, мои методы кодирования тогда были довольно плохими :) - person IVR; 12.01.2021
comment
Спасибо за ваш ответ. Установка as.character (PlainTextDocument (синонимы (слово))) заставляла код работать в Syns на самом верху. - person Ray; 25.01.2021

Я рекомендую вам прочитать ответы на этот вопрос, особенно первые два ответа действительно хороши.
Я также могу порекомендовать набор инструментов для обработки естественного языка (лично не пробовал)

person Jakub Tětek    schedule 18.01.2014

Для сходства между новостными статьями вы можете извлекать ключевые слова, используя часть речевых тегов. NLTK предоставляет хороший POS-теггер. Используя существительные и словосочетания в качестве ключевых слов, представляйте каждую новостную статью как вектор ключевых слов.

Затем используйте косинусное сходство или другую подобную меру текстового сходства для количественной оценки сходства.

Дальнейшие улучшения включают обработку синонимов, определение корней слова, обработку прилагательных, если это необходимо, использование TF-IDF в качестве весов ключевых слов в векторе и т. Д.

person Harry    schedule 20.01.2014
comment
Я пробовал этот метод. Но я хочу попробовать метод перефразирования прямо сейчас! - person Sumukha TV; 21.01.2014
comment
Возможно, вам стоит более подробно объяснить, чем вы хотите заниматься? Как вы рассчитываете вычислить сходство с помощью перефразирования? - person Harry; 21.01.2014