资源经验分享《tensorflow》实战学习笔记(二)——实现Word2Vec

《tensorflow》实战学习笔记(二)——实现Word2Vec

2019-11-07 | |  62 |   0

原标题:《tensorflow》实战学习笔记(二)——实现Word2Vec

原文来自:CSDN      原文链接:https://blog.csdn.net/qq_36151472/article/details/102912903


1 Word2Vec简介

       2013年,Google开源了一款用于词向量计算的工具——word2vec,引起了工业界和学术界的关注。首先,word2vec可以在百万数量级的词典和上亿的数据集上进行高效地训练;其次,该工具得到的训练结果——词向量(word embedding),可以很好地度量词与词之间的相似性。进而可以做其他自然语言处理任务,比如文本分类,词性标注,机器翻译等。
       提到Word2Vec,不得不说一下one-hot编码,one-hot编码是将每个单词与一个唯一的整数索引相关联,然后将这个整数索引i转换为长度为N的二进制向量(N是词表大小),这个向量只有第i个元素是1,其余全是0。因此它得到的词向量是离散稀疏且高维的。而Word2Vec是将高维词向量嵌入到一个低维空间,语义相近的词的向量夹角余弦相似度更相近,可以从数据中学习得到。具体可以参考这篇文章

2 tensorflow实现Word2Vec

       下面开始用tensorflow实现Word2Vec的训练,具体注解代码中已经注释的很详细了,为了方便copy就直接一整段都贴过来了。

# tensorflow实现Word2Vec的训练。因为要从网络上下载数据,因此需要的依赖库比较多。
import collections
import math
import os
import random
import sys
import numpy as np
import urllib
import tensorflow as tf
import zipfile36 as zipfile
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE

url = 'http://mattmahoney.net/dc/'
# 定义下载文本数据的函数
def maybe_download(filename,expected_bytes):
    if not os.path.exists(filename):
        # 用urllib.request.urlretrieve下载数据的压缩文件并核对文件尺寸,如果已经下载则跳过
        filename,_ = urllib.request.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)

# 解压下载的压缩文件,并使用tf.compat.as_str将数据转成单词的列表。通过程序输出,可以知道数据最后被转为了一个包含17005207个单词的列表。
def read_data(filename):
    with zipfile.ZipFile(filename, 'r') as f:
        data = tf.compat.as_str(f.read(f.namelist()[0])).split()
    return data

words = read_data(filename)
print('Data size',len(words))   # Data size 17005207

# 接下来创建vocabulary词汇表,我们使用collections.Counter统计单词列表中单词的频数。
# 然后用most_common方法取top50000频数的单词作为vocabulary。再创建一个dict,将vocabulary放入dictionary中,python中的dict查询复杂度为O(1),性能非常好。
# 对于50000以外的词汇认定为Unknown,将其编号为0,
vocabulary_size = 50000
def build_dataset(words):
    count = [['UNK', -1]]
    count.extend(collections.Counter(words).most_common(vocabulary_size-1))
    dictionary = dict()
    for word, _ in count:
        dictionary[word] = len(dictionary)
    data = list()
    unk_count = 0
    for word in words:
        if word in dictionary:
            index = dictionary[word]
        else:
            index = 0
            unk_count += 1
        data.append(index)
    count[0][1] = unk_count
    reverse_dictionary = dict(zip(dictionary.values(),dictionary.keys()))
    # 返回转换后的编码(data),每个单词的频数统计(count),词汇表(dictionary),及其反转的形式(reverse_dictionary)
    return data, count, dictionary, reverse_dictionary
data,count,dictionary,reverse_dictionary = build_dataset(words)
# 删除原始单词列表,可以节约内存
del words
print('Most common words (+UNK)',count[:5])   # Most common words (+UNK) [['UNK', 418391], ('the', 1061396), ('of', 593677), ('and', 416629), ('one', 411764)]
print('Sample data',data[:10],[reverse_dictionary[i] for i in data[:10]])  # Sample data [5234, 3081, 12, 6, 195, 2, 3134, 46, 59, 156] ['anarchism', 'originated', 'as', 'a', 'term', 'of', 'abuse', 'first', 'used', 'against']

# 下面生成word2vec的训练样本。
data_index = 0
# generate_batch用来生成训练用的batch数据,batch_size:batch的大小,num_skip:对每个单词生成多少个样本(不大于skip_window值的两倍,并且batch_size必须是它的整数倍)
def generate_batch(batch_size,num_skips,skip_window):
    global data_index
    assert batch_size % num_skips == 0
    assert num_skips <= 2*skip_window
    batch = np.ndarray(shape=(batch_size),dtype=np.int32)
    labels = np.ndarray(shape=(batch_size,1),dtype=np.int32)
    # span为对某个单词创建相关样本时会使用到的单词数量,包括目标单词本身和它前后的单词
    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    # 即buffer中第skip_window个变量为目标单词
        targets_to_avoid = [skip_window]    # 生成样本时需要避免的单词列表targets_to_avoid
        # 每次循环对一个语境单词生成样本,先产生随机数,知道随机数不在targets_to_avoid,代表可以使用的语境单词,然后产生一个样本。
        # 在对一个目标单词生成完所有样本后,我们再读入下一个单词(同时会抛掉buffer中第一个单词),即把滑窗向后移动一位,这样我们的目标单词也向后移动了一个,语境单词也整体后移了,便可以开始生成下一个目标单词的训练样本。
        for j in range(num_skips):
            while target in targets_to_avoid:
                target = random.randint(0, span - 1)
            targets_to_avoid.append(target)
            batch[i * num_skips + j] = buffer[skip_window]
            labels[i*num_skips +j,0] = buffer[target]
        buffer.append(data[data_index])
        data_index = (data_index + 1)%len(data)
    # 两层循环完成后,我们已经获得了batch_size个训练样本,返回batch和labels
    return batch, labels
