Лучший способ расчета схожести документов с помощью Lucene

Я индексирую набор документов с помощью Lucene, указывая TermVector во время индексирования. Затем я извлекаю термины и их частоту, читая индекс и вычисляя векторы оценок TF-IDF для каждого документа. Затем, используя векторы TF-IDF, я вычисляю попарное косинусное сходство между документами, используя уравнение косинусного сходства Википедии .

Это моя проблема: скажем, у меня есть два идентичных документа «A» и «B» в этой коллекции (A и B содержат более 200 предложений). Если я вычисляю попарное косинусное сходство между A и B, это дает мне значение косинуса = 1, что совершенно нормально. Но если я удалю одно предложение из документа «B», это даст мне значение косинусного сходства около 0,85 между этими двумя документами. Документы почти аналогичны, но значения косинуса - нет. Я понимаю, что проблема в уравнении, которое я использую.

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

Отредактировано

Вот как я вычисляю косинусное сходство, doc1[] и doc2[] - это векторы TF-IDF для соответствующего документа. вектор содержит только scores, но не words

private double cosineSimBetweenTwoDocs(float doc1[], float doc2[]) {
    double temp;
    int doc1Len = doc1.length;
    int doc2Len = doc2.length;
    float numerator = 0;
    float temSumDoc1 = 0;
    float temSumDoc2 = 0;
    double equlideanNormOfDoc1 = 0;
    double equlideanNormOfDoc2 = 0;
    if (doc1Len > doc2Len) {
        for (int i = 0; i < doc2Len; i++) {
            numerator += doc1[i] * doc2[i];
            temSumDoc1 += doc1[i] * doc1[i];
            temSumDoc2 += doc2[i] * doc2[i];
        }
        equlideanNormOfDoc1=Math.sqrt(temSumDoc1);
         equlideanNormOfDoc2=Math.sqrt(temSumDoc2);
    } else {
        for (int i = 0; i < doc1Len; i++) {
            numerator += doc1[i] * doc2[i];
            temSumDoc1 += doc1[i] * doc1[i];
            temSumDoc2 += doc2[i] * doc2[i];
        }
         equlideanNormOfDoc1=Math.sqrt(temSumDoc1);
         equlideanNormOfDoc2=Math.sqrt(temSumDoc2);
    }

    temp = numerator / (equlideanNormOfDoc1 * equlideanNormOfDoc2);
    return temp;
} 

person Kasun    schedule 18.05.2012    source источник
comment
Я думаю, что с вашим кодом что-то не так. Удаление одного предложения из 200 предложений даст вам число ›0,98. Чтобы проверить это, вы можете сгенерировать случайный вектор, изменить вектор и вычислить для него косинусное сходство, чтобы увидеть, что вы получите. Для вектора размером 1000 и случайных чисел в диапазоне [10,100], если я вычту случайное число в диапазоне [10,20] из всех чисел в векторе, результирующая мера сходства для меня всегда будет ›0,98.   -  person Helium    schedule 18.05.2012
comment
Я использовал Mathematica для проверки этого случая. Вот мой код: a = RandomInteger [{10, 100}, 1000]; b = a - RandomInteger [{10, 20}, 1000]; {Всего [a], Всего [b], Всего [a - b], N [(ab) / (Норма [a] Норма [b])]}, и вот результат: {55419, 40271, 15148, 0,98811}   -  person Helium    schedule 18.05.2012
comment
@Mohsen Удаление одного предложения из вектора B уменьшит количество элементов в этом векторе, если мы получим вектор размером 1000 после удаления предложений, размер вектора B станет, скажем, 995, а теперь вектор A имеет размер 1000, но, два вектора тоже не выровнены. При удалении предложения элементы вектора удаляются из середины, но не из конца вектора. Итак, если вы можете попробовать, удалив векторные элементы из середины, вы можете увидеть значение 0,85   -  person Kasun    schedule 18.05.2012


Ответы (1)


Как я сказал вам в своем комментарии, я думаю, вы где-то ошиблись. На самом деле векторы содержат <word,frequency> пары, а не только words. Поэтому, когда вы удаляете предложение, только частота соответствующих слов вычитается на 1 (слова после не сдвигаются). Рассмотрим следующий пример:

Документ а:

A B C A A B C. D D E A B. D A B C B A.

Документ б:

A B C A A B C. D A B C B A.

Вектор а:

A:6, B:5, C:3, D:3, E:1

Вектор b:

A:5, B:4, C:3, D:1, E:0

Что приводит к следующей мере сходства:

(6*5+5*4+3*3+3*1+1*0)/(Sqrt(6^2+5^2+3^2+3^2+1^2) Sqrt(5^2+4^2+3^2+1^2+0^2))=
62/(8.94427*7.14143)=
0.970648

Изменить. Я думаю, что ваш исходный код тоже не работает. Рассмотрим следующий код, который отлично работает с приведенным выше примером:

import java.util.HashMap;
import java.util.Map;

public class DocumentVector {
    Map<String, Integer> wordMap = new HashMap<String, Integer>();

    public void incCount(String word) {
        Integer oldCount = wordMap.get(word);
        wordMap.put(word, oldCount == null ? 1 : oldCount + 1);
    }

    double getCosineSimilarityWith(DocumentVector otherVector) {
        double innerProduct = 0;
        for(String w: this.wordMap.keySet()) {
            innerProduct += this.getCount(w) * otherVector.getCount(w);
        }
        return innerProduct / (this.getNorm() * otherVector.getNorm());
    }

    double getNorm() {
        double sum = 0;
        for (Integer count : wordMap.values()) {
            sum += count * count;
        }
        return Math.sqrt(sum);
    }

    int getCount(String word) {
        return wordMap.containsKey(word) ? wordMap.get(word) : 0;
    }

    public static void main(String[] args) {
        String doc1 = "A B C A A B C. D D E A B. D A B C B A.";
        String doc2 = "A B C A A B C. D A B C B A.";

        DocumentVector v1 = new DocumentVector();
        for(String w:doc1.split("[^a-zA-Z]+")) {
            v1.incCount(w);
        }

        DocumentVector v2 = new DocumentVector();
        for(String w:doc2.split("[^a-zA-Z]+")) {
            v2.incCount(w);
        }

        System.out.println("Similarity = " + v1.getCosineSimilarityWith(v2));
    }

}
person Helium    schedule 18.05.2012
comment
Я вручную удаляю предложения из документа B, а затем выполняю индексацию с помощью Lucene. Итак, для вашего примера, в Doc B Lucene не знает, что ранее в документе B был термин E. - person Kasun; 19.05.2012
comment
Возьмем этот пример Doc A {ABCEEBC. DDEEB. DEBCBE} Doc B {DDEEB.DEBCBE} Итак, теперь вектор A {A-1, B-5, C-3, D-3, E-6} (размер вектора A = 5); Вектор B {B-3, C-1, D-3, E-4} (размер вектора B = 4). Таким образом, это показывает, что на самом деле члены смещены, поэтому он будет сравнивать член A из вектора A с членом B вектора B. Это выводит значение косинуса около ~ 0,7. Есть ли способ удалить предложения ПОСЛЕ индексации в Lucene? - person Kasun; 19.05.2012
comment
@Kasun: Я сам реализовал меру косинусного сходства несколько лет назад, как я описал вас, и раньше он работал отлично. Проблема в том, что вы думаете, что слова нужно менять, а нельзя. Это правда, что векторы a и b в приведенном выше примере имеют разный размер, но если термин не существует в векторе, его частота просто должна быть равна нулю. Итак, частота термина E в документе a равна 1, тогда как его частота в документе b равна 0 (потому что он не существует). - person Helium; 19.05.2012
comment
Lucene не нужно знать, был ли он удален или вообще не существовал. В качестве дополнительного примера, частота термина F (или любого другого термина, которого нет ни в одном из документов) равна 0 как в векторе a, так и в b. Я почти уверен, что вы неправильно используете Lucene. Не могли бы вы отредактировать свой вопрос и добавить минимальный фрагмент кода, демонстрирующий вашу проблему. - person Helium; 19.05.2012
comment
Я отредактировал вопрос с помощью кода вычисления подобия косинуса. Пожалуйста, проверьте. - person Kasun; 19.05.2012