Python 深度学习架构实用指南:第一、二部分(3)

简介: Python 深度学习架构实用指南:第一、二部分(3)

Python 深度学习架构实用指南:第一、二部分(2)https://developer.aliyun.com/article/1426972

简单的方法

仅为一个简单的 DFN 编写所有上述代码似乎很乏味。 因此,TensorFlow 具有高级模块,使我们可以更轻松地构建模型。 Keras 通过提供构建层的函数来处理主要的编码结构,使我们能够专注于模型架构。 让我们使用 Keras 构建一个小型 DFN,如下所示:

import keras
# importing the sequential method in Keras
from keras.models import Sequential
# Importing the dense layer which creates a layer of deep feedforward network
from keras.layers import Dense, Activation, Flatten, Dropout
# getting the data as we did earlier
fashionObj = keras.datasets.fashion_mnist
(trainX, trainY), (testX, testY) = fashionObj.load_data()
print('train data x shape: ', trainX.shape)
print('test data x shape:', testX.shape)
print('train data y shape: ', trainY.shape)
print('test data y shape: ', testY.shape)
# Now we can directly jump to building model, we build in Sequential manner as discussed in Chapter 1
model = Sequential()
# the first layer we will use is to flatten the 2-d image input from (28,28) to 784
model.add(Flatten(input_shape = (28, 28)))
# adding first hidden layer with 512 units
model.add(Dense(512))
#adding activation to the output
model.add(Activation('relu'))
#using Dropout for Regularization
model.add(Dropout(0.2))
# adding our final output layer
model.add(Dense(10))
#softmax activation at the end
model.add(Activation('softmax'))
# normalizing input data before feeding
trainX = trainX / 255
testX = testX / 255
# compiling model with optimizer and loss
model.compile(optimizer= 'Adam', loss = 'sparse_categorical_crossentropy', metrics = ['accuracy'])
# training the model
model.fit(trainX, trainY, epochs = 5, batch_size = 64)
# evaluating the model on test data
model.evaluate(testX, testY)
print('Test Set average Accuracy: ', evalu[1])

上面的代码将输出以下内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bkpRZzPc-1681704767264)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/b9e72d48-b033-48c5-9860-d6df05237e86.png)]

总结

我们从 DFN 和深度学习的演变历史开始本章。 我们了解了 DFN 的分层架构以及训练中涉及的各个方面,例如损失函数,梯度下降,反向传播,优化器和正则化。 然后,我们使用 TensorFlow 和 Keras 通过第一个 DFN 进行编码。 我们从开源时尚 MNIST 数据开始,并逐步学习了建立网络的逐步过程,从处理数据到训练模型。

在下一章中,我们将看到玻尔兹曼机和自编码器的架构。

三、受限玻尔兹曼机和自编码器

当您在线购物或浏览电影时,您可能会想知道“您可能也喜欢的电影”产品如何工作。 在本章中,我们将说明幕后算法,称为受限玻尔兹曼机RBM)。 我们将首先回顾 RBM 及其发展路径。 然后,我们将更深入地研究其背后的逻辑,并在 TensorFlow 中实现 RBM。 我们还将应用它们来构建电影推荐器。 除了浅层架构,我们还将继续使用称为深度信念网络DBN)的 RBM 堆叠版本,并使用它对图像进行分类,当然,我们在 TensorFlow 中实现。

RBM 通过尝试重建输入数据来找到输入的潜在表示。 在本章中,我们还将讨论自编码器,这是另一种具有类似想法的网络。 在本章的后半部分,我们将继续介绍自编码器,并简要介绍它们的发展路径。 我们将说明按照其架构或形式化形式分类的各种自编码器。 我们还将采用不同类型的自编码器来检测信用卡欺诈。 这将是一个有趣的项目,更令人着迷的是,您将看到这些自编码器种类如何努力学习使用某些架构或强加约束形式的更健壮的表示形式。

我们将深入探讨以下主题:

  • 什么是 RBM?
  • RBM 的发展路径
  • 在 TensorFlow 中实现 RBM
  • 用于电影推荐的 RBM
  • 数据库
  • TensorFlow 中 DBN 的实现
  • 用于图像分类的 DBN
  • 什么是自编码器?
  • 自编码器的发展路径
  • 原始自编码器
  • 深度自编码器
  • 稀疏自编码器
  • 去噪自编码器
  • 压缩自编码器
  • 用于信用卡欺诈检测的自编码器

什么是 RBM?

RBM 是一种生成型随机神经网络。 通过说生成式,它表明网络对输入集上的概率分布进行建模。 随机意味着神经元在被激活时具有随机行为。 RBM 的一般图如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cY0pPDbt-1681704767264)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/7e117abf-04cc-407c-b57b-1683118b2ea8.png)]

通常,RBM 由一个输入层组成,该输入层通常称为可见层(v[1], v[2], v[3], v[4])和一个隐藏层(h[1], h[2], h[3], h[4])。 RBM 模型由与可见层和隐藏层之间的连接相关的权重W = {w[ij]}, 1 <= i <= |V|, 1 <= j <= |H|以及偏差a = {a[i]}, 1 <= i <= |V|用于可见层,偏置b = {b[j]}, 1 <= j <= |H|用于隐藏层。

RBM 中显然没有输出层,因此学习与前馈网络中的学习有很大不同,如下所示:

  • 与其减少描述地面实况与输出层之间差异的损失函数,不如尝试减少能量函数,该函数定义如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0mBuGClS-1681704767264)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/d06354bc-4963-4fef-b7aa-f100ae1ce33e.png)]

对于不熟悉能量函数的人,术语能量来自物理学,用于量化大型物体对另一个物体的重力。 物理学中的能量函数测量两个对象或机器学习中两个变量的兼容性。 能量越低,变量之间的兼容性越好,模型的质量越高。

  • 与产生输出相反,它在其可见和隐藏单元集上分配概率,并且每个单元在每个时间点处于0(关闭)或1(激活)的二进制状态下。 给定可见层v,隐藏单元被激活的概率计算如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lPEwILzr-1681704767264)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/33a47fba-518e-4f08-9a61-fafb69291569.png)]

