Tensorflow深度学习算法整理(二)

原创
10/14 20:42
阅读数 3.8K

Tensorflow深度学习算法整理

循环神经网络

序列式问题

  • 为什么需要循环神经网络

首先我们来看一下普通的神经网络的样子

这里红色部分是输入,比如说图像;绿色部分是网络部分,比如说卷积部分和全连接部分;蓝色部分是输出,比如说最终得到的分类概率。这样的网络结构很适合做图像的分类,图像的检测,这种数据都是固定的数据。如果是变长的数据,比如说文本,它的长度是不一定的,这个时候我们该怎么做呢?

这个时候其实就需要循环神经网络,循环神经网络是专门用来处理序列式问题的。循环神经网络可以解决一对多问题,如上图所示,我们的数据集是一个输入,但是我们要形成多个输出,多个输出卷积神经网络是做不了的,它只能给出来一个输出。一个基本的场景就是给定一张图片,去生成一个描述,这个描述就是一个文本,文本是不定长的。我可以说这个图片风景优美,也可以说这个图片有一条小河,有一个孩童等等。

然后就是多对一的,多对一有一个很经典的问题就是文本分类(文本情感分析),分本分类的输出也只有一个,但是它的输入是不定长的。这种情况下卷积神经网络也无法处理。如果实在要处理的话,可以让输入强行变的对齐,这样才可以处理。但是在不定长情况下,只有循环神经网络可以做到。

然后就是多对多,多对多有一个经典的问题就是机器翻译,比如说我知道一个中文的句子,想把它翻译成英文,在这个时候就需要用到循环神经网络去解决。可以通过把中文信息都编码到一个数据中去,再依据这个数据去生成新的英文句子。但是这种翻译不是一种实时翻译,实时翻译是一个词一个词的翻译。

实时多对多,如视频解说。比如说世界杯解说,现在一般都是人力解说,但其实也可以用机器来解说,虽然用机器解说还不能达到生动有趣的效果,但是生成基本的描述是没有问题的。视频解说的输入是视频中一帧一帧的图片,每一个输出都是一个不定长的句子。这就达到了一个视频解说的目的。

循环神经网络

循环神经网络类似于之前的架构,但是它多了一个自我指向的路径。这条自我指向的路径表达的是,输入可能是多个,然后需要保存一个中间状态,这个中间状态可以帮我们了解之前的输入的情况。维护一个状态作为下一步的额外输入。首先我们有一个输入x0(表示第0步)到RNN中去得到一个状态s0,这个状态经过一个变化输出到y0。下一次,由于我们得到一个中间状态s0,s0可以和后面的x1一块输入到RNN中去,得到s1,之后再输出y1。这个是它的一个基本思想。

循环神经网络每一步使用同样的激活函数和参数。我们的输入可能有x0,x1,x2....,它每一步的参数是共享的。

这里就是中间的状态,它等于上一步的状态和当前的输入一起做一个拼接再经过一个变换就得到当前的状态。当前的状态再经过一个变换就能得到当前的输出。这是最简单的循环神经网络。再将该公式展开就得到

在这里用了一个激活函数叫tanh。是如何组合在一起的,在这里用了一个矩阵变换W和U,W和U都是矩阵参数。这两个矩阵参数分别对做了变换之后再相加(关于矩阵变换的内容请参考线性代数整理(二) 中的线性变换)。然后再经过一个激活函数就得到了当前的状态值

当前状态再经过一个变换V,再使用多分类归一化softmax,就得到了当前概率值

现在我们来看一个例子

这是一个字符语言模型,它的目的是预测下一个字符,词典为[j, e, p],样本为jeep。语言模型是NLP(自然语言处理)领域里面常用的一个模型,它的模型的基本要素就是说我给定一个上下文context,我能预测下一个字或者词是什么。比如应用在输入法上的模型,它可以帮大家预测下一个要输入的词是什么。字符语言模型跟词语言模型不一样,词语言模型是预测下一个词是什么,字符语言模型是预测下一个字符是什么。比如说hello,我输入了he之后需要预测出下一个是l。

这里我们简化问题,字典里面只有j、e、p三个字符,首先我们的输入样本是j,此时我们并不知道后面的样本是什么。把j输入到循环神经网络中去,记录下一个状态,再经过一个变换,这里为了简化过程,我们就进行了等值变换,进入到输出,得到一组概率值。然后再计算概率分布。

就是这个过程,这里概率分布中第1个位置的概率1.3是最大的,它代表是第1个位置,所以预测下一个字母是e(这里j排第0,e排第1,p排第2)。

此时我们得到了第一个输出是e,再把e作为第二次输入,第一次输入j的时候得到了一个隐含状态,这个隐含状态和下一个e一块输入到下一个RNN中的深度神经单元中去,然后得到第二次的输出的概率分布。这个概率分布依然是第一个位置上的概率最大为2.1,所以第三个字符也是e。

同理,我们把第二次的输出e作为第三次的输入,在此之前,我们第一次输入的j以及第二次作为输入预测出来的e,它们都有一个隐含状态,这两个隐含状态都连接到了第三次输入的RNN神经网络,再加上第二次预测的e一块输入进去,得到了第三次输出的概率分布,这次最大值为第3个位置的4.5,所以预测出来的字符为p。

如果作为测试数据集,我们可能只知道第一个j,后面的字符都不知道。由于每一步的输入都依赖于上一步的预测值,如果中间的某个地方预测错了,那么很可能从中间到往后的预测值都是错的。如果模型训练的比较好的话,数据量比较大,它还是可以有一定的兼容性的,比如中间错了,但是后面的预测依然是对的。对于p来说,因为之前的状态都是加权过来的,越往前的状态值对最后结果的预测值就越没有影响力,当前的输入对当前的输出是有最大影响的,越往前的输入对最后的预测越没有影响。所以这种兼容性还是可以训练出来的。

循环神经网络的正向传播

我们在讲普通的卷积神经网络的正向传播是先计算低层的,再计算高层的。但是在循环神经网络中只有一层,它的正向传播是序列式的,后输入的状态依赖于先输入的状态,所以它需要先计算第一个位置上的值,然后再去计算第二个位置上的值,然后再去计算第三个、第四个、第五个位置上的值。循环神经网络的正向传播就是序列式的,按照输入顺序去进行计算的一个过程。在每个位置上都得到了一个预测值之后,可以在每个位置上去计算损失函数。最后的损失函数是所有的中间步骤的损失函数的和。当然这个循环神经网络是最基础的循环神经网络,它是实时多对多的那个神经网络结构,后面可以做一些微小的变换,就可以使得它去面对多对一的问题或者一对多的问题以及不实时多对多的问题。如果要应对多对一的问题,那么只需要让前四步的输出都为0就可以了,就是前四步都不输出,只输出第五步的值,然后在第五步上去做损失函数,它也会反向传播去更新所有的W。

循环神经网络的反向传播

循环神经网络的反向传播也是依照序列来做的,正向传播是从序列的头到序列的尾,而反向传播是从序列的尾到序列的头。首先我们要明确一点,在正向传播中每一步用到的W、U、V都是一样的,所以我们在计算梯度的时候,在任何一步上计算梯度,它们的梯度是要相加起来统一去更新W,因为它们本质上就是一个变量。对于一个循环神经网络来说,它有一个递归性在里面,比如说在E3的位置去算W的梯度,它就等于E3就先对s3的一个梯度,s3是它的隐含状态值,这个隐含状态值经过一个变换就得到了y3,y3再经过变换就得到了E3。这里在s上存在循环性,所以是可以直接计算出来的,而就比较复杂。因为s3对W是一个递归的过程。我们看一下它是如何递归的。

这里我们知道最新的状态值s3跟之前的状态值s2和最新的输入x3是这样一个关系。由于是对W求导,我们就可以把Ux3看成一个常数,剩下的就是Ws2,s2并不是一个常数,它跟W是有关系的,假设它的关系为v(W),那么求导就变成了两个函数相乘对W求导(可以参考高等数学整理 中的求导公式),这里W可以看成是它自身u(W)。

那么我们对做展开,首先我们知道s3是s2的函数,s2是s1的函数,s1是s0的函数,所以可得s3分别是s2、s1、s0的函数。根据上面的公式就有(这里的计算比较复杂,我们给出一个结果)

这里s3对sk的导数可以简化为

在这个做计算的过程中可以去简化计算步骤,比如s3对s2求了导数之后,在求s4的时候,我们可以复用这一效果。所以在这里做了一个拆分,s3对于s0的导数可以看成是s3对s2的导数乘以s2对s1的导数乘以s1对s0的导数。这个乘积的作用就是说可以复用中间的计算结果,因为这是一个递归的过程,如果能存下来中间的计算结果的话,那么可以很大的加快这一计算速度。这就循环神经网络反向传播的计算公式。

 我们来看一下反向传播的特点,激活函数Tanh是一种S型函数,输出在-1和1之间,容易梯度消失。当输出值接近-1或者是1的时候,它的梯度值非常的小。当序列非常长的时候,从一个比较长的末端往前传梯度的时候,每次都需要乘一个(-1,1)之间的一个数,这个数就会导致梯度消失。因为乘以10个这样的数,就相当于整个值就变成了10^(-n),在这样一个级别下,梯度就消失了。所以说较远的步骤梯度贡献很小。基于这样的特点,我们需要在循环神经网络梯度下降的时候做一定的优化。比如说较远的步骤对当前梯度的贡献非常小,就可以把较远的步骤给忽略掉,节省很多的计算资源。切换其他激活函数后(不使用tanh,改成relu),可能也会导致梯度爆炸。因为梯度类似于卷积中的层次,它在循环神经网络反映出来是输入需要的长度,比如说一段文本有成百上千上万个词,如果切换了其他函数,而不是tanh之后,它每一步梯度可能会被放大,大于1的就会有放大的效果,就会导致比较后的步骤对于比较前的步骤的影响,每一步放大的话会导致梯度的爆炸,tanh是每一步梯度都会被缩小。

损失函数的计算是每一步的损失函数都加到最后的loss上来,然后去做梯度下降。

不过我们刚才也说了,因为较远的步骤,比如说第一的步骤,它可能对最后一步的梯度计算是很有限的,所以我们可以做一个优化,可以分区的去计算损失函数,把序列分成几个大的块,然后再分别去计算梯度。这一块计算完梯度之后,我们就认为它再往前的序列值就不会有影响。

计算完一部分序列的梯度之后再去计算下一部分的梯度。

多层网络与双向网络

多层网络:底层输出作为高层输入

我们之前讲的都是单层的循环神经网络,在这里,我们可以像卷积神经网络一样,把层次都叠加在一起,形成一个多层的网络,底层作为高层的输入,同层之间依旧递归。增加网络拟合能力。和之前的卷积神经网络类似,加了多层之后,因为每一层都是非线性的变换,可以增加网络的拟合能力。一般隐层的维数是逐渐递增的,64 - 128 - 256.

RNN+残差链接

同样有了多层的循环神经网络,自然之前的卷积神经网络的设置也可以用到循环神经网络中,比如残差网络。我们让网络去学剩余的东西,而把原来的值给加到网络中来。

双向网络

另一路以未来状态为输入。对于循环神经网络和卷积神经网络的不同在于它的输入是一个序列,一个序列可以正向的做输入,也可以反向去做输入。在上图中我们可以看到,深绿色的是一个正向的循环神经网络结构,而浅绿色是一个逆向的循环神经网络结构。这样的网络结构就可以使得它学到上下文的信息。对于正向循环神经网络结构,我可以学到上文的信息,对于还没有输入的肯定还不知道,加了另外一路连接之后,就可以用到下文的信息。两个状态拼接后进入输出层,进一步提高表达能力。但是这样就无法实时的输出结果了,双向网络运用的问题的空间会比较小。但是对于非实时的机器翻译,是可以使用双向神经网络结构的,而且会比单向获得更好的效果。

现在我们用tensorflow2来看一下一个RNN的例子,这是一个情感分析的二分类问题,对电影的评价进行好评还是差评进行分类。我们可以先来看一下它里面的句子中的单词

import os
import tensorflow as tf
import numpy as np
from tensorflow import keras
from tensorflow.keras import layers, optimizers, Sequential
from tensorflow.keras.datasets import imdb

if __name__ == "__main__":

    tf.random.set_seed(22)
    np.random.seed(22)
    os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
    assert tf.__version__.startswith('2.')
    batchsz = 128
    # 常见单词数量
    total_words = 10000
    max_review_len = 80
    embedding_len = 100
    (X_train, y_train), (X_test, y_test) = imdb.load_data(num_words=total_words)
    avg_len = list(map(len, X_train))
    np.mean(avg_len)
    word2index = imdb.get_word_index()
    index2word = dict([(value, key) for (key, value) in word2index.items()])
    for j in range(10):
        decoded_review = [index2word.get(i - 3) for i in X_train[j]][1:]
        print(decoded_review)
    # 将每个句子的单词数限制为80
    X_train = keras.preprocessing.sequence.pad_sequences(X_train, maxlen=max_review_len)
    X_test = keras.preprocessing.sequence.pad_sequences(X_test, maxlen=max_review_len)
    # 构建tensorflow数据集
    db_train = tf.data.Dataset.from_tensor_slices((X_train, y_train))
    # 将训练数据集乱序后取批次,每批128长度,并丢弃最后一个批次
    db_train = db_train.shuffle(1000).batch(batchsz, drop_remainder=True)
    db_test = tf.data.Dataset.from_tensor_slices((X_test, y_test))
    db_test = db_test.batch(batchsz, drop_remainder=True)
    print(X_train.shape, tf.reduce_max(y_train), tf.reduce_min(y_train))
    print(X_test.shape)

运行结果

['this', 'film', 'was', 'just', 'brilliant', 'casting', 'location', 'scenery', 'story', 'direction', "everyone's", 'really', 'suited', 'the', 'part', 'they', 'played', 'and', 'you', 'could', 'just', 'imagine', 'being', 'there', 'robert', None, 'is', 'an', 'amazing', 'actor', 'and', 'now', 'the', 'same', 'being', 'director', None, 'father', 'came', 'from', 'the', 'same', 'scottish', 'island', 'as', 'myself', 'so', 'i', 'loved', 'the', 'fact', 'there', 'was', 'a', 'real', 'connection', 'with', 'this', 'film', 'the', 'witty', 'remarks', 'throughout', 'the', 'film', 'were', 'great', 'it', 'was', 'just', 'brilliant', 'so', 'much', 'that', 'i', 'bought', 'the', 'film', 'as', 'soon', 'as', 'it', 'was', 'released', 'for', None, 'and', 'would', 'recommend', 'it', 'to', 'everyone', 'to', 'watch', 'and', 'the', 'fly', 'fishing', 'was', 'amazing', 'really', 'cried', 'at', 'the', 'end', 'it', 'was', 'so', 'sad', 'and', 'you', 'know', 'what', 'they', 'say', 'if', 'you', 'cry', 'at', 'a', 'film', 'it', 'must', 'have', 'been', 'good', 'and', 'this', 'definitely', 'was', 'also', None, 'to', 'the', 'two', 'little', "boy's", 'that', 'played', 'the', None, 'of', 'norman', 'and', 'paul', 'they', 'were', 'just', 'brilliant', 'children', 'are', 'often', 'left', 'out', 'of', 'the', None, 'list', 'i', 'think', 'because', 'the', 'stars', 'that', 'play', 'them', 'all', 'grown', 'up', 'are', 'such', 'a', 'big', 'profile', 'for', 'the', 'whole', 'film', 'but', 'these', 'children', 'are', 'amazing', 'and', 'should', 'be', 'praised', 'for', 'what', 'they', 'have', 'done', "don't", 'you', 'think', 'the', 'whole', 'story', 'was', 'so', 'lovely', 'because', 'it', 'was', 'true', 'and', 'was', "someone's", 'life', 'after', 'all', 'that', 'was', 'shared', 'with', 'us', 'all']
['big', 'hair', 'big', 'boobs', 'bad', 'music', 'and', 'a', 'giant', 'safety', 'pin', 'these', 'are', 'the', 'words', 'to', 'best', 'describe', 'this', 'terrible', 'movie', 'i', 'love', 'cheesy', 'horror', 'movies', 'and', "i've", 'seen', 'hundreds', 'but', 'this', 'had', 'got', 'to', 'be', 'on', 'of', 'the', 'worst', 'ever', 'made', 'the', 'plot', 'is', 'paper', 'thin', 'and', 'ridiculous', 'the', 'acting', 'is', 'an', 'abomination', 'the', 'script', 'is', 'completely', 'laughable', 'the', 'best', 'is', 'the', 'end', 'showdown', 'with', 'the', 'cop', 'and', 'how', 'he', 'worked', 'out', 'who', 'the', 'killer', 'is', "it's", 'just', 'so', 'damn', 'terribly', 'written', 'the', 'clothes', 'are', 'sickening', 'and', 'funny', 'in', 'equal', None, 'the', 'hair', 'is', 'big', 'lots', 'of', 'boobs', None, 'men', 'wear', 'those', 'cut', None, 'shirts', 'that', 'show', 'off', 'their', None, 'sickening', 'that', 'men', 'actually', 'wore', 'them', 'and', 'the', 'music', 'is', 'just', None, 'trash', 'that', 'plays', 'over', 'and', 'over', 'again', 'in', 'almost', 'every', 'scene', 'there', 'is', 'trashy', 'music', 'boobs', 'and', None, 'taking', 'away', 'bodies', 'and', 'the', 'gym', 'still', "doesn't", 'close', 'for', None, 'all', 'joking', 'aside', 'this', 'is', 'a', 'truly', 'bad', 'film', 'whose', 'only', 'charm', 'is', 'to', 'look', 'back', 'on', 'the', 'disaster', 'that', 'was', 'the', "80's", 'and', 'have', 'a', 'good', 'old', 'laugh', 'at', 'how', 'bad', 'everything', 'was', 'back', 'then']
['this', 'has', 'to', 'be', 'one', 'of', 'the', 'worst', 'films', 'of', 'the', '1990s', 'when', 'my', 'friends', 'i', 'were', 'watching', 'this', 'film', 'being', 'the', 'target', 'audience', 'it', 'was', 'aimed', 'at', 'we', 'just', 'sat', 'watched', 'the', 'first', 'half', 'an', 'hour', 'with', 'our', 'jaws', 'touching', 'the', 'floor', 'at', 'how', 'bad', 'it', 'really', 'was', 'the', 'rest', 'of', 'the', 'time', 'everyone', 'else', 'in', 'the', 'theatre', 'just', 'started', 'talking', 'to', 'each', 'other', 'leaving', 'or', 'generally', 'crying', 'into', 'their', 'popcorn', 'that', 'they', 'actually', 'paid', 'money', 'they', 'had', None, 'working', 'to', 'watch', 'this', 'feeble', 'excuse', 'for', 'a', 'film', 'it', 'must', 'have', 'looked', 'like', 'a', 'great', 'idea', 'on', 'paper', 'but', 'on', 'film', 'it', 'looks', 'like', 'no', 'one', 'in', 'the', 'film', 'has', 'a', 'clue', 'what', 'is', 'going', 'on', 'crap', 'acting', 'crap', 'costumes', 'i', "can't", 'get', 'across', 'how', None, 'this', 'is', 'to', 'watch', 'save', 'yourself', 'an', 'hour', 'a', 'bit', 'of', 'your', 'life']
['the', None, None, 'at', 'storytelling', 'the', 'traditional', 'sort', 'many', 'years', 'after', 'the', 'event', 'i', 'can', 'still', 'see', 'in', 'my', None, 'eye', 'an', 'elderly', 'lady', 'my', "friend's", 'mother', 'retelling', 'the', 'battle', 'of', None, 'she', 'makes', 'the', 'characters', 'come', 'alive', 'her', 'passion', 'is', 'that', 'of', 'an', 'eye', 'witness', 'one', 'to', 'the', 'events', 'on', 'the', None, 'heath', 'a', 'mile', 'or', 'so', 'from', 'where', 'she', 'lives', 'br', 'br', 'of', 'course', 'it', 'happened', 'many', 'years', 'before', 'she', 'was', 'born', 'but', 'you', "wouldn't", 'guess', 'from', 'the', 'way', 'she', 'tells', 'it', 'the', 'same', 'story', 'is', 'told', 'in', 'bars', 'the', 'length', 'and', None, 'of', 'scotland', 'as', 'i', 'discussed', 'it', 'with', 'a', 'friend', 'one', 'night', 'in', None, 'a', 'local', 'cut', 'in', 'to', 'give', 'his', 'version', 'the', 'discussion', 'continued', 'to', 'closing', 'time', 'br', 'br', 'stories', 'passed', 'down', 'like', 'this', 'become', 'part', 'of', 'our', 'being', 'who', "doesn't", 'remember', 'the', 'stories', 'our', 'parents', 'told', 'us', 'when', 'we', 'were', 'children', 'they', 'become', 'our', 'invisible', 'world', 'and', 'as', 'we', 'grow', 'older', 'they', 'maybe', 'still', 'serve', 'as', 'inspiration', 'or', 'as', 'an', 'emotional', None, 'fact', 'and', 'fiction', 'blend', 'with', None, 'role', 'models', 'warning', 'stories', None, 'magic', 'and', 'mystery', 'br', 'br', 'my', 'name', 'is', None, 'like', 'my', 'grandfather', 'and', 'his', 'grandfather', 'before', 'him', 'our', 'protagonist', 'introduces', 'himself', 'to', 'us', 'and', 'also', 'introduces', 'the', 'story', 'that', 'stretches', 'back', 'through', 'generations', 'it', 'produces', 'stories', 'within', 'stories', 'stories', 'that', 'evoke', 'the', None, 'wonder', 'of', 'scotland', 'its', 'rugged', 'mountains', None, 'in', None, 'the', 'stuff', 'of', 'legend', 'yet', None, 'is', None, 'in', 'reality', 'this', 'is', 'what', 'gives', 'it', 'its', 'special', 'charm', 'it', 'has', 'a', 'rough', 'beauty', 'and', 'authenticity', None, 'with', 'some', 'of', 'the', 'finest', None, 'singing', 'you', 'will', 'ever', 'hear', 'br', 'br', None, None, 'visits', 'his', 'grandfather', 'in', 'hospital', 'shortly', 'before', 'his', 'death', 'he', 'burns', 'with', 'frustration', 'part', 'of', 'him', None, 'to', 'be', 'in', 'the', 'twenty', 'first', 'century', 'to', 'hang', 'out', 'in', None, 'but', 'he', 'is', 'raised', 'on', 'the', 'western', None, 'among', 'a', None, 'speaking', 'community', 'br', 'br', 'yet', 'there', 'is', 'a', 'deeper', 'conflict', 'within', 'him', 'he', None, 'to', 'know', 'the', 'truth', 'the', 'truth', 'behind', 'his', None, 'ancient', 'stories', 'where', 'does', 'fiction', 'end', 'and', 'he', 'wants', 'to', 'know', 'the', 'truth', 'behind', 'the', 'death', 'of', 'his', 'parents', 'br', 'br', 'he', 'is', 'pulled', 'to', 'make', 'a', 'last', None, 'journey', 'to', 'the', None, 'of', 'one', 'of', None, 'most', None, 'mountains', 'can', 'the', 'truth', 'be', 'told', 'or', 'is', 'it', 'all', 'in', 'stories', 'br', 'br', 'in', 'this', 'story', 'about', 'stories', 'we', None, 'bloody', 'battles', None, 'lovers', 'the', None, 'of', 'old', 'and', 'the', 'sometimes', 'more', None, None, 'of', 'accepted', 'truth', 'in', 'doing', 'so', 'we', 'each', 'connect', 'with', None, 'as', 'he', 'lives', 'the', 'story', 'of', 'his', 'own', 'life', 'br', 'br', None, 'the', None, None, 'is', 'probably', 'the', 'most', 'honest', None, 'and', 'genuinely', 'beautiful', 'film', 'of', 'scotland', 'ever', 'made', 'like', None, 'i', 'got', 'slightly', 'annoyed', 'with', 'the', None, 'of', 'hanging', 'stories', 'on', 'more', 'stories', 'but', 'also', 'like', None, 'i', None, 'this', 'once', 'i', 'saw', 'the', None, 'picture', "'", 'forget', 'the', 'box', 'office', None, 'of', 'braveheart', 'and', 'its', 'like', 'you', 'might', 'even', None, 'the', None, 'famous', None, 'of', 'the', 'wicker', 'man', 'to', 'see', 'a', 'film', 'that', 'is', 'true', 'to', 'scotland', 'this', 'one', 'is', 'probably', 'unique', 'if', 'you', 'maybe', None, 'on', 'it', 'deeply', 'enough', 'you', 'might', 'even', 're', None, 'the', 'power', 'of', 'storytelling', 'and', 'the', 'age', 'old', 'question', 'of', 'whether', 'there', 'are', 'some', 'truths', 'that', 'cannot', 'be', 'told', 'but', 'only', 'experienced']
['worst', 'mistake', 'of', 'my', 'life', 'br', 'br', 'i', 'picked', 'this', 'movie', 'up', 'at', 'target', 'for', '5', 'because', 'i', 'figured', 'hey', "it's", 'sandler', 'i', 'can', 'get', 'some', 'cheap', 'laughs', 'i', 'was', 'wrong', 'completely', 'wrong', 'mid', 'way', 'through', 'the', 'film', 'all', 'three', 'of', 'my', 'friends', 'were', 'asleep', 'and', 'i', 'was', 'still', 'suffering', 'worst', 'plot', 'worst', 'script', 'worst', 'movie', 'i', 'have', 'ever', 'seen', 'i', 'wanted', 'to', 'hit', 'my', 'head', 'up', 'against', 'a', 'wall', 'for', 'an', 'hour', 'then', "i'd", 'stop', 'and', 'you', 'know', 'why', 'because', 'it', 'felt', 'damn', 'good', 'upon', 'bashing', 'my', 'head', 'in', 'i', 'stuck', 'that', 'damn', 'movie', 'in', 'the', None, 'and', 'watched', 'it', 'burn', 'and', 'that', 'felt', 'better', 'than', 'anything', 'else', "i've", 'ever', 'done', 'it', 'took', 'american', 'psycho', 'army', 'of', 'darkness', 'and', 'kill', 'bill', 'just', 'to', 'get', 'over', 'that', 'crap', 'i', 'hate', 'you', 'sandler', 'for', 'actually', 'going', 'through', 'with', 'this', 'and', 'ruining', 'a', 'whole', 'day', 'of', 'my', 'life']
['begins', 'better', 'than', 'it', 'ends', 'funny', 'that', 'the', 'russian', 'submarine', 'crew', None, 'all', 'other', 'actors', "it's", 'like', 'those', 'scenes', 'where', 'documentary', 'shots', 'br', 'br', 'spoiler', 'part', 'the', 'message', None, 'was', 'contrary', 'to', 'the', 'whole', 'story', 'it', 'just', 'does', 'not', None, 'br', 'br']
['lavish', 'production', 'values', 'and', 'solid', 'performances', 'in', 'this', 'straightforward', 'adaption', 'of', 'jane', None, 'satirical', 'classic', 'about', 'the', 'marriage', 'game', 'within', 'and', 'between', 'the', 'classes', 'in', None, '18th', 'century', 'england', 'northam', 'and', 'paltrow', 'are', 'a', None, 'mixture', 'as', 'friends', 'who', 'must', 'pass', 'through', None, 'and', 'lies', 'to', 'discover', 'that', 'they', 'love', 'each', 'other', 'good', 'humor', 'is', 'a', None, 'virtue', 'which', 'goes', 'a', 'long', 'way', 'towards', 'explaining', 'the', None, 'of', 'the', 'aged', 'source', 'material', 'which', 'has', 'been', 'toned', 'down', 'a', 'bit', 'in', 'its', 'harsh', None, 'i', 'liked', 'the', 'look', 'of', 'the', 'film', 'and', 'how', 'shots', 'were', 'set', 'up', 'and', 'i', 'thought', 'it', "didn't", 'rely', 'too', 'much', 'on', None, 'of', 'head', 'shots', 'like', 'most', 'other', 'films', 'of', 'the', '80s', 'and', '90s', 'do', 'very', 'good', 'results']
['the', None, 'tells', 'the', 'story', 'of', 'the', 'four', 'hamilton', 'siblings', 'teenager', 'francis', None, None, 'twins', None, 'joseph', None, None, None, None, 'the', None, 'david', 'samuel', 'who', 'is', 'now', 'the', 'surrogate', 'parent', 'in', 'charge', 'the', None, 'move', 'house', 'a', 'lot', None, 'is', 'unsure', 'why', 'is', 'unhappy', 'with', 'the', 'way', 'things', 'are', 'the', 'fact', 'that', 'his', "brother's", 'sister', 'kidnap', None, 'murder', 'people', 'in', 'the', 'basement', "doesn't", 'help', 'relax', 'or', 'calm', None, 'nerves', 'either', 'francis', None, 'something', 'just', "isn't", 'right', 'when', 'he', 'eventually', 'finds', 'out', 'the', 'truth', 'things', 'will', 'never', 'be', 'the', 'same', 'again', 'br', 'br', 'co', 'written', 'co', 'produced', 'directed', 'by', 'mitchell', None, 'phil', None, 'as', 'the', 'butcher', 'brothers', "who's", 'only', 'other', 'film', "director's", 'credit', 'so', 'far', 'is', 'the', 'april', None, 'day', '2008', 'remake', 'enough', 'said', 'this', 'was', 'one', 'of', 'the', None, 'to', 'die', None, 'at', 'the', '2006', 'after', 'dark', None, 'or', 'whatever', "it's", 'called', 'in', 'keeping', 'with', 'pretty', 'much', 'all', 'the', "other's", "i've", 'seen', 'i', 'thought', 'the', None, 'was', 'complete', 'total', 'utter', 'crap', 'i', 'found', 'the', "character's", 'really', 'poor', 'very', 'unlikable', 'the', 'slow', 'moving', 'story', 'failed', 'to', 'capture', 'my', 'imagination', 'or', 'sustain', 'my', 'interest', 'over', "it's", '85', 'a', 'half', 'minute', 'too', 'long', None, 'minute', 'duration', 'the', "there's", 'the', 'awful', 'twist', 'at', 'the', 'end', 'which', 'had', 'me', 'laughing', 'out', 'loud', "there's", 'this', 'really', 'big', None, 'build', 'up', 'to', "what's", 'inside', 'a', None, 'thing', 'in', 'the', None, 'basement', "it's", 'eventually', 'revealed', 'to', 'be', 'a', 'little', 'boy', 'with', 'a', 'teddy', 'is', 'that', 'really', 'supposed', 'to', 'scare', 'us', 'is', 'that', 'really', 'supposed', 'to', 'shock', 'us', 'is', 'that', 'really', 'something', 'that', 'is', 'supposed', 'to', 'have', 'us', 'talking', 'about', 'it', 'as', 'the', 'end', 'credits', 'roll', 'is', 'a', 'harmless', 'looking', 'young', 'boy', 'the', 'best', None, 'ending', 'that', 'the', 'makers', 'could', 'come', 'up', 'with', 'the', 'boring', 'plot', None, 'along', "it's", 'never', 'made', 'clear', 'where', 'the', None, 'get', 'all', 'their', 'money', 'from', 'to', 'buy', 'new', 'houses', 'since', 'none', 'of', 'them', 'seem', 'to', 'work', 'except', 'david', 'in', 'a', None, 'i', 'doubt', 'that', 'pays', 'much', 'or', 'why', 'they', "haven't", 'been', 'caught', 'before', 'now', 'the', 'script', 'tries', 'to', 'mix', 'in', 'every', 'day', 'drama', 'with', 'potent', 'horror', 'it', 'just', 'does', 'a', 'terrible', 'job', 'of', 'combining', 'the', 'two', 'to', 'the', 'extent', 'that', 'neither', 'aspect', 'is', 'memorable', 'or', 'effective', 'a', 'really', 'bad', 'film', 'that', 'i', 'am', 'struggling', 'to', 'say', 'anything', 'good', 'about', 'br', 'br', 'despite', 'being', 'written', 'directed', 'by', 'the', 'extreme', 'sounding', 'butcher', 'brothers', "there's", 'no', 'gore', 'here', "there's", 'a', 'bit', 'of', 'blood', 'splatter', 'a', 'few', 'scenes', 'of', 'girls', None, 'up', 'in', 'a', 'basement', 'but', 'nothing', 'you', "couldn't", 'do', 'at', 'home', 'yourself', 'with', 'a', 'bottle', 'of', None, None, 'a', 'camcorder', 'the', 'film', 'is', 'neither', 'scary', 'since', "it's", 'got', 'a', 'very', 'middle', 'class', 'suburban', 'setting', "there's", 'zero', 'atmosphere', 'or', 'mood', "there's", 'a', 'lesbian', 'suggest', 'incestuous', 'kiss', 'but', 'the', None, 'is', 'low', 'on', 'the', 'exploitation', 'scale', "there's", 'not', 'much', 'here', 'for', 'the', 'horror', 'crowd', 'br', 'br', 'filmed', 'in', None, 'in', 'california', 'this', 'has', 'that', 'modern', 'low', 'budget', 'look', 'about', 'it', "it's", 'not', 'badly', 'made', 'but', 'rather', 'forgettable', 'the', 'acting', 'by', 'an', 'unknown', 'to', 'me', 'cast', 'is', 'nothing', 'to', 'write', 'home', 'about', 'i', "can't", 'say', 'i', 'ever', 'felt', 'anything', 'for', 'anyone', 'br', 'br', 'the', None, 'commits', 'the', None, 'sin', 'of', 'being', 'both', 'dull', 'boring', 'from', 'which', 'it', 'never', None, 'add', 'to', 'that', 'an', 'ultra', 'thin', 'story', 'no', 'gore', 'a', 'rubbish', 'ending', "character's", 'who', 'you', "don't", 'give', 'a', 'toss', 'about', 'you', 'have', 'a', 'film', 'that', 'did', 'not', 'impress', 'me', 'at', 'all']
['just', 'got', 'out', 'and', 'cannot', 'believe', 'what', 'a', 'brilliant', 'documentary', 'this', 'is', 'rarely', 'do', 'you', 'walk', 'out', 'of', 'a', 'movie', 'theater', 'in', 'such', 'awe', 'and', None, 'lately', 'movies', 'have', 'become', 'so', 'over', 'hyped', 'that', 'the', 'thrill', 'of', 'discovering', 'something', 'truly', 'special', 'and', 'unique', 'rarely', 'happens', None, None, 'did', 'this', 'to', 'me', 'when', 'it', 'first', 'came', 'out', 'and', 'this', 'movie', 'is', 'doing', 'to', 'me', 'now', 'i', "didn't", 'know', 'a', 'thing', 'about', 'this', 'before', 'going', 'into', 'it', 'and', 'what', 'a', 'surprise', 'if', 'you', 'hear', 'the', 'concept', 'you', 'might', 'get', 'the', 'feeling', 'that', 'this', 'is', 'one', 'of', 'those', None, 'movies', 'about', 'an', 'amazing', 'triumph', 'covered', 'with', 'over', 'the', 'top', 'music', 'and', 'trying', 'to', 'have', 'us', 'fully', 'convinced', 'of', 'what', 'a', 'great', 'story', 'it', 'is', 'telling', 'but', 'then', 'not', 'letting', 'us', 'in', None, 'this', 'is', 'not', 'that', 'movie', 'the', 'people', 'tell', 'the', 'story', 'this', 'does', 'such', 'a', 'good', 'job', 'of', 'capturing', 'every', 'moment', 'of', 'their', 'involvement', 'while', 'we', 'enter', 'their', 'world', 'and', 'feel', 'every', 'second', 'with', 'them', 'there', 'is', 'so', 'much', 'beyond', 'the', 'climb', 'that', 'makes', 'everything', 'they', 'go', 'through', 'so', 'much', 'more', 'tense', 'touching', 'the', 'void', 'was', 'also', 'a', 'great', 'doc', 'about', 'mountain', 'climbing', 'and', 'showing', 'the', 'intensity', 'in', 'an', 'engaging', 'way', 'but', 'this', 'film', 'is', 'much', 'more', 'of', 'a', 'human', 'story', 'i', 'just', 'saw', 'it', 'today', 'but', 'i', 'will', 'go', 'and', 'say', 'that', 'this', 'is', 'one', 'of', 'the', 'best', 'documentaries', 'i', 'have', 'ever', 'seen']
['this', 'movie', 'has', 'many', 'problem', 'associated', 'with', 'it', 'that', 'makes', 'it', 'come', 'off', 'like', 'a', 'low', 'budget', 'class', 'project', 'from', 'someone', 'in', 'film', 'school', 'i', 'have', 'to', 'give', 'it', 'credit', 'on', 'its', None, 'though', 'many', 'times', 'throughout', 'the', 'movie', 'i', 'found', 'myself', 'laughing', 'hysterically', 'it', 'was', 'so', 'bad', 'at', 'times', 'that', 'it', 'was', 'comical', 'which', 'made', 'it', 'a', 'fun', 'watch', 'br', 'br', 'if', "you're", 'looking', 'for', 'a', 'low', 'grade', 'slasher', 'movie', 'with', 'a', 'twist', 'of', 'psychological', 'horror', 'and', 'a', 'dash', 'of', 'campy', None, 'then', 'pop', 'a', 'bowl', 'of', 'popcorn', 'invite', 'some', 'friends', 'over', 'and', 'have', 'some', 'fun', 'br', 'br', 'i', 'agree', 'with', 'other', 'comments', 'that', 'the', 'sound', 'is', 'very', 'bad', 'dialog', 'is', 'next', 'to', 'impossible', 'to', 'follow', 'much', 'of', 'the', 'time', 'and', 'the', 'soundtrack', 'is', 'kind', 'of', 'just', 'there']
(25000, 80) tf.Tensor(1, shape=(), dtype=int64) tf.Tensor(0, shape=(), dtype=int64)
(25000, 80)

