1、简介

        在大型语言模型(LLM)训练前进行数据清理是至关重要的步骤,可以显著提高训练效率和模型性能。数据清理的方法有很多,需要根据收集的数据的特征进行有针对性的处理,本文主要是介绍一些数据清理的方法。 

2、数据清理

2.1、文本规范化

2.1.1、大小写的处理

不同的文本中,同样的单词可能有不同的大小写版本,例如:Hello, 可以是HELLO,也可以是HeLLo,而他们的意思是一样的,本质上也是一个单词,所以,首先就需要大小写的处理。

import re
import unicodedata

def normalize_text(text):
    # 统一为UTF-8编码并规范化Unicode字符
    text = unicodedata.normalize('NFKC', text)
    
    # 统一为小写(可选,根据任务决定)
    text = text.lower()    # 或者 text = text.casefold() 
    
    return text

# 示例
text = "Hello World"
print(normalize_text(text))  # 输出: "hello world"

2.1.2、特殊字符的处理

对于一些特殊符号,则可直接将其去除。例如:"Hello, This is a beautiful@ world. "里的特殊字符就没有什么必要保留。针对这些特殊字符,就可以通过正则表达式统一将特殊字符替换为空字符。

2.1.3、缩写的处理

在英文中经常有一些缩写,需要将缩写内容恢复为原来的单词,例如:I'm --> I am, 关于缩写可以使用专门的库 contractions 进行处理。

import contractions

test_text = "I'm a good student!"
clean_text = contractions.fix(test_text)
print(clean_text)  

运行代码,输出结果是 :

2.1.4、停用词(stop words)的处理

停用词(Stop Words)是指在自然语言处理(NLP)任务中,那些出现频率极高但对文本含义贡献很小的词语。这些词通常是功能词而非内容词,在不同语言中都有对应的停用词列表。

常见停用词示例

英语停用词

  • 冠词:the, a, an

  • 代词:I, you, he, she, it, we, they

  • 介词:in, on, at, of, for, with

  • 连词:and, but, or, so

  • 助动词:is, am, are, was, were, be, being, been

  • 其他常见词:this, that, these, those, there, here

中文停用词

  • 的、了、是、在、和、有、就、也、都、要、与、为、而、等、于

去除停用词的主要目标是避免使用那些在文本中频繁出现的但是对文本整体含义贡献较小的词语,以便更专注于重要的关键词。去除停用词,可以减少要处理的词汇量,提高处理效率;减少噪声干扰,降低计算复杂度,加快模型训练和推理速度;减少词表大小,降低嵌入矩阵维度,节省存储空间。

去除停用词的代码示例如下:

from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize

# 下载NLTK停用词数据(首次运行需要)
import nltk
nltk.download('stopwords')
nltk.download('punkt')

def remove_stopwords(text, language='english'):
    # 获取停用词列表
    stop_words = set(stopwords.words(language))
    
    # 分词
    word_tokens = word_tokenize(text)
    
    # 过滤停用词
    filtered_text = [word for word in word_tokens if word.lower() not in stop_words]
    
    return ' '.join(filtered_text)

# 示例
text = "This is a sample sentence, showing off the stop words filtration."
print(remove_stopwords(text))
# 输出: sample sentence , showing stop words filtration .

2.2、去除噪声数据

噪声数据主要包括:

  • HTML/XML标签

  • URL和电子邮件
  • 重复内容
  • 低质量文本(如乱码)

示例代码:

import re
from bs4 import BeautifulSoup

def clean_noise(text):
    # 去除HTML标签
    text = BeautifulSoup(text, 'html.parser').get_text()
    
    # 去除URL
    text = re.sub(r'http\S+|www\.\S+', '', text)
    
    # 去除电子邮件
    text = re.sub(r'\S+@\S+', '', text)
    
    # 去除特殊字符(保留基本标点)
    text = re.sub(r'[^\w\s.,!?\'"-]', '', text)
    
    return text.strip()