类似地,给定一个隐藏层h,我们可以如下计算可见单元被激活的概率:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VoF5zr2l-1681704767264)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/40046dd7-01ec-41c0-93d3-aa5df24b71c5.png)]

由于hv的状态基于彼此随机分配给01,因此可以通过重复少量采样过程实现收敛。 下图演示了此过程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bcJxwiiK-1681704767265)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/188cc358-b184-4998-aa60-b5de2c02b12a.png)]

从可见层v^(0)的初始状态开始,计算P(h | v^(0)); 隐藏层h^(0)P(h | v^(0))采样,然后计算P(v | h^(0))。 接下来,基于P(v | h^(0))采样状态v^(1)h^(1)基于P(h | v^(1))采样,依此类推。 此过程称为吉布斯采样。 也可以将其视为重建可见层。

  • 根据初始状态v^(0)k个吉布斯步骤之后的状态v^(k)计算梯度,其中表示外部乘积:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A3dFxvGA-1681704767265)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/d4a21cdd-b149-43ee-aa01-cb823662c83d.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OR4M2Qxh-1681704767265)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/4fb3b1ff-408f-4af7-bda0-4769dcf638c3.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wpu2SdSs-1681704767265)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/3c3310a9-f432-4dc1-b562-942e0fa94f88.png)]

这些梯度称为对比散度。

我希望您现在已经掌握了 RBM 背后的理论。 在简要介绍了 RBM 的演变路径之后,您将在动手部分中增强对 RBM 的理解,我们将在下一节中进行介绍。

RBM 的发展路径

顾名思义,RBM 源自玻尔兹曼机。玻尔兹曼机由 Geoffrey Hinton 和 Paul Smolensky 于 1983 年发明,是一种网络类型,其中所有单元(可见和隐藏)都处于二进制状态并连接在一起。 尽管他们具有学习有趣的表示形式的理论能力,但对他们来说还是有许多实际问题,包括训练时间,训练时间随模型大小呈指数增长(因为所有单元都已连接)。 玻尔兹曼机的总体示意图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i8prejmQ-1681704767266)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/34c9802d-3a2e-46c7-a490-5bc637bd517f.png)]

为了使学习玻尔兹曼机模型更容易,Paul Smolensky 于 1986 年首次发明了一种称为 Harmonium 的连接受限的版本。 在 2000 年中期,Geoffrey Hinton 和其他研究人员发明了一种效率更高的架构,该架构仅包含一个隐藏层,并且不允许隐藏单元之间进行任何内部连接。 从那时起,RBM 被应用于各种有监督的学习任务中,包括:

  • 图像分类(《使用判别受限的玻尔兹曼机进行分类》)
  • 语音识别(《使用受限的玻尔兹曼机学习语音声波的更好表示》)

它们也已应用于无监督的学习任务,包括以下内容:

  • 降维(《使用神经网络降低数据的维数》)
  • 特征学习(《无监督特征学习中的单层网络分析》),当然还有协同过滤和推荐系统 ,我们将在本节之后进行处理

您可能会注意到,RBM 有点,只有一个隐藏层。 Geoffrey Hinton 在 2006 年推出了称为 DBN 的版本的 RBM。DBN 可以看作是堆叠在一起的一组 RBM,其中一个 RBM 的隐藏层是下一个 RBM 的可见层。 隐藏层充当分层特征检测器。 DBN 的一般图如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dRBuKpG0-1681704767266)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/51a6bfe7-88bd-47ad-b74b-3bd214cce072.png)]

DBN 也有许多有趣的应用,例如:

  • 电话识别(《用于电话识别的深度信念网络》)
  • 脑电信号(《脑电图的深层信念网络:对近期贡献的回顾和未来展望》)
  • 自然语言理解(《深度信念网络在自然语言理解上的应用》)

按照承诺,我们现在将详细研究 RBM 及其深版本 DBN,然后将其应用于实际问题。

RBM 架构和应用

我们将首先介绍 RBM 及其实现,以及它们在推荐系统中的应用,然后再转到 DBN 并利用它们对图像进行分类。

RBM 及其在 TensorFlow 中的实现

让我们从初始化 RBM 模型的参数开始。 回想一下,RMB 模型由与可见层和隐藏层之间的连接关联的权重W,可见层的偏置a和偏置b组成。 用于隐藏层。 RBM 对象由权重W,偏差ab,可见单元数和隐藏单元数,吉布斯步骤数构成。 常规神经网络超参数,包括批量大小,学习率和周期数:

>>> import numpy as np
>>> import tensorflow as tf
>>> class RBM(object):
...     def __init__(self, num_v, num_h, batch_size, learning_rate, 
                     num_epoch, k=2):
...         self.num_v = num_v
...         self.num_h = num_h
...         self.batch_size = batch_size
...         self.learning_rate = learning_rate
...         self.num_epoch = num_epoch
...         self.k = k
...         self.W, self.a, self.b = self._init_parameter()

在属性初始化之后,我们定义_init_参数方法如下:

>>> def _init_parameter(self):
...     """ Initializing the model parameters including weights 
            and bias 
        """
...     abs_val = np.sqrt(2.0 / (self.num_h + self.num_v))
...     W = tf.get_variable('weights', shape=(self.num_v, self.num_h),
                   initializer=tf.random_uniform_initializer(
                                    minval=-abs_val, maxval=abs_val))
...     a = tf.get_variable('visible_bias', shape=(self.num_v),
                                    initializer=tf.zeros_initializer())
...     b = tf.get_variable('hidden_bias', shape=(self.num_h), 
                                    initializer=tf.zeros_initializer())
...     return W, a, b

直观地,我们可以安全地将所有偏差初始化为 0。对于权重,最好使用启发式方法将其初始化。 常用的启发式方法包括:

  • √(2 / 上一层大小)
  • √(1 / 上一层大小),也称为 xavier 初始化
  • √(2 / 上一层大小 + 这一层大小)

