当前位置:   article > 正文

《深度学习进阶:自然语言处理(第2章)》-读书笔记

《深度学习进阶:自然语言处理(第2章)》-读书笔记

第2章 自然语言和单词的分布式表示

2.2 同义词词典

在自然语言处理领域,最著名的同义词词典是 WordNet。WordNet 是普林斯顿大学于1985年开始开发的同义词词典,迄今已用于许多研究,并活跃于各种自然语言处理应用中。使用 WordNet,可以获得单词的近义词,或者利用单词网络。使用单词网络,可以计算单词之间的相似度。

  • 附录B

通过 Python 利用 WordNet,可以使用 NLTK(Natural Language Toolkit,自然语言处理工具包)这个库。NLTK 是用于自然语言处理的 Python 库,其中包含许多自然语言处理相关的便捷功能,比如词性标注、句法分析、信息抽取和语义分析等。

from nltk.corpus import wordnet
# 1. 看单词 car 存在多少个不同的含义(获得 car 的同义词)
print(wordnet.synsets('car'))

out:
[Synset('car.n.01'), 
 Synset('car.n.02'),
 Synset('car.n.03'),
 Synset('car.n.04'),
 Synset('cable_car.n.01')]  # 单词名称.属性(名词、动词等).簇的索引

# 2. 确认“car.n.01”这一标题词指定的同义词的含义
car = wordnet.synset('car.n.01') # 同义词簇
print(car.definition())

out:
'a motor vehicle with four wheels; usually propelled by an internal combustion engine'

# 3. 使用 lemma_names() 方法,可以获得同义词簇中存在的单词名称
print(car.lemma_names())

out:
['car', 'auto', 'automobile', 'machine', 'motorcar']
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

同义词词典的问题:

  1. 难以顺应时代变化:随着时间的推移,新词不断出现,而那些落满尘埃的旧词不知哪天就会被遗忘。比如,“众筹”(crowdfunding)就是一个最近才开始使用的新词。另外,语言的含义也会随着时间的推移而变化。
  2. 人力成本高:制作词典需要巨大的人力成本。以英文为例,据说现有的英文单词总数超过 1000 万个。在极端情况下,还需要对如此大规模的单词进行单词之间的关联。
  3. 无法表示单词的微妙差异:同义词词典中将含义相近的单词作为近义词分到一组。但实际上,即使是含义相近的单词,也有细微的差别。比如,vintage 和 retro 虽然表示相同的含义,但是用法不同,而这种细微的差别在同义词词典中是无法表示出来的(让人来解释是相当困难的)。

2.3 基于计数的方法

语料库就是大量的文本数据。不过,语料库并不是胡乱收集数据,一般收集的都是用于自然语言处理研究和应用的文本数据。其中的文章都是由人写出来的。换句话说,语料库中包含了大量的关于自然语言的实践知识,即文章的写作方法、单词的选择方法和单词含义等。基于计数的方法的目标就是从这些富有实践知识的语料库中,自动且高效地提取本质。

  • 基于 Python 的语料库的预处理
def preprocess(text):
    """准备语料库(预处理)"""
    text = text.lower()
    text = text.replace('.', ' .')
    words = text.split(' ')
    word_to_id = {}
    id_to_word = {}
    for word in words:
        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
    corpus = np.array([word_to_id[w] for w in words])
    # corpus:单词ID列表,word_to_id:单词到单词ID的字典,id_to_word:单词ID到单词的字典
    return corpus, word_to_id, id_to_word
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
text = 'You say goodbye and I say hello.'
corpus, word_to_id, id_to_word = preprocess(text)
print(corpus)  # [0 1 2 3 4 1 5 6]
print(id_to_word)  # {0: 'you', 1: 'say', 2: 'goodbye', 3: 'and', 4: 'i', 5: 'hello', 6: '.'}
  • 1
  • 2
  • 3
  • 4
  • 单词的分布式表示

使用 RGB 这样的向量表示可以更准确地指定颜色,并且这种基于三原色的表示方式很紧凑,也更容易让人想象到具体是什么颜色。比如,即便不知道“深绯”是什么样的颜色,但如果知道它的 (R, G, B) = (201, 23, 30),就至少可以知道它是红色系的颜色。此外,颜色之间的关联性(是否是相似的颜色)也更容易通过向量表示来判断和量化。

关注能准确把握单词含义的向量表示。在自然语言处理领域,这称为分布式表示

  • 分布式假设

“某个单词的含义由它周围的单词形成”,这称为分布式假设(distributional hypothesis)。

分布式假设所表达的理念非常简单。单词本身没有含义,单词含义由它所在的上下文(语境)形成。的确,含义相同的单词经常出现在相同的语境中。比如“I drink beer.”和“We drink wine.”,drink 的附近常有饮料出现。另外,从“I guzzle beer.”和“We guzzle wine.”可知,guzzle 和 drink 所在的语境相似。进而我们可以推测出,guzzle 和 drink 是近义词。

上下文是指某个居中单词的周围词汇。将上下文的大小(即周围的单词有多少个)称为窗口大小(window size)。窗口大小为1,上下文包含左右各1个单词;窗口大小为2,上下文包含左右各2个单词。

  • 共现矩阵

