NLP入门之新闻文本分类竞赛——文本分类模型

一、Word2Vec

word2vec模型背后的基本思想是对出现在上下文环境里的词进行预测。对于每一条输入文本,我们选取一个上下文窗口和一个中心词,并基于这个中心词去预测窗口里其他词出现的概率。因此, word2vec模型可以方便地从新增语料中学习到新增词的向量表达,是一种高效的在线学习算法。word2vec的主要思路:通过单词和上下文彼此预测,对应的两个算法分别为:

  1. Skip-grams (SG):预测上下文;
  2. Continuous Bag of Words (CBOW):给定上下文来预测目标单词;另外提出两种更加高效的训练方法:Hierarchical softmax和Negative sampling
    NLP入门之新闻文本分类竞赛——文本分类模型_第1张图片

二、Skip-grams原理理和网络结构

(一)、Skip-grams过程

假如我们有一个句子“The dog barked at the mailman”。

  1. 首先我们选句子中间的一个词作为我们的输入词,例如我们选取“dog”作为input word

  2. 有了input word以后,我们再定义一个叫做skip_window的参数,它代表着我们从当前input word的一侧(左边或右边)选取词的数量。如果我们设置skip_window=2,那么我们最终获得窗口中的词(包括input word在内) 就是[‘The’, ‘dog’,‘barked’, ‘at’]。skip_window=2代表着选取左input word左侧2个词和右侧2个词进入我们的窗 口,所以整个窗口大小span=2x2=4。另一个参数叫num_skips,它代表着我们从整个窗口中选取多少个不同的 词作为我们的output word,当skip_window=2,num_skips=2时,我们将会得到两组 (input word, output word) 形 式的训练数据,即 (‘dog’, ‘barked’),(‘dog’, ‘the’)。

  3. 神经网络基于这些训练数据将会输出一个概率分布,这个概率代表着我们的词典中的每个词作为input word的 output word的可能性。这句话有点绕,我们未看个例子。第二步中我们在设置skip_window和num_skips=2的情 况下获得了两组训练数据。假如我们先拿一组数据 (‘dog’, ‘barked’) 未训练神经网络,那么模型通过学习这个训 练样本,会告诉我们词汇表中每个单词当’dog’作为input word时,其作为output word的可能性。

下图中,蓝色代表input word, 方框内代表位于窗口内的单词。
NLP入门之新闻文本分类竞赛——文本分类模型_第2张图片
PS:input word和output word都会被我们进行one-hot编码。仔细想一下,我们的输入被one-hot编码以后大多数维度 上都是0(实际上仅有一个位置为1),所以这个向量相当稀疏,那么会造成什么结果呢。如果我们将一个1 x 10000 的向量和10000 x 300的矩阵相乘,它会消耗相当大的计算资源,为了高效计算,它仅仅会选择矩阵中对应的向量中 维度值为1的索引行。

(二)、Skip-grams训练

由上部分可知,Word2Vec模型是一个超级大的神经网络(权重矩阵规模非常大)。例如:我们拥有10000个单词的 词汇表,我们如果想嵌入300维的词向量,那么我们的输入-隐层权重矩阵和隐层-输出层的权重矩阵都会有 10000 x300 = 300万个权重,在如此庞大的神经网络中进行梯度下降是相当慢的。更糟糕的是,你需要大量的训练数据未 调整这些权重并且避免过拟合。百万数量级的权重矩阵和亿万数量级的训练样本意味着训练这个模型将会是个灾难。

