Java实现单词向量化与余弦相似度计算

在自然语言处理(NLP)中,单词向量化是将文本中的单词转换为数值向量的过程。通过这种方式,我们可以更方便地进行各种计算,例如文本相似度的计算。本文将介绍如何使用Java实现单词的向量化以及余弦相似度的计算,并提供代码示例。

单词向量化

单词向量化有多种方法,简化起见,我们可以使用词袋模型(Bag of Words)来进行单词向量化。词袋模型的核心思想是忽略单词中的语法和顺序,仅仅通过单词出现的频率来表示文本内容。

词袋模型的实现步骤

  1. 文本预处理:包括分词、去停用词、转换为小写等。
  2. 构建词汇表:从文本中提取所有唯一的单词。
  3. 向量化:将每个文本转换为对应的数字向量。

以下是词袋模型实现的Java代码示例:

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

public class BagOfWords {
    public static Map<String, Integer> buildVocabulary(List<String> documents) {
        Map<String, Integer> vocabulary = new HashMap<>();
        for (String document : documents) {
            String[] words = document.toLowerCase().split("\\W+");
            for (String word : words) {
                vocabulary.put(word, vocabulary.getOrDefault(word, 0) + 1);
            }
        }
        return vocabulary;
    }

    public static int[] vectorize(String document, Map<String, Integer> vocabulary) {
        int[] vector = new int[vocabulary.size()];
        String[] words = document.toLowerCase().split("\\W+");
        
        for (String word : words) {
            if (vocabulary.containsKey(word)) {
                int index = getIndex(vocabulary, word);
                vector[index]++;
            }
        }
        return vector;
    }

    private static int getIndex(Map<String, Integer> vocabulary, String word) {
        return (int) vocabulary.keySet().stream().filter(x -> x.equals(word)).count();
    }
}

使用示例

import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> documents = Arrays.asList(
            "Java is a programming language.",
            "Python is another popular programming language.",
            "Java and Python are widely used languages."
        );

        Map<String, Integer> vocabulary = BagOfWords.buildVocabulary(documents);

        // 向量化文档
        int[] vector1 = BagOfWords.vectorize(documents.get(0), vocabulary);
        int[] vector2 = BagOfWords.vectorize(documents.get(1), vocabulary);
        
        System.out.println("Vector for document 1: " + Arrays.toString(vector1));
        System.out.println("Vector for document 2: " + Arrays.toString(vector2));
    }
}

余弦相似度计算

余弦相似度是衡量两个向量之间相似度的重要指标,其计算公式如下:

[ \text{Cosine Similarity} = \frac{A \cdot B}{||A|| \cdot ||B||} ]

其中 (A) 和 (B) 是两个向量,(\cdot) 表示点积,(||A||) 和 (||B||) 是两个向量的模。

余弦相似度的实现步骤

  1. 计算点积:将两个向量对应元素相乘并求和。
  2. 计算模:使用平方和的平方根来计算每个向量的模。
  3. 计算相似度:将点积除以两个模的乘积。

以下是余弦相似度计算的Java代码示例:

public class CosineSimilarity {
    public static double calculateCosineSimilarity(int[] vector1, int[] vector2) {
        double dotProduct = 0.0;
        double magnitude1 = 0.0;
        double magnitude2 = 0.0;

        for (int i = 0; i < vector1.length; i++) {
            dotProduct += vector1[i] * vector2[i];
            magnitude1 += Math.pow(vector1[i], 2);
            magnitude2 += Math.pow(vector2[i], 2);
        }

        if (magnitude1 == 0 || magnitude2 == 0) {
            return 0.0;
        }
        return dotProduct / (Math.sqrt(magnitude1) * Math.sqrt(magnitude2));
    }
}

使用示例

public class SimilarityDemo {
    public static void main(String[] args) {
        // 向量示例
        int[] vector1 = {1, 0, 1, 0, 1}; // 举例的向量
        int[] vector2 = {0, 1, 1, 1, 0};

        double similarity = CosineSimilarity.calculateCosineSimilarity(vector1, vector2);
        System.out.println("Cosine Similarity: " + similarity);
    }
}

完整的代码示例

将以上两个部分整合在一起,我们可以创建一个完整的Java程序:

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Arrays;

public class TextSimilarity {
    public static void main(String[] args) {
        List<String> documents = Arrays.asList(
            "Java is a programming language.",
            "Python is another popular programming language."
        );

        Map<String, Integer> vocabulary = BagOfWords.buildVocabulary(documents);
        
        // 向量化文档
        int[] vector1 = BagOfWords.vectorize(documents.get(0), vocabulary);
        int[] vector2 = BagOfWords.vectorize(documents.get(1), vocabulary);
        
        double similarity = CosineSimilarity.calculateCosineSimilarity(vector1, vector2);
        System.out.println("Cosine Similarity: " + similarity);
    }

    public static class BagOfWords {
        public static Map<String, Integer> buildVocabulary(List<String> documents) {
            Map<String, Integer> vocabulary = new HashMap<>();
            for (String document : documents) {
                String[] words = document.toLowerCase().split("\\W+");
                for (String word : words) {
                    vocabulary.put(word, vocabulary.getOrDefault(word, 0) + 1);
                }
            }
            return vocabulary;
        }

        public static int[] vectorize(String document, Map<String, Integer> vocabulary) {
            int[] vector = new int[vocabulary.size()];
            String[] words = document.toLowerCase().split("\\W+");
            
            for (String word : words) {
                if (vocabulary.containsKey(word)) {
                    int index = getIndex(vocabulary, word);
                    vector[index]++;
                }
            }
            return vector;
        }

        private static int getIndex(Map<String, Integer> vocabulary, String word) {
            var keys = vocabulary.keySet().toArray();
            for (int i = 0; i < keys.length; i++) {
                if (keys[i].equals(word)) {
                    return i;
                }
            }
            return -1; // 不会到达这个位置
        }
    }

    public static class CosineSimilarity {
        public static double calculateCosineSimilarity(int[] vector1, int[] vector2) {
            double dotProduct = 0.0;
            double magnitude1 = 0.0;
            double magnitude2 = 0.0;

            for (int i = 0; i < vector1.length; i++) {
                dotProduct += vector1[i] * vector2[i];
                magnitude1 += Math.pow(vector1[i], 2);
                magnitude2 += Math.pow(vector2[i], 2);
            }

            if (magnitude1 == 0 || magnitude2 == 0) {
                return 0.0;
            }
            return dotProduct / (Math.sqrt(magnitude1) * Math.sqrt(magnitude2));
        }
    }
}

结论

在本文中,我们实现了用Java进行单词向量化的基本过程,并计算了余弦相似度。通过词袋模型,我们可以从文本中提取出单词并进行频率统计,最终形成向量以便进行相似度计算。在实际应用中,词袋模型通常与其他技术结合使用,以提高向量的表达能力,如TF-IDF和Word2Vec等。

这种方法提供了文本相似度计算的基础,适用于推荐系统、文档分类和信息检索等领域。希望本文的例子和解释能够帮助您更好地理解和实现单词向量化与余弦相似度的计算。根据实际需求,您可以扩展或优化这些方法,以适应更复杂的NLP任务。