![531bd740cf388df52ea32534d19e3e37.png](https://i-blog.csdnimg.cn/blog_migrate/e8fe89dbd85bafe7a35aead07e937448.jpeg)
本项目主要讲解嵌入层的使用
欢迎点击项目连接,在K-Lab中在线运行及调试代码~
项目链接:科赛 - Kesci.com
嵌入层(Embedding)
迄今为止,我们使用了 一组独热编码过的n维数组来表示文本,每个索引都对应了一个token,每个索引处的值都表示了和对应单词在句子中出现的次数。这种方法会完全丢失输入中的结构性信息。
[0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
我们还用过一种one-hot编码来表示输入,其中每个token都由一个n维数组表示。
[[0. 0. 0. ... 0. 0. 0.]
[0. 0. 1. ... 0. 0. 0.]
[0. 0. 0. ... 0. 0. 0.]
...
[0. 0. 0. ... 0. 0. 0.]
[0. 0. 0. ... 0. 0. 0.]
[0. 0. 0. ... 0. 0. 0.]]
用这种方法,输入中的结构性信息得以保存,但它有两个主要缺点。当词汇表很大时,每个token的表示长度也会相应变得巨大,导致运算量的加大。同时,文本中的结构信息被保存了下来,但token的实际表示和其他token之间的关系并没有被存下来。
在这个课程项目中,我们会了解学习 嵌入层(embedding) 以及它是如何解决我们目前接触的各种方法的问题。
概述
- 目标: 表示文本中包含内在结构关系的token。
- 优点:
- 维度低,同时可以找到关系
- 解释性强的token表示
- 缺点:
- 无 !
- 其他方面: 有许多已经训练好的嵌入层可供选择。也可以训练自己的嵌入层。
嵌入层学习
嵌入层的主要思想是将文本中词条的长度表示处理化为定值,而不再考虑词汇表中的词条数量。因此每个词条的表示就变成了 [1 X D] 而不是 [1 X V] (V 是词汇表的大小, D 是嵌入层的大小,通常为 50, 100, 200, 300 )。同时词条表示中的数字也不再是0和1,而是D维潜在空间中表示该词条的浮点数。如果嵌入层确实已经获取了词条之间的关系,那么我们就可以查看这个潜在空间然后确认这些已知的关系(我们会这么做的)。
那么首先,应该如何学习嵌入层呢? 嵌入层的意义在于让词条的定义不再取决于它本身,而是它的上下文。这是几种不同的做法:
- 给出上下文中的单词,预测目标词 (CBOW - 连续词袋)
- 给定目标词,预测上下文的单词 (skip-gram)
- 给出一个词序列,预测下一个单词 (LM - 语言模型)
所有这些方法都需要创建数据来训练模型。句子中的每个单词都会成为目标词,上下文的单词会由一个 窗口(window) 决定。下图显示了skip-gram 的过程,图中展示的窗口大小为 2。我们会对语料库中的每个句子都进行这个操作,从而生产出适用于非监督式学习的训练集。因为我们并没有对应上下文单词的正式标签,所以这会是一种非监督式学习技巧。核心思路是,相似的目标词汇会和相似的上下文一起出现,我们 可以通过重复训练模型学习这种(上下文,目标词之间的)匹配关系。
![6bfbd6dae5fa4d4347202c531889014b.png](https://i-blog.csdnimg.cn/blog_migrate/a7fdfd1c64bc7a7b6d1ceedc16138bca.png)
我们可以用上面的任意一种方法学习嵌入层,他们互有胜负。你当然可以查看学习后的嵌入层进行比较,但实际上最有效的选择方法是在一个监督式学习的任务中直接验证性能,取最优。我们当然可以用PyTorch建模来学习嵌入层,但是现在我们会直接使用一个专门用于嵌入层和主题建模的库,它叫作 Gensim。
# 安装Gensim库
!pip install gensim --upgrade -i https://pypi.tuna.tsinghua.edu.cn/simple --quiet
# 加载包
import os
from argparse import Namespace
import copy
import gensim
from gensim.models import Word2Vec
import json
import nltk; nltk.download('punkt')
import numpy as np
import pandas as pd
import re
import urllib
import warnings
warnings.filterwarnings('ignore')
args = Namespace(
seed=1234,
data_file="harrypotter.txt",
embedding_dim=100,
window=5,
min_count=3,
skip_gram=1, # 设为0使用CBOW
negative_sampling=20,
)
# 加载数据
url = "https://raw.githubusercontent.com/GokuMohandas/practicalAI/master/data/harrypotter.txt"
response = urllib.request.urlopen(url)
html = response.read()
with open(args.data_file, 'wb') as fp:
fp.write(html)
# 文本分割成句子
tokenizer = nltk.data.load('tokenizers/punkt/english.pickle')
with open(args.data_file, encoding='cp1252') as fp:
book = fp.read()
sentences = tokenizer.tokenize(book)
print (len(sentences))
print (sentences[11])
![d28080a33e096ff5ffdc31870b5952ff.png](https://i-blog.csdnimg.cn/blog_migrate/887103be0c04d48d1501f1cb5f17edf8.png)
# 预处理
def preprocess_text(text):
text = ' '.join(word.lower() for word in text.split(" "))
text = re.sub(r"([.,!?])", r" 1 ", text)
text = re.sub(r"[^a-zA-Z.,!?]+", r" ", text)
text = text.strip()
return text
# 数据清洗
sentences = [preprocess_text(sentence) for sentence in sentences]
print (sentences[11])
![dfd3bec2c37810c76c020a87f3befd22.png](https://i-blog.csdnimg.cn/blog_migrate/8354877574e335d2017fcb0973910057.png)
# 为gensim对句子进行预处理
sentences = [sentence.split(" ") for sentence in sentences]
print (sentences[11])
![d53b10b64e4b1977acfd3724ab28b20e.png](https://i-blog.csdnimg.cn/blog_migrate/7dfeeb13719e13c334981339ebcc455c.png)
当嵌入层的学习需要很大的词汇表时,事情很快就会变得复杂起来。之前我们提到,softmax 的反向传播会同时更新正确类和错误类的权重,这样每次反向传播得计算量就会变得巨大。一个变通方案是使用负采样 negative sampling,它只需要更新正确类和一些随机错误累的权重(negative_sampling=20)。我们有大量的训练数据,而训练数据中我们将多次看到和目标类相同的单词,所以我们可以使用负采样的方案而不会对模型性能产生明显影响。
# 由于底层是C语言编写优化,所以速度超级快
model = Word2Vec(sentences=sentences, size=args.embedding_dim,
window=args.window, min_count=args.min_count,
sg=args.skip_gram, negative=args.negative_sampling)
print (model)
![de31a15ec1cbb25af13110910cf76122.png](https://i-blog.csdnimg.cn/blog_migrate/df411218bb82d27c7d50b22723318096.png)
# 对每个词进行向量化
model.wv.get_vector("potter")
![4662c90e69c3af844720b1875ddd99b2.png](https://i-blog.csdnimg.cn/blog_migrate/46cd0993f3046ffd0867dcf5bb6b1532.png)
# 找到距离最近的单词(排除自己)
model.wv.most_similar(positive="scar", topn=5)
![9bee17e782826791017cae21edaab8ea.png](https://i-blog.csdnimg.cn/blog_migrate/87243187ad51c0a229c86b1315b8c01e.png)
# 保存权重
model.wv.save_word2vec_format('model.txt', binary=False)
预训练嵌入层
我们可以使用上面的任意一种方法从头开始学习嵌入层,另外我们也可以使用已经在数百万文档上经过训练的预训练嵌入层。目前流行的嵌入层包括Word2Vec (skip-gram)或GloVe (global word-word co-occurrence)。我们可以通过确认这些嵌入层捕获的有意义的语义关系来进行验证操作。
我们可以用之前提到的任意一种方法从头开始学习嵌入测过,但其实直接使用在数百万份文档上训练好的预训练嵌入层也是一种机智的选择。目前使用广泛的嵌入层包括 Word2Vec (skip-gram) 和 GloVe (global word-word co-occurrence)。 我们可以通过确认这些嵌入层所能发现的语义关系来进行验证。
from gensim.scripts.glove2word2vec import glove2word2vec
from gensim.models import KeyedVectors
from io import BytesIO
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from zipfile import ZipFile
from urllib.request import urlopen
!cp /home/kesci/input/glove1001480/glove.6B.100d.txt /home/kesci/work/glove.6B.100d.txt
我们已经直接在K-Lab中挂载了相关文件,所以这里不需要再下载了,如果在本地环境使用本项目你可以uncomment相关代码运行。
# 解压文件 (可能需要三分钟)
# resp = urlopen('http://nlp.stanford.edu/data/glove.6B.zip')
# zipfile = ZipFile(BytesIO(resp.read()))
# zipfile.namelist()
# 写入嵌入层
embeddings_file = 'glove.6B.{0}d.txt'.format(args.embedding_dim)
# zipfile.extract(embeddings_file)
# 保存word2vec格式的GloVe嵌入层到
word2vec_output_file = '{0}.word2vec'.format(embeddings_file)
glove2word2vec(embeddings_file, word2vec_output_file)
![13171cd87edfca6eee7d76fd216eaa21.png](https://i-blog.csdnimg.cn/blog_migrate/dcdacff3c11d4d154551c1c909e4e44f.png)
# 加载嵌入层(可能需要一分钟)
glove = KeyedVectors.load_word2vec_format(word2vec_output_file, binary=False)
# (king - man) + woman = ?
glove.most_similar(positive=['woman', 'king'], negative=['man'], topn=5)
![0e1e6bf71133459412b193e59eccefe6.png](https://i-blog.csdnimg.cn/blog_migrate/0d0eeca2c630f264e72b0f0dc1193814.png)
# 找到距离最近的单词(排除自己)
glove.wv.most_similar(positive="goku", topn=5)
![97798d534f0794bbda1a9fe6785a366c.png](https://i-blog.csdnimg.cn/blog_migrate/98418c696d109946e4abcab98929e561.png)
# 为绘图作降维
X = glove[glove.wv.vocab]
pca = PCA(n_components=2)
pca_results = pca.fit_transform(X)
def plot_embeddings(words, embeddings, pca_results):
for word in words:
index = embeddings.index2word.index(word)
plt.scatter(pca_results[index, 0], pca_results[index, 1])
plt.annotate(word, xy=(pca_results[index, 0], pca_results[index, 1]))
plt.show()
plot_embeddings(words=["king",