查找具有相似文本的文章的算法
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了查找具有相似文本的文章的算法相关的知识,希望对你有一定的参考价值。
我在数据库中有很多文章(标题,文字),我正在寻找一种算法来找到X最相似的文章,比如Stack Overflow的“相关问题”,当你提出问题时。
我尝试谷歌搜索,但只找到关于其他“类似文本”问题的页面,比如将每篇文章与所有其他文章进行比较并在某处存储相似性。因此,我只是在我刚输入的文本上“实时”执行此操作。
怎么样?
Edit distance不是一个可能的候选者,因为它将依赖于拼写/字序,并且考虑到您实际感兴趣的文档的大小和数量,使得您相信计算成本高得多。
像Lucene这样的东西是要走的路。您索引所有文档,然后当您想要查找与给定文档类似的文档时,将您的给定文档转换为查询,并搜索索引。在内部,Lucene将使用tf-idf和inverted index使整个过程花费的时间与可能匹配的文档数量成比例,而不是集合中的文档总数。
如果你正在寻找相似的单词,你可以转换为soundex和soundex单词匹配...为我工作
我尝试了一些方法,但都没有效果。可能会得到一个相对令人满意的结果:首先:获取所有文本的每个段落的Google SimHash代码并将其存储在数据库中。第二:SimHash代码的索引。第三步:处理你的文本,如上所述进行比较,得到一个SimHash代码,并通过SimHash索引搜索所有文本,除了形成汉明距离,如5-10。然后将simility与term vector进行比较。这可能适用于大数据。
你可以使用1)Minhash / LSH https://en.wikipedia.org/wiki/MinHash
(另见:http://infolab.stanford.edu/~ullman/mmds/book.pdf)
要么
2)协同过滤:https://en.wikipedia.org/wiki/Collaborative_filtering
@ alex77的答案中的链接指向由该文章的作者独立发现的Sorensen-Dice Coefficient - 该文章写得非常好,值得一读。
我最终根据自己的需要使用这个系数。但是,原始系数在处理时会产生错误的结果
- 包含一个拼写错误的三个字母单词对,例如
[and,amd]
和 - 三个字母的单词对,例如字谜
[and,dan]
在第一种情况下,Dice错误地报告零系数,而在第二种情况下,系数变为0.5,这是误导性的高。
改进has been suggested,其本质上包括取出单词的第一个和最后一个字符并创建一个额外的二元组。
在我看来,只有3个字母的单词确实需要改进 - 换句话说,其他双字母的缓冲效果可以解决问题。我的代码实现了这种改进,如下所示。
function wordPairCount(word)
{
var i,rslt = [],len = word.length - 1;
for(i=0;i < len;i++) rslt.push(word.substr(i,2));
if (2 == len) rslt.push(word[0] + word[len]);
return rslt;
}
function pairCount(arr)
{
var i,rslt = [];
arr = arr.toLowerCase().split(' ');
for(i=0;i < arr.length;i++) rslt = rslt.concat(wordPairCount(arr[i]));
return rslt;
}
function commonCount(a,b)
{
var t;
if (b.length > a.length) t = b, b = a, a = t;
t = a.filter(function (e){return b.indexOf(e) > -1;});
return t.length;
}
function myDice(a,b)
{
var bigrams = [],
aPairs = pairCount(a),
bPairs = pairCount(b);
debugger;
var isct = commonCount(aPairs,bPairs);
return 2*commonCount(aPairs,bPairs)/(aPairs.length + bPairs.length);
}
$('#rslt1').text(myDice('WEB Applications','php Web Application'));
$('#rslt2').text(myDice('And','Dan'));
$('#rslt3').text(myDice('and','aMd'));
$('#rslt4').text(myDice('abracadabra','abracabadra'));
*{font-family:arial;}
table
{
width:80%;
margin:auto;
border:1px solid silver;
}
thead > tr > td
{
font-weight:bold;
text-align:center;
background-color:aqua;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<table>
<thead>
<tr>
<td>Phrase 1</td>
<td>Phrase 2</td>
<td>Dice</td>
</tr>
<thead>
<tbody>
<tr>
<td>WEB Applications</td>
<td>PHP Web Application</td>
<td id='rslt1'></td>
</tr>
<tr>
<td>And</td>
<td>Dan</td>
<td id='rslt2'></td>
</tr>
<tr>
<td>and</td>
<td>aMd</td>
<td id='rslt3'></td>
</tr>
<tr>
<td>abracadabra</td>
<td>abracabadra</td>
<td id='rslt4'></td>
</tr>
</tbody>
</table>
给定一个示例文本,该程序列出了按相似性排序的存储库文本:simple implementation of bag of words in C++。该算法在样本文本和存储库文本的总长度上是线性的。此外,该程序是多线程的,以并行处理存储库文本。
这是核心算法:
class Statistics {
std::unordered_map<std::string, int64_t> _counts;
int64_t _totWords;
void process(std::string& token);
public:
explicit Statistics(const std::string& text);
double Dist(const Statistics& fellow) const;
bool IsEmpty() const { return _totWords == 0; }
};
namespace {
const std::string gPunctStr = ".,;:!?";
const std::unordered_set<char> gPunctSet(gPunctStr.begin(), gPunctStr.end());
}
Statistics::Statistics(const std::string& text) {
std::string lastToken;
for (size_t i = 0; i < text.size(); i++) {
int ch = static_cast<uint8_t>(text[i]);
if (!isspace(ch)) {
lastToken.push_back(tolower(ch));
continue;
}
process(lastToken);
}
process(lastToken);
}
void Statistics::process(std::string& token) {
do {
if (token.size() == 0) {
break;
}
if (gPunctSet.find(token.back()) != gPunctSet.end()) {
token.pop_back();
}
} while (false);
if (token.size() != 0) {
auto it = _counts.find(token);
if (it == _counts.end()) {
_counts.emplace(token, 1);
}
else {
it->second++;
}
_totWords++;
token.clear();
}
}
double Statistics::Dist(const Statistics& fellow) const {
double sum = 0;
for (const auto& wordInfo : _counts) {
const std::string wordText = wordInfo.first;
const double freq = double(wordInfo.second) / _totWords;
auto it = fellow._counts.find(wordText);
double fellowFreq;
if (it == fellow._counts.end()) {
fellowFreq = 0;
}
else {
fellowFreq = double(it->second) / fellow._totWords;
}
const double d = freq - fellowFreq;
sum += d * d;
}
return std::sqrt(sum);
}
比较摘要之间相似性的最简单,最快捷的方法可能是利用集合概念。首先将抽象文本转换为单词集。然后检查每组重叠的程度。 Python的设置功能非常适合执行此任务。您会惊讶地发现这种方法与GScholar,ADS,WOS或Scopus提供的那些“类似/相关论文”选项相比有多好。
这取决于你对类似的定义。
edit-distance算法是(拉丁语)字典建议的标准算法,可以在整个文本上工作。如果两个文本在相同的顺序中具有基本相同的单词(eh字母),则两个文本类似。因此,以下两本书评论将非常相似:
1)“这是一本好书”
2)“这些不是很好的书”
(要删除,插入,删除或改为将(2)转换为(1)的字母数称为“编辑距离”。)
要实现此功能,您需要以编程方式访问每个审核。这可能不像听起来那么昂贵,如果它太昂贵,你可以将比较作为后台任务进行,并将最类似的数据存储在数据库字段本身中。
另一种方法是了解(拉丁语)语言的结构。如果您删除短(非元素化或引用)单词,并为常用或唯一的单词(或前缀)分配权重,则可以进行贝叶斯比较。以下两本书的评论可能会被复制,并且发现类似:
3)“法国革命是等等的战争与和平等等等等法国。” - >法国/法国(2)革命(1)战争(1)和平(1)(请注意,字典已用于法国和法国的结合)
4)“这本书真的是法国菜的革命。” - >法国(1)革命(1)
要实现这一点,您需要在评论创建/更新时识别“关键字”,并查找类似评论在查询的where子句中使用这些关键字(理想情
以上是关于查找具有相似文本的文章的算法的主要内容,如果未能解决你的问题,请参考以下文章