Keras深度学习实践2—二分类、多分类和标量回归问题简单实现
内容参考以及代码整理自“深度学习四大名“著之一《Python深度学习》
一、简介
这次我们会基于keras来实现深度学习的三类常见问题的简单实现,他们是:
- 二分类问题:将电影评论划分为正面和负面
- 多分类问题: 将新闻按主题分类
- 回归问题: 根据房地产数据估算房屋价格
二、电影评论分类:二分类问题
本节使用IMDB数据集,它包含来自互联网电影数据库(IMDB)的50000条严重两级分化的评论。数据集被分为用于训练的25000条评论与用于测试的25000条评论,训练集合测试集都包含%50的正面评论和50%的负面评论。
1.数据加载
IMDB数据集也内置于Keras库。它已经经过预处理:评论(单词序列)已经被转换为正数序列,其中的整数代表某个单词。所以可以用以下代码获取数据集:
from keras.datasets import imdb
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)
但是由于数据集需要从国外网站下载,所以我采用了本地加载的方式,其中load_local方法是从keras源码中拆分出来,用于处理下载数据的过程,详细可以去github看完整实现
"""
@加载数据集, num_words=10000是仅保留训练数据中前10000个最常出现的单词
本应该使用: (train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)
但因为下载地址是国外网站经常断线,所以自己下载数据文件后,用自己的load_local方式加载数据
"""
(train_data, train_labels), (test_data, test_labels) = load_local(num_words=10000)
2.准备数据
整数序列不能直接输入到神经网络,需要将列表转换为张量。转换方法有一下两种。
- 填充列表,使其具有相同的长度,再将列表转换成形状为(samples, word_indices)的整数张量,然后网络一层适用能处理这种整数张量的层(即Embedding层)
- 对列表进行one-hot编码,将其转换为0和1组成的向量。举个例子,序列[3,5]将会被转换为10000维向量,只有索引为3和5的元素是1,其余元素都是0.然后网络第一层可以用Dense层,它能够处理浮点数向量数据。
这里我们选择one-hot将序列编码为二进制矩阵, 我们使用下面方法:
def vectorize_sequences(squences, dimension=10000):
"""
@函数功能:将序列向量化,初始化全0的序列,在单词索引对应的位置上置1
"""
resluts = np.zeros((len(squences), dimension))
for i, sequence in enumerate(squences):
resluts[i, sequence] = 1
return resluts
x_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)
然后我们需要将标签进行向量化:
y_train = np.asarray(train_labels).astype('float32')
y_test = np.asarray(test_labels).astype('float32')
数据准备完成,现在可以输入到神经网络中。
3.构建网络
对于这个二分类问题,输入的数据是向量,而标签是标量1和0。有一类网络在这种问题上表现的很好,就是带有relu激活的全连接层(Dense)的简单堆叠。比如,Dense(16, activation='relu')。这是一个具有16个隐藏单元的全连接层。
16个隐藏单元对应的权重矩阵W的形状为(input_dimension, 16), 与W做点积相当于将输入数据投影到16位表示统建中。你可以将空间的维度直观理解为“网络学习内部表示时所拥有的自由度”。隐藏单元越多(即更高维的表示空间),网络能学到更复杂的表示,但网络的计算代价也会变得更大,而且可能会导致学到不好的模式(这种模式会提高训练数据上的性能,但不会提高测试数据的性能)。
对于评论分类的问题,我们选择这样的网络:
- 两个中间层,每层都有16个隐藏单元(relu激活);
- 第三层输出一个标量,预测当前评论的情感(sigmoid激活)。
构建网络:
""" 构建神经网络
有两个中间层,每层16个隐藏单元
最后输出层使用sigmoid激活函数
"""
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'))
另外,我们还要选择算是损失函数和优化器。二分类问题最好选择是binary_crossentropy(二元交叉熵)损失。也可以选择均方误差。
""" 编译网络
因为是二分类问题,所以使用二元交叉熵函数作为损失函数,评价标准还是准确性
"""
model.compile(optimizer=optimizers.RMSprop(lr=0.001), loss=losses.binary_crossentropy, metrics=[metrics.binary_accuracy])
3.验证网络
首先我们要留出验证集:
"""留出一部分验证集"""
x_val = x_train[:10000]
partial_x_train = x_train[10000:]
然后训练模型:
"""训练网络"""
history = model.fit(partial_x_train, partial_y_train, epochs=20, batch_size=512, validation_data=(x_val, y_val))
为了衡量网络,我们来绘制训练精度和验证精度:
def show_acc(history):
""" 绘制精度曲线 """
plt.clf()
history_dict = history.history
acc = history_dict['binary_accuracy']
val_acc = history_dict['val_binary_accuracy']
epochs = range(1, len(val_acc) + 1)
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.xlabel('Epochs')
plt.ylabel('Acc')
plt.legend()
plt.show()
训练精度和验证损失
三、新闻分类:多分类问题
我们这次将构建一个网络,将路透社新闻划分为46个互斥的主题。这是一个单标签,多分类问题。如果每个数据点可以被划分到多个主题,那么它就是一个多标签、多分类问题。
1.数据集介绍和加载
这次我们使用的是路透社数据集,它包含许多短新闻和对应的主题。每个主题都至少有10个样本。首先我们还是加载数据。依然是使用本地加载的方式。
(train_data, train_labels), (test_data, test_labels) = load_local(num_words=10000)
与IMDB数据集一样,参数num_words=10000将数据限定为10000个最常出现的单词。训练样本数8982,测试样本数2246。每个样本都是一个整数列表:
>>>train_data[10]
[1, 245, 273, 207, 156, 53, 74, 160, 26, 14, 46, 296, 26, 39, 74, 2979,
3554, 14, 46, 4689, 4329, 86, 61, 3499, 4795, 14, 61, 451, 4329, 17, 12]
2.数据准备
首先还是现将数据向量化:
""" 训练数据向量化 """
x_train = vectorize_sequences(train_data)
y_train = vectorize_sequences(test_data)
然后我们要将标签向量化,主要有两种方式:可以将标签列表转换为整数张量,或者使用one-hot编码。keras内置了方法可以实现这个操作:
""" one-hot处理标签 """
one_hot_train_labels = to_categorical(train_labels)
one_hot_test_labels = to_categorical(train_labels)
3.构建网络:
""" 构建神经网络
有两个中间层,每层64个隐藏单元
多分类问题,最后输出层使用softmax激活函数
"""
model = models.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(46, activation='softmax'))
- 网络最后一层是大小为46的全连接层,对于每个样本,网络都会输出一个46维的向量,向量的每个元素代表不同的输出类别。
- 最后一层使用softmax激活。网络将输出一个46维向量,代表每个类别的概率分布,总和为1。
编译模型,选择的损失函数是categorical_crossentroy(分类交叉熵)。
""" 编译模型 """
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])
4.验证网络:
进行网络验证,并画出精度曲线
""" 留出验证集 """
x_val = x_train[:1000]
partial_x_train = x_train[1000:]
y_val = one_hot_train_labels[:1000]
partial_y_train = one_hot_train_labels[1000:]
""" 训练网络 """
history = model.fit(partial_x_train, partial_y_train, epochs=20, batch_size=512, validation_data=(x_val, y_val))
""" 展示结果 """
show_acc(history)
5.在新数据上生成预测结果:
predictions = model.predict(x_test)
predictions中每个元素都是长度为46的向量。
>>> predictions[0].shape
(46,)
这个向量所有元素的总和为1:
>>> np.sum(predictions[0])
1.0
最大元素就是预测类别,即概率最大的类别:
>>> np.argmax(predictions[0])
4
四、预测房价: 回归问题
回归问题预测一个连续值而不是离散的标签,例如,根据气象数据预测明天气温,或者根据软件说明书预测完成软件所需要的时间。
1.数据集介绍
波士顿房价数据集包含当时郊区的一些数据点,比如犯罪率、当地房地产税等。这个数据集的数据点相对较少,只有506个、分为404个训练样本和102个测试样本。输入数据的每个特征(比如犯罪率)都有不同的取值范围。例如,有些特征是比例,取值范围为01;有的取值范围112;还有的取值范围为0~100。每个样本都有13个数值特征,比如人均犯罪率、每个住宅的平均房间数、高速公路可达性等。预测的目标是房屋的中位数。
数据加载:
(train_data, train_targets), (test_data, test_targets) = load_local()
2.准备数据
各个数据特征的取值范围差异很大,这样不利于学习,对于这样的数据,普遍采用最佳实践是对每个特征做标准化,即对于输入数据的每个特征,减去特征平均值,再除以标准差,这样得到的特征平均值为0,标准差为1。
"""数据标准化: 减去平均值,再除以标准差,这样得到数据平均值为0,标准差为1"""
mean = train_data.mean(axis=0)
train_data -= mean
std = train_data.std(axis=0)
train_data /= std
test_data -= mean
test_data /= std
3.构建网络
由于训练样本很少,我们将使用一个非常小的网络,其中包含两个隐藏层,每层有64个单元。一般来说,训练数据越少,过拟合会越严重,而较小的网络可以降低过拟合。
def build_model():
"""
构建网络
这个样本数据量很少,我们将使用一个非常小的网络。训练数据越少,过拟合就会越严重,而较小的网络可以降低过拟合。
"""
model = models.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(train_data.shape[1],)))
model.add(layers.Dense(64, activation='relu'))
"""最后一层只有一个单元,没有激活"""
model.add(layers.Dense(1))
"""损失函数使用的均方误差,评价网络则使用的是平均绝对误差(MAE)"""
model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
return model
网络的最后一层只有一个单元,没有激活,是一个线性层。这是标量回归的典型设置。添加激活函数会限制输出范围。注意:编译网络用的是mse损失函数,即均方误差,预测值与目标值之差的平方。这是回归问题常用的损失函数。
评价网络用的指标是平均绝对误差。它是预测值与目标值之差的绝对值。
4.使用K折验证网络
由于数据点很少,验证集会非常小。验证的分数可能会有很大的波动,这取决与你选择的验证集合训练集。也就是说,验证集的划分方式可能造成验证分数上有很大的误差,这样就无法对模型进行可靠的评估。这就需要使用K折验证,这种方法通常将数据划分为K个分区,实例化K个相同的模型,将每个模型在K-1个分群上训练,并在剩下的一个分区上进行评估。
3折交叉验证"""
这里使用K值验证法,这是对小数据量网络处理的方式
"""
k = 4
num_val_samples = len(train_data) // k
num_epochs = 100
all_mae_histories = []
for i in range(k):
print('processing fold #', i)
val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples]
val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples]
partial_train_data = np.concatenate([train_data[:i * num_val_samples],
train_data[(i + 1) * num_val_samples:]], axis=0)
partial_train_targets = np.concatenate([train_targets[:i * num_val_samples],
train_targets[(i + 1) * num_val_samples:]],
axis=0)
model = build_model()
history = model.fit(partial_train_data, partial_train_targets,
validation_data=(val_data, val_targets),
epochs=num_epochs, batch_size=16, verbose=0)
mae_history = history.history['val_mean_absolute_error']
all_mae_histories.append(mae_history)
然后计算所有轮次K折验证的平均值
"""计算所有伦才中MAE的平均值"""
average_mae_history = [np.mean([x[i] for x in all_mae_histories]) for i in range(num_epochs)]
绘制验证分数图,这里对数据进行了平滑,将每个数据点替换为前面数据点的指数移动平均值。并且舍弃了前10个结果,因为训练初期,训练误差较大,影响我们观察曲线趋势
def smooth_curve(points, factor=0.9):
"""
将每个数据点替换为前面数据点的指数移动平均值,以得到到光滑的曲线
:param points:
:param factor:
:return:
"""
smoothed_points = []
for point in points:
if smoothed_points:
previous = smoothed_points[-1]
smoothed_points.append(previous * factor + point * (1 - factor))
else:
smoothed_points.append(point)
return smoothed_points
def show_mae(average_mae_history):
"""
绘制验证,对数据平滑,并且删除前10个数据点
"""
smooth_mae_history = smooth_curve(average_mae_history[10:])
plt.plot(range(1, len(smooth_mae_history) + 1), smooth_mae_history)
plt.xlabel('Epochs')
plt.ylabel('Validation MAE')
plt.show()
MAR曲线
五、小结
使用Keras解决了机器学习的三个任务:二分类问题、多分类问题、标量回归问题。要点如下:
- 原始数据使用之前,通常需要预处理,使数据适合网络处理
- 随着训练的进行,神经网络终会过拟合,在未见的数据上结果变差
- 训练数据较少的时候,选择较小的网络避免过拟合,采用K折的方式训练。
- 回归问题的损失函数和评价方式和分类问题不同。
查看完整代码,请看: https://github.com/ubwshook/MachineLearning