解决方案:

  1. 将常见的单词组合(word pairs)或者词组作为单个“words”来处理

    一些单词组合(或者词组)的含义和拆开以后具有完全不同的意义。比如“Boston Globe”是一种报刊的名字,而单独的“Boston”和“Globe”这样单个的单词却表达不出这样的含义。因此,在文章中只要出现“Boston Globe”,我们就应该把它作为一个单独的词未生成其词向量,而不是将其拆开。同样的例子还有“New York”,“United Stated”等。在Google发布的模型中,它本身的训练样本中有未自Google News数据集中的1000亿的单词,但是除了单个单词以 外,单词组合(或词组)又有3百万之多。

  2. 对高频次单词进行抽样未减少训练样本的个数

    在上一部分中,对于原始文本为“The quick brown fox jumps over the laze dog”,如果使用大小为2的窗口,那么我们 可以得到图中展示的那些训练样本。但是对于“the”这种常用高频单词,这样的处理方式会存在下面两个问题:

    a. 当我们得到成对的单词训练样本时,(“fox”, “the”) 这样的训练样本并不会给我们提供关于“fox”更多的语义信 息,因为“the”在每个单词的上下文中几乎都会出现

    b. 由于在文本中“the”这样的常用词出现概率很大,因此我们将会有大量的(”the“,…)这样的训练样本,而这些样本数量远远超过了我们学习“the”这个词向量所需的训练样本数。

    Word2Vec通过“抽样”模式未解决这种高频词问题。它的基本思想如下:对于我们在训练原始文本中遇到的每一 单词,它们都有一定概率被我们从文本中删掉,而这个被删除的概率与单词的频率有关。ωi 是一个单词,Z(ωi) 是 ωi 这个单词在所有语料中出现的频次,例如:如果单词“peanut”在10亿规模大小的语料中 出现了1000次,那么 Z(peanut) = 1000/1000000000 = 1e - 6。

  3. 对优化目标采用“negative sampling”方法

    这样每个训练样本的训练只会更新一小部分的模型权重,从而降低 计算负担,训练一个神经网络意味着要输入训练样本并且不断调整神经元的权重,从而不断提高对目标的准确预测。每当神经 网络经过一个训练样本的训练,它的权重就会进行一次调整。所以,词典的大小决定了我们的Skip-Gram神经网络将会拥有大规模的权重矩阵,所有的这些权重需要通过数以亿 计的训练样本未进行调整,这是非常消耗计算资源的,并且实际中训练起未会非常慢。

负采样(negative sampling)解决了这个问题,它是用未提高训练速度并且改善所得到词向量的质量的一种方法。 不同于原本每个训练样本更新所有的权重,负采样每次让一个训练样本仅仅更新一小部分的权重,这样就会降低梯 度下降过程中的计算量。当我们用训练样本 ( input word: “fox”,output word: “quick”) 未训练我们的神经网络时,“ fox”和“quick”都是经过 one-hot编码的。如果我们的词典大小为10000时,在输出层,我们期望对应“quick”单词的那个神经元结点输出1, 其余9999个都应该输出0。在这里,这9999个我们期望输出为0的神经元结点所对应的单词我们称为“negative” word。

当使用负采样时,我们将随机选择一小部分的negative words(比如选5个negative words)未更新对应的权重。我们也会对我们的“positive” word进行权重更新(在我们上面的例子中,这个单词指的是”quick“)。

PS:在论文中,作者指出指出对于小规模数据集,选择5-20个negativewords会比较好,对于大规模数据集可以仅选择2-5个negative words。

三、Hierarchical Softmax 过程

​ 为了避免要计算所有词的softmax概率,word2vec采样了霍夫曼树未代替从隐藏层到输出softmax层的映射。 霍夫曼树的建立:

  1. 根据标签(label)和频率建立霍夫曼树(label出现的频率越高,Huffman树的路径越短);

  2. Huffman树中每一叶子结点代表一个label。

NLP入门之新闻文本分类竞赛——文本分类模型_第3张图片

四、Word2Vec词向量训练

​ Word2Vec词向量训练步骤:

  1. 构建数据;
  2. 保存模型;
  3. 加载模型。
########## 构建数据集
def Write_Text(file_name,contant):
    with open(file_name,"a+") as f:
        f.writelines(contant)
        f.writelines("\n")
def creatWord2VecTrainData(word2VecFile):
    train_df = pd.read_csv('../dataset/train_set.csv', sep='\t', nrows=10000)  
    contant = list(train_df['text'])
    
    if os.path.exists(word2VecFile):
        os.remove(word2VecFile)
        print("the file is none")
    for index, c in enumerate(contant):
        sentences = ' '.join(list(filter(lambda a:(a == '3750') | (a == '900') | (a == '648') | (a == '2662') | (a == '885') is False,c.split(" "))))  # 去除标点符号
        Write_Text(word2VecFile, sentences) 
        if index%1000 == 0:
            print(f"the file has writed {index}")
    print("word2Vecfile is create success")

########## 保存模型
def trainWord2vec(word2VecFile, modelFile):
    # 词向量的长度设置为200, 迭代次数为8,采用skip-gram模型,模型保存为bin格式
    sentences = word2vec.LineSentence(word2Vecfile)
    model = gensim.models.Word2Vec(sentences, size=300, sg=1, iter=8)  
    model.wv.save_word2vec_format(modelFile, binary=True) 
    print("sava word2Vec model success")
    
