目录

1. 文本预处理概述

2. 分词处理

2.1 基础分词

2.2 词性标注

2.3 自定义词典

2.4 命名实体识别

2.5 停用词过滤

3. 文本清洗

3.1 去除特殊字符

3.2 数字处理

3.3 大小写统一

3.4 去除HTML标签

4. 文本向量化

4.1 One-hot编码

4.2 词袋模型

4.3 TF-IDF

4.4 Word2Vec

4.5 词嵌入层

5. 文本特征处理

5.1 N-gram特征

5.2 句子长度规范

5.3 文本统计特征

6. 完整预处理流程示例

7. 总结与最佳实践

7.1 预处理流程总结

7.2 最佳实践建议

7.3 推荐工具库

7.4 进阶学习方向



1. 文本预处理概述

文本预处理是自然语言处理(NLP)中的关键步骤,其目的是将原始文本数据转换为机器学习模型能够理解和处理的格式。预处理质量直接影响后续模型的性能。

预处理的主要目标

  • 清洗噪声数据,提高数据质量
  • 将文本转换为数值表示,便于计算机处理
  • 提取有用的特征,保留文本的关键信息
  • 减少数据维度,提高计算效率

2. 分词处理

2.1 基础分词

分词是将连续的文本序列切分成有意义的词汇单元的过程。对于中文等没有自然分隔符的语言,分词尤为重要。

import jieba

# 基础分词示例
text = "自然语言处理是人工智能的重要分支"
words = jieba.lcut(text)
print("分词结果:", words)
# 输出: ['自然语言', '处理', '是', '人工智能', '的', '重要', '分支']

# 精确模式 vs 全模式 vs 搜索引擎模式
print("精确模式:", jieba.lcut("我来到北京清华大学", cut_all=False))
print("全模式:", jieba.lcut("我来到北京清华大学", cut_all=True))
print("搜索引擎模式:", jieba.lcut_for_search("我来到北京清华大学"))

分词模式说明

  • 精确模式:试图将句子最精确地切开,适合文本分析
  • 全模式:把句子中所有的可以成词的词语都扫描出来,速度非常快,但是不能解决歧义
  • 搜索引擎模式:在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词

2.2 词性标注

词性标注是为每个分词结果标注其语法属性(名词、动词、形容词等)。

import jieba.posseg as pseg

text = "我爱北京天安门"
words = pseg.lcut(text)

print("词性标注结果:")
for word, flag in words:
    print(f"{word}({flag})", end=" ")
# 输出: 我(r) 爱(v) 北京(ns) 天安门(ns)

# 常见词性标签说明
pos_tags = {
    'n': '名词', 'v': '动词', 'a': '形容词',
    'd': '副词', 'r': '代词', 'p': '介词',
    'ns': '地名', 'nr': '人名', 'nt': '机构名'
}

2.3 自定义词典

当jieba默认词典无法准确识别专业术语或新词时,可以使用自定义词典。

# 创建自定义词典文件 user_dict.txt
"""
自然语言处理 10 n
人工智能 10 n
深度学习 10 n
卷积神经网络 5 n
循环神经网络 5 n
"""

# 加载自定义词典
jieba.load_userdict("user_dict.txt")

text = "自然语言处理和深度学习是人工智能的核心"
print("加载自定义词典后:", jieba.lcut(text))
# 输出: ['自然语言处理', '和', '深度学习', '是', '人工智能', '的', '核心']

# 动态添加词语
jieba.add_word('Transformer模型', freq=10, tag='n')
jieba.suggest_freq(('自然', '语言'), True)  # 调节词频

2.4 命名实体识别

命名实体识别(NER)旨在识别文本中具有特定意义的实体,如人名、地名、机构名等。

import jieba.posseg as pseg

