自然语言处理(NLP)作为人工智能领域中的重要技术之一,在近年来得到了越来越多的关注。其能够帮助我们对文本信息进行自动分类、抽取关键词、生成摘要、情感分析等多种自然语言处理任务,这些都离不开深度学习(Deep Learning)的最新技术。 通过本次实战项目,您将学习到如何利用深度学习模型开发出具有特定功能的NLP应用。您可以从零开始,编写一个完整的人工智能应用程序。 首先让我们回顾一下最基本的深度学习知识。深度学习是一个由人工神经网络组成的机器学习技术,它能够对输入数据进行特征提取、映射、转换并输出预测结果。如下图所示: 在深度学习中,神经网络通常由多个隐藏层(Hidden Layer)构成,每个隐藏层之间都是全连接的,即所有神经元都与前一层的所有神经元相连。输入数据首先进入第一个隐藏层,然后经过多个非线性变换函数,最终通过传播层(Propagation Layer)传输至输出层。输出层会给出预测值。如此循环往复,通过反向传播(Backpropagation)更新网络参数,最终达到优化目标。
在深度学习过程中,数据集非常重要。数据集就是训练集、验证集和测试集,它们是用于训练神经网络的数据集合。其中训练集用于训练模型,验证集用于选择模型参数,测试集用于评估模型的效果。一般来说,测试集的数据量要远小于训练集。训练集和验证集需要分割成为不同的部分。
在深度学习中,激活函数通常用作神经元输出值的非线性转换。常用的激活函数有sigmoid、tanh、ReLU等。对于每一种激活函数,都存在一些特定的优缺点,选择合适的激活函数对训练结果影响很大。如ReLU激活函数的优点是在深度网络中容易收敛,缺点则是产生死亡梯度问题,如果某些神经元一直保持激活状态,导致整个神经网络无法继续训练。sigmoid函数的优点是接近线性的输出,缺点则是易发生“梯度消失”或“梯度爆炸”。tanh函数是sigmoid函数的平滑版本,虽然也存在梯度消失和梯度爆炸的问题,但相比sigmoid更加平滑。
梯度下降法是神经网络模型训练过程中的关键一步。它通过迭代计算权重参数的更新值,使得网络误差最小化,达到预期的目标。由于数据量比较大,因此梯度下降法不能直接使用全部的数据,而是采用批处理(Batch)的方式,每次只使用一定数量的样本进行计算。一般情况下,采用随机梯度下降法(Stochastic Gradient Descent,SGD)。SGD一次仅更新一个样本,这样就可以有效减少内存占用。而采用批量梯度下降法(Batch Gradient Descent,BGD),一次更新整个训练集,效率较高。
损失函数用于衡量模型输出结果与实际值之间的差距。为了最小化损失函数的值,训练过程中神经网络会不断调整权重参数,使得损失函数达到极小值。损失函数有很多,常用的有均方误差、交叉熵等。其中交叉熵是最常用的损失函数。
权重衰减是指通过惩罚过大的权重值,对模型的复杂度进行限制。由于权重值的大小代表了模型的复杂度,因此过大的权重值可能会导致欠拟合现象。权重衰减可以通过添加正则项(Regularization Term)来实现,正则项会使得权重值的平方和等于一个固定的值。权重衰减的作用是防止模型出现过拟合现象。
本章节主要介绍基于卷积神经网络(Convolutional Neural Network,CNN)的人工智能技术。CNN是一个前沿技术,其具有极强的表征能力。它能够从图像、声音、文字等多媒体数据中提取特征,进而对数据进行分类、识别、预测等任务。目前市面上有各种基于CNN的人工智能产品,包括图像搜索、手写数字识别、视频分析、语音助手等。下面我们一起探讨一些基于CNN的人工智能技术。
一维卷积是指在二维平面的空间中,以单个或多个像素为单位,与输入数据做相关计算,再进行叠加求和。一维卷积与二维卷积不同的是,仅有一个水平方向上的卷积核。例如,下图展示了一个一维卷积过程:
如上图所示,假设有一个输入数据序列 $x$ ,长度为 $n+m-1$ 。其中 $n$ 表示卷积核的宽度,$m$ 为卷积核的个数。卷积核本身也可以看做是一组权重,每个权重对应一个像素位置。一维卷积的运算如下:
$$y(i)=\sum_{j=0}^{m} w_jx(i+j-m/2)+b $$
其中 $w_j$ 为第 $j$ 个卷积核的权重, $b$ 是偏置项。当 $i+j-m/2$ 超出了 $[0, n)$ 的范围时,卷积核的权重值对相应的元素不起作用。$y(i)$ 表示第 $i$ 个元素在卷积核处理之后的值。
二维卷积是指在二维平面空间中,以一个矩形窗口为单位,与输入数据做相关计算,再进行叠加求和。二维卷积与一维卷积类似,只是卷积核的宽度和高度不一样。下面是一个二维卷积过程的例子:
如上图所示,假设有一个输入数据矩阵 $X$ ,大小为 $(N_H \times N_W \times C_I)$ ,其中 $C_I$ 表示输入数据的通道数,即图像的颜色通道数。卷积核的大小为 $(k_H \times k_W)$ ,卷积核的个数为 $C_O$ 。假设卷积核的各权重分别记为 $w_{ik}$ ,第 $c_o$ 个卷积核对应的偏置项为 $b_c$ 。对于输出数据矩阵 $Y$ ,大小为 $(N'_H \times N'_W \times C_O)$ ,表示卷积结果。则二维卷积的运算如下:
$$Y=\sigma(\mathrm{conv}(X, W)+b )$$
其中 $\mathrm{conv}$ 函数表示二维卷积操作,$\sigma$ 表示激活函数,$W$ 和 $b$ 分别表示卷积核和偏置项。
Max Pooling 是卷积神经网络中另一个重要操作。它是一种池化操作,目的是降低卷积层对位置的敏感度,同时保留最大值信息。它的作用是将卷积层得到的特征图缩小,去除冗余信息。下面是一个 Max Pooling 的过程示例:
如上图所示,假设有一个输入数据矩阵 $X$ ,大小为 $(N_H \times N_W \times C_I)$ 。窗口大小为 $k$ x $k$ ,步长为 $s$ 。Max Pooling 将窗口内的最大值作为输出结果。即,
$$Z^{(l)}{ij}=max{X^{(l)}{:,i-1+ks},...,X^{(l)}_{:,i-1+ks+(k-1)\times s}}$$
$$Z^{(l)}_{:,::s}=(N_H/s)(N_W/s)K,$$
其中 $K$ 为窗口大小,$s$ 为步长。
Dropout 是深度学习中的一种正则化方法。它是指在训练阶段对神经网络的某些节点进行随机关闭,防止过拟合。下面是一个 Dropout 的过程示例:
如上图所示,假设有一个输入数据矩阵 $X$ ,大小为 $(N_H \times N_W \times C_I)$ 。Dropout 率为 $p$ ,其中 $p$ 在 $[0,1]$ 区间。对每个节点 $j$ ,按照概率 $p$ 随机将其关闭,即
$$A^{\ell}_j=drop(A^{\ell}_j;p), j \in [1, K]$$
其中 $K$ 为当前神经网络层的结点个数。当某个结点被关闭后,它不参与任何计算,相当于舍弃该结点的输出值。
现在,我们已经了解了卷积神经网络的一些基本概念,并且了解了卷积、Pooling、Dropout 操作。下面我们构建一个简单的 CNN 模型,作为实战项目的内容。模型结构如下图所示:
如上图所示,模型结构包括两个卷积层和三个全连接层。第一层的卷积核大小为 3 x 3,深度为 32;第二层的卷积核大小为 3 x 3,深度为 64;第三层的卷积核大小为 3 x 3,深度为 128。全连接层中,第一个全连接层的结点个数为 256,第二个全连接层的结点个数为 128,第三个全连接层的结点个数为 1。最后,输出层是一个 Softmax 函数,它把结点的输出值归一化为概率分布,以便后续的分类、预测任务。
对于 CNN 模型的训练,需要首先准备好数据集,即训练集、验证集、测试集。首先,对训练集数据进行预处理,将其转换为适合 CNN 模型的格式。比如,灰度图像需要转化为单通道的浮点数形式,标签需要被转换为独热码形式。然后,对模型进行初始化。接着,训练模型的过程如下:
整个训练流程需要重复以上步骤,直到模型性能达到要求或者资源耗尽。
为了实现以上内容,我们可以使用 TensorFlow 来构建我们的模型。下面我们将以 GitHub 上开源的 MNIST 数据集为例,逐步介绍 CNN 模型的搭建及训练过程。
首先,下载并导入必要的库。这里我们使用 TensorFlow、numpy 和 matplotlib 三大库。
import tensorflow as tf
import numpy as np
from tensorflow import keras
from tensorflow.keras import layers
import matplotlib.pyplot as plt
print("TensorFlow Version:",tf.__version__)
然后,载入 MNIST 数据集。MNIST 数据集包含 60,000 张训练图片和 10,000 张测试图片,图片大小为 28x28。
mnist = keras.datasets.mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
训练集和测试集被划分为图片和标签两部分。图片数据存储为一个四维数组,维度为 (60000, 28, 28)
。标签数据存储为整数数组,维度为 (60000,)
。显示第一张训练图片,可以看到数字 5。
plt.figure()
plt.imshow(train_images[0]) # 显示第一张训练图片
plt.colorbar()
plt.grid(False)
plt.show()
下面,我们对数据进行预处理。首先,数据类型需要转换为浮点数形式。然后,将标签转换为独热码形式,即每个数字都用一个唯一的整数表示。
# 对数据进行预处理
train_images = train_images.reshape((60000, 28, 28, 1)) # 添加一个维度,因为只有单通道
test_images = test_images.reshape((10000, 28, 28, 1))
train_images = train_images.astype('float32') / 255
test_images = test_images.astype('float32') / 255
train_labels = keras.utils.to_categorical(train_labels, num_classes=10) # 标签转换为独热码形式
test_labels = keras.utils.to_categorical(test_labels, num_classes=10)
接着,我们定义我们的 CNN 模型。这里,我们使用 Keras API 来构建我们的模型。
model = keras.Sequential([
layers.Conv2D(32, kernel_size=(3,3), activation='relu', input_shape=(28, 28, 1)),
layers.MaxPooling2D(pool_size=(2,2)),
layers.Conv2D(64, kernel_size=(3,3), activation='relu'),
layers.MaxPooling2D(pool_size=(2,2)),
layers.Flatten(),
layers.Dense(256, activation='relu'),
layers.Dense(128, activation='relu'),
layers.Dense(10, activation='softmax')
])
模型的第一层是一个卷积层,它有 32 个 3 x 3 的卷积核,激活函数为 relu 。然后,应用最大池化操作,将池化区域的大小设置为 2 x 2 。第二层是一个卷积层,它有 64 个 3 x 3 的卷积核,激活函数为 relu 。同样地,应用最大池化操作。
然后,模型被扩展成一个三维的向量,维度为 (batch_size, width*height, channels)
。这种方式被称为张量的 flatten 操作。接着,我们有三个全连接层,每层有 256 个结点,激活函数为 relu 。最后,有一个输出层,有 10 个结点,激活函数为 softmax ,用来计算分类的概率。
接着,我们编译模型。这里,我们设置优化器为 adam ,损失函数为 categorical_crossentropy ,度量标准为 accuracy 。
optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
loss_func = 'categorical_crossentropy'
acc_metric = 'accuracy'
model.compile(optimizer=optimizer, loss=loss_func, metrics=[acc_metric])
最后,我们开始训练模型。这里,我们将训练集切分为 60% 的训练集和 40% 的验证集。然后,训练模型,每隔 100 个 batch 打印一次日志。
BATCH_SIZE = 32
EPOCHS = 10
history = model.fit(
train_images,
train_labels,
epochs=EPOCHS,
validation_split=0.2,
verbose=1,
callbacks=[tf.keras.callbacks.EarlyStopping()],
batch_size=BATCH_SIZE)
训练结束后,我们可视化训练的结果。首先,绘制训练集的损失值和准确率曲线。
def plot_metrics():
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs_range = range(EPOCHS)
plt.figure(figsize=(8, 8))
plt.subplot(2, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')
plt.subplot(2, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
plot_metrics()
如上图所示,训练集的准确率一直在提高,验证集的准确率随着轮数增加慢慢下降。但是,验证集的准确率并不是一个绝对的指标,因为它受到测试集的干扰。因此,我们还应当在测试集上测试模型的准确率。
test_loss, test_acc = model.evaluate(test_images, test_labels, verbose=2)
print('\nTest accuracy:', test_acc)
输出:
Epoch 1/10
469/469 [==============================] - 14s 28ms/step - loss: 0.1913 - accuracy: 0.9408 - val_loss: 0.0422 - val_accuracy: 0.9850
Epoch 2/10
469/469 [==============================] - 13s 27ms/step - loss: 0.0691 - accuracy: 0.9772 - val_loss: 0.0303 - val_accuracy: 0.9897
Epoch 3/10
469/469 [==============================] - 13s 27ms/step - loss: 0.0535 - accuracy: 0.9833 - val_loss: 0.0257 - val_accuracy: 0.9909
Epoch 4/10
469/469 [==============================] - 13s 27ms/step - loss: 0.0434 - accuracy: 0.9873 - val_loss: 0.0234 - val_accuracy: 0.9915
Epoch 5/10
469/469 [==============================] - 13s 27ms/step - loss: 0.0365 - accuracy: 0.9895 - val_loss: 0.0219 - val_accuracy: 0.9921
Epoch 6/10
469/469 [==============================] - 13s 27ms/step - loss: 0.0312 - accuracy: 0.9912 - val_loss: 0.0209 - val_accuracy: 0.9921
Epoch 7/10
469/469 [==============================] - 13s 27ms/step - loss: 0.0280 - accuracy: 0.9920 - val_loss: 0.0203 - val_accuracy: 0.9922
Epoch 8/10
469/469 [==============================] - 13s 27ms/step - loss: 0.0254 - accuracy: 0.9924 - val_loss: 0.0197 - val_accuracy: 0.9927
Epoch 9/10
469/469 [==============================] - 13s 27ms/step - loss: 0.0237 - accuracy: 0.9931 - val_loss: 0.0194 - val_accuracy: 0.9927
Epoch 10/10
469/469 [==============================] - 13s 27ms/step - loss: 0.0223 - accuracy: 0.9934 - val_loss: 0.0187 - val_accuracy: 0.9933
Test accuracy: 0.9929
如上所示,在测试集上,模型的准确率为 99.29 % ,接近之前模型的准确率,说明模型的泛化能力较强。
深度学习是近几年热门的研究领域之一,除了计算机视觉、自然语言处理、推荐系统等技术外,还有许多其他应用场景正在逐渐受益于深度学习的带来的深刻改变。因此,相信随着时间的推移,深度学习会成为更多领域的基础性技术。 在深度学习的应用中,可以看出以下几个方向的发展趋势: