web-dev-qa-db-fra.com

Algorithme pour trouver des articles avec un texte similaire

J'ai beaucoup d'articles dans une base de données (avec titre, texte), je cherche un algorithme pour trouver les X articles les plus similaires, quelque chose comme "Related Questions" de Stack Overflow lorsque vous posez une question. 

J'ai essayé de chercher sur Google pour cela, mais je n'ai trouvé que des pages sur d'autres problèmes liés au "texte similaire", comme comparer chaque article à tous les autres et stocker une similarité quelque part. SO fait cela en "temps réel" sur le texte que je viens de taper.

Comment?

58
Osama Al-Maadeed

Modifier la distance n'est pas un candidat probable, car cela dépendrait de l'orthographe/de l'ordre des mots, et beaucoup plus onéreux en termes de calculs que ne le laisse croire Will, compte tenu de la taille et du nombre de documents que vous seriez réellement intéressé à chercher.

Quelque chose comme Lucene est la voie à suivre. Vous indexez tous vos documents, puis lorsque vous voulez trouver des documents similaires à un document donné, vous transformez votre document donné en requête et recherchez l'index. En interne, Lucene utilisera tf-idf et un index inversé pour que l'ensemble du processus prenne une durée proportionnelle au nombre de documents pouvant éventuellement correspondre, et non au nombre total de documents de la collection. .

33
Jay Kominek

Cela dépend de votre définition de similiar.

L'algorithme edit-distance est l'algorithme standard pour les suggestions de dictionnaire (en latin) et peut fonctionner sur des textes entiers. Deux textes sont similaires s'ils ont fondamentalement les mêmes mots (lettres eh) dans le même ordre. Donc, les deux critiques de livres suivantes seraient assez similaires:

1) "C'est un excellent livre"

2) "Ce ne sont pas de bons livres"

(Le nombre de lettres à supprimer, insérer, supprimer ou modifier pour transformer (2) en (1) est appelé la "distance de modification".)

Pour implémenter cela, vous voudriez visiter chaque revue par programmation. Cela n’est peut-être pas aussi coûteux que cela en a l'air, et s'il est trop coûteux, vous pouvez effectuer les comparaisons en tâche de fond et stocker le n-plus-similaire dans un champ de base de données.

Une autre approche consiste à comprendre quelque chose de la structure des langues (latines). Si vous supprimez des mots courts (non capitalisés ou cités) et attribuez une pondération aux mots (ou préfixes) communs ou uniques, vous pouvez effectuer une comparaison Bayesianesque. Les deux critiques de livre suivantes peuvent être simiplées et jugées similaires:

3) "La révolution française a été bla bla guerre et paix bla bla France." -> France/Français (2) Révolution (1) Guerre (1) Paix (1) (notez qu'un dictionnaire a été utilisé pour combiner la France et le français)

4) "Ce livre est bla bla une révolution dans la cuisine française." -> France (1) Révolution (1)

Pour implémenter cela, vous voudrez identifier les "mots-clés" dans une critique lors de sa création/mise à jour, et pour trouver des critiques similaires, utilisez ces mots-clés dans la clause where d'une requête (idéalement, "recherche en texte intégral" si la base de données la prend en charge ), avec éventuellement un post-traitement de l’ensemble de résultats pour la notation des candidats trouvés.

Les livres ont aussi des catégories - les thrillers se déroulent-ils en France de la même manière que les études historiques françaises, et ainsi de suite? Des méta-données au-delà du titre et du texte peuvent être utiles pour garder les résultats pertinents.

14
Will

Le didacticiel de ce lien ressemble à ce dont vous avez besoin. Il est facile à suivre et fonctionne très bien. 

Son algorithme récompense à la fois les sous-chaînes communes et un ordre commun de ces sous-chaînes.

9
alex77

Je suggère d'indexer vos articles à l'aide de Apache Lucene , , une bibliothèque de moteur de recherche de texte performante et complète, entièrement écrite en Java. Cette technologie convient à presque toutes les applications nécessitant une recherche en texte intégral, en particulier multiplate-forme. Une fois indexé, vous pouvez facilement trouver des articles connexes.

3
Guido