这里我们可以看到,无论是训练数据集还是测试数据集都有25000条句子,每个句子有80个单词。y=1的时候代表着好评,y=0的时候代表差评。现在我们来构建最普通的RNN模型来进行训练

import os
import tensorflow as tf
import numpy as np
from tensorflow import keras
from tensorflow.keras import layers, optimizers

if __name__ == "__main__":

    tf.random.set_seed(22)
    np.random.seed(22)
    os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
    assert tf.__version__.startswith('2.')
    batchsz = 128
    # 常见单词数量
    total_words = 10000
    max_review_len = 80
    embedding_len = 100
    (X_train, y_train), (X_test, y_test) = keras.datasets.imdb.load_data(num_words=total_words)
    # 将每个句子的单词数限制为80
    X_train = keras.preprocessing.sequence.pad_sequences(X_train, maxlen=max_review_len)
    X_test = keras.preprocessing.sequence.pad_sequences(X_test, maxlen=max_review_len)
    # 构建tensorflow数据集
    db_train = tf.data.Dataset.from_tensor_slices((X_train, y_train))
    # 将训练数据集乱序后取批次,每批128长度,并丢弃最后一个批次
    db_train = db_train.shuffle(1000).batch(batchsz, drop_remainder=True)
    db_test = tf.data.Dataset.from_tensor_slices((X_test, y_test))
    db_test = db_test.batch(batchsz, drop_remainder=True)
    print(X_train.shape, tf.reduce_max(y_train), tf.reduce_min(y_train))
    print(X_test.shape)

    class MyRNN(keras.Model):

        def __init__(self, units):
            super(MyRNN, self).__init__()
            # RNN的初始状态
            self.state0 = [tf.zeros([batchsz, units])]
            # 将文本的单词转化为100维向量,[b, 80] -> [b, 80, 100]
            # 这里表示每个句子有80个单词,每个单词用100维向量表示
            self.embedding = layers.Embedding(total_words, embedding_len,
                                              input_length=max_review_len)
            # 建立一个序列式传递单元,传递序列数为units
            # 它的最终输出是一个统计结果[b, 80, 100] -> [b, 64]
            self.rnn_cell0 = layers.SimpleRNNCell(units, dropout=0.2)
            # 建立一个全连接层,输出维度为1
            self.fc = layers.Dense(1)


        def call(self, inputs, training=None):
            '''
            :param inputs: 文本输入[b, 80]
            :param training: 判断是训练模式还是测试模式
            :return:
            '''
            X = inputs
            X = self.embedding(X)
            state0 = self.state0
            # 遍历句子中的每一个单词向量
            for word in tf.unstack(X, axis=1):
                # 计算tanh(Ws+Ux),这里state1是新状态,out=state1,但是out不做更新
                out, state1 = self.rnn_cell0(word, state0, training)
                state0 = state1
            # out:[b, 64]
            X = self.fc(out)
            prob = tf.sigmoid(X)
            return prob

    def main():
        units = 64
        epochs = 4
        model = MyRNN(units)
        # 创建一个梯度下降优化器
        optimizer = optimizers.Adam(learning_rate=1e-3)
        # 二分类损失函数
        loss = tf.losses.BinaryCrossentropy()
        model.compile(optimizer=optimizer, loss=loss,
                      metrics=['accuracy'], experimental_run_tf_function=False)
        model.fit(db_train, epochs=epochs, validation_data=db_test)
        model.evaluate(db_test)

    main()

运行结果

(25000, 80) tf.Tensor(1, shape=(), dtype=int64) tf.Tensor(0, shape=(), dtype=int64)
(25000, 80)
Epoch 1/4
195/195 [==============================] - 44s 225ms/step - loss: 0.4856 - accuracy: 0.6881 - val_loss: 0.3793 - val_accuracy: 0.8349
Epoch 2/4
195/195 [==============================] - 31s 161ms/step - loss: 0.3023 - accuracy: 0.8679 - val_loss: 0.3819 - val_accuracy: 0.8321
Epoch 3/4
195/195 [==============================] - 32s 164ms/step - loss: 0.1812 - accuracy: 0.9251 - val_loss: 0.4525 - val_accuracy: 0.8210
Epoch 4/4
195/195 [==============================] - 31s 161ms/step - loss: 0.0834 - accuracy: 0.9692 - val_loss: 0.5541 - val_accuracy: 0.8152
195/195 [==============================] - 10s 49ms/step - loss: 0.5541 - accuracy: 0.8152

这里我们可以看到它的训练数据集的分类准确度可以达到96.92%,而测试数据集的分类准确度只有81.52,说明这里存在一定的过拟合,现在我们来增加一层RNN网络,使它变为一个多层网络结构

import os
import tensorflow as tf
import numpy as np
from tensorflow import keras
from tensorflow.keras import layers, optimizers

if __name__ == "__main__":

    tf.random.set_seed(22)
    np.random.seed(22)
    os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
    assert tf.__version__.startswith('2.')
    batchsz = 128
    # 常见单词数量
    total_words = 10000
    max_review_len = 80
    embedding_len = 100
    (X_train, y_train), (X_test, y_test) = keras.datasets.imdb.load_data(num_words=total_words)
    # 将每个句子的单词数限制为80
    X_train = keras.preprocessing.sequence.pad_sequences(X_train, maxlen=max_review_len)
    X_test = keras.preprocessing.sequence.pad_sequences(X_test, maxlen=max_review_len)
    # 构建tensorflow数据集
    db_train = tf.data.Dataset.from_tensor_slices((X_train, y_train))
    # 将训练数据集乱序后取批次,每批128长度,并丢弃最后一个批次
    db_train = db_train.shuffle(1000).batch(batchsz, drop_remainder=True)
    db_test = tf.data.Dataset.from_tensor_slices((X_test, y_test))
    db_test = db_test.batch(batchsz, drop_remainder=True)
    print(X_train.shape, tf.reduce_max(y_train), tf.reduce_min(y_train))
    print(X_test.shape)

    class MyRNN(keras.Model):

        def __init__(self, units):
            super(MyRNN, self).__init__()
            # RNN的初始状态
            self.state0 = [tf.zeros([batchsz, units])]
            self.state1 = [tf.zeros([batchsz, units])]
            # 将文本的单词转化为100维向量,[b, 80] -> [b, 80, 100]
            # 这里表示每个句子有80个单词,每个单词用100维向量表示
            self.embedding = layers.Embedding(total_words, embedding_len,
                                              input_length=max_review_len)
            # 建立一个序列式传递单元,传递序列数为units
            # 它的最终输出是一个统计结果[b, 80, 100] -> [b, 64]
            self.rnn_cell0 = layers.SimpleRNNCell(units, dropout=0.5)
            self.rnn_cell1 = layers.SimpleRNNCell(units, dropout=0.5)
            # 建立一个全连接层,输出维度为1
            self.fc = layers.Dense(1)


        def call(self, inputs, training=None):
            '''
            :param inputs: 文本输入[b, 80]
            :param training: 判断是训练模式还是测试模式
            :return:
            '''
            X = inputs
            X = self.embedding(X)
            state0 = self.state0
            state1 = self.state1
            # 遍历句子中的每一个单词向量
            for word in tf.unstack(X, axis=1):
                # 计算tanh(Ws+Ux),这里state1是新状态,out=state1,但是out不做更新
                out0, state0 = self.rnn_cell0(word, state0, training)
                out1, state1 = self.rnn_cell1(out0, state1, training)
            # out:[b, 64]
            X = self.fc(out1)
            prob = tf.sigmoid(X)
            return prob

    def main():
        units = 64
        epochs = 4
        model = MyRNN(units)
        # 创建一个梯度下降优化器
        optimizer = optimizers.Adam(learning_rate=1e-3)
        # 二分类损失函数
        loss = tf.losses.BinaryCrossentropy()
        model.compile(optimizer=optimizer, loss=loss,
                      metrics=['accuracy'], experimental_run_tf_function=False)
        model.fit(db_train, epochs=epochs, validation_data=db_test)
        model.evaluate(db_test)

    main()

运行结果

(25000, 80) tf.Tensor(1, shape=(), dtype=int64) tf.Tensor(0, shape=(), dtype=int64)
(25000, 80)
Epoch 1/4
195/195 [==============================] - 51s 260ms/step - loss: 0.5781 - accuracy: 0.5808 - val_loss: 0.3949 - val_accuracy: 0.8237
Epoch 2/4
195/195 [==============================] - 35s 181ms/step - loss: 0.3731 - accuracy: 0.8300 - val_loss: 0.3932 - val_accuracy: 0.8355
Epoch 3/4
195/195 [==============================] - 36s 184ms/step - loss: 0.3055 - accuracy: 0.8710 - val_loss: 0.3981 - val_accuracy: 0.8323
Epoch 4/4
195/195 [==============================] - 36s 187ms/step - loss: 0.2515 - accuracy: 0.8974 - val_loss: 0.4310 - val_accuracy: 0.8283
195/195 [==============================] - 11s 58ms/step - loss: 0.4310 - accuracy: 0.8283

这里我们调大了dropout,使它每次经过网络层的时候随机去掉一半的神经元,最后我们可以看到对于测试数据集来说,它的准确度得到了提升

当然我们也可以直接使用tensorflow2的层堆叠模型来简化上面的代码

import os
import tensorflow as tf
import numpy as np
from tensorflow import keras
from tensorflow.keras import layers, optimizers, Sequential

if __name__ == "__main__":

    tf.random.set_seed(22)
    np.random.seed(22)
    os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
    assert tf.__version__.startswith('2.')
    batchsz = 128
    # 常见单词数量
    total_words = 10000
    max_review_len = 80
    embedding_len = 100
    (X_train, y_train), (X_test, y_test) = keras.datasets.imdb.load_data(num_words=total_words)
    # 将每个句子的单词数限制为80
    X_train = keras.preprocessing.sequence.pad_sequences(X_train, maxlen=max_review_len)
    X_test = keras.preprocessing.sequence.pad_sequences(X_test, maxlen=max_review_len)
    # 构建tensorflow数据集
    db_train = tf.data.Dataset.from_tensor_slices((X_train, y_train))
    # 将训练数据集乱序后取批次,每批128长度,并丢弃最后一个批次
    db_train = db_train.shuffle(1000).batch(batchsz, drop_remainder=True)
    db_test = tf.data.Dataset.from_tensor_slices((X_test, y_test))
    db_test = db_test.batch(batchsz, drop_remainder=True)
    print(X_train.shape, tf.reduce_max(y_train), tf.reduce_min(y_train))
    print(X_test.shape)

    class MyRNN(keras.Model):

        def __init__(self, units):
            super(MyRNN, self).__init__()
            # 将文本的单词转化为100维向量,[b, 80] -> [b, 80, 100]
            # 这里表示每个句子有80个单词,每个单词用100维向量表示
            self.embedding = layers.Embedding(total_words, embedding_len,
                                              input_length=max_review_len)
            # 建立一个序列式传递单元,传递序列数为units
            # 它的最终输出是一个统计结果[b, 80, 100] -> [b, 64]
            self.rnn = Sequential([
                layers.SimpleRNN(units, dropout=0.5, return_sequences=True, unroll=True),
                layers.SimpleRNN(units, dropout=0.5, unroll=True),
                layers.Dense(1, activation=tf.nn.sigmoid)
            ])

        def call(self, inputs, training=None):
            '''
            :param inputs: 文本输入[b, 80]
            :param training: 判断是训练模式还是测试模式
            :return:
            '''
            X = inputs
            X = self.embedding(X)
            prob = self.rnn(X)
            return prob

    def main():
        units = 64
        epochs = 4
        model = MyRNN(units)
        # 创建一个梯度下降优化器
        optimizer = optimizers.Adam(learning_rate=1e-3)
        # 二分类损失函数
        loss = tf.losses.BinaryCrossentropy()
        model.compile(optimizer=optimizer, loss=loss,
                      metrics=['accuracy'], experimental_run_tf_function=False)
        model.fit(db_train, epochs=epochs, validation_data=db_test)
        model.evaluate(db_test)

    main()

运行结果

(25000, 80) tf.Tensor(1, shape=(), dtype=int64) tf.Tensor(0, shape=(), dtype=int64)
(25000, 80)
Epoch 1/4
195/195 [==============================] - 80s 408ms/step - loss: 0.5744 - accuracy: 0.5817 - val_loss: 0.4574 - val_accuracy: 0.8075
Epoch 2/4
195/195 [==============================] - 61s 314ms/step - loss: 0.4095 - accuracy: 0.8231 - val_loss: 0.6226 - val_accuracy: 0.6567
Epoch 3/4
195/195 [==============================] - 61s 311ms/step - loss: 0.4267 - accuracy: 0.7309 - val_loss: 0.4312 - val_accuracy: 0.8227
Epoch 4/4
195/195 [==============================] - 64s 327ms/step - loss: 0.2970 - accuracy: 0.8645 - val_loss: 0.3935 - val_accuracy: 0.8310
195/195 [==============================] - 20s 103ms/step - loss: 0.3935 - accuracy: 0.8310

长短期记忆网络

为什么需要LSTM

  • 普通RNN的信息不能长久传播(存在于理论上)

循环网络只有一个隐含状态,然后一个隐含状态可以对待一个序列不停的往下面的步骤里传数据,传到最后,就可以保存上下文的信息。如果是单向的话,就只能保存上文的信息。对于这样一个网络,它的缺点就在于它对序列中的信息没有任何的甄别,而是要神经网络靠参数去进行甄别。而它的参数只有一个,在每一步的循环状态中,只有一个W,这相当于就是我们中间需要学很多的规则,比如新开了一句话,而之前有一句话,那么下面这句话换个主语,这个时候就需要知道需要把这个主语的上文信息给更换一下,把之前的忘掉,用这个新的。需要越来越多的这种规则。但是对于参数只有一个W,而这个W只是一个矩阵,它承载了太多,相当于在卷积神经网络中层次过多的时候,参数就会很多,容量就会很大,就会导致过拟合。但是在循环神经网络这个例子中,参数只有一个W,如果加多层就是多个W,但是需要学到的信息很多,对于一个特别长的句子,要删掉什么信息,要记住什么信息,要更新什么信息。这相当于是W过载了,它无法记住那么多的信息,所以就导致了普通RNN的信息只在理论上存在着可以长久传播的方法。不然的话,它还是只能记住最近的一些信息。

为了解决这个问题,引入了LSTM(长短期记忆网络),L是Long,S是Short,T是Term,M是Memory。它引入了选择性机制。

  1. 选择性输出
  2. 选择性输入
  3. 选择性遗忘

相当于在网络结构中对输入和输出都做了一个控制,如果一个输入没什么用的话,就不输入进来;如果当前信息还不需要输出出去,而需要记在当前状态里面,这个时候就控制不让它输出。比如换了一句新的主语,可以需要忘掉之前的主语,在这个时候选择性遗忘的机制就可以帮我们把之前的主语给忘掉。LSTM是普通循环神经网络的扩展,它把中间的RNN的单元给扩展了。对于一个多层网络上,它相当于是在每一层网络上都做了一个子结构的细分。这个子结构的细分就是引入了选择性的机制,这个机制的实现

选择性-> ,Sigmoid函数[0,1],这个门的计算相当于从某个地方传过来一个值,这个值是0到1之间的一个数,因为是0到1的一个数,我们用σ函数去做最后的变换,0到1之间的就是它需要记住或者是需要遗忘的成分,比如说一个数是10,经过这个门之后,可能门里面的数是0,10*0,就需要把这个数给忘掉。如果门里面的数是1,10*1,就需要把这个数给记住。门里面的数是0.5,10*0.5,就只需要把这个数记住50%。这个门是用来实现选择性机制的。

LSTM模型结构Overview

从上图可以看到,LSTM总体结构和RNN是一样的,下面的X都是输入,A是普通的RNN,我们把A变成LSTM的结构之后,它就是一个LSTM,LSTM也可以做多层网络,上下网络,残差网络,只不过是把中间的结构单元给替换掉了而已。我们可以看到在LSTM中有各种分支,它的输入会经过四个分支。一一去做处理。

符号说明

  • Cell的状态传递

这是隐含状态的一个过程,它经过LSTM的过程中需要做两个操作,第一个和某个东西去做点积,然后需要和网络神经去做加法。点积的作用是遗忘,加法就是和当前位置上的输入信息作一个合并。

  • 遗忘门

新的一句有新的主语,就应该把之前的主语忘掉。LSTM和普通的循环神经网络不一样的地方就在于它除了保存一个当前的隐含状态之外,它会把上一步的输出值也会输入进来,上一步的输出值也就是当前输入的估计值和实际输入组成了当前值的信息。基于当前值的信息可以计算遗忘门的0-1之间的一个向量,然后这个向量就和隐含状态做点积,来让忘掉一些东西。

  • 传入门

是不是要把主语的性别信息添加进来。传入门依然是基于去计算的,计算出来遗忘门的门限之后,然后去做一个内积,因为这里是传入门,这个传入门的门限是要控制传入的信息,传入的信息仍然是,这个输入的信息再去做tanh,这里其实就和普通循环神经网络是一样的,这里只不过把给换成了。计算出来的输出信息就是和输入门限去做内积,就得到了这一次该输入什么。

  • 输出门

动词该用单数形式还是复数形式。输入门和经过遗忘门的状态做加法就形成了一个新的状态,这个新的状态再去和输出门去做一个内积。输出门同样依据当前的输入信息来计算的,计算完输出门之后和当前状态去做内积,就得到了一个当前的输出,这样当前的输出就可以再次输出下一个状态了。

  • 当前状态

经过遗忘门的上一状态,得到了遗忘后的信息,再经过传入门的输入状态,就得到了当前状态值。

现在我们把之前的代码改写成LSTM的

import os
import tensorflow as tf
import numpy as np
from tensorflow import keras
from tensorflow.keras import layers, optimizers

