Fork me on GitHub

NLP学习2-单词的分布式表示

同义词词典

特点

  1. NLP中常用的不是《新华字典》,而是一种被称为同义词词典的词典
  2. 在同义词词典中,具有相同含义或者类似含义的单词被归类到同一个组别中
  3. NLP中会定义单词之间的粒度更细的关系,比如“上位-下位”“整体-部分”

WordNet

WordNet是NLP中常用的同义词词典,普林斯顿大学在1985年开发的;在NLTK模块中已经存在这个同义词词典

同义词词典问题

  1. 难以顺应时代变化:新词不断出现;旧词也可能有了新意
  2. 制作字典需要巨大的人力成本
  3. 无法表示单词的微妙关系

为了解决人工定义单词含义的方法存在的问题,提出两种方案:

  • 基于计数的方法
  • 基于神经网络的推理的方法

基于计数方法

基于python的语料库的预处理

NLP依赖于大量的语料库corpus;语料库就是大量的文本数据。著名的语料库:

  1. Wikipedia
  2. Google News
  3. 莎士比亚等伟大作家的作品集也会被用作语料库

文本切割

1
text = "You say goodbye and I say hello."
1
2
3
text = text.lower()  # 小写
text = text.replace('.',' .') # 将最后面的点用 空格+点代替
text
'you say goodbye and i say hello .'
1
2
words = text.split(' ')
words
['you', 'say', 'goodbye', 'and', 'i', 'say', 'hello', '.']
1
2
import re
re.split('(\W+) ?', text)
['you',
 ' ',
 'say',
 ' ',
 'goodbye',
 ' ',
 'and',
 ' ',
 'i',
 ' ',
 'say',
 ' ',
 'hello',
 ' .',
 '']

单词和单词ID对应关系

1
2
3
4
5
6
7
8
9
word_to_id = {}
id_to_word = {}

for word in words:
# 如果word不在word_to_id中,分别添加
if word not in word_to_id: # 实际上是word_to_id.keys()
new_id = len(word_to_id)
word_to_id[word] = new_id
id_to_word[new_id] = word
1
word_to_id
{'you': 0, 'say': 1, 'goodbye': 2, 'and': 3, 'i': 4, 'hello': 5, '.': 6}
1
id_to_word
{0: 'you', 1: 'say', 2: 'goodbye', 3: 'and', 4: 'i', 5: 'hello', 6: '.'}
1
id_to_word[1]
'say'
1
word_to_id["hello"]
5

单词;列表转成单词ID列表

1
2
3
4
5
6
# 使用列表解析式

import numpy as np
corpus = [word_to_id[word] for word in words] # 单词对应的单词id列表
corpus = np.array(corpus)
corpus
array([0, 1, 2, 3, 4, 1, 5, 6])

函数封装功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 这个函数会经常使用

def preprocess(text):
text = text.lower() # 转成小写
text = text.replace('.', ' .') # 增加空格
words = text.split(' ') # 切割

# 单词和单词ID的对应关系
word_to_id = {}
id_to_word = {}
for word in words:
if word not in word_to_id.keys(): # 原文 if word not in word_to_id:
new_id = len(word_to_id)
word_to_id[word] = new_id
id_to_word[new_id] = word
# 单词列表-----> 单词ID列表
corpus = np.array([word_to_id[w] for w in words])

return corpus, word_to_id, id_to_word
1
2
text = "You say goodbye and I say hello."
corpus, word_to_id, id_to_word = preprocess(text)
1
corpus
array([0, 1, 2, 3, 4, 1, 5, 6])
1
word_to_id
{'you': 0, 'say': 1, 'goodbye': 2, 'and': 3, 'i': 4, 'hello': 5, '.': 6}
1
id_to_word
{0: 'you', 1: 'say', 2: 'goodbye', 3: 'and', 4: 'i', 5: 'hello', 6: '.'}

单词的分布式表示

单词的分布式表示将单词表示为固定长度的向量。

这种向量是密集向量,即向量的大多数元素是非0实数表示。相对应的是稀疏向量,大多数都是0。

分布式假设:某个单词的含义由它周围的单词(上下文,语境)形成的。

窗口大小:周围的单词由多少个,window size

共现矩阵

生成原理

基于计数的方法:在关注某个单词的情况下,对它的周围出现了多少次什么单词进行计数,然后再汇总

1
2
3
4
5
6
7
8
import numpy as np
import sys
sys.path.append('..')

text = "You say goodbye and I say hello."
corpus, word_to_id, id_to_word = preprocess(text)

id_to_word
{0: 'you', 1: 'say', 2: 'goodbye', 3: 'and', 4: 'i', 5: 'hello', 6: '.'}

下面统计每个单词的上下文所包含的单词的词频数,比如单词you:上下文就只有say这个单词。

那么单词you用向量可以表示为: [0,1,0,0,0,0,0]

在比如单词say用向量可以表示为:[1,0,1,0,1,1,0]

结果汇总(图2-7)

上图的表格呈矩阵状,所以称之为共现矩阵co-occurence matrix

手动生成共现矩阵

1
2
3
4
5
6
7
8
9
10
11
C = np.array([
[0, 1, 0, 0, 0, 0, 0], # 0-you
[1, 0, 1, 0, 1, 1, 0], # 1-say
[0, 1, 0, 1, 0, 0, 0], # 2-hello
[0, 0, 1, 0, 1, 0, 0],
[0, 1, 0, 1, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 1, 0],
], dtype=np.int32)

C
array([[0, 1, 0, 0, 0, 0, 0],
       [1, 0, 1, 0, 1, 1, 0],
       [0, 1, 0, 1, 0, 0, 0],
       [0, 0, 1, 0, 1, 0, 0],
       [0, 1, 0, 1, 0, 0, 0],
       [0, 1, 0, 0, 0, 0, 1],
       [0, 0, 0, 0, 0, 1, 0]], dtype=int32)
