影评分类 深度学习

2018-11-09 21:50:25 qq_43452804 阅读数 785

原本打算一直在博客园上写博客,但是还是觉得多方开源比较好。于是csdn也上传一篇(毕竟csdn确实比博客园名气要大)。
我觉得把课本上的案例先自己抄一遍,然后将书看一遍。最后再写一篇博客记录自己所学过程的感悟。虽然与课本有很多相似之处。但自己写一遍感悟会更深

本节使用的是IMDB数据集,使用Jupyter作为编译器。这是我刚开始使用Jupyter,不得不说它的自动补全真的不咋地(以前一直用pyCharm)但是看在能够分块运行代码的份上,忍了。用pyCharm敲代码确实很爽,但是调试不好调试(可能我没怎么用心学),而且如果你完全不懂代码含义的话,就算你运行成功也不知道其中的含义,代码有点白敲的感觉,如果中途出现错误,有的时候很不好找(这是我在pyCharm上敲github上代码的领悟,有时候一敲两三百行,一运行报错,错误就不好找)。但是Jupyter就好一点,你可以使用多个cell,建议如果不打印一些东西,cell还是少一点,不然联想功能特别弱,敲代码特别难受。

1. 加载IMDB数据集
仅保留前10000个最常出现的单词,低频单词被舍弃

from keras.datasets import imdb
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)
train_data[0]
train_labels[0]

单词索引不会超过10000

max([max(sequence) for sequence in train_data])

9999
下面这段代码:将某条评论迅速解码为英文单词

word_index = imdb.get_word_index()
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
decoded_review = ' '.join([reverse_word_index.get(i - 3, '?') for i in train_data[0]])

2. 将整数序列编码为二进制矩阵

import numpy as np

def vectorize_sequences(sequences, dimension=10000):
    results = np.zeros((len(sequences), dimension))
    for i, sequence in enumerate(sequences):
        results[i, sequence] = 1
    return results

x_train = vectorize_sequences(train_data)

x_test = vectorize_sequences(test_data)
x_train[0]

array([ 0., 1., 1., …, 0., 0., 0.])
标签向量化

y_train = np.asarray(train_labels).astype('float32')
y_test = np.asarray(test_labels).astype('float32')

3. 模型定义

from keras import models
from keras import layers

model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000, )))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

4. 编译模型

model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['accuracy'])

5. 配置优化器

from keras import optimizers

model.compile(optimizer=optimizers.RMSprop(lr=0.001), loss='binary_crossentropy', metrics=['accuracy'])

6. 使用自定义的损失和指标

from keras import losses
from keras import metrics

model.compile(optimizer=optimizers.RMSprop(lr=0.001), loss=losses.binary_crossentropy, metrics=[metrics.binary_crossentropy])

7. 留出验证集

x_val = x_train[:10000]
partial_x_train = x_train[10000:]

y_val = y_train[:10000]
partial_y_train = y_train[10000:]

8. 训练模型

model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])

history = model.fit(partial_x_train, partial_y_train, epochs=20, batch_size=512, validation_data=(x_val, y_val))

history_dict = history.history
history_dict.keys()

9. 绘制训练损失和验证损失
这是一个比较固定的模块,以后会用到

import matplotlib.pyplot as plt

history_dict = history.history
loss_values = history_dict['loss']
val_loss_values = history_dict['val_loss']

epochs = range(1, len(loss_values) + 1)
# ’bo‘表示蓝色原点,’b’表示蓝色实线
plt.plot(epochs, loss_values, 'bo', label='Training loss')
plt.plot(epochs, val_loss_values, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()

plt.show()

在这里插入图片描述
损失降得太狠了,不过训练的损失和精度不太重要,它只反应训练集的训练程度。重点是验证精度。
10. 绘制训练精度和验证精度

plt.clf()     # 清除图像
acc = history_dict['acc']
val_acc = history_dict['val_acc']

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()

plt.show()

在这里插入图片描述
可以看到验证的精度并不高,只有86%左右。而训练的精度达到几乎100%,两者精度相差太大,出现了过拟合。为了防止过拟合,可以在3轮之后停止训练。还有很多方法降低过拟合。我们一般看验证精度曲线就是找最高点对应的轮次,然后从头开始训练一个新的模型
11. 从头开始训练一个模型

model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['accuracy'])