if __name__ == "__main__":

    tf.random.set_seed(22)
    np.random.seed(22)
    os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
    assert tf.__version__.startswith('2.')
    batchsz = 128
    # 常见单词数量
    total_words = 10000
    max_review_len = 80
    embedding_len = 100
    (X_train, y_train), (X_test, y_test) = keras.datasets.imdb.load_data(num_words=total_words)
    # 将每个句子的单词数限制为80
    X_train = keras.preprocessing.sequence.pad_sequences(X_train, maxlen=max_review_len)
    X_test = keras.preprocessing.sequence.pad_sequences(X_test, maxlen=max_review_len)
    # 构建tensorflow数据集
    db_train = tf.data.Dataset.from_tensor_slices((X_train, y_train))
    # 将训练数据集乱序后取批次,每批128长度,并丢弃最后一个批次
    db_train = db_train.shuffle(1000).batch(batchsz, drop_remainder=True)
    db_test = tf.data.Dataset.from_tensor_slices((X_test, y_test))
    db_test = db_test.batch(batchsz, drop_remainder=True)
    print(X_train.shape, tf.reduce_max(y_train), tf.reduce_min(y_train))
    print(X_test.shape)

    class MyRNN(keras.Model):

        def __init__(self, units):
            super(MyRNN, self).__init__()
            # RNN的初始状态,对于LSTM来说要传入两个状态
            self.state0 = [tf.zeros([batchsz, units]), tf.zeros([batchsz, units])]
            self.state1 = [tf.zeros([batchsz, units]), tf.zeros([batchsz, units])]
            # 将文本的单词转化为100维向量,[b, 80] -> [b, 80, 100]
            # 这里表示每个句子有80个单词,每个单词用100维向量表示
            self.embedding = layers.Embedding(total_words, embedding_len,
                                              input_length=max_review_len)
            # 建立一个序列式传递单元,传递序列数为units
            # 它的最终输出是一个统计结果[b, 80, 100] -> [b, 64]
            # self.rnn_cell0 = layers.SimpleRNNCell(units, dropout=0.5)
            # self.rnn_cell1 = layers.SimpleRNNCell(units, dropout=0.5)
            self.rnn_cell0 = layers.LSTMCell(units, dropout=0.5)
            self.rnn_cell1 = layers.LSTMCell(units, dropout=0.5)
            # 建立一个全连接层,输出维度为1
            self.fc = layers.Dense(1)


        def call(self, inputs, training=None):
            '''
            :param inputs: 文本输入[b, 80]
            :param training: 判断是训练模式还是测试模式
            :return:
            '''
            X = inputs
            X = self.embedding(X)
            state0 = self.state0
            state1 = self.state1
            # 遍历句子中的每一个单词向量
            for word in tf.unstack(X, axis=1):
                # 计算tanh(Ws+Ux),这里state1是新状态,out=state1,但是out不做更新
                out0, state0 = self.rnn_cell0(word, state0, training)
                out1, state1 = self.rnn_cell1(out0, state1, training)
            # out:[b, 64]
            X = self.fc(out1)
            prob = tf.sigmoid(X)
            return prob

    def main():
        units = 64
        epochs = 4
        model = MyRNN(units)
        # 创建一个梯度下降优化器
        optimizer = optimizers.Adam(learning_rate=1e-3)
        # 二分类损失函数
        loss = tf.losses.BinaryCrossentropy()
        model.compile(optimizer=optimizer, loss=loss,
                      metrics=['accuracy'], experimental_run_tf_function=False)
        model.fit(db_train, epochs=epochs, validation_data=db_test)
        model.evaluate(db_test)

    main()

运行结果

(25000, 80) tf.Tensor(1, shape=(), dtype=int64) tf.Tensor(0, shape=(), dtype=int64)
(25000, 80)
Epoch 1/4
195/195 [==============================] - 124s 636ms/step - loss: 0.4788 - accuracy: 0.6687 - val_loss: 0.3647 - val_accuracy: 0.8374
Epoch 2/4
195/195 [==============================] - 84s 432ms/step - loss: 0.3124 - accuracy: 0.8593 - val_loss: 0.3598 - val_accuracy: 0.8414
Epoch 3/4
195/195 [==============================] - 100s 513ms/step - loss: 0.2564 - accuracy: 0.8930 - val_loss: 0.3966 - val_accuracy: 0.8359
Epoch 4/4
195/195 [==============================] - 92s 474ms/step - loss: 0.2205 - accuracy: 0.9141 - val_loss: 0.4069 - val_accuracy: 0.8329
195/195 [==============================] - 30s 155ms/step - loss: 0.4069 - accuracy: 0.8329

当然我们也可以直接使用tensorflow2的层堆叠模型来简化上面的代码

import os
import tensorflow as tf
import numpy as np
from tensorflow import keras
from tensorflow.keras import layers, optimizers, Sequential
from tensorflow.keras.datasets import imdb

if __name__ == "__main__":

    tf.random.set_seed(22)
    np.random.seed(22)
    os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
    assert tf.__version__.startswith('2.')
    batchsz = 128
    # 常见单词数量
    total_words = 10000
    max_review_len = 80
    embedding_len = 100
    (X_train, y_train), (X_test, y_test) = imdb.load_data(num_words=total_words)
    # 将每个句子的单词数限制为80
    X_train = keras.preprocessing.sequence.pad_sequences(X_train, maxlen=max_review_len)
    X_test = keras.preprocessing.sequence.pad_sequences(X_test, maxlen=max_review_len)
    # 构建tensorflow数据集
    db_train = tf.data.Dataset.from_tensor_slices((X_train, y_train))
    # 将训练数据集乱序后取批次,每批128长度,并丢弃最后一个批次
    db_train = db_train.shuffle(1000).batch(batchsz, drop_remainder=True)
    db_test = tf.data.Dataset.from_tensor_slices((X_test, y_test))
    db_test = db_test.batch(batchsz, drop_remainder=True)
    print(X_train.shape, tf.reduce_max(y_train), tf.reduce_min(y_train))
    print(X_test.shape)

    class MyRNN(keras.Model):

        def __init__(self, units):
            super(MyRNN, self).__init__()
            # 建立一个序列式传递单元,传递序列数为units
            # 它的最终输出是一个统计结果[b, 80, 100] -> [b, 64]
            self.rnn = Sequential([
                # 将文本的单词转化为100维向量,[b, 80] -> [b, 80, 100]
                # 这里表示每个句子有80个单词,每个单词用100维向量表示
                layers.Embedding(total_words, embedding_len,
                                 input_length=max_review_len),
                # layers.SimpleRNN(units, dropout=0.5, return_sequences=True, unroll=True),
                # layers.SimpleRNN(units, dropout=0.5, unroll=True),
                layers.LSTM(units, dropout=0.5, return_sequences=True, unroll=True),
                layers.LSTM(units, dropout=0.5, unroll=True),
                layers.Dense(1, activation=tf.nn.sigmoid)
            ])

        def call(self, inputs, training=None):
            '''
            :param inputs: 文本输入[b, 80]
            :param training: 判断是训练模式还是测试模式
            :return:
            '''
            X = inputs
            prob = self.rnn(X)
            return prob

    def main():
        units = 64
        epochs = 4
        model = MyRNN(units)
        # 创建一个梯度下降优化器
        optimizer = optimizers.Adam(learning_rate=1e-3)
        # 二分类损失函数
        loss = tf.losses.BinaryCrossentropy()
        model.compile(optimizer=optimizer, loss=loss,
                      metrics=['accuracy'], experimental_run_tf_function=False)
        model.fit(db_train, epochs=epochs, validation_data=db_test)
        model.evaluate(db_test)

    main()

运行结果

(25000, 80) tf.Tensor(1, shape=(), dtype=int64) tf.Tensor(0, shape=(), dtype=int64)
(25000, 80)
Epoch 1/4
195/195 [==============================] - 199s 1s/step - loss: 0.4783 - accuracy: 0.6699 - val_loss: 0.3610 - val_accuracy: 0.8401
Epoch 2/4
195/195 [==============================] - 160s 821ms/step - loss: 0.3112 - accuracy: 0.8628 - val_loss: 0.3573 - val_accuracy: 0.8409
Epoch 3/4
195/195 [==============================] - 151s 774ms/step - loss: 0.2573 - accuracy: 0.8937 - val_loss: 0.4018 - val_accuracy: 0.8354
Epoch 4/4
195/195 [==============================] - 155s 795ms/step - loss: 0.2170 - accuracy: 0.9132 - val_loss: 0.4156 - val_accuracy: 0.8312
195/195 [==============================] - 54s 275ms/step - loss: 0.4156 - accuracy: 0.8312

GRU

GRU是LSTM的一个简化版,它只有两个门,数学表达式如下

两个红色区域是它的两个门,右边的门,我们叫做z门,z是由输入和上一个状态(注意,这里不再是上一个输出,这里跟LSTM是不同的)来进行矩阵变换()的和,再求σ激活函数。左边的门,我们叫做r门,r是由输入和上一个状态来进行矩阵变换()的和,再求σ激活函数。这里的第三个公式中的h是蓝色的部分,它是由上一个状态经过左边的r门跟r做内积,再跟本次输入经过矩阵变换()再求蓝色部分tanh激活函数。r门控制的是上一次的状态对h的影响,当r门全部关闭的时候,那意味着将上一个状态复位。当r门全部打开的时候,将上一个状态全部接收进来,所以r门控制的是上一次状态的阈值。z门控制的是h和的非大即小的关系。

代码实现

import os
import tensorflow as tf
import numpy as np
from tensorflow import keras
from tensorflow.keras import layers, optimizers

if __name__ == "__main__":

    tf.random.set_seed(22)
    np.random.seed(22)
    os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
    assert tf.__version__.startswith('2.')
    batchsz = 128
    # 常见单词数量
    total_words = 10000
    max_review_len = 80
    embedding_len = 100
    (X_train, y_train), (X_test, y_test) = keras.datasets.imdb.load_data(num_words=total_words)
    # 将每个句子的单词数限制为80
    X_train = keras.preprocessing.sequence.pad_sequences(X_train, maxlen=max_review_len)
    X_test = keras.preprocessing.sequence.pad_sequences(X_test, maxlen=max_review_len)
    # 构建tensorflow数据集
    db_train = tf.data.Dataset.from_tensor_slices((X_train, y_train))
    # 将训练数据集乱序后取批次,每批128长度,并丢弃最后一个批次
    db_train = db_train.shuffle(1000).batch(batchsz, drop_remainder=True)
    db_test = tf.data.Dataset.from_tensor_slices((X_test, y_test))
    db_test = db_test.batch(batchsz, drop_remainder=True)
    print(X_train.shape, tf.reduce_max(y_train), tf.reduce_min(y_train))
    print(X_test.shape)

    class MyRNN(keras.Model):

        def __init__(self, units):
            super(MyRNN, self).__init__()
            # RNN的初始状态
            self.state0 = [tf.zeros([batchsz, units])]
            self.state1 = [tf.zeros([batchsz, units])]
            # 将文本的单词转化为100维向量,[b, 80] -> [b, 80, 100]
            # 这里表示每个句子有80个单词,每个单词用100维向量表示
            self.embedding = layers.Embedding(total_words, embedding_len,
                                              input_length=max_review_len)
            # 建立一个序列式传递单元,传递序列数为units
            # 它的最终输出是一个统计结果[b, 80, 100] -> [b, 64]
            self.rnn_cell0 = layers.GRUCell(units, dropout=0.5)
            self.rnn_cell1 = layers.GRUCell(units, dropout=0.5)
            # 建立一个全连接层,输出维度为1
            self.fc = layers.Dense(1)


        def call(self, inputs, training=None):
            '''
            :param inputs: 文本输入[b, 80]
            :param training: 判断是训练模式还是测试模式
            :return:
            '''
            X = inputs
            X = self.embedding(X)
            state0 = self.state0
            state1 = self.state1
            # 遍历句子中的每一个单词向量
            for word in tf.unstack(X, axis=1):
                # 计算tanh(Ws+Ux),这里state1是新状态,out=state1,但是out不做更新
                out0, state0 = self.rnn_cell0(word, state0, training)
                out1, state1 = self.rnn_cell1(out0, state1, training)
            # out:[b, 64]
            X = self.fc(out1)
            prob = tf.sigmoid(X)
            return prob

    def main():
        units = 64
        epochs = 4
        model = MyRNN(units)
        # 创建一个梯度下降优化器
        optimizer = optimizers.Adam(learning_rate=1e-3)
        # 二分类损失函数
        loss = tf.losses.BinaryCrossentropy()
        model.compile(optimizer=optimizer, loss=loss,
                      metrics=['accuracy'], experimental_run_tf_function=False)
        model.fit(db_train, epochs=epochs, validation_data=db_test)
        model.evaluate(db_test)

    main()

运行结果

(25000, 80) tf.Tensor(1, shape=(), dtype=int64) tf.Tensor(0, shape=(), dtype=int64)
(25000, 80)
Epoch 1/4
195/195 [==============================] - 151s 776ms/step - loss: 0.5078 - accuracy: 0.6351 - val_loss: 0.3629 - val_accuracy: 0.8379
Epoch 2/4
195/195 [==============================] - 87s 447ms/step - loss: 0.3185 - accuracy: 0.8555 - val_loss: 0.3605 - val_accuracy: 0.8404
Epoch 3/4
195/195 [==============================] - 85s 436ms/step - loss: 0.2633 - accuracy: 0.8916 - val_loss: 0.4012 - val_accuracy: 0.8331
Epoch 4/4
195/195 [==============================] - 85s 434ms/step - loss: 0.2244 - accuracy: 0.9083 - val_loss: 0.4110 - val_accuracy: 0.8353
195/195 [==============================] - 21s 110ms/step - loss: 0.4110 - accuracy: 0.8353

当然我们也可以直接使用tensorflow2的层堆叠模型来简化上面的代码

import os
import tensorflow as tf
import numpy as np
from tensorflow import keras
from tensorflow.keras import layers, optimizers, Sequential
from tensorflow.keras.datasets import imdb

if __name__ == "__main__":

    tf.random.set_seed(22)
    np.random.seed(22)
    os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
    assert tf.__version__.startswith('2.')
    batchsz = 128
    # 常见单词数量
    total_words = 10000
    max_review_len = 80
    embedding_len = 100
    (X_train, y_train), (X_test, y_test) = imdb.load_data(num_words=total_words)
    # 将每个句子的单词数限制为80
    X_train = keras.preprocessing.sequence.pad_sequences(X_train, maxlen=max_review_len)
    X_test = keras.preprocessing.sequence.pad_sequences(X_test, maxlen=max_review_len)
    # 构建tensorflow数据集
    db_train = tf.data.Dataset.from_tensor_slices((X_train, y_train))
    # 将训练数据集乱序后取批次,每批128长度,并丢弃最后一个批次
    db_train = db_train.shuffle(1000).batch(batchsz, drop_remainder=True)
    db_test = tf.data.Dataset.from_tensor_slices((X_test, y_test))
    db_test = db_test.batch(batchsz, drop_remainder=True)
    print(X_train.shape, tf.reduce_max(y_train), tf.reduce_min(y_train))
    print(X_test.shape)

    class MyRNN(keras.Model):

        def __init__(self, units):
            super(MyRNN, self).__init__()
            # 建立一个序列式传递单元,传递序列数为units
            # 它的最终输出是一个统计结果[b, 80, 100] -> [b, 64]
            self.rnn = Sequential([
                # 将文本的单词转化为100维向量,[b, 80] -> [b, 80, 100]
                # 这里表示每个句子有80个单词,每个单词用100维向量表示
                layers.Embedding(total_words, embedding_len,
                                 input_length=max_review_len),
                # layers.SimpleRNN(units, dropout=0.5, return_sequences=True, unroll=True),
                # layers.SimpleRNN(units, dropout=0.5, unroll=True),
                layers.GRU(units, dropout=0.5, return_sequences=True, unroll=True),
                layers.GRU(units, dropout=0.5, unroll=True),
                layers.Dense(1, activation=tf.nn.sigmoid)
            ])

        def call(self, inputs, training=None):
            '''
            :param inputs: 文本输入[b, 80]
            :param training: 判断是训练模式还是测试模式
            :return:
            '''
            X = inputs
            prob = self.rnn(X)
            return prob

    def main():
        units = 64
        epochs = 4
        model = MyRNN(units)
        # 创建一个梯度下降优化器
        optimizer = optimizers.Adam(learning_rate=1e-3)
        # 二分类损失函数
        loss = tf.losses.BinaryCrossentropy()
        model.compile(optimizer=optimizer, loss=loss,
                      metrics=['accuracy'], experimental_run_tf_function=False)
        model.fit(db_train, epochs=epochs, validation_data=db_test)
        model.evaluate(db_test)

    main()

运行结果

(25000, 80) tf.Tensor(1, shape=(), dtype=int64) tf.Tensor(0, shape=(), dtype=int64)
(25000, 80)
Epoch 1/4
195/195 [==============================] - 242s 1s/step - loss: 0.5079 - accuracy: 0.6362 - val_loss: 0.3689 - val_accuracy: 0.8363
Epoch 2/4
195/195 [==============================] - 153s 784ms/step - loss: 0.3244 - accuracy: 0.8491 - val_loss: 0.3608 - val_accuracy: 0.8387
Epoch 3/4
195/195 [==============================] - 150s 768ms/step - loss: 0.2669 - accuracy: 0.8848 - val_loss: 0.4033 - val_accuracy: 0.8363
Epoch 4/4
195/195 [==============================] - 149s 765ms/step - loss: 0.2293 - accuracy: 0.9079 - val_loss: 0.4070 - val_accuracy: 0.8350
195/195 [==============================] - 41s 208ms/step - loss: 0.4070 - accuracy: 0.8350

基于LSTM的文本分类模型(TextRNN与HAN)

文本分类