这些启发式方法有助于防止收敛缓慢,并且通常是权重初始化的良好起点。

正如我们前面提到的,训练 RBM 模型是一个搜索参数的过程,该参数可以通过吉布斯采样最好地重构输入向量。 让我们实现吉布斯采样方法,如下所示:

>>> def _gibbs_sampling(self, v):
...     """
...     Gibbs sampling
...     @param v: visible layer
...     @return: visible vector before Gibbs sampling, 
                 conditional probability P(h|v) before Gibbs sampling,
                 visible vector after Gibbs sampling,
                 conditional probability P(h|v) after Gibbs sampling
...     """
...     v0 = v
...     prob_h_v0 = self._prob_h_given_v(v0)
...     vk = v
...     prob_h_vk = prob_h_v0
...     for _ in range(self.k):
...         hk = self._bernoulli_sampling(prob_h_vk)
...         prob_v_hk = self._prob_v_given_h(hk)
...         vk = self._bernoulli_sampling(prob_v_hk)
...         prob_h_vk = self._prob_h_given_v(vk)
...     return v0, prob_h_v0, vk, prob_h_vk

给定输入向量vk,吉布斯采样开始于计算P(h | v)。 然后执行吉布斯步骤。 在每个吉布斯步骤中,隐藏层h是根据P(h | v)通过伯努利采样获得的; 计算条件概率P(v | h)并用于生成可见向量v的重建版本; 并根据最新的可见向量更新条件概率P(h | v)。 最后,它返回吉布斯采样之前和之后的可见向量,以及吉布斯采样之前和之后的条件概率P(h | v)

现在,我们实现了条件概率P(v | h)P(h | v)的计算,以及伯努利采样:

  • 计算P(v[i] = 1 | h) = sigmoid(a[i] + Σ[j] h[j]w[ij])如下:
>>> def _prob_v_given_h(self, h):
...     """
...     Computing conditional probability P(v|h)
...     @param h: hidden layer
...     @return: P(v|h)
...     """
...     return tf.sigmoid(
              tf.add(self.a, tf.matmul(h, tf.transpose(self.W))))
  • 计算P(h[j] = 1 | v) = sigmoid(b[j] + Σ[i] v[i]w[ij])如下:
>>> def _prob_h_given_v(self, v):
...     """
...     Computing conditional probability P(h|v)
...     @param v: visible layer
...     @return: P(h|v)
...     """
...     return tf.sigmoid(tf.add(self.b, tf.matmul(v, self.W)))
  • 现在,我们将计算伯努利抽样,如下所示:
>>> def _bernoulli_sampling(self, prob):
...     """ Bernoulli sampling based on input probability """
...     distribution = tf.distributions.Bernoulli(
                                  probs=prob, dtype=tf.float32)
...     return tf.cast(distribution.sample(), tf.float32)

现在我们能够计算吉布斯采样前后的可见输入和条件概率P(h | v),我们可以计算梯度,包括ΔW = v[o] ⊗ P(h | v^(o)) - P(h | v^(k))Δa = v[0] - v[k]Δb = P(h | v^(k)) - P(h | v^(k)),如下所示:

>>> def _compute_gradients(self, v0, prob_h_v0, vk, prob_h_vk):
...     """
...     Computing gradients of weights and bias
...     @param v0: visible vector before Gibbs sampling
...     @param prob_h_v0: conditional probability P(h|v) 
                                         before Gibbs sampling
...     @param vk: visible vector after Gibbs sampling
...     @param prob_h_vk: conditional probability P(h|v) 
                                        after Gibbs sampling
...     @return: gradients of weights, gradients of visible bias,
                 gradients of hidden bias
...     """
...     outer_product0 = tf.matmul(tf.transpose(v0), prob_h_v0)
...     outer_productk = tf.matmul(tf.transpose(vk), prob_h_vk)
...     W_grad = tf.reduce_mean(outer_product0 - outer_productk, axis=0)
...     a_grad = tf.reduce_mean(v0 - vk, axis=0)
...     b_grad = tf.reduce_mean(prob_h_v0 - prob_h_vk, axis=0)
...     return W_grad, a_grad, b_grad

使用吉布斯采样和梯度,我们可以组合一个以时间为单位的参数更新,如下所示:

>>> def _optimize(self, v):
...     """
...     Optimizing RBM model parameters
...     @param v: input visible layer
...     @return: updated parameters, mean squared error of reconstructing v
...     """
...     v0, prob_h_v0, vk, prob_h_vk = self._gibbs_sampling(v)
...     W_grad, a_grad, b_grad = self._compute_gradients(v0, prob_h_v0, vk,  
                                                         prob_h_vk)
...     para_update=[tf.assign(self.W, 
                           tf.add(self.W, self.learning_rate*W_grad)),
...                  tf.assign(self.a, 
                           tf.add(self.a, self.learning_rate*a_grad)),
...                  tf.assign(self.b, 
                           tf.add(self.b, self.learning_rate*b_grad))]
...     error = tf.metrics.mean_squared_error(v0, vk)[1]
...     return para_update, error

除了更新权重W := W + lr * ΔW,偏差a := a + lr * Δa和偏差b := b + lr * Δb之外,我们还计算了重建可见层的均方误差。

到目前为止,我们已经准备好用于训练 RBM 模型的必要组件,因此下一步是将它们放在一起以形成train方法,如以下代码所示:

>>> def train(self, X_train):
...     """
...     Model training
...     @param X_train: input data for training
...     """
...     X_train_plac = tf.placeholder(tf.float32, [None, self.num_v])
...     para_update, error = self._optimize(X_train_plac)
...     init = tf.group(tf.global_variables_initializer(),
                        tf.local_variables_initializer())
...     with tf.Session() as sess:
...         sess.run(init)
...         epochs_err = []
...         n_batch = int(X_train.shape[0] / self.batch_size)
...         for epoch in range(1, self.num_epoch + 1):
...             epoch_err_sum = 0
...             for batch_number in range(n_batch):
...                 batch = X_train[batch_number * self.batch_size:
                                  (batch_number + 1) * self.batch_size]
