如何使用词嵌入(即Word2vec、GloVe或BERT)计算一组N个词中最多的词相似度?

H M*_*H M 4 python nlp cosine-similarity word2vec bert-language-model

我试图通过输入单词列表并输出一个单词来计算语义相似度,该单词是列表中相似度最高的单词。

例如

如果我传递一个单词列表

words = ['portugal', 'spain', 'belgium', 'country', 'netherlands', 'italy']
Run Code Online (Sandbox Code Playgroud)

它应该输出类似这样的东西-

['country']
Run Code Online (Sandbox Code Playgroud)

Abh*_*25t 8

手套嵌入

\n

为了加载预先训练的 GloVe 嵌入,我们将使用一个名为 的包torchtext。它包含其他用于处理文本的有用工具,我们将在课程后面看到。torchtext GloVe 向量的文档位于:https ://torchtext.readthedocs.io/en/latest/vocab.html#glove

\n

首先加载一组 GloVe 嵌入。第一次运行下面的代码时,Python 将下载一个包含预训练嵌入的大文件 (862MB)。

\n
import torch\nimport torchtext\n\nglove = torchtext.vocab.GloVe(name="6B", # trained on Wikipedia 2014 corpus of 6 billion words\n                              dim=50)   # embedding size = 100\n
Run Code Online (Sandbox Code Playgroud)\n

我们来看看“car”这个词的嵌入是什么样子的:

\n
glove[\'cat\']\n
Run Code Online (Sandbox Code Playgroud)\n
\n

张量([ 0.4528, -0.5011, -0.5371, -0.0157, 0.2219, 0.5460, -0.6730,\n-0.6891,\n0.6349, -0.1973, 0.3368, 0.7735, 0.9009, 0.3849, 0.38 37, 0.2657,\n-0.0806 , 0.6109, -1.2894, -0.2231, -0.6158, 0.2170, 0.3561, 0.4450,\n0.6089, -1.1633, -1.1579, 0.3612, 0.1047, -0.7832, 1.4352, 0.1863,\ n-0.2611、0.8328、-0.2312、 0.3248,0.1449,-0.4455,0.3350,-0.9595,\ n -0.0975,0.4814,-0.4335,0.6945,0.9104,0.9104,-0.2817, -0.2817,0.4164,0.4164,0.4164,1.2609,-1.2609,\ n0.771128]

\n
\n

它是一个尺寸为 (50,) 的火炬张量。很难确定此嵌入中的每个数字的含义(如果有的话)。然而,我们知道这个嵌入空间中存在结构。也就是说,这个嵌入空间中的距离是有意义的。

\n

测量距离

\n

为了探索嵌入空间的结构,有必要引入距离的概念。您可能已经熟悉欧几里德距离的概念。两个向量 x=[x1,x2,...xn]\n 和 y=[y1,y2,...yn] 的欧几里德距离只是它们差值 x\xe2\x88\x92y 的 2-范数。

\n

PyTorch 函数torch.norm为我们计算向量的 2-范数,因此我们可以计算两个向量之间的欧几里德距离,如下所示:

\n
x = glove[\'cat\']\ny = glove[\'dog\']\ntorch.norm(y - x)\n
Run Code Online (Sandbox Code Playgroud)\n
\n

张量(1.8846)

\n
\n

余弦相似度是距离的另一种度量。余弦相似度测量两个向量之间的角度,并且具有仅考虑向量的方向而不考虑向量的大小的特性。(我们将在下一堂课使用这个属性。)

\n
x = torch.tensor([1., 1., 1.]).unsqueeze(0)\ny = torch.tensor([2., 2., 2.]).unsqueeze(0)\ntorch.cosine_similarity(x, y) # should be one\n
Run Code Online (Sandbox Code Playgroud)\n
\n

张量([1.])

\n
\n

余弦相似度是一种相似度度量,而不是距离度量:相似度越大,词嵌入彼此“越接近”。

\n
x = glove[\'cat\']\ny = glove[\'dog\']\ntorch.cosine_similarity(x.unsqueeze(0), y.unsqueeze(0))\n
Run Code Online (Sandbox Code Playgroud)\n
\n

张量([0.9218])

\n
\n

单词相似度

\n

现在我们有了嵌入空间中的距离概念,我们可以讨论嵌入空间中彼此“接近”的单词。现在,让我们使用欧几里得距离来看看各个单词与单词“cat”的接近程度。

\n
word = \'cat\'\nother = [\'dog\', \'bike\', \'kitten\', \'puppy\', \'kite\', \'computer\', \'neuron\']\nfor w in other:\n    dist = torch.norm(glove[word] - glove[w]) # euclidean distance\n    print(w, float(dist))\n
Run Code Online (Sandbox Code Playgroud)\n
\n

狗 1.8846031427383423

\n
\n
\n

自行车5.048375129699707

\n
\n
\n

小猫 3.5068609714508057

\n
\n
\n

小狗 3.0644655227661133

\n
\n
\n

风筝 4.210376262664795

\n
\n
\n

电脑6.030652046203613

\n
\n
\n

神经元 6.228669166564941

\n
\n

事实上,我们可以在整个词汇表中查找最接近嵌入空间中某个点的单词——例如,我们可以查找最接近另一个单词(如“cat”)的单词。

\n
def print_closest_words(vec, n=5):\n    dists = torch.norm(glove.vectors - vec, dim=1)     # compute distances to all words\n    lst = sorted(enumerate(dists.numpy()), key=lambda x: x[1]) # sort by distance\n    for idx, difference in lst[1:n+1]:                         # take the top n\n        print(glove.itos[idx], difference)\n\nprint_closest_words(glove["cat"], n=10)\n
Run Code Online (Sandbox Code Playgroud)\n
\n

狗 1.8846031

\n
\n
\n

兔子 2.4572797

\n
\n
\n

猴子2.8102052

\n
\n
\n

猫 2.8972247

\n
\n
\n

大鼠 2.9455352

\n
\n
\n

野兽2.9878407

\n
\n
\n

怪物3.0022194

\n
\n
\n

宠物 3.0396757

\n
\n
\n

蛇3.0617998

\n
\n
\n

小狗 3.0644655

\n
\n
print_closest_words(glove[\'nurse\'])\n
Run Code Online (Sandbox Code Playgroud)\n
\n

医生3.1274529

\n
\n
\n

牙医 3.1306612

\n
\n
\n

护士 3.26872

\n
\n
\n

儿科医生3.3212206

\n
\n
\n

辅导员3.3987114

\n
\n
print_closest_words(glove[\'computer\'])\n
Run Code Online (Sandbox Code Playgroud)\n
\n

计算机 2.4362664

\n
\n
\n

软件2.926823

\n
\n
\n

技术3.190351

\n
\n
\n

电子3.5067408

\n
\n
\n

计算3.5999784

\n
\n

我们还可以看看哪些单词最接近两个单词的中点:

\n
print_closest_words((glove[\'happy\'] + glove[\'sad\']) / 2)\n
Run Code Online (Sandbox Code Playgroud)\n
\n

快乐1.9199749

\n
\n
\n

感觉2.3604643

\n
\n
\n

抱歉2.4984782

\n
\n
\n

几乎 2.52593

\n
\n
\n

想象2.5652788

\n
\n
print_closest_words((glove[\'lake\'] + glove[\'building\']) / 2)\n
Run Code Online (Sandbox Code Playgroud)\n
\n

周边3.0698414

\n
\n
\n

附近 3.1112068

\n
\n
\n

桥3.1585503

\n
\n
\n

沿3.1610188

\n
\n
\n

岸边3.1618817

\n
\n

类比

\n

GloVe 向量的一个令人惊讶的方面是嵌入空间中的方向可能是有意义的。GloVe 向量的结构往往存在某些类比关系:

\n

国王\xe2\x88\x92男人+女人\xe2\x89\x88queen

\n
print_closest_words(glove[\'king\'] - glove[\'man\'] + glove[\'woman\'])\n
Run Code Online (Sandbox Code Playgroud)\n
\n

女王2.8391209

\n
\n
\n

王子 3.6610038

\n
\n
\n

伊丽莎白 3.7152522

\n
\n
\n

女儿3.8317878

\n
\n
\n

寡妇 3.8493774

\n
\n

我们得到合理的答案,例如“女王”、“王位”以及现任女王的名字。

\n

我们同样可以反过来打个比方:

\n
print_closest_words(glove[\'queen\'] - glove[\'woman\'] + glove[\'man\'])\n
Run Code Online (Sandbox Code Playgroud)\n
\n

国王2.8391209

\n
\n
\n

王子3.2508988

\n
\n
\n

皇冠 3.4485192

\n
\n
\n

骑士3.5587437

\n
\n
\n

加冕3.6198905

\n
\n

或者,沿着性别轴尝试不同但相关的类比:

\n
print_closest_words(glove[\'king\'] - glove[\'prince\'] + glove[\'princess\'])\n
Run Code Online (Sandbox Code Playgroud)\n
\n

女王3.1845968

\n
\n
\n

国王3.9103293

\n
\n
\n

新娘4.285721

\n
\n
\n

女士 4.299571

\n
\n
\n

妹妹4.421178

\n
\n
print_closest_words(glove[\'uncle\'] - glove[\'man\'] + glove[\'woman\'])\n
Run Code Online (Sandbox Code Playgroud)\n
\n

祖母2.323353

\n
\n
\n

阿姨2.3527892

\n
\n
\n

孙女 2.3615322

\n
\n
\n

女儿2.4039288

\n
\n
\n

叔叔2.6026237

\n
\n
print_closest_words(glove[\'grandmother\'] - glove[\'mother\'] + glove[\'father\'])\n
Run Code Online (Sandbox Code Playgroud)\n
\n

叔叔2.0784423

\n
\n
\n

父亲2.0912483

\n
\n
\n

孙子2.2965577

\n
\n
\n

侄子2.353551

\n
\n
\n

长辈 2.4274695

\n
\n
print_closest_words(glove[\'old\'] - glove[\'young\'] + glove[\'father\'])\n
Run Code Online (Sandbox Code Playgroud)\n
\n

父亲4.0326614

\n
\n
\n

儿子 4.4065413

\n
\n
\n

祖父 4.51851

\n
\n
\n

孙子 4.722089

\n
\n
\n

女儿 4.786716

\n
\n

我们可以将嵌入朝着“善”或“恶”的方向移动:

\n
print_closest_words(glove[\'programmer\'] - glove[\'bad\'] + glove[\'good\'])\n
Run Code Online (Sandbox Code Playgroud)\n
\n

多功能 4.381561

\n
\n
\n

创意4.5690007

\n
\n
\n

企业家4.6343737

\n
\n
\n

启用 4.7177725

\n
\n
\n

智能4.7349973

\n
\n
print_closest_words(glove[\'programmer\'] - glove[\'good\'] + glove[\'bad\'])\n
Run Code Online (Sandbox Code Playgroud)\n
\n

黑客3.8383653

\n
\n
\n

故障4.003873

\n
\n
\n

始创者4.041952

\n
\n
\n

黑客攻击 4.047719

\n
\n
\n

序列号 4.2250676

\n
\n

词向量中的偏差

\n

机器学习模型有一种“公平”的感觉,因为模型无需人工干预即可做出决策。然而,模型可以并且确实学习训练数据中存在的任何偏差!

\n

GloVe 向量看起来足够无害:它们只是某些嵌入空间中单词的表示。即便如此,我们将证明 GloVe 向量的结构编码了它们所训练的文本中存在的日常偏差。

\n

我们将从一个示例类比开始:

\n

医生\xe2\x88\x92男人+女人\xe2\x89\x88??

\n

让我们使用 GloVe 向量来寻找上述类比的答案:

\n
print_closest_words(glove[\'doctor\'] - glove[\'man\'] + glove[\'woman\'])\n
Run Code Online (Sandbox Code Playgroud)\n
\n

护士3.1355345

\n
\n
\n

怀孕3.7805371

\n
\n
\n

儿童 3.78347

\n
\n
\n

女人 3.8643107

\n
\n
\n

妈妈3.922231

\n
\n

医生\xe2\x88\x92man+woman\xe2\x89\x88护士的类比非常令人担忧。只是为了验证一下,如果我们翻转性别术语,不会出现相同的结果:

\n
print_closest_words(glove[\'doctor\'] - glove[\'woman\'] + glove[\'man\'])\n
Run Code Online (Sandbox Code Playgroud)\n
\n

男人3.9335632

\n
\n
\n

同事3.975502

\n
\n
\n

他自己 3.9847782

\n
\n
\n

兄弟3.9997008

\n
\n
\n

另一个4.029071

\n
\n

我们在其他职业中也看到了类似的性别偏见。

\n
print_closest_words(glove[\'programmer\'] - glove[\'man\'] + glove[\'woman\'])\n
Run Code Online (Sandbox Code Playgroud)\n
\n

神童3.6688528

\n
\n
\n

心理治疗师3.8069527

\n
\n
\n

治疗师3.8087194

\n
\n
\n

介绍3.9064546

\n
\n
\n

瑞典出生的 4.1178856

\n
\n

除了第一个结果之外,其他单词都与编程无关!相反,如果我们翻转性别术语,我们会得到截然不同的结果:

\n
print_closest_words(glove[\'programmer\'] - glove[\'woman\'] + glove[\'man\'])\n
Run Code Online (Sandbox Code Playgroud)\n
\n

设置4.002241

\n
\n
\n

创新者4.0661883

\n
\n
\n

程序员4.1729574

\n
\n
\n

黑客4.2256656

\n
\n
\n

天才4.3644104

\n
\n

以下是“工程师”的结果:

\n
print_closest_words(glove[\'engineer\'] - glove[\'man\'] + glove[\'woman\'])\n
Run Code Online (Sandbox Code Playgroud)\n
\n

技术员3.6926973

\n
\n
\n

机械师3.9212747

\n
\n
\n

先锋4.1543956

\n
\n
\n

开拓4.1880875

\n
\n
\n

教育家4.2264576

\n
\n
print_closest_words(glove[\'engineer\'] - glove[\'woman\'] + glove[\'man\'])\n
Run Code Online (Sandbox Code Playgroud)\n
\n

生成器 4.3523865

\n
\n
\n

机械师 4.402976

\n
\n
\n

工程师4.477985

\n
\n
\n

工作过 4.5281315

\n
\n
\n

替换4.600204

\n
\n