# 示例
html_text = "<p>Visit our site <a href='http://example.com'>here</a> or email info@example.com</p>"
print(clean_noise(html_text))  # 输出: Visit our site here or email

2.3、语言检测与过滤

        语言检测和过滤主要是检测文本语言并保留目标语言,使用语言模型评分过滤低质量文本。

from langdetect import detect
import fasttext

# 下载语言检测模型(首次运行需要下载)
# ft_model = fasttext.load_model('lid.176.ftz')

def filter_by_language(text, target_lang='en'):
    try:
        lang = detect(text)
        return lang == target_lang
    except:
        return False
    
# 更精确的语言检测(使用fasttext)
def filter_by_language_fasttext(text, target_lang='en', threshold=0.7):
    model = fasttext.load_model('lid.176.ftz')
    predictions = model.predict(text.replace('\n', ' '), k=1)
    lang = predictions[0][0].replace('__label__', '')
    prob = predictions[1][0]
    return lang == target_lang and prob > threshold

# 示例
text1 = "This is an English sentence."
text2 = "这是一句中文。"
print(filter_by_language(text1))  # True
print(filter_by_language(text2))  # False

 2.4、语言检测与过滤

        分词与标记化作为NLP流水线的第一步,直接影响模型性能。现代LLM通常采用子词标记化方案,在保留语义的同时有效控制计算复杂度。选择方案时应考虑:

  • 语言特性(是否需分词)

  • 领域特殊性(专业术语处理)

  • 计算资源(词汇表大小影响内存)

2.4.1、 分词(Word Segmentation)

针对中文等无空格语言

  • 原理:将连续的字序列按照语义或语法规则切分为独立的词单元

  • 示例
    "自然语言处理很有趣" → ["自然", "语言", "处理", "很", "有趣"]

关键技术

  • 基于词典的最大匹配法(正向/逆向/双向)

  • 统计方法(HMM、CRF)

  • 深度学习(BiLSTM-CRF、BERT)

2.4.2、 标记化(Tokenization)

针对所有语言

  • 原理:将文本分解为模型可处理的最小单元(token)

  • 层级

    • 词级别"Don't" → ["Do", "n't"]

    • 子词级别"unhappiness" → ["un", "happiness"]

    • 字符级别"中文" → ["中", "文"]

现代LLM常用方法

  • Byte Pair Encoding (BPE):GPT系列使用

  • WordPiece:BERT使用

  • Unigram:SentencePiece支持

  • SentencePiece:跨语言统一处理

2.4.3、 作用

1. 语义单元提取
  • 将无结构文本转化为结构化表示

  • 保留语言的基本语义单元
    例:"深度学习"应作为一个整体而非"深度"+"学习"

2. 解决OOV(未登录词)问题
  • 子词标记化使模型能处理未见过的词
    例:"ChatGPT"→["Chat","G","PT"]

3. 控制输入维度
  • 平衡词汇表大小与序列长度

  • 典型词汇表大小:

    • 英语:30k-50k

    • 多语言:100k-250k

4. 跨语言统一处理
  • 相同算法处理不同语言
    例:SentencePiece可统一处理中英文混合文本

from nltk.tokenize import word_tokenize, sent_tokenize

text = "Hello, this is a beautifaul world."

word_token = word_tokenize(text)
sent_token = sent_tokenize(text)

 2.5、数据去重

  • 精确去重(完全相同的文档)

  • 模糊去重(相似内容)

import hashlib
from datasketch import MinHash, MinHashLSH

# 精确去重
def exact_deduplicate(documents):
    seen = set()
    unique_docs = []
    for doc in documents:
        doc_hash = hashlib.md5(doc.encode()).hexdigest()
        if doc_hash not in seen:
            seen.add(doc_hash)
            unique_docs.append(doc)
    return unique_docs

