023、自然语言处理(NLP)基础:词向量与文本分类
排查一个线上问题,用户反馈某商品的自动标签系统突然开始乱打标——把“充电宝”分类成“食品”,把“羽绒服”标记为“电子产品”。查日志发现,新上线的NLP分类模块在处理某些特定商品描述时,相似度计算完全失控。根本原因?词向量模型加载时用了错误的维度配置,导致“充电”和“充电宝”在向量空间里距离比“充电宝”和“移动电源”还远。今天我们就从这个问题切入,聊聊NLP里最基础也最易踩坑的两个概念:词向量和文本分类。
词向量:文字的数字替身
早年做文本处理,最直接的方法就是one-hot编码。每个词对应一个超长向量,只有自己位置是1,其他全是0。这种方法简单粗暴,但问题很明显——维度爆炸且毫无语义信息。“手机”和“电话”的向量距离,跟“手机”和“冰箱”一样远,这显然不符合我们的认知。
后来Word2Vec这类模型出来,算是打开了新世界的大门。它的核心思想很直观:一个词的语义,可以由它周围经常出现的词来定义。看这段训练代码:
# 旧版one-hot做法,别这样写了
def one_hot_encode(word, vocab):
vector = [0] * len(vocab)
vector[vocab[word]] = 1 # 除了自己位置是1,其他都是0
return vector
# 问题:50000个词就需要50000维,内存吃不消且没语义
# 改用词向量
from gensim.models import Word2Vec
# 训练自己的小模型
sentences = [["智能手机", "电池", "续航"],
["电池", "容量", "充电"],
["充电", "器", "快充"]] # 领域相关语料很重要
model = Word2Vec(sentences, vector_size=100, window=3, min_count=1)
# vector_size通常设100-300,太小信息不够,太大容易过拟合
# window控制上下文范围,一般3-5效果比较好
vector = model.wv["充电"]
print(f"向量维度:{vector.shape}") # (100,)
print(f"'充电'和'快充'相似度:{model.wv.similarity('充电', '快充'):.3f}")
这里有个实际调试的坑:预训练模型和自定义模型的维度必须对齐。我们线上问题就是加载了300维的预训练模型,但代码里写死了100维的查找逻辑,导致向量切片错位,语义完全乱套。
# 错误示例:混用不同维度的模型
pretrained_model = load_pretrained("tencent_embedding.bin") # 300维
custom_layer = nn.Linear(100, 10) # 期待100维输入
# 运行到这就崩了,维度对不上
# 正确做法:统一维度或做投影转换
if pretrained_model.vector_size != 100:
projection = nn.Linear(pretrained_model.vector_size, 100)
vector = projection(pretrained_vector) # 300维转100维
现在更常用的其实是BERT这类上下文相关的词向量,同一个词在不同句子里有不同表示。比如“苹果”在“苹果手机”和“苹果好吃”里向量不同,这比Word2Vec的静态向量更聪明。不过对于入门来说,先理解静态词向量是关键基础。
文本分类:从词袋到深度学习
有了词向量,文本分类就好办了。最早期的词袋模型(Bag of Words)完全忽略词序,只统计词频。虽然效果有限,但在某些场景下依然有用:
from sklearn.feature_extraction.text import CountVectorizer
corpus = [
"手机电池续航能力强",
"电池充电速度快",
"屏幕显示效果出色"
]
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(corpus)
print(vectorizer.get_feature_names_out())
# ['充电', '出色', '屏幕', '电池', '续航', '能力', '速度', '手机', '显示', '效果']
# 注意:中文需要先分词,这里为演示简化了
词袋模型最大的问题是维度高且稀疏。后来TF-IDF做了改进,降低常见词的权重,但依然没解决语义问题。
现在的主流做法是用词向量+神经网络。一个经典的TextCNN结构,几行PyTorch代码就能实现:
class TextCNN(nn.Module):
def __init__(self, vocab_size, embed_dim, num_classes):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim)
# 用不同尺寸的卷积核捕捉不同范围的语义
self.convs = nn.ModuleList([
nn.Conv2d(1, 100, (k, embed_dim)) for k in [2, 3, 4]
])
self.fc = nn.Linear(300, num_classes) # 100*3=300
def forward(self, x):
x = self.embedding(x) # [batch, seq_len, embed_dim]
x = x.unsqueeze(1) # 加通道维 [batch, 1, seq_len, embed_dim]
conv_results = []
for conv in self.convs:
conv_out = torch.relu(conv(x)).squeeze(3) # 去掉最后一维
pool_out = torch.max_pool1d(conv_out, conv_out.size(2)).squeeze(2)
conv_results.append(pool_out)
x = torch.cat(conv_results, 1) # 拼接不同卷积核的结果
return self.fc(x)
这个结构巧妙之处在于,用多个尺寸的卷积核同时捕捉2-gram、3-gram、4-gram特征。比如“电池续航”这种二元词组能被2-gram卷积核捕获,“充电速度快”这种三元组能被3-gram捕获。
实际部署时,建议先用FastText跑个基线。它的字符级n-gram特性对拼写错误、未登录词特别鲁棒:
from fasttext import FastText
model = FastText.train_supervised(
input="train.txt",
epoch=25,
lr=1.0,
wordNgrams=2, # 用2-gram特征
bucket=2000000
)
# 训练快,效果不错,特别适合应急上线
工程实践里的几个坑
-
领域适配问题
通用预训练模型在医疗、法律等专业领域效果会打折。我们之前做医疗文本分类,用通用BERT准确率只有72%,加入10万条医疗文献微调后到了89%。如果资源有限,至少用领域语料训练词向量层。 -
类别不平衡处理
真实场景中“其他”类可能占90%。除了重采样,试试Focal Loss:
class FocalLoss(nn.Module):
def __init__(self, alpha=0.25, gamma=2):
super().__init__()
self.alpha = alpha
self.gamma = gamma
def forward(self, inputs, targets):
BCE_loss = F.cross_entropy(inputs, targets, reduction='none')
pt = torch.exp(-BCE_loss)
loss = self.alpha * (1-pt)**self.gamma * BCE_loss
return loss.mean()
-
短文本分类
商品标题、搜索查询这类短文本,词向量效果可能不如传统方法。可以试试SVM+TF-IDF组合,有时候反而更稳定。 -
在线服务优化
BERT虽然效果好,但推理速度慢。可以蒸馏成小模型,或者用CNN/RNN结构。我们线上服务从BERT-base换成蒸馏后的3层Transformer,响应时间从120ms降到28ms,精度只掉了1.2个百分点。
个人经验建议
别一上来就怼BERT。先从FastText或TextCNN跑通流程,确保数据管道没问题。很多bug不是模型问题,而是数据预处理时编码不一致、分词方案冲突这类低级错误。
词向量一定要可视化检查。用TSNE降维后画个散点图,看看“手机”是不是靠近“电话”,“苹果”是不是同时靠近“水果”和“手机”。肉眼观察比指标更直观,能提前发现很多问题。
文本分类的评估指标要选对。多分类别只看准确率,特别是类别不平衡时。F1-score、混淆矩阵、AUC-ROC都看看。我们那个充电宝分类问题,如果早看混淆矩阵,就能发现模型把所有带“电”字的都归到了一类。
最后,模型上线后一定要加降级策略。当分类置信度低于阈值时,走规则匹配或人工审核流程。我们吃过亏——模型更新后有个隐层维度没对齐,线上全乱套,因为没有降级策略,直接导致标签系统瘫痪半小时。
NLP项目,30%时间在模型调优,70%时间在数据清洗和错误分析。多花时间看看模型分错的样本,比调超参有用得多。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)