word2vec

http://www.360doc.com/content/17/0705/18/10408243_669124232.shtml
# -*- coding:utf-8 -*-
import math
import os
import random
import zipfile
import numpy as np
import urllib
import tensorflow as tf
import collections

from sklearn.manifold import TSNE
import matplotlib.pyplot as plt


url='http://mattmahoney.net/dc/'

#下载数据
def maybe_download(filename,expected_bytes):
#文件是否存在
if not os.path.exists(filename):
#urlretrieve() 方法直接将远程数据下载到本地。
filename,_=urllib.urlretrieve(url+filename,filename)

#获取文件的状态
statinfo=os.stat(filename)
if statinfo.st_size==expected_bytes:
print ('found and verified',filename)
else:
print (statinfo.st_size)
raise Exception(
'failed to verify '+filename+'. can you get to it with a browser?'
)
return filename

#下载数据文件
filename=maybe_download('text8.zip',31344016)

#读取zip文件的内容
def read_data(filename):
with zipfile.ZipFile(filename) as f:
data=tf.compat.as_str(f.read(f.namelist()[0])).split()
return data


#words list
words=read_data(filename)
print (type(words))
print ('Data size',len(words))
#1000多万个单词


#单词库 5万
vocabulary_size=50000

def build_dataset(words):
count=[['UNK',-1]]#list 此时,len(count)=1,表示只有一组数据 词汇表vocabulary

count.extend(collections.Counter(words).most_common(vocabulary_size-1))
#这句话,将words中,最常见的49999个单词和对应的个数,放入count中。
##此时,len(count)=50000,表示只有50000组数据
#使用collections.Counter统计word单词列表中单词的频数,然后使用most_common方法取
#top 50000频数的单词作为词汇表vocabulary

dictionary=dict()#创建一个字典
#将全部单词转为编号(以频数排序的编号),top50000之外的单词,认为UnKown,编号为0,并统计这类词汇的数量

for word,_ in count:
dictionary[word]=len(dictionary)#将全部单词转为编号(以频数排序的编号)
#count中排序前五个为:the 1061396 of 593677 and 416629 one 411764 in 372201,......
#那么在dictionary中,dictionary['the']=1 dictionary['of']=2
#dictionary['and']=3 dictionary['one']=4,............
#

data=list()
unk_count=0
for word in words:#遍历单词列表,
#对于其中每一个单词,先判断是否出现在dictionary中,
if word in dictionary:
#如果出现,则转为其编号
index=dictionary[word]
else:#如果不是,则转为编号0
index=0
unk_count+=1
data.append(index)
#data是dictionary的数值 逆序 (频数)
#reverse_dictionary是dictionary的整个逆序
# 通过data索引获取的值,便可以通过reverse_dictionary查看单词

count[0][1]=unk_count
#count[0]:表示第一个的键,


reverse_dictionary=dict(zip(dictionary.values(),dictionary.keys()))

#最后返回转换后的编码data,通过data[索引],就可以得到一个数值,通过该数值,在reverse_dictionary就能知道该数值代表的单词
#例如:data[1]=3084 reverse_dictionary[3084]='originated'
# 每个单词的频数统计count,词汇表,词汇表反转的形式
return data,count,dictionary,reverse_dictionary
#dictionary和reverse_dictionary是互为对应的。
#dictionary 键‘字符串’ 值 整数数值 int类型
#reverse_dictionary 键 整数数值 int类型 值‘字符串’

#将从text解压出来的1000多万个单词,进行数据处理
#删除原始单词列表,以节约内存。
data,count,dictionary,reverse_dictionary=build_dataset(words)


del words
print ('most common words (+UNK)',count[:5])#打印词汇表count,中,出现频率最高的5个。
print ('sample data',data[:10],[reverse_dictionary[i] for i in data[:10]])
#编码data中前10个单词----编号----


data_index=0
#生成word2vec的训练用的batch数据,参数中batch_size为batch的大小;
#skip_window指单词最远可以联系的距离,设为1表示只能跟紧邻的两个单词生成样本。
#num_skips为对每个单词生成多少个样本,不能大于skip_windows的两倍,并且batch_size是其整数倍
def generate_batch(batch_size,num_skips,skip_window):

global data_index #数据索引,全局变量
#确保batch_size可以整除num_skips
assert batch_size%num_skips==0
assert num_skips<=2*skip_window#
#python assert断言是声明其布尔值必须为真的判定,如果发生异常就说明表达示为假。
# 可以理解assert断言语句为raise-if-not,用来测试表示式,其返回值为假,就会触发异常。

batch=np.ndarray(shape=(batch_size),dtype=np.int32)
#创建一个batch_size大小的数组,数据类型为int32类型,数值随机, 从0到2的32次方
labels = np.ndarray(shape=(batch_size, 1), dtype=np.int32)#数据维度:[batch_size,1]