# 模糊去重(使用MinHash)
def fuzzy_deduplicate(documents, threshold=0.8):
    lsh = MinHashLSH(threshold=threshold, num_perm=128)
    
    # 创建文档的MinHash
    minhashes = []
    for i, doc in enumerate(documents):
        mh = MinHash(num_perm=128)
        for word in doc.split():
            mh.update(word.encode('utf8'))
        lsh.insert(i, mh)
        minhashes.append(mh)
    
    # 查询相似文档
    duplicate_indices = set()
    for i, mh in enumerate(minhashes):
        result = lsh.query(mh)
        if len(result) > 1:  # 找到相似文档
            for j in result:
                if j != i and j not in duplicate_indices:
                    duplicate_indices.add(j)
    
    # 返回非重复文档
    return [doc for i, doc in enumerate(documents) if i not in duplicate_indices]

# 示例
docs = [
    "This is a test document.",
    "This is another document.",
    "This is a test document.",  # 完全重复
    "This is a test doc."        # 近似重复
]
print("Exact deduplication:", exact_deduplicate(docs))
print("Fuzzy deduplication:", fuzzy_deduplicate(docs))

 2.6、长度过滤

  • 过滤过短文本(无信息量)

  • 过滤过长文本(可能包含噪声)

def filter_by_length(texts, min_len=10, max_len=1000):
    filtered = []
    for text in texts:
        words = text.split()
        if min_len <= len(words) <= max_len:
            filtered.append(text)
    return filtered

# 示例
texts = ["Short.", "This is a normal length sentence.", "Very long..." * 100]
print(filter_by_length(texts))  # 输出: ['This is a normal length sentence.']

  2.7、毒性/敏感内容过滤

  • 使用预训练模型检测有害内容

  • 使用关键词过滤

from transformers import pipeline

# 加载毒性分类器
toxicity_classifier = pipeline("text-classification", model="facebook/roberta-hate-speech-dynabench-r4-target")

def filter_toxic_content(texts, threshold=0.5):
    safe_texts = []
    results = toxicity_classifier(texts)
    for text, result in zip(texts, results):
        if result['label'] == 'nothate' or result['score'] < threshold:
            safe_texts.append(text)
    return safe_texts

# 示例
texts = ["I love this!", "I hate you!"]
print(filter_toxic_content(texts))  # 可能只保留"I love this!"

  2.8、数据平衡

  • 对类别不平衡数据进行重采样

  • 对长尾分布数据进行截断

from collections import Counter
import random

def balance_data(texts, labels, target_count=None):
    label_counts = Counter(labels)
    if not target_count:
        target_count = min(label_counts.values())  # 取最小类别的数量
    
    balanced_texts = []
    balanced_labels = []
    
    for label in label_counts:
        indices = [i for i, l in enumerate(labels) if l == label]
        sampled_indices = random.sample(indices, min(target_count, len(indices)))
        balanced_texts.extend([texts[i] for i in sampled_indices])
        balanced_labels.extend([labels[i] for i in sampled_indices])
    
    return balanced_texts, balanced_labels

# 示例
texts = ["text1", "text2", "text3", "text4", "text5"]
labels = ["A", "A", "A", "B", "B"]
balanced_texts, balanced_labels = balance_data(texts, labels)
print(balanced_texts, balanced_labels)  # 每个类别2个样本(随机选择)

2.9、N-grams

N-grams是自然语言处理(NLP)中一种连续的N个项的序列,这里的"项"可以是音素、字母、单词或符号。在文本处理中,N-grams通常指连续的N个单词或字符的组合

常见类型:

  • Unigram (1-gram):单个词,如["自然", "语言", "处理"]

  • Bigram (2-gram):两个连续词,如["自然语言", "语言处理"]

  • Trigram (3-gram):三个连续词,如["自然语言处理"]

  • 更高阶的N-gram:如4-gram、5-gram等

N-grams的核心作用

