diff --git a/yudao-module-llm/yudao-module-llm-biz/src/main/java/cn/iocoder/yudao/module/llm/utils/DataProcessUtil.java b/yudao-module-llm/yudao-module-llm-biz/src/main/java/cn/iocoder/yudao/module/llm/utils/DataProcessUtil.java index f0367c58f..852a43a90 100644 --- a/yudao-module-llm/yudao-module-llm-biz/src/main/java/cn/iocoder/yudao/module/llm/utils/DataProcessUtil.java +++ b/yudao-module-llm/yudao-module-llm-biz/src/main/java/cn/iocoder/yudao/module/llm/utils/DataProcessUtil.java @@ -370,6 +370,54 @@ public class DataProcessUtil { * --------------------------------------------------------------- */ + /** + * 相似度去重配置 + * + * @param contents 文本内容列表 + * @param threshold 相似度阈值 + * @return 是否需要去重 + */ + public static List similarityDeduplication (List contents, double threshold) { + long l3 = System.currentTimeMillis(); + List simHashes = new ArrayList<>(); + for (String content : contents) { + simHashes.add(HammingUtils.getSimHash(content)); + } + // 存储相似元素的索引 + List similarityIndex = new ArrayList<>(); + for (int i = 0; i < simHashes.size(); i++) { + // 如果当前元素已经标记为相似,则跳过 + if (similarityIndex.contains(i)) { + continue; + } + for (int j = i + 1; j < simHashes.size(); j++) { + String hash1 = simHashes.get(i); + String hash2 = simHashes.get(j); + // 从 1 开始计数,所以 i 和 j 都加 1 + double similarity = HammingUtils.getSimilarity(hash1, hash2); + log.info("第 " + (i + 1) + " 个元素 " + " 和第 " + (j + 1) + " 个元素 " + " 的文本相似度是:" + similarity); + + if (similarity > threshold) { + log.info("相似度大于 {} 的文本:{} 和 {}", threshold,hash1, hash2); + // 移除相似的文本 + similarityIndex.add(j); + } + } + } + log.info("相似索引列表:" + similarityIndex); + + long l4 = System.currentTimeMillis(); + long diff = l4 - l3; + long minutes = diff / (60 * 1000); + long seconds = (diff % (60 * 1000)) / 1000; + long milliseconds = diff % 1000; + + log.info("总耗时: " + minutes + " 分 " + seconds + " 秒 " + milliseconds + " 毫秒"); + log.info("======================================"); + + return similarityIndex; + } + /* * --------------------------------------------------------------- * 🔖 【 去隐私配置 】 diff --git a/yudao-module-llm/yudao-module-llm-biz/src/main/java/cn/iocoder/yudao/module/llm/utils/HammingUtils.java b/yudao-module-llm/yudao-module-llm-biz/src/main/java/cn/iocoder/yudao/module/llm/utils/HammingUtils.java new file mode 100644 index 000000000..40b96187e --- /dev/null +++ b/yudao-module-llm/yudao-module-llm-biz/src/main/java/cn/iocoder/yudao/module/llm/utils/HammingUtils.java @@ -0,0 +1,119 @@ +package cn.iocoder.yudao.module.llm.utils; + +import com.hankcs.hanlp.HanLP; +import lombok.extern.slf4j.Slf4j; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.List; + +/** + * @Description : 海明距离算法 + */ +@Slf4j +public class HammingUtils { + public static String getHash (String str) { + try { + // 这里使用了MD5获得hash值 + MessageDigest messageDigest = MessageDigest.getInstance("MD5"); + return new BigInteger(1, messageDigest.digest(str.getBytes(StandardCharsets.UTF_8))).toString(2); + } catch (Exception e) { + log.error("getHash error:{}", e.getMessage(), e); + return str; + } + } + + /** + * 传入String,计算出它的simHash值,并以字符串形式输出 + * + * @param str 传入的String类型字符串 + * @return 返回str的simHash值 + */ + public static String getSimHash (String str) { + + // 用数组表示特征向量,取128位,从 0 1 2 位开始表示从高位到低位 + int[] v = new int[128]; + // 1、分词(使用了外部依赖hankcs包提供的接口) + //取出所有关键词 + List keywordList = HanLP.extractKeyword(str, str.length()); + // hash + int size = keywordList.size(); + //以i做外层循环 + int i = 0; + for (String keyword : keywordList) { + // 2、获取hash值 + StringBuilder keywordHash = new StringBuilder(getHash(keyword)); + if (keywordHash.length() < 128) { + // hash值可能少于128位,在低位以0补齐 + int dif = 128 - keywordHash.length(); + for (int j = 0; j < dif; j++) { + keywordHash.append("0"); + } + } + // 3、加权、合并 + for (int j = 0; j < v.length; j++) { + // 对keywordHash的每一位与'1'进行比较 + if (keywordHash.charAt(j) == '1') { + //权重分10级,由词频从高到低,取权重10~0 + v[j] += (10 - (i / (size / 10))); + } else { + v[j] -= (10 - (i / (size / 10))); + } + } + i++; + } + // 4、降维 + // 储存返回的simHash值 + StringBuilder simHash = new StringBuilder(); + for (int k : v) { + // 从高位遍历到低位 + if (k <= 0) { + simHash.append("0"); + } else { + simHash.append("1"); + } + } + return simHash.toString(); + } + + + /** 输入两个 simHash 值,计算它们的海明距离 + * + * @param simHash1 simHash1 + * @param simHash2 simHash2 + * @return 海明距离 + */ + public static int getHammingDistance(String simHash1, String simHash2) { + int distance = 0; + if (simHash1.length() != simHash2.length()) { + // 出错,返回-1 + distance = -1; + } else { + // 将 simHash1 转换为 BigInteger 类型 + BigInteger hash1 = new BigInteger(simHash1, 2); + // 将 simHash2 转换为 BigInteger 类型 + BigInteger hash2 = new BigInteger(simHash2, 2); + // 使用 XOR 找出不同的位 + BigInteger xor = hash1.xor(hash2); + // 计算不同位的数量 + distance = xor.bitCount(); + } + return distance; + } + + /** + * 输入两个 simHash 值,输出相似度 + * + * @param simHash1 simHash1 + * @param simHash2 simHash2 + * @return 相似度 + */ + public static double getSimilarity(String simHash1, String simHash2) { + // 通过 simHash1 和 simHash2 获得它们的海明距离 + int distance = getHammingDistance(simHash1, simHash2); + // 通过海明距离计算出相似度,并返回 + return 0.01 * (100 - (double) (distance * 100) / 128); + } + +}