model.fit(x_train, y_train, epochs=4, batch_size=512)
results = model.evaluate(x_test, y_test)

最终结果如下

results

[0.28940243008613586, 0.88488]

得到了88%的精度,还有待优化的空间
12. 使用训练好的模型在新数据上生成预测结果

model.predict(x_test)

array([[0.20151292],
[0.9997969 ],
[0.9158534 ],
…,
[0.1382984 ],
[0.0817486 ],
[0.69964325]], dtype=float32)
可见。网络对某些样本的结果是非常确信(大于等于0.99),但对其他结果却不怎么确信
13. 总结

  1. 加载数据集->对数据集进行预处理->模型定义->编译模型->配置优化器->使用自定义的损失和指标->留出验证集->训练模型->绘制图像
  2. 对于二分类问题,网络的最后一层应该是只有一个单元并使用sigmoid激活Dense层,网络输出应该是0~1范围内的标量,表示概率值
  3. 对于二分类问题的sigmoid标量输出,应该使用binary_crossentropy(二元交叉熵)损失函数。
2018-01-19 15:54:18 zwqjoy 阅读数 4545

机器学习(深度学习)通用工作流程

 Deep Learning with Python 4.5节

1. 定义问题并装载数据集(Defining the problem and assembling a dataset)

首先,你必须定义你手头的问题:

  • 输入数据是什么?你希望预测什么?只有在能够获得训练数据的情况下你才能进行预测:举个例子,如果你同时又电影的影评和对应的情感注释,你只能从中学习分类电影影评的情绪。因此,数据可用性是这个阶段的限制因素(除非你有办法雇人帮你收集数据)
  • 你面临什么类型的问题?它是二元分类吗?还是多类分类?标量回归?向量回归?多类多标签分类?或者其他的类型,例如聚类,生成问题或者增强学习?识别问题的类型能够指导你选择模型的构架,损失函数等等

直到你知道你的输入和输出是什么,以及你将使用哪些数据,你才能进入下一个阶段。注意你在这个阶段所做的假设:

  • 你假设你可以根据给定的输入预测输出
  • 你假设你的可用数据有足够的信息用于学习输入与输出之间的关系

当然,这仅仅只是假设,直到你有一个确切的模型,这些假设才能被验证或者被否定。并非所有问题都能解决。只是因为你仅仅收集了一些输入X和目标Y,这并不意味着X包含足够的信息去预测Y。举个例子,如果你试图通过股票的历史价格去预测股票的价格,那么你不可能成果,因为股票的历史价格不包含太多的预测信息。

非平稳问题是一种不可解决的问题,你应该注意此类问题。假设你正在尝试建立一个衣服的推荐引擎,你在某一个月的数据上进行训练(比如说,8月),你希望能够在冬天的开始的时候推送你的推荐。这里有一个很大的问题:人们购买的衣服类型会根据季节的变化而变化。衣服的购买在几个月的时间跨度中是一种非平衡现象。在这种情况下,正确的做法是不断地对过去的数据训练新的模型,或者在问题处于静止的时间范围内收集数据。对于想购买衣服这样的周期性问题,几年内的数据足以捕捉到季节的变化,但是记住要让一年中的时间成为你模型的输入。

请记住,机器学习只能记住训练数据中存在的模式。你只能认识你已经看到过的东西。利用机器学习对过去的数据进行训练,用于预测未来,这样的做法假设未来的行为将于过去类似。但是,通常并非如此。

2. 选择成功的衡量指标(Choosing a measure of success)

要控制某些东西,你需要能够观察到它。为了取得成功,你必须定义成功是什么,是正确率?精度或者召回率?还是客户保留率?你的成功指标的定义将会指导你选择损失函数,损失函数就是你模型将要优化的内容。损失函数应该能够直接与你的目标保持一致,例如你业务的成功。
对于均衡分类问题,这类问题中每个类别都有相同的可能性,准确率和ROC AUC是常用的指标。对于类不平衡问题,你可以用精度和召回。对于排名问题或者多标签问题,你可以用平均精度。定义你自己的评价指标并不罕见。要了解机器学习成功指标的多样性以及它们是如何关系不同的问题域,有必要去了解Kaggle上的数据科学竞赛,这些竞赛展示了广泛的问题和评价指标。

3. 决定一个验证策略(Deciding on an evaluation protocol)

