政安晨的个人主页:政安晨
欢迎 👍点赞✍评论⭐收藏
收录专栏: 政安晨的机器学习笔记
希望政安晨的博客能够对您有所裨益,如有不足之处,欢迎在评论区提出指正,让小伙伴们一起学习、交流进步!
作者对人工智能领域的机器学习方面的知识,一直是倾向于采用演绎的方式为大家讲解,今后也会继续采用这类方式。
简述
在人工智能机器学习领域中,神经网络是一种模拟人类神经系统的算法模型。它由许多互相连接的人工神经元组成,可以用于分类和回归任务。
神经网络的分类是指将输入数据映射到不同的类别中。在训练过程中,神经网络通过调整连接权重和激活函数的参数,学习如何将输入数据映射到正确的类别。常见的神经网络分类模型包括多层感知机(MLP),卷积神经网络(CNN),循环神经网络(RNN)等。
神经网络的回归是指将输入数据映射到连续的数值或函数。与分类不同,回归任务需要输出连续的数值或函数来拟合数据。在回归任务中,神经网络通过训练来学习数据的非线性模式,并预测出准确的连续数值。常见的神经网络回归模型包括前馈神经网络(Feedforward Neural Network),循环神经网络(RNN),长短期记忆网络(LSTM)等。
总的来说,神经网络在人工智能机器学习中既能用于分类任务,将输入数据映射到不同的类别,也能用于回归任务,将输入数据映射到连续的数值或函数。
通过训练,神经网络可以学习数据的非线性模式,并在未知数据上进行准确的分类或回归预测。
分类和回归术语表
分类和回归都涉及许多专业术语,这些术语在机器学习领域都有确切的定义,你应该熟悉这些定义:
样本(sample)或输入(input):进入模型的数据点。
预测(prediction)或输出(output):模型的输出结果。
目标(target):真实值。对于外部数据源,理想情况下模型应该能够预测出目标。
预测误差(prediction error)或损失值(loss value):模型预测与目标之间的差距。
类别(class):分类问题中可供选择的一组标签。举例来说,对猫狗图片进行分类时,“猫”和“狗”就是两个类别。
标签(label):分类问题中类别标注的具体实例。如果1234号图片被标注为包含类别“狗”,那么“狗”就是1234号图片的标签。
真实值(ground-truth)或标注(annotation):数据集的所有目标,通常由人工收集。
二分类(binary classification):一项分类任务,每个输入样本都应被划分到两个互斥的类别中。
多分类(multiclass classification):一项分类任务,每个输入样本都应被划分到两个以上的类别中,比如手写数字分类。
多标签分类(multilabel classification):一项分类任务,每个输入样本都可以被分配多个标签。举个例子,一张图片中可能既有猫又有狗,那么应该同时被标注“猫”标签和“狗”标签。每张图片的标签个数通常是可变的。
标量回归(scalar regression):目标是一个连续标量值的任务。预测房价就是一个很好的例子,不同的目标价格形成一个连续空间。
向量回归(vector regression):目标是一组连续值(比如一个连续向量)的任务。如果对多个值(比如图像边界框的坐标)进行回归,那就是向量回归。
小批量(mini-batch)或批量(batch):模型同时处理的一小部分样本(样本数通常在8和128之间)。样本数通常取2的幂,这样便于在GPU上分配内存。训练时,小批量用于计算一次梯度下降,以更新模型权重。
影评分类:二分类问题示例
二分类问题是最常见的一类机器学习问题,在本例中,你将学习如何根据影评文本将其划分为正面或负面。
咱们使用某个电影数据库的数据集来进行接下来的演绎。
电影数据集
咱们使用IMDB数据集,它包含来自互联网电影数据库的50 000条严重两极化的评论。
数据集被分为25 000条用于训练的评论与25 000条用于测试的评论,训练集和测试集都包含50%的正面评论与50%的负面评论。
与以前咱们演绎过的MNIST数据集一样,该数据集也已经内置于Keras库中。
它已经过预处理:评论(单词序列)已被转换为整数序列,其中每个整数对应字典中的某个单词。这样一来,我们就可以专注于模型的构建、训练与评估。
咱们准备好环境后,开始接下来的演绎。(Conda虚拟环境、Jupyter Notebook等)
第一次看到这里的小伙伴可以看一下我这个专栏:
政安晨的机器学习笔记http://t.csdnimg.cn/VYj8N
里面有搭建环境的部分。
现在咱们加载数据集,代码如下:
from tensorflow.keras.datasets import imdb
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(
num_words=10000)
演绎:
参数num_words=10000的意思是仅保留训练数据中前10 000个最常出现的单词,低频词将被舍弃。这样一来,我们得到的向量数据不会太大,便于处理。如果没有这个限制,那么我们需要处理训练数据中的88 585个单词。这个数字太大,且没有必要。许多单词只出现在一个样本中,它们对于分类是没有意义的。
train_data和test_data这两个变量都是由评论组成的列表,每条评论又是由单词索引组成的列表(表示单词序列)。train_labels和test_labels都是由0和1组成的列表,其中0代表负面(negative),1代表正面(positive)。
由于限定为前10 000个最常出现的单词,因此单词索引都不会超过10 000。
下面的代码可以让您将一条评论快速解码为英文单词:
# word_index是一个将单词映射为整数索引的字典
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]])
# ←----对评论解码。注意,索引减去了3,因为0、1、2分别是为“padding”(填充)、“start of sequence”(序列开始)、“unknown”(未知词)保留的索引
演绎如下:
准备数据
你不能将整数列表直接传入神经网络。
整数列表的长度各不相同,但神经网络处理的是大小相同的数据批量。你需要将列表转换为张量,转换方法有以下两种:
填充列表,使其长度相等,再将列表转换成形状为(samples, max_length)的整数张量,然后在模型第一层使用能处理这种整数张量的层(也就是Embedding层)。
对列表进行multi-hot编码,将其转换为由0和1组成的向量。
举个例子,将序列[8,5]转换成一个10 000维向量,只有索引8和5对应的元素是1,其余元素都是0。然后,模型第一层可以用Dense层,它能够处理浮点数向量数据。
下面我们采用后一种方法将数据向量化。
为尽可能讲清楚,咱们将手动实现这一方法:
如下代码(用multi-hot编码对整数序列进行编码):
import numpy as np
def vectorize_sequences(sequences, dimension=10000):
# 创建一个形状为(len(sequences), dimension)的零矩阵
results = np.zeros((len(sequences), dimension))
for i, sequence in enumerate(sequences):
for j in sequence:
# 将results[i]某些索引对应的值设为1
results[i, j] = 1.
return results
# 将训练数据向量化
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")
至此,数据可以传入神经网络中了。
构建模型
输入数据是向量,而标签是标量(1和0),这是你会遇到的最简单的一类问题。有一类模型在这种问题上的表现很好,那就是带有relu激活函数的密集连接层(Dense)的简单堆叠。
对于Dense层的这种堆叠,需要做出以下两个关键的架构决策:
神经网络有多少层
每层有多少个单元
架构的选择有很多“讲究”,这个咱们以后细聊,现在您只需要相信我选择的下列架构是合适的即可:
两个中间层,每层16个单元,第三层输出一个标量预测值,代表当前评论的情感类别。
以下是该模型的架构示意图:
模型定义:
from tensorflow import keras
from tensorflow.keras import layers
model = keras.Sequential([
layers.Dense(16, activation="relu"),
layers.Dense(16, activation="relu"),
layers.Dense(1, activation="sigmoid")
])
传入每个Dense层的第一个参数是该层的单元(unit)个数,即该层表示空间的维数。同时,每个带有relu激活函数的Dense层都实现了下列张量运算:
output = relu(dot(input, W) + b)
16个单元对应的权重矩阵W的形状为(input_dimension, 16),与W做点积相当于把输入数据投影到16维表示空间中(然后再加上偏置向量b并应用relu运算)。
你可以将表示空间的维度直观理解为“模型学习内部表示时所拥有的自由度”。
单元越多(表示空间的维度越高),模型就能学到更加复杂的表示,但模型的计算代价也变得更大,并可能导致学到不必要的模式(这种模式可以提高在训练数据上的性能,但不会提高在测试数据上的性能)。
中间层使用relu作为激活函数,最后一层使用sigmoid激活函数,以便输出一个介于0和1之间的概率值(表示样本目标值等于“1”的可能性,即评论为正面的可能性)。
relu函数将所有负值归零(见下图):
sigmoid函数则将任意值“压缩”到[0, 1]区间内(见下图),其输出可以看作概率值:
什么是激活函数?为什么要使用激活函数?
如果没有像relu这样的激活函数(也叫非线性激活函数),Dense层就只包含两个线性运算,即点积与加法:
output = dot(input, W) + b
这样的层只能学习输入数据的线性变换(仿射变换):该层的假设空间是从输入数据到16维空间所有可能的线性变换集合。这种假设空间非常受限,无法利用多个表示层的优势,因为多个线性层堆叠实现的仍是线性运算,增加层数并不会扩展假设空间。
为了得到更丰富的假设空间,从而充分利用多层表示的优势,需要引入非线性,也就是添加激活函数。relu是深度学习中最常用的激活函数,但还有许多其他函数可选,它们都有类似的奇怪名称,比如prelu、elu等。
最后,需要选择损失函数和优化器。
你面对的是一个二分类问题,模型输出是一个概率值(模型最后一层只有一个单元并使用sigmoid激活函数),所以最好使用binary_crossentropy(二元交叉熵)损失函数。这并不是唯一可行的选择,比如你还可以使用mean_squared_error(均方误差)。
但对于输出概率值的模型,交叉熵(crossentropy)通常是最佳选择。交叉熵是一个来自于信息论领域的概念,用于衡量概率分布之间的距离,在这个例子中就是真实分布与预测值之间的距离。
至于优化器,我们将使用rmsprop。对于几乎所有问题,它通常都是很好的默认选择。
接下来的步骤是用rmsprop优化器和binary_crossentropy损失函数来配置模型,另外注意,咱们还在训练过程中监控精度。
编译模型
model.compile(optimizer="rmsprop",
loss="binary_crossentropy",
metrics=["accuracy"])
模型编译完毕:
验证方法
深度学习模型不应该在训练数据上进行评估;
标准做法是使用验证集来监控训练过程中的模型精度。下面我们将从原始训练数据中留出10 000个样本作为验证集,如下代码所示(留出验证集):
x_val = x_train[:10000]
partial_x_train = x_train[10000:]
y_val = y_train[:10000]
partial_y_train = y_train[10000:]
现在我们将使用由512个样本组成的小批量,对模型训练20轮,即对训练数据的所有样本进行20次迭代。与此同时,我们还要监控在留出的10 000个样本上的损失和精度。
可以通过validation_data参数传入验证数据,如下代码所示:
训练模型
history = model.fit(partial_x_train,
partial_y_train,
epochs=20,
batch_size=512,
validation_data=(x_val, y_val))
演绎如下:
如果在CPU上运行,那么每轮的时间不到2秒(政安晨用的是CPU版本的TensorFlow,用了好多秒),整个训练过程将在20秒内完成。
每轮结束时会有短暂的停顿,因为模型需要计算在验证集的10 000个样本上的损失和精度。
注意,调用model.fit()返回了一个History对象。这个对象有一个名为history的成员,它是一个字典,包含训练过程中的全部数据。
咱们现在来看一下:
history_dict = history.history
history_dict.keys()
这个字典包含4个条目,分别对应训练过程和验证过程中监控的指标。
咱们将通过Matplotlib进行绘图展示。
下面代码在同一张图上绘制训练损失和验证损失图:
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"表示“蓝色圆点”
plt.plot(epochs, loss_values, "bo", label="Training loss")
# "b"表示“蓝色实线”
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()
演绎如下(您的演绎有可能和我的不同):
下面代码在同一张图上绘制训练精度和验证精度图:
# 清空图像
plt.clf()
acc = history_dict["accuracy"]
val_acc = history_dict["val_accuracy"]
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()
演绎如下:
如你所见,训练损失每轮都在减小,训练精度每轮都在提高。
这正是梯度下降优化的预期结果——我们想要最小化的量随着每次迭代变得越来越小。
但验证损失和验证精度并非如此,它们似乎在第4轮达到峰值。这正是我之前警告过的一种情况:模型在训练数据上的表现越来越好,但在前所未见的数据上不一定表现得越来越好。准确地说,这种现象叫作过拟合(overfit):在第4轮之后,你对训练数据过度优化,最终学到的表示仅针对于训练数据,无法泛化到训练集之外的数据。
在这种情况下,为防止过拟合,你可以在4轮之后停止训练。一般来说,你可以用多种方法来降低过拟合(咱们以后有机会介绍)
我们从头开始训练一个新模型,训练4轮,然后在测试数据上评估模型:
代码如下:
model = keras.Sequential([
layers.Dense(16, activation="relu"),
layers.Dense(16, activation="relu"),
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)
演绎与结果如下:
数字 1 指向显示的数字为测试损失,数字 2 指向显示的是测试精度。
小伙伴们可以看到,这种相当简单的方法得到了约88%的精度,利用最先进的方法,您应该可以得到接近95%的精度。
利用训练好的模型对新数据进行预测
训练好一个模型之后,最好用于实践,可以用predict方法来计算评论为正面的可能性。
如你所见,模型对某些样本的结果非常确信。
(某些样本 大于等于0.99,或小于等于0.01)
结论
通过上面的演绎,可以确信前面选择的神经网络架构是非常合理的,但可能仍旧有空间。
比如,我们在最后的分类层之前使用了两个表示层,可以尝试使用一个或三个表示层,然后观察这么做对验证精度和测试精度的影响。尝试使用更多或更少的单元,比如32个或64个。尝试使用mse损失函数代替binary_crossentropy。尝试使用tanh激活函数(这种激活函数在神经网络早期非常流行)代替relu。
这样咱们知道:
通常需要对原始数据进行大量预处理,以便将其转换为张量输入到神经网络中。
单词序列可以被编码为二进制向量,但也有其他编码方式。
带有relu激活函数的Dense层堆叠,可以解决很多问题(包括情感分类)。
你可能会经常用到这种模型。对于二分类问题(两个输出类别),模型的最后一层应该是只有一个单元并使用sigmoid激活函数的Dense层,模型输出应该是一个0到1的标量,表示概率值。
对于二分类问题的sigmoid标量输出,应该使用binary_crossentropy损失函数。
无论你的问题是什么,rmsprop优化器通常都是一个足够好的选择。
随着神经网络在训练数据上的表现越来越好,模型最终会过拟合,并在前所未见的数据上得到越来越差的结果,一定要一直监控模型在训练集之外的数据上的性能。