word2vec算法

目录

1:为什么要用词向量

2:Word2Vec简介

2.1:CBOW模型

2.1.1:模型结构

2.1.2:推导过程

2.2:Skip-Gram模型

2.2.1:模型结构

2.2.2:推导过程

3:分层Hierarchical Softmax

4:Negative Sampling

5:代码实战

参考文章:


 

1:为什么要用词向量

自然语言处理系统通常将词汇作为离散的单一符号,例如 “cat” 一词或可表示为 索引537 ,而 “dog” 一词或可表示为 索引143。这些符号编码毫无规律,无法提供不同词汇之间可能存在的关联信息。换句话说,在处理关于 “dogs” 一词的信息时,模型将无法利用已知的关于 “cats” 的信息(例如,它们都是动物,有四条腿,可作为宠物等等)。可见,将词汇表达为上述的独立离散符号将进一步导致数据稀疏,使我们在训练统计模型时不得不寻求更多的数据。而词汇的向量表示将克服上述的难题。上面的表示也就是NLP 中最直观,也是目前最常用的词表示方法是 One-hot Representation,它是将词符号化的一种方式,也就是每个词的向量大小就是词典大小,一个向量只有一个位置为1,其他位置为0,当然,这样的向量不包含任何语义信息。

onehot编码缺点:

  • 对于one hot编码,有多少个字,就得有多少维向量,假如有1万字,那么每个字向量就是1万维(常用的字可能不多,几千个左右,但是按照词的概念来看,常用的词可能就有十几万了),这样可能会造成维数爆炸
  • 词向量,能够包涵语义信息,向量的夹角余弦能够在某种程度上表示字、词的相似度等等

2:Word2Vec简介

Word2vec是一种可以进行高效率词嵌入学习的预测模型。NLP中的词语,是人类的抽象总结,是符号形式的(比如中文、英文、拉丁文等等),所以需要把他们转换成数值形式,或者说——嵌入到一个数学空间里,这种嵌入方式,就叫词嵌入(word embedding),而 Word2vec,就是词嵌入( word embedding) 的一种。其有两种变体是现在比较常用的,分别为:连续词袋模型(CBOW)及Skip-Gram模型。从算法角度看,这两种方法非常相似,其区别为CBOW根据源词上下文词汇(’the cat sits on the’)来预测目标词汇(例如,‘mat’),而Skip-Gram模型做法相反,它通过目标词汇来预测源词汇。

Skip-Gram模型采取CBOW的逆过程的动机在于:CBOW算法对于很多分布式信息进行了平滑处理(例如将一整段上下文信息视为一个单一观察量)。很多情况下,对于小型的数据集,这一处理是有帮助的。相比之下,Skip-Gram模型将每个“上下文-目标词汇”的组合视为一个新观察量,这种做法在大型数据集中会更为有效。

2.1:CBOW模型

2.1.1:模型结构

1 输入层:上下文单词的onehot
2 所有onehot分别乘以共享的输入权重矩阵W. 
3 所得的向量 {因为是onehot所以为向量} 相加求平均作为隐层向量, size为1N.
4 乘以输出权重矩阵W' 
5 得到向量 激活函数处理得到V-dim概率分布 {PS: 因为是onehot,其中的每一维都代表着一个单词概率}
6 概率最大的index所指示的单词为预测出的中间词与true label的onehot做比较,误差越小越好(根据误差更新权重矩阵)

2.1.2:推导过程

2.2:Skip-Gram模型

2.2.1:模型结构

preview

 

2.2.2:推导过程

3:分层Hierarchical Softmax