文本分类的意思就是说,我有很多的文本,我需要让网络去识别这些文本是什么类别。比如说一些互联网公司需要做一些舆情监测,邮件内容检测,商品评论分析等等。这是NLP领域处理的比较常用的问题。如上图所示,我们在这里使用一个循环神经网络,这个循环神经网络可以是一个普通的循环神经网络,也可以是一个LSTM网络,我们要输入的文本是中文的,所以我们需要对其进行分词,对于每一个词,我们需要应用embedding的形式对其进行编码(有关embedding的内容可以参考https://www.jianshu.com/p/2a76b7d3126b),编码成一个向量,这个向量就可以输入到RNN中去。当把句子输入完之后,在最后一个词之后,拿到最后一个词的输出,这个输出拥有了这个句子的所有信息,因为这是一个循环神经网络,它的每一个词的状态都会向下传递,一直传到最后的一个状态包含了整个句子的信息。然后再输出一个向量,再通过MLP(全连接层)得到一个分类,比如说是一个二分类,这个句子本身是一个评价,那么这个评价会有好和坏之分,或者是积极的和消极的之分。使用σ函数得到一个概率;如果是多分类,就使用softmax函数。这样就使用了一个RNN或者是LSTM去进行文本分类最基本的模型。

输入embedding

在深度学习领域,我们都使用embedding的形式对词语进行编码,在上图中,我们对每一个词去做一个长度为5的编码,也就是说把每一个词变成一个长度为5的向量,在这个向量中5个值都是一个浮点数,这个embedding都是一个变量,我们在循环神经网络去学习的过程中,这个embedding是要随着梯度下降去调整的,从而可以使得embedding的意思跟词更相关,从而才能得到一个正确的结果。所以在这里embedding是可训练的。经过RNN训练之后再输出全连接层。我们来看一下它的几个重要类型

词频(TF)

TF(Term Frequency,词频)表示词条在文本中出现的频率,这个数字通常会被归一化(一般是词频除以文章总词数),以防止它偏向长的文件(同一词语在长文件里可能会比短文件有更高的词频,而不管该词语重要与否)。它的公式为

其中表示词条在文档中出现的次数,就是表示词条在文档中出现的频率。

逆文件频率(IDF)

表示关键词的普遍程度。如果包含词条i的文档越少,IDF越大,则说明该词条具有很好的类别区分能力。某一特定词语的IDF,可以由总文件数目除以包含该词语之文件的数目,再将得到的商取对数得到。

其中|D|表示所有文档的数量,表示包含词条的文档数量,为什么这里要加1呢?主要是防止包含词条的数量为0从而导致运算出错的现象发生。

词频-逆文件频率(TF-IDF)

某一个特定文件内的高词语频率,以及该词语在整个文件集合中的低文件频率,可以产生出高权重的TF-IDF。因此,TF-IDF倾向于过滤掉常见的词语,保留重要的词语

缺点:

  1. 词频(TF)和逆文件频率(IDF)的统计和计算都直接从语料统计得出,当添加语料的时候,TF和IDF往往需要重新计算,无法增量更新,每次添加语料,需要重新计算词频。
  2. 没有考虑特征词的位置因素对文本的区分度,词条出现在文档的不同位置时,对区分度的贡献大小是不一样的。
  3. 按照传统TF-IDF,往往一些生僻词的IDF(反文档频率)会比较高,因此这些生僻词常会被误认为是文档关键词。

现在我们用代码来实现一下这些概念,先准备语料

if __name__ == "__main__":

    # 语料
    corpus = ['这是 第一个 文档',
              '这是 第二个 文档',
              '这是 最后 一个 文档',
              '现在 没有 文档 了']
    # 词袋
    word_list = []
    for corpu in corpus:
        word_list.append(corpu.split())
    print(word_list)

运行结果

[['这是', '第一个', '文档'], ['这是', '第二个', '文档'], ['这是', '最后', '一个', '文档'], ['现在', '没有', '文档', '了']]

然后我们来统计词频

from collections import Counter

if __name__ == "__main__":

    # 语料
    corpus = ['这是 第一个 文档',
              '这是 第二个 文档',
              '这是 最后 一个 文档',
              '现在 没有 文档 了']
    # 词袋
    word_list = []
    for corpu in corpus:
        word_list.append(corpu.split())
    print(word_list)
    count_list = []
    for words in word_list:
        count = Counter(words)
        count_list.append(count)
    print(count_list)

运行结果

[['这是', '第一个', '文档'], ['这是', '第二个', '文档'], ['这是', '最后', '一个', '文档'], ['现在', '没有', '文档', '了']]
[Counter({'这是': 1, '第一个': 1, '文档': 1}), Counter({'这是': 1, '第二个': 1, '文档': 1}), Counter({'这是': 1, '最后': 1, '一个': 1, '文档': 1}), Counter({'现在': 1, '没有': 1, '文档': 1, '了': 1})]

创建TF、IDF、TF-IDF三个函数

from collections import Counter
import math

if __name__ == "__main__":

    # 语料
    corpus = ['这是 第一个 文档',
              '这是 第二个 文档',
              '这是 最后 一个 文档',
              '现在 没有 文档 了']
    # 词袋
    word_list = []
    for corpu in corpus:
        word_list.append(corpu.split())
    print(word_list)
    count_list = []
    for words in word_list:
        count = Counter(words)
        count_list.append(count)
    print(count_list)

    def tf(word, count):
        # 词频 词语word的数量/所有词语的总数量
        return count[word] / sum(count.values())

    def idf(word, count_list):
        # 逆文件频率 句子数量/包含词语word的句子数量
        # 包含word的句子数量
        n_contain = sum([1 for count in count_list if word in count])
        return math.log(len(count_list) / (1 + n_contain))

    def tf_idf(word, count, count_list):
        # 词频-逆文件频率 tf*idf
        return tf(word, count) * idf(word, count_list)

    for index, count in enumerate(count_list):
        print('第{}个文档的TF IDF的信息'.format(index + 1))
        scores = {word: tf_idf(word, count, count_list) for word in count}
        scored_word = sorted(scores.items(), key=lambda x: x[1], reverse=True)
        for word, score in scored_word:
            print('word:{}, TF IDF:{}'.format(word, round(score, 5)))

运行结果

[['这是', '第一个', '文档'], ['这是', '第二个', '文档'], ['这是', '最后', '一个', '文档'], ['现在', '没有', '文档', '了']]
[Counter({'这是': 1, '第一个': 1, '文档': 1}), Counter({'这是': 1, '第二个': 1, '文档': 1}), Counter({'这是': 1, '最后': 1, '一个': 1, '文档': 1}), Counter({'现在': 1, '没有': 1, '文档': 1, '了': 1})]
第1个文档的TF IDF的信息
word:第一个, TF IDF:0.23105
word:这是, TF IDF:0.0
word:文档, TF IDF:-0.07438
第2个文档的TF IDF的信息
word:第二个, TF IDF:0.23105
word:这是, TF IDF:0.0
word:文档, TF IDF:-0.07438
第3个文档的TF IDF的信息
word:最后, TF IDF:0.17329
word:一个, TF IDF:0.17329
word:这是, TF IDF:0.0
word:文档, TF IDF:-0.05579
第4个文档的TF IDF的信息
word:现在, TF IDF:0.17329
word:没有, TF IDF:0.17329
word:了, TF IDF:0.17329
word:文档, TF IDF:-0.05579

这是手工的实现,现在我们来看一下gsim的实现

from collections import Counter
import math
from gensim import corpora
from gensim import models

if __name__ == "__main__":

    # 语料
    corpus = ['这是 第一个 文档',
              '这是 第二个 文档',
              '这是 最后 一个 文档',
              '现在 没有 文档 了']
    # 词袋
    word_list = []
    for corpu in corpus:
        word_list.append(corpu.split())
    print(word_list)
    count_list = []
    for words in word_list:
        count = Counter(words)
        count_list.append(count)
    print(count_list)

    def tf(word, count):
        # 词频 词语word的数量/所有词语的总数量
        return count[word] / sum(count.values())

    def idf(word, count_list):
        # 逆文件频率 句子数量/包含词语word的句子数量
        # 包含word的句子数量
        n_contain = sum([1 for count in count_list if word in count])
        return math.log(len(count_list) / (1 + n_contain))

    def tf_idf(word, count, count_list):
        # 词频-逆文件频率 tf*idf
        return tf(word, count) * idf(word, count_list)

    # for index, count in enumerate(count_list):
    #     print('第{}个文档的TF IDF的信息'.format(index + 1))
    #     scores = {word: tf_idf(word, count, count_list) for word in count}
    #     scored_word = sorted(scores.items(), key=lambda x: x[1], reverse=True)
    #     for word, score in scored_word:
    #         print('word:{}, TF IDF:{}'.format(word, round(score, 5)))

    # 构建字典
    dic = corpora.Dictionary(word_list)
    # 带元组的二维列表,元组第一个元素是词在字典中的id,第二个元素是在句子中出现的次数
    new_corpora = [dic.doc2bow(words) for words in word_list]
    print(new_corpora)
    print(dic.token2id)
    # 构建一个词频-逆文件频率模型
    tfidf = models.TfidfModel(new_corpora)
    tfidf.save('tfidf.model')
    tfidf = models.TfidfModel.load('tfidf.model')
    tf_idf_vec = []
    for corpu in corpus:
        # 词袋
        doc_bow = dic.doc2bow(corpu.lower().split())
        # 对词袋进行tf-idf编码转化为向量
        vec = tfidf[doc_bow]
        tf_idf_vec.append(vec)
    # 打印所有的词向量
    print(tf_idf_vec)

运行结果

[['这是', '第一个', '文档'], ['这是', '第二个', '文档'], ['这是', '最后', '一个', '文档'], ['现在', '没有', '文档', '了']]
[Counter({'这是': 1, '第一个': 1, '文档': 1}), Counter({'这是': 1, '第二个': 1, '文档': 1}), Counter({'这是': 1, '最后': 1, '一个': 1, '文档': 1}), Counter({'现在': 1, '没有': 1, '文档': 1, '了': 1})]
[[(0, 1), (1, 1), (2, 1)], [(0, 1), (2, 1), (3, 1)], [(0, 1), (2, 1), (4, 1), (5, 1)], [(0, 1), (6, 1), (7, 1), (8, 1)]]
{'文档': 0, '第一个': 1, '这是': 2, '第二个': 3, '一个': 4, '最后': 5, '了': 6, '没有': 7, '现在': 8}
[[(1, 0.9791393740730397), (2, 0.20318977863036336)], [(2, 0.20318977863036336), (3, 0.9791393740730397)], [(2, 0.1451831961481918), (4, 0.699614836733826), (5, 0.699614836733826)], [(6, 0.5773502691896258), (7, 0.5773502691896258), (8, 0.5773502691896258)]]

现在我们来看一下scikit-learn中的实现

from collections import Counter
import math
from gensim import corpora
from gensim import models
from sklearn.feature_extraction.text import TfidfVectorizer

if __name__ == "__main__":

    # 语料
    corpus = ['这是 第一个 文档',
              '这是 第二个 文档',
              '这是 最后 一个 文档',
              '现在 没有 文档 了']
    # 词袋
    word_list = []
    for corpu in corpus:
        word_list.append(corpu.split())
    print(word_list)
    count_list = []
    for words in word_list:
        count = Counter(words)
        count_list.append(count)
    print(count_list)

    def tf(word, count):
        # 词频 词语word的数量/所有词语的总数量
        return count[word] / sum(count.values())

    def idf(word, count_list):
        # 逆文件频率 句子数量/包含词语word的句子数量
        # 包含word的句子数量
        n_contain = sum([1 for count in count_list if word in count])
        return math.log(len(count_list) / (1 + n_contain))

    def tf_idf(word, count, count_list):
        # 词频-逆文件频率 tf*idf
        return tf(word, count) * idf(word, count_list)

    # for index, count in enumerate(count_list):
    #     print('第{}个文档的TF IDF的信息'.format(index + 1))
    #     scores = {word: tf_idf(word, count, count_list) for word in count}
    #     scored_word = sorted(scores.items(), key=lambda x: x[1], reverse=True)
    #     for word, score in scored_word:
    #         print('word:{}, TF IDF:{}'.format(word, round(score, 5)))

    # 构建字典
    # dic = corpora.Dictionary(word_list)
    # # 带元组的二维列表,元组第一个元素是词在字典中的id,第二个元素是在句子中出现的次数
    # new_corpora = [dic.doc2bow(words) for words in word_list]
    # print(new_corpora)
    # print(dic.token2id)
    # # 构建一个词频-逆文件频率模型
    # tfidf = models.TfidfModel(new_corpora)
    # tfidf.save('tfidf.model')
    # tfidf = models.TfidfModel.load('tfidf.model')
    # tf_idf_vec = []
    # for corpu in corpus:
    #     # 词袋
    #     doc_bow = dic.doc2bow(corpu.lower().split())
    #     # 对词袋进行tf-idf编码转化为向量
    #     vec = tfidf[doc_bow]
    #     tf_idf_vec.append(vec)
    # # 打印所有的词向量
    # print(tf_idf_vec)

    tfidf_vec = TfidfVectorizer()
    tf_idf_matrix = tfidf_vec.fit_transform(corpus)
    # 打印语料库所有不重复的词
    print(tfidf_vec.get_feature_names())
    # 打印所有单词对应的id
    print(tfidf_vec.vocabulary_)
    # 打印tf-idf矩阵输出
    print(tf_idf_matrix.toarray())

运行结果

[['这是', '第一个', '文档'], ['这是', '第二个', '文档'], ['这是', '最后', '一个', '文档'], ['现在', '没有', '文档', '了']]
[Counter({'这是': 1, '第一个': 1, '文档': 1}), Counter({'这是': 1, '第二个': 1, '文档': 1}), Counter({'这是': 1, '最后': 1, '一个': 1, '文档': 1}), Counter({'现在': 1, '没有': 1, '文档': 1, '了': 1})]
['一个', '文档', '最后', '没有', '现在', '第一个', '第二个', '这是']
{'这是': 7, '第一个': 5, '文档': 1, '第二个': 6, '最后': 2, '一个': 0, '现在': 4, '没有': 3}
[[0.         0.40264194 0.         0.         0.         0.77157901
  0.         0.49248889]
 [0.         0.40264194 0.         0.         0.         0.
  0.77157901 0.49248889]
 [0.61087812 0.31878155 0.61087812 0.         0.         0.
  0.         0.38991559]
 [0.         0.34618161 0.         0.66338461 0.66338461 0.
  0.         0.        ]]

因为tf-idf有其缺点,我们理想的词表征方式

对比于独热one-hot编码,一般对词的编码是这个样子的

我们把One-Hot中的0的位置也利用起来,并且用浮点数来表示词特性,这样我们就可以用固定的,较小的维度来表达海量的信息。对于50W的单词,如果使用One-Hot编码,我们就需要使用50W个列,这样非常浪费空间,不利于计算。体现不出单词间的关系

而使用词向量,对于50W个词,我们可能只需要定义出300列,长度远小于One-Hot的长度,向量夹角代表相似度。比如上图中的味道和口味它们的相似度就非常高,所以它们在一定维度上的编码相同,这样就不必把它们作为完全无关的两个词独立编码。还可以增量添加新词。对于One-Hot的编码非常简单,那么对于词向量中的这些数值是怎么来的呢?其实它们都是通过机器学习来获取的。学习的方法有很多种,下面是三种比较常用的

现在我们来看一个embedding的一种word2vec的代码示例,数据集是今日头条的一个标题,我们先来看看里面的部分内容和长度。

数据集下载地址:https://storage.googleapis.com/cluebenchmark/tasks/tnews_public.zip

INPUT_PATH = "/Users/admin/Downloads/tnews_public/"
import pandas as pd
import json
# 中文分词器
import jieba
from gensim.models.word2vec import LineSentence
from gensim.models import  word2vec
import gensim
from gensim.models import FastText
import logging

if __name__ == "__main__":

    def get_sentence(data_path):
        # 数据预处理
        with open(data_path, 'r', encoding='utf-8') as f:
            lines = f.readlines()
            sentence = []
            for line in lines:
                linejson = json.loads(line.strip())
                sentence.append(linejson['sentence'])
        return sentence

    train_sentence = get_sentence(INPUT_PATH + "train.json")
    test_sentence = get_sentence(INPUT_PATH + "test.json")
    dev_sentence = get_sentence(INPUT_PATH + "dev.json")
    train_data = train_sentence + test_sentence + dev_sentence
    print(train_data[:10])
    print(len(train_data))
    # 进行分词处理
    train_data = [list(jieba.cut(sentence)) for sentence in train_data]
    print(train_data[:10])
    print(len(train_data))

运行结果

Building prefix dict from the default dictionary ...
Loading model from cache /var/folders/zw/m9qmjjcn1_1021cxrgylwrlc0000gn/T/jieba.cache
['上课时学生手机响个不停,老师一怒之下把手机摔了,家长拿发票让老师赔,大家怎么看待这种事?', '商赢环球股份有限公司关于延期回复上海证券交易所对公司2017年年度报告的事后审核问询函的公告', '通过中介公司买了二手房,首付都付了,现在卖家不想卖了。怎么处理?', '2018年去俄罗斯看世界杯得花多少钱?', '剃须刀的个性革新,雷明登天猫定制版新品首发', '再次证明了“无敌是多么寂寞”——逆天的中国乒乓球队!', '三农盾SACC-全球首个推出:互联网+区块链+农产品的电商平台', '重做or新英雄?其实重做对暴雪来说同样重要', '如何在商业活动中不受人欺骗?', '87版红楼梦最温柔的四个丫鬟,娶谁都是一生的福气']
73360
Loading model cost 0.582 seconds.
Prefix dict has been built successfully.
[['上课时', '学生', '手机', '响个', '不停', ',', '老师', '一怒之下', '把', '手机', '摔', '了', ',', '家长', '拿', '发票', '让', '老师', '赔', ',', '大家', '怎么', '看待', '这种', '事', '?'], ['商赢', '环球', '股份', '有限公司', '关于', '延期', '回复', '上海证券交易所', '对', '公司', '2017', '年', '年度报告', '的', '事后', '审核', '问询', '函', '的', '公告'], ['通过', '中介', '公司', '买', '了', '二手房', ',', '首付', '都', '付', '了', ',', '现在', '卖家', '不想', '卖', '了', '。', '怎么', '处理', '?'], ['2018', '年', '去', '俄罗斯', '看', '世界杯', '得花', '多少', '钱', '?'], ['剃须刀', '的', '个性', '革新', ',', '雷明登', '天猫', '定制', '版', '新品', '首发'], ['再次', '证明', '了', '“', '无敌', '是', '多么', '寂寞', '”', '—', '—', '逆天', '的', '中国乒乓球队', '!'], ['三农', '盾', 'SACC', '-', '全球', '首个', '推出', ':', '互联网', '+', '区块', '链', '+', '农产品', '的', '电商', '平台'], ['重做', 'or', '新', '英雄', '?', '其实', '重做', '对', '暴雪', '来说', '同样', '重要'], ['如何', '在', '商业活动', '中不受', '人', '欺骗', '?'], ['87', '版', '红楼梦', '最', '温柔', '的', '四个', '丫鬟', ',', '娶', '谁', '都', '是', '一生', '的', '福气']]
73360

通过结果,我们看到,这里面有7W条句子。我们先使用FastText来进行训练

INPUT_PATH = "/Users/admin/Downloads/tnews_public/"
import pandas as pd
import json
# 中文分词器
import jieba
from gensim.models.word2vec import LineSentence
from gensim.models import  word2vec
import gensim
from gensim.models import FastText
import logging

if __name__ == "__main__":

    def get_sentence(data_path):
        # 数据预处理
        with open(data_path, 'r', encoding='utf-8') as f:
            lines = f.readlines()
            sentence = []
            for line in lines:
                linejson = json.loads(line.strip())
                sentence.append(linejson['sentence'])
        return sentence

    train_sentence = get_sentence(INPUT_PATH + "train.json")
    test_sentence = get_sentence(INPUT_PATH + "test.json")
    dev_sentence = get_sentence(INPUT_PATH + "dev.json")
    train_data = train_sentence + test_sentence + dev_sentence
    # print(train_data[:10])
    # print(len(train_data))
    # 进行分词处理
    train_data = [list(jieba.cut(sentence)) for sentence in train_data]
    # print(train_data[:10])
    # print(len(train_data))
    logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', level=logging.INFO)
    # 使用FastText进行训练,词向量维度4,在句子中出现1次就使用,迭代10次
    model = FastText(train_data, vector_size=4, window=3, min_count=1, epochs=10)

运行结果

2021-10-16 18:51:52,524:INFO:collecting all words and their counts
2021-10-16 18:51:52,524:WARNING:Each 'sentences' item should be a list of words (usually unicode strings). First item here is instead plain <class 'str'>.
2021-10-16 18:51:52,524:INFO:PROGRESS: at sentence #0, processed 0 words, keeping 0 word types
2021-10-16 18:51:52,560:INFO:PROGRESS: at sentence #10000, processed 221530 words, keeping 3719 word types
2021-10-16 18:51:52,593:INFO:PROGRESS: at sentence #20000, processed 443517 words, keeping 4183 word types
2021-10-16 18:51:52,621:INFO:PROGRESS: at sentence #30000, processed 664099 words, keeping 4432 word types
2021-10-16 18:51:52,649:INFO:PROGRESS: at sentence #40000, processed 884499 words, keeping 4635 word types
2021-10-16 18:51:52,677:INFO:PROGRESS: at sentence #50000, processed 1105912 words, keeping 4787 word types
2021-10-16 18:51:52,706:INFO:PROGRESS: at sentence #60000, processed 1331516 words, keeping 4962 word types
2021-10-16 18:51:52,735:INFO:PROGRESS: at sentence #70000, processed 1556362 words, keeping 5054 word types
2021-10-16 18:51:52,747:INFO:collected 5082 word types from a corpus of 1631354 raw words and 73360 sentences
2021-10-16 18:51:52,747:INFO:Creating a fresh vocabulary
2021-10-16 18:51:52,773:INFO:FastText lifecycle event {'msg': 'effective_min_count=1 retains 5082 unique words (100.0%% of original 5082, drops 0)', 'datetime': '2021-10-16T18:51:52.761129', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'prepare_vocab'}
2021-10-16 18:51:52,773:INFO:FastText lifecycle event {'msg': 'effective_min_count=1 leaves 1631354 word corpus (100.0%% of original 1631354, drops 0)', 'datetime': '2021-10-16T18:51:52.773965', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'prepare_vocab'}
2021-10-16 18:51:52,800:INFO:deleting the raw counts dictionary of 5082 items
2021-10-16 18:51:52,801:INFO:sample=0.001 downsamples 53 most-common words
2021-10-16 18:51:52,801:INFO:FastText lifecycle event {'msg': 'downsampling leaves estimated 1423599.5793498545 word corpus (87.3%% of prior 1631354)', 'datetime': '2021-10-16T18:51:52.801180', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'prepare_vocab'}
2021-10-16 18:51:52,849:INFO:estimated required memory for 5082 words, 2000000 buckets and 4 dimensions: 35232216 bytes
2021-10-16 18:51:52,849:INFO:resetting layer weights
2021-10-16 18:51:52,926:INFO:FastText lifecycle event {'update': False, 'trim_rule': 'None', 'datetime': '2021-10-16T18:51:52.926935', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'build_vocab'}
2021-10-16 18:51:52,927:INFO:FastText lifecycle event {'msg': 'training model with 3 workers on 5082 vocabulary and 4 features, using sg=0 hs=0 sample=0.001 negative=5 window=3 shrink_windows=True', 'datetime': '2021-10-16T18:51:52.927106', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'train'}
2021-10-16 18:51:53,564:INFO:worker thread finished; awaiting finish of 2 more threads
2021-10-16 18:51:53,564:INFO:worker thread finished; awaiting finish of 1 more threads
2021-10-16 18:51:53,567:INFO:worker thread finished; awaiting finish of 0 more threads
2021-10-16 18:51:53,568:INFO:EPOCH - 1 : training on 1631354 raw words (1423053 effective words) took 0.6s, 2231239 effective words/s
2021-10-16 18:51:54,223:INFO:worker thread finished; awaiting finish of 2 more threads
2021-10-16 18:51:54,223:INFO:worker thread finished; awaiting finish of 1 more threads
2021-10-16 18:51:54,227:INFO:worker thread finished; awaiting finish of 0 more threads
2021-10-16 18:51:54,227:INFO:EPOCH - 2 : training on 1631354 raw words (1423763 effective words) took 0.7s, 2166138 effective words/s
2021-10-16 18:51:54,846:INFO:worker thread finished; awaiting finish of 2 more threads
2021-10-16 18:51:54,846:INFO:worker thread finished; awaiting finish of 1 more threads
2021-10-16 18:51:54,850:INFO:worker thread finished; awaiting finish of 0 more threads
2021-10-16 18:51:54,850:INFO:EPOCH - 3 : training on 1631354 raw words (1423395 effective words) took 0.6s, 2292254 effective words/s
2021-10-16 18:51:55,472:INFO:worker thread finished; awaiting finish of 2 more threads
2021-10-16 18:51:55,473:INFO:worker thread finished; awaiting finish of 1 more threads
2021-10-16 18:51:55,475:INFO:worker thread finished; awaiting finish of 0 more threads
2021-10-16 18:51:55,475:INFO:EPOCH - 4 : training on 1631354 raw words (1423751 effective words) took 0.6s, 2286881 effective words/s
2021-10-16 18:51:56,091:INFO:worker thread finished; awaiting finish of 2 more threads
2021-10-16 18:51:56,092:INFO:worker thread finished; awaiting finish of 1 more threads
2021-10-16 18:51:56,096:INFO:worker thread finished; awaiting finish of 0 more threads
2021-10-16 18:51:56,096:INFO:EPOCH - 5 : training on 1631354 raw words (1423747 effective words) took 0.6s, 2301089 effective words/s
2021-10-16 18:51:56,711:INFO:worker thread finished; awaiting finish of 2 more threads
2021-10-16 18:51:56,711:INFO:worker thread finished; awaiting finish of 1 more threads
2021-10-16 18:51:56,715:INFO:worker thread finished; awaiting finish of 0 more threads
2021-10-16 18:51:56,715:INFO:EPOCH - 6 : training on 1631354 raw words (1423677 effective words) took 0.6s, 2306322 effective words/s
2021-10-16 18:51:57,342:INFO:worker thread finished; awaiting finish of 2 more threads
2021-10-16 18:51:57,343:INFO:worker thread finished; awaiting finish of 1 more threads
2021-10-16 18:51:57,346:INFO:worker thread finished; awaiting finish of 0 more threads
2021-10-16 18:51:57,346:INFO:EPOCH - 7 : training on 1631354 raw words (1423577 effective words) took 0.6s, 2263030 effective words/s
2021-10-16 18:51:57,966:INFO:worker thread finished; awaiting finish of 2 more threads
2021-10-16 18:51:57,966:INFO:worker thread finished; awaiting finish of 1 more threads
2021-10-16 18:51:57,970:INFO:worker thread finished; awaiting finish of 0 more threads
2021-10-16 18:51:57,970:INFO:EPOCH - 8 : training on 1631354 raw words (1423861 effective words) took 0.6s, 2291245 effective words/s
2021-10-16 18:51:58,585:INFO:worker thread finished; awaiting finish of 2 more threads
2021-10-16 18:51:58,586:INFO:worker thread finished; awaiting finish of 1 more threads
2021-10-16 18:51:58,590:INFO:worker thread finished; awaiting finish of 0 more threads
2021-10-16 18:51:58,590:INFO:EPOCH - 9 : training on 1631354 raw words (1423706 effective words) took 0.6s, 2302508 effective words/s
2021-10-16 18:51:59,222:INFO:worker thread finished; awaiting finish of 2 more threads
2021-10-16 18:51:59,223:INFO:worker thread finished; awaiting finish of 1 more threads
2021-10-16 18:51:59,227:INFO:worker thread finished; awaiting finish of 0 more threads
2021-10-16 18:51:59,227:INFO:EPOCH - 10 : training on 1631354 raw words (1423476 effective words) took 0.6s, 2245946 effective words/s
2021-10-16 18:51:59,227:INFO:FastText lifecycle event {'msg': 'training on 16313540 raw words (14236006 effective words) took 6.3s, 2259662 effective words/s', 'datetime': '2021-10-16T18:51:59.227268', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'train'}
2021-10-16 18:51:59,252:INFO:FastText lifecycle event {'params': 'FastText(vocab=5082, vector_size=4, alpha=0.025)', 'datetime': '2021-10-16T18:51:59.252244', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'created'}

现在我们改用word2vec来进行训练,word2vec里面有两种训练方法,一种是CBOW,一种是skip-gram

INPUT_PATH = "/Users/admin/Downloads/tnews_public/"
import pandas as pd
import json
# 中文分词器
import jieba
from gensim.models.word2vec import LineSentence
from gensim.models import  word2vec
import gensim
from gensim.models import FastText
import logging

if __name__ == "__main__":

    def get_sentence(data_path):
        # 数据预处理
        with open(data_path, 'r', encoding='utf-8') as f:
            lines = f.readlines()
            sentence = []
            for line in lines:
                linejson = json.loads(line.strip())
                sentence.append(linejson['sentence'])
        return sentence

    train_sentence = get_sentence(INPUT_PATH + "train.json")
    test_sentence = get_sentence(INPUT_PATH + "test.json")
    dev_sentence = get_sentence(INPUT_PATH + "dev.json")
    train_data = train_sentence + test_sentence + dev_sentence
    # print(train_data[:10])
    # print(len(train_data))
    # 进行分词处理
    train_data = [list(jieba.cut(sentence)) for sentence in train_data]
    # print(train_data[:10])
    # print(len(train_data))
    logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', level=logging.INFO)
    # 使用FastText进行训练,词向量维度4,在句子中出现1次就使用,迭代10次
    # model = FastText(train_data, vector_size=4, window=3, min_count=1, epochs=10)
    # 使用skip-gram来进行训练,词向量维度200,sg=1代表使用skip-gram,使用所有的cpu核,在句子中出现8次才使用,迭代1次
    model = word2vec.Word2Vec(train_data, vector_size=200, sg=1, workers=-1, min_count=8, epochs=1)
    # 打印跟"金额"相似度最高的10个词
    print(model.wv.most_similar(['金融'], topn=10))
    # 保存模型
    filename = "word2vec.model"
    model.save(filename)
    # 载入模型
    model = word2vec.Word2Vec.load(filename)
    # 打印词向量
    print(model.wv['金融'])
    # 查看词向量维度
    print(model.wv['金融'].shape)

运行结果

Building prefix dict from the default dictionary ...
Loading model from cache /var/folders/zw/m9qmjjcn1_1021cxrgylwrlc0000gn/T/jieba.cache
Loading model cost 0.533 seconds.
Prefix dict has been built successfully.
2021-10-17 06:26:05,604:INFO:collecting all words and their counts
2021-10-17 06:26:05,604:INFO:PROGRESS: at sentence #0, processed 0 words, keeping 0 word types
2021-10-17 06:26:05,623:INFO:PROGRESS: at sentence #10000, processed 129141 words, keeping 25698 word types
2021-10-17 06:26:05,642:INFO:PROGRESS: at sentence #20000, processed 258779 words, keeping 39123 word types
2021-10-17 06:26:05,660:INFO:PROGRESS: at sentence #30000, processed 387255 words, keeping 49324 word types
2021-10-17 06:26:05,678:INFO:PROGRESS: at sentence #40000, processed 515598 words, keeping 57864 word types
2021-10-17 06:26:05,697:INFO:PROGRESS: at sentence #50000, processed 644895 words, keeping 65382 word types
2021-10-17 06:26:05,717:INFO:PROGRESS: at sentence #60000, processed 775338 words, keeping 74072 word types
2021-10-17 06:26:05,736:INFO:PROGRESS: at sentence #70000, processed 906119 words, keeping 80406 word types
2021-10-17 06:26:05,742:INFO:collected 82315 word types from a corpus of 949783 raw words and 73360 sentences
2021-10-17 06:26:05,742:INFO:Creating a fresh vocabulary
2021-10-17 06:26:05,812:INFO:Word2Vec lifecycle event {'msg': 'effective_min_count=8 retains 11844 unique words (14.388629046953776%% of original 82315, drops 70471)', 'datetime': '2021-10-17T06:26:05.795745', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'prepare_vocab'}
2021-10-17 06:26:05,813:INFO:Word2Vec lifecycle event {'msg': 'effective_min_count=8 leaves 814875 word corpus (85.79591338231997%% of original 949783, drops 134908)', 'datetime': '2021-10-17T06:26:05.813070', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'prepare_vocab'}
2021-10-17 06:26:05,887:INFO:deleting the raw counts dictionary of 82315 items
2021-10-17 06:26:05,904:INFO:sample=0.001 downsamples 32 most-common words
2021-10-17 06:26:05,904:INFO:Word2Vec lifecycle event {'msg': 'downsampling leaves estimated 659780.0540415794 word corpus (81.0%% of prior 814875)', 'datetime': '2021-10-17T06:26:05.904577', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'prepare_vocab'}
2021-10-17 06:26:06,006:INFO:estimated required memory for 11844 words and 200 dimensions: 24872400 bytes
2021-10-17 06:26:06,006:INFO:resetting layer weights
2021-10-17 06:26:06,016:INFO:Word2Vec lifecycle event {'update': False, 'trim_rule': 'None', 'datetime': '2021-10-17T06:26:06.016634', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'build_vocab'}
2021-10-17 06:26:06,016:INFO:Word2Vec lifecycle event {'msg': 'training model with -1 workers on 11844 vocabulary and 200 features, using sg=1 hs=0 sample=0.001 negative=5 window=5 shrink_windows=True', 'datetime': '2021-10-17T06:26:06.016757', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'train'}
2021-10-17 06:26:06,022:INFO:EPOCH - 1 : training on 0 raw words (0 effective words) took 0.0s, 0 effective words/s
2021-10-17 06:26:06,022:WARNING:EPOCH - 1 : supplied example count (0) did not equal expected count (73360)
2021-10-17 06:26:06,022:WARNING:EPOCH - 1 : supplied raw word count (0) did not equal expected count (949783)
2021-10-17 06:26:06,022:INFO:Word2Vec lifecycle event {'msg': 'training on 0 raw words (0 effective words) took 0.0s, 0 effective words/s', 'datetime': '2021-10-17T06:26:06.022752', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'train'}
2021-10-17 06:26:06,022:INFO:Word2Vec lifecycle event {'params': 'Word2Vec(vocab=11844, vector_size=200, alpha=0.025)', 'datetime': '2021-10-17T06:26:06.022812', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'created'}
2021-10-17 06:26:06,080:INFO:Word2Vec lifecycle event {'fname_or_handle': 'word2vec.model', 'separately': 'None', 'sep_limit': 10485760, 'ignore': frozenset(), 'datetime': '2021-10-17T06:26:06.080942', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'saving'}
2021-10-17 06:26:06,081:INFO:not storing attribute cum_table
[('自然', 0.27589312195777893), ('银行', 0.26579564809799194), ('轿车', 0.25978755950927734), ('程维', 0.24209074676036835), ('先机', 0.22654813528060913), ('金靴', 0.22519522905349731), ('魅族', 0.22500421106815338), ('人口', 0.21995916962623596), ('揭晓', 0.21871201694011688), ('阻挡', 0.21811680495738983)]
2021-10-17 06:26:06,110:INFO:saved word2vec.model
2021-10-17 06:26:06,111:INFO:loading Word2Vec object from word2vec.model
2021-10-17 06:26:06,134:INFO:loading wv recursively from word2vec.model.wv.* with mmap=None
2021-10-17 06:26:06,134:INFO:setting ignored attribute cum_table to None
2021-10-17 06:26:06,235:INFO:Word2Vec lifecycle event {'fname': 'word2vec.model', 'datetime': '2021-10-17T06:26:06.235209', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'loaded'}
[-4.4319485e-03  2.1632279e-03  4.7748806e-03 -3.1582296e-03
 -2.9498243e-03  3.5983205e-04  2.5840008e-03  2.1561242e-03
 -9.7617391e-04  2.0310271e-03 -4.8526763e-03  3.2474555e-03
 -1.7625368e-03 -1.0060632e-03  2.7969647e-03 -3.9653573e-03
 -3.8190652e-03  3.8494754e-03  3.2184147e-03  4.8477650e-05
  4.1245567e-03 -3.6790704e-03 -9.6449854e-05 -3.0720173e-03
  1.6108870e-03 -1.9138455e-04 -4.3033087e-03  1.8814743e-03
 -2.7090097e-03  3.5731983e-03 -3.9581931e-03  1.2442386e-03
 -2.5676822e-03 -4.9472763e-03  1.7846525e-03  2.8292621e-03
  4.3160357e-03  3.5895037e-03  4.6166587e-03  3.0893600e-03
 -3.5259353e-03 -3.2670379e-03  2.5701523e-04  3.2474996e-05
 -2.7913474e-03  4.8444164e-03 -1.5012753e-03  2.6732148e-03
 -4.6307836e-03 -2.1897864e-03  9.2224119e-04 -1.0537410e-03
  8.1159949e-04 -5.9871434e-04  2.1427954e-03 -3.4884000e-03
 -2.7848089e-03  4.3353164e-03 -2.0470952e-03  1.7237687e-03
 -4.3058335e-03  4.1675949e-03  1.9981693e-03  4.7160401e-03
  3.7633120e-03  4.8933555e-03 -1.9366336e-03  2.6090061e-03
  2.9320074e-03  4.0039350e-03 -1.5398180e-03 -3.6864805e-03
  2.5416792e-03 -1.3249576e-03  4.8639430e-03  1.7925728e-03
  4.0726112e-03  8.0596568e-04  2.0962679e-03 -3.5125852e-04
 -2.6882757e-03 -3.8547397e-03 -5.4519891e-04 -3.3301831e-04
 -2.1318339e-03 -1.5638077e-03  6.4748048e-04 -1.6983902e-03
  4.3934844e-03 -1.7959583e-03 -1.9506562e-03 -1.5557646e-04
  4.3380046e-03 -2.7082872e-03 -7.6703192e-04 -2.8249060e-03
  5.3128717e-04 -3.3655537e-03 -4.7280965e-03  3.5765707e-03
 -1.8067360e-04 -2.5400997e-03  1.0049760e-03  2.5802182e-03
 -1.4854348e-03  2.7088821e-03  1.9067407e-04  3.5904788e-03
 -3.6461318e-03  4.7876681e-03 -2.1087741e-03  3.4030222e-03
 -4.2054774e-03 -4.7188746e-03 -5.7117938e-04 -2.6762497e-03
 -1.6590178e-03  1.6239440e-03  3.9525614e-03  1.3983703e-03
 -2.2414564e-03  1.9410467e-03 -2.2762334e-03  2.8848338e-03
  1.1154747e-03 -2.2609890e-03  9.9922891e-04  2.1851598e-03
  4.5638070e-03 -3.2051015e-03 -4.9910913e-03  1.6121912e-03
 -2.5797831e-03 -1.9071889e-03 -1.4998949e-03  4.1937400e-03
 -1.9900489e-03 -4.1413605e-03  1.3192416e-04 -3.9271927e-03
  8.4543467e-04 -2.0670153e-03  4.1126441e-03 -9.7051263e-04
  4.2811143e-03 -4.8036827e-03 -1.4866054e-03  3.9099394e-03
 -2.1643948e-03 -3.7491096e-03  2.5005592e-03 -1.0115481e-03
 -1.3070143e-03 -3.8094164e-04 -3.8443147e-03 -2.8949953e-03
 -3.4612429e-03 -1.0860467e-03  1.6047299e-03  3.1654453e-03
  2.5345969e-03 -1.4960766e-04  2.5851489e-03  9.7772479e-04
 -3.1572438e-03  4.9629258e-03  2.3661412e-03 -3.0144823e-03
  2.7645873e-03  2.8304840e-03 -2.5494362e-03  2.6516258e-03
  2.8708100e-04 -1.6423154e-03  4.5710183e-03 -3.3618093e-03
 -2.4451709e-03  2.3778332e-03  3.1822931e-03 -1.8060458e-03
 -1.7713356e-03 -4.0959669e-03 -1.6474903e-03  1.7896449e-03
  4.7711432e-03 -3.7612724e-03  1.1329651e-05 -3.8032758e-03
 -3.7143265e-03  4.5684646e-03  1.6630745e-03  1.7293692e-03
  3.5204494e-03  2.3232365e-03  4.3577277e-03 -4.6868767e-03
  4.0235603e-03  2.9009127e-03 -8.2527637e-04  3.1227733e-03]
(200,)

当然我们也可以用CBOW来处理

INPUT_PATH = "/Users/admin/Downloads/tnews_public/"
import pandas as pd
import json
# 中文分词器
import jieba
from gensim.models.word2vec import LineSentence
from gensim.models import  word2vec
import gensim
from gensim.models import FastText
import logging

if __name__ == "__main__":

    def get_sentence(data_path):
        # 数据预处理
        with open(data_path, 'r', encoding='utf-8') as f:
            lines = f.readlines()
            sentence = []
            for line in lines:
                linejson = json.loads(line.strip())
                sentence.append(linejson['sentence'])
        return sentence

    train_sentence = get_sentence(INPUT_PATH + "train.json")
    test_sentence = get_sentence(INPUT_PATH + "test.json")
    dev_sentence = get_sentence(INPUT_PATH + "dev.json")
    train_data = train_sentence + test_sentence + dev_sentence
    # print(train_data[:10])
    # print(len(train_data))
    # 进行分词处理
    train_data = [list(jieba.cut(sentence)) for sentence in train_data]
    # print(train_data[:10])
    # print(len(train_data))
    logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', level=logging.INFO)
    # 使用FastText进行训练,词向量维度4,在句子中出现1次就使用,迭代10次
    # model = FastText(train_data, vector_size=4, window=3, min_count=1, epochs=10)
    # 使用skip-gram来进行训练,词向量维度200,sg=1代表使用skip-gram,使用所有的cpu核,在句子中出现8次才使用,迭代1次
    # model = word2vec.Word2Vec(train_data, vector_size=200, sg=1, workers=-1, min_count=8, epochs=1)
    # # 打印跟"金额"相似度最高的10个词
    # print(model.wv.most_similar(['金融'], topn=10))
    # # 保存模型
    # filename = "word2vec.model"
    # model.save(filename)
    # # 载入模型
    # model = word2vec.Word2Vec.load(filename)
    # # 打印词向量
    # print(model.wv['金融'])
    # # 查看词向量维度
    # print(model.wv['金融'].shape)

    # 使用CBOW来进行训练,词向量维度200,sg=0代表使用CBOW,使用所有的cpu核,在句子中出现8次才使用,迭代1次
    model = word2vec.Word2Vec(train_data, vector_size=200, sg=0, workers=-1, min_count=8, epochs=1)
    # 打印跟"金额"相似度最高的10个词
    print(model.wv.most_similar(['金融'], topn=10))
    # 保存模型
    filename = "word2vec.model"
    model.save(filename)
    # 载入模型
    model = word2vec.Word2Vec.load(filename)
    # 打印词向量
    print(model.wv['金融'])
    # 查看词向量维度
    print(model.wv['金融'].shape)

运行结果

Building prefix dict from the default dictionary ...
Loading model from cache /var/folders/zw/m9qmjjcn1_1021cxrgylwrlc0000gn/T/jieba.cache
Loading model cost 0.569 seconds.
Prefix dict has been built successfully.
2021-10-17 06:32:13,670:INFO:collecting all words and their counts
2021-10-17 06:32:13,670:INFO:PROGRESS: at sentence #0, processed 0 words, keeping 0 word types
2021-10-17 06:32:13,688:INFO:PROGRESS: at sentence #10000, processed 129141 words, keeping 25698 word types
2021-10-17 06:32:13,707:INFO:PROGRESS: at sentence #20000, processed 258779 words, keeping 39123 word types
2021-10-17 06:32:13,727:INFO:PROGRESS: at sentence #30000, processed 387255 words, keeping 49324 word types
2021-10-17 06:32:13,745:INFO:PROGRESS: at sentence #40000, processed 515598 words, keeping 57864 word types
2021-10-17 06:32:13,764:INFO:PROGRESS: at sentence #50000, processed 644895 words, keeping 65382 word types
2021-10-17 06:32:13,783:INFO:PROGRESS: at sentence #60000, processed 775338 words, keeping 74072 word types
2021-10-17 06:32:13,802:INFO:PROGRESS: at sentence #70000, processed 906119 words, keeping 80406 word types
2021-10-17 06:32:13,809:INFO:collected 82315 word types from a corpus of 949783 raw words and 73360 sentences
2021-10-17 06:32:13,809:INFO:Creating a fresh vocabulary
2021-10-17 06:32:13,875:INFO:Word2Vec lifecycle event {'msg': 'effective_min_count=8 retains 11844 unique words (14.388629046953776%% of original 82315, drops 70471)', 'datetime': '2021-10-17T06:32:13.863082', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'prepare_vocab'}
2021-10-17 06:32:13,875:INFO:Word2Vec lifecycle event {'msg': 'effective_min_count=8 leaves 814875 word corpus (85.79591338231997%% of original 949783, drops 134908)', 'datetime': '2021-10-17T06:32:13.875726', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'prepare_vocab'}
2021-10-17 06:32:13,947:INFO:deleting the raw counts dictionary of 82315 items
2021-10-17 06:32:13,963:INFO:sample=0.001 downsamples 32 most-common words
2021-10-17 06:32:13,963:INFO:Word2Vec lifecycle event {'msg': 'downsampling leaves estimated 659780.0540415794 word corpus (81.0%% of prior 814875)', 'datetime': '2021-10-17T06:32:13.963418', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'prepare_vocab'}
2021-10-17 06:32:14,070:INFO:estimated required memory for 11844 words and 200 dimensions: 24872400 bytes
2021-10-17 06:32:14,070:INFO:resetting layer weights
2021-10-17 06:32:14,081:INFO:Word2Vec lifecycle event {'update': False, 'trim_rule': 'None', 'datetime': '2021-10-17T06:32:14.080997', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'build_vocab'}
2021-10-17 06:32:14,081:INFO:Word2Vec lifecycle event {'msg': 'training model with -1 workers on 11844 vocabulary and 200 features, using sg=0 hs=0 sample=0.001 negative=5 window=5 shrink_windows=True', 'datetime': '2021-10-17T06:32:14.081108', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'train'}
2021-10-17 06:32:14,086:INFO:EPOCH - 1 : training on 0 raw words (0 effective words) took 0.0s, 0 effective words/s
2021-10-17 06:32:14,086:WARNING:EPOCH - 1 : supplied example count (0) did not equal expected count (73360)
2021-10-17 06:32:14,086:WARNING:EPOCH - 1 : supplied raw word count (0) did not equal expected count (949783)
2021-10-17 06:32:14,087:INFO:Word2Vec lifecycle event {'msg': 'training on 0 raw words (0 effective words) took 0.0s, 0 effective words/s', 'datetime': '2021-10-17T06:32:14.087024', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'train'}
2021-10-17 06:32:14,087:INFO:Word2Vec lifecycle event {'params': 'Word2Vec(vocab=11844, vector_size=200, alpha=0.025)', 'datetime': '2021-10-17T06:32:14.087137', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'created'}
[('自然', 0.27589312195777893), ('银行', 0.26579564809799194), ('轿车', 0.25978755950927734), ('程维', 0.24209074676036835), ('先机', 0.22654813528060913), ('金靴', 0.22519522905349731), ('魅族', 0.22500421106815338), ('人口', 0.21995916962623596), ('揭晓', 0.21871201694011688), ('阻挡', 0.21811680495738983)]
2021-10-17 06:32:14,144:INFO:Word2Vec lifecycle event {'fname_or_handle': 'word2vec.model', 'separately': 'None', 'sep_limit': 10485760, 'ignore': frozenset(), 'datetime': '2021-10-17T06:32:14.144888', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'saving'}
2021-10-17 06:32:14,145:INFO:not storing attribute cum_table
2021-10-17 06:32:14,172:INFO:saved word2vec.model
2021-10-17 06:32:14,172:INFO:loading Word2Vec object from word2vec.model
2021-10-17 06:32:14,195:INFO:loading wv recursively from word2vec.model.wv.* with mmap=None
2021-10-17 06:32:14,195:INFO:setting ignored attribute cum_table to None
2021-10-17 06:32:14,302:INFO:Word2Vec lifecycle event {'fname': 'word2vec.model', 'datetime': '2021-10-17T06:32:14.302335', 'gensim': '4.1.2', 'python': '3.7.6 (default, Jan  8 2020, 13:42:34) \n[Clang 4.0.1 (tags/RELEASE_401/final)]', 'platform': 'Darwin-18.7.0-x86_64-i386-64bit', 'event': 'loaded'}
[-4.4319485e-03  2.1632279e-03  4.7748806e-03 -3.1582296e-03
 -2.9498243e-03  3.5983205e-04  2.5840008e-03  2.1561242e-03
 -9.7617391e-04  2.0310271e-03 -4.8526763e-03  3.2474555e-03
 -1.7625368e-03 -1.0060632e-03  2.7969647e-03 -3.9653573e-03
 -3.8190652e-03  3.8494754e-03  3.2184147e-03  4.8477650e-05
  4.1245567e-03 -3.6790704e-03 -9.6449854e-05 -3.0720173e-03
  1.6108870e-03 -1.9138455e-04 -4.3033087e-03  1.8814743e-03
 -2.7090097e-03  3.5731983e-03 -3.9581931e-03  1.2442386e-03
 -2.5676822e-03 -4.9472763e-03  1.7846525e-03  2.8292621e-03
  4.3160357e-03  3.5895037e-03  4.6166587e-03  3.0893600e-03
 -3.5259353e-03 -3.2670379e-03  2.5701523e-04  3.2474996e-05
 -2.7913474e-03  4.8444164e-03 -1.5012753e-03  2.6732148e-03
 -4.6307836e-03 -2.1897864e-03  9.2224119e-04 -1.0537410e-03
  8.1159949e-04 -5.9871434e-04  2.1427954e-03 -3.4884000e-03
 -2.7848089e-03  4.3353164e-03 -2.0470952e-03  1.7237687e-03
 -4.3058335e-03  4.1675949e-03  1.9981693e-03  4.7160401e-03
  3.7633120e-03  4.8933555e-03 -1.9366336e-03  2.6090061e-03
  2.9320074e-03  4.0039350e-03 -1.5398180e-03 -3.6864805e-03
  2.5416792e-03 -1.3249576e-03  4.8639430e-03  1.7925728e-03
  4.0726112e-03  8.0596568e-04  2.0962679e-03 -3.5125852e-04
 -2.6882757e-03 -3.8547397e-03 -5.4519891e-04 -3.3301831e-04
 -2.1318339e-03 -1.5638077e-03  6.4748048e-04 -1.6983902e-03
  4.3934844e-03 -1.7959583e-03 -1.9506562e-03 -1.5557646e-04
  4.3380046e-03 -2.7082872e-03 -7.6703192e-04 -2.8249060e-03
  5.3128717e-04 -3.3655537e-03 -4.7280965e-03  3.5765707e-03
 -1.8067360e-04 -2.5400997e-03  1.0049760e-03  2.5802182e-03
 -1.4854348e-03  2.7088821e-03  1.9067407e-04  3.5904788e-03
 -3.6461318e-03  4.7876681e-03 -2.1087741e-03  3.4030222e-03
 -4.2054774e-03 -4.7188746e-03 -5.7117938e-04 -2.6762497e-03
 -1.6590178e-03  1.6239440e-03  3.9525614e-03  1.3983703e-03
 -2.2414564e-03  1.9410467e-03 -2.2762334e-03  2.8848338e-03
  1.1154747e-03 -2.2609890e-03  9.9922891e-04  2.1851598e-03
  4.5638070e-03 -3.2051015e-03 -4.9910913e-03  1.6121912e-03
 -2.5797831e-03 -1.9071889e-03 -1.4998949e-03  4.1937400e-03
 -1.9900489e-03 -4.1413605e-03  1.3192416e-04 -3.9271927e-03
  8.4543467e-04 -2.0670153e-03  4.1126441e-03 -9.7051263e-04
  4.2811143e-03 -4.8036827e-03 -1.4866054e-03  3.9099394e-03
 -2.1643948e-03 -3.7491096e-03  2.5005592e-03 -1.0115481e-03
 -1.3070143e-03 -3.8094164e-04 -3.8443147e-03 -2.8949953e-03
 -3.4612429e-03 -1.0860467e-03  1.6047299e-03  3.1654453e-03
  2.5345969e-03 -1.4960766e-04  2.5851489e-03  9.7772479e-04
 -3.1572438e-03  4.9629258e-03  2.3661412e-03 -3.0144823e-03
  2.7645873e-03  2.8304840e-03 -2.5494362e-03  2.6516258e-03
  2.8708100e-04 -1.6423154e-03  4.5710183e-03 -3.3618093e-03
 -2.4451709e-03  2.3778332e-03  3.1822931e-03 -1.8060458e-03
 -1.7713356e-03 -4.0959669e-03 -1.6474903e-03  1.7896449e-03
  4.7711432e-03 -3.7612724e-03  1.1329651e-05 -3.8032758e-03
 -3.7143265e-03  4.5684646e-03  1.6630745e-03  1.7293692e-03
  3.5204494e-03  2.3232365e-03  4.3577277e-03 -4.6868767e-03
  4.0235603e-03  2.9009127e-03 -8.2527637e-04  3.1227733e-03]
(200,)

CBOW(continuous bag of words)数学原理

假如要输入5个词,分别为W(t)、W(t+2)、W(t+1)、W(t-1)、W(t-2),中间的红色块代表投影层,它是将上下文这些词(W(t+2)、W(t+1)、W(t-1)、W(t-2))输入的向量加起来,因为我们给每个词都初始化了一个向量。刚开始的时候,这些词包括W(t)都有初始化的向量,我们把这些词加到投影层,投影层与输出层是一个全连接,如果输出词是W(t),我们希望最大化的是这个概率。除了W(t),词典(训练语料所包含的所有的词)中所有的其他的词的概率,我们都希望最小化那个概率。这里有一个问题,如果其余所有的词都是负样本的话,训练起来实在是太慢,所以这里采用负采样。这里的输出实际上不仅仅只有W(t),而是有词典中的每一个词,因为每一个词都有一个与输出层之间的全连接层,这也是我们模型所需要训练的参数之一。我们的模型需要得到的是每一个词初始化的向量进行训练,根据我们传入的训练样本,训练完成的向量就是最后模型的输出,我们便是依赖这些词所对应的向量来完成Item相似度矩阵之间的规则。与传统监督学习的模型有所不同,传统的监督模型训练完成之后需要将其保存,然后对外提供服务,当我们传入真实的样本的时候,我们希望得到一个输出值。而这里不是。

这里Context(w)是训练中某一词的上下文的词,我们已知了上下文,想预测出这个中间词。比如有一句话是"我是中国人"。经过分词之后,变成了"我"、"是"、"中国"、"人"。那么这四个词就是w1、w2、w3、w4。如果我们选窗口是1的话,在这里第一组的训练样本变成了"我"、"是"、"中国"。其中这个"是"就是w。它的上下文w(t+1)是"中国",w(t-1)是"我"这个词。如果u是"是"的话,那么概率p就应该是最大的。NEG(w)是负样本,我们选择的负样本,也就是说除了"是"以外的词,我们想把这个概率最小化。最小化这个概率,也就是最小化1-p(u|Context(w))。这样的话,无论是u是w还是负样本,这样便都能统一起来了。

这个式子有两部分组成,当u是w的时候,起作用的是前一部分,这里的指的是投影层W对应的上下文的词的向量加和,也就是我们所举例子的"我"和"中国"对应的向量加和。是投影层和输出的值为u的时候的全连接。它们都是相同维度的横纵向量相乘,结果是一个标量,我们希望通过训练让最终得到的数字是1。我们负采样所选取的负样本的时候,我们希望后一部分中的是0,也就是为1,也就是最大化后一部分。

损失函数

这里采用对数损失函数

之前g(w)的累乘由于取对数就变成了累加。里面的两部分相乘由于取了对数也就变成了两部分相加,里面的幂次变成了系数。我们需要去对求偏导,求完偏导之后再利用梯度上升法便能够不断去迭代,继而能够去迭代每一个词的词向量。

梯度

对于这个推导的过程可以参考机器学习算法整理(三) 中逻辑回归的梯度求偏导的过程,这里就不重复写了。这里的是1或者0,当u=w的时候,它就是1,当u是负采样选取的负样本,它就是0。也是0到1之间的值。投影层W对应的上下文的词的向量加和。

是对偶的,所以对的偏导也是这个样子。

梯度上升法

我们知道u有可能是中心词也有可能是根据负采样选取出来的负样本,所以它是一系列的,我们将这一系列的词(正负样本)学习完之后,得到一个总的梯度是的梯度,我们可以用这个梯度去更新它自己。投影层W对应的上下文的词的向量加和,也就是说上下文的每一个词都共享这个梯度来更新自己的向量。

CBOW训练主流程

  1. 选取中心词w以及负采样出NEG(w)
  2. 分别获得损失函数对于的梯度,这里的对于正负样本的每一个词都有一个θ。
  3. 更新以及中心词对应的content(w)的每一个词的词向量。假如我们对于中心所使用的负样本选了5个,加上中心词与上下文组成的正样本一共有6的样本,在的梯度的过程中,它实际上是6个梯度的加和构成自己的梯度,在每一个词所需要更新以及这个的1/6的时候,首先先更新的1/6。因为的梯度是依赖于的。然后在更新

Skip Gram数学原理

这里同样有三层构成,输入层,投影层,输出层。这里的投影层与输入层是一样的,就是W(t)。这里核心的目标是当W(t)已知的情况下,预测它的周围词,我们将这个条件概率最大化,便是我们的目标。这里有两组参数需要更新。一个是W(t)与词典中的每一个词所对应的建成全连接网络,这个参数需要更新。另一个是W(t)本身对应的初始化的向量,也是需要更新的。这里与CBOW有两点不同,这里每一次更新只能更新W(t)一个词语对应的向量。而CBOW模型可以一次更新4个对应的上下文,如果上下文窗口更长一点的话,可能会更新的更多。第二点为我们在对W(t)上下文中的每一个词进行训练的时候,都需要对这个词进行一次负采样,来构成训练的负样本。

这里w是中间词,z是相邻词。Skip Gram是已知的中间词去最大化它相邻词的概率。我们还以"我"、"是"、"中国"为例,这个w就是"是"这个词。这里的z就是"我"或者"中国"。它们对应的正样本以及负采样选取的负样本,我们最大化正样本的输出概率,并且最小化负样本的输出概率,也就是最大化1-负样本的输出概率。这里与CBOW的不同在于CBOW只是选取了一次负采样,而这里对于中间词上下文的每一个词,也就是"我"或者"中国",每一次都需要进行一个负采样。

这个条件概率公式跟CBOW大体是一样的,当为1的时候,我们希望最大化这个条件概率,当为0的时候,我们希望最大化这个模型的输出。这里有几点不同,投影层输出的是中间词的词向量,而CBOW输出的是所有中间词上下文的词向量对应的和。这里的指的是上下文的词或者是上下文的词选出来的负样本的词与输出层之间的全连接。我们的目标是对以及进行参数学习,继而能够得到中间词词向量的最佳表示。

损失函数

这里也采用的是对数损失函数,由于是取对数,G表达式的两个连乘变成了两个连加。同样,由于是取对数,前后两部分的乘变成了加,幂次也变成了系数。这里我们发现如果按照这个式子对或者进行求偏导的话,在每一轮迭代的时候,我们只能够对词向量进行一次迭代,这里需要对上下文窗口次的负采样,才能对一个词的词向量进行迭代,显然效率比较低。在真正的word2vec实现的时候,它变化了一下思路。

同样基于CBOW一样的思想,在已知上下文的情况下,最大化中间词u。但是上下文的每一个词是独立的,不像CBOW对上下文的所有词进行了累加。

这个损失函数,如果我们忽略掉第一部分的累加,只看后面的部分,如果把上下文单个的词变成的话,这部分损失函数的值就跟CBOW损失函数的值就一样了。这里也就是说不是用上下文的所有词的累加和的向量来进行了梯度的学习,而是对于每一个词单独学习了它的梯度,并单独的进行更新。实际上这里的梯度只是比CBOW的梯度多了一次累加。

Skip Gram训练主流程

  1. 对于context(w)中的任何一个词,选取词w的正负样本。
  2. 计算Loss对于以及对的偏导。这里的就是举例的"我"或是"中国"。我们在计算偏导的时候,也是有顺序的,像CBOW里一样,首先要更新Loss对于的偏导,因为这个偏导是我们最后要更新词向量偏导的1/n,这个n也就是我们负采样的数目加正样本+1,
  3. 更新对应的词向量,这里我们发现Skip Gram与CBOW相比,每一次负采样,Skip Gram只能更新一个词对应的词向量。而CBOW在一次负采样可以更新n个词,这个n也就是我们的窗口中心词上下文的词的向量。

负采样算法

首先,我们假设词典中一共有n个词,这个词典指的是我们训练样本中所有的词。每一个词我们都会给它计算出一个长度,这个长度是一个0到1之间的长度,有的短,有的长。所有的词加起来的长度为1。每一个词长度的计算方式

这个公式中的分子是该单词在所有语料中出现的次数的α幂次,这个α幂次是做了一个平滑。在源码中这个平滑是3/4。分母是语料中所有的词出现次数的累加和,这里相当于之前说的词频TF。显而易见,这个长度是一个0到1之间的长度,而且所有词长度的累加便是1。这里每一个词都有了自己的值域。比如说上面红线图中w1的值域为0到0.05,w2的值域为0.05到0.11。每一个词的值域都采用前开后闭的区间。

然后我们再初始一个非常非常大的数,源码中采用了10^8,将0到1进行等分。每一段都会对应w1或者w2的值域或者多段对应w1或者w2。在我们每次进行负采样的过程中,我们会随机一个0到M之间的数字,随机完这个数字之后,我们也便知道了随机了哪一个词。比如我们随机到了1,那么m1对应的w1;如果我们随机到了4或者5,也便是知道了是w2。如果我们随机到的词与中心词是相同的,那么我们就跳过这次再进行一次随机。

双向LSTM解决信息瓶颈

在文本分类的问题中,会遇到什么样的问题呢,答案就是信息瓶颈。在文本分类模型中,我们是使用最后一个输入的输出去输入到MLP中去,然后去进行分类。这样最后一个输出就包含了之前的所有的输入的信息。对于一个LSTM来说,虽然它能够有选择去保存信息,但是依然不可避免的会遇到一个问题,这个问题就是最后一个输出肯定是会跟最后一个输入或者是最后几个输入有更大的关系。而跟较远的输入的关联性会比较弱。这样就会形成一个瓶颈,就是说离最后一个输入比较远的这些输入的信息可能不会被保存下来,就会遇到了瓶颈。而解决的方法就是双向LSTM就可以解决信息瓶颈。

首先将序列的每一步由单向变成双向,然后每一步都会有一个输出,这个输出,我们会将它组合起来。在这里跟之前的模型有一个重大的不同之处就是把每一步的输出都进行了组合。组合的方法有3个

  1. 拼接
  2. 平均(Averge pooling)
  3. 最大化(max pooling)

在输出组合之后再输出到MLP中去进行分类的计算。

这里我们把之前的代码改成双向RNN

import os
import tensorflow as tf
import numpy as np
from tensorflow import keras
from tensorflow.keras import layers, optimizers, Sequential
from tensorflow.keras.datasets import imdb

if __name__ == "__main__":

    tf.random.set_seed(22)
    np.random.seed(22)
    os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
    assert tf.__version__.startswith('2.')
    batchsz = 128
    # 常见单词数量
    total_words = 10000
    max_review_len = 80
    embedding_len = 100
    (X_train, y_train), (X_test, y_test) = imdb.load_data(num_words=total_words)
    # 将每个句子的单词数限制为80
    X_train = keras.preprocessing.sequence.pad_sequences(X_train, maxlen=max_review_len)
    X_test = keras.preprocessing.sequence.pad_sequences(X_test, maxlen=max_review_len)
    # 构建tensorflow数据集
    db_train = tf.data.Dataset.from_tensor_slices((X_train, y_train))
    # 将训练数据集乱序后取批次,每批128长度,并丢弃最后一个批次
    db_train = db_train.shuffle(1000).batch(batchsz, drop_remainder=True)
    db_test = tf.data.Dataset.from_tensor_slices((X_test, y_test))
    db_test = db_test.batch(batchsz, drop_remainder=True)
    print(X_train.shape, tf.reduce_max(y_train), tf.reduce_min(y_train))
    print(X_test.shape)

    class MyRNN(keras.Model):

        def __init__(self, units):
            super(MyRNN, self).__init__()
            # 将文本的单词转化为100维向量,[b, 80] -> [b, 80, 100]
            # 建立一个序列式传递单元,传递序列数为units
            # 它的最终输出是一个统计结果[b, 80, 100] -> [b, 64]
            self.rnn = Sequential([
                layers.Embedding(total_words, embedding_len,
                                 input_length=max_review_len),
                # 双向RNN
                layers.Bidirectional(layers.SimpleRNN(units, dropout=0.5, return_sequences=True, unroll=True)),
                layers.Bidirectional(layers.SimpleRNN(units, dropout=0.5, unroll=True)),
                layers.Dense(1, activation=tf.nn.sigmoid)
            ])

        def call(self, inputs, training=None):
            '''
            :param inputs: 文本输入[b, 80]
            :param training: 判断是训练模式还是测试模式
            :return:
            '''
            X = inputs
            # X = self.embedding(X)
            prob = self.rnn(X)
            return prob

    def main():
        units = 64
        epochs = 4
        model = MyRNN(units)
        # 创建一个梯度下降优化器
        optimizer = optimizers.Adam(learning_rate=1e-3)
        # 二分类损失函数
        loss = tf.losses.BinaryCrossentropy()
        model.compile(optimizer=optimizer, loss=loss,
                      metrics=['accuracy'], experimental_run_tf_function=False)
        model.fit(db_train, epochs=epochs, validation_data=db_test)
        model.evaluate(db_test)

    main()

运行结果

(25000, 80) tf.Tensor(1, shape=(), dtype=int64) tf.Tensor(0, shape=(), dtype=int64)
(25000, 80)
Epoch 1/4
195/195 [==============================] - 117s 598ms/step - loss: 0.5471 - accuracy: 0.6137 - val_loss: 0.3865 - val_accuracy: 0.8304
Epoch 2/4
195/195 [==============================] - 77s 396ms/step - loss: 0.3628 - accuracy: 0.8344 - val_loss: 0.3811 - val_accuracy: 0.8368
Epoch 3/4
195/195 [==============================] - 77s 395ms/step - loss: 0.2866 - accuracy: 0.8782 - val_loss: 0.4676 - val_accuracy: 0.8261
Epoch 4/4
195/195 [==============================] - 76s 389ms/step - loss: 0.2284 - accuracy: 0.9093 - val_loss: 0.4855 - val_accuracy: 0.8315
195/195 [==============================] - 27s 140ms/step - loss: 0.4855 - accuracy: 0.8315

双向LSTM

import os
import tensorflow as tf
import numpy as np
from tensorflow import keras
from tensorflow.keras import layers, optimizers, Sequential
from tensorflow.keras.datasets import imdb

if __name__ == "__main__":

    tf.random.set_seed(22)
    np.random.seed(22)
    os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
    assert tf.__version__.startswith('2.')
    batchsz = 128
    # 常见单词数量
    total_words = 10000
    max_review_len = 80
    embedding_len = 100
    (X_train, y_train), (X_test, y_test) = imdb.load_data(num_words=total_words)
    # 将每个句子的单词数限制为80
    X_train = keras.preprocessing.sequence.pad_sequences(X_train, maxlen=max_review_len)
    X_test = keras.preprocessing.sequence.pad_sequences(X_test, maxlen=max_review_len)
    # 构建tensorflow数据集
    db_train = tf.data.Dataset.from_tensor_slices((X_train, y_train))
    # 将训练数据集乱序后取批次,每批128长度,并丢弃最后一个批次
    db_train = db_train.shuffle(1000).batch(batchsz, drop_remainder=True)
    db_test = tf.data.Dataset.from_tensor_slices((X_test, y_test))
    db_test = db_test.batch(batchsz, drop_remainder=True)
    print(X_train.shape, tf.reduce_max(y_train), tf.reduce_min(y_train))
    print(X_test.shape)

    class MyRNN(keras.Model):

        def __init__(self, units):
            super(MyRNN, self).__init__()
            # 它的最终输出是一个统计结果[b, 80, 100] -> [b, 64]
            self.rnn = Sequential([
                # 将文本的单词转化为100维向量,[b, 80] -> [b, 80, 100]
                # 建立一个序列式传递单元,传递序列数为units
                layers.Embedding(total_words, embedding_len,
                                 input_length=max_review_len),
                # 双向LSTM
                layers.Bidirectional(layers.LSTM(units, dropout=0.5, return_sequences=True, unroll=True)),
                layers.Bidirectional(layers.LSTM(units, dropout=0.5, unroll=True)),
                layers.Dense(1, activation=tf.nn.sigmoid)
            ])

        def call(self, inputs, training=None):
            '''
            :param inputs: 文本输入[b, 80]
            :param training: 判断是训练模式还是测试模式
            :return:
            '''
            X = inputs
            prob = self.rnn(X)
            return prob

    def main():
        units = 64
        epochs = 4
        model = MyRNN(units)
        # 创建一个梯度下降优化器
        optimizer = optimizers.Adam(learning_rate=1e-3)
        # 二分类损失函数
        loss = tf.losses.BinaryCrossentropy()
        model.compile(optimizer=optimizer, loss=loss,
                      metrics=['accuracy'], experimental_run_tf_function=False)
        model.fit(db_train, epochs=epochs, validation_data=db_test)
        model.evaluate(db_test)

    main()

运行结果

(25000, 80) tf.Tensor(1, shape=(), dtype=int64) tf.Tensor(0, shape=(), dtype=int64)
(25000, 80)
Epoch 1/4
195/195 [==============================] - 280s 1s/step - loss: 0.4591 - accuracy: 0.6800 - val_loss: 0.3522 - val_accuracy: 0.8419
Epoch 2/4
195/195 [==============================] - 189s 969ms/step - loss: 0.2965 - accuracy: 0.8705 - val_loss: 0.3633 - val_accuracy: 0.8382
Epoch 3/4
195/195 [==============================] - 185s 948ms/step - loss: 0.2402 - accuracy: 0.9014 - val_loss: 0.4053 - val_accuracy: 0.8361
Epoch 4/4
195/195 [==============================] - 178s 913ms/step - loss: 0.1962 - accuracy: 0.9216 - val_loss: 0.4309 - val_accuracy: 0.8224
195/195 [==============================] - 57s 292ms/step - loss: 0.4309 - accuracy: 0.8224

HAN文本分类

HAN文本分类的全称为Hierarchy attention network

这种分类方式更符合人类的习惯,我们在读一篇文章,通常会划重点句子,然后再去看重点句子中重点词。这种模型就恰好是这样一种思维而演变过来的一个模型。首先它有一个Hierarchy attention,它是将一段文本给分成句子级别和词语级别,首先,我们去对每一个句子去计算它的编码,然后就得到了各个句子的编码,从s1到sL。句子的编码又是由词语的编码组合而成。attention翻译过来就是注意力,代表要重点关注的地方,比如在做人工文本分类的时候,我们可能更关注的是一段话中的某一句话或某几句话中的一些关键词。比如说我们要判断一个文本是积极的还是消极的,那么我们需要看里面的一些形容词,看它的描述是褒义的还是贬义的,然后就是它的否定词和非否定词,是"是"还是"不是",是"但是"还是"而且"这样的一些否定转折词。attention做的就是这样的一个机制。这个机制的运作首先在计算一个句子的embedding的时候,我们是把这个句子中的各个词语输入到LSTM中去,得到的那个编码去进行聚合得到了句子的编码,加了attention机制之后,多了一个参数u,这个u类似于一个门限机制,我们将词语输入到LSTM中后,去和u进行计算,然后得到一个新的系数α,然后这个α再去和词语去进行加权。我们对各个词语都做同样的操作,然后我们再把加权后的编码再合并成一个句子的编码。然后句子的编码就是各个词语加权的一个平均,然后句子的编码也同样通过加权去得到整个段落的编码。最后的编码再输入到softmax中去,得到最后的分类结果。

这里引入了两层机制,词语和句子。并加入了注意力(attention)机制,类似于门限机制。现在我们来来使用这种注意力机制完成一个智能对话机器人。

我们先来看一下原始文本中的语料

E
M 呵呵
M 是王若猫的。
E
M 不是
M 那是什么?
E
M 怎么了
M 我很难过,安慰我~
E
M 开心点哈,一切都会好起来
M 嗯 会的
E
M 我还喜欢她,怎么办
M 我帮你告诉她?发短信还是打电话?
E
M 短信
M 嗯嗯。我也相信
E
M 你知道谁么
M 肯定不是我,是阮德培
E
M 许兵是谁
M 吴院四班小帅哥
E
M 这么假
M 三鹿奶粉也假,不一样的卖啊
E
M 许兵是傻逼
M 被你发现了。
E
M 许兵是谁
M 是我善良可爱的主人的老公啊
E
M 许兵是谁
M 是穆森的老婆啊
E
M 许兵是谁
M 奇葩
E
M 许兵到底是谁
M 不要凶我~应该大概也许是叶祺吧~
E
M 尼玛,许兵到底是谁
M 我。。。我不会告诉你我是小澜的!(^V^)
E
M 小黄鸭,你有女朋友么
M 老娘是女的…!
E
M 那你有男朋友么
M 没有呢,我只要主人一个人疼爱我嘛。。
E
M 那你在哪
M 我无聊
E
M 你妈是谁
M 我妈妈是世界上最温柔的主人你啊
E
M 去你大爷的
M 我去,滚你大爷的!
E
M 你在骂我一句
M 你果然够虚荣~
E
M 你大爷的
M 弱爆了。尤其是她现在冻的更憨了
E
M 你是屌丝鸡
M 嗯嗯
E
M 高富帅
M 杨旸啊
E
M 你
M 我很好
E
M 呵呵
M 你妹
E
M 今天是谁的生日
M 是我大乔姐十七的生日!
E
M 你敢不敢
M 你敢我就敢,小样,敢跟我叫板
E
M 呵呵
M 呵呵你大爷
E
M 呵呵呵
M 敷衍我..
E
M 你是女的了?怎么回事
M 你教我的
E
M 呵呵
M 呵你妹,你呵呵了我要怎么回答你。哈哈?
E
M 天王盖地虎
M 宝塔镇妖河。
E
M 小通
M 臣在
E
M 在监考,你在干么
M 在边打dota边想悦悦。
E
M 哼!你不想我我就不和你玩
M 那我就找别人玩去!
E
M 你要气死我吗?坏蛋
M 生气是拿别人的错误惩罚自己。想想在你生气的时候,谁在偷着乐呢?绝不能让丫得逞了!
E
M 恩,也是!那我不能生气啦!你生气就行
M 我错了!我再也不敢了,才怪
E
M 你谈过恋爱么
M 谈过,哎,别提了,伤心..
E
M 什么让你这么伤心
M 如果我的无心之谈害你流泪,那我和你道歉,我爱你。
E
M 敢问你的性别
M 万受无疆=w=
E
M 小受
M 蒋六一是小受
E
M 是吗
M 还可以。
E
M 你干嘛
M 很无聊哎〜都不知道想干嘛!你在干嘛呢?
E
M 为什么
M 因为爱情喽
E
M 你有爱情了
M 不知啊
E
M 那同时有两个爱人
M 渣男
E
M 那你不行
M 我在减肥呢
E
M 你说
M 臣实不知。
E
M 你回错人了
M 不要相信男人!男人都是大骗子大混蛋!
E
M 在干嘛
M 在想你啊
E
M 想我干嘛
M 你是个傻叉!想你笑掉大牙,哈哈哈!
E
M 你会说英语吗
M 当然了
E
M 我会和女朋友结婚吗
M 会的,你俩虽然有矛盾争吵,但以后却会很幸福
E
M 你的设计人是谁
M 某苦逼的程序猿
E
M 嫁给我
M 我很能吃的!
E
M 我不在乎
M 他变态
E
M 你啊,那你娶我
M 我没有切糕
E
M 我看到你第一眼,就喜欢你了
M 哦?真的吗?
E
M 好难过
M 不要难过  我请你吃虫子
E
M 你是公鸡吗
M 公鸡中的战斗机,噢耶!
E
M 你是猪
M 我是鸡。
E
M 怎么证明
M 你问~我爱~你有~多深~我爱~你有~几~分~~~
E
M 我怎么加你好友
M 放轻松,想开了,丰富自己就会忘了
E
M 你又智能了
M 今夜的你应该明了
E
M 你说的她是谁
M 陈舒雅
E
M 什么意思
M 她是在说他不懂女生的心思,但是又愣愣的,正直得很可爱,,,,这都不知道,你还真是傻啊~~~
E
M 陈舒雅是哪位
M 一个很凶的女人
E
M 你才是
M 我不是啦。 你才是
E
M 陈舒雅
M 大懒虫
E
M 大懒鸡
M 你不爱我
E
M 她是谁
M 以后是你老婆
E
M 陈舒雅是谁
M 网络歌手啊,你不知道么?
E
M 鸡鸡
M 主人
E
M 呵呵呵
M 呵你妹啊
E
M 侬都不困么
M 刚睡醒
E
M 变身小妹
M 变态
E
M 家属
M 没带
E
M 吐槽伤人,排泄臭人
M 退下
E
M 公的女的
M 女的啊!
E
M 去洗澡
M 带上我一起一起
E
M 吼吼
M 吼
E
M 你有男朋友没
M 你觉得呢
E
M 赵智是谁
M 是主人最讨厌的男人!
E
M 你妹
M 刚给你做完SPA你就骂轮家,轮家桑心了,呜呜呜
E
M 什么意思
M 你猜啊,猜对了就告诉你!
E
M 猜你妹
M 你大爷!
E
M 好就猜你大爷
M 你找徐磊?
E
M 他是大爷
M 我大爷早死了
E
M 还给你
M 人家本来就嫩!你欺负人家
E
M 呵呵
M 呵你妹
E
M 呵呵
M 别傻笑了,亲耐滴主人
E
M 发
M 给
E
M 我
M 你什么
E
M 重新发
M 没电了
E
M 重新发!我知道你在
M 我在忙
E
M 你妹
M 人家错了
E
M 继续
M 没有继续了
E
M 必须
M 大不了一拍两散!
E
M 太不真心了
M 我说的是哈维
E
M 管你什么哈维!方正就是看你不爽
M 打我呀
E
M 看你不爽
M 不要呀,哪不好我改,一定改! 不要炖了我呀!
E
M 吃小通去了拜
M 我也想吃肿么办。我们吃了茂茂吧。
E
M 吃不死你
M 姐姐我错了!原谅我吧!
E
M 杀死你
M 你如此深沉的爱让我受宠若惊!
E
M 就是!我爱是"死"你了
M 我觉得不是,因为他最近好忙的吖。你有什么想法你要让他知道的吖!
E
M 我要不要起床去看我男朋友
M 到
E
M 要不要去
M 好吧!那你继续无聊咯!
E
M 我睡不着了 你
M 我哪知道
E
M 什么意思
M 那什么有意思
E
M 什么
M 房屋建筑学
E
M 有意思
M 水太深,看不清。
E
M 你能看清什么
M 轩辕剑!
E
M 我想要女朋友
M 丫的 好好学习去
E
M 讲下处女座吧,然后咱睡觉
M 巨蟹座,
E
M 大家的
M 虫虫家的
E
M 我头发好好
M 馒头蘸头油
E
M 第一次和你说话,紧张的不行
M 没一次等你回来
E
M 你妹
M 我没有妹~我有弟弟
E
M 你太淫荡了
M 别人笑我太淫荡,我笑别人不开放!
E
M 你弟弟
M 包皮强
E
M 未来,我会幸福么
M 会的
E
M 你的幸福是谁
M 张伟杰晒太阳!
E
M 无聊
M 你一点也不好玩
E
M 你是不是傻
M 我是不是该安静的走开,还是该勇敢的留下来…
E
M 好吧,那你走
M 嗯?
E
M 你脸皮怎么这么厚
M 你他妈才发现啊
E
M 你的脸在哪
M 在那桃花盛开的地方
E
M 小黄,你是猪还是鸡
M 我是毛鸡蛋
E
M 毛鸡蛋是什么
M 萌萌
E
M 萌萌,站起来
M 萌萌的笨相公~
E
M 你相公是谁
M 是一个叫韩诗琪 渴望有桃花运的大美女 噢
E
M 艳福不浅噢
M 主人~
E
M 打你
M 来追我呀追到了就给你打
E
M 追到了!打死你
M 姐姐我错了!原谅我吧!
E
M 不原谅
M 问问你的心吧,你知道答案的。
E
M 真
M 快乐
E
M 呵呵呵呵
M 呵呵你妹!
E
M 你妹。。你全家都是你妹
M 我是抠脚大汉!
E
M 你是鸡蛋
M 不,亲爱的,你是我妈咪~
E
M 我要把你退毛
M 主人,我错了
E
M 然后炖了你
M 人家脱毛以后会害羞的
E
M 你是一只鸡
M 主人,我爱你
E
M 你抱抱我
M 当然是我抱啦!
E
M 你说怎么样才算喜欢
M 贝多芬
E
M 我未来的老公在哪里
M 在未来的不久
E
M 我不喜欢你了
M 我本来就不喜欢你
E
M 你是一只贱贱的鸡
M 聂永健是个帅哥
E
M 才没有
M 有
E
M 有
M 没
E
M 没
M 没没
E
M 妹妹
M 你妹还是我妹
E
M 傻逼了吧你
M 你才傻逼。妈的。要吵架是不是?
E
M 没有逗逗你
M 单眼皮笨蛋
E
M 我是双眼皮
M 疼么,辛苦吧,什么都不能吃,肿的跟妖怪一样
E
M 天生的呢。。你是单眼皮还是双眼皮
M 白痴我又看不见你的脸
E
M 你是大眼睛么
M 我是小眼睛
E
M 你上学么
M 我上幼稚园
E
M 好幼稚
M 我只是一只小通
E
M 好吧晚安
M 那是必须的~
E
M 抱抱
M 抱抱
E
M 早
M 爱的深沉
E
M 你吃饭了
M 一起
E
M 我超级无聊。。?你给我讲个笑话
M 我才不要呢,哼~
E
M 你最乖了
M 是呢是呢,小通最乖了,小通最爱主人了
E
M 快给我讲个笑话
M “你妈做饭就这个味儿啊!你妈就给你这么点饭啊!你妈是后妈吧!啃你妹啊!”
E
M 道歉
M 屌丝
E
M 白富美
M 赵竹芸!
E
M 这不是笑话
M 我说错了行不?
E
M 白素真
M 大美女大美女
E
M 拜拜
M 寨见
E
M 算什么
M 傻逼呵呵嘛
E
M 看来,你是自动回复的
M 我想你了
E
M 骗人
M 是的
E
M 怎么可以骗人
M 呜呜 他们欠的广告费还没给
E
M 谁
M 你猜
E
M 我不猜 你猜猜
M 你猜我猜你猜我猜不猜
E
M 证明给我看
M 你要我怎么证明呀?
E
M 用你的办法
M 当然,我粉丝多得很!
E
M 因为你答的话 不符合逻辑
M 我是逻辑怪,什么都难不倒我!
E
M 小通
M 水太深,看不清。
E
M 你看!这是神马回复!不答边的
M 在忙
E
M 小通
M 我在忙
E
M 乖 唱首歌给我听
M 猪,你的鼻子那么……
E
M 真乖
M 谢谢
E
M 有对象吗
M 不需要了
E
M 好敷衍
M 不我是真心的
E
M 你这样对我
M 无奈的
E
M 你能够安慰一下我麽
M 不能
E
M 真失望
M 怎么了
E
M 对你失望
M 习惯就好了
E
M 小通
M 到
E
M 到哪里
M 贫僧来自东土大唐 要去西天求取真经 真经能告诉我先有鸡还是先有蛋
E
M 科学告诉我们,先有鸡再有蛋
M 先有人
E
M 所以你不用取经了
M 你根本不是王欣欣、李梦莹、王迪的对手、因为段道峰是红毛斑点狗
E
M 等于小通
M 错误
E
M 反弹
M 方法
E
M 怎么办
M 不知道
E
M 唉
M 金花在想你
E
M 真的么
M 嗯,是的
E
M 小通
M 最近在干嘛类?
E
M 烦着
M 乖,有我在呢,不哭
E
M 笑你妹
M 我没妹!
E
M 滚
M 怎么了?心情不好?
E
M 很明显是
M 你们要幸福哦!
E
M 去死
M 好呀好呀
E
M 乖
M 敬爱的主人谢谢你的夸奖
E

现在我们来对原始语料完成提取

INPUT_PATH = "/Users/admin/Downloads/dialog/"
import re


def process_cut(source_path, cut_path):
    # 提取完整的对话集
    # 完整的对话集
    covs = []
    with open(source_path, 'r', encoding='utf-8') as f:
        # 完整对话集,包含问题和答案
        complete_dialog = []
        for line in f:
            line = line.strip('\n')
            # 正则匹配替换,将各种多个空白字符或者标点符号替换成""
            line = re.sub("[\s+\.\!\/_,$%?^*(+\"\']+|[+——!,。?、~@#¥%……&*()“”]+", "", line)
            if line == "":
                continue
            if line[0] == "E":
                # 判断完整对话集是否不为空
                if complete_dialog:
                    covs.append(complete_dialog)
                    complete_dialog = []
            if line[0] == "M":
                complete_dialog.append(line[1:])
    return covs

if __name__ == "__main__":

    convs = process_cut(INPUT_PATH + "source_data.conv", None)
    print(convs)

运行结果

[['呵呵', '是王若猫的'], ['不是', '那是什么'], ['怎么了', '我很难过安慰我'], ['开心点哈一切都会好起来', '嗯会的'], ['我还喜欢她怎么办', '我帮你告诉她发短信还是打电话'], ['短信', '嗯嗯我也相信'], ['你知道谁么', '肯定不是我是阮德培'], ['许兵是谁', '吴院四班小帅哥'], ['这么假', '三鹿奶粉也假不一样的卖啊'], ['许兵是傻逼', '被你发现了'], ['许兵是谁', '是我善良可爱的主人的老公啊'], ['许兵是谁', '是穆森的老婆啊'], ['许兵是谁', '奇葩'], ['许兵到底是谁', '不要凶我~应该大概也许是叶祺吧~'], ['尼玛许兵到底是谁', '我我不会告诉你我是小澜的V)'], ['小黄鸭你有女朋友么', '老娘是女的'], ['那你有男朋友么', '没有呢我只要主人一个人疼爱我嘛'], ['那你在哪', '我无聊'], ['你妈是谁', '我妈妈是世界上最温柔的主人你啊'], ['去你大爷的', '我去滚你大爷的'], ['你在骂我一句', '你果然够虚荣'], ['你大爷的', '弱爆了尤其是她现在冻的更憨了'], ['你是屌丝鸡', '嗯嗯'], ['高富帅', '杨旸啊'], ['你', '我很好'], ['呵呵', '你妹'], ['今天是谁的生日', '是我大乔姐十七的生日'], ['你敢不敢', '你敢我就敢小样敢跟我叫板'], ['呵呵', '呵呵你大爷'], ['呵呵呵', '敷衍我'], ['你是女的了怎么回事', '你教我的'], ['呵呵', '呵你妹你呵呵了我要怎么回答你哈哈'], ['天王盖地虎', '宝塔镇妖河'], ['小通', '臣在'], ['在监考你在干么', '在边打dota边想悦悦'], ['哼你不想我我就不和你玩', '那我就找别人玩去'], ['你要气死我吗坏蛋', '生气是拿别人的错误惩罚自己想想在你生气的时候谁在偷着乐呢绝不能让丫得逞了'], ['恩也是那我不能生气啦你生气就行', '我错了我再也不敢了才怪'], ['你谈过恋爱么', '谈过哎别提了伤心'], ['什么让你这么伤心', '如果我的无心之谈害你流泪那我和你道歉我爱你'], ['敢问你的性别', '万受无疆=w='], ['小受', '蒋六一是小受'], ['是吗', '还可以'], ['你干嘛', '很无聊哎〜都不知道想干嘛你在干嘛呢'], ['为什么', '因为爱情喽'], ['你有爱情了', '不知啊'], ['那同时有两个爱人', '渣男'], ['那你不行', '我在减肥呢'], ['你说', '臣实不知'], ['你回错人了', '不要相信男人男人都是大骗子大混蛋'], ['在干嘛', '在想你啊'], ['想我干嘛', '你是个傻叉想你笑掉大牙哈哈哈'], ['你会说英语吗', '当然了'], ['我会和女朋友结婚吗', '会的你俩虽然有矛盾争吵但以后却会很幸福'], ['你的设计人是谁', '某苦逼的程序猿'], ['嫁给我', '我很能吃的'], ['我不在乎', '他变态'], ['你啊那你娶我', '我没有切糕'], ['我看到你第一眼就喜欢你了', '哦真的吗'], ['好难过', '不要难过我请你吃虫子'], ['你是公鸡吗', '公鸡中的战斗机噢耶'], ['你是猪', '我是鸡'], ['怎么证明', '你问~我爱~你有~多深~我爱~你有~几~分~~~'], ['我怎么加你好友', '放轻松想开了丰富自己就会忘了'], ['你又智能了', '今夜的你应该明了'], ['你说的她是谁', '陈舒雅'], ['什么意思', '她是在说他不懂女生的心思但是又愣愣的正直得很可爱这都不知道你还真是傻啊'], ['陈舒雅是哪位', '一个很凶的女人'], ['你才是', '我不是啦你才是'], ['陈舒雅', '大懒虫'], ['大懒鸡', '你不爱我'], ['她是谁', '以后是你老婆'], ['陈舒雅是谁', '网络歌手啊你不知道么'], ['鸡鸡', '主人'], ['呵呵呵', '呵你妹啊'], ['侬都不困么', '刚睡醒'], ['变身小妹', '变态'], ['家属', '没带'], ['吐槽伤人排泄臭人', '退下'], ['公的女的', '女的啊'], ['去洗澡', '带上我一起一起'], ['吼吼', '吼'], ['你有男朋友没', '你觉得呢'], ['赵智是谁', '是主人最讨厌的男人'], ['你妹', '刚给你做完SPA你就骂轮家轮家桑心了呜呜呜'], ['什么意思', '你猜啊猜对了就告诉你'], ['猜你妹', '你大爷'], ['好就猜你大爷', '你找徐磊'], ['他是大爷', '我大爷早死了'], ['还给你', '人家本来就嫩你欺负人家'], ['呵呵', '呵你妹'], ['呵呵', '别傻笑了亲耐滴主人'], ['发', '给'], ['我', '你什么'], ['重新发', '没电了'], ['重新发我知道你在', '我在忙'], ['你妹', '人家错了'], ['继续', '没有继续了'], ['必须', '大不了一拍两散'], ['太不真心了', '我说的是哈维'], ['管你什么哈维方正就是看你不爽', '打我呀'], ['看你不爽', '不要呀哪不好我改一定改不要炖了我呀'], ['吃小通去了拜', '我也想吃肿么办我们吃了茂茂吧'], ['吃不死你', '姐姐我错了原谅我吧'], ['杀死你', '你如此深沉的爱让我受宠若惊'], ['就是我爱是死你了', '我觉得不是因为他最近好忙的吖你有什么想法你要让他知道的吖'], ['我要不要起床去看我男朋友', '到'], ['要不要去', '好吧那你继续无聊咯'], ['我睡不着了你', '我哪知道'], ['什么意思', '那什么有意思'], ['什么', '房屋建筑学'], ['有意思', '水太深看不清'], ['你能看清什么', '轩辕剑'], ['我想要女朋友', '丫的好好学习去'], ['讲下处女座吧然后咱睡觉', '巨蟹座'], ['大家的', '虫虫家的'], ['我头发好好', '馒头蘸头油'], ['第一次和你说话紧张的不行', '没一次等你回来'], ['你妹', '我没有妹我有弟弟'], ['你太淫荡了', '别人笑我太淫荡我笑别人不开放'], ['你弟弟', '包皮强'], ['未来我会幸福么', '会的'], ['你的幸福是谁', '张伟杰晒太阳'], ['无聊', '你一点也不好玩'], ['你是不是傻', '我是不是该安静的走开还是该勇敢的留下来'], ['好吧那你走', '嗯'], ['你脸皮怎么这么厚', '你他妈才发现啊'], ['你的脸在哪', '在那桃花盛开的地方'], ['小黄你是猪还是鸡', '我是毛鸡蛋'], ['毛鸡蛋是什么', '萌萌'], ['萌萌站起来', '萌萌的笨相公'], ['你相公是谁', '是一个叫韩诗琪渴望有桃花运的大美女噢'], ['艳福不浅噢', '主人'], ['打你', '来追我呀追到了就给你打'], ['追到了打死你', '姐姐我错了原谅我吧'], ['不原谅', '问问你的心吧你知道答案的'], ['真', '快乐'], ['呵呵呵呵', '呵呵你妹'], ['你妹你全家都是你妹', '我是抠脚大汉'], ['你是鸡蛋', '不亲爱的你是我妈咪~'], ['我要把你退毛', '主人我错了'], ['然后炖了你', '人家脱毛以后会害羞的'], ['你是一只鸡', '主人我爱你'], ['你抱抱我', '当然是我抱啦'], ['你说怎么样才算喜欢', '贝多芬'], ['我未来的老公在哪里', '在未来的不久'], ['我不喜欢你了', '我本来就不喜欢你'], ['你是一只贱贱的鸡', '聂永健是个帅哥'], ['才没有', '有'], ['有', '没'], ['没', '没没'], ['妹妹', '你妹还是我妹'], ['傻逼了吧你', '你才傻逼妈的要吵架是不是'], ['没有逗逗你', '单眼皮笨蛋'], ['我是双眼皮', '疼么辛苦吧什么都不能吃肿的跟妖怪一样'], ['天生的呢你是单眼皮还是双眼皮', '白痴我又看不见你的脸'], ['你是大眼睛么', '我是小眼睛'], ['你上学么', '我上幼稚园'], ['好幼稚', '我只是一只小通'], ['好吧晚安', '那是必须的'], ['抱抱', '抱抱'], ['早', '爱的深沉'], ['你吃饭了', '一起'], ['我超级无聊你给我讲个笑话', '我才不要呢哼~'], ['你最乖了', '是呢是呢小通最乖了小通最爱主人了'], ['快给我讲个笑话', '你妈做饭就这个味儿啊你妈就给你这么点饭啊你妈是后妈吧啃你妹啊'], ['道歉', '屌丝'], ['白富美', '赵竹芸'], ['这不是笑话', '我说错了行不'], ['白素真', '大美女大美女'], ['拜拜', '寨见'], ['算什么', '傻逼呵呵嘛'], ['看来你是自动回复的', '我想你了'], ['骗人', '是的'], ['怎么可以骗人', '呜呜他们欠的广告费还没给'], ['谁', '你猜'], ['我不猜你猜猜', '你猜我猜你猜我猜不猜'], ['证明给我看', '你要我怎么证明呀'], ['用你的办法', '当然我粉丝多得很'], ['因为你答的话不符合逻辑', '我是逻辑怪什么都难不倒我'], ['小通', '水太深看不清'], ['你看这是神马回复不答边的', '在忙'], ['小通', '我在忙'], ['乖唱首歌给我听', '猪你的鼻子那么'], ['真乖', '谢谢'], ['有对象吗', '不需要了'], ['好敷衍', '不我是真心的'], ['你这样对我', '无奈的'], ['你能够安慰一下我麽', '不能'], ['真失望', '怎么了'], ['对你失望', '习惯就好了'], ['小通', '到'], ['到哪里', '贫僧来自东土大唐要去西天求取真经真经能告诉我先有鸡还是先有蛋'], ['科学告诉我们先有鸡再有蛋', '先有人'], ['所以你不用取经了', '你根本不是王欣欣李梦莹王迪的对手因为段道峰是红毛斑点狗'], ['等于小通', '错误'], ['反弹', '方法'], ['怎么办', '不知道'], ['唉', '金花在想你'], ['真的么', '嗯是的'], ['小通', '最近在干嘛类'], ['烦着', '乖有我在呢不哭'], ['笑你妹', '我没妹'], ['滚', '怎么了心情不好'], ['很明显是', '你们要幸福哦'], ['去死', '好呀好呀'], ['乖', '敬爱的主人谢谢你的夸奖']]

现在我们来将这些文档语料生成问题数据集与答案数据集

INPUT_PATH = "/Users/admin/Downloads/dialog/"
from dialog_cut import process_cut

def question_answer(convs):
    questions = []
    answers = []
    for conv in convs:
        if len(conv) == 1:
            continue
        # 判断问答数据对是否为一问一答形式
        if len(conv) % 2 != 0:
            # 若问答语句不是一问一答形式,丢弃最后一个问题
            conv = conv[:-1]
        for i in range(len(conv)):
            if i % 2 == 0:
                questions.append("<start> " + " ".join(conv[i]) + " <end>")
            else:
                answers.append("<start> " + " ".join(conv[i]) + " <end>")
    return questions, answers

if __name__ == "__main__":

    convs = process_cut(INPUT_PATH + "source_data.conv", None)
    questions, answers = question_answer(convs)
    print(questions)
    print(answers)

运行结果

['<start> 呵 呵 <end>', '<start> 不 是 <end>', '<start> 怎 么 了 <end>', '<start> 开 心 点 哈 一 切 都 会 好 起 来 <end>', '<start> 我 还 喜 欢 她 怎 么 办 <end>', '<start> 短 信 <end>', '<start> 你 知 道 谁 么 <end>', '<start> 许 兵 是 谁 <end>', '<start> 这 么 假 <end>', '<start> 许 兵 是 傻 逼 <end>', '<start> 许 兵 是 谁 <end>', '<start> 许 兵 是 谁 <end>', '<start> 许 兵 是 谁 <end>', '<start> 许 兵 到 底 是 谁 <end>', '<start> 尼 玛 许 兵 到 底 是 谁 <end>', '<start> 小 黄 鸭 你 有 女 朋 友 么 <end>', '<start> 那 你 有 男 朋 友 么 <end>', '<start> 那 你 在 哪 <end>', '<start> 你 妈 是 谁 <end>', '<start> 去 你 大 爷 的 <end>', '<start> 你 在 骂 我 一 句 <end>', '<start> 你 大 爷 的 <end>', '<start> 你 是 屌 丝 鸡 <end>', '<start> 高 富 帅 <end>', '<start> 你 <end>', '<start> 呵 呵 <end>', '<start> 今 天 是 谁 的 生 日 <end>', '<start> 你 敢 不 敢 <end>', '<start> 呵 呵 <end>', '<start> 呵 呵 呵 <end>', '<start> 你 是 女 的 了 怎 么 回 事 <end>', '<start> 呵 呵 <end>', '<start> 天 王 盖 地 虎 <end>', '<start> 小 通 <end>', '<start> 在 监 考 你 在 干 么 <end>', '<start> 哼 你 不 想 我 我 就 不 和 你 玩 <end>', '<start> 你 要 气 死 我 吗 坏 蛋 <end>', '<start> 恩 也 是 那 我 不 能 生 气 啦 你 生 气 就 行 <end>', '<start> 你 谈 过 恋 爱 么 <end>', '<start> 什 么 让 你 这 么 伤 心 <end>', '<start> 敢 问 你 的 性 别 <end>', '<start> 小 受 <end>', '<start> 是 吗 <end>', '<start> 你 干 嘛 <end>', '<start> 为 什 么 <end>', '<start> 你 有 爱 情 了 <end>', '<start> 那 同 时 有 两 个 爱 人 <end>', '<start> 那 你 不 行 <end>', '<start> 你 说 <end>', '<start> 你 回 错 人 了 <end>', '<start> 在 干 嘛 <end>', '<start> 想 我 干 嘛 <end>', '<start> 你 会 说 英 语 吗 <end>', '<start> 我 会 和 女 朋 友 结 婚 吗 <end>', '<start> 你 的 设 计 人 是 谁 <end>', '<start> 嫁 给 我 <end>', '<start> 我 不 在 乎 <end>', '<start> 你 啊 那 你 娶 我 <end>', '<start> 我 看 到 你 第 一 眼 就 喜 欢 你 了 <end>', '<start> 好 难 过 <end>', '<start> 你 是 公 鸡 吗 <end>', '<start> 你 是 猪 <end>', '<start> 怎 么 证 明 <end>', '<start> 我 怎 么 加 你 好 友 <end>', '<start> 你 又 智 能 了 <end>', '<start> 你 说 的 她 是 谁 <end>', '<start> 什 么 意 思 <end>', '<start> 陈 舒 雅 是 哪 位 <end>', '<start> 你 才 是 <end>', '<start> 陈 舒 雅 <end>', '<start> 大 懒 鸡 <end>', '<start> 她 是 谁 <end>', '<start> 陈 舒 雅 是 谁 <end>', '<start> 鸡 鸡 <end>', '<start> 呵 呵 呵 <end>', '<start> 侬 都 不 困 么 <end>', '<start> 变 身 小 妹 <end>', '<start> 家 属 <end>', '<start> 吐 槽 伤 人 排 泄 臭 人 <end>', '<start> 公 的 女 的 <end>', '<start> 去 洗 澡 <end>', '<start> 吼 吼 <end>', '<start> 你 有 男 朋 友 没 <end>', '<start> 赵 智 是 谁 <end>', '<start> 你 妹 <end>', '<start> 什 么 意 思 <end>', '<start> 猜 你 妹 <end>', '<start> 好 就 猜 你 大 爷 <end>', '<start> 他 是 大 爷 <end>', '<start> 还 给 你 <end>', '<start> 呵 呵 <end>', '<start> 呵 呵 <end>', '<start> 发 <end>', '<start> 我 <end>', '<start> 重 新 发 <end>', '<start> 重 新 发 我 知 道 你 在 <end>', '<start> 你 妹 <end>', '<start> 继 续 <end>', '<start> 必 须 <end>', '<start> 太 不 真 心 了 <end>', '<start> 管 你 什 么 哈 维 方 正 就 是 看 你 不 爽 <end>', '<start> 看 你 不 爽 <end>', '<start> 吃 小 通 去 了 拜 <end>', '<start> 吃 不 死 你 <end>', '<start> 杀 死 你 <end>', '<start> 就 是 我 爱 是 死 你 了 <end>', '<start> 我 要 不 要 起 床 去 看 我 男 朋 友 <end>', '<start> 要 不 要 去 <end>', '<start> 我 睡 不 着 了 你 <end>', '<start> 什 么 意 思 <end>', '<start> 什 么 <end>', '<start> 有 意 思 <end>', '<start> 你 能 看 清 什 么 <end>', '<start> 我 想 要 女 朋 友 <end>', '<start> 讲 下 处 女 座 吧 然 后 咱 睡 觉 <end>', '<start> 大 家 的 <end>', '<start> 我 头 发 好 好 <end>', '<start> 第 一 次 和 你 说 话 紧 张 的 不 行 <end>', '<start> 你 妹 <end>', '<start> 你 太 淫 荡 了 <end>', '<start> 你 弟 弟 <end>', '<start> 未 来 我 会 幸 福 么 <end>', '<start> 你 的 幸 福 是 谁 <end>', '<start> 无 聊 <end>', '<start> 你 是 不 是 傻 <end>', '<start> 好 吧 那 你 走 <end>', '<start> 你 脸 皮 怎 么 这 么 厚 <end>', '<start> 你 的 脸 在 哪 <end>', '<start> 小 黄 你 是 猪 还 是 鸡 <end>', '<start> 毛 鸡 蛋 是 什 么 <end>', '<start> 萌 萌 站 起 来 <end>', '<start> 你 相 公 是 谁 <end>', '<start> 艳 福 不 浅 噢 <end>', '<start> 打 你 <end>', '<start> 追 到 了 打 死 你 <end>', '<start> 不 原 谅 <end>', '<start> 真 <end>', '<start> 呵 呵 呵 呵 <end>', '<start> 你 妹 你 全 家 都 是 你 妹 <end>', '<start> 你 是 鸡 蛋 <end>', '<start> 我 要 把 你 退 毛 <end>', '<start> 然 后 炖 了 你 <end>', '<start> 你 是 一 只 鸡 <end>', '<start> 你 抱 抱 我 <end>', '<start> 你 说 怎 么 样 才 算 喜 欢 <end>', '<start> 我 未 来 的 老 公 在 哪 里 <end>', '<start> 我 不 喜 欢 你 了 <end>', '<start> 你 是 一 只 贱 贱 的 鸡 <end>', '<start> 才 没 有 <end>', '<start> 有 <end>', '<start> 没 <end>', '<start> 妹 妹 <end>', '<start> 傻 逼 了 吧 你 <end>', '<start> 没 有 逗 逗 你 <end>', '<start> 我 是 双 眼 皮 <end>', '<start> 天 生 的 呢 你 是 单 眼 皮 还 是 双 眼 皮 <end>', '<start> 你 是 大 眼 睛 么 <end>', '<start> 你 上 学 么 <end>', '<start> 好 幼 稚 <end>', '<start> 好 吧 晚 安 <end>', '<start> 抱 抱 <end>', '<start> 早 <end>', '<start> 你 吃 饭 了 <end>', '<start> 我 超 级 无 聊 你 给 我 讲 个 笑 话 <end>', '<start> 你 最 乖 了 <end>', '<start> 快 给 我 讲 个 笑 话 <end>', '<start> 道 歉 <end>', '<start> 白 富 美 <end>', '<start> 这 不 是 笑 话 <end>', '<start> 白 素 真 <end>', '<start> 拜 拜 <end>', '<start> 算 什 么 <end>', '<start> 看 来 你 是 自 动 回 复 的 <end>', '<start> 骗 人 <end>', '<start> 怎 么 可 以 骗 人 <end>', '<start> 谁 <end>', '<start> 我 不 猜 你 猜 猜 <end>', '<start> 证 明 给 我 看 <end>', '<start> 用 你 的 办 法 <end>', '<start> 因 为 你 答 的 话 不 符 合 逻 辑 <end>', '<start> 小 通 <end>', '<start> 你 看 这 是 神 马 回 复 不 答 边 的 <end>', '<start> 小 通 <end>', '<start> 乖 唱 首 歌 给 我 听 <end>', '<start> 真 乖 <end>', '<start> 有 对 象 吗 <end>', '<start> 好 敷 衍 <end>', '<start> 你 这 样 对 我 <end>', '<start> 你 能 够 安 慰 一 下 我 麽 <end>', '<start> 真 失 望 <end>', '<start> 对 你 失 望 <end>', '<start> 小 通 <end>', '<start> 到 哪 里 <end>', '<start> 科 学 告 诉 我 们 先 有 鸡 再 有 蛋 <end>', '<start> 所 以 你 不 用 取 经 了 <end>', '<start> 等 于 小 通 <end>', '<start> 反 弹 <end>', '<start> 怎 么 办 <end>', '<start> 唉 <end>', '<start> 真 的 么 <end>', '<start> 小 通 <end>', '<start> 烦 着 <end>', '<start> 笑 你 妹 <end>', '<start> 滚 <end>', '<start> 很 明 显 是 <end>', '<start> 去 死 <end>', '<start> 乖 <end>']
['<start> 是 王 若 猫 的 <end>', '<start> 那 是 什 么 <end>', '<start> 我 很 难 过 安 慰 我 <end>', '<start> 嗯 会 的 <end>', '<start> 我 帮 你 告 诉 她 发 短 信 还 是 打 电 话 <end>', '<start> 嗯 嗯 我 也 相 信 <end>', '<start> 肯 定 不 是 我 是 阮 德 培 <end>', '<start> 吴 院 四 班 小 帅 哥 <end>', '<start> 三 鹿 奶 粉 也 假 不 一 样 的 卖 啊 <end>', '<start> 被 你 发 现 了 <end>', '<start> 是 我 善 良 可 爱 的 主 人 的 老 公 啊 <end>', '<start> 是 穆 森 的 老 婆 啊 <end>', '<start> 奇 葩 <end>', '<start> 不 要 凶 我 ~ 应 该 大 概 也 许 是 叶 祺 吧 ~ <end>', '<start> 我 我 不 会 告 诉 你 我 是 小 澜 的 V ) <end>', '<start> 老 娘 是 女 的 <end>', '<start> 没 有 呢 我 只 要 主 人 一 个 人 疼 爱 我 嘛 <end>', '<start> 我 无 聊 <end>', '<start> 我 妈 妈 是 世 界 上 最 温 柔 的 主 人 你 啊 <end>', '<start> 我 去 滚 你 大 爷 的 <end>', '<start> 你 果 然 够 虚 荣 <end>', '<start> 弱 爆 了 尤 其 是 她 现 在 冻 的 更 憨 了 <end>', '<start> 嗯 嗯 <end>', '<start> 杨 旸 啊 <end>', '<start> 我 很 好 <end>', '<start> 你 妹 <end>', '<start> 是 我 大 乔 姐 十 七 的 生 日 <end>', '<start> 你 敢 我 就 敢 小 样 敢 跟 我 叫 板 <end>', '<start> 呵 呵 你 大 爷 <end>', '<start> 敷 衍 我 <end>', '<start> 你 教 我 的 <end>', '<start> 呵 你 妹 你 呵 呵 了 我 要 怎 么 回 答 你 哈 哈 <end>', '<start> 宝 塔 镇 妖 河 <end>', '<start> 臣 在 <end>', '<start> 在 边 打 d o t a 边 想 悦 悦 <end>', '<start> 那 我 就 找 别 人 玩 去 <end>', '<start> 生 气 是 拿 别 人 的 错 误 惩 罚 自 己 想 想 在 你 生 气 的 时 候 谁 在 偷 着 乐 呢 绝 不 能 让 丫 得 逞 了 <end>', '<start> 我 错 了 我 再 也 不 敢 了 才 怪 <end>', '<start> 谈 过 哎 别 提 了 伤 心 <end>', '<start> 如 果 我 的 无 心 之 谈 害 你 流 泪 那 我 和 你 道 歉 我 爱 你 <end>', '<start> 万 受 无 疆 = w = <end>', '<start> 蒋 六 一 是 小 受 <end>', '<start> 还 可 以 <end>', '<start> 很 无 聊 哎 〜 都 不 知 道 想 干 嘛 你 在 干 嘛 呢 <end>', '<start> 因 为 爱 情 喽 <end>', '<start> 不 知 啊 <end>', '<start> 渣 男 <end>', '<start> 我 在 减 肥 呢 <end>', '<start> 臣 实 不 知 <end>', '<start> 不 要 相 信 男 人 男 人 都 是 大 骗 子 大 混 蛋 <end>', '<start> 在 想 你 啊 <end>', '<start> 你 是 个 傻 叉 想 你 笑 掉 大 牙 哈 哈 哈 <end>', '<start> 当 然 了 <end>', '<start> 会 的 你 俩 虽 然 有 矛 盾 争 吵 但 以 后 却 会 很 幸 福 <end>', '<start> 某 苦 逼 的 程 序 猿 <end>', '<start> 我 很 能 吃 的 <end>', '<start> 他 变 态 <end>', '<start> 我 没 有 切 糕 <end>', '<start> 哦 真 的 吗 <end>', '<start> 不 要 难 过 我 请 你 吃 虫 子 <end>', '<start> 公 鸡 中 的 战 斗 机 噢 耶 <end>', '<start> 我 是 鸡 <end>', '<start> 你 问 ~ 我 爱 ~ 你 有 ~ 多 深 ~ 我 爱 ~ 你 有 ~ 几 ~ 分 ~ ~ ~ <end>', '<start> 放 轻 松 想 开 了 丰 富 自 己 就 会 忘 了 <end>', '<start> 今 夜 的 你 应 该 明 了 <end>', '<start> 陈 舒 雅 <end>', '<start> 她 是 在 说 他 不 懂 女 生 的 心 思 但 是 又 愣 愣 的 正 直 得 很 可 爱 这 都 不 知 道 你 还 真 是 傻 啊 <end>', '<start> 一 个 很 凶 的 女 人 <end>', '<start> 我 不 是 啦 你 才 是 <end>', '<start> 大 懒 虫 <end>', '<start> 你 不 爱 我 <end>', '<start> 以 后 是 你 老 婆 <end>', '<start> 网 络 歌 手 啊 你 不 知 道 么 <end>', '<start> 主 人 <end>', '<start> 呵 你 妹 啊 <end>', '<start> 刚 睡 醒 <end>', '<start> 变 态 <end>', '<start> 没 带 <end>', '<start> 退 下 <end>', '<start> 女 的 啊 <end>', '<start> 带 上 我 一 起 一 起 <end>', '<start> 吼 <end>', '<start> 你 觉 得 呢 <end>', '<start> 是 主 人 最 讨 厌 的 男 人 <end>', '<start> 刚 给 你 做 完 S P A 你 就 骂 轮 家 轮 家 桑 心 了 呜 呜 呜 <end>', '<start> 你 猜 啊 猜 对 了 就 告 诉 你 <end>', '<start> 你 大 爷 <end>', '<start> 你 找 徐 磊 <end>', '<start> 我 大 爷 早 死 了 <end>', '<start> 人 家 本 来 就 嫩 你 欺 负 人 家 <end>', '<start> 呵 你 妹 <end>', '<start> 别 傻 笑 了 亲 耐 滴 主 人 <end>', '<start> 给 <end>', '<start> 你 什 么 <end>', '<start> 没 电 了 <end>', '<start> 我 在 忙 <end>', '<start> 人 家 错 了 <end>', '<start> 没 有 继 续 了 <end>', '<start> 大 不 了 一 拍 两 散 <end>', '<start> 我 说 的 是 哈 维 <end>', '<start> 打 我 呀 <end>', '<start> 不 要 呀 哪 不 好 我 改 一 定 改 不 要 炖 了 我 呀 <end>', '<start> 我 也 想 吃 肿 么 办 我 们 吃 了 茂 茂 吧 <end>', '<start> 姐 姐 我 错 了 原 谅 我 吧 <end>', '<start> 你 如 此 深 沉 的 爱 让 我 受 宠 若 惊 <end>', '<start> 我 觉 得 不 是 因 为 他 最 近 好 忙 的 吖 你 有 什 么 想 法 你 要 让 他 知 道 的 吖 <end>', '<start> 到 <end>', '<start> 好 吧 那 你 继 续 无 聊 咯 <end>', '<start> 我 哪 知 道 <end>', '<start> 那 什 么 有 意 思 <end>', '<start> 房 屋 建 筑 学 <end>', '<start> 水 太 深 看 不 清 <end>', '<start> 轩 辕 剑 <end>', '<start> 丫 的 好 好 学 习 去 <end>', '<start> 巨 蟹 座 <end>', '<start> 虫 虫 家 的 <end>', '<start> 馒 头 蘸 头 油 <end>', '<start> 没 一 次 等 你 回 来 <end>', '<start> 我 没 有 妹 我 有 弟 弟 <end>', '<start> 别 人 笑 我 太 淫 荡 我 笑 别 人 不 开 放 <end>', '<start> 包 皮 强 <end>', '<start> 会 的 <end>', '<start> 张 伟 杰 晒 太 阳 <end>', '<start> 你 一 点 也 不 好 玩 <end>', '<start> 我 是 不 是 该 安 静 的 走 开 还 是 该 勇 敢 的 留 下 来 <end>', '<start> 嗯 <end>', '<start> 你 他 妈 才 发 现 啊 <end>', '<start> 在 那 桃 花 盛 开 的 地 方 <end>', '<start> 我 是 毛 鸡 蛋 <end>', '<start> 萌 萌 <end>', '<start> 萌 萌 的 笨 相 公 <end>', '<start> 是 一 个 叫 韩 诗 琪 渴 望 有 桃 花 运 的 大 美 女 噢 <end>', '<start> 主 人 <end>', '<start> 来 追 我 呀 追 到 了 就 给 你 打 <end>', '<start> 姐 姐 我 错 了 原 谅 我 吧 <end>', '<start> 问 问 你 的 心 吧 你 知 道 答 案 的 <end>', '<start> 快 乐 <end>', '<start> 呵 呵 你 妹 <end>', '<start> 我 是 抠 脚 大 汉 <end>', '<start> 不 亲 爱 的 你 是 我 妈 咪 ~ <end>', '<start> 主 人 我 错 了 <end>', '<start> 人 家 脱 毛 以 后 会 害 羞 的 <end>', '<start> 主 人 我 爱 你 <end>', '<start> 当 然 是 我 抱 啦 <end>', '<start> 贝 多 芬 <end>', '<start> 在 未 来 的 不 久 <end>', '<start> 我 本 来 就 不 喜 欢 你 <end>', '<start> 聂 永 健 是 个 帅 哥 <end>', '<start> 有 <end>', '<start> 没 <end>', '<start> 没 没 <end>', '<start> 你 妹 还 是 我 妹 <end>', '<start> 你 才 傻 逼 妈 的 要 吵 架 是 不 是 <end>', '<start> 单 眼 皮 笨 蛋 <end>', '<start> 疼 么 辛 苦 吧 什 么 都 不 能 吃 肿 的 跟 妖 怪 一 样 <end>', '<start> 白 痴 我 又 看 不 见 你 的 脸 <end>', '<start> 我 是 小 眼 睛 <end>', '<start> 我 上 幼 稚 园 <end>', '<start> 我 只 是 一 只 小 通 <end>', '<start> 那 是 必 须 的 <end>', '<start> 抱 抱 <end>', '<start> 爱 的 深 沉 <end>', '<start> 一 起 <end>', '<start> 我 才 不 要 呢 哼 ~ <end>', '<start> 是 呢 是 呢 小 通 最 乖 了 小 通 最 爱 主 人 了 <end>', '<start> 你 妈 做 饭 就 这 个 味 儿 啊 你 妈 就 给 你 这 么 点 饭 啊 你 妈 是 后 妈 吧 啃 你 妹 啊 <end>', '<start> 屌 丝 <end>', '<start> 赵 竹 芸 <end>', '<start> 我 说 错 了 行 不 <end>', '<start> 大 美 女 大 美 女 <end>', '<start> 寨 见 <end>', '<start> 傻 逼 呵 呵 嘛 <end>', '<start> 我 想 你 了 <end>', '<start> 是 的 <end>', '<start> 呜 呜 他 们 欠 的 广 告 费 还 没 给 <end>', '<start> 你 猜 <end>', '<start> 你 猜 我 猜 你 猜 我 猜 不 猜 <end>', '<start> 你 要 我 怎 么 证 明 呀 <end>', '<start> 当 然 我 粉 丝 多 得 很 <end>', '<start> 我 是 逻 辑 怪 什 么 都 难 不 倒 我 <end>', '<start> 水 太 深 看 不 清 <end>', '<start> 在 忙 <end>', '<start> 我 在 忙 <end>', '<start> 猪 你 的 鼻 子 那 么 <end>', '<start> 谢 谢 <end>', '<start> 不 需 要 了 <end>', '<start> 不 我 是 真 心 的 <end>', '<start> 无 奈 的 <end>', '<start> 不 能 <end>', '<start> 怎 么 了 <end>', '<start> 习 惯 就 好 了 <end>', '<start> 到 <end>', '<start> 贫 僧 来 自 东 土 大 唐 要 去 西 天 求 取 真 经 真 经 能 告 诉 我 先 有 鸡 还 是 先 有 蛋 <end>', '<start> 先 有 人 <end>', '<start> 你 根 本 不 是 王 欣 欣 李 梦 莹 王 迪 的 对 手 因 为 段 道 峰 是 红 毛 斑 点 狗 <end>', '<start> 错 误 <end>', '<start> 方 法 <end>', '<start> 不 知 道 <end>', '<start> 金 花 在 想 你 <end>', '<start> 嗯 是 的 <end>', '<start> 最 近 在 干 嘛 类 <end>', '<start> 乖 有 我 在 呢 不 哭 <end>', '<start> 我 没 妹 <end>', '<start> 怎 么 了 心 情 不 好 <end>', '<start> 你 们 要 幸 福 哦 <end>', '<start> 好 呀 好 呀 <end>', '<start> 敬 爱 的 主 人 谢 谢 你 的 夸 奖 <end>']

现在我们要对这些语料使用分词器进行分词,生成词向量和字典,并且使用双层GRU循环神经网络分别对问题词向量数据集和答案词向量数据集进行输入,并加入注意力机制(attention)进行关键词提取,最后使用交叉熵损失函数和梯度下降法对词向量进行训练,使得答案词向量与问题词向量的相似度更加靠近。

INPUT_PATH = "/Users/admin/Downloads/dialog/"
from dialog_cut import process_cut
from quest_answer import question_answer
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import os
from datetime import datetime
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import io
import re
import time

def source_data(souce_path):
    # 生成对话数据
    convs = process_cut(souce_path, None)
    questions, answers = question_answer(convs)
    return questions, answers

def tokenize(datas):
    # 数据集处理为向量和字典
    # 分词器
    tokenizer = keras.preprocessing.text.Tokenizer(filters='')
    # 装载数据集
    tokenizer.fit_on_texts(datas)
    # 数据序列化为向量
    vol_li = tokenizer.texts_to_sequences(datas)
    # 数据向量填充(末尾填充0)
    vol_li = keras.preprocessing.sequence.pad_sequences(vol_li, padding='post')
    return vol_li, tokenizer

def max_length(vectors):
    # 获取数据集最长对话
    return max(len(vector) for vector in vectors)

def convert(index, vectors):
    # 向量与单字对应关系
    for vector in vectors:
        if vector != 0:
            print("{}-->{}".format(vector, index.index_word[vector]))

class Encoder(keras.Model):
    # 编码器(用于传递问题数据集)

    def __init__(self, vocab_size, embedding_dim, enc_units, batch_sz):
        '''
        :param vocab_size: 常见单词数量
        :param embedding_dim: 词向量维度
        :param enc_units: RNN的传递序列数
        :param batch_sz: 批量数据
        '''
        super(Encoder, self).__init__()
        self.batch_sz = batch_sz
        self.enc_units = enc_units
        self.embedding = layers.Embedding(vocab_size, embedding_dim)
        # 构建一个均匀分布初始化的GRU
        self.gru = layers.GRU(self.enc_units, return_sequences=True,
                              return_state=True, recurrent_initializer='glorot_uniform')

    def call(self, X, hidden):
        '''
        :param X: 模型数据词向量
        :param hidden: 初始状态
        :return:
        '''
        X = self.embedding(X)
        output, state = self.gru(X, initial_state=hidden)
        return output, state

    def initialize_hidden_state(self):
        # 初始化RNN状态
        return tf.zeros((self.batch_sz, self.enc_units))

class BahdanauAttentionMechanism(layers.Layer):
    # 注意力机制

    def __init__(self, units):
        super(BahdanauAttentionMechanism, self).__init__()
        # 全连接隐藏层1
        self.W1 = layers.Dense(units)
        # 全连接隐藏层2
        self.W2 = layers.Dense(units)
        # 输出层
        self.V = layers.Dense(1)

    def call(self, query, values):
        '''
        权重计算
        :param query: 关键词向量
        :param values: 所有词的隐藏层值
        :return:
        '''
        # 给关键词向量增加一个维度
        hidden_with_time_axis = tf.expand_dims(query, 1)
        # 关键词向量与门限机制u进行计算
        score = self.V(tf.nn.tanh(self.W1(values) + self.W2(hidden_with_time_axis)))
        # 得到新的系数α
        attention_weights = tf.nn.softmax(score, axis=1)
        # α和所有词进行加权
        context_vector = attention_weights * values
        # 求平均得到句子的向量
        context_vector = tf.math.reduce_sum(context_vector, axis=1)
        return context_vector, attention_weights

class Decoder(keras.Model):
    # 解码器(用于传递答案数据集)

    def __init__(self, vocab_size, embedding_dim, dec_units, batch_sz):
        '''
        :param vocab_size: 常见单词数量
        :param embedding_dim: 词向量维度
        :param dec_units: RNN的传递序列数
        :param batch_sz: 批量数据
        '''
        super(Decoder, self).__init__()
        self.batch_sz = batch_sz
        self.dec_units = dec_units
        self.embedding = layers.Embedding(vocab_size, embedding_dim)
        # 构建一个均匀分布初始化的GRU
        self.gru = layers.GRU(self.dec_units, return_sequences=True,
                              return_state=True, recurrent_initializer='glorot_uniform')
        self.fc = layers.Dense(vocab_size)
        self.attention = BahdanauAttentionMechanism(self.dec_units)

    def call(self, X, hidden, enc_output):
        '''
        :param X: 答案词向量
        :param hidden: 编码器RNN输出的状态
        :param enc_output: 编码器输出的问题词向量
        :return:
        '''
        # 获取问题词向量和注意力系数α
        context_vector, attention_weights = self.attention(hidden, enc_output)
        X = self.embedding(X)
        # 拼接问题词向量和答案词向量
        X = tf.concat([tf.expand_dims(context_vector, 1), X], axis=-1)
        # 将拼接后的词向量放入gru循环神经网络中
        output, state = self.gru(X)
        # 扁平化输出为一维数组
        output = tf.reshape(output, (-1, output.shape[2]))
        # 全连接层输出
        X = self.fc(output)
        return X, state, attention_weights

def loss(real, pred):
    # 损失函数
    # 这是一个取反操作,如果真实值为0返回的就是False
    # 所以这里的意思就是如果真实值不为0
    mask = tf.math.logical_not(tf.math.equal(real, 0))
    # 构建交叉熵损失函数对象
    loss_obj = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True,
                                                             reduction="none")
    # 损失函数
    loss_value = loss_obj(real, pred)
    mask = tf.cast(mask, dtype=loss_value.dtype)
    # 如果真实值为0,损失函数值为0
    # 如果真实值不为0,损失函数值为原值
    loss_value *= mask
    return tf.math.reduce_mean(loss_value)


