赞
踩
Word2Vec是一种词的表示,用一组固定维度的向量来表示一个词或者字
1.基于词袋模型的one-hot编码在判定同义词,相思句子的时候很无力。
2.word2vec充分利用了上下文信息,这是one-hot编码没有的。从此词向量的表示就成了一个稠密的固定维度向量,不再是稀疏向量。
3,由于word2vec充分利用了上下文信息,因此在判断相似词或者句子都有很好的效果,也就是在深层次的语义理解相关任务上有更好的效果。
首先说明,在word2vec模型的训练过程中,每个词都会作为中心词,和背景词,因此,每个词在训练结束后都有两个向量,一个是作为当它作为中心词的向量,另一个是作为背景词的向量。
这里讲两种实现Word2Vec的代码,一种是利用tensorflow实现,另一种是调包实现,主要是方便想改进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['劳德诺'])
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是一款文本分类与向量化的工具。目前最新版本0.9.1,与前面的几个版本改动较大,本篇文章主要用0.9.1来做讲解,毕竟人要往前看,哈哈。
以往的词向量都是以词汇表中独立单词作为基本单元来进行训练学习,这会造成两个问题:
fasttext将词打散到字符级别,提取字的多种n-gram信息,丰富词内部的信息,最后一个词的向量由它的所有n-gram的向量求和得到。这样不但解决了低频词,未登录词的问题,还提高了效果。
相同点:模型结构差不多,都是三层,输入层,隐含层,输出层,都是对多个词向量的叠加平均。
不同点:CBOW输入的是单词的上下文,fasttext输入的是多个单词及其n-grram特征,这些特征用来表示单个文档;CBOW的输入单词呗one-hot编码过,fasttext输入特征时被embedding过;CBOW的输出是目标词汇,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)
glove是一个基于全局词频统计的词表征工具,它可以把一个单词表达成一个由实数组成的向量,这些向量捕捉到了单词之间的一些语义特征,如相似性,类比性等。
共现矩阵的样子:
LSA也是基于共现矩阵进行训练的,只不过采用的是SVD奇异值分解的矩阵分解技术对大矩阵进行降维,SVD的复杂度是很高的,所以它的计算代价比较大,同时它对所有单词的统计权重都是一致的,而这些在glove中都被克服了。
word2vec的两种模式都是基于局部滑动窗口计算的,即该方法只利用了局部的上下文特征。
LSA和word2vec是两大类方法的代表,一个利用了全局特征的矩阵分解方法,一个利用了局部的上下文特征,而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
待续
这里针对三种词向量来进行简单的原理讲解和实战代码,在面试过程中如果问到词向量,这些内容大概率都是会问的,当然有些还会问公式,推导过程,以及你是否没有调包直接复现过(大部分针对wrod2vec),因此这里使用了tensorflow复现word2vec的skipgram模式,对于公式,有很多好的博客介绍的很详细,这篇文章主要是针对实践部分,毕竟懂了原理也得会应用在自己的项目上,理论可能不是很详细,大伙想深入理解理论,就看看其他大佬优秀的文章吧。相关代码及数据集下载地址:相关数据集及完整代码
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。