文本预处理在NLP流程中扮演基石角色,目标是把非结构化文本转化为机器学习和深度学习模型能够理解的数值化表示。预处理的质量直接影响后续模型的效果,因此掌握一套系统、可复用的预处理代码十分必要。本文将基于Python生态,从分词、清洗、向量化到特征工程逐步展开,并提供可直接使用的脚本片段。
一、分词处理
对于中文等没有自然分隔符的语言,分词是首要步骤。推荐使用jieba库,它支持精确模式、全模式和搜索引擎模式。精确模式适合文本分析,全模式速度快但存在歧义,搜索引擎模式在精确模式基础上对长词再次切分,适合检索场景。
- import jieba
- text = "自然语言处理是人工智能的重要分支"
- print(jieba.lcut(text)) # 精确模式
- print(jieba.lcut(text, cut_all=True)) # 全模式
- print(jieba.lcut_for_search(text)) # 搜索引擎模式
复制代码
此外,jieba内置词性标注功能,通过posseg模块可获取每个词的词性标签。常见标签包括n(名词)、v(动词)、a(形容词)、ns(地名)、nr(人名)等。
- import jieba.posseg as pseg
- words = pseg.lcut("我爱北京天安门")
- for word, flag in words:
- print(f"{word}({flag})", end=" ")
复制代码
当默认词典无法识别专业术语时,可以加载自定义词典。词典文件格式为“词语 词频 词性”,每行一个词。也可通过jieba.add_word动态添加。
- jieba.load_userdict("user_dict.txt")
- jieba.add_word('Transformer模型', freq=10, tag='n')
- print(jieba.lcut("自然语言处理和深度学习是人工智能的核心"))
复制代码
命名实体识别(NER)可利用jieba的词性标签提取人名、地名、机构名。如果要更准确,可以使用百度开源的LAC库。
- from LAC import LAC
- lac = LAC(mode='lac')
- result = lac.run("李白出生于蜀郡绵州昌隆县")
- print(result)
复制代码
停用词过滤是常见操作,通常加载一个停用词表,对分词结果进行过滤。停用词多为高频虚词,如“的、是、在”。
- stopwords = {'的', '了', '在', '是', '我', '有', '和', '就', '不', '人'}
- def remove_stopwords(words, stopwords):
- return [word for word in words if word not in stopwords]
复制代码
二、文本清洗
原始文本常包含HTML标签、URL、特殊符号、多余空格等。正则表达式是最灵活的清洗工具。
- import re
- def clean_text(text):
- text = re.sub(r'<[^>]+>', '', text) # 去除HTML标签
- text = re.sub(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', '', text) # 去除URL
- text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9\s,。!?、;:""''()]', '', text) # 保留中文标点
- text = re.sub(r'\s+', ' ', text).strip()
- return text
复制代码
数字处理有三种模式:替换为占位符、直接删除、保留。在文本分类场景中,替换为<NUM>可减少特征维度。
- def process_numbers(text, mode='replace'):
- if mode == 'replace':
- return re.sub(r'\d+', '<NUM>', text)
- elif mode == 'remove':
- return re.sub(r'\d+', '', text)
- else:
- return text
复制代码
大小写统一使用字符串的lower/upper/title方法。注意中文不受影响。
- def normalize_case(text, mode='lower'):
- if mode == 'lower':
- return text.lower()
- elif mode == 'upper':
- return text.upper()
- elif mode == 'title':
- return text.title()
复制代码
对于复杂的HTML文本,建议使用BeautifulSoup库提取纯文本。
- from bs4 import BeautifulSoup
- def remove_html_tags(text):
- soup = BeautifulSoup(text, 'html.parser')
- return soup.get_text()
复制代码
三、文本向量化
计算机无法直接处理单词,需将文本转换为数值向量。One-hot编码简单但高维稀疏,且无法表达语义关系。手动实现或使用sklearn的OneHotEncoder。
- import numpy as np
- from collections import Counter
- def one_hot_encode(texts):
- 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
复制代码
词袋模型(Bag of Words)用词频向量表示句子。使用CountVectorizer可快速构建。
- from sklearn.feature_extraction.text import CountVectorizer
- vectorizer = CountVectorizer()
- jieba_cut = lambda x: ' '.join(jieba.lcut(x))
- cut_docs = [jieba_cut(doc) for doc in documents]
- X = vectorizer.fit_transform(cut_docs)
- print(vectorizer.get_feature_names_out())
复制代码
TF-IDF通过词频和逆文档频率加权,突出重要词。sklearn的TfidfVectorizer集成该功能,并可计算文档相似度(余弦相似度)。
- from sklearn.feature_extraction.text import TfidfVectorizer
- from sklearn.metrics.pairwise import cosine_similarity
- tfidf_vectorizer = TfidfVectorizer()
- tfidf_matrix = tfidf_vectorizer.fit_transform(cut_docs)
- sim_matrix = cosine_similarity(tfidf_matrix)
复制代码
Word2Vec能学习语义向量,使用gensim库可训练CBOW或Skip-gram模型。CBOW用上下文预测中心词,训练快;Skip-gram用中心词预测上下文,对罕见词更好。
- from gensim.models import Word2Vec
- tokenized_sentences = [jieba.lcut(s) for s in sentences]
- model_cbow = Word2Vec(sentences=tokenized_sentences, vector_size=100, window=5, min_count=1, workers=4, sg=0)
- model_sg = Word2Vec(sentences=tokenized_sentences, vector_size=100, window=5, min_count=1, workers=4, sg=1)
- print(model_cbow.wv.most_similar('自然语言处理', topn=3))
复制代码
在深度学习模型中,Embedding层将词索引映射为稠密向量。可自定义训练,也可加载预训练词向量作为权重。
- from tensorflow.keras.layers import Embedding
- from tensorflow.keras.preprocessing.text import Tokenizer
- from tensorflow.keras.preprocessing.sequence import pad_sequences
- tokenizer = Tokenizer()
- tokenizer.fit_on_texts(cut_texts)
- sequences = tokenizer.texts_to_sequences(cut_texts)
- padded = pad_sequences(sequences, maxlen=max_length, padding='post')
- embedding_layer = Embedding(input_dim=vocab_size, output_dim=50, input_length=max_length)
复制代码
四、特征处理
N-gram特征考虑词序,常用bi-gram或tri-gram。sklearn的CountVectorizer可直接设置ngram_range。
- vectorizer_ngram = CountVectorizer(ngram_range=(1, 2))
- X_ngram = vectorizer_ngram.fit_transform(cut_docs)
复制代码
另外,句子长度规范、统计特征(如词数、标点数量)也可作为特征加入模型。
五、完整预处理流程示例
将上述模块组合成一个流水线函数:
- def preprocess_pipeline(text, stopwords, num_mode='replace', case_mode='lower', use_ner=False):
- text = clean_text(text)
- text = process_numbers(text, num_mode)
- text = normalize_case(text, case_mode)
- words = jieba.lcut(text)
- words = remove_stopwords(words, stopwords)
- if use_ner:
- entities = extract_entities(text)
- words = entities['person'] + entities['location'] + entities['organization']
- return words
复制代码
六、最佳实践建议
- 预处理流程应在训练和测试集上保持一致,避免数据泄露。
- 自定义词典需根据业务领域持续更新,降低未登录词比例。
- TF-IDF和Word2Vec的维度选择需实验调优,通常在100~300之间。
- 停用词表在不同任务中差异较大,建议基于数据统计生成领域停用词。
- 推荐工具库:jieba、gensim、sklearn、tensorflow、keras、LAC、BeautifulSoup。
进阶方向:预训练语言模型(BERT、GPT)的Tokenizer和子词单位处理,这在本文的基础上可进一步学习。 |