def extract_entities(text):
    """提取命名实体"""
    entities = {
        'person': [],  # 人名
        'location': [],  # 地名
        'organization': []  # 机构名
    }

    words = pseg.lcut(text)
    for word, flag in words:
        if flag == 'nr':  # 人名
            entities['person'].append(word)
        elif flag == 'ns':  # 地名
            entities['location'].append(word)
        elif flag == 'nt':  # 机构名
            entities['organization'].append(word)

    return entities

text = "马云在杭州创办了阿里巴巴集团,马化腾在深圳创立了腾讯公司"
entities = extract_entities(text)
print("命名实体识别结果:")
for entity_type, entity_list in entities.items():
    print(f"{entity_type}: {entity_list}")

# 使用LAC进行更准确的NER(百度开源)
# pip install lac
from LAC import LAC
lac = LAC(mode='lac')
text = "李白出生于蜀郡绵州昌隆县"
result = lac.run(text)
print("LAC识别结果:", result)

2.5 停用词过滤

停用词是在文本中频繁出现但通常不携带重要信息的词汇(如"的"、"是"、"在"等)。

# 加载停用词表
def load_stopwords(filepath='stopwords.txt'):
    with open(filepath, 'r', encoding='utf-8') as f:
        return set([line.strip() for line in f])

# 常见中文停用词
stopwords = {'的', '了', '在', '是', '我', '有', '和', '就', '不', '人', '都',
             '一', '一个', '上', '也', '很', '到', '说', '要', '去', '你', '会'}

def remove_stopwords(words, stopwords):
    """去除停用词"""
    return [word for word in words if word not in stopwords]

text = "我今天来到了北京,这里是一个非常美丽的地方"
words = jieba.lcut(text)
filtered_words = remove_stopwords(words, stopwords)

print("原始分词:", words)
print("去除停用词后:", filtered_words)
# 输出: ['今天', '来到', '北京', '非常', '美丽', '地方']

3. 文本清洗

3.1 去除特殊字符

import re

def clean_text(text):
    """清洗文本"""
    # 去除HTML标签
    text = re.sub(r'<[^>]+>', '', text)

    # 去除URL
    text = re.sub(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', '', text)

    # 去除特殊字符和标点符号(保留中文标点)
    text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9\s,。!?、;:""''()]', '', text)

    # 去除多余的空格和换行
    text = re.sub(r'\s+', ' ', text).strip()

    return text

text = "<p>这是一段包含HTML标签的文本!访问https://example.com了解更多。</p>"
cleaned_text = clean_text(text)
print("清洗后:", cleaned_text)
# 输出: 这是一段包含HTML标签的文本!访问了解更多。

3.2 数字处理

def process_numbers(text, mode='replace'):
    """
    处理文本中的数字
    mode: 'replace' 替换为<NUM>, 'remove' 直接删除, 'keep' 保留
    """
    if mode == 'replace':
        return re.sub(r'\d+', '<NUM>', text)
    elif mode == 'remove':
        return re.sub(r'\d+', '', text)
    else:
        return text

text = "今天是2023年10月15日,气温25度,价格100元"
print("替换数字:", process_numbers(text, 'replace'))
# 输出: 今天是<NUM>年<NUM>月<NUM>日,气温<NUM>度,价格<NUM>元

print("删除数字:", process_numbers(text, 'remove'))
# 输出: 今天是年月日,气温度,价格元

3.3 大小写统一

def normalize_case(text, mode='lower'):
    """
    统一大小写
    mode: 'lower' 全小写, 'upper' 全大写, 'title' 首字母大写
    """
    if mode == 'lower':
        return text.lower()
    elif mode == 'upper':
        return text.upper()
    elif mode == 'title':
        return text.title()
    return text

text = "Hello World! 自然语言处理NLP"
print("小写:", normalize_case(text, 'lower'))
print("大写:", normalize_case(text, 'upper'))

3.4 去除HTML标签

from bs4 import BeautifulSoup

def remove_html_tags(text):
    """使用BeautifulSoup去除HTML标签"""
    soup = BeautifulSoup(text, 'html.parser')
    return soup.get_text()

