Word2Vec之Skip-Gram模型实现代码详解

Word2Vec之Skip-Gram模型实现代码详解

转载自:
Skip-Gram代码

引入

对于一个句话,我们认为这句话中的每两个词之间都可能存在一定联系,但这种联系不是必然的,比如”明天要下雨”这句话,我们不能推测明天和下雨一定有关系。但我们基于统计学来看,给我们大量的逻辑上正确的话,当我们总是能在这些话里面找到某两个词同时出现,那么就可以说明这两个词确实存在关系,比如分析100万句话,然后发现人工智能和机器学习出现在同一句话的频次较高,那么我们就可以推断这两个词之间是存在联系。
如何去实现我们的想法呢?大量数据总是和神经网络相关的

网络模型

原理

给我们一篇非常长的文章,可以认为文章中相邻的词(或者是位置相近的词)之间存在关系。
具体是操作方法各类各样,为了方便弄清原理,我们简化了操作。
例如下面的文章

Word2Vec之Skip-Gram模型实现代码详解
对于前几个单词anarchism originated as a term of abuse
首先设置参数:
skip_window=1,表示去左侧1个词和右侧1个词进入窗口。
num_skips=2,表示我们从当前整个窗口中选择多少个不同的词作为我们的output word。
那么当我们选中单词originated作为input word时,我们会得到两组(input word, output word) 形式的训练数据,即 (‘originated’, ‘as’),(‘originated’, ‘anarchism’)。我们便得到了两个训练数据:
输入值:originated 输出值:as
输入值:originated 输出值:anarchism
但是计算机无法直接对单词进行计算和处理,所以需要先把单词变成数字形式。

; 单词格式转化数字格式

因为文中单词种类特别多,所以我们只取50000个出现最多的单词分析。
将所有单词按出现频次从大到小的顺序,并以二维数组形式保存前49999个,且将剩余的单词记为UNK,数量记为-1,放在数组的第一位。
这个二维数组的前十个元素为:[[‘UNK’, -1], (‘the’, 1061396), (‘of’, 593677), (‘and’, 416629), (‘one’, 411764), (‘in’, 372201), (‘a’, 325873), (‘to’, 316376), (‘zero’, 264975), (‘nine’, 250430)]
再将这些单词按在二维数组中的序号来代替出现频次,并存放在字典中。
这个字典前十个元素为:{“UNK”:0,”the”:1,…,”nine”:9}
至此所有单词都有了唯一的编号与之对应
最后将原文中所有的单词都转化为数字编号,并保存在一个新的数组data中。

print(data[:10])
'''[5234, 3081, 12, 6, 195, 2, 3134, 46, 59, 156]'''

代码详解

导入库


import time
import collections
import math
import os
import random
import zipfile
import numpy as np
import urllib
import pprint
import tensorflow as tf
import matplotlib.pyplot as plt
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"

准备数据集


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

def maybe_download(filename, expected_bytes):
"""
    判断文件是否已经下载,如果没有,则下载数据集
"""
    if not os.path.exists(filename):

        filename, _ = urllib.request.urlretrieve(url + filename, filename)

    stateinfo = os.stat(filename)

    if stateinfo.st_size == expected_bytes:
        print("数据集已存在,且文件尺寸合格!", filename)
    else :
        print(stateinfo.st_size)
        raise Exception(
            "文件尺寸不对 !请重新下载,下载地址为:"+url
        )
    return filename

"""
测试文件是否存在
"""
filename = maybe_download("text8.zip", 31344016)

解压文件

def read_data(filename):

    with zipfile.ZipFile(filename) as f:

        data = tf.compat.as_str(f.read(f.namelist()[0])).split()
        '''
        使用 zipfile.ZipFile()来提取压缩文件,然后我们可以使用
        zipfile 模块中的读取器功能。首先,namelist()函数检索该
        档案中的所有成员——在本例中只有一个成员,所以我们可以使
        用 0 索引对其进行访问。然后,我们使用 read()函数读取文
        件中的所有文本,并传递给 TensorFlow 的 as_str 函数,以确
        保文本保存为字符串数据类型。最后,我们使用 split()函数
        创建一个列表,该列表包含文本文件中所有的单词,并用空格字
        符分隔'''
    return data

filename = "text8.zip"
words = read_data(filename)
print("总的单词个数:", len(words))

构造单词与编号对应表


vocabulary_size = 50000

def build_dataset(words):
    count = [["UNK", -1]]

    count.extend(collections.Counter(words).most_common(vocabulary_size - 1))

    dictionary = {}

    for word, _ in count:
        dictionary[word] = len(dictionary)

    data = []
    unk_count = 0
    for word in words:

        if word in dictionary:

            index = dictionary[word]
        else:

            index = 0
            unk_count += 1
        data.append(index)
"""
    print(data[:10])
    [5234, 3081, 12, 6, 195, 2, 3134, 46, 59, 156]
"""
    count[0][1] = unk_count

    reverse_dictionary = dict(zip(dictionary.values(), dictionary.keys()))
    return data, count, dictionary, reverse_dictionary

data,count,dictionary,reverse_dictionary = build_dataset(words)
del words

生成训练样本


data_index = 0

