it-swarm.com.ru

Алгоритм поиска статей с похожим текстом

У меня есть много статей в базе данных (с заголовком, текстом), я ищу алгоритм, чтобы найти X наиболее похожих статей, что-то вроде Stack Overflow «Связанные вопросы», когда вы задаете вопрос. 

Я попробовал поискать в Google, но нашел только страницы о других «похожих текстовых» проблемах, что-то вроде сравнения каждой статьи со всеми остальными и сохранения где-то сходства. SO делает это в режиме реального времени для текста, который я только что набрал.

Как?

58
Osama Al-Maadeed

Изменить расстояние не является вероятным кандидатом, так как это будет правописание/зависит от порядка слов и намного дороже в вычислительном отношении, чем заставляет вас поверить Уилл, учитывая размер и количество документов, которыми вы на самом деле будете заинтересован в поиске.

Что-то вроде Lucene - это путь. Вы индексируете все свои документы, а затем, когда хотите найти документы, похожие на данный документ, вы превращаете данный документ в запрос и выполняете поиск по индексу. Внутренне Lucene будет использовать tf-idf и инвертированный индекс , чтобы весь процесс занимал количество времени, пропорциональное количеству документов, которое может совпадать, а не общее количество документов в коллекции ,.

33
Jay Kominek

Это зависит от вашего определения подобного.

Алгоритм edit-distance является стандартным алгоритмом для (латинского языка) предложений словаря и может работать с целыми текстами. Два текста похожи, если они имеют в основном одни и те же слова (например, буквы) в одном и том же порядке. Таким образом, следующие две рецензии на книги будут довольно похожи:

1) «Это отличная книга»

2) «Это не великие книги»

(Количество букв, которые нужно удалить, вставить, удалить или изменить, чтобы превратить (2) в (1), называется «расстояние редактирования».)

Для реализации этого вы хотели бы посетить каждый обзор программно. Возможно, это не так дорого, как кажется, и если это слишком дорого, вы можете выполнить сравнения в качестве фоновой задачи и сохранить n-Most-similiar в самом поле базы данных.

Другой подход заключается в понимании структуры (латинских) языков. Если вы удаляете короткие (не прописные или заключенные в кавычки) слова и назначаете веса для слов (или префиксов), которые являются общими или уникальными, вы можете выполнить байесовское сравнение. Два следующих рецензии на книги могут быть сопоставлены и признаны похожими:

3) «Французская революция была бла-бла, война и мир, бла-бла, Франция». -> Франция/Французская (2) Революция (1) Война (1) Мир (1) (обратите внимание, что словарь использовался для объединения Франции и Франции)

4) «Эта книга - бла-бла-революция во французской кухне». -> Франция (1) Революция (1)

Чтобы реализовать это, вы захотите определить «ключевые слова» в обзоре, когда он был создан/обновлен, и найти похожие обзоры, используя эти ключевые слова в предложении where запроса (в идеале - полнотекстовый поиск, если база данных поддерживает его). ), возможно, с последующей обработкой набора результатов для оценки найденных кандидатов.

Книги также имеют категории - триллерные сюжеты во Франции похожи на исторические исследования Франции и так далее? Метаданные помимо заголовка и текста могут быть полезны для сохранения релевантности результатов.

14
Will

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

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

9
alex77

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

3
Guido

Один из распространенных алгоритмов - Самоорганизующаяся карта . Это тип нейронной сети, которая автоматически классифицирует ваши статьи. Затем вы можете просто найти местоположение, на котором находится текущая статья, и все статьи, расположенные рядом с ней, связаны между собой. Важной частью алгоритма является то, как вы бы вектор квантовали свой вход . Есть несколько способов сделать с текстом. Вы можете хешировать свой документ/заголовок, вы можете считать слова и использовать это как n-мерный вектор и т.д. Надеюсь, это поможет, хотя я, возможно, открыл ящик Пандоры для вас в бесконечном путешествии по ИИ.

2
mempko

Поэтому сравнение выполняется только по заголовку, а не по основному тексту вопроса, а только по довольно коротким строкам.

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

1
Treb

Отдельное предложение Lucene для полнотекстового, но обратите внимание, что Java не является обязательным требованием; порт .NET доступен . Также смотрите главную страницу Lucene для ссылок на другие проекты, включая Lucy, порт C .

1
b w

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

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

1
Vinnie

Если вы ищете слова, которые намотаны одинаково, вы можете преобразовать в soundex и слова soundex, чтобы соответствовать ... работал для меня

0
spacemonkeys

Учитывая пример текста, эта программа перечисляет тексты репозитория, отсортированные по сходству: простая реализация пакета слов в 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);
}
0
Serge Rogatch

Вы можете использовать полнотекстовый индекс SQL Server, чтобы получить умное сравнение. Я полагаю, что SO использует ajax-вызов, который выполняет запрос для возврата аналогичных вопросов.

Какие технологии вы используете?

0
Mitchel Sellers

Ссылка в ответе @ alex77 указывает на коэффициент Соренсена-Дайса , который был независимо обнаружен автором этой статьи - статья очень хорошо написана и заслуживает прочтения.

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

  • трехбуквенные пары слов, которые содержат одну орфографическую ошибку, например, [and,AMD] и
  • трехбуквенные пары слов, которые являются анаграммами, например [and,dan]

В первом случае Dice ошибочно сообщает, что коэффициент равен нулю, тогда как во втором случае коэффициент увеличивается до 0,5, что обманчиво велико.

Улучшение было предложено , которое по своей сути состоит из взятия первого и последнего символа Слова и создания дополнительного биграмма.

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

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>

Обратите внимание на преднамеренное написание в последнем примере: abracadabra vs abracabadra. Несмотря на то, что дополнительная коррекция биграммы не применяется, указанный коэффициент равен 0,9. С коррекцией это было бы 0,91.

Надеюсь, это поможет другим, кто наткнется на эту ветку.

0
DroidOS

Я попробовал какой-то метод, но ни один из них не работает хорошо. Один из них может получить относительно удовлетворительный результат, например: Во-первых: получите код Google SimHash для каждого абзаца всего текста и сохраните его в базе данных . Второе: индекс для кода SimHash . Третье: обработайте ваш текст для сравнения, как описано выше, получите код SimHash и выполните поиск по всему тексту по индексу SimHash, который, кроме того, образует расстояние Хэмминга, например 5-10. Затем сравните подобие с термином вектор . Это может работать для больших данных.

0
Luna_one

вы можете использовать любой 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

0
alex