1. 从字符串到向量:为什么文本必须变成数字
做自然语言处理这些年,我越来越觉得,文本表示这件事就像给机器装翻译器。你给机器看"苹果"这两个字,它看到的只是两个字符编码,完全不知道这玩意儿能吃、能砸到牛顿、还能是手机品牌。向量语义要解决的核心问题就一个:怎么把人类语言里那些微妙的含义,塞进一串数字里,让机器能算、能比、能推理。
我最早接触这块是在做一个文本分类的小项目,当时用词袋模型跑了个baseline,准确率惨不忍睹。后来换成TF-IDF,提升了一截,再后来上了Word2Vec,效果直接起飞。这个过程让我深刻理解了一件事:文本表示的质量,直接决定了整个NLP系统的天花板。你后面接再花哨的模型,前面喂进去的向量是垃圾,结果也好不到哪去。
这篇文章我打算把余弦相似度、TF-IDF、Word2Vec这三块串起来讲。它们不是孤立的三个知识点,而是一条演进路线:从最朴素的统计方法,到加权统计,再到基于神经网络的分布式表示。每一代方法都在解决上一代的痛点,理解了这个演进逻辑,你就能在实际项目里做出合理的技术选型,而不是盲目跟风。
适合谁看?如果你刚入门NLP,正在被各种向量化方法搞得头晕,这篇文章能帮你理清脉络。如果你已经有一定经验,但想搞清楚每种方法背后的数学直觉和工程取舍,这里也有你需要的细节。我会尽量用生活化的类比来解释,同时给出可以直接跑的代码和参数建议。
2. 余弦相似度:衡量向量"方向"的一把尺子
2.1 为什么不用欧氏距离而用余弦
先说一个我踩过的坑。刚开始做文本相似度的时候,我下意识用了欧氏距离,觉得距离越小越相似嘛,很直观。结果发现一个严重问题:两篇内容几乎一样的文章,一篇长一篇短,欧氏距离居然很大。因为长文章的词频向量在每个维度上的数值都更大,向量整体"更长",但方向其实是一致的。
余弦相似度就是来解决这个问题的。它不看向量的长度,只看方向。公式很简单:
cos(A, B) = (A · B) / (||A|| * ||B||)分子是点积,分母是两个向量的模长乘积。算出来的值在-1到1之间,1表示方向完全一致,0表示正交(无关),-1表示方向完全相反。
注意:在文本场景下,词频向量每个维度都是非负的,所以余弦相似度的实际取值范围是0到1,不会出现负值。
用生活类比来说,欧氏距离像比较两个人的身高体重绝对值,余弦相似度像比较两个人的体型比例。一个高个子瘦子和一个矮个子瘦子,体型比例是一样的,余弦相似度会认为他们"很像"。
2.2 手算一个例子把直觉建立起来
光看公式容易飘,咱们拿个小例子实际算一遍。假设有三个词:猫、狗、鱼。现在有两个句子:
- 句子A:"猫 狗 猫 鱼" → 向量 [2, 1, 1]
- 句子B:"猫 猫 狗 狗" → 向量 [2, 2, 0]
先算点积:2×2 + 1×2 + 1×0 = 4 + 2 + 0 = 6
再算模长:
- ||A|| = √(4 + 1 + 1) = √6 ≈ 2.449
- ||B|| = √(4 + 4 + 0) = √8 ≈ 2.828
余弦相似度 = 6 / (2.449 × 2.828) ≈ 6 / 6.928 ≈ 0.866
这个值挺高的,说明两个句子在"猫狗鱼"这个语义空间里方向比较接近。你可以自己改改数字感受一下,比如把句子B改成"鱼 鱼 鱼 鱼",向量变成[0, 0, 4],点积变成0,余弦相似度直接归零,说明两个句子在这个空间里完全正交。
2.3 代码实现与工程注意事项
Python里用NumPy实现余弦相似度就是几行的事:
import numpy as np def cosine_similarity(a, b): a = np.array(a, dtype=float) b = np.array(b, dtype=float) dot = np.dot(a, b) norm_a = np.linalg.norm(a) norm_b = np.linalg.norm(b) if norm_a == 0 or norm_b == 0: return 0.0 return dot / (norm_a * norm_b)看着简单,但工程上有几个坑必须注意:
第一,零向量问题。如果某个文本在所有词上的权重都是0(比如停用词过滤后啥都不剩),模长为0,除法会报错。我一般会加个判断,零向量直接返回0,表示跟谁都不相似。
第二,稀疏向量的性能。实际文本的TF-IDF向量维度可能几万甚至几十万,但大部分维度是0。用稠密数组算会浪费大量内存和计算。生产环境建议用scipy的稀疏矩阵:
from sklearn.metrics.pairwise import cosine_similarity from scipy.sparse import csr_matrix # 假设 tfidf_matrix 是稀疏矩阵 sim_matrix = cosine_similarity(tfidf_matrix)第三,批量计算的效率。如果你要算N个文档两两之间的相似度,别写双重循环一个个算。sklearn的cosine_similarity支持矩阵输入,底层用矩阵乘法优化过,速度快几个数量级。我之前有个项目,用循环算5000篇文档的相似度矩阵跑了十几分钟,换成矩阵运算后几秒钟搞定。
实操心得:余弦相似度对向量长度不敏感这个特性,在文本场景下是优点也是缺点。优点是消除了文档长度的影响,缺点是如果你确实需要区分长短文本(比如检测抄袭时短文本抄长文本),光靠余弦相似度不够,得结合其他特征。
3. TF-IDF:给词语加权的经典智慧
3.1 从词袋到TF-IDF的演进逻辑
词袋模型是最朴素的文本向量化方法:统计每个词在文档里出现了几次,组成一个向量。简单粗暴,但有个明显的问题——所有词一视同仁。"的"出现了10次和"算法"出现了10次,在词袋模型里权重一样,但显然"算法"对理解文档主题更重要。
TF-IDF的核心思想就是:一个词在当前文档里出现越多越重要(TF高),但在所有文档里出现越普遍越不重要(IDF低)。这两个因素乘起来,就能把真正有区分度的词凸显出来。
这个直觉其实很符合生活经验。你在一篇技术文章里看到"神经网络"反复出现,会觉得这篇文章就是讲神经网络的。但如果"的"反复出现,你根本不会在意,因为每篇文章里"的"都很多,它没有区分度。
3.2 TF和IDF的计算细节与变体
TF(词频)的计算有几种常见方式:
| 计算方式 | 公式 | 特点 |
|---|---|---|
| 原始计数 | tf = count | 简单,但长文档数值偏大 |
| 归一化频率 | tf = count / total_words | 消除文档长度影响 |
| 对数平滑 | tf = 1 + log(count) | 抑制高频词的过度影响 |
| 二值化 | tf = 1 if count > 0 else 0 | 只看出现与否 |
IDF(逆文档频率)的标准公式是:
idf = log(N / df)其中N是文档总数,df是包含该词的文档数。但实际工程中,sklearn用的是平滑版本:
idf = log((1 + N) / (1 + df)) + 1加1是为了避免df为0时除零错误,外层加1是为了保证即使某个词在所有文档都出现(idf本该为0),权重也不会完全消失。
最终TF-IDF权重就是两者相乘:
tfidf = tf * idf3.3 sklearn实战:从原始文本到相似度矩阵
光说不练假把式,下面是一个完整的TF-IDF文本相似度计算流程:
from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity # 模拟语料 corpus = [ "自然语言处理是人工智能的重要分支", "深度学习推动了自然语言处理的发展", "向量语义表示是自然语言处理的核心技术", "今天天气不错适合出去散步", "机器学习包括深度学习和传统方法" ] # 初始化向量化器 vectorizer = TfidfVectorizer( analyzer='char', # 中文用字符级,英文可用'word' ngram_range=(1, 2), # 用1-gram和2-gram max_features=5000, # 限制特征数量 min_df=1, # 至少出现在1个文档中 max_df=0.95 # 出现在超过95%文档中的词丢弃 ) # 拟合并转换 tfidf_matrix = vectorizer.fit_transform(corpus) # 计算相似度矩阵 sim_matrix = cosine_similarity(tfidf_matrix) # 查看结果 print("特征词数量:", len(vectorizer.get_feature_names_out())) print("相似度矩阵形状:", sim_matrix.shape)跑出来你会发现,前三句关于NLP的相似度明显高于跟第四句"天气"的相似度,说明TF-IDF确实抓住了主题差异。
3.4 中文场景的特殊处理与参数调优
中文跟英文不一样,英文天然有空格分词,中文得先分词。但分词本身就会引入误差,分错了后面全错。我在实际项目里试过两种方案:
方案一:先分词再TF-IDF。用jieba之类的工具分词,然后按词级别做TF-IDF。优点是语义单元清晰,缺点是分词错误会传播,而且领域专有名词容易分错。
方案二:字符级TF-IDF。直接用单个字或双字组合(bigram)作为特征。优点是避免分词误差,对短文本效果不错,缺点是特征维度高,而且丢失了词级别的语义信息。
我的经验是:短文本分类用字符级,长文本主题分析用词级。如果领域术语多,可以先自定义词典再分词。
参数调优方面,几个关键参数:
ngram_range:中文建议(1,2)或(1,3),能捕捉"自然语言"这种固定搭配max_df:设太高会保留太多无区分度的词,设太低会丢掉重要但常见的词,一般0.8到0.95之间min_df:过滤低频噪声,小语料设1,大语料可以设2到5sublinear_tf:设为True会用1+log(tf)替代原始tf,对长文档更友好
踩坑记录:有一次做新闻分类,max_df设了0.9,结果"记者"、"报道"这类词被过滤了,但恰恰这些词对区分新闻和非新闻很重要。后来改成0.95才正常。所以参数没有万能值,得看具体任务。
4. Word2Vec:让词语拥有"语义坐标"
4.1 分布式假设:理解Word2Vec的钥匙
TF-IDF虽然好用,但它有个根本局限:它把每个词当成独立的符号,无法捕捉词与词之间的语义关系。"猫"和"猫咪"在TF-IDF眼里是两个完全不同的维度,没有任何关联。这就导致数据稀疏问题——你训练集里没见过"猫咪"这个词,模型就完全无法处理。
Word2Vec基于一个重要的语言学假设:分布式假设。意思是,一个词的含义由它经常出现的上下文决定。如果你看到"___在沙发上睡觉",大概率填"猫"或"狗",不太可能填"冰箱"。所以"猫"和"狗"因为出现在相似的上下文里,它们的向量表示应该接近。
这个思想把词从离散符号变成了连续空间里的点,语义相近的词在空间里距离就近。更神奇的是,这种表示还能支持类比推理:"国王 - 男人 + 女人 ≈ 女王",向量运算居然能对应语义关系。
4.2 CBOW与Skip-gram:两种训练架构的取舍
Word2Vec有两种训练方式:
CBOW(连续词袋):用上下文词预测中心词。比如给"猫 在 睡觉",预测中间的"沙发"。训练速度快,对高频词效果好。
Skip-gram:用中心词预测上下文词。比如给"沙发",预测它周围可能出现"猫"、"睡觉"。训练慢一些,但对低频词和生僻词效果更好。
| 对比维度 | CBOW | Skip-gram |
|---|---|---|
| 预测方向 | 上下文→中心词 | 中心词→上下文 |
| 训练速度 | 快 | 慢 |
| 低频词效果 | 一般 | 好 |
| 适合场景 | 大语料快速训练 | 小语料精细训练 |
| 内存占用 | 较低 | 较高 |
我一般默认用Skip-gram,因为实际项目里低频专有名词往往很重要,CBOW容易把它们训练得很差。如果语料特别大(比如上亿词)且对速度有要求,再考虑CBOW。
4.3 训练参数详解与实操配置
用gensim训练Word2Vec的典型代码:
from gensim.models import Word2Vec import jieba # 假设有分好词的语料 sentences = [ ["自然", "语言", "处理", "是", "人工智能", "的", "分支"], ["深度", "学习", "推动", "了", "自然", "语言", "处理"], # ...更多句子 ] model = Word2Vec( sentences=sentences, vector_size=100, # 向量维度 window=5, # 上下文窗口大小 min_count=2, # 低于此频率的词丢弃 sg=1, # 1=Skip-gram, 0=CBOW negative=5, # 负采样数量 epochs=10, # 训练轮数 workers=4 # 并行线程数 ) # 查看词向量 print(model.wv["自然"]) # 找相似词 print(model.wv.most_similar("自然", topn=5))几个关键参数的选取逻辑:
vector_size:维度不是越高越好。100到300是常见范围。太小表达力不够,太大容易过拟合且内存爆炸。我一般先用100试,效果不够再加。
window:窗口越大,捕捉的语义关系越偏向主题相关;窗口越小,越偏向语法功能相似。做主题分析用5到10,做词性分析用2到3。
min_count:过滤低频词。小语料设1到2,大语料可以设5到10。低频词因为样本太少,训练出来的向量质量很差,不如直接丢掉。
negative:负采样数量。一般5到20之间,越大训练越慢但效果可能更好。默认5通常够用。
epochs:训练轮数。gensim默认5,我一般设10到20。但要注意,轮数太多会过拟合,表现为相似词列表里出现很多奇怪的词。
4.4 预训练模型与微调策略
从头训练Word2Vec需要大量语料,一般项目很难凑够。实际工作中更常见的是用预训练模型。中文有腾讯词向量、百度词向量等,英文有Google News词向量。
用预训练模型的好处是覆盖词多、质量高,但缺点是可能跟你的领域不匹配。比如你做医疗文本,预训练模型里"高血压"的向量可能跟"压力"很近,但在医疗语境下它应该跟"心血管"更近。
我的策略是:如果领域跟通用语料差异大,用预训练向量初始化,然后在自己的语料上继续训练几个epoch做微调。这样既保留了通用语义知识,又注入了领域特性。
# 加载预训练模型后继续训练 model = Word2Vec.load("pretrained.model") model.build_vocab(domain_sentences, update=True) model.train(domain_sentences, total_examples=len(domain_sentences), epochs=5)注意:继续训练时学习率要调小,否则会把预训练学到的知识冲掉。gensim里可以通过调整alpha参数控制。
5. 三种方法的对比与选型指南
5.1 从原理到效果的横向对比
把三种方法放在一起看,它们的本质区别在于对"语义"的理解层次不同:
| 对比维度 | 余弦相似度 | TF-IDF | Word2Vec |
|---|---|---|---|
| 核心功能 | 相似度度量 | 文本向量化 | 词向量表示 |
| 语义捕捉 | 依赖底层表示 | 词级别统计 | 分布式语义 |
| 数据稀疏 | 取决于表示 | 稀疏 | 稠密 |
| 可解释性 | 高 | 高 | 低 |
| 训练成本 | 无 | 低 | 高 |
| 新词处理 | 取决于表示 | 无法处理 | 无法处理(除非微调) |
| 适合任务 | 相似度计算 | 分类、检索 | 语义分析、下游任务 |
需要说明的是,余弦相似度其实不是跟TF-IDF和Word2Vec并列的方法,它是一种度量方式,可以作用在TF-IDF向量上,也可以作用在Word2Vec向量上。我把它放在一起讲,是因为很多人第一次接触文本相似度就是从余弦相似度开始的。
5.2 实际项目中的组合使用
真实项目里,这三种方法经常是组合使用的。举几个我做过的场景:
场景一:文档检索系统。用TF-IDF做粗排,快速从百万文档里筛出几百篇候选,然后用Word2Vec向量做精排,计算语义相似度。这样兼顾了速度和效果。
场景二:文本分类。把TF-IDF特征和Word2Vec词向量平均后的文档向量拼接起来,一起喂给分类器。TF-IDF提供精确的词级别信号,Word2Vec提供语义泛化能力,两者互补。
场景三:短文本相似度。短文本TF-IDF稀疏严重,效果不好。这时候用Word2Vec把每个词映射成向量,然后取平均或加权平均作为句子向量,再算余弦相似度,效果明显更好。
import numpy as np def sentence_vector(sentence, model): """用词向量平均得到句子向量""" words = jieba.lcut(sentence) vectors = [] for word in words: if word in model.wv: vectors.append(model.wv[word]) if not vectors: return np.zeros(model.vector_size) return np.mean(vectors, axis=0) # 计算两个句子的语义相似度 vec1 = sentence_vector("自然语言处理很有趣", model) vec2 = sentence_vector("NLP是一门有意思的学科", model) sim = cosine_similarity(vec1.reshape(1, -1), vec2.reshape(1, -1)) print(f"语义相似度: {sim[0][0]:.4f}")5.3 选型决策树与经验法则
面对一个新项目,怎么快速决定用哪种方法?我总结了一个简单的决策流程:
数据量很小(几百条):直接用TF-IDF加余弦相似度,简单可靠,别折腾Word2Vec,数据不够训不出好向量。
数据量中等(几千到几万条),任务偏分类:TF-IDF作为baseline,如果效果不够再上Word2Vec。
数据量充足(十万条以上),任务涉及语义理解:Word2Vec或预训练词向量,配合下游模型。
需要处理未登录词、语义泛化:必须用Word2Vec或更强的预训练模型,TF-IDF无能为力。
对可解释性要求高:TF-IDF优先,你能清楚看到哪些词贡献了权重。Word2Vec的维度含义很难解释。
实操心得:别一上来就追求最先进的方法。我见过太多项目,数据才几千条就上BERT,结果过拟合严重,还不如TF-IDF加逻辑回归。方法要跟数据规模匹配,这是铁律。
6. 常见问题与排查技巧实录
6.1 相似度计算结果不符合直觉怎么办
这是最常见的问题。你算出来两句话相似度0.9,但你觉得它们根本不相关。排查思路:
第一步,检查分词。中文分词错误是万恶之源。把分词结果打印出来看看,"自然语言"被切成"自然"和"语言"还好,被切成"自"和"然语言"就完蛋了。
第二步,检查停用词。如果停用词没过滤干净,"的"、"了"、"是"这些词会主导相似度计算,导致所有文本看起来都差不多。
第三步,检查向量维度。如果TF-IDF特征维度太低(比如max_features设了100),很多重要词被丢了,相似度自然不准。
第四步,检查语料规模。Word2Vec在小于几万词的语料上训练,效果基本靠运气。这时候不如退回TF-IDF。
6.2 Word2Vec训练慢或内存爆了怎么优化
Word2Vec训练确实吃资源,几个优化方向:
- 减小vector_size:从300降到100,内存和训练时间都大幅下降,效果损失通常可接受。
- 增大min_count:过滤更多低频词,词汇表变小,训练加速。
- 用CBOW替代Skip-gram:训练速度能快2到3倍。
- 减少negative采样数:从10降到5,速度提升明显。
- 分批训练:语料太大一次性加载不了,可以用gensim的在线训练方式,分批次喂数据。
6.3 问题速查表
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 相似度普遍偏高 | 停用词未过滤 | 加停用词表 |
| 相似度普遍偏低 | 特征维度过高且稀疏 | 降维或换Word2Vec |
| 相似词列表质量差 | 语料太小或epochs不够 | 增加语料或训练轮数 |
| 训练时报内存错误 | 词汇表太大 | 提高min_count |
| 新词无法处理 | 词表固定 | 使用子词信息或字符级表示 |
| 结果不稳定 | 随机种子未固定 | 设置seed参数 |
6.4 几个容易被忽略的细节
关于余弦相似度的对称性。余弦相似度是对称的,sim(A,B) = sim(B,A)。但有些场景下相似度不应该对称,比如搜索query和文档,短query包含在长文档里和反过来,语义关系不一样。这种场景要考虑非对称度量。
关于TF-IDF的增量更新。新文档加入时,IDF值会变化,理论上需要重新计算所有文档的向量。但实际中如果新文档占比很小,可以忽略这个变化,定期全量重算就行。
关于Word2Vec的OOV问题。训练时没见过的词,模型无法给出向量。解决方案有:用fastText(考虑子词信息)、用字符级n-gram、或者直接跳过。我一般会维护一个OOV词表,分析时看看有多少词被跳过了,如果比例太高说明词表覆盖不够。
关于向量归一化。算余弦相似度前,如果先把向量归一化成单位向量,那么余弦相似度就退化成点积,计算更快。sklearn的normalize函数可以批量做这件事。
from sklearn.preprocessing import normalize tfidf_normalized = normalize(tfidf_matrix) # 此时 cosine_similarity(tfidf_normalized) 等价于 tfidf_normalized @ tfidf_normalized.T这个技巧在需要频繁计算相似度的场景下很有用,能省不少时间。
7. 从词向量到句向量:一个实用的扩展方向
7.1 词向量平均的局限与改进
用Word2Vec得到词向量后,最直接的句子表示方法就是取所有词向量的平均。但这个方法有个明显问题:它把所有词同等对待,"的"和"算法"对句子语义的贡献显然不一样。
改进方案是加权平均,用TF-IDF权重来加权:
def weighted_sentence_vector(sentence, model, tfidf_dict): words = jieba.lcut(sentence) vectors = [] weights = [] for word in words: if word in model.wv: vectors.append(model.wv[word]) weights.append(tfidf_dict.get(word, 1.0)) if not vectors: return np.zeros(model.vector_size) weights = np.array(weights).reshape(-1, 1) vectors = np.array(vectors) return np.sum(vectors * weights, axis=0) / np.sum(weights)这样重要的词对句子向量的贡献更大,效果通常比简单平均好一截。
7.2 SIF加权:一个简单有效的技巧
SIF(Smooth Inverse Frequency)是另一个我常用的加权方法。它的核心思想是:词频越高,权重越低,但用平滑函数避免低频词权重爆炸。公式是:
weight(w) = a / (a + p(w))其中a是超参数(一般取0.001),p(w)是词的出现概率。算完加权平均后,还要减去第一主成分,去除句子向量中的共同成分。
这个方法实现简单,但在很多相似度任务上效果出奇地好。我做过对比实验,SIF加权比简单平均在STS基准上能提升5到10个百分点。
7.3 什么时候该上更强的模型
词向量加权平均终究是权宜之计,它无法处理词序、句法结构、多义词等问题。"苹果手机"和"手机苹果"平均出来的向量是一样的,但语义完全不同。
如果你的任务对语义精度要求高,且数据量充足,该上BERT这类上下文相关的预训练模型就上。但要注意,BERT的计算成本比Word2Vec高几个数量级,推理速度慢很多。实际部署时要权衡效果和延迟。
我的建议是:先用Word2Vec加权平均快速验证任务可行性,如果效果达标就上线,不达标再考虑升级模型。别一上来就堆最复杂的方案,迭代速度会拖垮整个项目。
8. 工程落地中的性能优化经验
8.1 相似度计算的加速策略
当文档数量到十万级别,两两算相似度就是O(n²)的复杂度,根本跑不动。几个实用的加速方法:
方法一:用近似最近邻搜索。把向量建索引,用Annoy、Faiss这类库做近似查询,把O(n²)降到O(n log n)。代价是损失一点精度,但通常可接受。
方法二:先聚类再算。先用K-Means把文档聚成几百个簇,查询时只跟同簇或邻近簇的文档算相似度,大幅减少计算量。
方法三:用倒排索引做粗筛。基于TF-IDF的倒排索引,先找出共享词多的候选文档,再精算相似度。这是搜索引擎的经典做法。
8.2 向量存储与检索的工程考量
Word2Vec训练出来的向量文件可能几百MB甚至几个GB,怎么高效存储和加载也是个问题。
- 存储格式:gensim默认用二进制格式,比文本格式省空间且加载快。如果只需要部分词向量,可以用KeyedVectors加载后按需取。
- 内存映射:gensim支持mmap模式,向量存在磁盘上,用的时候才加载到内存,适合内存有限的场景。
- 量化压缩:把float32降成float16甚至int8,内存占用能降一半到四分之三,精度损失很小。
# 用mmap模式加载,节省内存 from gensim.models import KeyedVectors wv = KeyedVectors.load("vectors.kv", mmap='r')8.3 线上服务的延迟优化
如果相似度计算要在线实时响应,延迟就是生命线。几个优化点:
- 预计算:能离线算的都离线算好,线上只做查询。
- 缓存:热门query的相似度结果缓存起来,避免重复计算。
- 批处理:把多个请求攒一批一起算,利用矩阵运算的并行性。
- 降维:用PCA把向量从300维降到50维,计算量降6倍,精度损失通常不到5%。
我在一个检索项目里,把向量从300维降到64维,QPS从200提升到1500,而召回率只掉了2个百分点。这个 trade-off 非常划算。
9. 我个人的一些经验体会
做了这么多文本向量化的项目,最大的体会是:没有最好的方法,只有最合适的方法。TF-IDF看似老土,但在很多场景下依然能打,而且可解释性强、调试方便。Word2Vec很强大,但需要足够的语料和调参经验,小项目上未必划算。
另一个体会是,预处理的重要性被严重低估。分词、停用词、归一化这些步骤,做好了能让效果提升一大截,做不好再牛的模型也救不回来。我见过太多人把精力全花在模型选型上,结果分词都没搞对。
最后分享一个小技巧:做文本相似度任务时,先别急着上模型,拿几条典型样本手动算一遍TF-IDF余弦相似度,看看结果是否符合直觉。如果不符合,先排查预处理问题,而不是换模型。这个习惯帮我省了大量瞎折腾的时间。
向量语义这个领域还在快速演进,从Word2Vec到BERT再到各种大模型,表示能力越来越强。但底层的核心思想——把语义映射到向量空间、用距离度量相似性——一直没变。理解了这些基础,面对新方法时你就能快速抓住本质,而不是被各种名词唬住。