1
C[0]
array([0, 1, 0, 0, 0, 0, 0], dtype=int32)
1
C[4]
array([0, 1, 0, 1, 0, 0, 0], dtype=int32)
1
C[word_to_id["goodbye"]]
array([0, 1, 0, 1, 0, 0, 0], dtype=int32)

自动生成共现矩阵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def create_to_matrix(corpus, vocab_size,window_size=1):
"""
corpus:单词ID列表
vocab_size:词汇个数
window_size:窗口大小
"""

corpus_size = len(corpus)
# 全0矩阵初始化
co_matrix = np.zeros((vocab_size, vocab_size), dtype=np.int32)

for idx, word_id in enumerate(corpus): # 遍历语料库中的每个单词
for i in range(1, window_size + 1): # 遍历窗口中的数据;是否超出语料库的左右端
left_idx = idx - 1 # 左右索引值
right_idx = idx + 1

if left_idx >= 0: # 判断左索引大于0的时候
left_word_id = corpus[left_idx] # 取出索引对应的word_id
co_matrix[word_id, left_word_id] += 1 # 对应的位置赋值1

if right_idx < corpus_size: # 右索引小于整体的语料库长度
right_word_id = corpus[right_idx]
co_matrix[word_id, right_word_id] += 1

return co_matrix

向量间的相似度

代码实现

常用来表示向量间相似度的方法:

  1. 向量内积
  2. 欧氏距离
  3. 余弦相似度(单词向量的相似度用)

下面是具体的计算过程:

1
2
3
4
5
6
7
8
9
def cos_similarity(x, y):
"""
余弦相似度的计算
1、先对x和y两个数组进行正规化
2、再求内积
"""
nx = x / np.sqrt(np.sum(x ** 2)) # x的正规化
ny = y / np.sqrt(np.sum(y ** 2)) # y的正规化
return np.dot(nx,ny)

上面的代码有一个问题:全0向量被赋值给参数时,会出现"除数为0"的错误。解决办法:在执行除法时,加上一个微小值eps

1
2
3
4
5
6
7
def cos_similarity(x, y, eps=1e-8):
"""
优化版本
"""
nx = x / (np.sqrt(np.sum(x ** 2)) + eps) # x的正规化
ny = y / (np.sqrt(np.sum(y ** 2)) + eps) # y的正规化
return np.dot(nx,ny)

案例演示

求余弦相似度的案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy as np
import sys
sys.path.append('..')

text = "You say goodbye and I say hello."
corpus, word_to_id, id_to_word = preprocess(text)
vocab_size = len(word_to_id)

C = create_to_matrix(corpus, vocab_size) # 共现矩阵
c0 = C[word_to_id["you"]]
c1 = C[word_to_id["i"]]

cos_similarity(c0, c1)
0.7071067691154799

余弦相似度的值在-1到1之间,这个值说明you和i之间的相似度挺高的;实际也是如此。

相似单词的降序排列

代码实现

和某个查询词相似的单词按照降序显示出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def most_similar(query, word_to_id, id_to_word,word_matrix, top=5):
"""
query:查询单词
word_to_id:单词到单词ID
id_to_word:单词ID到单词
word_matrix:汇总了单词向量的矩阵,假定保存了与各行对应的单词向量(共现矩阵)
top:显示到前几位
"""

if query not in word_to_id: # 不存在查询词的处理
print(f"{query} is not found")
return

print(f'{query}')

query_id = word_to_id[query] # 先找到查询词的id
query_vec = word_matrix[query_id] # 从共现矩阵中找出对应id的向量

# 计算相似度
vocab_size = len(id_to_word) # 词汇总长度
similarity = np.zeros(vocab_size) # 相似度初始值;全0

for i in range(vocab_size): # 循环计算余弦相似度;
similarity[i] = cos_similarity(word_matrix[i], query_vec) # 赋值给对应的similarity的位置

# 基于余弦相似度降序输出值
count = 0
for i in (-1 * similarity).argsort(): # argsort是返回索引值
if id_to_word[i] == query:
continue

print(f'{id_to_word[i]}: {similarity[i]}')

count += 1
if count >= top:
return
1
2
3
4
5
# argsort的使用说明:排序的数组的元素的原索引值

k = np.array([100,-20,40])

k.argsort()
array([1, 2, 0])

对k数组进行升序排列:[-20,40,100]-20在原数组中的位置是140的索引是2100的位置0

如果是降序排列:

1
(-k).argsort()   # 降序 [-100,20,-40]  ---->  [-100,-40, 20] 在原数组中的位置 [0,2,1]
array([0, 2, 1])

案例演示

1
2
3
4
5
6
7
8
9
10
11
import numpy as np
import sys
sys.path.append('..')

text = "You say goodbye and I say hello."
corpus, word_to_id, id_to_word = preprocess(text)
vocab_size = len(word_to_id)

C = create_to_matrix(corpus, vocab_size) # 共现矩阵

most_similar("you", word_to_id, id_to_word, C, top=5)
you
goodbye: 0.7071067691154799
i: 0.7071067691154799
hello: 0.7071067691154799
say: 0.0
and: 0.0

本文标题:NLP学习2-单词的分布式表示

发布时间:2023年01月28日 - 22:01

原始链接:http://www.renpeter.cn/2023/01/28/%E9%B1%BC%E4%B9%A6%E7%AC%94%E8%AE%B02-%E5%8D%95%E8%AF%8D%E7%9A%84%E5%88%86%E5%B8%83%E5%BC%8F%E8%A1%A8%E7%A4%BA.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

Coffee or Tea