1. 捕捉上下文信息

  • 解决"词袋模型"(Bag-of-Words)忽略词序的问题

  • 保留语言中的局部依赖关系

  • 例如:"不错"和"不错不错"通过bigram可以区分强度差异

2. 提升特征表达能力

  • 组合词作为整体特征(如"机器学习"比单独"机器"+"学习"更有意义)

  • 识别固定搭配和短语(如"人工智能"、"深度学习")

3. 改善统计语言模型

  • 用于计算P(wₙ|w₁...wₙ₋₁) ≈ P(wₙ|wₙ₋ₖ...wₙ₋₁)

  • 著名的Katz回退或Kneser-Ney平滑都基于N-gram

from nltk import ngrams
from collections import Counter

text = "自然语言处理是人工智能领域的重要方向".split()

# 生成bigram
bigrams = list(ngrams(text, 2))
print("Bigrams:", bigrams)
# 输出: [('自然', '语言'), ('语言', '处理'), ('处理', '是'), 
#       ('是', '人工智能'), ('人工智能', '领域'), ('领域', '的'), 
#       ('的', '重要'), ('重要', '方向')]

# 生成trigram
trigrams = list(ngrams(text, 3))
print("\nTrigrams:", trigrams)
# 输出: [('自然', '语言', '处理'), ('语言', '处理', '是'), 
#       ('处理', '是', '人工智能'), ('是', '人工智能', '领域'),
#       ('人工智能', '领域', '的'), ('领域', '的', '重要'), 
#       ('的', '重要', '方向')]

# 统计ngram频率
bigram_counts = Counter(ngrams(text, 2))
print("\nBigram频率:", bigram_counts.most_common(3))
# 输出: [(('自然', '语言'), 1), (('语言', '处理'), 1), (('处理', '是'), 1)]

N-grams作为经典的文本表示方法,虽然已被深度学习部分取代,但在资源有限、需要解释性或特定任务中仍具有重要价值。理解n-gram是掌握NLP基础的关键一步。 

2.10、完整数据处理流程示例

def full_data_processing_pipeline(raw_texts, target_lang='en'):
    processed_texts = []
    
    for text in raw_texts:
        # 1. 噪声去除
        text = clean_noise(text)
        
        # 2. 文本规范化
        text = normalize_text(text)
        
        # 3. 语言过滤
        if not filter_by_language(text, target_lang):
            continue
            
        # 4. 长度过滤
        if len(text.split()) < 10 or len(text.split()) > 1000:
            continue
            
        processed_texts.append(text)
    
    # 5. 去重
    processed_texts = exact_deduplicate(processed_texts)
    processed_texts = fuzzy_deduplicate(processed_texts)
    
    # 6. 毒性过滤(可选)
    processed_texts = filter_toxic_content(processed_texts)
    
    return processed_texts

# 使用示例
raw_texts = ["<html>Hello World!</html>", "Bonjour le monde", "Hello again!" * 1000]
clean_texts = full_data_processing_pipeline(raw_texts)
print(clean_texts)

 

3、数据清理总结

3.1、作用

  1. 提升数据质量:消除噪声和无关内容,保留有价值信息

  2. 增强模型性能:减少干扰因素,提高特征提取效率

  3. 降低计算成本:通过数据精简减少训练资源消耗

  4. 改善泛化能力:避免模型学习到数据中的虚假模式

  5. 统一数据格式:确保后续处理流程的一致性

清理类型 典型操作 主要作用
文本规范化 统一编码/大小写/标点/数字格式 消除格式差异,减少词汇表膨胀
噪声去除 清除HTML/URL/乱码/特殊字符 排除非文本干扰因素
语言过滤 保留目标语言文本 避免多语言混杂导致模型混淆
停用词处理 移除高频低信息量词汇 聚焦关键内容,降低维度
分词与标记化 将文本分解为语义单元 构建模型可处理的最小单位
长度过滤 剔除过短或过长文本 保证输入质量,控制序列长度
去重处理 消除相同/相似内容 防止数据偏斜,提升多样性
毒性内容过滤 识别并移除有害内容 确保模型输出安全性