...                 _, batch_err = sess.run((para_update, error),
                                       feed_dict={X_train_plac: batch})
...                 epoch_err_sum += batch_err
...             epochs_err.append(epoch_err_sum / n_batch)
...             if epoch % 10 == 0:
...                 print("Training error at epoch %s: %s" % 
                                               (epoch,epochs_err[-1]))

请注意,我们在训练中采用小批量梯度下降,并记录每个周期的训练误差。 整个训练过程都依赖于_optimize方法,该方法适合每个数据批量上的模型。 它还会每隔10个时间段输出训练误差,以确保质量。

我们刚刚完成了 RBM 算法的实现。 在下一节中,我们将其应用于电影推荐。

用于电影推荐的 RBM

众所周知,电子商务网站会根据用户的购买和浏览历史向他们推荐产品。 相同的逻辑适用于电影推荐。 例如,Netflix 根据用户在观看的电影上提供的反馈(例如评分)来预测用户喜欢的电影。 RBM 是推荐系统最受欢迎的解决方案之一。 让我们看一下推荐的 RBM 的工作原理。

给定经过训练的 RBM 模型,由用户喜欢,不喜欢和未观看的一组电影组成的输入从可见层转到隐藏层,然后又回到可见层,从而生成输入的重构版本 。 除了与用户进行交互的电影外,重构的输入还包含以前未评级的信息。 它可以预测是否会喜欢这些电影。 一般图如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RCr5NTkl-1681704767266)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/166067d5-6d17-45ad-9449-6dbb6edad311.png)]

在此示例中,输入内容包括六部电影,其中三部被点赞(用1表示),两部不喜欢(用0表示),而另一部未分级(用?表示)。 该模型接受输入并对其进行重构,包括缺少的电影。

因此,模型如何知道缺少的单元应为0(或1)? 回忆每个隐藏的单元都连接到所有可见的单元。 在训练过程中,一个隐藏的单元试图发现一个潜在因素,该潜在因素可以解释数据中的一个属性,或本例中的所有电影。 例如,一个隐藏的二元单元可以了解电影类型是否是喜剧电影,是否属于正义电影,主题是否为复仇电影或其他任何捕捉到的东西。 在重构阶段,将为输入分配一个新值,该值是根据代表所有这些潜在因素的隐藏单元计算得出的。

听起来神奇吗? 让我们开始构建基于 RBM 的电影推荐器。

我们将使用 MovieLens 中的电影评分数据集。 这是一个非商业性网站,用于收集用户的移动收视率并提供个性化建议,由明尼苏达大学的研究实验室 GroupLens 运营。

首先,我们将在这个页面中查看 1M 基准数据集。 它包含来自 6,040 位用户的 3,706 部电影的大约一百万个收视率。 我们可以通过这里下载数据集并解压缩下载的文件。 等级包含在ratings.dat文件中,每一行是一个等级,格式如下:

UserID::MovieID::Rating::Timestamp

评分记录如下所示:

1::1193::5::978300760
2::1357::5::978298709
10::1022::5::979775689

有几件事要注意:

  • 用户 ID 的范围是 1 到 6,040
  • MovieID 的范围是 1 到 3,952,但并非每部电影都经过分级
  • 评分是{1, 2, 3, 4, 5}之一
  • 每个用户给几部电影评分

我们可以建立 RBM 模型,根据用户和其他人的电影评分推荐用户尚未观看的电影。

您可能会注意到输入额定值不是二进制的。 我们如何将它们提供给 RBM 模型? 最简单的解决方法是二进制化,例如,将大于三的等级转换为1(类似),否则将其转换为0(不喜欢)。 但是,这可能会导致信息丢失。 或者,在我们的解决方案中,我们将原始评级缩放到[0,1]范围,并将每个重新缩放的评级视为获得1的概率。 也就是说,v = P(v = 1 | h),不需要伯努利采样。

现在,让我们加载数据集并构建训练数据集。 不要忘了跟踪已分级的电影,因为并非所有电影都已分级:

>>> import numpy as np
>>> data_path = 'ml-1m/ratings.dat'
>>> num_users = 6040
>>> num_movies = 3706
>>> data = np.zeros([num_users, num_movies], dtype=np.float32)
>>> movie_dict = {}
>>> with open(data_path, 'r') as file:
    ... for line in file.readlines()[1:]:
    ... user_id, movie_id, rating, _ = line.split("::")
    ... user_id = int(user_id) - 1
    ... if movie_id not in movie_dict:
    ... movie_dict[movie_id] = len(movie_dict)
    ... rating = float(rating) / 5
    ... data[user_id, movie_dict[movie_id]] = rating
>>> data = np.reshape(data, [data.shape[0], -1])
>>> print(data.shape)(6040, 3706)

训练数据集的大小为6,040 x 3,706,每行包含3706缩放等级,包括0.0,表示未分级。 可以将其显示为下表(虚拟)以获得更直观的视图:

movie_0 movie_1 movie_2 movie_n
user_0 0.0 0.2 0.8 0.0 1.0 0.0
user_1 0.8 0.0 0.6 1.0 0.0 0.0
user_2 0.0 0.0 1.0 1.0 0.8 0.0
user_m 0.0 0.6 0.0 0.8 0.0 0.8

看一下它们的分布,如下所示:

>>> values, counts = np.unique(data, return_counts=True)
>>> for value, count in zip(values, counts):
...     print('Number of {:2.1f} ratings: {}'.format(value, count))
Number of 0.0 ratings: 21384032
Number of 0.2 ratings: 56174
Number of 0.4 ratings: 107557
Number of 0.6 ratings: 261197
Number of 0.8 ratings: 348971
Number of 1.0 ratings: 226309

我们可以看到矩阵非常稀疏。 同样,那些0代表未被相应用户评级的电影,而不是获得1的可能性为零。 因此,在整个训练过程中,我们应将未分级电影的分级保持为零,这意味着我们应在每个吉布斯步骤之后将其还原为0。 否则,它们的重构值将包含在隐藏层和梯度的计算中,结果,该模型将在很大程度上未优化。

