【新闻文本分类】(task4)使用gensim训练word2vec

学习总结

(1)学习训练Word2Vec 词向量,为后面task搭建 TextCNN 模型、BILSTM 模型训练预测作准备。Word2vec 的研究中提出的模型结构、目标函数、负采样方法、负采样中的目标函数在后续研究重复使用和优化。所谓的预测,取周围词随机初始化的embedding,进行平均池化后与中心词embedding进行点积,进行softmax多分类的预测任务,类别是所有的候选词,然后反向传播更新周围词语中心词的embedding,通过不断迭代得到最终的每个词向量。

(2)Embedding 就是用一个数值向量”表示”一个对象的方法。通过 Embedding,我们又引出了 Word2vec,Word2vec 是生成对”词”的向量表达的模型。其中,Word2vec 的训练样本是通过滑动窗口一一截取词组生成的。在训练完成后,模型输入向量矩阵(即本文的W V × N W_{V\times N}W V ×N ​)的第 i i i 个行向量,就是我们要提取的第 i i i 词向量。

(3)Skip-gram 模型(中心词决定了它的相邻词),而CBOW相反(是相邻词决定当前的词)。最后学习了 Item2vec,它是 Word2vec 在任意序列数据上的推广。

【新闻文本分类】(task4)使用gensim训练word2vec
(4)推荐一个在线测试word2vec:https://ronxin.github.io/wevi/
【新闻文本分类】(task4)使用gensim训练word2vec

文章目录

; 一、Word2vec算法

自从 2013 年谷歌提出 Word2vec 以来,Embedding 技术从自然语言处理领域推广到广告、搜索、图像、推荐等几乎所有深度学习的领域,成了深度学习知识框架中不可或缺的技术点。Word2vec是经典的 Embedding 方法。

1.1 CBOW 和 Skip-gram

Word2vec即”word to vector”,是一个生成对”词”的向量表达的模型。

想要训练 Word2vec 模型,我们需要准备由一组句子组成的语料库。假设其中一个长度为 T 的句子包含的词有 w1,w2……wt,并且我们假定每个词都跟其相邻词的关系最密切。

根据模型假设的不同,Word2vec 模型分为两种形式,CBOW 模型(图 3 左)和 Skip-gram 模型(图 3 右)。

【新闻文本分类】(task4)使用gensim训练word2vec

图3 Word2vec的两种模型结构CBOW和Skip-gram

(1)CBOW 模型假设句子中每个词的选取都由相邻的词决定,因此我们就看到 CBOW 模型的输入是 wt周边的词,预测的输出是 wt。

CBOW是用周围词预测中心词,训练过程中其实是在从output的loss学习周围词的信息也就是embedding,但是在中间层是average的,一共预测V次;

(2)Skip-gram 模型则正好相反,它假设句子中的每个词都决定了相邻词的选取,所以你可以看到 Skip-gram 模型的输入是 wt,预测的输出是 wt周边的词。按照一般的经验,Skip-gram 模型的效果会更好一些,所以下面会以 Skip-gram 作为框架,来讲Word2vec 的模型细节。

Skip-gram是用中心词预测周围词,对每一个中心词都有K个词作为output,对一个词的预测有K次,所以能够更有效的从context中学习信息,共预测K*V次,因此,skip-gram的训练时间更长。

总结:鉴于skip-gram学习的词向量更细致。当数据量较少或者语料库中有大量低频词时,使用skip-gram学习比较合适。

; 1.2 Word2vec的样本怎样生成

作为一个自然语言处理的模型,训练 Word2vec 的样本当然来自于语料库,比如我们想训练一个电商网站中关键词的 Embedding 模型,那么电商网站中所有物品的描述文字就是很好的语料库。

我们从语料库中抽取一个句子,选取一个长度为 2c+1(目标词前后各选 c 个词)的滑动窗口,将滑动窗口由左至右滑动,每移动一次,窗口中的词组就形成了一个训练样本。根据 Skip-gram 模型(中心词决定了它的相邻词),就可以根据这个训练样本定义出 Word2vec 模型的输入和输出,输入是样本的中心词,输出是所有的相邻词。