一旦你知道你的目标是什么,你必须确定你将如何衡量你当前的进度。我们之前已经了解了三种常用的验证策略:

  • 保留一个hold-out验证集,当你有足够多的数据时,用这种方法
  • K-fold 交叉验证。数据太少,不足以使用第一种验证方法的使用,用这种方法。
  • 迭代 K-fold 交叉验证。只有很少的数据可用时,用于执行高度准确的模型评估。

选择其中一个。在大多数情况下,第一种方法工作得很好。

4. 准备你的数据(Preparing your data)

一旦你知道你在训练什么,你正在优化什么,如何评估你的方法,你几乎已经准备好开始训练模型。但是首先,你应该将数据格式化为机器学习模型所能接受的形式。这里,我们假设这个模型是一个深度学习模型,那么:

  • 正如前面提到的那样,你的数据应该格式化为张量
  • 通常情况下,这些张量的值被缩小为较小的值,比如说缩放到[-1,1]或者[0,1]
  • 如果不同的特征采取不同范围的值,那么数据应该做归一化处理
  • 你可能想做一些特征工作,特别是对于数据集不大的问题

5. 开发一个比基线好的模型(Developing a model that does better than a baseline)

在这个阶段,你的目标是做到statistical power(不会翻译),也就是开发一个能够击败基线的模型。在MNIST数字分类示例中,任何达到大于0.1精度都可以说是具有statistical power; 在IMDB的例子中,大于0.5就可以了。

请注意,达到statistical power并不总是可能的。如果在尝试了多个合理的体系构架之后,仍然无法打败一个随机基线,那么可能是你要求的问题的答案无法从输入数据中获得。记住你提出的两个假设:

  • 你假设你可以根据给定的输入预测输出
  • 你假设你的可用数据有足够的信息用于学习输入与输出之间的关系

这些假设有可能是错误的,在这种情况下你必须重新开始。
假设目前为止一切都很顺利,你需要作出三个关键的选择来建立你的第一个工作模型:

  • 最后一层的激活函数,这为网络的输入设定了限制。例如,在IMDB分类问题中,最后一层使用了sigmoid; 在回归问题中,最后一层没有使用任何激活函数
  • 损失函数,这应该与你正在尝试解决的问题的类型相匹配。例如在IMBD二元分类问题中,使用了binary_crossentropy,回归问题中使用了mse等等。
  • 优化配置,你将使用什么优化器?学习率是多少?在大多数情况在,使用rmsprop和默认的学习率是安全的。

关于损失函数的选择,请注意,并不总是可以直接优化metric。有时候,没有简单的方法可以将metric转换为损失函数;损失函数毕竟只需要一个小批量的数据就能计算(理想情况下,损失函数只需要一个数据就能计算),并且损失函数必须是可微分的(否则,你不能使用反向传播来训练你的网络)。例如,广泛使用的分类度量ROC AUC就不能直接优化。因此,在分类问题中,通常针对ROC AUC的代理指标(例如,交叉熵)进行优化,一般来说,你希望如果越低的交叉熵,你就能获得更高的ROC AUC。
下面的表格可以帮助为几种常见的问题选择最后一层激活函数和损失函数

问题类型 最后一层激活函数 损失函数
二元分类 sigmoid binary_crossentropy
多类别,单标签分类 softmax categorical_crossentropy
多类别,多标签分类 sigmoid binary_crossentropy
任意值的回归问题 None mse
[0,1]之间的回归问题 sigmoid mse 或者 binary_crossentropy

6. 全面升级:开发一个过拟合的模型

一旦你的模型达到了statistical power,那么问题就变成了:你的模型是否足够强大?你是否有足够多的网络层和参数来正确建模你的问题?例如,具有两个神经元的单层网络在MNIST具有statistical power,但是不能很好的解决MNIST分类问题。
请记住,机器学习中最困难的就是在优化和泛华之间取得平衡;理想的模型就是站在欠拟合与过拟合之间。要弄清楚这个边界在哪里,你必须先穿过它。
要弄清楚你需要多大的模型,你必须先开发一个过拟合的模型。这很容易:

  1. 增加网络层
  2. 让网络层变大
  3. 训练更多次

始终监视着训练误差和验证误差,以及你所关心的metrics。当你看到模型在验证集上性能开始下降,就达到了过拟合。下个阶段是开始正则化和调整模型,尽可能的接近既不是欠拟合又不是过拟合的理想模型。

