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


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

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

Как?

15 58

15 ответов:

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

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

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

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

1) "это великая книга"

2) "это не великие книги"

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

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

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

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

4) "Эта книга бла-бла-революция в Франции кухня."-> Франция(1) Революция(1)

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

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

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

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

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

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

Так же как и сравнение только по заголовку, а не по тексту вопроса, поэтому только на довольно коротких строках.

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

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

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

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

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

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

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

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

вы можете использовать либо 1) Minhash / LSHhttps://en.wikipedia.org/wiki/MinHash

(см. также: http://infolab.stanford.edu/~Уллман/продленным доступом/книга.формат PDF )

или

2) коллаборативная фильтрация:https://en.wikipedia.org/wiki/Collaborative_filtering

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

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

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

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

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

на мой взгляд, улучшение только действительно требуется для 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>

обратите внимание на намеренную опечатку в последнем примере: abracaдабра vs abracaБадра. Несмотря на то, что дополнительная коррекция биграммы не применяется, коэффициент, о котором сообщается, равен 0,9. С поправкой это было бы 0.91.

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

учитывая пример текста, эта программа перечисляет тексты репозитория, отсортированные по сходству:простая реализация мешка слов в 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.