ottenere similarità del coseno tra i due documenti in Lucene
-
12-09-2019 - |
Domanda
Ho costruito un indice in Lucene. Voglio senza specificare una query, solo per ottenere un punteggio (somiglianza coseno o in un altro a distanza?) Tra i due documenti nell'indice.
Per esempio sto ottenendo da IndexReader aperto in precedenza ir i documenti con ID 2 e 4. d1 = documento ir.document (2); d2 Document = ir.document (4);
Come posso ottenere la somiglianza del coseno tra questi due documenti?
Grazie
Soluzione
Quando l'indicizzazione, c'è la possibilità di memorizzare i vettori di frequenza termine.
Nel corso di esecuzione, cercare i vettori di frequenza termine per entrambi i documenti utilizzando IndexReader.getTermFreqVector (), e cercare i dati di frequenza documento per ogni termine con IndexReader.docFreq (). Che vi darà tutti i componenti necessari per calcolare la similarità del coseno tra i due documenti.
Un modo più semplice potrebbe essere quella di presentare doc A come una query (l'aggiunta di tutte le parole alla query come termini O, aumentando ogni per frequenza termine) e cercare doc B nel set di risultati.
Altri suggerimenti
Come Julia sottolinea di Sujit Pal esempio è molto utile ma l'API Lucene 4 presenta modifiche sostanziali. Ecco una versione riscritta per Lucene 4.
import java.io.IOException;
import java.util.*;
import org.apache.commons.math3.linear.*;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.core.SimpleAnalyzer;
import org.apache.lucene.document.*;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.index.*;
import org.apache.lucene.store.*;
import org.apache.lucene.util.*;
public class CosineDocumentSimilarity {
public static final String CONTENT = "Content";
private final Set<String> terms = new HashSet<>();
private final RealVector v1;
private final RealVector v2;
CosineDocumentSimilarity(String s1, String s2) throws IOException {
Directory directory = createIndex(s1, s2);
IndexReader reader = DirectoryReader.open(directory);
Map<String, Integer> f1 = getTermFrequencies(reader, 0);
Map<String, Integer> f2 = getTermFrequencies(reader, 1);
reader.close();
v1 = toRealVector(f1);
v2 = toRealVector(f2);
}
Directory createIndex(String s1, String s2) throws IOException {
Directory directory = new RAMDirectory();
Analyzer analyzer = new SimpleAnalyzer(Version.LUCENE_CURRENT);
IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_CURRENT,
analyzer);
IndexWriter writer = new IndexWriter(directory, iwc);
addDocument(writer, s1);
addDocument(writer, s2);
writer.close();
return directory;
}
/* Indexed, tokenized, stored. */
public static final FieldType TYPE_STORED = new FieldType();
static {
TYPE_STORED.setIndexed(true);
TYPE_STORED.setTokenized(true);
TYPE_STORED.setStored(true);
TYPE_STORED.setStoreTermVectors(true);
TYPE_STORED.setStoreTermVectorPositions(true);
TYPE_STORED.freeze();
}
void addDocument(IndexWriter writer, String content) throws IOException {
Document doc = new Document();
Field field = new Field(CONTENT, content, TYPE_STORED);
doc.add(field);
writer.addDocument(doc);
}
double getCosineSimilarity() {
return (v1.dotProduct(v2)) / (v1.getNorm() * v2.getNorm());
}
public static double getCosineSimilarity(String s1, String s2)
throws IOException {
return new CosineDocumentSimilarity(s1, s2).getCosineSimilarity();
}
Map<String, Integer> getTermFrequencies(IndexReader reader, int docId)
throws IOException {
Terms vector = reader.getTermVector(docId, CONTENT);
TermsEnum termsEnum = null;
termsEnum = vector.iterator(termsEnum);
Map<String, Integer> frequencies = new HashMap<>();
BytesRef text = null;
while ((text = termsEnum.next()) != null) {
String term = text.utf8ToString();
int freq = (int) termsEnum.totalTermFreq();
frequencies.put(term, freq);
terms.add(term);
}
return frequencies;
}
RealVector toRealVector(Map<String, Integer> map) {
RealVector vector = new ArrayRealVector(terms.size());
int i = 0;
for (String term : terms) {
int value = map.containsKey(term) ? map.get(term) : 0;
vector.setEntry(i++, value);
}
return (RealVector) vector.mapDivide(vector.getL1Norm());
}
}
Lo so domanda è stato risposto, ma per le persone che potrebbero venire qui in futuro, bell'esempio della soluzione può essere trovata qui:
http://sujitpal.blogspot.ch/2011/ 10 / informatico-documento-similarità-using.html
E 'una soluzione molto buona da Mark Butler, tuttavia i calcoli dei pesi / IDF TF sono sbagliati!
Term-frequenza (tf): quanto questo termine è apparso in questo documento (non tutti i documenti come nel codice con termsEnum.totalTermFreq ())
.Documento di frequenza (df):. Il numero totale di documenti che questo termine è apparso in
Inversa Document Frequency:. IDF = log (N / df), dove N è il numero totale di documenti
Tf / peso IDF = tf * IDF, per un determinato periodo e un determinato documento.
speravo in un calcolo efficiente utilizzando Lucene! Sono in grado di trovare un un calcolo efficiente per la corretta se / pesi IDF.
Modifica : Ho fatto questo codice per calcolare i pesi come pesi IDF TF / e termine frequenza non come puro. Funziona abbastanza bene, ma mi chiedo se c'è un modo più efficiente.
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.math3.linear.ArrayRealVector;
import org.apache.commons.math3.linear.RealVector;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.core.SimpleAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.DocsEnum;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.Terms;
import org.apache.lucene.index.TermsEnum;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.Version;
public class CosineSimeTest {
public static void main(String[] args) {
try {
CosineSimeTest cosSim = new
CosineSimeTest( "This is good",
"This is good" );
System.out.println( cosSim.getCosineSimilarity() );
} catch (IOException e) {
e.printStackTrace();
}
}
public static final String CONTENT = "Content";
public static final int N = 2;//Total number of documents
private final Set<String> terms = new HashSet<>();
private final RealVector v1;
private final RealVector v2;
CosineSimeTest(String s1, String s2) throws IOException {
Directory directory = createIndex(s1, s2);
IndexReader reader = DirectoryReader.open(directory);
Map<String, Double> f1 = getWieghts(reader, 0);
Map<String, Double> f2 = getWieghts(reader, 1);
reader.close();
v1 = toRealVector(f1);
System.out.println( "V1: " +v1 );
v2 = toRealVector(f2);
System.out.println( "V2: " +v2 );
}
Directory createIndex(String s1, String s2) throws IOException {
Directory directory = new RAMDirectory();
Analyzer analyzer = new SimpleAnalyzer(Version.LUCENE_CURRENT);
IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_CURRENT,
analyzer);
IndexWriter writer = new IndexWriter(directory, iwc);
addDocument(writer, s1);
addDocument(writer, s2);
writer.close();
return directory;
}
/* Indexed, tokenized, stored. */
public static final FieldType TYPE_STORED = new FieldType();
static {
TYPE_STORED.setIndexed(true);
TYPE_STORED.setTokenized(true);
TYPE_STORED.setStored(true);
TYPE_STORED.setStoreTermVectors(true);
TYPE_STORED.setStoreTermVectorPositions(true);
TYPE_STORED.freeze();
}
void addDocument(IndexWriter writer, String content) throws IOException {
Document doc = new Document();
Field field = new Field(CONTENT, content, TYPE_STORED);
doc.add(field);
writer.addDocument(doc);
}
double getCosineSimilarity() {
double dotProduct = v1.dotProduct(v2);
System.out.println( "Dot: " + dotProduct);
System.out.println( "V1_norm: " + v1.getNorm() + ", V2_norm: " + v2.getNorm() );
double normalization = (v1.getNorm() * v2.getNorm());
System.out.println( "Norm: " + normalization);
return dotProduct / normalization;
}
Map<String, Double> getWieghts(IndexReader reader, int docId)
throws IOException {
Terms vector = reader.getTermVector(docId, CONTENT);
Map<String, Integer> docFrequencies = new HashMap<>();
Map<String, Integer> termFrequencies = new HashMap<>();
Map<String, Double> tf_Idf_Weights = new HashMap<>();
TermsEnum termsEnum = null;
DocsEnum docsEnum = null;
termsEnum = vector.iterator(termsEnum);
BytesRef text = null;
while ((text = termsEnum.next()) != null) {
String term = text.utf8ToString();
int docFreq = termsEnum.docFreq();
docFrequencies.put(term, reader.docFreq( new Term( CONTENT, term ) ));
docsEnum = termsEnum.docs(null, null);
while (docsEnum.nextDoc() != DocIdSetIterator.NO_MORE_DOCS) {
termFrequencies.put(term, docsEnum.freq());
}
terms.add(term);
}
for ( String term : docFrequencies.keySet() ) {
int tf = termFrequencies.get(term);
int df = docFrequencies.get(term);
double idf = ( 1 + Math.log(N) - Math.log(df) );
double w = tf * idf;
tf_Idf_Weights.put(term, w);
//System.out.printf("Term: %s - tf: %d, df: %d, idf: %f, w: %f\n", term, tf, df, idf, w);
}
System.out.println( "Printing docFrequencies:" );
printMap(docFrequencies);
System.out.println( "Printing termFrequencies:" );
printMap(termFrequencies);
System.out.println( "Printing if/idf weights:" );
printMapDouble(tf_Idf_Weights);
return tf_Idf_Weights;
}
RealVector toRealVector(Map<String, Double> map) {
RealVector vector = new ArrayRealVector(terms.size());
int i = 0;
double value = 0;
for (String term : terms) {
if ( map.containsKey(term) ) {
value = map.get(term);
}
else {
value = 0;
}
vector.setEntry(i++, value);
}
return vector;
}
public static void printMap(Map<String, Integer> map) {
for ( String key : map.keySet() ) {
System.out.println( "Term: " + key + ", value: " + map.get(key) );
}
}
public static void printMapDouble(Map<String, Double> map) {
for ( String key : map.keySet() ) {
System.out.println( "Term: " + key + ", value: " + map.get(key) );
}
}
}
Calcolo Cosine somiglianza in Lucene versione 4.x è diversa da quelle da 3.x. Seguendo post è dettagliata spiegazione con tutto il codice necessario per il calcolo coseno di similitudine in Lucene 4.10.2. ComputerGodzilla: Calcolato Cosine somiglianza in Lucene
è possibile trovare soluzione migliore @ http://darakpanand.wordpress.com/2013/06/01/document-comparison-by-cosine-methodology-using-lucene/#more-53 . seguito sono riportati i passaggi
- il codice Java che costruisce vettore termine dai contenuti con l'aiuto di Lucene (verificare: http: //lucene.apache .org / core / ).
- Utilizzando calcolo biblioteca coseno commons-math.jar tra i due documenti è fatto.
Se non è necessario per memorizzare i documenti per Lucene e vogliono solo per calcolare similarità tra due documenti, ecco il codice più veloce (Scala, dal mio blog http://chepurnoy.org/blog/2014/03/faster-cosine-similarity- tra-due-dicuments-con-scala-e-lucene / )
def extractTerms(content: String): Map[String, Int] = {
val analyzer = new StopAnalyzer(Version.LUCENE_46)
val ts = new EnglishMinimalStemFilter(analyzer.tokenStream("c", content))
val charTermAttribute = ts.addAttribute(classOf[CharTermAttribute])
val m = scala.collection.mutable.Map[String, Int]()
ts.reset()
while (ts.incrementToken()) {
val term = charTermAttribute.toString
val newCount = m.get(term).map(_ + 1).getOrElse(1)
m += term -> newCount
}
m.toMap
}
def similarity(t1: Map[String, Int], t2: Map[String, Int]): Double = {
//word, t1 freq, t2 freq
val m = scala.collection.mutable.HashMap[String, (Int, Int)]()
val sum1 = t1.foldLeft(0d) {case (sum, (word, freq)) =>
m += word ->(freq, 0)
sum + freq
}
val sum2 = t2.foldLeft(0d) {case (sum, (word, freq)) =>
m.get(word) match {
case Some((freq1, _)) => m += word ->(freq1, freq)
case None => m += word ->(0, freq)
}
sum + freq
}
val (p1, p2, p3) = m.foldLeft((0d, 0d, 0d)) {case ((s1, s2, s3), e) =>
val fs = e._2
val f1 = fs._1 / sum1
val f2 = fs._2 / sum2
(s1 + f1 * f2, s2 + f1 * f1, s3 + f2 * f2)
}
val cos = p1 / (Math.sqrt(p2) * Math.sqrt(p3))
cos
}
Quindi, per calcolare somiglianza tra text1 e text2 basta chiamare similarity(extractTerms(text1), extractTerms(text2))