【example】:这里选取了”Embedding 技术对深度学习推荐系统的重要性”作为句子样本。
(1)我们对它进行分词、去除停用词的过程,生成词序列;
(2)再选取大小为 3 的滑动窗口从头到尾依次滑动生成训练样本;
(3)然后我们把中心词当输入,边缘词做输出,就得到了训练 Word2vec 模型可用的训练样本。

【新闻文本分类】(task4)使用gensim训练word2vec

图4 生成Word2vec训练样本的例子

1.3 Word2vec模型结构

它的结构本质上就是一个三层的神经网络(如图 5)

【新闻文本分类】(task4)使用gensim训练word2vec

图5 Word2vec模型的结构

它的输入层和输出层的维度都是 V,这个 V 其实就是语料库词典的大小。假设语料库一共使用了 10000 个词,那么 V 就等于 10000。根据图 4 生成的训练样本:
(1)输入向量自然就是由输入词转换而来的 One-hot 编码向量;
(2)输出向量则是由多个输出词转换而来的 Multi-hot 编码向量。
显然,基于 Skip-gram 框架的 Word2vec 模型解决的是一个多分类问题。

隐层的维度是 N,N 的选择就需要一定的调参能力了,需要对模型的效果和模型的复杂度进行权衡,来决定最后 N 的取值,并且最终每个词的 Embedding 向量维度也由 N 来决定。

激活函数:注意隐层神经元是没有激活函数的,或者说采用了输入即输出的恒等函数作为激活函数,而 输出层神经元采用了 softmax 作为激活函数。为什么要这样设置 Word2vec 的神经网络,为什么要这样选择激活函数呢?因为这个神经网络其实是为了表达从输入向量到输出向量的这样的一个条件概率关系:

p ( w O ∣ w I ) = exp ⁡ ( v w O ′ v w I ) ∑ i = 1 V exp ⁡ ( v w i ′ ⊤ v w I ) p\left(w_{O} \mid w_{I}\right)=\frac{\exp \left(v_{w_{O}}^{\prime} v_{w_{I}}\right)}{\sum_{i=1}^{V} \exp \left(v_{w_{i}}^{\prime}{ }^{\top} v_{w_{I}}\right)}p (w O ​∣w I ​)=∑i =1 V ​exp (v w i ​′​⊤v w I ​​)exp (v w O ​′​v w I ​​)​
这个由输入词 WI 预测输出词 WO 的条件概率,其实就是 Word2vec 神经网络要表达的东西。通过极大似然的方法去最大化这个条件概率,就能够让相似的词的内积距离更接近,这就是我们希望 Word2vec 神经网络学到的。

如果你对数学和机器学习的底层理论没那么感兴趣的话,也不用太深入了解这个公式的由来,因为现在大多数深度学习平台都把它们封装好了,你不需要去实现损失函数、梯度下降的细节,你只要大概清楚他们的概念就可以了。

【注意】Word2vec 还有很多值得挖掘的东西,比如
为了节约训练时间,Word2vec 经常会采用负采样(Negative Sampling)或者分层 softmax(Hierarchical Softmax)的训练方法。
关于这一点,推荐阅读《Word2vec Parameter Learning Explained》这篇文章,有最详细和准确的解释。

; 二、把词向量从 Word2vec 模型中提取出来

在训练完 Word2vec 的神经网络之后,我们想得到每个词对应的 Embedding 向量,这个 Embedding 在哪呢?其实它就藏在输入层到隐层的权重矩阵 W V × N W_{V\times N}W V ×N ​ 中。

图中橙色的部分,Embedding Matrix。

【新闻文本分类】(task4)使用gensim训练word2vec

图6 词向量藏在Word2vec的权重矩阵中