html_text = """
<html>
<head><title>NLP教程</title></head>
<body>
<h1>文本预处理</h1>
<p>这是<b>重要</b>的步骤。</p>
</body>
</html>
"""

clean_text = remove_html_tags(html_text)
print("去除HTML标签后:", clean_text.strip())

4. 文本向量化

4.1 One-hot编码

One-hot编码将每个词表示为一个长向量,向量中只有一个维度为1,其余为0。

import numpy as np
from collections import Counter

def one_hot_encode(texts):
    """手动实现One-hot编码"""
    # 构建词汇表
    all_words = []
    for text in texts:
        words = jieba.lcut(text)
        all_words.extend(words)

    word_counts = Counter(all_words)
    vocab = sorted(word_counts.keys())
    word_to_idx = {word: idx for idx, word in enumerate(vocab)}

    # 编码
    encoded_texts = []
    for text in texts:
        words = jieba.lcut(text)
        encoded = []
        for word in words:
            one_hot = np.zeros(len(vocab))
            one_hot[word_to_idx[word]] = 1
            encoded.append(one_hot)
        encoded_texts.append(encoded)

    return encoded_texts, vocab, word_to_idx

# 示例
texts = ["我喜欢自然语言处理", "自然语言处理很有趣"]
encoded, vocab, word_to_idx = one_hot_encode(texts)
print("词汇表大小:", len(vocab))
print("第一个句子的编码形状:", np.array(encoded[0]).shape)

# 使用sklearn的OneHotEncoder
from sklearn.preprocessing import OneHotEncoder
import jieba

# 准备数据
words_list = [jieba.lcut(text) for text in texts]
flat_words = [word for words in words_list for word in words]

# 创建编码器
encoder = OneHotEncoder(sparse=False)
encoded = encoder.fit_transform(np.array(flat_words).reshape(-1, 1))
print("One-hot编码示例:\n", encoded[:5])

One-hot的优缺点

  • 优点:简单直观,易于实现
  • 缺点
    • 维度灾难:词汇表大时向量维度极高
    • 稀疏性:向量中大部分元素为0
    • 无法表达词之间的语义关系
    • 无法处理未登录词(OOV)

4.2 词袋模型

词袋模型(Bag of Words)将文本表示为词汇表中词的频率向量。

from sklearn.feature_extraction.text import CountVectorizer

# 示例文本
documents = [
    "自然语言处理是人工智能的重要方向",
    "深度学习在自然语言处理中应用广泛",
    "人工智能改变世界"
]

# 创建词袋模型
vectorizer = CountVectorizer()
jieba_cut = lambda x: ' '.join(jieba.lcut(x))
cut_documents = [jieba_cut(doc) for doc in documents]

# 转换为词频矩阵
X = vectorizer.fit_transform(cut_documents)

print("词汇表:", vectorizer.get_feature_names_out())
print("词频矩阵:\n", X.toarray())
print("矩阵形状:", X.shape)

# 添加n-gram特征
vectorizer_ngram = CountVectorizer(ngram_range=(1, 2))  # 使用unigram和bigram
X_ngram = vectorizer_ngram.fit_transform(cut_documents)
print("包含n-gram的词汇表大小:", len(vectorizer_ngram.get_feature_names_out()))

4.3 TF-IDF

TF-IDF(词频-逆文档频率)是一种统计方法,用于评估一个词对于一个文档集或语料库中的其中一份文档的重要程度。

from sklearn.feature_extraction.text import TfidfVectorizer
import numpy as np

# 示例文档
documents = [
    "自然语言处理是人工智能的分支",
    "深度学习用于自然语言处理",
    "人工智能包括机器学习和深度学习"
]

# 预处理
jieba_cut = lambda x: ' '.join(jieba.lcut(x))
cut_documents = [jieba_cut(doc) for doc in documents]

