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

Я заинтересован в поиске подобного контента (текста) на основе перефразирования. Как мне это сделать? Существуют ли какие-либо конкретные инструменты, которые могут сделать это? В питоне желательно.

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"

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

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

ура

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

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

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

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

Другие вопросы по тегам