中文词向量质量的评估
最近要对一些存量的模型评估性能,其中涉及到中文词向量模型的质量评估,因此也研究了一下这方面,把测试的方法和结果进行了总结。
1. 词向量质量的评估方法
对于中文词向量的质量评估,通常有内在评估和外在评估两种方法:
-
内在评估(Intrinsic Evaluation): 内在评估是通过一些标准化的测试集来评估词向量的质量,这些测试集通常包括词相似度任务、类比推理任务等。
-
词相似度任务:在这类任务中,评估模型通过比较词向量之间的距离(如余弦相似度)来预测词对相似度的能力。测试集可能包括人工标注的相似词对,模型需要正确地将这些词对排在前面。
-
类比推理任务:这类任务通常采用“A : B :: C : D”的形式,其中A和B之间存在某种关系,需要找到与C具有相同关系的D。例如,“男人:女人 :: 儿子:女儿”。测试集包含多个这样的类比问题,模型需要正确地找出D词。
-
-
外在评估(Extrinsic Evaluation): 外在评估是通过将词向量应用到具体的下游任务(如文本分类、情感分析等)中来评估其性能。这种方法直接反映了词向量在实际应用中的效果。
-
下游任务性能:使用词向量作为特征输入,评估其在特定NLP任务(如命名实体识别、机器翻译等)上的表现。通常,这些任务的性能指标(如准确率、F1分数)可以用来评估词向量的质量。
-
A/B测试:在实际应用中,通过对比使用不同词向量的模型性能,来评估它们在实际业务中的效果。
-
2. 测试模型的选择
我这里也选取了三个中文词向量模型进行评测,这三个模型分别是:
- 腾讯AI实验室发布的中文词向量,其数据来源包括新闻、网页、小说,词表构建参考了维基百科、百度百科,以及使用特定论文中的方法发现新词。训练方法基于Directional Skip-Gram,这是一种Skip-Gram的改进版,能够更好地区分左和右上下文。我采用的是轻量版,包括简化后的高频143613个词,每个词向量是200维。
- Jina发布的词向量v3版本,这是一个前沿的多语言文本向量模型,拥有570M个参数和8192个词元长度。可以根据需求针对检索、聚类、分类和匹配等不同场景进行定制,以获得更精准的向量化效果。输出维度可以定制,默认为1024,但可以根据需要缩减到32,性能几乎不受影响。
- Shibing624发布的text2vec-base-chinese,这是一个基于 CoSENT(Cosine Sentence)模型的中文文本向量化模型,将句子映射到一个768维的密集向量空间,可以用于句子嵌入、文本匹配或语义搜索等任务。
3. 内在评估测试
3.1 词相似度任务
首先进行词语相似度测试,这里我准备了一些测试用的词语,将其内容保存到一个文本文件,词语列表如下:
国王
王后
皇帝
皇后
春天
播种
秋天
收获
学生
教师
医生
医院
汽车
轮胎
电脑
键盘
男孩
少年
女孩
少女
红色
颜色
圆形
形状
苹果
水果
书本
知识
火车
轨道
飞机
跑道
父亲
儿子
祖父
孙子
猫
喵
狗
汪
重要
关键
高兴
快乐
快速
迅速
高
低
热
冷
成功
失败
家具
椅子
鼠标
医生
护士
学生
老师
树
飞机
海洋
书本
音乐
春节
红包
京剧
脸谱
书法
毛笔
画蛇添足
多此一举
守株待兔
坐享其成
杯水车薪
无济于事
画龙点睛
龙
对牛弹琴
牛
井底之蛙
井
网红
网络
云计算
计算
自媒体
媒体
DNA
基因
股票
牛市
算法
优化
以下代码是分别加载这三个模型,并且对以上的词语获取词向量。
from gensim.models import KeyedVectors
from transformers import AutoModel, AutoTokenizer
import requests
import json
import numpy as np
import pandas as pd
import base64
import torch
#加载词语
with open('dataset/wordembed/words.txt', 'r', encoding='utf-8') as f:
records = f.readlines()
words = []
for record in records:
words.append(record.strip())
#加载腾讯词向量
wordembed_tencent = KeyedVectors.load_word2vec_format("models/light_Tencent_AILab_ChineseEmbedding.bin", binary=True)
#调用jinnai接口
url = 'https://api.jina.ai/v1/embeddings'
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer jina_xxxx'
}
data = {
"model": "jina-embeddings-v3",
"task": "text-matching",
"dimensions": 512,
"late_chunking": False,
"embedding_type": "float",
"input": words
}
response = requests.post(url, headers=headers, json=data)
results = response.json()
wordembed_jinaai = {}
for i in range(len(words)):
wordembed_jinaai[words[i]] = np.array(results['data'][i]['embedding'])
#加载Text2Vec模型
model_path = 'models/shibing624/text2vec-base-chinese'
model = AutoModel.from_pretrained(model_path, trust_remote_code=True)
tokenizer = AutoTokenizer.from_pretrained(model_path)
def mean_pooling(model_output, attention_mask):
token_embeddings = model_output[0] # First element of model_output contains all token embeddings
input_mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()
return torch.sum(token_embeddings * input_mask_expanded, 1) / torch.clamp(input_mask_expanded.sum(1), min=1e-9)
encoded_input = tokenizer(words, padding=True, truncation=True, return_tensors='pt')
with torch.no_grad():
model_output = model(**encoded_input)
words_embeddings = mean_pooling(model_output, encoded_input['attention_mask'])
words_embeddings = words_embeddings.numpy()
wordembed_text2vec = {}
for i in range(len(words)):
wordembed_text2vec[words[i]] = words_embeddings[i]
然后基于以上的词语来设计一些词语对,计算其相似度,词语对如下:
words_pair = [
('画龙点睛', '龙'),
('画龙点睛', '猫'),
('画龙点睛', '牛'),
('画龙点睛', '狗'),
('对牛弹琴', '牛'),
('对牛弹琴', '龙'),
('对牛弹琴', '猫'),
('对牛弹琴', '狗'),
('重要', '关键'),
('重要', '毛笔'),
('重要', '猫'),
('重要', '家具'),
('高兴', '快乐'),
('高兴', '算法'),
('高兴', '椅子'),
('快速', '迅速'),
('高', '低'),
('热', '冷'),
('成功', '失败'),
('家具', '椅子'),
('鼠标', '电脑'),
('医生', '护士'),
('学生', '老师'),
('猫', '树'),
('飞机', '海洋'),
('书本', '音乐'),
('春节', '红包'),
('京剧', '脸谱'),
('书法', '毛笔'),
('画蛇添足', '多此一举'),
('杯水车薪', '无济于事'),
('网红', '网络'),
('股票', '牛市'),
('算法', '优化')
]
以下代码是对词语对的相似度进行计算,采用了余弦距离来计算词向量的相似度,代码如下:
compare_data = []
for pair in words_pair:
vec1 = wordembed_jinaai[pair[0]]
vec2 = wordembed_jinaai[pair[1]]
cos_sim_1 = vec1.dot(vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))
vec1 = wordembed_tencent[pair[0]]
vec2 = wordembed_tencent[pair[1]]
cos_sim_2 = vec1.dot(vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))
vec1 = wordembed_text2vec[pair[0]]
vec2 = wordembed_text2vec[pair[1]]
cos_sim_3 = vec1.dot(vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))
compare_data.append([','.join(pair), cos_sim_1, cos_sim_2, cos_sim_3])
df_compare = pd.DataFrame(data=compare_data, columns=['pair', 'jinaai', 'tencent', 'text2vec'])
df_compare.head(100)
结果如下:
Word Pair | Jinaai | Tecent | text2vec | |
0 | 画龙点睛,龙 | 0.694554 | 0.295584 | 0.620481 |
---|---|---|---|---|
1 | 画龙点睛,猫 | 0.503897 | 0.208964 | 0.239367 |
2 | 画龙点睛,牛 | 0.483506 | 0.149991 | 0.374364 |
3 | 画龙点睛,狗 | 0.459065 | 0.203133 | 0.231066 |
4 | 对牛弹琴,牛 | 0.751564 | 0.389501 | 0.501578 |
5 | 对牛弹琴,龙 | 0.590734 | 0.317333 | 0.320396 |
6 | 对牛弹琴,猫 | 0.477671 | 0.262733 | 0.259276 |
7 | 对牛弹琴,狗 | 0.513931 | 0.362945 | 0.294140 |
8 | 重要,关键 | 0.864326 | 0.714651 | 0.700409 |
9 | 重要,毛笔 | 0.504326 | 0.212462 | 0.294377 |
10 | 重要,猫 | 0.374544 | 0.263950 | 0.149595 |
11 | 重要,家具 | 0.434116 | 0.312056 | 0.397837 |
12 | 高兴,快乐 | 0.882747 | 0.560641 | 0.677769 |
13 | 高兴,算法 | 0.377916 | 0.257204 | 0.416744 |
14 | 高兴,椅子 | 0.431899 | 0.301036 | 0.325853 |
15 | 快速,迅速 | 0.976108 | 0.785857 | 0.888736 |
16 | 高,低 | 0.712852 | 0.844621 | 0.701662 |
17 | 热,冷 | 0.716923 | 0.662093 | 0.665288 |
18 | 成功,失败 | 0.732019 | 0.679045 | 0.774350 |
19 | 家具,椅子 | 0.852049 | 0.497557 | 0.524857 |
20 | 鼠标,电脑 | 0.645176 | 0.678132 | 0.514942 |
21 | 医生,护士 | 0.746815 | 0.813362 | 0.635125 |
22 | 学生,老师 | 0.706460 | 0.743812 | 0.503007 |
23 | 猫,树 | 0.483812 | 0.423557 | 0.239481 |
24 | 飞机,海洋 | 0.540330 | 0.388808 | 0.258021 |
25 | 书本,音乐 | 0.490692 | 0.434931 | 0.309046 |
26 | 春节,红包 | 0.502351 | 0.520290 | 0.444165 |
27 | 京剧,脸谱 | 0.317975 | 0.534120 | 0.360256 |
28 | 书法,毛笔 | 0.649993 | 0.651860 | 0.527564 |
29 | 画蛇添足,多此一举 | 0.717479 | 0.748855 | 0.396121 |
30 | 杯水车薪,无济于事 | 0.592463 | 0.607984 | 0.381541 |
31 | 网红,网络 | 0.638327 | 0.473689 | 0.619157 |
32 | 股票,牛市 | 0.639231 | 0.740894 | 0.552013 |
33 | 算法,优化 | 0.600145 | 0.585439 | 0.620129 |
从以上结果,综合来说Jinaai>Text2Vec>Tencent。
3.2 类比推理任务
设计一些词语对,以(A,B), (C,D)的方式组合,如以下代码:
words_sim_pairs = [
[('国王', '王后'), ('皇帝', '皇后')],
[('春天', '播种'), ('秋天', '收获')],
[('汽车', '轮胎'), ('电脑', '键盘')],
[('男孩', '少年'), ('女孩', '少女')],
[('红色', '颜色'), ('圆形', '形状')],
[('苹果', '水果'), ('书本', '知识')],
[('火车', '轨道'), ('飞机', '跑道')],
[('父亲', '儿子'), ('祖父', '孙子')],
[('猫', '喵'), ('狗', '汪')]
]
以下代码进行推理计算:
compare_tuili_data = []
for pair in words_sim_pairs:
vec1 = wordembed_jinaai[pair[0][0]]
vec2 = wordembed_jinaai[pair[0][1]]
vec3 = wordembed_jinaai[pair[1][0]]
vec4 = wordembed_jinaai[pair[1][1]]
cos_sim_1 = np.dot((vec3 - vec1 + vec2), (vec4)) / (np.linalg.norm(vec3 - vec1 + vec2) * np.linalg.norm(vec4))
vec1 = wordembed_tencent[pair[0][0]]
vec2 = wordembed_tencent[pair[0][1]]
vec3 = wordembed_tencent[pair[1][0]]
vec4 = wordembed_tencent[pair[1][1]]
cos_sim_2 = np.dot((vec3 - vec1 + vec2), (vec4)) / (np.linalg.norm(vec3 - vec1 + vec2) * np.linalg.norm(vec4))
vec1 = wordembed_text2vec[pair[0][0]]
vec2 = wordembed_text2vec[pair[0][1]]
vec3 = wordembed_text2vec[pair[1][0]]
vec4 = wordembed_text2vec[pair[1][1]]
cos_sim_3 = np.dot((vec3 - vec1 + vec2), (vec4)) / (np.linalg.norm(vec3 - vec1 + vec2) * np.linalg.norm(vec4))
compare_tuili_data.append([pair[0][0] + ',' + pair[0][1] + '__' + pair[1][0] + ',' + pair[1][1], cos_sim_1, cos_sim_2, cos_sim_3])
df_compare_tuili = pd.DataFrame(data=compare_tuili_data, columns=['sim_pair', 'jinaai', 'tencent', 'text2vec'])
df_compare_tuili.head(100)
结果如下:
Words Pair | Jinaai | Tencent | Text2Vec | |
0 | 国王,王后__皇帝,皇后 | 0.748771 | 0.793300 | 0.820785 |
---|---|---|---|---|
1 | 春天,播种__秋天,收获 | 0.499666 | 0.463778 | 0.449349 |
2 | 汽车,轮胎__电脑,键盘 | 0.561318 | 0.626217 | 0.338431 |
3 | 男孩,少年__女孩,少女 | 0.967823 | 0.777842 | 0.849556 |
4 | 红色,颜色__圆形,形状 | 0.759594 | 0.765586 | 0.635655 |
5 | 苹果,水果__书本,知识 | 0.529218 | 0.464707 | 0.287142 |
6 | 火车,轨道__飞机,跑道 | 0.572020 | 0.598807 | 0.555075 |
7 | 父亲,儿子__祖父,孙子 | 0.837878 | 0.815037 | 0.706733 |
8 | 猫,喵__狗,汪 | 0.570702 | 0.531914 | 0.508749 |
从以上结果看到,Tencent>Jinaai>Text2Vec
3.3 CA8评测
从以上评测,我们可以大概了解不同的词向量的质量,但是由于测试数据集不容易准备,我们无法做出很全面的比较。这里我们可以采用CA8这个数据集,https://github.com/Embedding/chinese-word-vectors.git。这是一个中文词类比任务数据集,它由北京师范大学和人民大学的研究人员提供,旨在评估中文词向量的质量。这个数据集特别为中文设计,包含了17813个词类比问题,覆盖了语法和语义任务,使得它能够更全面地评估词向量的性能。CA8提供了配套的评测工具,这些工具可以帮助研究人员和开发者评估和优化他们的词向量模型。评测工具支持稠密和稀疏向量的评测,可以通过命令行工具运行,提供了灵活的评测方式。
CA8的数据集主要分为两部分,Morphological和Semantic。Morphological是指与词的形态变化相关,如重复或叠词(天和天天,清楚和清清楚楚),半词缀化(木和木匠,虎和老虎)方面对语言的理解。Semantic考察了地理(广东和广州),历史(汉和刘邦),自然(盐和氯化钠),人物(阿里巴巴和马云)方面对语言的理解。
要进行CA8测试,首先要准备一个词汇表文件,这个文件的第一行是词语总数和词向量的维度,后面每一行是词语以及相应词向量每一维度的值,中间都以空格分隔开。以下代码是建立一个CA8的词汇表,并映射为对应模型的词向量。
vocabs = []
with open('dataset/wordembed/morphological.txt', 'r', encoding='utf-8') as f:
records = f.readlines()
for record in records:
words_list = record.strip().split(' ')
for w in words_list:
if w == ':':
break
if w not in vocabs:
vocabs.append(w)
with open('dataset/wordembed/semantic.txt', 'r', encoding='utf-8') as f:
records = f.readlines()
for record in records:
words_list = record.strip().split(' ')
for w in words_list:
if w == ':':
break
if w not in vocabs:
vocabs.append(w)
with open('vocabs.txt', 'w', encoding='utf-8') as f:
f.write('\n'.join(vocabs))
#映射为腾讯词向量
vocabs_tecent = {}
for w in vocabs:
try:
vocabs_tecent[w] = wordembed_tencent[w]
except KeyError:
vocabs_tecent[w] = wordembed_tencent.get_mean_vector(list(w))
with open('vocabs_tecent.txt', 'w', encoding='utf-8') as f:
keys = vocabs_tecent.keys()
f.write(str(len(keys)) + ' 200\n')
for k in keys:
f.write(k + ' ' + ' '.join([str(v) for v in vocabs_tecent[k]]) + '\n')
#映射为Jinaai词向量
vocabs_jinaai = {}
data = {
"model": "jina-embeddings-v3",
"task": "text-matching",
"dimensions": 512,
"late_chunking": False,
"embedding_type": "float"
}
length = len(vocabs)
splite_list = [vocabs[:int(length/2)], vocabs[int(length/2):]]
for words_list in splite_list:
data['input'] = words_list
response = requests.post(url, headers=headers, json=data)
results = response.json()
for i in range(len(words_list)):
vocabs[words_list[i]] = np.array(results['data'][i]['embedding'])
with open('vocabs_jinaai.txt', 'w', encoding='utf-8') as f:
keys = vocabs_jinaai.keys()
f.write(str(len(keys)) + ' 512\n')
for k in keys:
f.write(k + ' ' + ' '.join([str(v) for v in vocabs[k]]) + '\n')
#映射为Text2Vec词向量
vocabs_text2vec = {}
encoded_input = tokenizer(vocabs, padding=True, truncation=True, return_tensors='pt')
with torch.no_grad():
model_output = model(**encoded_input)
words_embeddings = mean_pooling(model_output, encoded_input['attention_mask'])
words_embeddings = words_embeddings.numpy()
for i in range(len(vocabs)):
vocabs_text2vec[vocabs[i]] = words_embeddings[i]
with open('vocabs_text2vec.txt', 'w', encoding='utf-8') as f:
keys = vocabs_text2vec.keys()
f.write(str(len(keys)) + ' 768\n')
for k in keys:
f.write(k + ' ' + ' '.join([str(v) for v in vocabs_text2vec[k]]) + '\n')
有了词向量文件后,我们可以运行CA8的evaluation目录下的ana_eval_dense.py来进行测试
3.3.1 Morphological测试
首先是分别测试这三个模型在Morphological测试的性能。
测试腾讯模型
python ana_eval_dense.py -v vocabs_tecent.txt -a ../testsets/CA8/morphological.txt
结果如下:
A add/mul: 0.585/0.605
prefix add/mul: 0.437/0.438
AB add/mul: 0.454/0.443
suffix add/mul: 0.525/0.529
Total accuracy (add): 0.5
Total accuracy (mul): 0.504
测试Jinaai模型
python ana_eval_dense.py -v vocabs_jinaai.txt -a ../testsets/CA8/morphological.txt
结果如下:
A add/mul: 0.849/0.867
prefix add/mul: 0.92/0.928
AB add/mul: 0.929/0.932
suffix add/mul: 0.947/0.953
Total accuracy (add): 0.911
Total accuracy (mul): 0.92
测试Text2Vec模型
python ana_eval_dense.py -v vocabs_text2vec.txt -a ../testsets/CA8/morphological.txt
结果如下:
A add/mul: 0.857/0.885
prefix add/mul: 0.934/0.947
AB add/mul: 0.936/0.947
suffix add/mul: 0.961/0.968
Total accuracy (add): 0.922
Total accuracy (mul): 0.937
从以上测试结果可以得知,在Morphological测试中,Text2Vec>Jinaai>>Tencent
3.3.2 Semantic测试
现在分别测试这三个模型在Semantic测试的性能。
测试腾讯模型
python ana_eval_dense.py -v vocabs_tecent.txt -a ../testsets/CA8/semantic.txt
结果如下:
geography add/mul: 0.323/0.327
nature add/mul: 0.445/0.444
history add/mul: 0.029/0.033
people add/mul: 0.169/0.17
Total accuracy (add): 0.256
Total accuracy (mul): 0.258
测试Jinaai模型
python ana_eval_dense.py -v vocabs_jinaai.txt -a ../testsets/CA8/semantic.txt
结果如下:
geography add/mul: 0.473/0.48
nature add/mul: 0.422/0.437
history add/mul: 0.024/0.024
people add/mul: 0.142/0.144
Total accuracy (add): 0.308
Total accuracy (mul): 0.314
测试Text2Vec模型
python ana_eval_dense.py -v vocabs_text2vec.txt -a ../testsets/CA8/semantic.txt
结果如下:
geography add/mul: 0.375/0.379
nature add/mul: 0.448/0.453
history add/mul: 0.027/0.031
people add/mul: 0.126/0.124
Total accuracy (add): 0.269
Total accuracy (mul): 0.272
从以上测试结果可以得知,在Semantic测试中,Jinaai>Text2Vec>Tencent
结论
通过以上的内在评估测试,我们综合比较了三个不同的词向量模型生成的词向量的质量,可以看到整体而言Jinaai的模型质量是最好的,Text2Vec次之。考虑到Jinnai的多语言支持功能,可以说Jinnai是值得推荐的,另外Jinnai和Text2Vec还可以输出模型的隐向量,我们可以将其输入到自己的NLP模型来适配下游的任务,进行微调。
稍后有时间我会继续进行模型的外在评估测试,通过ATEC、BQ和LCQMC这三个中文自然语言处理(NLP)领域中的重要的测试任务来进行测试,以评估模型的性能