第一个深度学习实战案例:电影评论分类
开始深度学习的内容,本文是《Python深度学习》一书中的实战案例:电影评论的二分类问题。
训练集和测试集
这是一个典型的二分类问题。使用的是IMDB数据集,训练集是25000条,测试也是25000条
In [1]:
1 | import pandas as pd |
In [2]:
1 | # 10000:仅保留训练数据中前10000个最常见的词语 |
- train_data、test_data:评论组成的列表
- train_labels、test_lables:0-1组成的列表。其中0-负面 1-正面
In [3]:
1 | train_data.shape |
Out[3]:
1 | (25000,) |
In [4]:
1 | type(train_data) |
Out[4]:
1 | numpy.ndarray |
In [5]:
1 |
In [6]:
1 | test_labels[1] # 标签值都是0或者1 |
Out[6]:
1 | 1 |
单词的最大索引不超过10000:
In [7]:
1 | # [max(i) for i in train_data]:每组数据的 |
Out[7]:
1 | 9999 |
数据还原
将数值还原到对应的评论中
In [8]:
1 | # 步骤1:单词和数值组成的字典 |
In [9]:
1 | # 步骤2: key-value翻转 |
准备数据
不能将整数序列直接输入到神经网络,必须先转成张量;提供两种转换方法:
- 填充列表,使其具有相同的长度,再将列表转成(samples,word_indices)的整数张量;再使用Embedding层进行处理(后续介绍)
- 对列表进行one-hot编码,转成0-1组成的向量。网络第一层可以Dense层,处理浮点数向量数据
one-hot编码
In [10]:
1 | # one-hot编码实现 |
In [11]:
1 | x_train[0] |
Out[11]:
1 | array([0., 1., 1., ..., 0., 0., 0.]) |
标签向量化
In [12]:
1 | y_train = np.asarray(train_labels).astype("float32") |
构建神经网络
输入数据是向量,标签是标量(0或者1)。在该类问题上表现好的神经网络:带有relu激活函数的全连接Dense层网络
1 | Dense(16,activation='relu') |
- 16:表示隐藏单元的个数;一个隐藏单元表示空间的一个维度
- 每层都对应一个表示空间,数据经过层层变换,最终映射到解
中间层使用relu函数作为激活函数,使用的主要运算:
1 | output = relu(dot(W,input) + b) |
最后一层使用sigmod激活,输出一个0-1之间的概率值作为样本的目标值等于1的可能性,即正面的可能性
- relu函数:将全部负值归0
- sigmoid函数:将数据压缩到0-1之间
模型定义(修改)
In [13]:
1 | import tensorflow as tf # add |
损失函数和优化器(修改)
这个问题属于二分类问题,网络输出的是一个概率值。
最后一层使用sigmoid函数作为激活函数,最好使用binary_crossentropy(二元交叉熵)作为损失。
温馨提示:对于输出是概率值的模型,最好使用交叉熵crossentropy(用于衡量概率值分布之间的距离)。
下面的优化过程是使用:
- 优化器:rmsprop
- 损失函数:binary_crossentropy
In [14]:
1 | # 编译模型 |
自定义优化器、损失函数、指标函数等:
In [15]:
1 | # 配置优化器 |
In [16]:
1 | # 自定义损失函数和指标 |
验证模型
在原始==训练==数据集中留出10000个样本作为验证集
In [17]:
1 | # 留出验证集 |
分批次进行迭代:
- 使用512个样本组成小批量
- 10000个样本将模型训练20次
同时监控模型在10000个样本上精度和损失
训练模型
In [18]:
1 | model.compile(optimizer="rmsprop", |
调用模型model.fit方法会返回一个history对象。这个对象有一个history成员,它是一个字典,包含训练过程中的所有数据:
In [19]:
1 | history_dict = history.history |
查看不同的key
In [20]:
1 | history_dict.keys() # 训练过程和验证过程的两组指标 |
Out[20]:
1 | dict_keys(['loss', 'acc', 'val_loss', 'val_acc']) |
绘图可视化
In [25]:
1 | # 损失绘图 |
1 | # 精度绘图 |
结论:
- 训练的损失每轮都在降低;训练的精度每轮都在提升(红色)
- 验证集的损失和精度似乎都在第4轮达到最优值
也就是:模型在训练集上表现良好,但是在验证集上表现的不好,这种现象就是过拟合
重新训练模型
通过上面的观察,第四轮的效果是比较好的:
In [23]:
1 | import tensorflow as tf # add |
Out[23]:
1 | [0.30533847212791443, 0.8800399899482727] |
可以看到在这种最简单的方法下,精度居然达到了88.54%! 奈斯~
对测试集进行预测
训练好的网络对测试集进行预测:
In [24]:
1 | model.predict(x_test) |
Out[24]:
1 | array([[0.23233771], |
模型对某些样本的预测结果还是可信的,比如0.999和0.10等,有些效果不理想:出现0.56的概率值,导致无法判断
进一步实验
- 前面的案例使用的是两个隐藏层:可以尝试使用1个或者3个
- 尝试使用更多或更少的隐藏单元,比如32或者64个
- 尝试使用mse损失函数代替binary_crossentropy
- 尝试使用tanh函数(早期流行的激活函数)代替relu激活函数
小结
- 对原始数据进行大量地预处理工作
- 带有relu激活函数的Dense堆叠层,可以解决多种问题(包含情感分类)
- 对于二分类问题:
- 网络的最后一层使用带有sigmoid激活的Dense层,输出是0-1之间的概率值;
- 同时建议使用binary_crossentropy作为损失函数
- 优化器的最佳选择:rmsprop
- 过拟合现象是常见的,因此一定要监控模型在训练数据集之外的数据集上的性能