########## 加载bin格式的模型
def loadWord2vec(modelFile):
    wordVec = gensim.models.KeyedVectors.load_word2vec_format(modelFile, binary=True)
    #print(wordVec.vocab)
    print(wordVec.vectors)
    """
    [[-0.01052924 -0.1279599  -0.01904371 ...  0.07920815  0.22228035
  -0.04407839]
 [ 0.18784362 -0.02365937 -0.02105375 ... -0.06552303  0.22494133
   0.07121059]
 [ 0.00984113 -0.07766325 -0.08419503 ...  0.08153868  0.08172731
  -0.06043071]
 ...
 [ 0.06315739  0.07745016  0.03186593 ...  0.01776537 -0.06750683
   0.07837379]
 [ 0.00361621  0.08324724 -0.02496305 ...  0.02484753 -0.02246555
  -0.1115197 ]
 [ 0.12445355 -0.03489489 -0.08211292 ... -0.05408037  0.00077594
  -0.19323835]]
    """
    print(wordVec.vectors.shape)
    """
    # 训练10000条数据 共4376个词
    (4376, 300) 
    """
    
    # calculate the distance between two or more words
    print("the distance is : ", wordVec.distance("2967", "339"))
    
    # retreive words similarity
    print("retreive words similarity : ", wordVec.wv.similar_by_word('4464', topn =10)) # 与4464相似度最高的前10个词
    print("the similarity is : ", wordVec.wv.similarity("4464", "3659")) # 计算两个词之间的相似度
    """
    calculate the distance between two or more words : 0.9542501345276833
    retreive words similarity :
    [('3659', 0.9462677240371704), ('2799', 0.904218316078186), ('4646', 0.9016894102096558), 			('4149', 0.8958635330200195), ('6065', 0.8915048241615295), ('3700', 0.8826210498809814), 			('5602', 0.8771995306015015), ('1324', 0.8682945370674133), ('3370', 0.8460589647293091), 			('6250', 0.5700042247772217)]
    the similarity is : 0.94626784
	"""
    
word2VecFile = './data/word2VecTrain.txt'
modelFile = '../model/word2Vec.bin'
creatWord2VecTrainData(word2VecFile)
trainWord2vec(word2VecFile, modelFile)
loadWord2vec(modelFile)

"""
主要参数介绍如下:
1) sentences:我们要分析的语料,可以是一个列表,或者从文件中遍历读出(word2vec.LineSentence(filename) )。

2) size:词向量的维度,默认值是100。这个维度的取值一般与我们的语料的大小相关,如果是不大的语料,比如小于100M的文本语料,则使用默认值一般就可以了。如果是超大的语料,建议增大维度。

3) window:即词向量上下文最大距离,window越大,则和某一词较远的词也会产生上下文关系。默认值为5,在实际使用中,可以根据实际的需求来动态调整这个window的大小。如果是小语料则这个值可以设的更小。对于一般的语料这个值推荐在[5;10]之间。

4) sg:即我们的word2vec两个模型的选择了。如果是0, 则是CBOW模型;是1则是Skip-Gram模型;默认是0即CBOW模型。

5) hs:即我们的word2vec两个解法的选择了。如果是0, 则是Negative Sampling;是1的话并且负采样个数negative大于0, 则是Hierarchical Softmax。默认是0即Negative Sampling。

6) negative:即使用Negative Sampling时负采样的个数,默认是5。推荐在[3,10]之间。这个参数在我们的算法原理篇中标记为neg。

7) cbow_mean:仅用于CBOW在做投影的时候,为0,则算法中的xw为上下文的词向量之和,为1则为上下文的词向量的平均值。在我们的原理篇中,是按照词向量的平均值来描述的。个人比较喜欢用平均值来表示xw,默认值也是1,不推荐修改默认值。

8) min_count:需要计算词向量的最小词频。这个值可以去掉一些很生僻的低频词,默认是5。如果是小语料,可以调低这个值。

9) iter:随机梯度下降法中迭代的最大次数,默认是5。对于大语料,可以增大这个值。

10) alpha:在随机梯度下降法中迭代的初始步长。算法原理篇中标记为η,默认是0.025。

11) min_alpha: 由于算法支持在迭代的过程中逐渐减小步长,min_alpha给出了最小的迭代步。
"""

五、TextCNN和TextRNN

(一)、TextCNN模型架构

TextCNN利用CNN(卷积神经网络)进行文本特征抽取,不同大小的卷积核分别抽取n-gram特征,卷积计算出的特 征图经过MaxPooling保留最大的特征值,然后将拼接成一个向量作为文本的表示。这里我们基于TextCNN原始论文的设定,分别采用了100个大小为2,3,4的卷积核,最后得到的文本向量大小为100*3=300维。可通过下图对模型进行详细介绍:

NLP入门之新闻文本分类竞赛——文本分类模型_第4张图片

首先对这些矩阵数据从0-17做了标号,方便后续的说明;其中0为输入数据“I like this movie very much !”,nlp中首先会将要处理的一句话转换为矩阵的表示,通常使用word2vec的方式转为矩阵向量,上图中word2vec的矩阵维度d=5,用5个0-1的浮点数组表示一个词的向量;1、2、3、4、5、6为6个不同卷积核,其中卷积核的宽度d必须为5,(1、2),(3、4),(5、6)的维度两两相同;卷积核1从上向下与0相乘,首先是0的前四列(I like this movie)与1相乘,得到7的第一行(就一个值),然后0的第二列到第五列(like this movie very)与1相乘,得到7的第二行…依次类推,我们就得到了7、8、9、10、11、12的卷积结果,再经过max pooling,取7最大的值及8中最大的值组成13,取9、10中各自最大的值组成14,取11、12中各自最大的值组成15,最后将13、14、15拼接成16,整个卷积、池化这些特征提取的工作就完成了,最后在16和17之间加上一层全连接,17表示网络的输出为2,到这里整个分类工程基本搭建好了。

TextCNN模型参考链接: https://www.cnblogs.com/go-ahead-wsg/p/12650693.html

(二)、TextRNN模型架构

TextRNN利用RNN(循环神经网络)进行文本特征抽取,由于文本本身是一种序列,而LSTM天然适合建模序列数据。TextRNN将句子中每个词的词向量依次输入到双向双层LSTM,分别将两个方向最后一个有效位置的隐藏层拼接成一个向量作为文本的表示。

NLP入门之新闻文本分类竞赛——文本分类模型_第5张图片

1.输入层

和TextCNN一样,输入层也是将每个词从onehot类型embedding成稠密的词向量。对于不同长度的问题文本,pad和截断成一样长度的。太短的就补空格,太长的就截断。

2.LSTM层

LSTM层中,设定输出的维度为128维向量,LSTM其实对每个词语都会有一个隐向量,这里是用最后一个词语ht作为最终使用的隐向量,可以看做是包含了前面所有词语的信息(LSTM层默认就是输出最后一个词语的隐向量,如果要使用其他词语的隐向量,需要设定return_sequences=Ture),因此该层输出是128维向量。

3.全连接层

输出层与TextCNN相同,使用全连接层,使用softmax作为激活函数进行输出。

六、HAN网络

Hierarchical Attention Network for Document Classification(HAN)基于层级注意力,在单词和句子级别分别编码并基 于注意力获得文档的表示,然后经过Softmax进行分类。其中word encoder的作用是获得句子的表示,可以替换为上 节提到的TextCNN和TextRNN,也可以替换为下节中的BERT。

NLP入门之新闻文本分类竞赛——文本分类模型_第6张图片
上图就是HAN模型的整体框架,可以看出主要分为四个部分:

  • word encoder (BiGRU layer)
  • word attention (Attention layer)
  • sentence encoder (BiGRU layer)
  • sentence attention (Attention layer)

1.word encoder layer

首先,将每个句子中的单词做embedding转换成词向量,然后,输入到双向GRU网络中,结合上下文的信息,获得该单词对应的隐藏状态输出。

2.word attention layer

attention机制的目的就是要把一个句子中,对句子表达最重要的单词找出来,赋予一个更大的比重。首先将word encoder那一步的输出得到的输入到一个单层的感知机中得到结果作为其隐含表示;接着为了衡量单词的重要性,定义了一个随机初始化的单词层面上下文向量,计算其与句子中每个单词的相似度,然后经过一个softmax操作获得了一个归一化的attention权重矩阵,代表句子i中第t个单词的权重。于是,句子的向量Si就可以看做是句子中单词的向量的加权求和。

3.sentence encoder

通过上述步骤我们得到了每个句子的向量表示,然后可以用相似的方法得到文档向量。

4.sentence attention

和词级别的attention类似,通过一个句子级别的上下文向量,来衡量一个句子在整篇文本的重要性。

附:

文本分类相关学习链接: https://www.cnblogs.com/jiangxinyang/p/10207273.html

textCNN代码参考: https://tianchi.aliyun.com/notebook-ai/detail?spm=5176.12586969.1002.6.64063dadfiXoX7&postId=118258

你可能感兴趣的:(competition,神经网络,自然语言处理,python,算法)