[update] 数据处理-过滤配置补充

This commit is contained in:
Liuyang 2025-01-15 14:28:07 +08:00
parent 8b9649d2d3
commit aa05b784e2
2 changed files with 118 additions and 54 deletions

View File

@ -78,6 +78,13 @@
<version>5.9</version>
</dependency>
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>sensitive-word</artifactId>
<version>0.24.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -1,6 +1,8 @@
package cn.iocoder.yudao.module.llm.utils;
import com.github.houbb.opencc4j.util.ZhConverterUtil;
import com.github.houbb.sensitive.word.core.SensitiveWordHelper;
import lombok.extern.slf4j.Slf4j;
import java.time.Year;
import java.time.format.DateTimeFormatter;
@ -8,6 +10,7 @@ import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Slf4j
public class DataProcessUtil {
/*
@ -19,10 +22,11 @@ public class DataProcessUtil {
/**
* 移除不可见字
* 移除ASCII中的一些不可见字符, 如0-32 和127-160这两个范围
*
* @param input
* @return
*/
public static String removeNonVisibleAsciiChars(String input) {
public static String removeNonVisibleAsciiChars (String input) {
// 使用StringBuilder来构建正则表达式因为我们需要动态地添加字符范围
StringBuilder regex = new StringBuilder();
regex.append("[\\x00-\\x1F]"); // 0-31范围的字符
@ -35,12 +39,13 @@ public class DataProcessUtil {
/**
* 移除不可见字符
*
* <p>
* 将不同的unicode空格比如u2008转成正常的空格
*
* @param input
* @return
*/
public static String convertUnicodeSpacesToNormalSpaces(String input) {
public static String convertUnicodeSpacesToNormalSpaces (String input) {
// Unicode空格字符的正则表达式包括但不限于u2008等
String unicodeSpacesRegex = "[\\u0020\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]";
@ -50,12 +55,13 @@ public class DataProcessUtil {
/**
* 移除不可见字符
*
* <p>
* 去除乱码和无意义的unicode
*
* @param input
* @return
*/
public static String removeNonPrintableUnicodeChars(String input) {
public static String removeNonPrintableUnicodeChars (String input) {
// 构建一个正则表达式匹配所有非打印ASCII和非打印Unicode字符
// \p{C} 匹配所有控制字符和格式字符
// \p{Zs} 匹配所有空白分隔符比如U+2000到U+200F之间的字符
@ -78,12 +84,13 @@ public class DataProcessUtil {
/**
* 繁体转简体
*
* <p>
* 繁体转简体不經意妳的笑容清洗成不经意你的笑容
*
* @param input
* @return
*/
public static String traditionalToSimplified(String input) {
public static String traditionalToSimplified (String input) {
return ZhConverterUtil.toSimple(input);
}
@ -92,12 +99,13 @@ public class DataProcessUtil {
/**
* 去除网页标识符
*
* <p>
* 移除文档中的html标签<html>,<dev><p>
*
* @param input
* @return
*/
public static String removeHtmlTags(String input) {
public static String removeHtmlTags (String input) {
if (input == null || input.isEmpty()) {
return input;
}
@ -108,20 +116,21 @@ public class DataProcessUtil {
// 这是一个简化的正则表达式用于匹配常见的emoji表情符号
// 请注意它可能不会涵盖所有可能的emoji因为Unicode标准在不断发展
private static final String EMOJI_REGEX = "[\\uD83C-\\uD83D\\uD83E-\\uD83F\\u2600-\\u27FF"
+ "\\u2B00-\\u2BFF\\u2F00-\\u2FFF\\u3000-\\u303F"
+ "\\u3200-\\u32FF\\uA490-\\uA4CF\\uA900-\\uA97F"
+ "\\uAC00-\\uAC7F\\uAC80-\\uACFF\\uD700-\\uD7AF"
+ "\\uF900-\\uFAFF\\uFB00-\\uFB4F\\uFB50-\\uFDFF"
+ "\\uFE00-\\uFE6F\\uFE70-\\uFEFF\\uFF00-\\uFFEF]";
+ "\\u2B00-\\u2BFF\\u2F00-\\u2FFF\\u3000-\\u303F"
+ "\\u3200-\\u32FF\\uA490-\\uA4CF\\uA900-\\uA97F"
+ "\\uAC00-\\uAC7F\\uAC80-\\uACFF\\uD700-\\uD7AF"
+ "\\uF900-\\uFAFF\\uFB00-\\uFB4F\\uFB50-\\uFDFF"
+ "\\uFE00-\\uFE6F\\uFE70-\\uFEFF\\uFF00-\\uFFEF]";
/**
* 去除表情
*
* <p>
* 去除文档中的表情🐰👵
*
* @param input
* @return
*/
public static String removeEmojis(String input) {
public static String removeEmojis (String input) {
if (input == null || input.isEmpty()) {
return input;
}
@ -136,14 +145,14 @@ public class DataProcessUtil {
// 方法计算字符串中的中文字符数量
// 注意这里假设输入字符串只包含中文字符和可能的分隔符如空格标点符号等
// 并且中文字符在UTF-16编码中占用两个char但被视为一个逻辑字符
private static int countChineseChars(String input) {
private static int countChineseChars (String input) {
// 使用正则表达式匹配中文词汇并计算匹配到的字符总数这里需要除以2因为每个中文字符占用两个char
// 但为了简化我们可以直接遍历字符检查每个字符是否在中文范围内
int count = 0;
for (char c : input.toCharArray()) {
if (Character.UnicodeBlock.of(c) == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
|| Character.UnicodeBlock.of(c) == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
|| Character.UnicodeBlock.of(c) == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
|| Character.UnicodeBlock.of(c) == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
|| Character.UnicodeBlock.of(c) == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
// 可以根据需要添加更多Unicode块
) {
count++;
@ -161,12 +170,13 @@ public class DataProcessUtil {
/**
* 检查文档的词数目
* 词数目不在指定范围会被过滤掉如中文[1,1000000]
*
* @param text
* @param minChars
* @param maxChars
* @return
*/
public static List<String> filterWords(String text, int minChars, int maxChars) {
public static List<String> filterWords (String text, int minChars, int maxChars) {
List<String> result = new ArrayList<>();
Pattern pattern = Pattern.compile(CHINESE_WORD_REGEX);
Matcher matcher = pattern.matcher(text);
@ -183,13 +193,14 @@ public class DataProcessUtil {
/**
* 检查文档的字重复率
*
* <p>
* 如果字重复率太高意味着文档中重复的字太多文档会被过滤掉
* @param lines 文档行列表
* @param threshold // 设置字重复率的阈值例如10%
*
* @param lines 文档行列表
* @param threshold // 设置字重复率的阈值例如10%
* @return
*/
public static boolean calculateCharacterRepetitionRate(List<String> lines, double threshold) {
public static boolean calculateCharacterRepetitionRate (List<String> lines, double threshold) {
StringBuilder content = new StringBuilder();
for (String line : lines) {
content.append(line);
@ -222,7 +233,7 @@ public class DataProcessUtil {
}
// 简单的基于空格和标点符号的分词方法
private static List<String> tokenize(String text) {
private static List<String> tokenize (String text) {
// 使用正则表达式匹配非单词字符包括空格标点符号等并将它们作为分隔符
Pattern pattern = Pattern.compile("\\W+");
String[] words = pattern.split(text.toLowerCase()); // 转换为小写以进行不区分大小写的比较
@ -239,13 +250,14 @@ public class DataProcessUtil {
/**
* 检查文档的词重复率
*
* <p>
* 如果词重复率太高意味着文档中重复的词太多文档会被过滤掉
*
* @param content
* @param threshold
* @return
*/
public static boolean calculateWordRepetitionRate(String content, double threshold) {
public static boolean calculateWordRepetitionRate (String content, double threshold) {
// 分词
List<String> words = tokenize(content);
@ -278,11 +290,12 @@ public class DataProcessUtil {
/**
* 检查文档的特殊字符率
* 如果特殊字符率太高意味着文档中特殊字符太多文档会被过滤掉
*
* @param content
* @param threshold
* @return
*/
public static boolean checkSpecialCharacterRate(String content, double threshold) {
public static boolean checkSpecialCharacterRate (String content, double threshold) {
// 使用正则表达式匹配特殊字符非字母数字字符
Pattern pattern = Pattern.compile("[^a-zA-Z0-9]");
@ -308,6 +321,48 @@ public class DataProcessUtil {
return specialCharRate > threshold;
}
/**
* 检查文档的色情暴力词率
* <p>
* 如果色情暴力词率太高文档会被过滤掉取值范围[0,100]
* </p>
*
* @param content 文本内容
* @param threshold 阈值
* @return 是否过滤文档
*/
public static boolean checkSensitiveWordRate (String content, double threshold) {
// TODO: 先使用 sensitive-word 处理有修改再调整
// 检测是否包含色情暴力词
boolean isFalse = SensitiveWordHelper.contains(content);
if (!isFalse){
return false;
}
//返回所有敏感词
List<String> wordList = SensitiveWordHelper.findAll(content);
log.info("返回所有敏感词====>>>>{}", wordList);
// 统计敏感词的字符数量
int sensitiveWordLength = 0;
for (String word : wordList) {
sensitiveWordLength += word.length();
}
// 计算文档的总字符数不包括换行符等空白字符可以根据需要调整
// 或者使用 content.replaceAll("\\s+", "").length() 来排除空白字符
int totalCharCount = content.length();
// 计算敏感词长度占总长度的百分比
double specialCharRate = ((double) sensitiveWordLength / totalCharCount) * 100;
// 打印敏感词字符率和阈值方便调试
log.info("敏感词字符率: {}", String.format("%.3f", specialCharRate));
log.info("阈值: {}", threshold);
// 如果敏感词字符率超过阈值返回true表示需要过滤掉文档
return specialCharRate > threshold;
}
/*
* ---------------------------------------------------------------
* 🔖 去重配置
@ -327,7 +382,7 @@ public class DataProcessUtil {
private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX);
// 去除文本中的电子邮件地址
private static String removeEmails(String text) {
private static String removeEmails (String text) {
Matcher matcher = EMAIL_PATTERN.matcher(text);
// 使用空字符串替换匹配的电子邮件地址
return matcher.replaceAll("");
@ -335,11 +390,12 @@ public class DataProcessUtil {
/**
* 去除Email
*
* <p>
* 去除email地址
*
* @param content
*/
public static String processFile(String content) {
public static String processFile (String content) {
// 去除电子邮件地址
String modifiedContent = removeEmails(content);
@ -352,9 +408,9 @@ public class DataProcessUtil {
// 定义一个正则表达式来匹配IPv4地址
private static final String IPV4_REGEX =
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)";
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)";
// 定义一个正则表达式来匹配IPv6地址
// 这个正则表达式相对简单可能无法匹配所有复杂的IPv6地址格式
@ -371,7 +427,7 @@ public class DataProcessUtil {
/**
* 去除文本中的IPv4和IPv6地址
*/
public static String removeIPAddresses(String text) {
public static String removeIPAddresses (String text) {
Matcher ipv4Matcher = IPV4_PATTERN.matcher(text);
text = ipv4Matcher.replaceAll("");
Matcher ipv6Matcher = IPV6_PATTERN.matcher(text);
@ -407,12 +463,13 @@ public class DataProcessUtil {
/**
* 去除数字
*
* <p>
* 去除数字和字母数字标识符如电话号码信用卡号十六进制散列等同时跳过年份和简单数字的实例
*
* @param text
* @return
*/
public static String removeIdentifiers(String text) {
public static String removeIdentifiers (String text) {
Matcher phoneMatcher = PHONE_PATTERN.matcher(text);
text = phoneMatcher.replaceAll("");
@ -424,27 +481,27 @@ public class DataProcessUtil {
// 使用StringBuilder和StringBuilder的replace方法去除其他数字但跳过年份和简单数字
// TODO: 这里目前有bug先注释掉了
// StringBuilder sb = new StringBuilder(text);
// int index = 0;
// while ((index = findNextNumberToReplace(sb.toString())) != -1) {
// String number = sb.substring(index, findEndOfNumber(sb.toString(), index));
// if (!isYear(number) && !isSimpleNumber(number)) {
// sb.replace(index, index + number.length(), "");
// }
// }
// StringBuilder sb = new StringBuilder(text);
// int index = 0;
// while ((index = findNextNumberToReplace(sb.toString())) != -1) {
// String number = sb.substring(index, findEndOfNumber(sb.toString(), index));
// if (!isYear(number) && !isSimpleNumber(number)) {
// sb.replace(index, index + number.length(), "");
// }
// }
return text;
}
// 查找下一个要替换的数字的起始索引
private static int findNextNumberToReplace(String text) {
private static int findNextNumberToReplace (String text) {
// 这里可以添加更复杂的逻辑来定位要替换的数字但为了简化我们假设数字以空格或非数字字符分隔
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (Character.isDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
// 找到数字的起始位置
while (i < text.length() && (Character.isDigit(text.charAt(i)) ||
(text.charAt(i) >= 'a' && text.charAt(i) <= 'f') ||
(text.charAt(i) >= 'A' && text.charAt(i) <= 'F'))) {
(text.charAt(i) >= 'a' && text.charAt(i) <= 'f') ||
(text.charAt(i) >= 'A' && text.charAt(i) <= 'F'))) {
i++;
}
// 返回数字的起始索引减1因为我们要在循环外部处理i的递增
@ -455,12 +512,12 @@ public class DataProcessUtil {
}
// 找到数字的结束索引
private static int findEndOfNumber(String text, int startIndex) {
private static int findEndOfNumber (String text, int startIndex) {
// 从startIndex开始向后查找直到遇到非数字字符
for (int i = startIndex; i < text.length(); i++) {
if (!(Character.isDigit(text.charAt(i)) ||
(text.charAt(i) >= 'a' && text.charAt(i) <= 'f') ||
(text.charAt(i) >= 'A' && text.charAt(i) <= 'F'))) {
(text.charAt(i) >= 'a' && text.charAt(i) <= 'f') ||
(text.charAt(i) >= 'A' && text.charAt(i) <= 'F'))) {
return i;
}
}
@ -468,7 +525,7 @@ public class DataProcessUtil {
}
// 检查一个字符串是否是年份
private static boolean isYear(String str) {
private static boolean isYear (String str) {
try {
int year = Integer.parseInt(str);
Year y = Year.parse(str, YEAR_FORMAT);
@ -479,7 +536,7 @@ public class DataProcessUtil {
}
// 检查一个字符串是否是简单数字这里假设不超过六位的连续数字
private static boolean isSimpleNumber(String str) {
private static boolean isSimpleNumber (String str) {
try {
int number = Integer.parseInt(str);
return String.valueOf(number).equals(str) && number >= 0 && number < 1000000;
@ -488,7 +545,7 @@ public class DataProcessUtil {
}
}
public static void main(String[] args) {
public static void main (String[] args) {
String textWithIdentifiers = "Here are some identifiers: 123-456-7890, 1234567812345678, a1b2c3d4e5f6a1b2c3d4e5f6, 2023, and 987654.";
// 去除标识符
String textWithoutIdentifiers = removeIdentifiers(textWithIdentifiers);