利用余弦相似度在大量文章中找出抄袭的文章

        我前面的2篇文章分别讲了如果利用余弦相似度来判断2篇文章的相似度,来确定文章是否存在抄袭,和余弦相似度的原理,即余弦相似度到底是怎么来判断文章的相似性高低的等等。这一篇再说下,对于文章字数多和大量文章时,如果找到两篇相似度高的文章。这里就需要考虑内存溢出的风险了。所以对第一篇的代码进行了改造。在一定程度上降低了内存溢出的风险。

pom依赖

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-math3</artifactId>
    <version>3.6.1</version>
</dependency>

       这里和第一篇略有不同,即第一篇采用的hankcs包实现的余弦相似度算法。本篇文章时通过math3包实现的。但是原理相同。

代码如下

package com.lsl.config;

import org.apache.commons.math3.linear.ArrayRealVector;
import org.apache.commons.math3.linear.RealVector;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

public class PlagiarismDetector {

    // 计算余弦相似度
    public static double cosineSimilarity(RealVector vectorA, RealVector vectorB) {
        double dotProduct = vectorA.dotProduct(vectorB);
        double normA = vectorA.getNorm();
        double normB = vectorB.getNorm();
        return dotProduct / (normA * normB);
    }

    // 将文本转换为词频向量
    public static Map<String, Integer> textToWordFrequency(String text) {
        Map<String, Integer> wordFrequency = new HashMap<>();
        String[] words = text.split("\\s+");
        for (String word : words) {
            wordFrequency.put(word, wordFrequency.getOrDefault(word, 0) + 1);
        }
        return wordFrequency;
    }

    // 将词频映射转换为向量
    public static RealVector wordFrequencyToVector(Map<String, Integer> wordFrequency, List<String> vocabulary) {
        double[] vector = new double[vocabulary.size()];
        for (int i = 0; i < vocabulary.size(); i++) {
            vector[i] = wordFrequency.getOrDefault(vocabulary.get(i), 0);
        }
        return new ArrayRealVector(vector);
    }

    // 读取文件内容(流式读取)
    public static String readFile(String filePath) throws IOException {
        StringBuilder content = new StringBuilder();
        try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = br.readLine()) != null) {
                content.append(line).append("\n");
            }
        }
        return content.toString();
    }

    // 构建词汇表(增量构建)
    public static List<String> buildVocabulary(Path papersDir) throws IOException {
        Set<String> vocabulary = new HashSet<>();
        Files.list(papersDir).forEach(path -> {
            try {
                String content = readFile(path.toString());
                String[] words = content.split("\\s+");
                vocabulary.addAll(Arrays.asList(words));
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        return new ArrayList<>(vocabulary);
    }

    // 主函数
    public static void main(String[] args) throws IOException {
        // 论文文件目录
        Path papersDir = Paths.get("D:\\codeabc");

        // 构建词汇表
        List<String> vocabulary = buildVocabulary(papersDir);

        // 存储每篇论文的词频向量
        List<RealVector> vectors = new ArrayList<>();

        // 逐篇处理论文
        Files.list(papersDir).forEach(path -> {
            try {
                String content = readFile(path.toString());
                Map<String, Integer> wordFrequency = textToWordFrequency(content);
                RealVector vector = wordFrequencyToVector(wordFrequency, vocabulary);
                vectors.add(vector);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        System.err.println("共有=" + vectors.size() + "文章");

        // 比较每对论文的相似度
        for (int i = 0; i < vectors.size(); i++) {
            for (int j = i + 1; j < vectors.size(); j++) {
                double similarity = cosineSimilarity(vectors.get(i), vectors.get(j));
                if (similarity > 0.9) { // 假设相似度大于0.9认为是抄袭
                    System.out.printf("Paper %d and Paper %d are similar with cosine similarity: %.2f%n", i, j, similarity);
                }
            }
        }
    }
}

运行截图如下:

改进点说明

  1. 流式读取文件

    • 使用BufferedReader逐行读取文件内容,避免一次性加载整个文件到内存中。

  2. 增量构建词汇表

    • 使用Files.list逐篇读取论文内容,逐步构建词汇表,而不是一次性加载所有论文内容。

  3. 逐篇处理论文

    • 在构建词频向量时,逐篇处理论文,避免一次性加载所有论文的词频向量到内存中。

  4. 内存优化

    • 使用HashSet存储词汇表,避免重复词汇占用额外内存。

    • 使用ArrayList存储词频向量,确保内存使用可控

进一步优化建议

  1. 分布式计算

    • 如果数据量非常大(如100,000篇论文),可以考虑使用分布式计算框架(如Apache Spark)来并行处理数据。

  2. 外部存储

    • 将词汇表和词频向量存储到磁盘(如数据库或文件),避免内存不足。

  3. 分块比较

    • 将论文分成多个块,逐块比较相似度,进一步减少内存占用。

  4. 剔除干扰词汇

    • 比如代码中对于一些import导入语句可以剔除

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一路奔跑1314

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值