7. 正则化你的模型并调整你的超参数(Regularizing your model and tuning your hyperparameters)

这一步将花费大量时间,你将重复修改你的模型,并对其进行训练,在验证集上进行评估,再次修改,如此重复,知道模型达到所能达到的最佳效果。以下是你应该尝试做的一些事情:

  • 添加Dropout
  • 尝试不同的体系结构的网络:添加或者删除网络层
  • 添加 L1/L2 正则化
  • 尝试不同的超参数(例如每一层的神经元个数或者优化器学习率),以获得最佳的参数选择
  • (可选)迭代特征工程:添加新特征,或者删除似乎没有提供信息的特征

请注意以下几点:每次使用验证集来调整模型参数时,都会将有关验证的信息泄露在模型中。重复几次是无害的;但是如果重复了很多很多次,那么最终会导致你的模型在验证集上过拟合(即使没有直接在验证集上进行训练),这使得验证过程不太可靠。

一旦你开发出令人满意的模型,你可以根据所有可用的数据(训练集和验证集)来训练你最终的模型。如果测试集的结果明显低于验证集上结果,那么可能意味着你的验证过程不太可靠,或者你的模型在验证集中已经过拟合了。在这种情况下,你可能需要更为靠谱的验证策略(例如迭代K-fold验证)

2019-05-12 21:31:05 qq_35164554 阅读数 740

本文中我希望用IMDB数据集和神经网络对数据集中的影评内容进行“正面影评”和“负面影评”的二分类。

IMDB

IMDB数据集是Tensorflow中带有的数据集,其中包含来自互联网电影库的50000条影评文本,首先来下载该数据集并且查看一下:

加载数据(如果缓存中没有回自动下载该数据):

import tensorflow as tf
from tensorflow import keras
import numpy as np

imdb = keras.datasets.imdb
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)

查看数据:

print("Trraining entries:{},labels:{}".format(len(train_data),len(train_labels)))

输出中可以看到我们训练集的样本量为25000

查看数据中的其中一个样本是什么:

print(train_data[0])

可以看书虽然是影评,在我们的数据集中译英转换为了数值类型而不是字符型,其中每一个数字都代表一个词语,后续会用其他的方法将数值型转换为字符型。

我们知道在神经网络的输入中必须要有相同的长度,所以我们先查看几条数据的长度是不是相同的:

print(len(train_data[0]),len(train_data[10]),len(train_data[100]))

由这个输出结果可以看出,每条影评的长度是不相同的所以我们要想办法处理这个问题,在这里我的办法设置一个最长值得限制,并将短的数据用零来填充:

train_data = keras.preprocessing.sequence.pad_sequences(train_data,
                                                        value=0,
                                                        padding='post',
                                                        maxlen=256)

test_data = keras.preprocessing.sequence.pad_sequences(test_data,
                                                       value=0,
                                                       padding='post',
                                                       maxlen=256)
print(len(train_data[0]),len(train_data[10]),len(train_data[100]))

输出结果如下,此时我们可以看出每一条数据的长度都是相同的:

keras.preprocessing.sequence.pad_sequences(sequences, maxlen=None, dtype=’int32’, padding=’pre’, truncating=’pre’, value=0.) 

函数说明: 
将长为nb_samples的序列(标量序列)转化为形如(nb_samples,nb_timesteps)2D numpy array。如果提供了参数maxlen,nb_timesteps=maxlen,否则其值为最长序列的长度。其他短于该长度的序列都会在后部填充0以达到该长度。长于nb_timesteps的序列将会被截断,以使其匹配目标长度。padding和截断发生的位置分别取决于padding和truncating. 
参数:

sequences:浮点数或整数构成的两层嵌套列表

maxlen:None或整数,为序列的最大长度。大于此长度的序列将被截短,小于此长度的序列将在后部填0.

dtype:返回的numpy array的数据类型

padding:‘pre’或‘post’,确定当需要补0时,在序列的起始还是结尾补

truncating:‘pre’或‘post’,确定当需要截断序列时,从起始还是结尾截断

value:浮点数,此值将在填充时代替默认的填充值0


构建模型

神经网络通过层数的堆叠创建成,下面先说一下每一层的作用。