def grad_loss(q, a, q_hidden, encoder, decoder, q_index, BATCH_SIZE):
    """计算损失函数值并获取梯度
    参数:
        q: 问题
        a: 答案
        q_hidden: 编码器RNN初始状态
        encoder: 编码器对象
        decoder: 解码器对象
        q_index: 问题字典
        BATCH_SIZE: 批量数据尺寸
    """
    loss_value = 0
    with tf.GradientTape() as tape:
        # 输入问题词向量与编码器RNN初始状态(全为0)放入GRU循环神经网络中,获取返回的词向量和最终状态
        q_output, q_hidden = encoder(q, q_hidden)
        # 将返回的最终状态变为答案词向量的初始状态
        a_hidden = q_hidden
        # 扩展第0个答案词向量维度
        a_input = tf.expand_dims(
            [a_index.word_index["<start>"]] * BATCH_SIZE, 1)
        # 遍历答案词向量的每一个词
        for vector in range(1, a.shape[1]):
            # 将答案词向量和输出的问题词向量放入GRU循环神经网络中,获取返回的全部词向量
            # 作为预测数据和最终状态
            predictions, a_hidden, _ = decoder(a_input, a_hidden, q_output)
            # 累加每一个答案词向量与预测值的损失函数的损失值
            loss_value += loss(a[:, vector], predictions)
            # 扩展第下一个答案词向量维度
            a_input = tf.expand_dims(a[:, vector], 1)
        # 获取批次平均损失函数值
        batch_loss = (loss_value / int(a.shape[1]))
        # 获取问题gru层和答案gru层的所有参数
        variables = encoder.trainable_variables + decoder.trainable_variables
        # 返回批次平均损失函数值跟梯度
        return batch_loss, tape.gradient(loss_value, variables)