1.输入层:包含(Context(w)中2c个词的词向量v(Context(w)1),v(Context(w)2),...,v(Context(w)2c)∈Rm。这里,m表示词向量的长度。
2.投影层:将输入层的2c个向量求和。
3.输出层:输出层对应一棵二叉树,它以语料中出现过的词当叶节点,以各词在语料中出现的次数当权值构造出来的哈夫曼树。在这颗哈夫曼树中,叶节点有N(=|D|)个,分别对应词典D中的词。

优点:在神经概率语言模型中,模型的大部分计算集中在隐藏层和输出层之间的矩阵向量运算,以及输出层上的softmax 归一化运算.而从上面的对比中可见,CBOW 模型对这些计算复杂度高的地方有针对性地进行了改变,首先,去掉了隐藏层,其次,输出层改用了Huffiman 树,所有叶子节点加起来的概率和为1,从而为利用Hierarchical softmax 技术奠定了基础。

举例:

首先建立哈夫曼树,非叶节点上都有一个未知向量,因为哈夫曼树是一个二叉树,每一次向下遍历只有向左和向右两个方向,即二分类。那么自然联想到用逻辑回归来做这个事情,每一次向左记为1;每一次向右记为0;比如在给定了“足球”的上下文的条件下,我们当然希望模型预测出“足球”的概率最大,那我们就建立多个逻辑回归模型进行多次分类(向着“足球”行进),然后,就找到优化目标了:最大化最大对数似然函数(将所有的p相乘然后取对数)。然后使用梯度下降法更新参数(哈夫曼树中非叶结点上都有一个未知向量的参数)和词向量(之前都是随机生成的)

对于从根节点出发到达“足球”这个叶子节点所经历的4次二分类,将每次分类结果的概率写出来就是:

 

最优化目标:

其中是投影层计算出来的“足球”上下文向量的和,就是哈夫曼非叶节点 j 上的向量。接下来就要更新Xw和,其中更新上下文向量,是将梯度共享到上下文向量上面。

4:Negative Sampling

Negative Sampling是用来提高训练速度并改善所得词向量的质量。其不再使用复杂的哈夫曼树,而是利用简单的随机负采样,能大幅提高性能,因而Negative Sampling可作为Hierarchical Softmax的一种替代。在Hierarchical Softmax框架里我们用了哈夫曼树作为构造优化目标的桥梁,但是效率较低。Negative Sampling(负采样)框架是如何作为构造优化目标的桥梁的呢?负采样的思想通俗讲就是:还是用“足球”的例子,比如已经给定了“足球”的上下文,那我们需要让模型预测为“足球”的概率最大,预测为其他词(负样本)的概率较小。负采样技术就是随机的从词典里抽一些不是“足球”的词。因此优化目标就是让他是“足球”的概率最大,是采样出来的其他负样本的概率较小。

优化目标:

增大正样本的概率同时降低负样本的概率。于是,对于一个给定的语料库C,函数就可以作为整体优化的目标。为了计算方便,对G取对数,最终的目标函数就是:

  为了推倒方便,简记

 

采样的规则:

word2vec中的采样的方法并不复杂,如果词汇表的大小为VV,那么我们就将一段长度为1的线段分成VV份,每份对应词汇表中的一个词。当然每个词对应的线段长度是不一样的,高频词对应的线段长,低频词对应的线段短。每个词ww的线段长度由下式决定:

这里写图片描述

5:代码实战

没有进行优化的实现,后面有时间会用负采样实现一下。

import os
import collections
import numpy as np
import tensorflow.compat.v1 as tf
tf.disable_v2_behavior()

class DataSet(object):
    def __init__(self, file):
        self.file = file
        self.data_index = 0
        self._build_dataset()
    def _build_dataset(self):
        if not os.path.exists(self.file):
            raise ValueError("File does not exist --> %s" % self.file)
        f = open(self.file, mode='rt', encoding='utf8')
        self.data = tf.compat.as_str(f.read()).split()
        print("the length of data %d" % len(self.data))
        print("data value:" , self.data)
        if f:
            f.close()
        c = collections.Counter(self.data).most_common()
        print(c)
        self.vocab_size = len(c)
        self.counter = c.insert(0, ('UNK', -1))
        self.vocab_size += 1
        self.word2id = dict()
        self.id2word = dict()
        for word, _ in c:
            self.word2id[word] = len(self.word2id)#建立word到id的映射
            self.id2word[len(self.id2word)] = word#建立id到word的映射
    def gen_batch_inputs(self, batch_size, skip_window):
        raise NotImplementedError()


class SkipGramDataSet(DataSet):
    def gen_batch_inputs(self, batch_size, window_size):
        features = np.ndarray(shape=(batch_size,), dtype=np.int32)#shape(4,)和shape(4,1)是维度不一样
        labels = np.ndarray(shape=(batch_size,), dtype=np.int32)
        i = 0
        while True:
            if self.data_index == len(self.data):
                self.data_index = 0
            left = max(0, self.data_index - window_size)
            right = min(len(self.data), self.data_index + window_size + 1)
            for k in range(left, right):
                if k != self.data_index:
                    features[i] = self.word2id[self.data[self.data_index]]
                    labels[i] = self.word2id[self.data[k]]
                    i += 1
                    if i == batch_size:
                        return features, labels
            self.data_index += 1


dataset = SkipGramDataSet('/Users/zhongqiqiang/nease_projrct/recommend_recall/word2vec/test.txt')
VOCAB_SIZE = dataset.vocab_size
EMBEDDING_SIZE = 128
SKIP_WINDOW = 2
NUM_SAMPLED = 64
BATCH_SIZE = 32
WINDOW_SIZE = 2
LOG_DIR = "./tmp/word2vec"
TRAIN_STEPS = 10000
LEARNING_RATE = 0.1

class Word2Vec(object):
    def __init__(self):
        self.graph = tf.Graph()
        with self.graph.as_default():
            with tf.name_scope("inputs"):
                self.x = tf.placeholder(shape=(None, VOCAB_SIZE), dtype=tf.float32)
                self.y = tf.placeholder(shape=(None, VOCAB_SIZE), dtype=tf.float32)
            with tf.name_scope("layer1"):
                self.W1 = tf.Variable(tf.random_uniform([VOCAB_SIZE, EMBEDDING_SIZE], -1, 1),dtype=tf.float32)
                self.b1 = tf.Variable(tf.random_normal([EMBEDDING_SIZE]),dtype=tf.float32)
            hidden = tf.add(self.b1, tf.matmul(self.x, self.W1))
            with tf.name_scope("layer2"):
                self.W2 = tf.Variable(tf.random_uniform([EMBEDDING_SIZE, VOCAB_SIZE], -1, 1),dtype=tf.float32)
                self.b2 = tf.Variable(tf.random_normal([VOCAB_SIZE]),dtype=tf.float32)
            self.prediction = tf.nn.softmax(tf.add(tf.matmul(hidden, self.W2), self.b2))
            """
            损失函数
            """
            log = self.y * tf.log(self.prediction)
            self.loss = tf.reduce_mean(-tf.reduce_sum(log, reduction_indices=[1], keepdims=True))
            self.opt = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(self.loss)

    def _one_hot_input(self, dataset):
        features, labels = dataset.gen_batch_inputs(BATCH_SIZE, WINDOW_SIZE)
        f, l = [], []
        for w in features:
            tmp = np.zeros([VOCAB_SIZE])
            tmp[w] = 1
            f.append(tmp)
        for w in labels:
            tmp = np.zeros(VOCAB_SIZE)
            tmp[w] = 1
            l.append(tmp)
        return f, l

    def train(self, dataset, n_iters, ):
        with tf.Session(graph=self.graph) as sess:
            sess.run(tf.global_variables_initializer())
            for i in range(n_iters):
                features, labels = self._one_hot_input(dataset)

                predi, loss = sess.run([self.prediction, self.loss],
                                       feed_dict={
                                           self.x: features,
                                           self.y: labels
                                       })
                print("loss:%s" % loss)

word2vec = Word2Vec()
word2vec.train(dataset, TRAIN_STEPS)

参考文章:

https://www.cnblogs.com/itmorn/p/8196605.html

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值