示例中,输入数据由字词-索引数组构成,要预测的标签是0或1(正面影评和负面影评)

第一层:Embedding层,该层会在整数编码的词汇表中查找每个字词-索引的嵌入向量,模型在接受训练时会学习这些向量,这些向量会向输出数组中添加一个维度,添加后的维度是(batch,sequence,embedding);

第二层:一个 GlobalAveragePooling1D 层通过对序列维度求平均值,针对每个样本返回一个长度固定的输出向量。这样,模型便能够以尽可能简单的方式处理各种长度的输入。

第三层:该长度固定的输出向量会传入一个全连接 (Dense) 层(包含 16 个隐藏单元)。

第四层:最后一层与单个输出节点密集连接。应用 sigmoid 激活函数后,结果是介于 0 到 1 之间的浮点值,表示概率或置信水平。

vocab_size = 10000

model = keras.Sequential()
model.add(keras.layers.Embedding(vocab_size, 16))
model.add(keras.layers.GlobalAveragePooling1D())
model.add(keras.layers.Dense(16, activation=tf.nn.relu))
model.add(keras.layers.Dense(1, activation=tf.nn.sigmoid))

model.summary()

输出如下:

损失函数和优化器

构建完了神经网络下面我们来定义一个损失函数和优化器

model.compile(optimizer=tf.train.AdamOptimizer(),
              loss='binary_crossentropy',
              metrics=['accuracy'])

创建验证集

创建数据集的目的是为了检测模型处理未见过的数据时的准确率,我们取总数据的20%也就是10000条数据来创建验证集。

x_val = train_data[:10000]
partial_x_train = train_data[10000:]

y_val = train_labels[:10000]
partial_y_train = train_labels[10000:]

训练模型并且评估模型

用有 512 个样本的小批次训练模型 40 个周期。这将对 x_train 和 y_train 张量中的所有样本进行 40 次迭代。在训练期间,监控模型在验证集的 10000 个样本上的损失和准确率,最后我们使用evaluate来查看模型的误差和准确率:

history = model.fit(partial_x_train,
                    partial_y_train,
                    epochs=40,
                    batch_size=512,
                    validation_data=(x_val, y_val),
                    verbose=1)
results = model.evaluate(test_data, test_labels)

print(results)

最后的输出结果如下:

由此可见我们的模型准确率为0.87,并不是很完美,如果采用更好的方法可能会对准确率有更大的提高。

 

2020-04-21 01:14:55 a15261893837 阅读数 91

 电影评论分类数据集加载

import tensorflow as tf
import tensorflow.keras as keras
#所有imdb.*使用keras.datasets.imdb.*

(train_data,train_labels),(test_data,test_labels)=keras.datasets.imdb.load_data(num_words=10000)
train_data[0]
word_index = keras.datasets.imdb.get_word_index()

​

​

 

2018-06-30 13:43:01 apengpengpeng 阅读数 2181

【应用场景】
在深度学习中,文本和序列有着很多的应用场景:

  • 文本分类、时间序列分类。eg. 确定一篇文章的主题,确定一本书的作者
  • 时间序列的相互比较。eg. 文本相似度,股票行情预测
  • 语言序列的学习。eg. 英译汉,汉译英,翻译系统
  • 情感分析。eg. 一条微博携带的情感色彩,电影评论好与坏
  • 时间序列预测。eg. 在一个确定地点预测未来的天气,给出最近的天气

【用文本数据工作】
在深度学习的模型,并不会将原始的文本数据直接送进神经网络中,会将文本装换成数值张量,向量化是其中的一种方式。有很多种不同的方式:

  • 将text分割成word, 将每个word装换成vector;
  • 将text分割成character,将每个character装换成vector;
  • 提取word和character的n-gram,并将每个n-gram转换成一个vector。

解释一下几个在文本处理中常用的几个名词:

  • token:指的是将文本分割成word、character、n-gram,其中word、character、n-gram均可称为是token
  • tokenization:将文本转化成token的过程;
  • n-grams:从一句话中抽出N个连续词组成的集合。举个例子:“The cat sat on the mat.”;
    那么2-grams:{“The”, “The cat”, “cat”, “cat sat”, “sat”, “sat on”, “on”, “on the”, “the”, “the mat”, “mat”}
    ,同样,3-grams:{“The”, “The cat”, “cat”, “cat sat”, “The cat sat”, “sat”, “sat on”, “on”, “cat sat on”, “on the”, “the”, “sat on the”, “the mat”, “mat”, “on the mat”};
  • bag-of-words:指的是无序的词库集合,也就是经过tokenization之后产生的集合。