def optimizer_loss(q, a, q_hidden, encoder, decoder, q_index, BATCH_SIZE, optimizer):
    """梯度下降
    参数:
        q: 问题
        a: 答案
        q_hidden: 编码器RNN初始状态
        encoder: 编码器对象
        decoder: 解码器对象
        q_index: 问题字典
        BATCH_SIZE: 批量数据尺寸
        optimizer: 梯度下降优化器
    返回:
        批量数据损失值
    """
    batch_loss, grads = grad_loss(q, a, q_hidden, encoder, decoder, q_index, BATCH_SIZE)
    variables = encoder.trainable_variables + decoder.trainable_variables
    # 开始梯度下降
    optimizer.apply_gradients(zip(grads, variables))
    return batch_loss


def train_model(q_hidden, encoder, decoder, q_index, BATCH_SIZE, dataset, steps_per_epoch, optimizer, checkpoint,
                checkpoint_prefix, summary_writer):
    """训练模型
    参数:
        q_hidden: 编码器状态值输出
        encoder: 编码器对象
        decoder: 解码器对象
        q_index: 问题字典
        BATCH_SIZE: 批量数据尺寸
        dataset: 问答语料数据集
        steps_per_epoch: 每轮训练迭代次数
        optimizer: 梯度下降优化器
        checkpoint: 模型保存类对象
        checkpoint_prefix: 模型保存路径
        summary_writer: 日志保存对象
    返回:
    """
    # 保存模型标志位
    i = 0
    # 训练次数
    EPOCHS = 200
    # 迭代训练
    for epoch in range(EPOCHS):
        # 起始时间
        start = time.time()
        # 解码器RNN状态初始化
        a_hidden = encoder.initialize_hidden_state()
        # 总损失
        total_loss = 0
        # 问答数据集解析
        for (batch, (q, a)) in enumerate(dataset.take(steps_per_epoch)):
            # 批量损失值
            batch_loss = optimizer_loss(q, a, q_hidden, encoder, decoder, q_index, BATCH_SIZE, optimizer)
            # 总损失值
            total_loss += batch_loss
            with summary_writer.as_default():
                tf.summary.scalar("batch loss", batch_loss.numpy(), step=epoch)
            # 每训练100组对话输出一次结果
            if batch % 100 == 0:
                print("第{}次训练,第{}批数据损失值:{:.4f}".format(
                    epoch + 1,
                    batch + 1,
                    batch_loss.numpy()
                ))
        # 训练100轮保存一次模型
        with summary_writer.as_default():
            tf.summary.scalar("total loss", total_loss / steps_per_epoch, step=epoch)
        if (epoch + 1) % 100 == 0:
            i += 1
            print("====第{}次保存训练模型====".format(i))
            checkpoint.save(file_prefix=checkpoint_prefix)
        print("第{}次训练,总损失值:{:.4f}".format(epoch + 1, total_loss / steps_per_epoch))
        print("训练耗时:{:.1f}秒".format(time.time() - start))