# 创建TF-IDF向量化器
tfidf_vectorizer = TfidfVectorizer()
tfidf_matrix = tfidf_vectorizer.fit_transform(cut_documents)

print("词汇表:", tfidf_vectorizer.get_feature_names_out())
print("TF-IDF矩阵:\n", tfidf_matrix.toarray())

# 查看每个词的TF-IDF值
feature_names = tfidf_vectorizer.get_feature_names_out()
for doc_idx, doc in enumerate(cut_documents):
    print(f"\n文档{doc_idx + 1}: {documents[doc_idx]}")
    feature_index = tfidf_matrix[doc_idx, :].nonzero()[1]
    tfidf_scores = zip(feature_index, [tfidf_matrix[doc_idx, x] for x in feature_index])
    for idx, score in tfidf_scores:
        print(f"  {feature_names[idx]}: {score:.4f}")

# 计算文档相似度
from sklearn.metrics.pairwise import cosine_similarity
similarity_matrix = cosine_similarity(tfidf_matrix)
print("\n文档相似度矩阵:\n", similarity_matrix)

TF-IDF的数学原理

  • TF(词频):词在文档中出现的频率
    • TF(t,d) = 词t在文档d中出现的次数 / 文档d的总词数
  • IDF(逆文档频率):衡量词的普遍重要性
    • IDF(t) = log(总文档数 / (包含词t的文档数 + 1))
  • TF-IDF = TF × IDF

4.4 Word2Vec

Word2Vec是一种将词映射到向量空间的模型,能够捕捉词之间的语义关系。

from gensim.models import Word2Vec
import jieba

# 准备训练数据
sentences = [
    "自然语言处理是人工智能的重要分支",
    "深度学习在自然语言处理中应用广泛",
    "卷积神经网络用于图像处理",
    "循环神经网络用于序列数据处理",
    "注意力机制是Transformer的核心"
]

# 分词
tokenized_sentences = [jieba.lcut(sentence) for sentence in sentences]

# 训练Word2Vec模型
# CBOW模型
model_cbow = Word2Vec(
    sentences=tokenized_sentences,
    vector_size=100,  # 词向量维度
    window=5,  # 上下文窗口大小
    min_count=1,  # 最小词频
    workers=4,  # 并行线程数
    sg=0  # 0表示CBOW,1表示Skip-gram
)

# Skip-gram模型
model_sg = Word2Vec(
    sentences=tokenized_sentences,
    vector_size=100,
    window=5,
    min_count=1,
    workers=4,
    sg=1  # 使用Skip-gram
)

# 使用模型
print("CBOW模型:")
print("'自然语言处理'的词向量形状:", model_cbow.wv['自然语言处理'].shape)
print("与'自然语言处理'最相似的词:", model_cbow.wv.most_similar('自然语言处理', topn=3))

print("\nSkip-gram模型:")
print("'深度学习'的词向量形状:", model_sg.wv['深度学习'].shape)

# 词向量运算示例
# 国王 - 男人 + 女人 ≈ 女王
try:
    result = model_cbow.wv.most_similar(positive=['人工智能', '处理'],
                                       negative=['学习'], topn=1)
    print("'人工智能' + '处理' - '学习' ≈ ", result)
except:
    print("词向量运算需要更大的语料库")

# 保存和加载模型
model_cbow.save("word2vec_cbow.model")
# loaded_model = Word2Vec.load("word2vec_cbow.model")

Word2Vec的两种架构

  1. 1.CBOW(连续词袋模型):用上下文词预测中心词
    • 训练速度快
    • 对频繁词有更好的表示
  2. 2.Skip-gram:用中心词预测上下文词
    • 对少量数据效果更好
    • 能更好地表示罕见词

4.5 词嵌入层

在深度学习模型中,词嵌入层将词索引映射到密集向量。

import numpy as np
import tensorflow as tf
from tensorflow.keras.layers import Embedding, Dense, Flatten
from tensorflow.keras.models import Sequential
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences

# 示例数据
texts = [
    "自然语言处理是人工智能的重要分支",
    "深度学习在自然语言处理中应用广泛",
    "卷积神经网络用于图像识别",
    "循环神经网络处理序列数据"
]

# 文本分词
jieba_cut = lambda x: ' '.join(jieba.lcut(x))
cut_texts = [jieba_cut(text) for text in texts]

# 创建Tokenizer
tokenizer = Tokenizer()
tokenizer.fit_on_texts(cut_texts)

# 转换为序列
sequences = tokenizer.texts_to_sequences(cut_texts)

# 填充序列使其等长
max_length = max(len(seq) for seq in sequences)
padded_sequences = pad_sequences(sequences, maxlen=max_length, padding='post')

# 词汇表大小
vocab_size = len(tokenizer.word_index) + 1

# 创建包含Embedding层的简单模型
model = Sequential([
    Embedding(input_dim=vocab_size,
              output_dim=50,  # 嵌入维度
              input_length=max_length),
    Flatten(),
    Dense(1, activation='sigmoid')
])

model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
print("模型结构:")
model.summary()

# 单独使用Embedding层
embedding_layer = Embedding(input_dim=vocab_size, output_dim=50)
embedded_sequences = embedding_layer(padded_sequences)
print("嵌入后的序列形状:", embedded_sequences.shape)

# 使用预训练的词向量
def create_embedding_matrix(word_index, embedding_model, embedding_dim):
    """创建预训练词向量的嵌入矩阵"""
    embedding_matrix = np.zeros((len(word_index) + 1, embedding_dim))

    for word, i in word_index.items():
        if word in embedding_model.wv:
            embedding_matrix[i] = embedding_model.wv[word]
        else:
            # 未登录词使用随机初始化
            embedding_matrix[i] = np.random.normal(size=(embedding_dim,))

    return embedding_matrix

# 使用之前训练的Word2Vec模型创建嵌入矩阵
embedding_dim = 100
embedding_matrix = create_embedding_matrix(tokenizer.word_index, model_cbow, embedding_dim)

# 创建使用预训练词向量的Embedding层
pretrained_embedding_layer = Embedding(
    input_dim=vocab_size,
    output_dim=embedding_dim,
    weights=[embedding_matrix],
    input_length=max_length,
    trainable=False  # 是否微调词向量
)

词嵌入的优点

  • 动态编码:同一个词在不同上下文中有不同的向量表示
  • 维度较低:相比One-hot,嵌入向量维度大大降低
  • 语义相似:语义相似的词在向量空间中距离较近

5. 文本特征处理

5.1 N-gram特征

N-gram是将连续的N个项目(词或字符)组合在一起的特征。

from sklearn.feature_extraction.text import CountVectorizer import jieba  def generate_ngrams(text, n=2):  """手动生成n-gram"""  words = jieba.lcut(text)  ngrams = []  for i in range(len(words) - n + 1):  ngram = ''.join(words[i:i+n])  ngrams.append(ngram)  return ngrams  text = "自然语言处理是人工智能的重要分支" print("1-gram (unigram):", generate_ngrams(text, 1)) print("2-gram (bigram):", generate_ngrams(text, 2)) print("3-gram (trigram):", generate_ngrams(text, 3))  # 使用sklearn生成n-gram特征 documents = [  "自然语言处理是人工智能分支",  "深度学习用于自然语言处理",  "人工智能改变世界" ]  # 分词 jieba_cut = lambda x: ' '.join(jieba.lcut(x)) cut_documents = [jieba_cut(doc) for doc in documents]  # 不同n-gram范围的向量化 for ngram_range in [(1, 1), (1, 2), (2, 2)]:  vectorizer = CountVectorizer(ngram_range=ngram_range)  X = vectorizer.fit_transform(cut_documents)  print(f"\n{ngram_range}特征数量:", len(vectorizer.get_feature_names_out()))  print("部分特征:", list(vectorizer.get_feature_names_out())[:10]) from sklearn.feature_extraction.text import CountVectorizer
import jieba