Tips:

  • 在提取n-grams时,其实这个过程就像是在提取一句话的特征,那么在深度学习中,就是用一维卷积、RNN等方法去替代n-grams;
  • 虽然现在越来越多的复杂的任务均转移到了深度学习,但是在处理一些轻量级的任务时,不可避免的去使用n-grams,还有一些传统高效的方法,比如:逻辑回归、随机森林。

【token的两种编码方式】

  • one-hot
    one-hot是一种常见的编码方式,通过几个toy example来了解一下

词(word)级别的one-hot编码

import numpy as np

samples = ['The cat sat on the mat.', 'The dog ate my homework.']

# 10
# 定义一个集合,得到{'The': 1, 'cat': 2, 'sat': 3, 'on': 4, 'the': 5, 'mat.': 6, 'dog': 7, 'ate': 8, 'my': 9, 'homework.': 10},也就是筛选出这个句子中对应的了哪些词,然后并赋予索引值,其实就是个词库
token_index = {}
for sample in samples:
    for word in sample.split():
        if word not in token_index:
            token_index[word] = len(token_index) + 1

# 限制了读取的句子的长度,一句话最长10个词
max_length = 10
results = np.zeros(shape=(len(samples),
                          max_length,
                          max(token_index.values()) + 1))

# print(results) 2, 10, 11
for i, sample in enumerate(samples):
    for j, word in list(enumerate(sample.split()))[:max_length]:
        index = token_index.get(word)
        results[i, j, index] = 1.
print(results)

The cat sat on the mat.
The dog ate my homework.

字符(character)级别的one-hot编码

import numpy as np
import string
samples = ['The cat sat on the mat.', 'The dog ate my homework.']
# 预先定义一个字符集 '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\\'()*+,-./:;<=>?@[\\]^_`{|}~‘
characters = string.printable
token_index = dict(zip(range(1, len(characters) + 1), characters))

max_length = 50
results = np.zeros((len(samples), max_length, max(token_index.keys()) + 1))
for i, sample in enumerate(samples):
    for j, character in enumerate(sample):
        for key, value in token_index.items():
            if value == character:
                index = key
                results[i, j, index] = 1.


print(results)

截取部分图

用Keras实现基于词(word)级别的one-hot编码,封装的是真的好。


samples = ['The cat sat on the mat.', 'The dog ate my homework.']
tokenizer = Tokenizer(num_words=1000)
tokenizer.fit_on_texts(samples)

sequences = tokenizer.texts_to_sequences(samples)
one_hot_results = tokenizer.texts_to_matrix(samples, mode='binary')

word_index = tokenizer.word_index
print('Found %s unique tokens.' % len(word_index))

还有一种多样化的one-hot编码,称为one-hot散列法。由于hash值是唯一的,因此这种方法应用于由于在词典中的不同token的数量太大而不能明确处理。但是唯一的缺陷就是会产生哈希碰撞,也就是两个不同词可能对应同一个哈希值,然而神经网络并不能说出这两个词之间的差别。解决方案是让哈希空间的维度大小远远大于不同token已有哈希值的数量

samples = ['The cat sat on the mat.', 'The dog ate my homework.']

dimensionality = 1000
max_length = 10

results = np.zeros((len(samples), max_length, dimensionality))
for i, sample in enumerate(samples):
    for j, word in list(enumerate(sample.split()))[:max_length]:
        # 计算hash值
        index = abs(hash(word)) % dimensionality
        results[i, j, index] = 1