因此,我们修改了_gibbs_sampling_optimize方法,如下所示:

>>> def _gibbs_sampling(self, v):
...     """
...     Gibbs sampling (visible units with value 0 are unchanged)
...     @param v: visible layer
...     @return: visible vector before Gibbs sampling,
                 conditional probability P(h|v) before Gibbs sampling,
...              visible vector after Gibbs sampling,
                 conditional probability P(h|v) after Gibbs sampling
...     """
...     v0 = v
...     prob_h_v0 = self._prob_h_given_v(v0)
...     vk = v
...     prob_h_vk = prob_h_v0
...     for _ in range(self.k):
...         hk = self._bernoulli_sampling(prob_h_vk)
...         prob_v_hk = self._prob_v_given_h(hk)
...         vk_tmp = prob_v_hk
...         vk = tf.where(tf.equal(v0, 0.0), v0, vk_tmp)
...         prob_h_vk = self._prob_h_given_v(vk)
...     return v0, prob_h_v0, vk, prob_h_vk

我们采用v = P(v = 1 | h)并使用0恢复等级,如下所示:

>>> def _optimize(self, v):
...     """
...     Optimizing RBM model parameters
...     @param v: input visible layer
...     @return: updated parameters, mean squared error of reconstructing v
...     """
...     v0, prob_h_v0, vk, prob_h_vk = self._gibbs_sampling(v)
...     W_grad, a_grad, b_grad = self._compute_gradients(
                                    v0, prob_h_v0, vk, prob_h_vk)
...     para_update=[tf.assign(self.W, 
                            tf.add(self.W, self.learning_rate*W_grad)),
...                 tf.assign(self.a, 
                            tf.add(self.a, self.learning_rate*a_grad)),
...                 tf.assign(self.b, tf.add(self.b,
                            self.learning_rate*b_grad))]
...     bool_mask = tf.cast(tf.where(tf.equal(v0, 0.0),
                            x=tf.zeros_like(v0), y=tf.ones_like(v0)),                     
                            dtype=tf.bool)
...     v0_mask = tf.boolean_mask(v0, bool_mask)
...     vk_mask = tf.boolean_mask(vk, bool_mask)
...     error = tf.metrics.mean_squared_error(v0_mask, vk_mask)[1]
...     return para_update, error

在计算训练误差时,我们只考虑那些额定的电影,否则它将变得非常小。 通过这些更改,我们现在可以安全地将 RBM 模型拟合到训练集上,如以下代码所示:

>>> rbm = RBM(num_v=num_movies, num_h=80, batch_size=64, 
              num_epoch=100, learning_rate=0.1, k=5)

我们以80隐藏单元,64,100周期的批量大小,0.1的学习率和5吉布斯步骤初始化模型,如下所示:

>>> rbm.train(data)
Training error at epoch 10: 0.043496965727907545
Training error at epoch 20: 0.041566036522705505
Training error at epoch 30: 0.040718327296224044
Training error at epoch 40: 0.04024859795227964
Training error at epoch 50: 0.03992816338196714
Training error at epoch 60: 0.039701666445174116
Training error at epoch 70: 0.03954154300562879
Training error at epoch 80: 0.03940619274656823
Training error at epoch 90: 0.03930238915726225
Training error at epoch 100: 0.03921664716239939

训练误差减少到0.039,我们可以使用训练后的模型推荐电影。 为此,我们需要返回优化的参数并使用这些参数添加预测方法。

在我们之前定义的训练方法中,我们通过更改以下行来保留更新的参数:

... _, batch_err = sess.run(
                 (para_update, error),feed_dict={X_train_plac: batch})

我们需要将以下几行替换为:

... parameters, batch_err = sess.run((para_update, error),
                                      feed_dict={X_train_plac: batch})

然后,我们需要在方法末尾返回最后更新的参数,如下所示:

... return parameters

引入训练后的模型并重建输入数据的预测方法定义如下:

>>> def predict(self, v, parameters):
...     W, a, b = parameters
...     prob_h_v = 1 / (1 + np.exp(-(b + np.matmul(v, W))))
...     h = np.random.binomial(1, p=prob_h_v)
...     prob_v_h = 1 / 
                 (1 + np.exp(-(a + np.matmul(h, np.transpose(W)))))
...     return prob_v_h

现在,我们可以获得输入数据的预测,如下所示:

>>> parameters_trained = rbm.train(data)
>>> prediction = rbm.predict(data, parameters_trained)

以一个用户为例,我们将五星级的电影与未评级的电影进行比较,但预计其评级将高于0.9。 以下代码均显示了这些内容:

>>> sample, sample_pred = data[0], prediction[0]
>>> five_star_index = np.where(sample == 1.0)[0]
>>> high_index = np.where(sample_pred >= 0.9)[0]
>>> index_movie = {value: key for key, value in movie_dict.items()}
>>> print('Movies with five-star rating:', ', 
         '.join(index_movie[index] for index in five_star_index))
Movies with five-star rating: 2918, 1035, 3105, 1097, 1022, 1246, 3257, 265, 1957, 1968, 1079, 39, 1704, 1923, 1101, 597, 1088, 1380, 300, 1777, 1307, 62, 543, 249, 440, 2145, 3526, 2248, 1013, 2671, 2059, 381, 3429, 1172, 2690
>>> print('Movies with high prediction:',
      ', '.join(index_movie[index] for index in high_index if index not
      in five_star_index))
Movies with high prediction: 527, 745, 318, 50, 1148, 858, 2019, 922, 787, 2905, 3245, 2503, 53

我们可以在movies.dat文件中查找相应的电影。 例如,该用户喜欢3257::The Bodyguard1101::Top Gun是有道理的,因此他/她也将喜欢50::The Usual Suspects858::The Godfather527::Schindler's List。 但是,由于 RBM 的不受监督的性质,除非我们咨询每个用户,否则很难评估模型的表现。 我们需要开发一种模拟方法来测量预测精度。

我们为每个用户随机选择 20% 的现有评分,并在将其输入经过训练的 RBM 模型中时暂时使它们未知。 然后,我们比较所选模拟等级的预测值和实际值。

首先,让我们将用户分成 90% 的训练集和 10% 的测试集,它们的等级将分别用于训练模型和执行仿真。 如下代码所示:

>>> np.random.seed(1)
>>> np.random.shuffle(data)
>>> data_train, data_test = data[:num_train, :], data[num_train:, :]

其次,在测试集上,我们从每个用户中随机选择现有评级的 20% 进行模拟,如下所示:

>>> sim_index = np.zeros_like(data_test, dtype=bool)
>>> perc_sim = 0.2
>>> for i, user_test in enumerate(data_test):
...     exist_index = np.where(user_test > 0.0)[0]
...     sim_index[i, np.random.choice(exist_index,
                  int(len(exist_index)*perc_sim))] = True

所选等级暂时变为未知,如下所示:

>>> data_test_sim = np.copy(data_test)
>>> data_test_sim[sim_index] = 0.0

接下来,我们在训练集上训练模型,并在模拟测试集上进行预测,如下所示:

>>> rbm = RBM(num_v=num_movies, num_h=80, batch_size=64,
              num_epoch=100, learning_rate=1, k=5)
>>> parameters_trained = rbm.train(data_train)
Training error at epoch 10: 0.039383551327600366
Training error at epoch 20: 0.03883369417772407
Training error at epoch 30: 0.038669846597171965
Training error at epoch 40: 0.038585483273934754
Training error at epoch 50: 0.03852854181258451
Training error at epoch 60: 0.03849853335746697
Training error at epoch 70: 0.03846755987476735
Training error at epoch 80: 0.03844876645044202
Training error at epoch 90: 0.03843735127399365
Training error at epoch 100: 0.038423490045326095
>>> prediction = rbm.predict(data_test_sim, parameters_trained)

最后,我们可以通过计算预测值与所选等级的实际值之间的 MSE 来评估预测准确率,如下所示:

>>> from sklearn.metrics import mean_squared_error
>>> print(mean_squared_error(
             data_test[sim_index],prediction[sim_index]))
0.037987366148405505

我们基于 RBM 的电影推荐器可实现0.038的 MSE。 如果您有兴趣,可以使用更大的数据集,例如位于这里,以及位于这里的 1000 万个收视率数据集。

通过其实现和应用,我们已经获得了更多有关 RBM 的知识。 按照承诺,在下一节中,我们将介绍 RBM 的堆叠架构-DBN。

DBN 及其在 TensorFlow 中的实现

DBN 就是一组堆叠在一起的 RBM,其中一个 RBM 的隐藏层是下一个 RBM 的可见层。 在训练层的参数期间,前一层的参数保持不变。 换句话说,以顺序方式逐层训练 DBN 模型。 通过将每个层添加到顶部,我们可以从先前提取的特征中提取特征。 这就是深度架构的来源,也是 DBN 分层特征检测器的成因。

要实现 DBN,我们需要重用 RBM 类中的大多数代码,因为 DBN 由一系列 RBM 组成。 因此,我们应该为每个 RBM 模型的参数明确定义变量范围。 否则,我们将为多个 RBM 类引用同一组变量,这在 TensorFlow 中是不允许的。 因此,我们添加了一个属性 ID,并使用它来区分不同 RBM 模型的参数:

>>> class RBM(object):
...     def __init__(self, num_v, id, num_h, batch_size,
                      learning_rate, num_epoch, k=2):
...     self.num_v = num_v
...     self.num_h = num_h
...     self.batch_size = batch_size
...     self.learning_rate = learning_rate
...     self.num_epoch = num_epoch
...     self.k = k
...     self.W, self.a, self.b = self._init_parameter(id)
...
>>> def _init_parameter(self, id):
...     """ Initializing parameters the the id-th model
                including weights and bias """
...     abs_val = np.sqrt(2.0 / (self.num_h + self.num_v))
...     with tf.variable_scope('rbm{}_parameter'.format(id)):
...         W = tf.get_variable('weights', shape=(self.num_v,
                self.num_h), initializer=tf.random_uniform_initializer(
                minval=-abs_val, maxval=abs_val))
...         a = tf.get_variable('visible_bias', shape=(self.num_v),
                            initializer=tf.zeros_initializer())
...         b = tf.get_variable('hidden_bias', shape=(self.num_h),
                            initializer=tf.zeros_initializer())
...     return W, a, b

而且,训练的 RBM 的隐藏向量被用作下一个 RBM 的输入向量。 因此,我们定义了一种额外的方法来简化此操作,如下所示:

>>> def hidden_layer(self, v, parameters):
...     """
...     Computing hidden vectors
...     @param v: input vectors
...     @param parameters: trained RBM parameters
...     """
...     W, a, b = parameters
...     h = 1 / (1 + np.exp(-(b + np.matmul(v, W))))
...     return h

RBM 类的其余部分与我们先前实现的类相同。 现在,我们可以处理 DBN,如下所示:

>>> class DBN(object):
...     def __init__(self, layer_sizes, batch_size, 
                    learning_rates, num_epoch, k=2):
...     self.rbms = []
...     for i in range(1, len(layer_sizes)):
...         rbm = RBM(num_v=layer_sizes[i-1], id=i,
                      num_h=layer_sizes[i], batch_size=batch_size,
                      learning_rate=learning_rates[i-1],
                      num_epoch=num_epoch, k=k)
...         self.rbms.append(rbm)

DBN 类接受的参数包括layer_sizes(每层的单元数,从第一个输入层开始),batch_sizelearning_rates(每个 RBM 单元的学习率列表),num_epoch和吉布斯步骤k

训练方法定义如下,其中在原始输入数据或先前隐藏层的输出上训练隐藏层的参数:

...     def train(self, X_train):
...         """
...         Model training
...         @param X_train: input data for training
...         """
...         self.rbms_para = []
...         input_data = None
...         for rbm in self.rbms:
...             if input_data is None:
...                 input_data = X_train.copy()
...             parameters = rbm.train(input_data)
...             self.rbms_para.append(parameters)
...             input_data = rbm.hidden_layer(input_data, parameters)

使用训练过的参数,predict方法将计算最后一层的输出,如下所示:

...     def predict(self, X):
...         """
...         Computing the output of the last layer
...         @param X: input data for training
...         """
...         data = None
...         for rbm, parameters in zip(self.rbms, self.rbms_para):
...             if data is None:
...                 data = X.copy()
...             data = rbm.hidden_layer(data, parameters)
...         return data

最后一层的输出是提取的特征,这些特征用于下游任务,例如分类,回归或聚类。 在下一节中,我们将说明如何将 DBN 应用于图像分类。

用于图像分类的 DBN

我们将使用的数据集由1797 10 类手写数字图像组成。 每个图像的尺寸为8 x 8,每个像素值的范围为 0 到 16。让我们读取数据集并将数据缩放到01的范围,然后将其分为训练和测试集,如下所示 :

>>> from sklearn import datasets
>>> data = datasets.load_digits()
>>> X = data.data
>>> Y = data.target
>>> print(X.shape)
(1797, 64)
>>> X = X / 16.0
>>> np.random.seed(1)
>>> from sklearn.model_selection import train_test_split
>>> X_train, X_test, Y_train, Y_test = 
                train_test_split(X, Y, test_size = 0.2)

我们使用一个分别具有两个256512隐藏单元隐藏层的 DBN,并在训练集上对其进行训练,如下所示:

>>> dbn = DBN([X_train.shape[1], 256, 512], 10, [0.05, 0.05], 20, k=2)
>>> dbn.train(X_train)
Training error at epoch 10: 0.0816881338824759
Training error at epoch 20: 0.07888000140656957
Training error at epoch 10: 0.005190357937106303
Training error at epoch 20: 0.003952089745968164

使用训练有素的 DBN,我们为训练和测试集生成最后一个隐藏层的输出向量,如以下代码所示:

>>> feature_train = dbn.predict(X_train)
>>> feature_test = dbn.predict(X_test)
>>> print(feature_train.shape)
(1437, 512)
>>> print(feature_test.shape)
(360, 512)

然后,我们将提取的 512 维特征输入到逻辑回归模型中以完成数字分类任务,如下所示:

>>> from sklearn.linear_model import LogisticRegression
>>> lr = LogisticRegression(C=10000)
>>> lr.fit(feature_train, Y_train)

整个算法的流程如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pSAGx8uN-1681704767267)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/4b2953f5-915b-47f0-a1b1-b773f48ece30.png)]

