https://blog.csdn.net/laojie4124/article/details/102915141
从word2vec-->fasttext-->glove的原理及实战
一、Word2Vec
什么是Word2Vec
Word2vec比较one-hot优点
Word2Vec的两种训练方式
Word2Vec需要注意的几个关键点
Word2Vec的实现方式
Word2Vec的训练方式
分别用tensorflow和gensim实现Word2Vec
二、Fasttext
是什么Fasttext
Fasttext的优点
Fasttext和其他词向量的对比
Fasttext和CBOW对比
Fasttext代码实战
三、glove
什么是glove
glove的实现方式
Glove与word2ec,LSA的训练方式比较
Glove实战
四、ELMO, GPT, BERT的发展与对比
五、总结
一、Word2Vec
什么是Word2Vec
Word2Vec是一种词的表示,用一组固定维度的向量来表示一个词或者字
Word2vec比较one-hot优点
1.基于词袋模型的one-hot编码在判定同义词,相思句子的时候很无力。
2.word2vec充分利用了上下文信息,这是one-hot编码没有的。从此词向量的表示就成了一个稠密的固定维度向量,不再是稀疏向量。
3,由于word2vec充分利用了上下文信息,因此在判断相似词或者句子都有很好的效果,也就是在深层次的语义理解相关任务上有更好的效果。
Word2Vec的两种训练方式
CBOW(用上下文预测中心词)
Skip-gram(用中心词预测上下文)
二者从实现方式来看,只是输入输出发生了变化
Word2Vec需要注意的几个关键点
word2vec的本质: 无监督学习,因为输出并没有label,虽然从输入输出的形式上看,很像有监督学习,但其实并不是。
为什么不是有监督:词向量的本质可以看成只有一层的神经网络,因此必须有输入、输出。训练的过程并不是看最后预测的单词或者分类的结果,而是要获得神经网络中隐层的权重。这也符合词向量是神经网络模型中的副产品这一说法。
Word2Vec的实现方式
首先说明,在word2vec模型的训练过程中,每个词都会作为中心词,和背景词,因此,每个词在训练结束后都有两个向量,一个是作为当它作为中心词的向量,另一个是作为背景词的向量。
CBOW模型
CBOW也叫连续词袋模型,用一个中心词前后的背景词来预测该中心词,例如:‘我’,‘爱’,‘红色’,‘这片’,‘土地’,窗口大小为2,就是用‘我’,‘爱’,‘这片’,‘土地’这四个背景词,来预测生成 ‘红色’ 这个中心词的条件概率。
当CBOW模型训练完成时,我们得到两组向量,中心词和背景词,CBOW选用的是背景词向量作为最终词向量。
Skip-gram模型
Skip-gram也叫跳字模型,用一个中心词来预测它在文本序列周围的背景词。例如:‘我’,‘爱’,‘红色’,‘这片’,‘土地’,窗口大小为2,就是用 ‘红色’生成与它距离不超过2个词的背景词 ‘我’,‘爱’,‘这片’,‘土地’的条件概率。
当Skip-gram模型训练完成时,我们得到两组向量,中心词和背景词,Skip-gram选用的是中心词向量作为最终词向量。
Word2Vec的训练方式
负采样(常用)
层序softmax
分别用tensorflow和gensim实现Word2Vec
这里讲两种实现Word2Vec的代码,一种是利用tensorflow实现,另一种是调包实现,主要是方便想改进Word2Vec和想快速使用的两种同学。
使用gensim快速使用Word2Vec
import jieba
import re
from gensim.models import word2vec
# 读取停用词
stop_words = []
with open('../data/stopword.txt', 'r', encoding='utf-8') as f_reader:
for line in f_reader:
line = line.replace('\r','').replace('r','').strip()
stop_words.append(line)
print(len(stop_words))
stop_words = set(stop_words)
print(len(stop_words))
# 文本处理
sentences = []
rules = '[\u4e00-\u9fa5]+'
pattern = re.compile(rules)
f_writer = open('../data/分好词的笑傲江湖.txt', 'w', encoding='utf-8')
with open('../data/笑傲江湖.txt', 'r', encoding='utf-8') as f_reader:
for line in f_reader:
line = line.replace('\r','').replace('\n', '').strip()
if line == '' or line is None:
continue
line = ' '.join(jieba.cut(line))
seg_list = pattern.findall(line)
word_list = []
for word in seg_list:
if word not in stop_words:
word_list.append(word)
if len(word_list) > 0:
sentences.append(word_list) # [ [] , [] ...]
line = ' '.join(word_list)
f_writer.write(line+'\n')
f_writer.flush()
f_writer.close()
# 训练
# sg=[0,1] 0是CBOW模型,1是skip-gram模型,默认为0
# window: 词向量的上下文最大距离,默认为5
# hs=[0,1] 0是负采样方法,1是层次softmax, 默认为0
# min_count: 最小词频,少于这个频率的词不管,默认为5
model = word2vec.Word2Vec(sentences, iter=50, window=5, size=100, sg=0, hs=0, min_count=5)
# word2vec常用的几种方法
# 选出与某个词最相近的10个词
for e in model.most_similar(positive=['林平之'], topn=10):
print(e[0], e[1]) # word: similar_value
# 直接从文本加载训练语料
sentences2 = word2vec.Text8Corpus('../data/分好词的笑傲江湖.txt')
# 保存模型
model.save('./笑傲江湖.model')
# 加载模型
model2 = word2vec.Word2Vec.load('./笑傲江湖.model')
# 计算两个词语的相似度
sim_value = model.similarity('林平之','木高峰')
print(sim_value)
# 计算两个集合的相似度
list1 = ['劳德诺', '林平之']
list2 = ['劳德诺', '陆大有']
sim_value1 = model.n_similarity(list1,list2)
print(sim_value1)
# 选出集合中不同类型的词
list3 = ['劳德诺', '陆大有', '木高峰']
print(model.doesnt_match(list3))
# 查看词向量
print(model['劳德诺'])
使用tensorflow训练Word2Vec
给出几个重要的方法定义,非完整代码,贴出来太占空间,所有代码会在文末给出。
import re
import math
import random
import jieba
import collections
import numpy as np
import tensorflow as tf
data_index = 0
def generate_batch(batch_size, num_skips, skip_window):
'''
这步主要是获取中心词对应的窗口大小内的背景词作为label,自己好好调试,一步步看
'''
global data_index
batch = np.ndarray(shape = (batch_size), dtype = np.int32)
labels = np.ndarray(shape = (batch_size, 1), dtype = np.int32)
span = 2*skip_window + 1
buffer = collections.deque(maxlen = span)
for _ in range(span):
buffer.append(data[data_index])
data_index = (data_index + 1) % len(data)
for i in range(batch_size // num_skips):
target = skip_window
target_to_aviod = [skip_window]
for j in range(num_skips):
while target in target_to_aviod:
target = random.randint(0, span-1)
target_to_aviod.append(target)
batch[i * num_skips + j] = buffer[skip_window]
labels[i * num_skips + j] = buffer[target]
buffer.append(data[data_index])
data_index = (data_index + 1) % len(data)
return batch , labels
# skip_gram model
batch_size = 128
embedding_size = 100
skip_window = 2
num_skips = 4
valid_window = 100
num_sampled = 64
learning_rate = 0.01
graph = tf.Graph()
with graph.as_default():
# 输入数据
train_inputs = tf.placeholder(dtype=tf.int32, shape=[batch_size])
train_labels = tf.placeholder(dtype=tf.int32, shape=[batch_size, 1])
valid_dataset = tf.constant(valid_examples, dtype=tf.int32)
with tf.device('/cpu:0'):
embeddings = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))
embed = tf.nn.embedding_lookup(embeddings, train_inputs)
# 从截断的正态分布中输出随机值。
nce_weights = tf.Variable(tf.truncated_normal([vocabulary_size, embedding_size], stddev=1.0/math.sqrt(embedding_size)))
nce_biases = tf.Variable(tf.zeros([vocabulary_size]), dtype=tf.float32)
loss = tf.reduce_mean(tf.nn.nce_loss(weights=nce_weights, biases=nce_biases, inputs=embed, labels=train_labels,
num_sampled=num_sampled, num_classes=vocabulary_size))
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss)
norm = tf.sqrt(tf.reduce_sum(tf.square(embeddings), 1, keep_dims=True))
normalized_embeddings = embeddings / norm
valid_embeddings = tf.nn.embedding_lookup(normalized_embeddings, valid_dataset)
similarity = tf.matmul(valid_embeddings, normalized_embeddings, transpose_b=True)
init = tf.global_variables_initializer()
num_steps = 2000000
with tf.Session(graph=graph) as session:
init.run() # 初始化
print('initialized')
average_loss = 0
for step in range(num_steps):
batch_inputs, batch_labels = generate_batch(batch_size, num_skips, skip_window)
feed_dict = {train_inputs: batch_inputs, train_labels: batch_labels}
_, loss_val = session.run([optimizer, loss], feed_dict=feed_dict)
average_loss += loss_val
if step % 2000 == 0:
if step > 0:
average_loss /= 2000
print('Average loss at step ', step, ': ', average_loss)
average_loss = 0
if step % 10000 == 0: # 每10000步看看训练效果,获取与验证集中最相似的top_k个词
sim = similarity.eval()
for i in range(len(valid_word)):
val_word = reverse_dictionary[valid_examples[i]]
top_k = 8
nearest = (-sim[i, :]).argsort()[: top_k]
log_str = 'Nearest to %s:' % val_word
for k in range(top_k):
close_word = reverse_dictionary[nearest[k]]
log_str = '%s %s' % (log_str, close_word)
print(log_str)
final_embeddings = normalized_embeddings.eval()
二、Fasttext
是什么Fasttext
Fasttext是一款文本分类与向量化的工具。目前最新版本0.9.1,与前面的几个版本改动较大,本篇文章主要用0.9.1来做讲解,毕竟人要往前看,哈哈。
Fasttext的优点
速度超快, 根据Fcebook的报告,在普通的多核cpu上,10亿的词训练只要不到10分钟就训练好了。
Fasttext利用了词内的n-gram信息,和层次softmax的训练trick,使用n-gram信息,将上下文中每个词的都进行基于词的n-gram,最后将所有n-gram和原词相加,来代表上下文信息。这样可以在词与词之间建立联系。
Fasttext和其他词向量的对比
以往的词向量都是以词汇表中独立单词作为基本单元来进行训练学习,这会造成两个问题:
低频词,罕见词在文本中出现的次数不多,因此得不到足够的训练,效果不佳
未登录词,没在词汇表中出现的词,传统模型更加无能为力。
fasttext将词打散到字符级别,提取字的多种n-gram信息,丰富词内部的信息,最后一个词的向量由它的所有n-gram的向量求和得到。这样不但解决了低频词,未登录词的问题,还提高了效果。
Fasttext和CBOW对比
相同点:模型结构差不多,都是三层,输入层,隐含层,输出层,都是对多个词向量的叠加平均。
不同点:CBOW输入的是单词的上下文,fasttext输入的是多个单词及其n-grram特征,这些特征用来表示单个文档;CBOW的输入单词呗one-hot编码过,fasttext输入特征时被embedding过;CBOW的输出是目标词汇,fasttext是文档对应的类标。
Fasttext代码实战
# -*- coding:utf-8 -*-
import jieba
import fasttext
def process_data():
# 文本处理
stop_words = []
with open('../data/stopword.txt', 'r', encoding='utf-8') as f_reader:
for line in f_reader:
line = line.replace('\r','').replace('r','').strip()
stop_words.append(line)
# print(len(stop_words))
stop_words = list(set(stop_words))
# print(len(stop_words))
category = []
f_writer = open('./train.txt', 'w', encoding='utf-8')
with open('../data/news.train.txt', 'r', encoding='utf-8') as f_reader:
for line in f_reader:
line = line.replace('\r','').replace('\n', '').strip()
line_list = line.split('\t')
if len(line_list) == 2:
seg_list = jieba.cut(line_list[1])
word_list = []
for word in seg_list:
if word not in stop_words:
word_list.append(word)
line = ' '.join(word_list)
line = '__label__' + line_list[0] + '\t' + line + '\n'
f_writer.write(line)
f_writer.flush()
if line_list[0] not in category:
category.append(line_list[0])
f_writer.close()
return stop_words,category
if __name__ == '__main__':
stopwords, category = process_data()
# print(category)
#
# 利用fasttext做文本分类训练
model = fasttext.train_supervised('./train.txt')
model.save_model('./fasttext.model')
# 测试集上的准确率和召回率
train_result = model.test('./test.txt')
print(train_result)
# 载入模型
model = fasttext.load_model('./fasttext.model')
# 测试新的样本
text = ['Google在开源BERT模型时已经在英文问答数据集SQuAD上获得SOTA值,经过我们的实验,BERT在处理中文问答任务时同样有十分出色的表现。这证明了BERT作为一种强大的预训练模型,的确可以很好地表征token的词义特征、语义特征、及句法特征']
texts = []
for word in jieba.cut(text[0]):
if word not in stopwords:
texts.append(word)
process_text = [' '.join(texts)]
label = model.predict(text, k=1)
print(label)
# 利用fasttext 指定模式训练词向量
word2vec = fasttext.train_unsupervised('./train.txt', model='cbow') # model = 'skipgram‘
print(word2vec.get_dimension()) # 获得向量维度
print(word2vec.get_word_vector('篮球')) # 过去篮球的向量
# 查看训练的语料词
print(model.words)
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
三、glove
什么是glove
glove是一个基于全局词频统计的词表征工具,它可以把一个单词表达成一个由实数组成的向量,这些向量捕捉到了单词之间的一些语义特征,如相似性,类比性等。
glove的实现方式
根据语料库构建一个共现矩阵(矩阵中每个值代表两个单词在特定窗口大小下共同出现的次数,一般是1,但是论文认为是d,同时提出一个衰减函数,随着窗口大小越大,距离越远的两个单词所占计数的权重更小)
构建词向量和共现矩阵之间的近似关系
构造loss function
共现矩阵的样子:
在这里插入图片描述
Glove与word2ec,LSA的训练方式比较
LSA也是基于共现矩阵进行训练的,只不过采用的是SVD奇异值分解的矩阵分解技术对大矩阵进行降维,SVD的复杂度是很高的,所以它的计算代价比较大,同时它对所有单词的统计权重都是一致的,而这些在glove中都被克服了。
word2vec的两种模式都是基于局部滑动窗口计算的,即该方法只利用了局部的上下文特征。
LSA和word2vec是两大类方法的代表,一个利用了全局特征的矩阵分解方法,一个利用了局部的上下文特征,而Glove就是将这两种特征合并在一起,既使用了语料库的全局统计特征,也使用了局部的上下文特征(滑动窗口)。
Glove实战
由于训练时间原因,同时个人觉得在大部分任务上word2vec和glove的效果其实差不多,并没有论文说的那么好,因此直接使用一些工具训练好的glove向量,mxnet中有大量训练好的词向量,包括word2vec,fasttext,glove词向量都有,这里教大家如何调用mxnet中训练好的词向量,同理可以使用其中其他训练好的词向量。
from mxnet import nd
from mxnet.contrib import text
# 得到里面所有glove训练好的模型,直接调出来用,方便
# glove可以换成word2vec,fasttext等
# 这里的输出结果就是mxnet下所有训练好的glove向量的名字
glove_vec = text.embedding.get_pretrained_file_names("glove")
print(glove_vec)
# 调用你要选择的训练好的词向量
glove_6b50d = text.embedding.create('glove', pretrained_file_name="glove.6B.50d.txt")
word_size = len(glove_6b50d)
print(word_size)
#词的索引
index = glove_6b50d.token_to_idx['happy']
print(index)
#索引到词
word = glove_6b50d.idx_to_token[1752]
print(word)
#词向量
print(glove_6b50d.idx_to_vec[1752])
# Glove应用
#余弦相似度
def cos_sim(x, y):
return nd.dot(x,y)/(x.norm() * y.norm())
a = nd.array([4,5])
b = nd.array([400,500])
print(cos_sim(a,b))
#求近义词
def norm_vecs_by_row(x):
# 分母中添加的 1e-10 是为了数值稳定性。
return x / (nd.sum(x * x, axis=1) + 1e-10).sqrt().reshape((-1, 1))
def get_knn(token_embedding, k, word):
word_vec = token_embedding.get_vecs_by_tokens([word]).reshape((-1, 1))
vocab_vecs = norm_vecs_by_row(token_embedding.idx_to_vec)
dot_prod = nd.dot(vocab_vecs, word_vec)
indices = nd.topk(dot_prod.reshape((len(token_embedding), )), k=k+1,
ret_typ='indices')
indices = [int(i.asscalar()) for i in indices]
# 除去输入词。
return token_embedding.to_tokens(indices[1:])
sim_list = get_knn(glove_6b50d,10, 'baby')
print(sim_list)
sim_val = cos_sim(glove_6b50d.get_vecs_by_tokens('baby'), glove_6b50d.get_vecs_by_tokens('babies'))
print(sim_val)
print(get_knn(glove_6b50d,10,'computer'))
print(get_knn(glove_6b50d,10,'run'))
print(get_knn(glove_6b50d,10,'love'))
#求类比词
#vec(c)+vec(b)−vec(a)
def get_top_k_by_analogy(token_embedding, k, word1, word2, word3):
word_vecs = token_embedding.get_vecs_by_tokens([word1, word2, word3])
word_diff = (word_vecs[1] - word_vecs[0] + word_vecs[2]).reshape((-1, 1))
vocab_vecs = norm_vecs_by_row(token_embedding.idx_to_vec)
dot_prod = nd.dot(vocab_vecs, word_diff)
indices = nd.topk(dot_prod.reshape((len(token_embedding), )), k=k,
ret_typ='indices')
indices = [int(i.asscalar()) for i in indices]
return token_embedding.to_tokens(indices)
#验证vec(son)+vec(woman)-vec(man) 与 vec(daughter) 两个向量之间的余弦相似度
def cos_sim_word_analogy(token_embedding, word1, word2, word3, word4):
words = [word1, word2, word3, word4]
vecs = token_embedding.get_vecs_by_tokens(words)
return cos_sim(vecs[1] - vecs[0] + vecs[2], vecs[3])
word_list = get_top_k_by_analogy(glove_6b50d,1,'man','woman','son')
print(word_list)
word_list = get_top_k_by_analogy(glove_6b50d,1,'man','son','woman')
print(word_list)
sim_val = cos_sim_word_analogy(glove_6b50d, 'man','woman','son','daughter')
print(sim_val)
word_list = get_top_k_by_analogy(glove_6b50d,1,'beijing','china','tokyo')
print(word_list)
word_list = get_top_k_by_analogy(glove_6b50d,1,'bad','worst','big')
print(word_list)
word_list = get_top_k_by_analogy
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
四、ELMO, GPT, BERT的发展与对比
待续
五、总结
这里针对三种词向量来进行简单的原理讲解和实战代码,在面试过程中如果问到词向量,这些内容大概率都是会问的,当然有些还会问公式,推导过程,以及你是否没有调包直接复现过(大部分针对wrod2vec),因此这里使用了tensorflow复现word2vec
的skipgram模式,对于公式,有很多好的博客介绍的很详细,这篇文章主要是针对实践部分,毕竟懂了原理也得会应用在自己的项目上,以上所有的代码以及所使用数据集,如有想要的大佬,可以留邮箱或者私信我,我看到及时发给你,理论可能不是很详细,大伙想深入理解理论,就看看其他大佬优秀的文章吧。