Мне интересно найти похожий контент (текст) на основе перефразирования. Как мне это сделать? Есть ли какие-то специальные инструменты, которые могут это сделать? Желательно на питоне.
Находите похожие тексты на основе обнаружения перефразирования
Ответы (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 также требует тонны памяти, и когда у вас особенно большой корпус, вы не сможете его использовать.
Ваше здоровье
Я рекомендую вам прочитать ответы на этот вопрос, особенно первые два ответа действительно хороши.
Я также могу порекомендовать набор инструментов для обработки естественного языка (лично не пробовал)
Для сходства между новостными статьями вы можете извлекать ключевые слова, используя часть речевых тегов. NLTK предоставляет хороший POS-теггер. Используя существительные и словосочетания в качестве ключевых слов, представляйте каждую новостную статью как вектор ключевых слов.
Затем используйте косинусное сходство или другую подобную меру текстового сходства для количественной оценки сходства.
Дальнейшие улучшения включают обработку синонимов, определение корней слова, обработку прилагательных, если это необходимо, использование TF-IDF в качестве весов ключевых слов в векторе и т. Д.