以句子“you say goodbye and i say hello.”为例:

单词 you 的上下文仅有 say,可用如下表格表示单词 you 的上下文共现的单词的频数:

yousaygoodbyeandihello.
you0100000

这也意味着可以用向量 [0, 1, 0, 0, 0, 0, 0] 表示单词 you。

用表格汇总各个单词的上下文中共同出现的单词的频数如下:

yousaygoodbyeandihello.
you0100000
say1010110
goodbye0101000
and0010100
i0101000
hello0100001
.0000010

这个表格的各行对应相应单词的向量。因为表格呈矩阵状,所以称为共现矩阵(co-occurence matrix)。

实现一个直接从语料库生成共现矩阵的函数:

def create_co_matrix(corpus, vocab_size, window_size=1):
    """直接从语料库生成共现矩阵的函数
       corpus:单词 ID 列表,vocab_size:词汇个数,window_size:窗口大小
    """
    corpus_size = len(corpus)  # 获取总单词个数
    co_matrix = np.zeros((vocab_size, vocab_size), dtype=np.int32)  # 初始化一个全零方阵
    for idx, word_id in enumerate(corpus):  # 获取每个单词的索引及ID
        for i in range(1, window_size + 1):  # 根据窗口大小迭代将每个单词的上下文加1
            left_idx = idx - i
            right_idx = idx + i
            if left_idx >= 0:
                left_word_id = corpus[left_idx]
                co_matrix[word_id, left_word_id] += 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
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 向量间的相似度

测量向量间的相似度有很多方法,其中具有代表性的方法有向量内积欧式距离等。在测量单词的向量表示的相似度方面,余弦相似度(cosine similarity)是很常用的。

设有 x = ( x 1 , x 2 , x 3 , ⋅ ⋅ ⋅ , x n ) \mathcal{x} = (x_1, x_2, x_3, ··· , x_n) x=(x1,x2,x3,⋅⋅⋅,xn) y = ( y 1 , y 2 , y 3 , ⋅ ⋅ ⋅ , y n ) \mathcal{y} = (y_1, y_2, y_3, ··· , y_n) y=(y1,y2,y3,⋅⋅⋅,yn) 两个向量,它们之间的余弦相似度的定义如下式:
s i m i l a r i t y ( x , y ) = x ⋅ y ∣ ∣ x ∣ ∣ ∣ ∣ y ∣ ∣ = x 1 y 1 + ⋯ + x n y n x 1 2 + ⋯ + x n 2 y 1 2 + ⋯ + y n 2 similarity(\mathcal{x},\mathcal{y})=\frac{\mathcal{x}·\mathcal{y}}{||\mathcal{x}||||\mathcal{y}||}=\frac{x_1y_1+\dots+x_ny_n}{\sqrt{x_1^2+\dots+x_n^2}\sqrt{y_1^2+\dots+y_n^2}} similarity(x,y)=∣∣x∣∣∣∣y∣∣xy=x12++xn2 y12++yn2 x1y1++xnyn

余弦相似度的要点是先对向量进行正规化,再求它们的内积。直观地表示了“两个向量在多大程度上指向同一方向”。两个向量完全指向相同的方向时,余弦相似度为 1;完全指向相反的方向时,余弦相似度为 −1。

def cos_similarity(x, y, eps=1e-8):  # eps微小值,防止零向量
    """计算余弦相似度"""
    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
  • 相似单词的排序

当某个单词被作为查询词时,将与这个查询词相似的单词按降序显示出来。即相似度排序。

def most_similar(query, word_to_id, id_to_word, word_matrix, top=5):
    """单词相似度排序
       query:查询词,word_matrix:单词向量矩阵, top=5:显示前几位
    """
    # 1、取出查询词
    if query not in word_to_id:
        print('%s is not found' % query)
        return
    print('\n[query] ' + query)
    query_id = word_to_id[query]
    query_vec = word_matrix[query_id]
    # 2、计算余弦相似度
    vocab_size = len(id_to_word)
    similarity = np.zeros(vocab_size)
    for i in range(vocab_size):
        similarity[i] = cos_similarity(word_matrix[i], query_vec)
    # 3、基于余弦相似度,按降序输出值
    count = 0
    for i in (-1 * similarity).argsort():  
        # argsort()方法可以按升序对 NumPy 数组进行排序,并返回排序后的索引
        if id_to_word[i] == query:
            continue
        print(' %s: %s' % (id_to_word[i], similarity[i]))
        count += 1
        if count >= top:
            return
  • 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

2.4 基于计数的方法的改进

  • 点互信息

考虑某个语料库中 the 和 car 共现的情况。在这种情况下,我们会看到很多“…the car…”这样的短语。因此,它们的共现次数将会很大。另外,car 和 drive 也明显有很强的相关性。但是,如果只看单词的出现次数,那么与 drive 相比,the 和 car 的相关性更强。这意味着,仅仅因为 the 是个常用词,它就被认为与 car 有很强的相关性。