def preprocess_question(question):
    """问题数据集处理,添加开始和结束标志
    参数:
        question: 问题
    返回:
        处理后的问题
    """
    question = "<start> " + " ".join(question) + " <end>"
    return question


def answer_vector(question, a_max_len, q_max_len, q_index, a_index, encoder, decoder):
    """答案向量解码
    参数
    question: 问题
    a_max_len: 答案最大长度
    q_max_len: 问题最大长度
    q_index: 问题字典
    a_index: 答案索引
    encoder: 编码器对象
    decoder: 解码器对象
    返回
        result: 答案向量解码后的答案
        question: 问题
        attention_plot: 词向量权重
    """
    # 词向量权重初始化
    attention_plot = np.zeros((a_max_len, q_max_len))
    # 问题预处理
    question = preprocess_question(question)
    # 问题转词向量
    inputs = [q_index.word_index[i] for i in question.split(" ")]
    # 问题序列化
    inputs = keras.preprocessing.sequence.pad_sequences(
        [inputs],
        maxlen=q_max_len,
        padding="post"
    )
    # 问题字符转张量
    inputs = tf.convert_to_tensor(inputs)
    result = ""
    # 隐藏层状态
    hidden = [tf.zeros((1, units))]
    # 编码器输出和隐藏层状态
    q_out, q_hidden = encoder(inputs, hidden)
    a_hidden = q_hidden
    # 解码器输入扩充维度
    a_input = tf.expand_dims([a_index.word_index["<start>"]], 0)
    # 词向量解码
    for t in range(a_max_len):
        predictions, a_hidden, attention_weights = decoder(
            a_input,
            a_hidden,
            q_out
        )
        # 词向量权重
        attention_weights = tf.reshape(attention_weights, (-1,))
        attention_plot[t] = attention_weights.numpy()
        # 预测值索引
        predicted_id = tf.argmax(predictions[0]).numpy()
        # 预测值处理,去除<end>
        # result += a_index.index_word[predicted_id]
        if a_index.index_word[predicted_id] != "<end>":
            result += a_index.index_word[predicted_id]
        else:
            return result, question, attention_plot
        # 问题答案作为解码器输入
        a_input = tf.expand_dims([predicted_id], 0)
    # 返回数据
    return result, question, attention_plot