print(results)
  • 词嵌入(word-embedding)
    word-embedding也是对文本进行编码的一种方法,相比与one-hot,word-embedding显得更为灵活,我们来对比一下:
    one-hot编码
    one-hot是高维的(词库中有多少个token,就有多少维),稀疏的(大部分均为0),是二分的,硬编码的;
    word-emedding编码
    word-emedding是低维的浮点型张量,能够用更低的维度去叠加更多的信息,全连接的,是从数据中学习而来;word-embedding具体获取的方式有两种:
    1)在神经网络中添加embedding层去学习word-embedding,它是在词向量之间反映出语义关系,将人类自然语言映射到几何空间中。比如,我们希望同义词被嵌入到相似的词向量中。
    toy example
    拿上面的例子简单解释一下,这四个词是嵌入到2D空间中的向量,这几个词中一些语义关系会通过几何代换进行编码,我们发现两组的相似的向量,从cat到tiger,从dog到wolf,这组向量会被解释为“从宠物到野生动物”这种语义关系,同样的,从dog到cat, 从wolf到tiger,这组向量会被解释为“从犬类到猫科动物”。因此我们能更好的去理解word-embedding相比于one-hot具有好的灵活性,每一个词向量所在的空间位置都是有确定的语义关系,我们通过神经网络将这种语义关系学习出来。word-embedding类似于找到一个空间(或多个空间),在每个空间中都包含着许多词(同义词),这些词在这个空间不是孤立,都是有特定语义的。但是因为不同的任务中的embedding-space不同,都是唯一的,正所谓“具体问题具体分析”,所以需要进行重新学习。
    2)另一种方式是利用预训练的word-embedding,尤其是适用于拥有少量的训练数据的情况下,预训练的原理在此不再赘述,原理和做图像分类时利用ImgaeNet中的VGG16结构一样,重利用在复杂问题上学习到的特征应用到自己的任务中,这是一种简单而有效的方法。我们在预训练中采用已有的word-embedding预计算的数据库,例如,word2vec,Glove。
    Notes:在预训练模型我们始终应该注意的是所有预训练的模型在训练的时候参数不应该被更新,若将所有参数进行更新,由于随机初始化隐藏层引发的巨大的梯度更新将会破坏掉已经训练的特征。
    【举个栗子】
    我们利用IMDB电影评论数据做好的评论和不好的评论的分类,并以GLoVE作为预计算的word-embedding空间。
  • 数据集
    IMDB数据集------自行翻墙下载或者下方留言邮箱
    GLoVE数据------百度网盘
  • 模型建立与完整代码
    该模型是添加了embedding层
import os
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import numpy as np
from keras.models import Sequential
from keras.layers import Embedding, Flatten, Dense
import matplotlib.pyplot as plt


# settings
max_len = 100
training_samples = 200
validation_samples = 10000
max_words = 10000
embedding_dim = 100


def process_data():
    '''
    处理IMDB数据,将数据按标签分为pos,neg
    :return: labels,texts
    '''
    imdb_dir = 'D:\\text2sequences\\aclImdb\\aclImdb'
    train_dir = os.path.join(imdb_dir, 'train')

    labels = []
    texts = []

    for label_type in ['pos', 'neg']:
        dir_name = os.path.join(train_dir, label_type)
        for fname in os.listdir(dir_name):
            if fname[-4:] == '.txt':
                f = open(os.path.join(dir_name, fname), 'r', encoding='UTF-8')
                texts.append(f.read())
                f.close()
                if label_type == 'neg':
                    labels.append(0)
                else:
                    labels.append(1)

    return labels, texts


def tokennize_data():
    '''
    将text向量化,切分训练集和验证集
    :return:x_train, y_train, x_val, y_val即训练集和验证集的label和text
    '''

    labels, texts = process_data()
    tokenizer = Tokenizer(num_words=max_words)
    tokenizer.fit_on_texts(texts=texts)
    sequences = tokenizer.texts_to_sequences(texts=texts)
    word_index = tokenizer.word_index
    print('Found %s unique tokens.' % len(word_index))
    data = pad_sequences(sequences, maxlen=max_len)

    labels = np.asarray(labels)
    print('Shape of data tensor:', data.shape)
    print('Shape of label tensor:', labels.shape)
    indices = np.arange(data.shape[0])
    np.random.shuffle(indices)
    data = data[indices]
    labels = labels[indices]

    x_train = data[:training_samples]
    y_train = labels[:training_samples]
    x_val = data[training_samples: training_samples + validation_samples]
    y_val = labels[training_samples: training_samples + validation_samples]

    return x_train, y_train, x_val, y_val, word_index


