同义词词典
特点
- NLP中常用的不是《新华字典》,而是一种被称为同义词词典的词典
- 在同义词词典中,具有相同含义或者类似含义的单词被归类到同一个组别中
- NLP中会定义单词之间的粒度更细的关系,比如“上位-下位”“整体-部分”
WordNet
WordNet是NLP中常用的同义词词典,普林斯顿大学在1985年开发的;在NLTK模块中已经存在这个同义词词典
同义词词典问题
- 难以顺应时代变化:新词不断出现;旧词也可能有了新意
- 制作字典需要巨大的人力成本
- 无法表示单词的微妙关系
为了解决人工定义单词含义的方法存在的问题,提出两种方案:
- 基于计数的方法
- 基于神经网络的推理的方法
基于计数方法
基于python的语料库的预处理
NLP依赖于大量的语料库corpus;语料库就是大量的文本数据。著名的语料库:
- Wikipedia
- Google News
- 莎士比亚等伟大作家的作品集也会被用作语料库
文本切割
代码语言:javascript复制text = "You say goodbye and I say hello."
代码语言:javascript复制text = text.lower() # 小写
text = text.replace('.',' .') # 将最后面的点用 空格 点代替
text
代码语言:javascript复制'you say goodbye and i say hello .'
代码语言:javascript复制words = text.split(' ')
words
代码语言:javascript复制['you', 'say', 'goodbye', 'and', 'i', 'say', 'hello', '.']
代码语言:javascript复制import re
re.split('(W ) ?', text)
代码语言:javascript复制['you',
' ',
'say',
' ',
'goodbye',
' ',
'and',
' ',
'i',
' ',
'say',
' ',
'hello',
' .',
'']
单词和单词ID对应关系
代码语言:javascript复制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
代码语言:javascript复制word_to_id
代码语言:javascript复制{'you': 0, 'say': 1, 'goodbye': 2, 'and': 3, 'i': 4, 'hello': 5, '.': 6}
代码语言:javascript复制id_to_word
代码语言:javascript复制{0: 'you', 1: 'say', 2: 'goodbye', 3: 'and', 4: 'i', 5: 'hello', 6: '.'}
代码语言:javascript复制id_to_word[1]
代码语言:javascript复制'say'
代码语言:javascript复制word_to_id["hello"]
代码语言:javascript复制5
单词;列表转成单词ID列表
代码语言:javascript复制# 使用列表解析式
import numpy as np
corpus = [word_to_id[word] for word in words] # 单词对应的单词id列表
corpus = np.array(corpus)
corpus
代码语言:javascript复制array([0, 1, 2, 3, 4, 1, 5, 6])
函数封装功能
代码语言:javascript复制# 这个函数会经常使用
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
代码语言:javascript复制text = "You say goodbye and I say hello."
corpus, word_to_id, id_to_word = preprocess(text)
代码语言:javascript复制corpus
代码语言:javascript复制array([0, 1, 2, 3, 4, 1, 5, 6])
代码语言:javascript复制word_to_id
代码语言:javascript复制{'you': 0, 'say': 1, 'goodbye': 2, 'and': 3, 'i': 4, 'hello': 5, '.': 6}
代码语言:javascript复制id_to_word
代码语言:javascript复制{0: 'you', 1: 'say', 2: 'goodbye', 3: 'and', 4: 'i', 5: 'hello', 6: '.'}
单词的分布式表示
单词的分布式表示将单词表示为固定长度的向量。
这种向量是密集向量,即向量的大多数元素是非0实数表示。相对应的是稀疏向量,大多数都是0。
分布式假设:某个单词的含义由它周围的单词(上下文,语境)形成的。
窗口大小:周围的单词由多少个,window size
共现矩阵
生成原理
基于计数的方法:在关注某个单词的情况下,对它的周围出现了多少次什么单词进行计数,然后再汇总
代码语言:javascript复制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
代码语言:javascript复制{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
手动生成共现矩阵
代码语言:javascript复制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
代码语言:javascript复制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)
代码语言:javascript复制C[0]
代码语言:javascript复制array([0, 1, 0, 0, 0, 0, 0], dtype=int32)
代码语言:javascript复制C[4]
代码语言:javascript复制array([0, 1, 0, 1, 0, 0, 0], dtype=int32)
代码语言:javascript复制C[word_to_id["goodbye"]]
代码语言:javascript复制array([0, 1, 0, 1, 0, 0, 0], dtype=int32)
自动生成共现矩阵
代码语言:javascript复制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
向量间的相似度
代码实现
常用来表示向量间相似度的方法:
- 向量内积
- 欧氏距离
- 余弦相似度(单词向量的相似度用)
下面是具体的计算过程:
代码语言:javascript复制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
代码语言:javascript复制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)
案例演示
求余弦相似度的案例:
代码语言:javascript复制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)
代码语言:javascript复制0.7071067691154799
余弦相似度的值在-1到1之间,这个值说明you和i之间的相似度挺高的;实际也是如此。
相似单词的降序排列
代码实现
和某个查询词相似的单词按照降序显示出来
代码语言:javascript复制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
代码语言:javascript复制# argsort的使用说明:排序的数组的元素的原索引值
k = np.array([100,-20,40])
k.argsort()
代码语言:javascript复制array([1, 2, 0])
对k数组进行升序排列:[-20,40,100]
;-20
在原数组中的位置是1
,40
的索引是2
,100
的位置0
如果是降序排列:
代码语言:javascript复制(-k).argsort() # 降序 [-100,20,-40] ----> [-100,-40, 20] 在原数组中的位置 [0,2,1]
代码语言:javascript复制array([0, 2, 1])
案例演示
代码语言:javascript复制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)
代码语言:javascript复制you
goodbye: 0.7071067691154799
i: 0.7071067691154799
hello: 0.7071067691154799
say: 0.0
and: 0.0