# 这里简单测试一下generate_batch函数,
# 结果以第一个样本为例"3084 originated -> 5235 anarchism",3084是目标单词originated的编号,这个单词对应的语境单词是anarchism,其编号为5235.
# 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 = 128
embedding_size = 128    # 单词转为稠密向量的维度,一般是50~1000
skip_window = 1       # 单词间最远可以联系的距离
num_skips = 2       # 对每个目标单词提取的样本数

valid_size = 16      # 用来抽取的验证单词数
valid_window = 100      # 验证单词只从频数最高的100个单词中抽取
valid_examples = np.random.choice(valid_window, valid_size, replace=False)      # 验证数据
num_sampled = 64        # 训练时用来做负样本的噪声单词的数量

# 下面开始定义skip_gram word2vec模型的网络结构
graph = tf.Graph()
with graph.as_default():
    train_inputs = tf.placeholder(tf.int32,shape=[batch_size])
    train_labels = tf.placeholder(tf.int32,shape=[batch_size,1])
    valid_dataset = tf.constant(valid_examples,dtype=tf.int32)
    # 限定所有计算再CPU上执行,因为接下去的一些计算操作再GPU上可能还没有实现。
    with tf.device('/cpu:0'):
        # 随机生成所有单词的词向量embeddings,单词表大小为50000,向量为都为128
        embeddings = tf.Variable(
            tf.random_uniform([vocabulary_size,embedding_size], -1.0, 1.0)
        )
        # 查找输入train_inputs对应的向量embed
        embed = tf.nn.embedding_lookup(embeddings, train_inputs)
        # NCE Loss为训练的优化目标
        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]))

    loss = tf.reduce_mean(tf.nn.nce_loss(weights=nce_weights,
                                         biases=nce_biases,
                                         labels=train_labels,
                                         inputs=embed,
                                         num_sampled=num_sampled,
                                         num_classes=vocabulary_size))

    # 定义优化器为SGD,学习速率为1.0
    optimizer = tf.train.GradientDescentOptimizer(1.0).minimize(loss)
    # 计算嵌入向量embeddings的L2范数norm
    norm = tf.sqrt(tf.reduce_sum(tf.square(embeddings),1,keep_dims=True))
    # 再将embeddings除以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
    )
    # 初始化所有模型参数
    init = tf.global_variables_initializer()

num_steps = 100001
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
        # 每两千次循环计算一下平均loss并显示出来。
        if step % 2000 == 0:
            if step > 0:
                average_loss /= 2000
            print("Average loss at step ", step, ": ", average_loss)
            average_loss = 0
        # 每10000次循环计算一次验证单词与全部单词的相似度并将每个验证单词最相似的8个单词展示出来。
        if step%10000 == 0:
            sim = similarity.eval()
            for i in range(valid_size):
                valid_word = reverse_dictionary[valid_examples[i]]
                top_k = 8
                nearest = (-sim[i, :]).argsort()[1:top_k+1]
                log_str = "Nearest to %s:" % valid_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()

# 可视化效果
# 这里的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))
    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)  # 保存到本地

# 利用sklearn.manifold.TSNE实现降维,将128维降到2维
tsne = TSNE(perplexity=30,n_components=2,init='pca',n_iter=5000)
plot_only = 100    # 这里只显示词频最高的100个单词
low_dim_embs = tsne.fit_transform((final_embeddings[:plot_only,:]))
labels = [reverse_dictionary[i] for i in range(plot_only)]
plot_with_labels(low_dim_embs,labels)

       如图所示,距离相近的单词在语义上具有很高的相似性,例如most和more,zero~nine等。
1.png

免责声明:本文来自互联网新闻客户端自媒体,不代表本网的观点和立场。

合作及投稿邮箱:E-mail:editor@tusaishared.com

上一篇:用OpenCV(python)编写一个程序,实现打开摄像头并保存一张照片的功能

下一篇:【LVCSR】各种大词表连续语音识别技术,它们之间的异同,并比较优缺点。

用户评价
全部评价

热门资源

  • Python 爬虫(二)...

    所谓爬虫就是模拟客户端发送网络请求,获取网络响...

  • TensorFlow从1到2...

    原文第四篇中,我们介绍了官方的入门案例MNIST,功...

  • TensorFlow从1到2...

    “回归”这个词,既是Regression算法的名称,也代表...

  • 机器学习中的熵、...

    熵 (entropy) 这一词最初来源于热力学。1948年,克...

  • TensorFlow2.0(10...

    前面的博客中我们说过,在加载数据和预处理数据时...