这个W V × N W_{V\times N}W V ×N ​的意思是W权重矩阵,下角标是(VxN),也就是输入层和隐藏层的权重矩阵吧! 然后我们一直在说embedding,那么embedding在哪儿呢? 如果看一下维度的话,我们输入是一个10000维的词的one-hot编码,那么这里的V就是10000,我们的输入应该是VxV的,那么我们的隐藏层有N个神经元,那么我们的权重矩阵不就是VxN的咯?而我们在python代码里运行torch.nn.Embedding()时候,第一个参数是输入维度,第二个参数是隐藏层维度,所以也就是说 我们习惯取这样的输入和隐藏层之间的权重矩阵为我们的Embedding矩阵。

输入向量矩阵 W V × N W_{V\times N}W V ×N ​ 的每一个行向量对应的就是我们要找的”词向量”(即上图中橙色矩阵中的每行深色橙色向量)。
比如我们要找词典里第 i 个词对应的 Embedding,因为输入向量是采用 One-hot 编码的,所以输入向量的第 i 维就应该是 1,那么输入向量矩阵 W V × N W_{V\times N}W V ×N ​ 中第 i 行的行向量自然就是该词的 Embedding 。

输出向量矩阵 W′ 也遵循这个道理,确实是这样的,但一般来说,我们还是习惯于使用输入向量矩阵(即这里的W V × N W_{V\times N}W V ×N ​)作为词向量矩阵。

2.1 提取词向量

在实际的使用过程中,我们往往会把输入向量矩阵转换成词向量查找表(Lookup table,如图 7 所示)。例如,输入向量是 10000 个词组成的 One-hot 向量,隐层维度是 300 维,那么输入层到隐层的权重矩阵为 10000×300 维。在转换为词向量 Lookup table 后,每行的权重即成了对应词的 Embedding 向量。如果我们把这个查找表存储到线上的数据库中,就可以轻松地在推荐物品的过程中使用 Embedding 去计算相似性等重要的特征了。

【新闻文本分类】(task4)使用gensim训练word2vec

图7 Word2vec的Lookup table

; 2.2 Word2vec 对 Embedding 技术的意义

Word2vec 是由谷歌于 2013 年正式提出的,其实它并不完全是原创性的,学术界对词向量的研究可以追溯到 2003 年,甚至更早的时期。但正是谷歌对 Word2vec 的成功应用,让词向量的技术得以在业界迅速推广,进而使 Embedding 这一研究话题成为热点。Word2vec 对深度学习时代 Embedding 方向的研究具有奠基性的意义。

从另一个角度来看,Word2vec 的研究中提出的模型结构、目标函数、负采样方法、负采样中的目标函数在后续的研究中被重复使用并被屡次优化。掌握 Word2vec 中的每一个细节成了研究 Embedding 的基础。

三、代码实践

这里继续天池的入门赛题——新闻分类。
用10折交叉验证,将数据分为10份,9份训练1份验证。
(1)用到了训练好的词向量文件:词向量下载链接: https://pan.baidu.com/s/1ewlck3zwXVQuAzraZ26Euw 提取码: qbpr

(2) all_data2fold函数的作用:针对类别不均衡问题【task1中看到的有的类别数据多,有的类别数据少】,所有类别进行平均分为10份再放在一起,这样其中任意9份做训练集,另外一份做测试集,的到的模型鲁棒性会更好。
(3)注意要将 Word2vec函数的参数 size改为 vector_size


"""
Created on Wed Nov 10 20:08:40 2021

@author: 86493
"""
import logging
import random

import numpy as np
import torch

logging.basicConfig(level=logging.INFO, format='%(asctime)-15s %(levelname)s: %(message)s')

seed = 666
random.seed(seed)
np.random.seed(seed)
torch.cuda.manual_seed(seed)
torch.manual_seed(seed)

fold_num = 10
data_file = 'train_set.csv'
import pandas as pd