最后,我们使用经过训练的逻辑回归模型来预测从测试集中提取的特征,如下所示:

>>> print(lr.score(feature_test, Y_test))
0.9777777777777777

用这种方法可以达到 97.8% 的分类精度。

什么是自编码器?

在上一部分中,我们刚刚学习了 RBM 及其变体 DBN,并获得了实践经验。 回想一下,RBM 由输入层和隐藏层组成,后者试图通过查找输入的潜在表示来重建输入数据。 从本节开始,我们将学习的神经网络模型自编码器AE)具有相似的想法。 基本 AE 由三层组成:输入层,隐藏层和输出层。 输出层是通过隐藏层的输入的重建。 AE 的一般图如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wfzXHVFo-1681704767267)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/3c4a7a71-d7ee-4437-8535-ea5452e3f8a7.png)]

可以看到,当自编码器接收数据时,它首先对其进行编码以适合隐藏层,然后尝试将其重新构造回原始输入数据。 同时,隐藏层可以提取输入数据的潜在表示。 由于这种结构,网络的前半部分称为编码器,该编码器将输入数据压缩为潜在表示。 相反,后半部分是解码器,用于对提取的表示进行解压缩。

AE 和 RBM 都旨在最小化重构误差,但是 AE 与 RBM 在以下方面有所不同:

  • AE 以判别性方式了解隐藏表示,而无需考虑输入数据的概率分布
  • RBM 通过从隐藏层和输入层中进行采样以随机方式找到隐藏表示

现在,让我们快速了解 AE 的发展历程,然后再将其应用于实际问题。

自编码器的发展路径

《无监督预训练的一种方法》首次引入自编码器作为神经网络中模块化学习。 然后《通过多层感知器进行的自动关联和奇异值分解》将它们用于降维,《自编码器,最小描述长度和亥姆霍兹 F 能量》将其用于线性特征学习。

自编码器随着时间的推移而发展,在过去的十年中提出了几种变体。 在 2008 年,P.Vincent 等人。 《使用降噪自编码器提取和构成稳健特征》介绍了去噪自编码器DAE), 网络被迫从损坏的版本中重建输入数据,以便他们可以学习更强大的特征。

I.Goodfellow 等开发了稀疏自编码器,它通过引入稀疏约束来扩大隐藏表示。 可以在《测量深度网络中的不变性》中找到详细信息。

压缩自编码器由 S. Rifai 在《压缩自编码器:特征提取期间的显式不变性》中提出。 将惩罚项添加到成本函数,以便网络能够提取对输入数据周围的微小变化不太敏感的表示形式。

2013 年,在《自编码变分贝叶斯》中,提出了一种称为变分自编码器VAE)的特殊类型,其中考虑了潜在变量的概率分布。

我们将在 Keras 中实现 AE 的几种变体,并使用它们来解决信用卡欺诈检测问题。

自编码器架构和应用

我们将从基本的原始 AE 开始,然后是深度版本,然后是稀疏自编码器,去噪自编码器,然后使用收缩自编码器结束。

在整个部分中,我们将以信用卡欺诈数据集为例,演示如何应用各种架构的自编码器。

原始自编码器

这是最基本的三层架构,非常适合于开始实现自编码器。 让我们准备数据集。 我们正在使用的数据集来自 Kaggle 竞赛,可以从这个页面中的Data页面下载。 每行包含 31 个字段,如下所示:

  • Time:自数据集中第一行以来的秒数
  • V1, V2, ..., V28:通过 PCA 获得的原始特征的主要成分
  • Amount:交易金额
  • Class1用于欺诈性交易,0否则