def generate_batch(batch_size, num_skips, skip_window):
"""

    :param batch_size: 每个训练批次的数据量,8
    :param num_skips: 每个单词生成的样本数量,不能超过skip_window的两倍,并且必须是batch_size的整数倍,2
    :param skip_window: 单词最远可以联系的距离,设置为1则表示当前单词只考虑前后两个单词之间的关系,也称为滑窗的大小,1
    :return:返回每个批次的样本以及对应的标签
"""
    global data_index

    assert batch_size % num_skips == 0
    assert num_skips  skip_window * 2

    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)
"""
    print(batch,"\n",labels)
    batch :[0 ,-805306368  ,405222565 ,1610614781 ,-2106392574 ,2721-,2106373584 ,163793]
    labels: [[         0]
            [-805306368]
            [ 407791039]
            [ 536872957]
            [         2]
            [         0]
            [         0]
            [    131072]]
"""

    for _ in range(span):
        buffer.append(data[data_index])
        data_index = (data_index+1) % len(data)
"""
    print(buffer,"\n",data_index)  输出:
                                    deque([5234, 3081, 12], maxlen=3)
                                    3
"""

    for i in range(batch_size // num_skips):
        target = skip_window
        targets_avoid = [skip_window]
        for j in range(num_skips):
            """第二层循环,每次循环对一个语境单词生成样本,先产生随机数,直到不在需要避免的单词中,也即需要找到可以使用的语境词语"""
            while target in targets_avoid:
                target = random.randint(0, span-1)

            targets_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)

    return batch, labels

batch, labels = generate_batch(8, 2, 1)

"""
for i in range(8):
    print("目标单词:"+reverse_dictionary[batch[i]]+"对应编号为:".center(20)+str(batch[i])+"   对应的语境单词为: ".ljust(20)+reverse_dictionary[labels[i,0]]+"    编号为",labels[i,0])
测试结果:
目标单词:originated         对应编号为:   3080    对应的语境单词为:  as           编号为 12
目标单词:originated         对应编号为:   3080    对应的语境单词为:  anarchism    编号为 5233
目标单词:as                 对应编号为:   12      对应的语境单词为:  originated   编号为 3080
目标单词:as                 对应编号为:   12      对应的语境单词为:  a            编号为 6
目标单词:a              对应编号为:   6       对应的语境单词为:  as           编号为 12
目标单词:a              对应编号为:   6       对应的语境单词为:  term         编号为 195
目标单词:term           对应编号为:   195     对应的语境单词为:  of           编号为 2
目标单词:term           对应编号为:   95      对应的语境单词为:  a            编号为 6
"""

训练模型


batch_size = 128
embedding_size = 128
skip_window = 1
num_skips = 1

valid_size = 16
valid_window = 100
valid_examples = np.random.choice(valid_window, valid_size, replace=False)
num_sampled = 64

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

    train_inputs = tf.compat.v1.placeholder(tf.int32, [batch_size])
    train_labels = tf.compat.v1.placeholder(tf.int32, [batch_size, 1])
    valid_dataset = tf.constant(valid_examples, tf.int32)

    with tf.device("/cpu:0"):

        embeddings = tf.Variable(tf.compat.v1.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))

        embed = tf.nn.embedding_lookup(embeddings, train_inputs)

        weights = tf.Variable(tf.compat.v1.truncated_normal([vocabulary_size, embedding_size], stddev=1.0 /math.sqrt(embedding_size)))
        biases = tf.Variable(tf.zeros([vocabulary_size]))

        hidden_out = tf.matmul(embed, tf.transpose(weights)) + biases

        train_one_hot = tf.one_hot(train_labels, vocabulary_size)

        cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=hidden_out, labels=train_one_hot))

        optimizer = tf.compat.v1.train.GradientDescentOptimizer(1.0).minimize(cross_entropy)

        norm = tf.compat.v1.sqrt(tf.compat.v1.reduce_sum(tf.square(weights), 1, keepdims=True))
        normalized_embeddings = weights / norm
        valid_embeddings = tf.nn.embedding_lookup(normalized_embeddings, valid_dataset)

        similarity = tf.matmul(
            valid_embeddings, normalized_embeddings, transpose_b=True
        )
        init = tf.compat.v1.global_variables_initializer()

开始训练


num_steps = 150001
t0 = time.time()

with tf.compat.v1.Session(graph=graph) as session:
    init.run()
    print("初始化完成!")
    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}

        optimizer_trained, loss_val = session.run([optimizer, cross_entropy], feed_dict=feed_dict)
        average_loss += loss_val

        if step % 2000 == 0:
            if step > 0:
                average_loss /= 2000
            print('第%d轮迭代用时:%s' % (step, time.time() - t0))
            t0 = time.time()
            print("第{}轮迭代后的损失为:{}".format(step, average_loss))
            average_loss = 0

        if step % 1000 == 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 = "与单词 {} 最相似的: ".format(str(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()

可视化


def plot_with_labels(low_dim_embs, labels, filename = "tsne.png"):

    assert low_dim_embs.shape[0] >= len(labels), "标签数超过了嵌入向量的个数!!"
    plt.figure(figsize=(20, 20))
    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)
from sklearn.manifold import TSNE
tsne = TSNE(perplexity=30, n_components=2, init="pca", n_iter=5000)
plot_only = 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)
plt.show()

Original: https://blog.csdn.net/qq_43138237/article/details/124235639
Author: 新客草草
Title: Word2Vec之Skip-Gram模型实现代码详解

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/528104/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球