span = 2 * skip_window + 1 # [ skip_window target skip_window ] 跨度
#span为对某个单词创建相关样本时,会是严格哦你到的单词数量。包括目标单词本身和它前后的单词

buffer = collections.deque(maxlen=span) # buffer用来存取w上下文word的id
#创建最大容量为span的deque,双向队列。

for _ in range(span):
buffer.append(data[data_index]) # data:ids 在对双向队列deque使用append方法添加变量时,只会保留最后插入的span个变量
data_index = (data_index + 1) % len(data)
#从序号data_index开始,把span个单词顺序读入buffer作为初始值。因为容量为span的deque,所以此时buffer已经填充满
#后续数据会替换掉前面的数据。

#进入到第以层循环,次数为 batch_size // num_skips
for i in range(batch_size // num_skips): # how many num_skips in a batch
#每次循环内,对一个目标单词生成样本。

#现在bufffer中目是标单词和所有相关单词。定义target = skip_window ,
#即buffer中第skip_window个变量为目标单词
#然后定义生成样本时需要避免的单词列表targets_to_avoid,该列表一开始就包括第skip_window个单词(目标单词)
#因为要预测的是语境单词,不包括单词本身。

target = skip_window # target label at the center of the buffer
targets_to_avoid = [skip_window] # extract the middle word

#第二层循环
for j in range(num_skips):
#每次循环中对一个语境单词单词生成样本。#先产生随机数,直到随机数不再targets_to_avoid中。
#代表可以使用的语境单词,然后产生一个样本。
while target in targets_to_avoid: #直到随机数不再targets_to_avoid中。 context中的word,一个只取一次
target = random.randint(0, span - 1)#先产生随机数,

targets_to_avoid.append(target) ##代表可以使用的语境单词,然后产生一个样本。

batch[i * num_skips + j] = buffer[skip_window] #buffer[skip_window]目标词汇 feature
labels[i * num_skips + j, 0] = buffer[target] # label 标签:buffer[target]
#同时,因为这个语境单词被使用了,所以在把它添加到targets_to_avoid中过滤

#在对一个目标单词生成完所有的样本之后(num_skip个样本),我们在读下一个单词,同时会抛掉buffer中的第一个单词,即
#把滑窗向后移以为,这样目标单词也向后移动以为,语境单词也整体后移了,便可以生成下一个目标单词的训练样本。
buffer.append(data[data_index]) # update the buffer, append the next word to buffer
data_index = (data_index + 1) % len(data)

#两层循环完成之后,就已经获得batch_size个训练样本,将batch和labels作为函数结果返回。
return batch, labels # batch: ids [batch_size] lebels:ids [batch_size*1]


#调用generate_batch简单测试功能。
batch, labels = generate_batch(batch_size=8, num_skips=2, skip_window=1)

for i in range(8):
print(batch[i], reverse_dictionary[batch[i]],'->', labels[i, 0], reverse_dictionary[labels[i, 0]])



#在本代码中,训练时,batch_size代表的是一个batch中,word的个数,而不是sentense的个数。
batch_size = 128
embedding_size = 128 # 单词转为稠密向量的维度,这里使用128作为词向量的维度
skip_window = 1 # How many words to consider left and right.单词间最远可以联系的距离
num_skips = 2 # 每个目标单词提取的样本数How many times to reuse an input(buffer) to generate a label.

#验证数据 valid_examples,这里随机抽取一些频数最高的单词,看向量空间上跟他们最近的单词是否相关性比较高
valid_size = 16 # 抽取的验证单词数
valid_window = 100 # 指验证单词只从频数最高的100个单词中抽取
valid_examples = np.random.choice(valid_window, valid_size, replace=False)#使用np.random.choice函数进行随机抽取
num_sampled = 64 # 训练是用作负样本的噪声单词的数量


#
graph = tf.Graph()
with graph.as_default():

# 创建训练数据中的inputs和balels的placeholder
#在这里,我们只输入word对应的id,假设batch_size是128,那么我们第一次就输入文本前128个word所对应的id
train_inputs = tf.placeholder(tf.int32, shape=[batch_size])
#labels和inputs是一样的, 只不过一个是行向量(tensor),一个是列向量(tensor)
train_labels = tf.placeholder(tf.int32, shape=[batch_size, 1])

valid_dataset = tf.constant(valid_examples, dtype=tf.int32)#
#将前面随机产生的valid_examples转换为Tensorflow中的constant

# Ops and variables pinned to the CPU because of missing GPU implementation
with tf.device('/cpu:0'):
# Look up embeddings for inputs.
#使用tf.random_uniform随机生成所有单词的词向量embeddings
#单词表大小为50000,向量维度128
embeddings = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))
#tf.nn.embedding_lookup查找输入的train_inputs对应的向量embed.
embed = tf.nn.embedding_lookup(embeddings, train_inputs)