def parse_word_embedding(word_index):
    '''
    将预计算的词向量空间的word建立索引和矩阵
    :return:
    '''
    glove_dir = 'D:\\text2sequences\\glove.6B'

    embeddings_index = {}
    f = open(os.path.join(glove_dir, 'glove.6B.100d.txt'), 'r', encoding='UTF-8')
    for  line in f:
        values = line.split()
        word = values[0]
        coefs = np.asarray(values[1:], dtype='float32')
        embeddings_index[word] = coefs

    f.close()
    print('Found %s word vectors.' % len(embeddings_index))

    embedding_matrix = np.zeros((max_words, embedding_dim))

    for word, i in word_index.items():
        if i < max_words:
            embedding_vector = embeddings_index.get(word)
            if embedding_vector is not None:
                embedding_matrix[i] = embedding_vector

    return embedding_matrix


def train_model():
    '''
    训练模型
    :return:训练时loss,acc
    '''
    model = Sequential()
    model.add(Embedding(max_words, embedding_dim, input_length=max_len))
    model.add(Flatten())
    model.add(Dense(32, activation='relu'))
    model.add(Dense(1, activation='sigmoid'))
    model.summary()

    # 将GLOVE加载到模型中
    x_train, y_train, x_val, y_val, word_index = tokennize_data()
    embedding_matrix = parse_word_embedding(word_index)
    model.layers[0].set_weights([embedding_matrix])
    model.layers[0].trainable = False

    model.compile(optimizer='rmsprop',
                  loss='binary_crossentropy',
                  metrics=['acc'])

    history = model.fit(x_train, y_train,
                        epochs=10,
                        batch_size=32,
                        validation_data=(x_val, y_val))
    model.save('pre_trained_glove_model.h5')

    return history


def plott_results():
    '''
    作图
    '''
    history = train_model()
    acc = history.history['acc']
    val_acc = history.history['val_acc']
    loss = history.history['loss']
    val_loss = history.history['val_loss']
    epochs = range(1, len(acc) + 1)
    plt.plot(epochs, acc, 'bo', label='Training acc')
    plt.plot(epochs, val_acc, 'b', label='Validation acc')
    plt.title('Training and validation accuracy')
    plt.legend()
    plt.figure()
    plt.plot(epochs, loss, 'bo', label='Training loss')
    plt.plot(epochs, val_loss, 'b', label='Validation loss')
    plt.title('Training and validation loss')
    plt.legend()
    plt.show()


if __name__ == '__main__':
    plott_results()
  • 训练效果与评估分析
    训练效果

评估分析
从上面的训练结果可以看出,使用预训练的embedding层出现了严重的过拟合,验证集的准确率只在0.5左右。因为我们的训练集太少了,模型的性能严重的依赖我们选择的200个样本(并且这200个样本还是随机选取的)。为了解决过拟合这一问题,我们对这个base 版本进行改进,不进行预训练再来看看

  • 版本改进
    我们将embedding层变为可训练的,可以对embedding层的参数进行更新。修改train_model函数为以下:

def train_model():
    '''
    训练模型
    :return:训练时loss,acc
    '''
    model = Sequential()
    model.add(Embedding(max_words, embedding_dim, input_length=max_len))
    model.add(Flatten())
    model.add(Dense(32, activation='relu'))
    model.add(Dense(1, activation='sigmoid'))
    model.summary()

    # 将GLOVE加载到模型中
    x_train, y_train, x_val, y_val, word_index = tokennize_data()
    embedding_matrix = parse_word_embedding(word_index)

    model.compile(optimizer='rmsprop',
                  loss='binary_crossentropy',
                  metrics=['acc'])

    history = model.fit(x_train, y_train,
                        epochs=10,
                        batch_size=32,
                        validation_data=(x_val, y_val))
    model.save('pre_trained_glove_model.h5')

    return history
  • 训练结果和评估分析
    训练效果


    评估分析
    看来,联合学习的embeeding层训练出来的效果不如预训练的embeeding层效果好,这个效果貌似更差。同时我们对模型进行评估,在测试集上得到的准确率只有0.51,可见在小样本上进行训练是有多么的困难。
    loss+acc

【参考文献】

  • 《DEEP LEARNING with Python》
    一本很好的有关深度学习的书籍,能让你构建一个正确的、系统的深度学习的思想和体系,值得推荐~
  • 电子版链接–百度网盘

【代码】

【感谢】

感谢每一个读到这里的朋友,如有疑问,请在下方留言与评论,或者发到我的邮箱,互相学习,互相分享~

  • 邮箱:zhangpeng@webprague.com