def generate_ngrams(text, n=2):
    """手动生成n-gram"""
    words = jieba.lcut(text)
    ngrams = []
    for i in range(len(words) - n + 1):
        ngram = ''.join(words[i:i+n])
        ngrams.append(ngram)
    return ngrams

text = "自然语言处理是人工智能的重要分支"
print("1-gram (unigram):", generate_ngrams(text, 1))
print("2-gram (bigram):", generate_ngrams(text, 2))
print("3-gram (trigram):", generate_ngrams(text, 3))

# 使用sklearn生成n-gram特征
documents = [
    "自然语言处理是人工智能分支",
    "深度学习用于自然语言处理",
    "人工智能改变世界"
]

# 分词
jieba_cut = lambda x: ' '.join(jieba.lcut(x))
cut_documents = [jieba_cut(doc) for doc in documents]

# 不同n-gram范围的向量化
for ngram_range in [(1, 1), (1, 2), (2, 2)]:
    vectorizer = CountVectorizer(ngram_range=ngram_range)
    X = vectorizer.fit_transform(cut_documents)
    print(f"\n{ngram_range}特征数量:", len(vectorizer.get_feature_names_out()))
    print("部分特征:", list(vectorizer.get_feature_names_out())[:10])

N-gram的作用

  • 增加上下文信息:捕获词序和局部依赖关系
  • 处理数据稀疏性:在某些情况下比单个词更有效
  • 语言建模:用于预测下一个词的概率

5.2 句子长度规范

from tensorflow.keras.preprocessing.sequence import pad_sequences
import numpy as np

def pad_or_truncate_sequences(sequences, max_length, padding='post', truncating='post'):
    """
    填充或截断序列
    sequences: 序列列表
    max_length: 最大长度
    padding: 'pre'或'post',填充位置
    truncating: 'pre'或'post',截断位置
    """
    return pad_sequences(sequences, maxlen=max_length,
                        padding=padding, truncating=truncating)

# 示例
sequences = [
    [1, 2, 3, 4, 5],
    [6, 7, 8],
    [9, 10, 11, 12, 13, 14, 15]
]

# 填充到相同长度
padded_sequences = pad_or_truncate_sequences(sequences, max_length=6)
print("填充后的序列:\n", padded_sequences)

# 使用自定义填充值
padded_custom = pad_sequences(sequences, maxlen=6, value=0, padding='post')
print("\n使用0填充:\n", padded_custom)

# 动态确定最大长度(基于百分位数)
def get_optimal_max_length(sequences, percentile=95):
    """基于百分位数确定最大长度"""
    lengths = [len(seq) for seq in sequences]
    return int(np.percentile(lengths, percentile))

optimal_max_length = get_optimal_max_length(sequences, percentile=90)
print(f"\n基于90%百分位数的最优最大长度: {optimal_max_length}")

5.3 文本统计特征

import numpy as np
import jieba
from collections import Counter

def extract_text_statistics(text):
    """提取文本统计特征"""
    words = jieba.lcut(text)

    features = {
        'char_count': len(text),  # 字符数
        'word_count': len(words),  # 词数
        'avg_word_length': np.mean([len(word) for word in words]) if words else 0,  # 平均词长
        'unique_word_count': len(set(words)),  # 唯一词数
        'lexical_diversity': len(set(words)) / len(words) if words else 0,  # 词汇多样性
        'sentence_count': len([s for s in text.split('。') if s.strip()]),  # 句子数
        'punctuation_count': sum(1 for char in text if char in ',。!?、;:'),  # 标点符号数
        'digit_count': sum(1 for char in text if char.isdigit()),  # 数字字符数
        'chinese_char_count': sum(1 for char in text if '\u4e00' <= char <= '\u9fff'),  # 中文字符数
    }

    # 词频统计
    word_freq = Counter(words)
    features['most_common_words'] = word_freq.most_common(5)

    return features

