# Python实战:用jieba和nltk玩转Bigram中文分词(附完整代码)
如果你正在处理中文文本,无论是做情感分析、构建聊天机器人,还是训练一个语言模型,分词都是绕不开的第一步。但中文不像英文有天然的空格分隔,一个句子就是一串连续的字符,如何准确地将其切割成有意义的词语,直接决定了后续任务的效果。传统的基于词典的分词方法在面对新词、网络用语或专业术语时常常力不从心,而基于统计的N-gram模型,特别是Bigram(二元模型),为我们提供了一种更灵活、更“智能”的视角。
Bigram的核心思想很简单:它认为文本中相邻的两个词(或字)之间存在某种“粘性”。通过统计海量语料中所有可能的二字组合出现的频率,模型就能学习到哪些组合更可能构成一个词或一个稳定的短语。比如,“自然”和“语言”经常一起出现,那么“自然语言”作为一个整体被识别出来的概率就很高。这种方法不依赖一个固定的词典,而是让数据自己说话,因此对新词和特定领域的术语有更好的适应性。
今天,我们就抛开复杂的理论推导,直接上手实战。我将带你用Python中两个强大的库——`jieba`和`nltk`,从零开始构建一个完整的中文Bigram分词流程。我们会从最基础的文本预处理开始,一步步实现Bigram的提取、特征工程,并最终将其应用到情感分析和文本分类的实例中。整个过程不仅有清晰的代码示例,还会穿插我实际项目中踩过的坑和总结的技巧,希望能帮你把Bigram从概念真正变成工具箱里趁手的武器。
## 1. 环境准备与核心库解析
在开始写代码之前,确保你的Python环境已经就绪。我推荐使用Python 3.8或以上版本,以获得更好的库兼容性和性能。我们将主要依赖两个库:`jieba`用于基础的中文分词,`nltk`则提供了丰富的自然语言处理工具,包括我们需要的N-gram功能。
首先,通过pip安装必要的库:
```bash
pip install jieba nltk scikit-learn pandas
```
安装完成后,我们还需要下载`nltk`的一些数据包。打开Python解释器或创建一个脚本,执行以下代码:
```python
import nltk
nltk.download('punkt') # 用于分词的数据包
nltk.download('averaged_perceptron_tagger') # 可选,用于词性标注
```
现在,让我们快速了解一下这两个库在本次任务中的角色。
**jieba**:这是一个“工业级”的中文分词工具,速度快,精度高,并且支持多种分词模式(精确模式、全模式、搜索引擎模式)。在我们的流程中,`jieba`扮演着“初切”的角色。虽然我们的目标是基于Bigram进行更灵活的分词或特征提取,但`jieba`的精确模式可以为我们提供一个高质量的基线分词结果,作为Bigram分析的输入。它内置的词典能很好地处理常见词汇,减少后续统计模型的负担。
**nltk (Natural Language Toolkit)**:这是自然语言处理领域的瑞士军刀。我们主要使用它的`ngrams`函数来轻松生成Bigram序列。例如,给定一个分词后的词列表`['我', '爱', '自然语言处理']`,`nltk`可以一键生成`[('我', '爱'), ('爱', '自然语言处理')]`。除此之外,`nltk`还提供了丰富的语料库、词干提取、词性标注等功能,虽然本文不深入涉及,但知道它的潜力对后续扩展很有帮助。
一个常见的误解是,`jieba`和基于统计的Bigram方法是互斥的。实际上,它们可以很好地协同工作。`jieba`的词典和规则保证了基本分词的准确性,而Bigram模型则可以在`jieba`的结果之上,进一步捕捉更细微的搭配关系和上下文信息,或者用于处理`jieba`词典未覆盖的新词。这种“规则+统计”的组合策略,在实践中往往能取得最佳效果。
## 2. 从文本到Bigram:完整的数据处理流水线
有了工具,我们开始构建数据处理流水线。我们的目标是将一段原始的中文文本,转换成一个Bigram特征列表或矩阵,供机器学习模型使用。这个过程可以分为三个核心步骤:文本预处理、基础分词和Bigram生成。
### 2.1 文本清洗与预处理
原始文本通常包含很多“噪音”,比如HTML标签、特殊符号、多余的空格和换行符。这些噪音会影响分词质量和后续的统计。因此,第一步是清洗。
```python
import re
import jieba
def clean_text(text):
"""
清洗中文文本。
1. 移除HTML标签。
2. 移除URL链接。
3. 移除邮箱地址。
4. 移除多余的空格、制表符和换行符,只保留一个空格。
5. 移除所有非中文字符、数字和常见中文标点(可选,根据任务决定)。
"""
# 移除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'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', '', text)
# 将多个空白字符(空格、换行、制表符)替换为单个空格
text = re.sub(r'\s+', ' ', text).strip()
# 可选:移除所有非中文、数字、常见中文标点和基本英文标点(用于保留中英文混合文本中的英文)
# 如果只想保留纯中文,可以使用更严格的正则表达式
# text = re.sub(r'[^\u4e00-\u9fa5,。!?;:“”‘’、\d\s]', '', text)
return text
# 示例
raw_text = "这是一个示例文本,包含http://example.com链接和<strong>HTML标签</strong>。 还有多余的空格。"
cleaned_text = clean_text(raw_text)
print(f"清洗前: {raw_text}")
print(f"清洗后: {cleaned_text}")
```
> **注意**:关于是否移除所有非中文字符,需要根据你的任务来决定。如果你的语料是中英文混合的(比如技术文档),保留英文单词可能更有价值。这时,清洗策略需要调整,可能只移除特殊符号而保留字母数字。
### 2.2 使用jieba进行基础分词
清洗后的文本,我们使用`jieba`进行初步分词。`jieba.cut`函数返回一个生成器,`jieba.lcut`则直接返回列表,后者更方便。
```python
def tokenize_with_jieba(text, use_paddle=False, cut_all=False):
"""
使用jieba进行中文分词。
:param text: 待分词的字符串
:param use_paddle: 是否使用paddle模式,需要安装paddlepaddle-tiny,对未登录词识别更好
:param cut_all: 是否采用全模式。精确模式(False) vs 全模式(True)
:return: 分词后的词语列表
"""
# 如果启用paddle模式,需要先启用(仅第一次需要)
if use_paddle:
try:
jieba.enable_paddle()
except Exception as e:
print(f"启用paddle模式失败,将使用默认模式。错误信息: {e}")
use_paddle = False
if cut_all:
# 全模式:扫描出所有可能成词的词语,速度快,但歧义多
seg_list = jieba.lcut(text, cut_all=True)
else:
# 精确模式(默认):试图最精确地切分,适合文本分析
seg_list = jieba.lcut(text, cut_all=False, HMM=True) # HMM模型用于识别未登录词
# 通常我们会过滤掉停用词和长度过短的词(如单个标点)
# 这里我们先返回原始分词结果,停用词过滤放在后续步骤
return seg_list
# 示例
sample_text = "自然语言处理是一门融合了计算机科学和人工智能的交叉学科。"
tokens = tokenize_with_jieba(sample_text)
print(f"分词结果: {tokens}")
# 输出: ['自然语言', '处理', '是', '一门', '融合', '了', '计算机科学', '和', '人工智能', '的', '交叉学科', '。']
```
可以看到,`jieba`成功地将“自然语言”和“计算机科学”识别为完整的词语,这得益于其内置的词典。
### 2.3 生成与统计Bigram
现在,我们有了一个干净的词语列表。接下来就是使用`nltk`从中提取Bigram。
```python
from nltk.util import ngrams
from collections import Counter
def generate_bigrams(token_list):
"""
从分词列表中生成Bigram序列。
:param token_list: 分词后的词语列表
:return: Bigram元组列表,如 [('自然语言', '处理'), ('处理', '是'), ...]
"""
# 使用nltk的ngrams函数,n=2即为Bigram
bigram_list = list(ngrams(token_list, 2))
return bigram_list
def get_top_bigrams(bigram_list, top_n=10):
"""
统计Bigram的频率并返回最常见的N个。
:param bigram_list: Bigram元组列表
:param top_n: 需要返回的最高频Bigram数量
:return: 最常见Bigram及其计数的列表
"""
bigram_freq = Counter(bigram_list)
return bigram_freq.most_common(top_n)
# 组合示例
tokens = ['自然语言', '处理', '是', '一门', '融合', '了', '计算机科学', '和', '人工智能', '的', '交叉学科', '。']
bigrams = generate_bigrams(tokens)
print(f"生成的Bigram序列: {bigrams}")
top_10 = get_top_bigrams(bigrams, top_n=5)
print("\n出现频率最高的5个Bigram:")
for bigram, count in top_10:
print(f" {bigram[0]} {bigram[1]}: {count}次")
```
然而,上面的例子只统计了一句话,频率信息没有意义。让我们用一个更实际的例子——读入一个文本文件,进行完整的处理。
```python
def process_text_file(file_path):
"""
从文本文件读取内容,完成清洗、分词、Bigram生成和统计的全流程。
"""
with open(file_path, 'r', encoding='utf-8') as f:
raw_text = f.read()
# 1. 清洗
cleaned_text = clean_text(raw_text)
# 2. 分词
tokens = tokenize_with_jieba(cleaned_text)
# 3. 生成Bigram
bigrams = generate_bigrams(tokens)
# 4. 统计
bigram_freq = Counter(bigrams)
return tokens, bigrams, bigram_freq
# 假设我们有一个名为'sample_corpus.txt'的语料文件
# tokens, bigrams, freq = process_text_file('sample_corpus.txt')
# print(f"总共生成 {len(bigrams)} 个Bigram。")
# print(f"共有 {len(freq)} 个不同的Bigram。")
```
通过这个流水线,我们成功地将原始文本转化为了结构化的Bigram数据。这些数据可以直接用于分析文本的搭配模式,也可以作为特征输入到机器学习模型中。
## 3. 特征工程:将Bigram转化为模型可用的特征
生成了Bigram序列后,我们需要将其转化为机器学习或深度学习模型能够理解的数值特征。最常用且直接的方法是使用**词袋模型(Bag of Words)**的扩展——**N-gram词袋模型**。`scikit-learn`库中的`CountVectorizer`和`TfidfVectorizer`可以完美地支持这一点。
### 3.1 使用CountVectorizer构建Bigram特征矩阵
`CountVectorizer`可以将文本集合转换为词频矩阵。通过设置`ngram_range=(2,2)`,我们可以让它只考虑Bigram。
```python
from sklearn.feature_extraction.text import CountVectorizer
import pandas as pd
# 示例文档集合
documents = [
"自然语言处理技术发展迅速。",
"机器学习是人工智能的核心领域。",
"深度学习推动了自然语言处理的进步。",
"人工智能和机器学习息息相关。"
]
# 关键步骤:自定义分词器,使其先使用jieba分词
def jieba_tokenizer(text):
return tokenize_with_jieba(text) # 使用我们之前定义的分词函数
# 创建CountVectorizer实例,指定使用Bigram和自定义分词器
bigram_vectorizer = CountVectorizer(
tokenizer=jieba_tokenizer, # 使用jieba进行分词
ngram_range=(2, 2), # 只提取二元语法(Bigram)
min_df=1, # 忽略在所有文档中出现次数小于1的Bigram
max_features=1000 # 只保留最常见的1000个Bigram特征
)
# 拟合模型并转换文档
X_bigram_counts = bigram_vectorizer.fit_transform(documents)
# 查看特征名称(即有哪些Bigram)
feature_names = bigram_vectorizer.get_feature_names_out()
print("Bigram特征名称(前20个):")
print(feature_names[:20])
# 将稀疏矩阵转换为易于查看的DataFrame
df_bigram_counts = pd.DataFrame(
X_bigram_counts.toarray(),
columns=feature_names
)
print("\nBigram词频矩阵:")
print(df_bigram_counts)
```
这段代码会输出一个矩阵,其中行代表文档,列代表不同的Bigram,单元格的值是该Bigram在对应文档中出现的次数。例如,你可能会看到`('自然语言', '处理')`、`('机器', '学习')`这样的特征。
### 3.2 使用TfidfVectorizer获取加权特征
单纯的词频(Count)可能会受到常见但无实际区分意义的Bigram(如“的 是”、“了 的”)干扰。TF-IDF(词频-逆文档频率)是一种加权方案,可以降低常见词的权重,提高稀有且重要词的权重。
```python
from sklearn.feature_extraction.text import TfidfVectorizer
# 创建TfidfVectorizer实例
bigram_tfidf_vectorizer = TfidfVectorizer(
tokenizer=jieba_tokenizer,
ngram_range=(2, 2),
min_df=2, # 忽略在少于2个文档中出现的Bigram,进一步过滤噪音
max_df=0.8 # 忽略在超过80%的文档中出现的Bigram(如停用词组合)
)
X_bigram_tfidf = bigram_tfidf_vectorizer.fit_transform(documents)
feature_names_tfidf = bigram_tfidf_vectorizer.get_feature_names_out()
print("TF-IDF加权后的Bigram特征(示例):")
# 查看第一个文档的TF-IDF权重最高的几个Bigram
first_doc_vector = X_bigram_tfidf[0]
sorted_indices = first_doc_vector.toarray().argsort()[0][::-1] # 降序排列
for idx in sorted_indices[:5]:
if first_doc_vector[0, idx] > 0:
print(f" {feature_names_tfidf[idx]}: {first_doc_vector[0, idx]:.4f}")
```
TF-IDF矩阵更适合作为分类、聚类等任务的输入,因为它能更好地表征文档的独特内容。
### 3.3 处理停用词与特征过滤
在Bigram层面,停用词处理变得稍微复杂。我们不仅要过滤掉“的”、“了”、“是”这样的单个停用词,还要过滤掉由停用词组成的Bigram(如“的 是”、“了 的”),因为它们通常不携带有效信息。
我们可以定义一个两阶段的过滤策略:
```python
def build_bigram_vectorizer_with_stopwords(stop_words_path=None):
"""
构建一个集成了停用词过滤的Bigram向量化器。
"""
# 加载停用词表
stop_words = set()
if stop_words_path:
with open(stop_words_path, 'r', encoding='utf-8') as f:
for line in f:
stop_words.add(line.strip())
else:
# 也可以使用一个基础的停用词列表
stop_words = {'的', '了', '是', '在', '和', '与', '及', '等', '我', '你', '他', '她', '它'}
def custom_tokenizer(text):
# 1. 使用jieba分词
tokens = tokenize_with_jieba(text)
# 2. 过滤单个停用词
filtered_tokens = [token for token in tokens if token not in stop_words and len(token.strip()) > 0]
return filtered_tokens
vectorizer = TfidfVectorizer(
tokenizer=custom_tokenizer,
ngram_range=(2, 2),
min_df=2,
max_df=0.9,
token_pattern=None # 使用自定义分词器时需设为None
)
return vectorizer
# 使用示例
# vectorizer = build_bigram_vectorizer_with_stopwords('chinese_stopwords.txt')
# X = vectorizer.fit_transform(documents)
```
通过这种方式,我们构建了一个更干净、更有信息量的Bigram特征空间,为下游的机器学习任务打下了坚实的基础。
## 4. 实战应用:情感分析与文本分类案例
理论讲得再多,不如看一个实际例子。假设我们有一个电商评论数据集,包含“好评”和“差评”两类。我们的任务是训练一个分类器,自动判断新评论的情感倾向。我们将对比仅使用Unigram(单个词)和加入Bigram特征后,模型性能的提升。
### 4.1 数据准备与特征提取
首先,我们模拟一个小的数据集,并提取Unigram和Bigram特征。
```python
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
# 模拟数据:中文电商评论
reviews = [
"手机质量非常好,运行流畅,电池耐用,拍照清晰。",
"物流速度太慢了,等了一个星期才到货,包装还破损了。",
"客服态度很差,问什么都不耐烦,解决问题效率低。",
"产品与描述完全一致,性价比超高,非常满意的一次购物。",
"屏幕有坏点,申请换货流程复杂,体验很差。",
"音质效果出乎意料的好,低音沉稳,高音清澈,物超所值。",
"买了不到一周就降价了,心里很不平衡,价格保护形同虚设。",
"设计时尚,手感舒适,系统界面美观,操作简单。",
]
labels = [1, 0, 0, 1, 0, 1, 0, 1] # 1代表好评,0代表差评
# 分割训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(reviews, labels, test_size=0.25, random_state=42)
# 定义特征提取函数
def extract_features(texts, ngram_range=(1,1)):
"""提取TF-IDF特征"""
vectorizer = TfidfVectorizer(
tokenizer=jieba_tokenizer,
ngram_range=ngram_range,
min_df=1,
max_df=1.0
)
features = vectorizer.fit_transform(texts)
return features, vectorizer
# 提取Unigram特征
X_train_uni, uni_vectorizer = extract_features(X_train, ngram_range=(1,1))
X_test_uni = uni_vectorizer.transform(X_test)
# 提取Unigram+Bigram特征
X_train_bi, bi_vectorizer = extract_features(X_train, ngram_range=(1,2)) # (1,2)表示同时包含Unigram和Bigram
X_test_bi = bi_vectorizer.transform(X_test)
print(f"Unigram特征维度: {X_train_uni.shape[1]}")
print(f"Unigram+Bigram特征维度: {X_train_bi.shape[1]}")
```
可以看到,加入Bigram后,特征空间显著扩大,因为包含了词与词之间的组合信息。
### 4.2 模型训练与对比
我们使用一个简单的逻辑回归模型进行训练和对比。
```python
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report
# 训练Unigram模型
model_uni = LogisticRegression(max_iter=1000, random_state=42)
model_uni.fit(X_train_uni, y_train)
y_pred_uni = model_uni.predict(X_test_uni)
acc_uni = accuracy_score(y_test, y_pred_uni)
print(f"仅使用Unigram的测试集准确率: {acc_uni:.4f}")
# 训练Unigram+Bigram模型
model_bi = LogisticRegression(max_iter=1000, random_state=42)
model_bi.fit(X_train_bi, y_train)
y_pred_bi = model_bi.predict(X_test_bi)
acc_bi = accuracy_score(y_test, y_pred_bi)
print(f"使用Unigram+Bigram的测试集准确率: {acc_bi:.4f}")
# 查看分类报告
print("\nUnigram+Bigram模型分类报告:")
print(classification_report(y_test, y_pred_bi, target_names=['差评', '好评']))
```
在小样本上,准确率可能波动,但通常引入Bigram后,模型能捕捉到像“物流 速度”、“客服 态度”、“价格 保护”这样的负面组合,以及“运行 流畅”、“性价比 超高”这样的正面组合,从而做出更准确的判断。
### 4.3 特征重要性分析
逻辑回归模型的系数可以帮助我们理解哪些特征(词或Bigram)对判断“好评”或“差评”贡献最大。
```python
def plot_top_features(vectorizer, model, class_index=1, n=10):
"""打印对指定类别预测最重要的N个特征"""
feature_names = vectorizer.get_feature_names_out()
# 获取对应类别的系数(对于二分类,通常看class_index=1的系数)
if len(model.coef_.shape) > 1:
coefficients = model.coef_[class_index]
else:
coefficients = model.coef_
# 将特征名和系数配对,并按系数绝对值排序
coef_df = pd.DataFrame({
'feature': feature_names,
'coefficient': coefficients
})
# 对“好评”贡献大的特征(系数正且大)
top_positive = coef_df.nlargest(n, 'coefficient')
# 对“差评”贡献大的特征(系数负且小)
top_negative = coef_df.nsmallest(n, 'coefficient')
print(f"对预测为 **好评** 最重要的 {n} 个特征:")
for _, row in top_positive.iterrows():
print(f" {row['feature']}: {row['coefficient']:.4f}")
print(f"\n对预测为 **差评** 最重要的 {n} 个特征:")
for _, row in top_negative.iterrows():
print(f" {row['feature']}: {row['coefficient']:.4f}")
print("=== Unigram+Bigram模型特征重要性分析 ===")
plot_top_features(bi_vectorizer, model_bi, n=8)
```
运行这段代码,你可能会发现一些有趣的Bigram,比如“**物流 速度**”有很强的负权重(指向差评),而“**非常 满意**”有很强的正权重(指向好评)。这种可解释性是单纯使用深度学习黑盒模型难以获得的。
## 5. 进阶技巧与性能优化
在实际项目中,直接将所有Bigram都作为特征可能会面临**维度爆炸**和**数据稀疏**的问题。想象一下,如果有1万个不同的词,理论上就可能产生近1亿个不同的Bigram。我们需要一些策略来优化。
### 5.1 特征选择与降维
* **基于频率的过滤**:这是最直接的方法,使用`CountVectorizer`或`TfidfVectorizer`的`min_df`和`max_df`参数。`min_df`(例如`min_df=5`)可以过滤掉在极少文档中出现的稀有Bigram,它们可能是噪音或特异性过强。`max_df`(例如`max_df=0.8`)可以过滤掉在绝大多数文档中都出现的常见Bigram(如“的 是”),它们缺乏区分度。
* **使用卡方检验(Chi-Square)选择特征**:`scikit-learn`的`SelectKBest`可以配合`chi2`统计量,选择与目标类别最相关的K个特征。
```python
from sklearn.feature_selection import SelectKBest, chi2
# 假设 X_train_tfidf 是原始的TF-IDF特征矩阵,y_train是标签
selector = SelectKBest(chi2, k=5000) # 选择5000个最好的特征
X_train_selected = selector.fit_transform(X_train_tfidf, y_train)
X_test_selected = selector.transform(X_test_tfidf)
# 然后在这个降维后的特征上训练模型
```
### 5.2 处理大规模文本的流式处理
当处理GB级别的大型语料时,无法一次性将全部数据读入内存。这时可以使用`HashingVectorizer`,它使用哈希函数将词或N-gram映射到固定维度的特征空间,无需在内存中存储词汇表,实现了流式处理。
```python
from sklearn.feature_extraction.text import HashingVectorizer
# 使用HashingVectorizer处理Bigram
hash_vectorizer = HashingVectorizer(
tokenizer=jieba_tokenizer,
ngram_range=(2, 2),
n_features=2**18, # 特征维度,例如2^18=262144
alternate_sign=False # 为了兼容非负模型如朴素贝叶斯
)
# 可以分批拟合和转换数据
# for batch in batch_generator:
# X_batch = hash_vectorizer.transform(batch)
# # ... 处理X_batch
```
### 5.3 与深度学习模型结合
在深度学习时代,Bigram的思想依然有价值。虽然像BERT、GPT这类模型使用更复杂的子词分词(如WordPiece、BPE),但在模型输入层或特定任务中,显式地加入Bigram特征可以作为补充。
一种简单的做法是,在将文本输入神经网络之前,除了词嵌入(Word Embedding)外,额外拼接一个Bigram特征的向量。这个Bigram特征向量可以是通过TF-IDF计算得到的稀疏向量,也可以是通过另一个嵌入层学习的稠密向量。
```python
# 伪代码,展示思路
import torch
import torch.nn as nn
class HybridModel(nn.Module):
def __init__(self, vocab_size, bigram_feature_dim, hidden_dim, num_classes):
super().__init__()
self.word_embedding = nn.Embedding(vocab_size, 128)
# 假设我们有一个预计算好的Bigram特征提取器,输出维度为bigram_feature_dim
# 或者,我们也可以用一个线性层来处理Bigram的one-hot表示
self.bigram_projection = nn.Linear(bigram_feature_dim, 64)
self.combined_layer = nn.Linear(128 + 64, hidden_dim)
self.classifier = nn.Linear(hidden_dim, num_classes)
def forward(self, word_ids, bigram_features):
word_vecs = self.word_embedding(word_ids).mean(dim=1) # 平均池化得到句子表示
bigram_vecs = self.bigram_projection(bigram_features)
combined = torch.cat([word_vecs, bigram_vecs], dim=1)
output = self.classifier(self.combined_layer(combined))
return output
```
这种混合模型在小规模数据或特定领域任务上,有时能获得比纯深度学习模型更稳定或更具解释性的结果。
在我最近做的一个新闻标题分类项目中,原始数据中网络新词和特定领域术语很多。单独使用预训练BERT模型效果一般,F1值在0.82左右徘徊。后来,我尝试在BERT的`[CLS]` token的输出向量后面,拼接了由TF-IDF计算的Top-500 Bigram特征。就是这个简单的操作,让模型F1值提升了近3个百分点。事后分析发现,一些领域特有的固定搭配(如“量化 宽松”、“熔断 机制”)被Bigram特征很好地捕捉到了,而这些信息在BERT的子词切分中可能被分散了。这个案例让我深刻体会到,即使在Transformer当道的今天,这些经典的语言学特征和统计方法,依然是我们工具箱里不可或缺的补充。