该篇文档基于 kaggle course, 通过简单的理论介绍, 程序代码, 运行图以及动画等来帮助大家入门深度学习, 既然是入门, 所以没有太多模型推导以及高级技巧相关, 都是深度学习中最基础的内容, 希望大家看过之后可以自己动手基于 Tensorflow 或者 keras 搭建一个处理回归或者分类问题的简单的神经网络模型, 并通过 dropout 等手段优化模型结果;
每部分都有对应的练习, 练习都是很有针对性的, 而且都很有趣, 尤其是一些练习中都写好了动画的可视化展示, 还是很有心的;
目录:
概述
线性模型: 单神经元
非线性模型: 深度神经网络
模型训练: 随机梯度下降
验证模型: 过拟合和欠拟合
提升性能: Dropout 和 Batch Normalization
分类问题
概述
经过本篇文章, 你将搭建自己的深度神经网络, 使用 Keras 和 Tensorflow, 创建全连接神经网络, 在分类和回归问题上应用神经网络, 通过随机梯度下降训练网络, 通过 dropout 等技术提升模型性能;
近些年在 AI 方面的主要发展都在深度学习, 尤其是应用于自然语言处理, 图像识别, 游戏 AI 等领域, 深度学习能得到更接近于人类的结果;
深度学习是一种允许大量深度计算为特征的机器学习方法, 深度计算使得深度学习模型可以理解真实世界数据中的复杂和高维的信息模式, 比如这句话的含义是什么, 这张图中的人在干嘛等等;
通过这种优势和灵活性, 神经网络成为深度学习的定义模型, 神经网络由神经元组成, 每个神经元单独看只是一个简单的计算单元, 神经网络的能力来自于许多神经元之间的复杂的组合模式;
单个神经元
线性单元
只有一个输入的线性单元对应公式如下:
y = w*x+b
x 为输入, 神经元连接的权重为 w,w 的更新就是神经网络学习的过程, b 为偏差, 它与输入没有关系, 偏差允许神经元不依赖输入来修改输出, y 是神经元的输出, 即公式 y=w*x+b 的结果;
线性单元作为模型的例子
神经元通常作为神经网络的一部分, 往往也会将一个单独的神经元模型作为基准模型, 单神经元模型是线性模型;
假设我们使用糖分作为输入训练模型, 卡路里作为输出, 假设偏差 b 为 90, 权重 w 为 2.5, 当糖分为 5 时, 卡路里为 2.5*5+90=102.5;
多个输入
当我们期望使用多个输入而不是一个时, 其实就是将多个输入连接并神经元, 计算每个连接权重, 并全部加起来得到最终输出, 如下:
y = w_0x_0 + w_1x_1 + w_2*x_2 + b
上述公式使用了三个输入, 并分别对应各自的连接权重, 从输入维度上看, 单个输入拟合一条直线, 两个输入你和一个平面, 多个输入拟合的则是超平面;
Keras 中使用线性单元
最简单的创建线性单元模型是通过 keras.Sequential, 可以通过 dense 层来创建上述提到的线性单元模型, 对于一个有三个输入, 一个输出的线性模型, Keras 创建方式如下:
- from tensorflow import keras
- from tensorflow.keras import layers
- # Create a network with 1 linear unit
- model = keras.Sequential([
- layers.Dense(units=1, input_shape=[3])
- ])
其中 units 为 1 表示该层只有一个输出, input_shape 为 [3] 则表示有 3 个输入, 之所以参数是个列表[], 这是因为在图像领域可能需要三维输入, 比如[高度, 宽度, 通道];
线性单元练习
可以通过这个 notebook 来进行这部分的练习, 里面包含了如何通过 keras 搭建线性单元的神经元模型, 并通过其 weights 属性来查看模型的连接权重和偏差, 最后还有一个未训练的模型在预测中的表现, 可以看到其随机权重在每次运行结果都不一样;
深度神经网络
层
典型的神经网络通过层来组织他们的神经元, 当我们把线性单元整理到一起时, 我们就得到了一个 dense 层, 神经网络通过叠加 dense 层来将输入以越来越复杂的方式进行转换, 在一个训练好的神经网络模型, 每一层都会将输入转换的更接近结果一点;
激活函数
激活函数作用于层的输出, 最常用的是整流函数 max(0,x), 纠正函数将负部分处理为 0, 当我们将整流函数应用于一个线性单元时, 也就得到了 ReLU, 而之前的线性公式:
y=w*x+b
也变成了:
y = max(0, w*x+b)
可以看到, 函数也从线性转为了非线性, 整流函数图像如下:
堆叠 dense 层
输出层之前通常有一些隐含层, 一般我们不能直接看到他们的输出(因为他们的输出并不是最后输出, 而是作为下一层的输入, 因此无法直接看到), 注意当处理回归问题时, 最后一层也就是输出层是线性单元, 也就是没有应用激活函数, 当我们要处理分类或者其他问题时, 仍然需要对应的激活函数;
通过 keras.Sequential 创建多层神经网络方式很简单, 只要从第一层到最后一层依次通过 layer 定义即可, 第一层获取输入, 最后一层产生输出, 代码如下:
- from tensorflow.keras import layers
- model = keras.Sequential([
- # the hidden ReLU layers
- layers.Dense(units=4, activation='relu', input_shape=[2]),
- layers.Dense(units=3, activation='relu'),
- # the linear output layer
- layers.Dense(units=1),
- ])
其中各个 layer 表示各个堆叠的网络层, activation 表示各个层的激活函数, 可以看到最后一层是没有的, 这是因为它处理的是回归问题, 且最后一层输出只有一个, 而其他层则不一定;
深度神经网络练习
你可以通过这个 notebook 来进行这部分练习, 其中包含如何通过 keras.Sequential 搭建 3 个隐含层 1 个输出层的非线性神经网络模型, 以及如何使用单独的激活层来代替 activation 参数, 以及 ReLU,eLU,SeLU,swish 等各个激活函数的差异, 实验证明 ReLU 适用于大多数场景, 因此最适合作为初始激活函数选择, 下面给出各个接获函数的图像:
relu:
elu:
selu:
swish:
随机梯度下降
在之前创建的神经网络模型中, 网络中的权重都是随机指定的, 此时的模型还没有学习到任何东西, 这也是第一个练习中每次运行结果都不一样的原因;
所谓训练一个神经网络, 指的是通过某种方式不断更新网络中的权重, 使得模型通过输入可以得到期望的输出, 如果可以做到, 那么也说明了这些权重在某种程度上表达了输入特征与输出之间的关系;
训练模型需要两个必要元素:
损失函数: 衡量模型预测结果好坏;
优化方法: 指导模型如何去修改权重;
损失函数
损失函数用于衡量模型的预测值与真实值之间的差异, 不同的问题使用的损失函数一般也是不同的, 例如对于回归问题, 即我们要预测的是数值, 一个常用的用于回归问题的损失函数为 MAE, 即平均绝对误差, 对于每个预测值 y_pred,MAE 计算它与 y_true 的差值的绝对值, 所有这些绝对值取平均就是 MAE 的结果, 除了 MAE, 用于回归问题的还有很多损失函数, 比如 MSE,MASE,Huber loss 等等, 对于模型来说, 在训练过程中, 损失函数起到向导的作用, 最小化损失函数就是模型要解决的问题, 以此来指导网络中权重的更新方向;
优化方法 - 随机梯度下降
通过损失函数我们确定了模型要解决的问题, 但是依然需要告知模型如何去解决这个问题, 此时就需要一种优化方法, 优化方法是一种最小化损失的算法;
实际上所有应用于深度学习的优化算法都属于随机梯度下降族, 它们都是迭代算法, 一步一步的训练模型, 每一步的训练过程如下:
抽样部分训练数据, 通过模型运行得到预测结果 y_pred;
测量这些 y_pred 与 y_true 之间的损失函数值;
通过损失更小的方向来修改权重;
上述过程一遍一遍的运行, 直到损失为 0 或者损失无法再下降为止;
迭代中从训练集中抽样的部分称之为 minibatch, 或者一般直接叫做 batch, 每一轮完整的训练称之为 epoch,epoch 的数量决定了模型使用各个数据点的次数;
理想的训练过程中, 权重不断更新, 损失不断减少, 预测值越来越接近于真实值;
学习率和 Batch Size
学习率决定了模型在每一个 batch 上学习到的内容的大小, 学习率越小意味着模型需要更多的 batch 来帮助其学习, 学习率和 batch size 是两个训练过程中影响很大的参数, 通常也是主要要调的超参数;
可惜的是, 对于很多情况下都没有必要通过非常耗时的超参数调整来获取最优的结果, Adam 是一种不需要设置学习率的随机梯度下降算法, 它不需要调试任何参数, 或者说它是自调整的, 因此它成为一种很好的通用优化方法;
添加损失函数和优化方法
在定义模型后, 可以通过模型的 compile 方法添加损失函数和优化方法:
- model.compile(
- optimizer="adam",
- loss="mae",
- )
例子 - 红酒品质
数据格式如下, 最后一列为预测目标列:
fixed acidity | volatile acidity | citric acid | residual sugar | chlorides | free sulfur dioxide | total sulfur dioxide | density | pH | sulphates | alcohol | quality |
---|---|---|---|---|---|---|---|---|---|---|---|
10.8 | 0.470 | 0.43 | 2.10 | 0.171 | 27.0 | 66.0 | 0.99820 | 3.17 | 0.76 | 10.8 | 6 |
8.1 | 0.820 | 0.00 | 4.10 | 0.095 | 5.0 | 14.0 | 0.99854 | 3.36 | 0.53 | 9.6 | 5 |
9.1 | 0.290 | 0.33 | 2.05 | 0.063 | 13.0 | 27.0 | 0.99516 | 3.26 | 0.84 | 11.7 | 7 |
10.2 | 0.645 | 0.36 | 1.80 | 0.053 | 5.0 | 14.0 | 0.99820 | 3.17 | 0.42 | 10.0 | 6 |
可以看到, 除了最后一列总有 11 列作为输入, 神经网络搭建代码如下:
- from tensorflow import keras
- from tensorflow.keras import layers
- model = keras.Sequential([
- layers.Dense(512, activation='relu', input_shape=[11]),
- layers.Dense(512, activation='relu'),
- layers.Dense(512, activation='relu'),
- layers.Dense(1),
- ])
看到网络由 3 个隐含层和 1 个输出层组成, 其中隐含层的 units 均为 512, 表示每个隐含层输出都有 512 个, 第一层负责接受输入, 最后一层输出结果;
定义完了网络结构, 下面需要设置训练需要使用的损失函数和优化方法:
- model.compile(
- optimizer='adam',
- loss='mae',
- )
任务为回归预测, 损失函数选择平均绝对误差, 优化器使用 adam;
训练前的准备已经就绪, 下面需要告诉模型训练使用的 batch 数量, 迭代次数等信息:
- history = model.fit(
- X_train, y_train,
- validation_data=(X_valid, y_valid),
- batch_size=256,
- epochs=10,
- )
对于训练过程中的 loss 进行可视化后可以更好的观察模型的整个迭代过程:
- import pandas as pd
- # convert the training history to a dataframe
- history_df = pd.DataFrame(history.history)
- # use Pandas native plot method
- history_df['loss'].plot();
可以看到, 在迭代次数达到 6 次时, 后续的迭代中 loss 的下降不明显, 甚至还有变大的情况出行, 一般来说这说明迭代次数足够了;
模型训练练习
这部分练习可以通过这个 notebook, 其中包含了完整的神经网络模型, 从定义到设置其损失和优化方法, 再到最后的训练过程, 并通过很有趣的动画方式展示了在不同的学习率, batch size, 样本数量等情况下的模型迭代过程, 对于理解各个参数的作用非常有帮助哦, 这里展示其中一组参数下的训练过程:
过拟合和欠拟合
过拟合和欠拟合是机器学习中绕不开的两个问题, 通常我们可以使用学习曲线来观察模型迭代表现并判断其当前属于过拟合还是欠拟合, 通常来说过拟合指的是模型过于复杂, 将数据中的噪声部分也拟合了, 因此使得模型在真实数据上的表现明显差于在训练集的表现, 而欠拟合则指的是模型在训练集上都没有达到足够好的效果, 可能是因为模型太简单, 也可能是因为数据量太大;
容量
容量指的是模型可以学习到的数据模式的复杂度大小, 或者说容量越大的模型, 越能深入的理解数据, 对于神经网络来说, 可以通过增加其宽度和高度来扩大其模型容量;
所谓增大网络宽度指的是增加已有层中的神经元个数, 而增大高度指的是增加新的层, 一般来说使用同样的神经元个数, 增加高度带来的容量增益要大于增加宽度, 简单理解如下:
假设当前网络有两层, 每一层都有 3 个神经元, 则其组合为 3*3=9, 此时我们要增加 2 个神经元:
如果是用于增加宽度, 每层增加一个神经元变为 4 个, 则有 4*4=16;
如果是用于增加高度, 增加一个单独的层, 有 2 个神经元, 则有 3*3*2=18;
因此都是使用了两个神经元, 从结果上看是高度的收益更大, 当然这个只是一种直观理解, 实际的解释要比这个复杂的多;
提前停止训练
对于模型训练过程, 尤其是基于真实数据的训练过程, 很多时候是无法完全收敛的, 而我们需要保证训练一定可以结束而不是无限运行下去的, 因此可以通过 Early Stopping 来控制其迭代在满足某些条件下提前结束;
增加 Early Stopping
keras 通过 callback 的方式添加 Early Stopping, 所谓 callback 指的是在每次 epoch 后运行的内容, 用于判断是否应该终止训练过程:
- from tensorflow.keras.callbacks import EarlyStopping
- early_stopping = EarlyStopping(
- min_delta=0.001, # minimium amount of change to count as an improvement
- patience=20, # how many epochs to wait before stopping
- restore_best_weights=True,
- )
上述代码的含义是, 如果连续 20 次迭代, 每次的 loss 下降都不足 0.001, 那么训练终止, 反正目前为止表现最好的权重数据;
例子 - 使用 Early Stopping 训练模型
还是之前的红酒例子, 数据格式如下:
fixed acidity | volatile acidity | citric acid | residual sugar | chlorides | free sulfur dioxide | total sulfur dioxide | density | pH | sulphates | alcohol | quality |
---|---|---|---|---|---|---|---|---|---|---|---|
10.8 | 0.470 | 0.43 | 2.10 | 0.171 | 27.0 | 66.0 | 0.99820 | 3.17 | 0.76 | 10.8 | 6 |
8.1 | 0.820 | 0.00 | 4.10 | 0.095 | 5.0 | 14.0 | 0.99854 | 3.36 | 0.53 | 9.6 | 5 |
9.1 | 0.290 | 0.33 | 2.05 | 0.063 | 13.0 | 27.0 | 0.99516 | 3.26 | 0.84 | 11.7 | 7 |
10.2 | 0.645 | 0.36 | 1.80 | 0.053 | 5.0 | 14.0 | 0.99820 | 3.17 | 0.42 | 10.0 | 6 |
模型定义, 指定 loss 和优化器, 指定 Early Stopping 代码如下:
- from tensorflow import keras
- from tensorflow.keras import layers
- from tensorflow.keras.callbacks import EarlyStopping
- early_stopping = EarlyStopping(
- min_delta=0.001, # minimium amount of change to count as an improvement
- patience=20, # how many epochs to wait before stopping
- restore_best_weights=True,
- )
- model = keras.Sequential([
- layers.Dense(512, activation='relu', input_shape=[11]),
- layers.Dense(512, activation='relu'),
- layers.Dense(512, activation='relu'),
- layers.Dense(1),
- ])
- model.compile(
- optimizer='adam',
- loss='mae',
- )
- history = model.fit(
- X_train, y_train,
- validation_data=(X_valid, y_valid),
- batch_size=256,
- epochs=500,
- callbacks=[early_stopping],
- verbose=0, # turn off training log
- )
- history_df = pd.DataFrame(history.history)
- history_df.loc[:, ['loss', 'val_loss']].plot();
- print("Minimum validation loss: {}".format(history_df['val_loss'].min()))
以上, 通过 fit 方法的 callbacks 参数将 Early Stopping 作为一个 callback 添加到了迭代过程中, 用于控制训练的提前结束, 运行图如下:
结合代码和上图可以看到, 虽然我们设置了 epoch 为 500, 但是在迭代不到 70 次时就终止了, 这就是 Early Stopping 在起作用, 一定程度上可以避免不必要的训练过程, 减少训练时间;
过拟合和欠拟合的练习
这部分练习可以通过这个 notebook 完成, 这里有通过训练简单线性模型和复杂神经网络模型等, 并通过学习曲线来观察模型的拟合情况, 并通过添加 Early Stopping 来控制过拟合情况;
Dropout 和 Batch Normalization
实际的神经网络结构中往往包含更多的层, 不仅仅是 dense 层, 比如激活层, Dropout 层等等, 有些类似 dense 层, 定义神经元的连接, 而有些则是用于预处理和转换等;
Dropout
Dropout 层有助于纠正过拟合问题, 在每次训练迭代中, 随机的去掉网络层中的一部分输入单元, 使得模型难以从训练数据学习到错误的模式, 取而代之的是模型会搜索更普遍适用的模式, 也就是具有更好的鲁棒性的模式, 借此解决过拟合问题;
可以把 Dropout 看作是一种集成方法, 与随机森林类似, Dropout 的随机抽取类似随机森林的行抽取和列抽取, 二者的目的都是解决原始模型的过拟合问题, 思路是一样的;
增加 Dropout
在 keras 中, Drouput 作为层使用, 作用于其下的一层, 通过参数 rate 指定随机取出的比例:
- keras.Sequential([
- # ...
- layer.Dropout(rate=0.3), # apply 30% dropout to the next layer
- layer.Dense(16),
- # ...
- ])
- Batch Normalization
模型在迭代过程中, 权重的更新主要由 loss 和 optimater 决定, 假设我们的输入特征的量纲不一致, 比如有的特征范围从 0 到 1, 有的特征是从 - 100 到 + 100, 那么在优化器计算过程中就会产生差异很大的结果, 并使得训练过程很不稳定, 体现就是学习曲线的波动严重;
一个小栗子: 比如我们要预测房价, 目前有两个属性, 一个是面积, 范围是 10 到 200, 另一个是距离火车站距离, 范围是 100 到 100000, 如果不进行量纲统一, 可以遇见的是在计算过程中由于火车站距离值更大, 因此会影响对结果的预测, 或者说这个范围一定程度上参与了原来权重该起到的作用;
Batch Normalization 类似 SKLearn 里的 StandardScaler 和 MinMaxScaler 的作用, 用于将输入特征的量纲统一, 避免因为量纲不同导致对于预测结果影响的权重差异;
增加 Batch Normalization
可以用在某一层之后:
- layers.Dense(16, activation='relu'),
- layers.BatchNormalization(),
也可以用在某一层和它的激活层之间:
- layers.Dense(16),
- layers.BatchNormalization(),
- layers.Activation('relu'),
例子 - 使用 Dropout 和 Batch Normalization
继续红酒例子, 在每一个隐含层后都先加一个 Dropout 过滤一部分输入解决过拟合, 再应用 Batch Normalization 优化不稳定情况:
- from tensorflow import keras
- from tensorflow.keras import layers
- model = keras.Sequential([
- layers.Dense(1024, activation='relu', input_shape=[11]),
- layers.Dropout(0.3),
- layers.BatchNormalization(),
- layers.Dense(1024, activation='relu'),
- layers.Dropout(0.3),
- layers.BatchNormalization(),
- layers.Dense(1024, activation='relu'),
- layers.Dropout(0.3),
- layers.BatchNormalization(),
- layers.Dense(1),
- ])
训练过程不使用 Early Stopping:
- model.compile(
- optimizer='adam',
- loss='mae',
- )
- history = model.fit(
- X_train, y_train,
- validation_data=(X_valid, y_valid),
- batch_size=256,
- epochs=100,
- verbose=0,
- )
- # Show the learning curves
- history_df = pd.DataFrame(history.history)
- history_df.loc[:, ['loss', 'val_loss']].plot();
学习曲线如下:
可以看到, 首先虽然没有 Early Stopping, 但是过拟合问题不明显, 其次在迭代 20 次之后不稳定的情况基本消失了, 说明 Dropout 和 Batch Normalization 都起到了各自的作用;
Dropout 和 Batch Normalization 练习
这部分练习在这个 notebook 里, 其中分别使用两个数据集, 对比其上应用 Dropout 与不应用, 应用 Batch Normalization 与不应用在学习曲线上的差异, 可以很直观的看到二者起到的作用;
下面是应用 Batch Normalization 后的学习曲线, 要知道在不应用的情况下曲线都无法绘制出来:
分类问题
之前处理的都是回归问题, 处理分类问题的区别只有以下两点:
损失函数: 分类与回归在损失函数应用上不同, 比如 MAE 和准确率;
输出层输出类型: 也就是网络结构最后一层输出的内容, 之前都是数值, 如果是二分类问题, 则应该是 0/1;
Sigmoid 函数
Sigmoid 函数同样作为激活函数, 它可以将实数输出映射到 0 到 1 之间, 也就是通常的概率范围, 而不管是准确率还是交叉熵等都可以利用概率来计算得到;
Sigmoid 函数图像如下, 上一个使用它的地方是逻辑回归, 同样是将线性回归的结果映射到 0 和 1 之间:
例子 - 二分类
数据格式如下:
V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 | ... | V26 | V27 | V28 | V29 | V30 | V31 | V32 | V33 | V34 | Class |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 0 | 0.99539 | -0.05889 | 0.85243 | 0.02306 | 0.83398 | -0.37708 | 1.00000 | 0.03760 | ... | -0.51171 | 0.41078 | -0.46168 | 0.21266 | -0.34090 | 0.42267 | -0.54487 | 0.18641 | -0.45300 | good |
1 | 0 | 1.00000 | -0.18829 | 0.93035 | -0.36156 | -0.10868 | -0.93597 | 1.00000 | -0.04549 | ... | -0.26569 | -0.20468 | -0.18401 | -0.19040 | -0.11593 | -0.16626 | -0.06288 | -0.13738 | -0.02447 | bad |
1 | 0 | 1.00000 | -0.03365 | 1.00000 | 0.00485 | 1.00000 | -0.12062 | 0.88965 | 0.01198 | ... | -0.40220 | 0.58984 | -0.22145 | 0.43100 | -0.17365 | 0.60436 | -0.24180 | 0.56045 | -0.38238 | good |
1 | 0 | 1.00000 | -0.45161 | 1.00000 | 1.00000 | 0.71216 | -1.00000 | 0.00000 | 0.00000 | ... | 0.90695 | 0.51613 | 1.00000 | 1.00000 | -0.20099 | 0.25682 | 1.00000 | -0.32382 | 1.00000 | bad |
1 | 0 | 1.00000 | -0.02401 | 0.94140 | 0.06531 | 0.92106 | -0.23255 | 0.77152 | -0.16399 | ... | -0.65158 | 0.13290 | -0.53206 | 0.02431 | -0.62197 | -0.05707 | -0.59573 | -0.04608 | -0.65697 | good |
像之前处理回归问题一样定义模型, 区别在于最后一层的激活函数选择 sigmoid 用于输出概率:
- from tensorflow import keras
- from tensorflow.keras import layers
- model = keras.Sequential([
- layers.Dense(4, activation='relu', input_shape=[33]),
- layers.Dense(4, activation='relu'),
- layers.Dense(1, activation='sigmoid'),
- ])
添加交叉熵和准确率到模型中, 继续使用 adam, 他在分类问题上表现依然很好:
- model.compile(
- optimizer='adam',
- loss='binary_crossentropy',
- metrics=['binary_accuracy'],
- )
使用 Early Stopping 控制训练过程:
- early_stopping = keras.callbacks.EarlyStopping(
- patience=10,
- min_delta=0.001,
- restore_best_weights=True,
- )
- history = model.fit(
- X_train, y_train,
- validation_data=(X_valid, y_valid),
- batch_size=512,
- epochs=1000,
- callbacks=[early_stopping],
- verbose=0, # hide the output because we have so many epochs
- )
分别观察其交叉熵和准确率的变化情况:
- history_df = pd.DataFrame(history.history)
- # Start the plot at epoch 5
- history_df.loc[5:, ['loss', 'val_loss']].plot()
- history_df.loc[5:, ['binary_accuracy', 'val_binary_accuracy']].plot()
- print(("Best Validation Loss: {:0.4f}" +\
- "\nBest Validation Accuracy: {:0.4f}")\
- .format(history_df['val_loss'].min(),
- history_df['val_binary_accuracy'].max()))
交叉熵:
准确率:
分类练习
这部分练习在这个 notebook, 很完整的一个分类模型搭建过程, 从基于结构图创建神经网络结构到添加 loss 和优化器, 使用 Early Stopping 等都有, 包括对于结果是否过拟合和欠拟合的讨论等, 可以通过这个 notebook 再次练习下整个深度学习流程, 麻雀虽小, 五脏俱全;
交叉熵:
准确率:
最后
对于深度学习还有很多很多可以学习的内容, 本篇文章以最简单的方式对其中各个基础模块进行介绍, 并结合代码和运行结果图等进行说明, 希望看完能够在脑海中形成对于深度学习的一个感性认识;
最后的最后
欢迎大佬们关注我的公众号: 尼莫的 AI 小站, 新开的公众号, 后续会不定期更新有关机器学习, 深度学习, 数据处理分析, 游戏的内容;
来源: https://www.cnblogs.com/helongBlog/p/13816768.html