# 示例
text = "自然语言处理是人工智能的重要分支,深度学习在其中扮演关键角色。2023年,NLP技术取得了显著进展!"
stats = extract_text_statistics(text)

print("文本统计特征:")
for key, value in stats.items():
    if key != 'most_common_words':
        print(f"{key}: {value}")
    else:
        print(f"最常见的词: {value}")

6. 完整预处理流程示例

import jieba
import jieba.posseg as pseg
import re
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences

class TextPreprocessor:
    """文本预处理类"""

    def __init__(self, stopwords_path=None):
        self.stopwords = set()
        if stopwords_path:
            self.load_stopwords(stopwords_path)

        # 常见正则表达式模式
        self.patterns = {
            'html': re.compile(r'<[^>]+>'),
            'url': re.compile(r'http[s]?://\S+'),
            'email': re.compile(r'\S+@\S+'),
            'number': re.compile(r'\d+'),
            'special_char': re.compile(r'[^\u4e00-\u9fa5a-zA-Z0-9\s]')
        }

    def load_stopwords(self, filepath):
        """加载停用词"""
        with open(filepath, 'r', encoding='utf-8') as f:
            self.stopwords = set([line.strip() for line in f])

    def clean_text(self, text, remove_html=True, remove_urls=True,
                  remove_numbers=False, lowercase=True):
        """文本清洗"""
        if remove_html:
            text = self.patterns['html'].sub('', text)
        if remove_urls:
            text = self.patterns['url'].sub('', text)

        text = self.patterns['email'].sub('', text)

        if remove_numbers:
            text = self.patterns['number'].sub('', text)

        # 去除特殊字符(保留中文标点)
        text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9\s,。!?、;:""''()]', '', text)

        if lowercase:
            text = text.lower()

        # 去除多余空格
        text = re.sub(r'\s+', ' ', text).strip()

        return text

    def tokenize(self, text, use_stopwords=True):
        """分词"""
        words = jieba.lcut(text)

        if use_stopwords and self.stopwords:
            words = [word for word in words if word not in self.stopwords]

        return words

    def extract_features(self, texts, method='tfidf', max_features=10000):
        """特征提取"""
        # 分词
        tokenized_texts = [' '.join(self.tokenize(text)) for text in texts]

        if method == 'tfidf':
            vectorizer = TfidfVectorizer(max_features=max_features)
            features = vectorizer.fit_transform(tokenized_texts)
            return features, vectorizer
        elif method == 'count':
            from sklearn.feature_extraction.text import CountVectorizer
            vectorizer = CountVectorizer(max_features=max_features)
            features = vectorizer.fit_transform(tokenized_texts)
            return features, vectorizer
        else:
            raise ValueError("method must be 'tfidf' or 'count'")

    def prepare_for_deep_learning(self, texts, max_words=10000,
                                 max_sequence_length=100):
        """为深度学习模型准备数据"""
        # 分词
        tokenized_texts = [' '.join(self.tokenize(text)) for text in texts]

        # 创建Tokenizer
        tokenizer = Tokenizer(num_words=max_words)
        tokenizer.fit_on_texts(tokenized_texts)

        # 转换为序列
        sequences = tokenizer.texts_to_sequences(tokenized_texts)

        # 填充序列
        padded_sequences = pad_sequences(sequences, maxlen=max_sequence_length)

        return padded_sequences, tokenizer