为了解决这一问题,可以使用点互信息(Pointwise Mutual Information,PMI)这一指标。对于随机变量 x 和 y,它们的 PMI 定义如下:
P M I ( x , y ) = log ⁡ 2 P ( x , y ) P ( x ) P ( y ) PMI(x,y)=\log_2{\frac{P(x,y)}{P(x)P(y)}} PMI(x,y)=log2P(x)P(y)P(x,y)
其中,P(x) 表示 x 发生的概率,P(y) 表示 y 发生的概率,P(x, y) 表示 x 和 y 同时发生的概率。PMI 的值越高,表明相关性越强。

为了解决两个单词的共现次数为 0 时, l o g 2 0 = − ∞ log_20 = −∞ log20= 问题,实践上我们会使用正的点互信息(Positive PMI,PPMI):
P P M I ( x , y ) = max ⁡ ( 0 , P M I ( x , y ) ) PPMI(x, y) = \max(0,PMI(x,y)) PPMI(x,y)=max(0,PMI(x,y))
来实现将共现矩阵转化为 PPMI 矩阵的函数:

def ppmi(C, verbose=False, eps=1e-8):
    """将共现矩阵转化为 PPMI 矩阵的函数
       C:共现矩阵,verbose:决定是否输出运行情况的标志。当处理大语料库时,设置 verbose=True,可以用于确认运行情况
    """
    M = np.zeros_like(C, dtype=np.float32)
    N = np.sum(C)
    S = np.sum(C, axis=0)
    total = C.shape[0] * C.shape[1]
    cnt = 0
    for i in range(C.shape[0]):
        for j in range(C.shape[1]):
            pmi = np.log2(C[i, j] * N / (S[j]*S[i]) + eps)
            M[i, j] = max(0, pmi)
            if verbose:
                cnt += 1
                if cnt % (total//100+1) == 0:
                    print('%.1f%% done' % (100*cnt/total))
    return M
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
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_co_matrix(corpus, vocab_size)
W = ppmi(C)
np.set_printoptions(precision=3) # 有效位数为3位
print('covariance matrix')
print(C)
print('-'*50)
print('PPMI')
print(W)
out:
covariance matrix
[[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]]
--------------------------------------------------
PPMI
[[ 0.     1.807  0.     0.     0.     0.     0.   ]
 [ 1.807  0.     0.807  0.     0.807  0.807  0.   ]
 [ 0.     0.807  0.     1.807  0.     0.     0.   ]
 [ 0.     0.     1.807  0.     1.807  0.     0.   ]
 [ 0.     0.807  0.     1.807  0.     0.     0.   ]
 [ 0.     0.807  0.     0.     0.     0.     2.807]
 [ 0.     0.     0.     0.     0.     2.807  0.   ]]
  • 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

这个 PPMI 矩阵存在一个很大的问题,那就是随着语料库的词汇量增加,各个单词向量的维数也会增加。如果语料库的词汇量达到 10 万,则单词向量的维数也同样会达到 10 万。实际上,处理 10 万维向量是不现实的。

另外,这个矩阵中很多元素都是 0。这表明向量中的绝大多数元素并不重要,也就是说,每个元素拥有的“重要性”很低。另外,这样的向量也容易受到噪声影响,稳健性差。对于这些问题,一个常见的方法是向量降维

2.5 降维

降维(dimensionality reduction),顾名思义,就是减少向量维度(特征个数)。但是,并不是简单地减少,而是在尽量保留“重要信息”的基础上减少。

  • 奇异值分解

奇异值分解(Singular Value Decomposition,SVD)将任意矩阵分解为 3 个矩阵的乘积,如下式所示:
X = U S V T \mathbf{X}=\mathbf{U}\mathbf{S}\mathbf{V}^\Tau X=USVT
SVD 将任意的矩阵 X 分解为 U、S、V 这 3 个矩阵的乘积,其中 U 和 V 是列向量彼此正交的正交矩阵,S 是除了对角线元素以外其余元素均为 0 的对角矩阵。

求解 U、S、V 矩阵:

  • U 矩阵:解特征方程 ( X X T ) α = λ α (\mathbf{X}\mathbf{X}^\Tau)\alpha=λ\alpha (XXT)α=λα ,特征向量组成的矩阵即为 U 矩阵。
  • V 矩阵:解特征方程 ( X T X ) α = λ α (\mathbf{X}^\Tau\mathbf{X})\alpha=λ\alpha (XTX)α=λα ,特征向量组成的矩阵即为 V 矩阵。
  • S 矩阵:由上述结果带入原式即可解出 S 矩阵。

继续阅读:
《深度学习进阶:自然语言处理(第1章)》-读书笔记
《深度学习进阶:自然语言处理(第2章)》-读书笔记
《深度学习进阶:自然语言处理(第3章)》-读书笔记
《深度学习进阶:自然语言处理(第4章)》-读书笔记
《深度学习进阶:自然语言处理(第5章)》-读书笔记
《深度学习进阶:自然语言处理(第6章)》-读书笔记
《深度学习进阶:自然语言处理(第7章)》-读书笔记
《深度学习进阶:自然语言处理(第8章)》-读书笔记

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/667167
推荐阅读
  

闽ICP备14008679号