3.2、关键注意事项

1. 预处理顺序策略

  • 推荐流程:编码统一 → 噪声去除 → 规范化 → 语言过滤 → 长度过滤 → 去重 → 分词 → 其他处理

  • 反例警示:若先分词再去除URL,可能无法正确识别跨分词的URL

2. 领域适应性原则

  • 医疗文本:保留数字和单位(如"5mg"不应被规范化)

  • 法律文本:保持大小写敏感性(如"Apple"公司 vs "apple"水果)

  • 社交媒体:谨慎处理表情符号和缩略语("IMO"可能包含重要信息)

3. 语言特性考量

  • 中文:需先分词再处理,注意成语/专有名词的完整性

  • 阿拉伯语:注意从右向左书写方向的特殊性

  • 德语:保留复合词不分割(如"Lebensversicherungsgesellschaft")

4. 模型兼容性

  • BERT类模型:需保留原始分词结构(中文按字分,英文按WordPiece)

  • GPT类模型:适配BPE分词器的特殊token(如Ġ表示空格)

  • 跨语言模型:检查是否支持混合语言标记化(如mBERT)

5. 数据平衡技巧

  • 重采样策略:对稀有类别过采样时保持n-gram分布

  • 截断策略:长文本截断应优先保留首尾部分(重要信息常在此位置)

3.3、最佳实践建议

1、保留原始数据副本

# 建立处理流水线时始终保留原始数据
def process_text(text):
    raw_text = text  # 保留原始
    processed = clean_text(text)
    return {"raw": raw_text, "processed": processed}

2、渐进式清理验证

# 分阶段验证清理效果
stages = [normalize, remove_urls, filter_language]
for stage in stages:
    text = stage(text)
    assert text_quality_improved(text)

3、多语言处理模板

from langdetect import detect

def multilingual_clean(text, target_lang):
    if detect(text) != target_lang:
        return None
    # 语言特定的清理流程
    if target_lang == 'zh':
        text = chinese_specific_clean(text)
    elif target_lang == 'ar':
        text = arabic_specific_clean(text)
    return text

4、标记化兼容性检查

# 验证标记化结果是否符合预期
tokenizer = AutoTokenizer.from_pretrained("bert-base-multilingual-cased")
sample = "日本語の処理"
tokens = tokenizer.tokenize(sample)
assert len(tokens) > 0  # 检查是否被错误地处理为[UNK]

5、可视化质量监控

# 使用pandas_profiling生成清理前后对比报告
import pandas as pd
from pandas_profiling import ProfileReport

df = pd.DataFrame({"raw": raw_texts, "cleaned": cleaned_texts})
ProfileReport(df, title="Data Cleaning Report").to_file("report.html")

3.4、最佳实践建议

  1. 过度清理

    • 删除所有标点符号(影响句子边界识别)

    • 保留基本标点,标准化变体符号

  2. 语言误判

    • 仅凭字符范围判断中文(可能混入日文汉字)

    • 使用fasttext等可靠语言检测库

  3. 标记不一致

    • 训练/推理阶段使用不同分词器

    • 持久化分词器配置确保一致性

  4. 内存爆炸

    • 一次性加载全部数据再清理

    • 使用生成器逐批处理:

def batch_clean(text_iter, batch_size=1000):
    for i in range(0, len(text_iter), batch_size):
        yield [clean_text(t) for t in text_iter[i:i+batch_size]]

            数据清理是NLP项目成功的基础环节,需要根据具体任务需求在"清理彻底性"和"信息保留度"之间找到平衡点。建议通过小规模实验(如清理前后模型性能对比)来确定最优清理策略。

    Logo

    GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

    更多推荐