# 使用示例
if __name__ == "__main__":
    # 示例数据
    texts = [
        "自然语言处理是人工智能的重要分支,深度学习在其中扮演关键角色。",
        "2023年,NLP技术取得了显著进展,特别是大语言模型的发展。",
        "文本预处理是NLP任务的基础,包括分词、清洗、向量化等步骤。",
        "机器学习算法需要数值型输入,因此文本必须转换为向量表示。"
    ]

    labels = [1, 1, 0, 0]  # 示例标签

    # 初始化预处理器
    preprocessor = TextPreprocessor()

    # 清洗文本
    cleaned_texts = [preprocessor.clean_text(text) for text in texts]
    print("清洗后的文本:")
    for text in cleaned_texts:
        print(f"  {text}")

    # 提取TF-IDF特征
    features, vectorizer = preprocessor.extract_features(cleaned_texts, method='tfidf')
    print(f"\nTF-IDF特征矩阵形状: {features.shape}")

    # 准备深度学习数据
    X, tokenizer = preprocessor.prepare_for_deep_learning(cleaned_texts,
                                                         max_sequence_length=20)
    print(f"\n深度学习输入形状: {X.shape}")
    print(f"词汇表大小: {len(tokenizer.word_index)}")

    # 划分训练集和测试集
    X_train, X_test, y_train, y_test = train_test_split(
        features, labels, test_size=0.2, random_state=42
    )
    print(f"\n训练集大小: {X_train.shape[0]}, 测试集大小: {X_test.shape[0]}")

7. 总结与最佳实践

7.1 预处理流程总结

  1. 1.数据收集:获取原始文本数据
  2. 2.文本清洗:去除噪声(HTML、URL、特殊字符等)
  3. 3.分词:将文本切分为词汇单元
  4. 4.标准化:大小写转换、词形还原、词干提取
  5. 5.去停用词:移除常见但无信息量的词
  6. 6.特征提取:将文本转换为数值表示
    • 传统方法:词袋模型、TF-IDF
    • 深度学习方法:词嵌入(Word2Vec、GloVe、BERT等)
  7. 7.特征选择:选择最相关的特征
  8. 8.数据划分:划分训练集、验证集和测试集

7.2 最佳实践建议

根据任务选择预处理策略

  • 文本分类:通常需要去停用词、使用TF-IDF或词嵌入
  • 情感分析:保留标点符号和表情符号可能有用
  • 机器翻译:需要更精细的分词和子词处理
  • 命名实体识别:保留大小写信息和标点符号

常见陷阱及解决方案

  1. 1.过度清洗:可能移除重要信息
    • 解决方案:根据任务调整清洗强度
  2. 2.分词错误:专业术语或新词识别不准确
    • 解决方案:使用自定义词典或更先进的分词工具
  3. 3.维度灾难:特征空间过大
    • 解决方案:使用特征选择、降维或深度学习
  4. 4.数据不平衡:某些类别样本过少
    • 解决方案:过采样、欠采样或使用加权损失函数

性能优化技巧

  • 使用多进程处理大规模数据
  • 缓存中间结果避免重复计算
  • 使用增量学习处理流式数据
  • 选择合适的算法和数据结构

7.3 推荐工具库

  • 分词:jieba、pkuseg、HanLP、LAC
  • 文本清洗:re、BeautifulSoup、NLTK
  • 特征提取:scikit-learn、gensim
  • 深度学习:TensorFlow、PyTorch、Hugging Face Transformers
  • 中文处理:中文NLP工具包(如thulac、nlpcda)

7.4 进阶学习方向

  1. 1.预训练语言模型:BERT、GPT、RoBERTa等
  2. 2.文本增强:同义词替换、随机插入、回译等
  3. 3.多语言处理:跨语言词向量、多语言BERT
  4. 4.领域自适应:在特定领域微调预训练模型
  5. 5.可解释性:理解模型决策过程

注意:本文档中的代码示例使用Python 3.7+编写,需要安装以下依赖:

pip install jieba scikit-learn tensorflow gensim beautifulsoup4 numpy 

对于更复杂的NLP任务,建议使用预训练模型和大型语料库进行训练。预处理步骤应根据具体任务和数据进行调整优化。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