zl程序教程

您现在的位置是:首页 >  其他

当前栏目

NLP经典书籍鱼书笔记2-单词的分布式表示

2023-03-07 09:02:50 时间

同义词词典

特点

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

<!--MORE-->

WordNet

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

同义词词典问题

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

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

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

基于计数方法

基于python的语料库的预处理

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

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

文本切割

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

单词和单词ID对应关系

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
word_to_id
{'you': 0, 'say': 1, 'goodbye': 2, 'and': 3, 'i': 4, 'hello': 5, '.': 6}
id_to_word
{0: 'you', 1: 'say', 2: 'goodbye', 3: 'and', 4: 'i', 5: 'hello', 6: '.'}
id_to_word[1]
'say'
word_to_id["hello"]
5

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

# 使用列表解析式

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])

函数封装功能

# 这个函数会经常使用

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
text = "You say goodbye and I say hello."
corpus, word_to_id, id_to_word = preprocess(text)
corpus
array([0, 1, 2, 3, 4, 1, 5, 6])
word_to_id
{'you': 0, 'say': 1, 'goodbye': 2, 'and': 3, 'i': 4, 'hello': 5, '.': 6}
id_to_word
{0: 'you', 1: 'say', 2: 'goodbye', 3: 'and', 4: 'i', 5: 'hello', 6: '.'}

单词的分布式表示

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

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

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

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

共现矩阵

生成原理

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

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

手动生成共现矩阵

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)
C[0]
array([0, 1, 0, 0, 0, 0, 0], dtype=int32)
C[4]
array([0, 1, 0, 1, 0, 0, 0], dtype=int32)
C[word_to_id["goodbye"]]
array([0, 1, 0, 1, 0, 0, 0], dtype=int32)

自动生成共现矩阵

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. 余弦相似度(单词向量的相似度用)

下面是具体的计算过程:

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

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)

案例演示

求余弦相似度的案例:

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之间的相似度挺高的;实际也是如此。

相似单词的降序排列

代码实现

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

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 
# argsort的使用说明:排序的数组的元素的原索引值

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

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

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

如果是降序排列:

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

案例演示

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