Un algorithme couramment utilisé est la carte auto-organisée . C'est un type de réseau de neurones qui catégorise automatiquement vos articles. Ensuite, vous pouvez simplement trouver l'emplacement d'un article actuel sur la carte et tous les articles proches de celle-ci sont liés. La partie importante de l’algorithme est la manière dont vous feriez quantifier le vecteur de votre entrée } _. Il y a plusieurs façons de faire avec du texte. Vous pouvez hacher votre document/titre, vous pouvez compter les mots et l'utiliser comme un vecteur n dimensionnel, etc. J'espère que cela m'aidera, même si j'ai peut-être ouvert une boîte de Pandore d'un parcours sans fin dans l'IA.

2
mempko

SO fait la comparaison uniquement sur le titre, pas sur le corps du texte de la question, donc uniquement sur des chaînes plutôt courtes.

Vous pouvez utiliser leur algorithme (aucune idée de son apparence) sur le titre de l'article et sur les mots-clés . Si vous avez plus de temps CPU à graver, également sur les résumés de vos articles.

1
Treb

Appuyant la suggestion de Lucene pour le texte intégral, mais notez que Java n'est pas une obligation; un port .NET est disponible . Voir également la page main Lucene pour des liens vers d’autres projets, notamment Lucy, un port C .

1
b w

Peut-être que ce que vous cherchez est quelque chose qui fait paraphrasant . Je n’en ai qu’une connaissance sommaire, mais la paraphrase est un concept traitement du langage naturel permettant de déterminer si deux passages de texte correspondent réellement même chose - bien que l’on puisse utiliser des mots tout à fait différents.

Malheureusement, je ne connais aucun outil vous permettant de le faire (bien que je serais intéressé d'en trouver un)

1
Vinnie

Si vous recherchez des mots qui ressemblent à des blessures, vous pouvez convertir en soundex et les mots soundex à assortir ... ont fonctionné pour moi

0
spacemonkeys

À partir d’un exemple de texte, ce programme répertorie les textes du référentiel classés par similarité: implémentation simple du sac de mots en C++ . L'algorithme est linéaire dans la longueur totale du texte exemple et du texte du référentiel. De plus, le programme est multi-threadé pour traiter les textes du référentiel en parallèle.

Voici l'algorithme de base:

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

Vous pouvez utiliser l'index de texte intégral SQL Server pour obtenir la comparaison intelligente. Je crois que SO utilise un appel ajax, qui effectue une requête pour retourner des questions similaires.

Quelles technologies utilisez-vous?

0
Mitchel Sellers

Le lien dans la réponse de @ alex77 indique un coefficient coefficient de Sorensen-Dice qui a été découvert de manière indépendante par l'auteur de cet article - l'article est très bien écrit et mérite d'être lu.

J'ai fini par utiliser ce coefficient pour mes propres besoins. Cependant, le coefficient initial peut donner des résultats erronés lorsqu’il s’agit de 

  • des paires de mots de trois lettres qui en contiennent une, par exemple. [and,AMD] et
  • paires de mots de trois lettres qui sont des anagrammes, par exemple. [and,dan]

Dans le premier cas, Dice rapporte à tort un coefficient de zéro, tandis que dans le second cas, le coefficient est égal à 0,5, ce qui est erronément élevé.

Une amélioration a été suggérée qui consiste essentiellement à prendre le premier et le dernier caractère de la Parole et à créer un bigram supplémentaire.

À mon avis, l’amélioration n’est vraiment nécessaire que pour les mots de 3 lettres - en termes plus longs, les autres bigrammes ont un effet tampon qui recouvre le problème ... Le code qui implémente cette amélioration est donné ci-dessous.

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>

Notez la faute d'orthographe délibérée dans le dernier exemple: abracadabra vs abracabadra. Même si aucune correction bigramme supplémentaire n’est appliquée, le coefficient indiqué est de 0,9. Avec la correction, cela aurait été de 0,91.

J'espère que cela aidera les autres qui rencontrent ce fil.

0
DroidOS

J'ai essayé une méthode mais aucune ne fonctionne bien. On peut obtenir un résultat relativement satisfait comme celui-ci: Premièrement: obtenez un code Google SimHash pour chaque paragraphe de tout le texte et stockez-le dans la base de données . Deuxièmement: Index pour le code SimHash . Troisièmement: traitez votre texte à comparer comme ci-dessus, obtenez un code SimHash et recherchez tout le texte par index SimHash qui, à part, forment une distance de Hamming comme 5-10. Ensuite, comparez la similité avec le vecteur terme . Cela peut fonctionner pour le Big Data.

0
Luna_one
0
alex