def all_data2fold(fold_num, num=10000):
    fold_data = []
    f = pd.read_csv(data_file, sep='\t', encoding='UTF-8')

    texts = f['text'].tolist()[:num]

    labels = f['label'].tolist()[:num]

    total = len(labels)

    index = list(range(total))

    np.random.shuffle(index)

    all_texts = []
    all_labels = []

    for i in index:
        all_texts.append(texts[i])

        all_labels.append(labels[i])

    label2id = {}
    for i in range(total):
        label = str(all_labels[i])
        if label not in label2id:
            label2id[label] = [i]
        else:

            label2id[label].append(i)

    all_index = [[] for _ in range(fold_num)]

    for label, data in label2id.items():

        batch_size = int(len(data) / fold_num)

        other = len(data) - batch_size * fold_num

        for i in range(fold_num):

            cur_batch_size = batch_size + 1 if i < other else batch_size

            batch_data = [data[i * batch_size + b] for b in range(cur_batch_size)]

            all_index[i].extend(batch_data)

    batch_size = int(total / fold_num)
    other_texts = []
    other_labels = []
    other_num = 0
    start = 0
    for fold in range(fold_num):

        num = len(all_index[fold])

        texts = [all_texts[i] for i in all_index[fold]]
        labels = [all_labels[i] for i in all_index[fold]]
        '''
          以fold=0为例,   all_index[0]=[label_1_1折 expand label_2_1折 expand label_3_1折...expand label_19_1折 ]
          其中,label_1_1折 代表label——1划分到1折中的数据,数据的内容是label-1在原始数据中的索引位置 data[i * batch_size + b]
          所以,all_index[0] 代表是是,各个label划分到1折中的数据,数据内容是每个label在原始数据中的索引位置

          然后,利用这些索引位置,在all_texts和all_labels中取到真正的数据

        '''

        if num > batch_size:

            fold_texts = texts[:batch_size]

            other_texts.extend(texts[batch_size:])
            fold_labels = labels[:batch_size]

            other_labels.extend(labels[batch_size:])

            other_num += num - batch_size

        elif num < batch_size:

            end = start + batch_size - num

            fold_texts = texts + other_texts[start: end]
            fold_labels = labels + other_labels[start: end]

            start = end
        else:
            fold_texts = texts
            fold_labels = labels

        assert batch_size == len(fold_labels)

        index = list(range(batch_size))

        np.random.shuffle(index)

        shuffle_fold_texts = []
        shuffle_fold_labels = []
        for i in index:
            shuffle_fold_texts.append(fold_texts[i])

            shuffle_fold_labels.append(fold_labels[i])

        data = {'label': shuffle_fold_labels, 'text': shuffle_fold_texts}

        fold_data.append(data)

    logging.info("Fold lens %s", str([len(data['label']) for data in fold_data]))

    return fold_data

fold_data = all_data2fold(10)

fold_id = 9

train_texts = []
for i in range(0, fold_id):
    data = fold_data[i]
    train_texts.extend(data['text'])

logging.info('Total %d docs.' % len(train_texts))

logging.info('Start training...')
from gensim.models.word2vec import Word2Vec

num_features = 100

num_workers = 1

train_texts = list(map(lambda x: list(x.split()), train_texts))
model = Word2Vec(train_texts, workers=num_workers, vector_size=num_features)
model.init_sims(replace=True)

model.save("./word2vec.bin")

model = Word2Vec.load("./word2vec.bin")

model.wv.save_word2vec_format('./word2vec.txt', binary=False)

生成的word2vec文件:

【新闻文本分类】(task4)使用gensim训练word2vec

Reference

(1)阿里天池平台
(2)阿里天池 NLP 入门赛 TextCNN 方案代码详细注释和流程讲解
(3)天池学习赛-NLP新闻文本分类(5/6)-Word2Vec+TextCNN模型
(4)https://tianchi.aliyun.com/forum/postDetail?postId=122736

Original: https://blog.csdn.net/qq_35812205/article/details/121177195
Author: 山顶夕景
Title: 【新闻文本分类】(task4)使用gensim训练word2vec

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

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

(0)

大家都在看

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