# Construct the variables for the NCE loss
#权重参数
nce_weights = tf.Variable( #every word has a corresponding nce_weight ad nce_biase
tf.truncated_normal([vocabulary_size, embedding_size],
stddev=1.0 / math.sqrt(embedding_size)))
#偏置
nce_biases = tf.Variable(tf.zeros([vocabulary_size]))

# Compute the average NCE loss for the batch.
# tf.nce_loss automatically draws a new sample of the negative labels each
# time we evaluate the loss.
#使用tf.nn.nce_loss计算学习出的词向量embedding在训练数据上的loss,并使用tf.reduce_mean进行汇总
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))#关于nce_loss的介绍在文章最后

# Construct the SGD optimizer using a learning rate of 1.0.
#优化器为SGD,学习素来为1.0
optimizer = tf.train.GradientDescentOptimizer(1.0).minimize(loss)

# Compute the cosine similarity between minibatch examples and all embeddings.
#计算嵌入向量embeeddings的L2范数norm
norm = tf.sqrt(tf.reduce_sum(tf.square(embeddings), 1, keep_dims=True))
#再将embeeddings除以其L2范数得到标准化的normalized_embeddings
normalized_embeddings = embeddings / norm
#再使用tf.nn.embedding_lookup查询验证单词的嵌入向量,并
valid_embeddings = tf.nn.embedding_lookup(
normalized_embeddings, valid_dataset)
#并计算验证单词的嵌入向量和词汇表中所有单词的相似性
similarity = tf.matmul(
valid_embeddings, normalized_embeddings, transpose_b=True)

# Add variable initializer.初始化所有模型参数
init = tf.global_variables_initializer()

# Step 5: Begin training.
num_steps = 100001

temp=tf.Variable(tf.random_normal(shape=[6]))
# 保存操作


with tf.Session(graph=graph) as session:
# We must initialize all variables before we use them.
init.run()
print("Initialized")
summary_writer = tf.summary.FileWriter('./Out', session.graph)
saver = tf.train.Saver(max_to_keep=5)

average_loss = 0
for step in xrange(num_steps):
batch_inputs, batch_labels = generate_batch(batch_size, num_skips, skip_window)
feed_dict = {train_inputs : batch_inputs, train_labels : batch_labels}

# 使用session.run执行一次优化器运算(即一次参数更新)和损失计算
_, loss_val = session.run([optimizer, loss], feed_dict=feed_dict)
average_loss += loss_val

#每2000次循环,计算平均loss并显示出来
if step % 2000 == 0:
if step > 0:
average_loss /= 2000
# The average loss is an estimate of the loss over the last 2000 batches.
print("Average loss at step ", step, ": ", average_loss)
average_loss = 0
saver.save(session, "word2vec", global_step=step)


# Note that this is expensive (~20% slowdown if computed every 500 steps)
#每10000次循环,计算一次验证单词与全部单词的相似度,并将与每个验证单词最相似的8个单词展示出来
if step % 10000 == 0:
sim = similarity.eval()
for i in xrange(valid_size):
valid_word = reverse_dictionary[valid_examples[i]]
top_k = 8 # number of nearest neighbors
nearest = (-sim[i, :]).argsort()[1:top_k+1]
log_str = "Nearest to %s:" % valid_word
for k in xrange(top_k):
close_word = reverse_dictionary[nearest[k]]
log_str = "%s %s," % (log_str, close_word)
print(log_str)
final_embeddings = normalized_embeddings.eval()


# Step 6: Visualize the embeddings.
#定义一个用来可视化word2vec效果的函数。
#low_dim_embs是降维到2维的单词的空间向量,我们将在图片中显示每个单词的位置

def plot_with_labels(low_dim_embs, labels, filename='tsne.png'):
assert low_dim_embs.shape[0] >= len(labels), "More labels than embeddings"
plt.figure(figsize=(18, 18)) # in inches
for i, label in enumerate(labels):
x, y = low_dim_embs[i, :]
plt.scatter(x, y)
#展示单词本身
plt.annotate(label,
xy=(x, y),
xytext=(5, 2),
textcoords='offset points',
ha='right',
va='bottom')
#保存文件
plt.savefig(filename)


try:
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt

#TSNE实现降维
tsne = TSNE(perplexity=30, n_components=2, init='pca', n_iter=5000)
plot_only = 500
#直接将原始的128维的嵌入向量降到2维,再用前面的plot_with_labels进行显示
low_dim_embs = tsne.fit_transform(final_embeddings[:plot_only, :])
labels = [reverse_dictionary[i] for i in xrange(plot_only)]
plot_with_labels(low_dim_embs, labels)

except ImportError:
print("Please install sklearn, matplotlib, and scipy to visualize embeddings.")

posted on 2019-01-29 15:49  AI大道理  阅读(452)  评论(0编辑  收藏  举报

导航