我们将数据加载到 pandas 数据框中,并删除Time字段,因为它提供的信息很少,如下所示:

>>> import pandas as pd
>>> data = pd.read_csv("creditcard.csv").drop(['Time'], axis=1)
>>> print(data.shape)
(284807, 30)

数据集包含 284,000 个样本,但高度不平衡,几乎没有欺诈性样本,如下所示:

>>> print('Number of fraud samples: ', sum(data.Class == 1))
Number of fraud samples: 492
>>> print('Number of normal samples: ', sum(data.Class == 0))
Number of normal samples: 284315

这里Data页面中的函数可视化面板中可以看出,V1 至 V28 是高斯标准分布,而Amount 不是。 因此,我们需要标准化Amount函数,如以下代码所示:

>>> from sklearn.preprocessing import StandardScaler
>>> scaler = StandardScaler()
>>> data['Amount'] =
         scaler.fit_transform(data['Amount'].values.reshape(-1, 1))

经过预处理后,我们将数据分为 80% 的训练和 20% 的测试,如下所示:

>>> import numpy as np
>>> np.random.seed(1)
>>> data_train, data_test = train_test_split(data, test_size=0.2)

正如我们所估计的那样,欺诈类仅占总人口的 0.17%,因此传统的监督学习算法可能很难从少数民族中选择足够的模式。 因此,我们求助于基于 AE 的无监督学习解决方案。 训练有素的自编码器可以完美地重建输入数据。 如果我们仅在正常样本上安装自编码器,则该模型将成为仅擅于再现非异常数据的正常数据重构器。 但是,如果我们将此模型输入异常输入,则重构输出和输入之间会有相对较大的差异。 因此,我们可以通过使用 AE 测量重建误差来检测异常。

因此,我们重组了训练和测试集,因为仅需要正常样本即可拟合模型,如下所示:

>>> data_test = data_test.append(data_train[data_train.Class == 1],
                                 ignore_index=True)
>>> data_train = data_train[data_train.Class == 0]

由于我们的方法不受监督,因此我们不需要训练目标。 因此,我们仅采用训练集中的特征,如下所示:

>>> X_train = data_train.drop(['Class'], axis=1).values
>>> X_test = data_test.drop(['Class'], axis=1).values
>>> Y_test = data_test['Class']

现在可以使用这些数据了。 现在是时候在 Keras 中构建原始自编码器了。 现在,让我们开始导入必要的模块,如下所示:

>>> from keras.models import Model
>>> from keras.layers import Input, Dense
>>> from keras.callbacks import ModelCheckpoint, TensorBoard
>>> from keras import optimizers

第一层是输入层,单元为29(输入数据为29-维度),如下所示:

>>> input_size = 29
>>> input_layer = Input(shape=(input_size,))

第二层是具有40单元的隐藏层,对输入数据进行编码,如下所示:

>>> hidden_size = 40
>>> encoder = Dense(hidden_size, activation="relu")(input_layer)

最后,还有最后一层,即输出层,其大小与输入层相同,它对隐藏的表示进行解码,如下所示:

>>> decoder = Dense(input_size)(encoder)

Python 深度学习架构实用指南:第一、二部分(4)https://developer.aliyun.com/article/1426974

相关文章
|
5天前
|
机器学习/深度学习 API 语音技术
|
5天前
|
机器学习/深度学习 PyTorch API
|
5天前
|
机器学习/深度学习 语音技术 算法框架/工具
|
26天前
|
机器学习/深度学习 数据采集 数据可视化
如何使用Python的PyBrain库进行深度学习?
PyBrain是Python的深度学习库,用于构建和训练模型。步骤包括安装库、导入模块、准备数据集、创建网络、训练、预测、评估和可视化。示例代码展示了如何使用PyBrain处理线性数据,包括数据预处理、构建2-3-1网络、BackpropTrainer训练、计算MSE误差和结果可视化。
12 0
|
1月前
|
机器学习/深度学习 人工智能 Serverless
20行代码,Serverless架构下用Python轻松搞定图像分类和预测
本文将AI项目与Serverless架构进行结合,在Serverless架构下用20行Python代码搞定图像分类和预测。
111479 125
|
1月前
|
API 开发者 微服务
深入浅出:使用Python构建微服务架构
在当今快速发展的软件行业中,微服务架构因其高度的灵活性和可扩展性而成为了一种流行趋势。本文将探讨如何使用Python语言,结合Flask框架来构建一个简单的微服务架构。我们将从微服务的基本概念入手,逐步深入到实现细节,最后展示一个基于此架构的小型应用案例。不同于传统的摘要,本文旨在通过一个具体的实践案例,让读者能够直观地理解微服务架构的优势以及在Python环境下的实现方法。
|
1月前
|
机器学习/深度学习 PyTorch TensorFlow
有什么资源或教程可以帮助我学习深度学习和Python中的深度学习框架?
【2月更文挑战第14天】【2月更文挑战第39篇】有什么资源或教程可以帮助我学习深度学习和Python中的深度学习框架?
|
1月前
|
机器学习/深度学习 PyTorch TensorFlow
python实现深度学习模型(如:卷积神经网络)。
【2月更文挑战第14天】【2月更文挑战第38篇】实现深度学习模型(如:卷积神经网络)。
|
2月前
|
机器学习/深度学习 JSON 自然语言处理
python自动化标注工具+自定义目标P图替换+深度学习大模型(代码+教程+告别手动标注)
python自动化标注工具+自定义目标P图替换+深度学习大模型(代码+教程+告别手动标注)
45 0
|
2月前
|
机器学习/深度学习 算法 TensorFlow
使用Python实现基于深度学习的图像分类器
本文介绍了如何使用Python编写一个基于深度学习的图像分类器。我们将使用TensorFlow框架和Keras库来建立模型,并使用MNIST手写数字数据集进行训练和测试。通过本文,您将了解到如何设计和训练一个卷积神经网络(CNN),并将其应用于图像分类任务中。