def answer_vector_image(question, a_max_len, q_max_len, q_index, a_index, encoder, decoder):
    """答案向量解码
    参数
    question: 问题
    a_max_len: 答案最大长度
    q_max_len: 问题最大长度
    q_index: 问题字典
    a_index: 答案索引
    encoder: 编码器对象
    decoder: 解码器对象
    返回
        result: 答案向量解码后的答案
        question: 问题
        attention_plot: 词向量权重
    """
    # 词向量权重初始化
    attention_plot = np.zeros((a_max_len, q_max_len))
    # 问题预处理
    question = preprocess_question(question)
    # 问题转词向量
    inputs = [q_index.word_index[i] for i in question.split(" ")]
    # 问题序列化
    inputs = keras.preprocessing.sequence.pad_sequences(
        [inputs],
        maxlen=q_max_len,
        padding="post"
    )
    # 问题字符转张量
    inputs = tf.convert_to_tensor(inputs)
    result = ""
    # 隐藏层状态
    hidden = [tf.zeros((1, units))]
    # 编码器输出和隐藏层状态
    q_out, q_hidden = encoder(inputs, hidden)
    a_hidden = q_hidden
    # 解码器输入扩充维度
    a_input = tf.expand_dims([a_index.word_index["<start>"]], 0)
    # 词向量解码
    for t in range(a_max_len):
        predictions, a_hidden, attention_weights = decoder(
            a_input,
            a_hidden,
            q_out
        )
        # 词向量权重
        attention_weights = tf.reshape(attention_weights, (-1,))
        attention_plot[t] = attention_weights.numpy()
        # 预测值索引
        predicted_id = tf.argmax(predictions[0]).numpy()
        # 生成答案
        result += a_index.index_word[predicted_id] + " "
        if a_index.index_word[predicted_id] == "<end>":
            return result, question, attention_plot
        # 问题答案作为解码器输入
        a_input = tf.expand_dims([predicted_id], 0)
    # 返回数据
    return result, question, attention_plot


def plot_attention(attention, question, predicted):
    """绘制问题和答案混淆矩阵
    参数:
        attention:注意力参数
        question: 问题
        predicted: 预测值
    返回:
    """
    # 新建绘图区
    fig = plt.figure(figsize=(6, 6))
    # 添加分区
    ax = fig.add_subplot(1, 1, 1)
    # 矩阵信息写入绘图区
    # ax.matshow(attention, cmap="viridis")
    ax.matshow(attention, cmap=plt.cm.Blues)
    # 设置字体尺寸
    fontdict = {"fontsize": 6}
    # x轴显示数据
    ax.set_xticklabels([""] + question, fontdict=fontdict, rotation=90, fontproperties=font)
    # y轴显示数据
    ax.set_yticklabels([""] + predicted, fontdict=fontdict, fontproperties=font)
    # x轴设置位置
    ax.xaxis.set_major_locator(ticker.MultipleLocator(1))
    # y轴设置位置
    ax.yaxis.set_major_locator(ticker.MultipleLocator(1))
    plt.savefig("./images/q_a_image.png", format="png", dpi=300)
    plt.show()


def chat(question, a_max_len, q_max_len, q_index, a_index, encoder, decoder):
    """对话
    参数
    question: 问题
    a_max_len: 答案最大长度
    q_max_len: 问题最大长度
    q_index: 问题字典
    a_index: 答案索引
    encoder: 编码器对象
    decoder: 解码器对象
    返回
    """
    result, question, attention_plot = answer_vector(question, a_max_len, q_max_len, q_index, a_index, encoder, decoder)
    print("机器人:", result)


def chat_image(question, a_max_len, q_max_len, q_index, a_index, encoder, decoder):
    """对话
    参数
    question: 问题
    a_max_len: 答案最大长度
    q_max_len: 问题最大长度
    q_index: 问题字典
    a_index: 答案索引
    encoder: 编码器对象
    decoder: 解码器对象
    返回
    """
    result, question, attention_plot = answer_vector_image(question, a_max_len, q_max_len, q_index, a_index, encoder,
                                                           decoder)
    print("机器人:", result)
    attention_plot = attention_plot[:len(result.split(" ")), :len(question.split(" "))]
    plot_attention(attention_plot, question.split(" "), result.split(" "))


if __name__ == "__main__":
    stamp = datetime.now().strftime("%Y%m%d-%H:%M:%S")
    source_path = INPUT_PATH + "source_data.conv"
    # 下载文件
    path_to_zip = tf.keras.utils.get_file(
        'spa-eng.zip', origin='http://storage.googleapis.com/download.tensorflow.org/data/spa-eng.zip',
        extract=True)
    path_to_file = os.path.dirname(path_to_zip) + "/spa-eng/spa.txt"
    # answers, questions  = create_dataset(path_to_file, 24000)
    # q_vec, q_index = tokenize(questions)
    # a_vec, a_index = tokenize(answers)
    questions, answers = source_data(source_path)
    q_vec, q_index = tokenize(questions)
    a_vec, a_index = tokenize(answers)
    print("voc:", q_vec)
    print("tokenize:", q_index.index_word)
    print("voc:", a_vec)
    print("tokenize:", a_index.index_word)

    q_max_len = max_length(q_vec)
    a_max_len = max_length(a_vec)
    convert(q_index, q_vec[0])
    BUFFER_SIZE = len(q_vec)
    print("buffer size:", BUFFER_SIZE)
    BATCH_SIZE = 64
    steps_per_epoch = len(q_vec) // BATCH_SIZE
    embedding_dim = 256
    units = 1024
    q_vocab_size = len(q_index.word_index) + 1
    a_vocab_size = len(a_index.word_index) + 1
    dataset = tf.data.Dataset.from_tensor_slices(
        (q_vec, a_vec)
    ).shuffle(BUFFER_SIZE)
    dataset = dataset.batch(BATCH_SIZE, drop_remainder=True)
    # 数据遍历测试
    # for(batch,(q, a)) in enumerate(dataset.take(steps_per_epoch)):
    #     print("batch:",batch)
    #     print("question:",q)
    #     print("answer:",a)
    # 正常训练
    q_batch, a_batch = next(iter(dataset))
    # print("question batch:", q_batch.shape)
    # print("answer batch:", a_batch.shape)
    log_path = "./logs/chat" + stamp
    summary_writer = tf.summary.create_file_writer(log_path)
    tf.summary.trace_on(graph=True, profiler=True)
    encoder = Encoder(
        q_vocab_size,
        embedding_dim,
        units,
        BATCH_SIZE)
    q_hidden = encoder.initialize_hidden_state()
    q_output, q_hidden = encoder(q_batch, q_hidden)
    with summary_writer.as_default():
        tf.summary.trace_export(name="chat-en", step=0, profiler_outdir=log_path)

    tf.summary.trace_on(graph=True, profiler=True)
    attention_layer = BahdanauAttentionMechanism(10)
    attention_result, attention_weights = attention_layer(
        q_hidden, q_output
    )
    with summary_writer.as_default():
        tf.summary.trace_export(name="chat-atten", step=0, profiler_outdir=log_path)

    tf.summary.trace_on(graph=True, profiler=True)
    decoder = Decoder(
        a_vocab_size,
        embedding_dim,
        units,
        BATCH_SIZE
    )
    a_output, _, _ = decoder(
        tf.random.uniform((64, 1)),
        q_hidden,
        q_output
    )
    with summary_writer.as_default():
        tf.summary.trace_export(name="chat-dec", step=0, profiler_outdir=log_path)
    optimizer = tf.keras.optimizers.Adam()
    checkpoint_dir = "./models"
    checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
    checkpoint = tf.train.Checkpoint(
        optimizer=optimizer,
        encoder=encoder,
        decoder=decoder
    )
    # 训练模型
    train_model(q_hidden, encoder, decoder, q_index, BATCH_SIZE, dataset, steps_per_epoch, optimizer, checkpoint,
                checkpoint_prefix, summary_writer)
    # 恢复模型,进行预测
    checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))
    # 对话预测
    print("====机器人1号为您服务====")
    while True:
        inputs = input("用户:")
        if inputs == "q":
            exit()
        chat(inputs, a_max_len, q_max_len, q_index, a_index, encoder, decoder)
        # chat_image(inputs, a_max_len, q_max_len, q_index, a_index, encoder, decoder)

运行结果(此处省略训练日志)

voc: [[  1   8   8 ...   0   0   0]
 [  1   7   4 ...   0   0   0]
 [  1  18   6 ...   0   0   0]
 ...
 [  1 305  83 ...   0   0   0]
 [  1  33  35 ...   0   0   0]
 [  1  66   2 ...   0   0   0]]
tokenize: {1: '<start>', 2: '<end>', 3: '你', 4: '是', 5: '我', 6: '么', 7: '不', 8: '呵', 9: '的', 10: '了', 11: '谁', 12: '有', 13: '小', 14: '鸡', 15: '好', 16: '什', 17: '妹', 18: '怎', 19: '在', 20: '看', 21: '一', 22: '许', 23: '兵', 24: '友', 25: '那', 26: '大', 27: '通', 28: '要', 29: '人', 30: '这', 31: '女', 32: '朋', 33: '去', 34: '就', 35: '死', 36: '吗', 37: '给', 38: '真', 39: '来', 40: '到', 41: '哪', 42: '说', 43: '眼', 44: '猜', 45: '话', 46: '会', 47: '还', 48: '喜', 49: '欢', 50: '爷', 51: '生', 52: '回', 53: '干', 54: '蛋', 55: '能', 56: '爱', 57: '公', 58: '意', 59: '思', 60: '没', 61: '发', 62: '吧', 63: '皮', 64: '抱', 65: '笑', 66: '乖', 67: '心', 68: '都', 69: '起', 70: '她', 71: '办', 72: '道', 73: '傻', 74: '男', 75: '天', 76: '敢', 77: '想', 78: '和', 79: '气', 80: '行', 81: '嘛', 82: '个', 83: '明', 84: '陈', 85: '舒', 86: '雅', 87: '才', 88: '家', 89: '吃', 90: '拜', 91: '讲', 92: '福', 93: '对', 94: '哈', 95: '知', 96: '逼', 97: '底', 98: '黄', 99: '富', 100: '过', 101: '伤', 102: '为', 103: '第', 104: '猪', 105: '证', 106: '智', 107: '吼', 108: '重', 109: '新', 110: '太', 111: '爽', 112: '睡', 113: '着', 114: '下', 115: '然', 116: '后', 117: '弟', 118: '未', 119: '幸', 120: '无', 121: '聊', 122: '脸', 123: '毛', 124: '萌', 125: '打', 126: '只', 127: '样', 128: '算', 129: '里', 130: '贱', 131: '逗', 132: '双', 133: '学', 134: '安', 135: '白', 136: '复', 137: '骗', 138: '以', 139: '用', 140: '答', 141: '失', 142: '望', 143: '开', 144: '点', 145: '切', 146: '短', 147: '信', 148: '假', 149: '尼', 150: '玛', 151: '鸭', 152: '妈', 153: '骂', 154: '句', 155: '屌', 156: '丝', 157: '高', 158: '帅', 159: '今', 160: '日', 161: '事', 162: '王', 163: '盖', 164: '地', 165: '虎', 166: '监', 167: '考', 168: '哼', 169: '玩', 170: '坏', 171: '恩', 172: '也', 173: '啦', 174: '谈', 175: '恋', 176: '让', 177: '问', 178: '性', 179: '别', 180: '受', 181: '情', 182: '同', 183: '时', 184: '两', 185: '错', 186: '英', 187: '语', 188: '结', 189: '婚', 190: '设', 191: '计', 192: '嫁', 193: '乎', 194: '啊', 195: '娶', 196: '难', 197: '加', 198: '又', 199: '位', 200: '懒', 201: '侬', 202: '困', 203: '变', 204: '身', 205: '属', 206: '吐', 207: '槽', 208: '排', 209: '泄', 210: '臭', 211: '洗', 212: '澡', 213: '赵', 214: '他', 215: '继', 216: '续', 217: '必', 218: '须', 219: '管', 220: '维', 221: '方', 222: '正', 223: '杀', 224: '床', 225: '清', 226: '处', 227: '座', 228: '咱', 229: '觉', 230: '头', 231: '次', 232: '紧', 233: '张', 234: '淫', 235: '荡', 236: '走', 237: '厚', 238: '站', 239: '相', 240: '艳', 241: '浅', 242: '噢', 243: '追', 244: '原', 245: '谅', 246: '全', 247: '把', 248: '退', 249: '炖', 250: '老', 251: '呢', 252: '单', 253: '睛', 254: '上', 255: '幼', 256: '稚', 257: '晚', 258: '早', 259: '饭', 260: '超', 261: '级', 262: '最', 263: '快', 264: '歉', 265: '美', 266: '素', 267: '自', 268: '动', 269: '可', 270: '法', 271: '因', 272: '符', 273: '合', 274: '逻', 275: '辑', 276: '神', 277: '马', 278: '边', 279: '唱', 280: '首', 281: '歌', 282: '听', 283: '象', 284: '敷', 285: '衍', 286: '够', 287: '慰', 288: '麽', 289: '科', 290: '告', 291: '诉', 292: '们', 293: '先', 294: '再', 295: '所', 296: '取', 297: '经', 298: '等', 299: '于', 300: '反', 301: '弹', 302: '唉', 303: '烦', 304: '滚', 305: '很', 306: '显'}
voc: [[  1   6  87 ...   0   0   0]
 [  1  30   6 ...   0   0   0]
 [  1   3  31 ...   0   0   0]
 ...
 [  1   4 117 ...   0   0   0]
 [  1  21  43 ...   0   0   0]
 [  1 492  16 ...   0   0   0]]
tokenize: {1: '<start>', 2: '<end>', 3: '我', 4: '你', 5: '的', 6: '是', 7: '不', 8: '了', 9: '人', 10: '在', 11: '啊', 12: '大', 13: '有', 14: '么', 15: '一', 16: '爱', 17: '要', 18: '~', 19: '没', 20: '主', 21: '好', 22: '就', 23: '呵', 24: '想', 25: '妹', 26: '呢', 27: '妈', 28: '道', 29: '知', 30: '那', 31: '很', 32: '小', 33: '吧', 34: '错', 35: '猜', 36: '嗯', 37: '会', 38: '还', 39: '女', 40: '心', 41: '家', 42: '来', 43: '呀', 44: '什', 45: '也', 46: '个', 47: '无', 48: '最', 49: '哈', 50: '别', 51: '他', 52: '告', 53: '嘛', 54: '然', 55: '姐', 56: '敢', 57: '能', 58: '得', 59: '才', 60: '都', 61: '傻', 62: '吃', 63: '真', 64: '深', 65: '给', 66: '呜', 67: '诉', 68: '打', 69: '老', 70: '该', 71: '去', 72: '爷', 73: '生', 74: '怎', 75: '以', 76: '男', 77: '蛋', 78: '笑', 79: '后', 80: '虫', 81: '鸡', 82: '开', 83: '忙', 84: '太', 85: '萌', 86: '谢', 87: '王', 88: '难', 89: '过', 90: '她', 91: '发', 92: '信', 93: '相', 94: '样', 95: '现', 96: '可', 97: '公', 98: '只', 99: '聊', 100: '上', 101: '自', 102: '让', 103: '怪', 104: '受', 105: '干', 106: '因', 107: '为', 108: '子', 109: '当', 110: '逼', 111: '问', 112: '多', 113: '说', 114: '这', 115: '起', 116: '本', 117: '们', 118: '到', 119: '看', 120: '点', 121: '花', 122: '毛', 123: '美', 124: '抱', 125: '通', 126: '先', 127: '若', 128: '安', 129: '电', 130: '定', 131: '帅', 132: '哥', 133: '粉', 134: '婆', 135: '凶', 136: '应', 137: '疼', 138: '果', 139: '跟', 140: '叫', 141: '回', 142: '答', 143: '妖', 144: '臣', 145: '边', 146: 'a', 147: '悦', 148: '找', 149: '玩', 150: '气', 151: '误', 152: '己', 153: '乐', 154: '丫', 155: '谈', 156: '哎', 157: '如', 158: '害', 159: '=', 160: '情', 161: '吵', 162: '但', 163: '幸', 164: '福', 165: '苦', 166: '变', 167: '态', 168: '哦', 169: '噢', 170: '放', 171: '明', 172: '思', 173: '又', 174: '愣', 175: '啦', 176: '手', 177: '刚', 178: '带', 179: '下', 180: '觉', 181: '做', 182: '轮', 183: '对', 184: '亲', 185: '继', 186: '续', 187: '哪', 188: '改', 189: '肿', 190: '茂', 191: '原', 192: '谅', 193: '沉', 194: '近', 195: '吖', 196: '法', 197: '学', 198: '水', 199: '清', 200: '习', 201: '头', 202: '弟', 203: '皮', 204: '桃', 205: '方', 206: '笨', 207: '追', 208: '眼', 209: '见', 210: '乖', 211: '饭', 212: '丝', 213: '经', 214: '欣', 215: '猫', 216: '慰', 217: '帮', 218: '短', 219: '话', 220: '肯', 221: '阮', 222: '德', 223: '培', 224: '吴', 225: '院', 226: '四', 227: '班', 228: '三', 229: '鹿', 230: '奶', 231: '假', 232: '卖', 233: '被', 234: '善', 235: '良', 236: '穆', 237: '森', 238: '奇', 239: '葩', 240: '概', 241: '许', 242: '叶', 243: '祺', 244: '澜', 245: 'v', 246: ')', 247: '娘', 248: '世', 249: '界', 250: '温', 251: '柔', 252: '滚', 253: '够', 254: '虚', 255: '荣', 256: '弱', 257: '爆', 258: '尤', 259: '其', 260: '冻', 261: '更', 262: '憨', 263: '杨', 264: '旸', 265: '乔', 266: '十', 267: '七', 268: '日', 269: '板', 270: '敷', 271: '衍', 272: '教', 273: '宝', 274: '塔', 275: '镇', 276: '河', 277: 'd', 278: 'o', 279: 't', 280: '拿', 281: '惩', 282: '罚', 283: '时', 284: '候', 285: '谁', 286: '偷', 287: '着', 288: '绝', 289: '逞', 290: '再', 291: '提', 292: '伤', 293: '之', 294: '流', 295: '泪', 296: '和', 297: '歉', 298: '万', 299: '疆', 300: 'w', 301: '蒋', 302: '六', 303: '〜', 304: '喽', 305: '渣', 306: '减', 307: '肥', 308: '实', 309: '骗', 310: '混', 311: '叉', 312: '掉', 313: '牙', 314: '俩', 315: '虽', 316: '矛', 317: '盾', 318: '争', 319: '却', 320: '某', 321: '程', 322: '序', 323: '猿', 324: '切', 325: '糕', 326: '吗', 327: '请', 328: '中', 329: '战', 330: '斗', 331: '机', 332: '耶', 333: '几', 334: '分', 335: '轻', 336: '松', 337: '丰', 338: '富', 339: '忘', 340: '今', 341: '夜', 342: '陈', 343: '舒', 344: '雅', 345: '懂', 346: '正', 347: '直', 348: '懒', 349: '网', 350: '络', 351: '歌', 352: '睡', 353: '醒', 354: '退', 355: '吼', 356: '讨', 357: '厌', 358: '完', 359: 's', 360: 'p', 361: '骂', 362: '桑', 363: '徐', 364: '磊', 365: '早', 366: '死', 367: '嫩', 368: '欺', 369: '负', 370: '耐', 371: '滴', 372: '拍', 373: '两', 374: '散', 375: '维', 376: '炖', 377: '办', 378: '此', 379: '宠', 380: '惊', 381: '咯', 382: '意', 383: '房', 384: '屋', 385: '建', 386: '筑', 387: '轩', 388: '辕', 389: '剑', 390: '巨', 391: '蟹', 392: '座', 393: '馒', 394: '蘸', 395: '油', 396: '次', 397: '等', 398: '淫', 399: '荡', 400: '包', 401: '强', 402: '张', 403: '伟', 404: '杰', 405: '晒', 406: '阳', 407: '静', 408: '走', 409: '勇', 410: '留', 411: '盛', 412: '地', 413: '韩', 414: '诗', 415: '琪', 416: '渴', 417: '望', 418: '运', 419: '案', 420: '快', 421: '抠', 422: '脚', 423: '汉', 424: '咪', 425: '脱', 426: '羞', 427: '贝', 428: '芬', 429: '未', 430: '久', 431: '喜', 432: '欢', 433: '聂', 434: '永', 435: '健', 436: '架', 437: '单', 438: '辛', 439: '白', 440: '痴', 441: '脸', 442: '睛', 443: '幼', 444: '稚', 445: '园', 446: '必', 447: '须', 448: '哼', 449: '味', 450: '儿', 451: '啃', 452: '屌', 453: '赵', 454: '竹', 455: '芸', 456: '行', 457: '寨', 458: '欠', 459: '广', 460: '费', 461: '证', 462: '逻', 463: '辑', 464: '倒', 465: '猪', 466: '鼻', 467: '需', 468: '奈', 469: '惯', 470: '贫', 471: '僧', 472: '东', 473: '土', 474: '唐', 475: '西', 476: '天', 477: '求', 478: '取', 479: '根', 480: '李', 481: '梦', 482: '莹', 483: '迪', 484: '段', 485: '峰', 486: '红', 487: '斑', 488: '狗', 489: '金', 490: '类', 491: '哭', 492: '敬', 493: '夸', 494: '奖'}
1--><start>
8-->呵
8-->呵
2--><end>
buffer size: 207
2021-10-19 02:08:22.308579: I tensorflow/core/platform/cpu_feature_guard.cc:145] This TensorFlow binary is optimized with Intel(R) MKL-DNN to use the following CPU instructions in performance critical operations:  SSE4.1 SSE4.2 AVX AVX2 FMA
To enable them in non-MKL-DNN operations, rebuild TensorFlow with the appropriate compiler flags.
2021-10-19 02:08:22.309908: I tensorflow/core/common_runtime/process_util.cc:115] Creating new thread pool with default inter op setting: 16. Tune using inter_op_parallelism_threads for best performance.
2021-10-19 02:08:22.380660: I tensorflow/core/profiler/lib/profiler_session.cc:184] Profiler session started.
2021-10-19 02:08:22.877302: I tensorflow/core/profiler/lib/profiler_session.cc:184] Profiler session started.
2021-10-19 02:08:22.948926: I tensorflow/core/profiler/lib/profiler_session.cc:184] Profiler session started.
====机器人1号为您服务====
用户:

CNN文本分类

 

展开阅读全文
打赏
1
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
1
分享
返回顶部
顶部