深度神经网络终极指南:从数学本质到工业级实现(附Keras版本代码)
为什么深度学习需要重新理解?(与浅层模型的本质差异)
模型类型 | 参数容量 | 特征学习方式 | 适合问题类型 |
---|---|---|---|
浅层模型 | 102-104 | 手动特征工程 | 低维结构化数据 |
深度模型 | 106-109 | 自动特征提取 | 高维非结构化数据 |
一、用Keras单隐层网络预测客户流失率
1.1数据的准备与分析
import numpy as np #导入NumPy数学工具箱
import pandas as pd #导入Pandas数据处理工具箱
df_bank = pd.read_csv("/kaggle/input/deep-neural-networks-data/BankCustomer.csv") # 读取文件
df_bank.head() # 显示文件前5行
显示分布情况
import matplotlib.pyplot as plt #导入matplotlib画图工具箱
import seaborn as sns #导入seaborn画图工具箱
# 显示不同特征的分布情况
features=[ 'City', 'Gender','Age','Tenure',
'ProductsNo', 'HasCard', 'ActiveMember', 'Exited']
fig=plt.subplots(figsize=(15,15))
for i, j in enumerate(features):
plt.subplot(4, 2, i+1)
plt.subplots_adjust(hspace = 1.0)
sns.countplot(x=j,data = df_bank)
plt.title("No. of costumers")
从图中大概看得出,北京的客户最多,男女客户比例大概一致,年龄和客户数量呈现正态分布(钟形曲线,中间高两边低)。
对这个数据集,主要做以下3方面的清理工作。
(1)性别:二元类别特征,需要转换为0/1代码格式进行读取处理。
(2)城市:一个多元类别特征,应把转换为多个二元类别哑变量。
(3)姓名:这个字段对于客户流失与否的预测应该是完全不相关的,可以进一步处理之前忽略。
当然原始数据集的标签也应该除去,放置于标签y。
# 把二元类别文本数字化
df_bank['Gender'].replace("Female",0,inplace = True)
df_bank['Gender'].replace("Male",1,inplace=True)
# 显示数字类别
print("Gender unique values",df_bank['Gender'].unique())
# 把多元类别转换成多个二元哑变量,然后贴回原始数据集
d_city = pd.get_dummies(df_bank['City'], prefix = "City")
df_bank = [df_bank, d_city]
df_bank = pd.concat(df_bank, axis = 1)
# 构建特征和标签集合
y = df_bank ['Exited']
X = df_bank.drop(['Name', 'Exited','City'], axis=1)
X.head() #显示新的特征集
1.2先尝试逻辑回归算法
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, random_state=0)
from sklearn.linear_model import LogisticRegression # 导入Sklearn模型
lr = LogisticRegression() # 逻辑回归模型
history = lr.fit(X_train,y_train) # 训练机器
print("逻辑回归测试集准确率 {:.2f}%".format(lr.score(X_test,y_test)*100))
逻辑回归测试集准确率 78.35%
1.3 单隐层神经网络的Keras实现
1.3.1 用序贯模型构建网络
import keras # 导入Keras库
from keras.models import Sequential # 导入Keras序贯模型
from keras.layers import Dense # 导入Keras密集连接层
ann = Sequential() # 创建一个序贯ANN(Artifical Neural Network)模型
ann.add(Dense(units=12, input_dim=12, activation = 'relu')) # 添加输入层
ann.add(Dense(units=24, activation = 'relu')) # 添加隐层
ann.add(Dense(units=1, activation = 'sigmoid')) # 添加输出层
ann.summary() # 显示网络模型(这个语句不是必须的)
序贯(sequential)模型,也可以叫作顺序模型,是最常用的深度网络层和层间的架构,也就是一个层接着一个层,顺序地堆叠。
密集(dense)层,是最常用的深度网络层的类型,也称为全连接层,即当前层和其下一层的所有神经元之间全有连接。
模型的创建:ann = Sequential()
创建了一个序贯神经网络模型(其实就是一个Python的类)。
在Keras中,绝大多数的神经网络都是通过序贯模型所创建的。与之对应的还有另外一种模型,称为函数式API,可以创建更为复杂的网络结构。
输入层:通过add方法,可开始神经网络层的堆叠,序贯模型,也就是一层一层的顺序堆叠。
Dense
是层的类型,代表密集层网络,是神经网络层中最基本的层,也叫全连接层。
input_dim
是输入维度,输入维度必须与特征维度相同。这里指定的网络能接收的输入维度是11。如果和实际输入网络的特征维度不匹配,Python就会报错。
unit
是输出维度,设置为12。该参数也可写为output_dim=12
,甚至忽略参数名,写为Dense(12,input_dim=11,activation=‘relu’),这些都是正确格式。
12这个值目前是随意选择的,这代表了经过线性变化和激活之后的假设空间维度,其实也就是神经元的个数。维度越大,则模型的覆盖面也越大,但是模型也就越复杂,需要的计算量也多。对于简单问题,12维也许是一个合适的数字:太多的话容易过拟合,太少的话(不要少于特征维度)则拟合能力不够。
activation
是激活函数,这是每一层都需要设置的参数。这里的激活函数选择的是“relu”,而不是Sigmoid。relu是神经网络中常用的激活函数。
隐层:。在输入层之后的所有层都不需要重新指定输入维度,因为网络能够通过上一层的输出自动地调整。
输出层:仍然是一个全连接层,指定的输出维度是1。因为对于二分类问题,输出维度必须是1。而对于多分类问题,有多少个类别,维度就是多少。
激活函数方面。对于二分类问题的输出层,Sigmoid是固定的选择。如果是用神经网络解决回归问题的话,那么输出层不用指定任何激活函数。
下面编译刚才建好的这个网络:
# 编译神经网络,指定优化器,损失函数,以及评估标准
ann.compile(optimizer = 'adam', S #优化器
loss = 'binary_crossentropy', #损失函数
metrics = ['acc']) #评估指标
- 优化器(
optimizer
):一般情况下,“adam
”或者“rmsprop
”都是很好的优化器选项。 - 损失函数(loss):对于二分类问题来说,基本上二元交叉熵函数(
binary_crossentropy
)是固定选项;如果是用神经网络解决线性的回归问题,那么均方误差函数是合适的选择。 - 评估指标(
metrics
):这里采用预测准确率acc
(也就是accuracy
的缩写,两者在代码中是等价的)作为评估网络性能的标准;而对于回归问题,平均误差函数是合适的选择。
1.4 训练单隐层神经网络
history = ann.fit(X_train, y_train, # 指定训练集
epochs=30, # 指定训练的轮次
batch_size=64, # 指定数据批量
validation_data=(X_test, y_test)) #指定验证集,这里为了简化模型,直接用测试集数据进行验证
1.5 训练的图形化展示
# 这段代码参考《Python深度学习》一书中的学习曲线的实现
def show_history(history): # 显示训练过程中的学习曲线
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(loss) + 1)
plt.figure(figsize=(12,4))
plt.subplot(1, 2, 1)
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
acc = history.history['acc']
val_acc = history.history['val_acc']
plt.subplot(1, 2, 2)
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()
show_history(history) # 调用这个函数,并将神经网络训练历史数据作为参数输入
二.分类数据–准确率不是唯一的指标
预测全部客户都不会离开,也就是标签y值永远为0。由于这个数据集中的客户流失率其实就是20%左右,因此就达到了80%的准确率。”
这甚至比我们的训练后的准确率还高。
2.1混滑矩阵、精确率、召回率和F1分数
假设有一个手机生产厂商,每天生产手机1 000部。某一天生产的手机中,出现了2个劣质品。
其中数据集真值和机器学习模型的合格品和劣质品个数如下表所示。
机器学习显示合格品999个,劣质品1个,准确率为99.9%。
然而从我们的目标来说,这个模型实际上是失败了。这个模型本就是为了检测劣质品而生(劣质品即标签值为1的阳性正样本),但一共有2个劣质品,只发现了1个,有50%的正样本没有测准。
因此,模型的好与不好,是基于用什么标准衡量。
为了评估这种数据集,需要引入一个预测值与真值组成的矩阵,4个象限从上到下、从左到右分别为:
- 真负(真值为负,预测为负,即TrueNegative,TN)
- 假正(真值为负,预测为正,即False Positive,FP)
- 假负(真值为正,预测为负,即False Negative,FN)
- 真正(真值为正,预测为正,即True Positive,TP)。
2.1.1 混淆矩阵(confusion matrix)
2.1.2 精确率
一个标准是精确率,也叫查准率,其公式是用“被模型预测为正的正样本”除以“被模型预测为正的正样本”与“被模型预测为负的正样本”的和。公式如下:
P r e c i s i o n = T P T P + F P = T P T o t a l P r e d i c t e d P o s t i v e Precision=\frac{TP}{TP+FP}=\frac{TP}{TotalPredictedPostive} Precision=TP+FPTP=TotalPredictedPostiveTP
因此,精确率是对“假正”的测量。本例的精确率为1/(1+0) =100%。
这样看来,这个模型相对于劣质品的精确率也不差。因为判定的一个劣质品果然是劣质品,而且没有任何合格品被判为劣质品。
2.1.3 召回率
召回率,也叫查全率。就是劣质品蒙混过了质检这关(劣质品识别为了合格品),“跑”出厂了,得召回来,销毁掉。
公式如下:
R e c a l l = T P T P + F P = T P T o t a l True Postive Recall=\frac{TP}{TP+FP}=\frac{TP}{Total\text{ True Postive}} Recall=TP+FPTP=Total True PostiveTP
为1/(1+1) = 50%。
所以这个模型对于劣质品来说,召回率不高。
2.1.4 F1分数
把精确率和召回率结合起来,就得到F1分数。
这是一个可以同时体现上面两个评估效果的标准,数学上定义为精确率和召回率的调和均值。它也是在评估这类样本分类数据不平衡的问题时,所着重看重的标准。
F 1 = 2 • P r e c i s i o n • R e c a l l P r e c i s i o n + R e c a l l F1=2•\frac{Precision•Recall}{Precision+Recall} F1=2•Precision+RecallPrecision•Recall
对于这种大量标签是普通值,一小部分标签是特殊值的数据集来说,这3个标准的重要性在此时要远远高于准确率。
2.2使用分类报告和混淆矩阵
利用Sklearn中的分类报告(classification report)功能来计算上面这几种标准。
from sklearn.metrics import classification_report # 导入分类报告
def show_report(X_test, y_test, y_pred): # 定义一个函数显示分类报告
if y_test.shape != (2000,1):
y_test = y_test.values # 把Panda series转换成Numpy array
y_test = y_test.reshape((len(y_test),1)) # 转换成与y_pred相同的形状
print(classification_report(y_test,y_pred,labels=[0, 1])) #调用分类报告
from sklearn.metrics import confusion_matrix # 导入混淆矩阵
def show_matrix(y_test, y_pred): # 定义一个函数显示混淆矩阵
cm = confusion_matrix(y_test,y_pred) # 调用混淆矩阵
plt.title("ANN Confusion Matrix") # 标题
sns.heatmap(cm,annot=True,cmap="Blues",fmt="d",cbar=False) # 热力图设定
plt.show() # 显示混淆矩阵
这段程序需要在模型的fit拟合之后执行,运行之后将给出目前机器的预测结果
三、特征缩放的魔力
数值过大的数据以及离群样本的存在会使函数曲线变得奇形怪状,从而影响梯度下降过程中的收敛。
而特征缩放,将极大地提高梯度下降(尤其是神经网络中常用的随机梯度下降)的效率。
公式如下:对于输入数据的每个特征(也就是输入数据矩阵中的一整列),减去特征平均值,再除以标准差,之后得到的特征平均值为0,标准差为1。
x ′ = x − m e a n ( x ) s t d ( x ) x^{\prime}=\frac{x-mean(x)}{std(x)} x′=std(x)x−mean(x)
mean = X_train.mean(axis=0) # 计算训练集均值
X_train -= mean # 训练集减去训练集均值
std = X_train.std(axis=0) # 计算训练集方差
X_train /= std # 训练集除以训练集标准差
X_test -= mean # 测试集减去训练集均值
X_test /= std # 测试集减去训练集均值
也可以直接使用Standard Scaler
工具:
from sklearn.preprocessing import StandardScaler # 导入特征缩放器
sc = StandardScaler() # 特征缩放器
X_train = sc.fit_transform(X_train) # 拟合并应用于训练集
X_test = sc.transform (X_test) # 训练集结果应用于测试集
我们重新运行逻辑回归和单隐层神经网络,查看效果。
逻辑回归:
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression() # 逻辑回归模型
history = lr.fit(X_train,y_train) # 训练机器
print("逻辑回归测试集准确率 {:.2f}%".format(lr.score(X_test,y_test)*100))
逻辑回归测试集准确率 80.50%
特征工程后的神经网络:
history = ann.fit(X_train, y_train, # 指定训练集
epochs=30, # 指定训练的轮次
batch_size=64, # 指定数据批量
validation_data=(X_test, y_test)) #指定验证集
y_pred = ann.predict(X_test,batch_size=10) # 预测测试集的标签
y_pred = np.round(y_pred) # 将分类概率值转换成0/1整数值
神经网络测试集准确率 86.15%
显示损失曲线和准确率曲线:
show_history(history)
显示混淆矩阵:
show_report(X_test, y_test, y_pred)
show_matrix(y_test, y_pred)
四、从单隐层神经网络到深度神经网络
4.1 梯度下降:正向传播和反向传播
深度神经网络的梯度下降和参数优化过程是通过优化器实现的,其中包括正向传播(forward propagation)算法,反向传播(Back Propagation, BP)算法。
正向传播:计算损失的过程。
反向传播:参数优化的过程。
不太理解并没有关系,只要理解了基本原理,就可以在这些工具、框架上对网络进行调试,我们要做的只是解决问题,而不是炫耀数学功底。”
通过正向传播和反向传播,神经网络实现了内部参数的调整。
说神经网络中的可调超参数,具体包括以下几个。
- 优化器
- 激活函数
- 损失函数
- 评估指标
4.2 梯度下降优化器
多种优化思路的集大成者—AdamAdam 全 称 为 Adaptive Moment Estimation , 相 当 于 Adaptive Momentum。
就目前而言,Adam是多种优化思路的集大成者,一般是优化器的首选项。
keras.optimizers.Adam(learning_rate=0.001, # 学习速率
beta_1=0.9, # 一阶动量指数衰减速率
beta_2=0.999, # 二阶动量指数衰减速率, 对于稀疏矩阵值应接近1
amsgrad=False)
4.3激活函数
ReLU函数后来人们就发现了能够解决梯度消失问题的ReLU(Rectified LinearUnit)函数。ReLU函数的特点是单侧抑制,输入信号小于等于0时,输出是0;输入信号大于0时,输出等于输入。
ReLU对于随机梯度下降的收敛很迅速,因为相较于Sigmoid和Tanh在求导时的指数运算,对Re LU求导几乎不存在任何计算量。其公式和图像如下:
f ( z ) = m a x ( 0 , z ) f(z)=max(0,z) f(z)=max(0,z)
用Re LU的时候,学习速率绝对不能设得太大,因为那样会“杀死”网络中的很多神经元。
4.4 损失函数
神经网络中损失函数的选择是根据问题类型而定的,指导原则如下。
对于连续值向量的回归问题,用均方误差损失函数:
# 对于连续值向量的回归问题
ann.compile(optimizer='adam',loss='mse') # 均方误差损失函数对于二分类问题,使用同样熟悉的二元交叉熵损失函数:
# 对于二分类问题
ann.compile(optimizer='adam',
loss='binary_crossentropy', # 二元交叉熵损失函数
metrics=['accuracy'])
对于多分类问题,如果输出是one-hot编码,则用分类交叉熵损失函数:
# 对于多分类问题
ann.compile(optimizer='adam',
loss='categorical_crossentropy', # 分类交叉熵损失函数
metrics=['accuracy'])
对于多分类问题,如果输出是整数数值,则使用稀疏分类交叉熵损失函数:
# 对于多分类问题
ann.compile(optimizer='adam',
loss='sparse_categorical_crossentropy', # 稀疏分类交叉熵损失函数
metrics=['accuracy'])
对于序列问题,如语音识别等,则可以用时序分类(Connectionist Temporal Classification, CTC) 等损失函数。
4.5 评估指标
神经网络的评估指标,也就是评估网络模型好不好的标准,这个标准也叫目标函数。
评估指标和损失函数有点相似,都是追求真值和预测值之间的最小误差
其差别在于:
损失函数作用于训练集,用以训练机器,为梯度下降提供方向;
评估指标(目标函数)作用于验证集和测试集,用来评估模型。
对于回归问题,神经网络中使用MAE作为评估指标是常见的。
对于普通分类问题,神经网络中使用准确率作为评估指标也是常见的,但是对于类别分布不平衡的情况,应辅以精确率、召回率、F1分数等其他评估指标。
损失函数和评估指标,有相似之处,但意义和作用又不尽相同。
五、用Keras深度神经网络预测客户流失率
ann = Sequential() # 创建一个序贯ANN模型
ann.add(Dense(units=12, input_dim=12, activation = 'relu')) # 添加输入层
ann.add(Dense(units=24, activation = 'relu')) # 添加隐层
ann.add(Dense(units=48, activation = 'relu')) # 添加隐层
ann.add(Dense(units=96, activation = 'relu')) # 添加隐层
ann.add(Dense(units=192, activation = 'relu')) # 添加隐层
ann.add(Dense(units=1, activation = 'sigmoid')) # 添加输出层
# 编译神经网络,指定优化器,损失函数,以及评估指标
ann.compile(optimizer = 'RMSprop', # 优化器
loss = 'binary_crossentropy', # 损失函数
metrics = ['acc']) # 评估指标
history = ann.fit(X_train, y_train, # 指定训练集
epochs=30, # 指定训练的轮次
batch_size=64, # 指定数据批量
validation_data=(X_test, y_test)) # 指定验证集
y_pred = ann.predict(X_test,batch_size=10) # 预测测试集的标签
y_pred = np.round(y_pred) # 将分类概率值转换成0/1整数值
- 较深的神经网络训练效率要高于小型网络,一两个轮次之后,准确率迅速提升到0.84以上,而单隐层神经网络需要好几轮才能达到这个准确率
- 从准确率上看,没有什么提升;而从F1分数上看,目前这个比较深的神经网络反而不如简单的单隐层神经网络,从0.58下降到0.55:
- 从损失函数图像上看,深度神经网络在几轮之后就开始出现过拟合的问题,而且验证集上损失的波动也很大。因为随着轮次的增加,训练集的误差值逐渐减小,但是验证集的误差反而越来越大了。
也就是说,网络的参数逐渐地对训练集的数据形成了过高的适应性。这对于较大网络来说的确是常见情况。
5.1 选择多种优化器
ann = Sequential() # 创建一个序贯ANN模型
ann.add(Dense(units=12, input_dim=12, activation = 'relu')) # 添加输入层
ann.add(Dense(units=24, activation = 'relu')) # 添加隐层
ann.add(Dense(units=48, activation = 'relu')) # 添加隐层
ann.add(Dense(units=96, activation = 'relu')) # 添加隐层
ann.add(Dense(units=192, activation = 'relu')) # 添加隐层
ann.add(Dense(units=1, activation = 'sigmoid')) # 添加输出层
# 编译神经网络,指定优化器,损失函数,以及评估标准
ann.compile(optimizer = 'adam', loss = 'binary_crossentropy', metrics = ['acc'])
history = ann.fit(X_train, y_train, epochs=30, batch_size=64, validation_data=(X_test, y_test))
y_pred = ann.predict(X_test,batch_size=10) # 预测测试集的标签
y_pred = np.round(y_pred) # 将分类概 率值转换成0/1整数值
更换优化器之后,重新训练、测试网络。发现最为关心的F1分数有所上升,上升至0.56,如下输出结果所示。但这仍然低于单隐层神经网络的0.58:
损失曲线显示,过拟合现象仍然十分严重。也许这个过拟合问题就是深层神经网络效率低的症结所在。
5.2 神经网络正则化:添加Dropout层
原理非常奇特:在某一层之后添加Dropout层,意思就是随机将该层的一部分神经元的输出特征丢掉(设为0),相当于随机消灭一部分神经元。
from keras.layers import Dropout # 导入Dropout
ann = Sequential() # 创建一个序贯ANN模型
ann.add(Dense(units=12, input_dim=12, activation = 'relu')) # 添加输入层
ann.add(Dense(units=24, activation = 'relu')) # 添加隐层
ann.add(Dropout(0.5)) # 添加Dropout
ann.add(Dense(units=48, activation = 'relu')) # 添加隐层
ann.add(Dropout(0.5)) # 添加Dropout
ann.add(Dense(units=96, activation = 'relu')) # 添加隐层
ann.add(Dropout(0.5)) # 添加Dropout
ann.add(Dense(units=192, activation = 'relu')) # 添加隐层
ann.add(Dropout(0.5)) # 添加Dropout
ann.add(Dense(units=1, activation = 'sigmoid')) # 添加输出层
ann.compile(optimizer = 'adam', # 优化器
loss = 'binary_crossentropy', #损失函数
metrics = ['acc']) # 评估指标
history = ann.fit(X_train, y_train, epochs=30, batch_size=64, validation_data=(X_test, y_test))
y_pred = ann.predict(X_test,batch_size=10) # 预测测试集的标签
y_pred = np.round(y_pred) # 将分类概率值转换成0/1整数值
损失曲线显示,添加Dropout层之后,过拟合现象被大幅度地抑制了。
针对客户流失样本的F1分数上升到了令人惊讶的0.62
新的混淆矩阵显示,400多个即将流失的客户中,我们成功地捕捉到了200多人。这是非常有价值的商业信息。
其实准确率或者F1分数本身的提升并不重要,更有价值的是网络优化过程中所做的各种尝试和背后的思路。
六、深度神经网络的调试及性能优化
6.1 使用回调功能
开始训练之前,我们不知道多少轮之后会开始出现过拟合的征兆。
比如运行100轮,才发现原来15轮才是比较正确的选择。本来进行15轮就得到最佳结果,却需要先运行100轮。有没有可能一次性找到最合适的轮点?”
类似的运行时动态控制可以通过回调(callback)功能来实现。所谓回调,就是在训练进行过程中,根据一些预设的指示对训练进行控制。下面是几个常用的回调函数。
Model Checkpoint
:在训练过程中的不同时间点保存模型,也就是保存当前网络的所有权重。
Early Stopping
:如果验证损失不再改善,则中断训练。这个回调函数常与Model Checkpoint结合使用,以保存最佳模型。
Reduce LROn Plateau
:在训练过程中动态调节某些参数值,比如优化器的学习速率,从而跳出训练过程中的高原区。
TensorBoard
:将模型训练过程可视化。
# 导入回调功能
from keras.callbacks import Model Checkpoint
from keras.callbacks import Early Stopping
from keras.callbacks import Reduce LROn Plateau
# 设定要回调的功能
earlystop = Early Stopping(monitor='val_acc', patience=20,
verbose=1, restore_best_weights=True)
reducelr = Reduce LROn Plateau(monitor='val_acc',
factor=0.5,
patience=3, verbose=1, min_lr=1e-7)
modelckpt = Model Checkpoint(filepath='ann.h5',
monitor='val_acc',
verbose=1, save_best_only=True,
mode='max')
callbacks = [earlystop, reducelr, modelckpt] # 设定回调
history = ann.fit(X_train, y_train, # 指定训练集
batch_size=128, # 指定批量大小
validation_data = (X_test, y_test), # 指定验证集
epochs=100, # 指定轮次
callbacks=callbacks) # 指定回调功能
6.2 使用TensorBoard
TensorBoard
是一个内置于Tensor Flow的可视化工具,用以帮助我们在训练过程中监控模型内部发生的信息。具体包括以下功能。
- 在训练过程中监控指标。
- 将模型的架构可视化。
- 显示激活和梯度的直方图。
- 以三维的形式显示词嵌入。
在Kaggle中,只需要用下面两句代码配置TensorBoard:
# 导入并激活TensorBoard
%load_ext tensorboard
%tensorboard --logdir logs
# 创建机器学习模型
import tensorflow as tf
mnist = tf.keras.datasets.mnist
((x_train, y_train), (x_test, y_test)) = mnist.load_data()
(x_train, x_test) = (x_train / 255.0, x_test / 255.0)
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(input_shape=(28, 28)),
tf.keras.layers.Dense(512, activation=tf.nn.relu),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Dense(10, activation=tf.nn.softmax)
])
model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'],
)
# 回调Tensorboard
tensorboard_callback = tf.keras.callbacks.TensorBoard("logs")
model.fit(
x_train,
y_train,
epochs=5,
callbacks=[tensorboard_callback],
)
6.3 神经网络中的过拟合
(1)首先,根据奥卡姆剃刀定律,因为网络越大,越容易过拟合。如果能够用较简单的小型网络解决问题,就不要强迫自己使用大网络。
(2)一种思路是在训练大型网络之前使用少量数据训练一个较小的模型,小模型的泛化好,再去训练更深、更大的网络。不然的话,费了很多精力直接训练一个大网络。
(3)另外,最常见且有效地降低神经网络过拟合的方法就是在全连接层之间添加一些Dropout层。这是很好用的标准做法,不过Dropout层会对训练速度稍有影响。
(4)最后,使用较低的学习速率配合神经元的权重正则化可能是解决过拟合问题的手段之一。
《Deep Residual Learning for Image Recognition》
中提出的。通过残差连接,可以很轻松地构建几百层,甚至上千层的网络,而不用担心梯度消失过快的问题。
前向传播的数学本质(附4D张量可视化)
import tensorflow as tf
from tensorflow.keras.layers import Dense
# 手动实现矩阵变换
def forward_pass(X, W1, b1, W2, b2):
h1 = tf.matmul(X, W1) + b1
a1 = tf.nn.relu(h1)
h2 = tf.matmul(a1, W2) + b2
return tf.nn.softmax(h2)
# 4D权重可视化
plt.figure(figsize=(12,6))
for i in range(16):
plt.subplot(4,4,i+1)
plt.imshow(W1[:,i].numpy().reshape(28,28), cmap='viridis')
plt.suptitle('第一层权重可视化')
反向传播的工程实现技巧(双框架代码)
PyTorch版本(动态计算图)
class DeepNN(torch.nn.Module):
def __init__(self):
super().__init__()
self.layer1 = torch.nn.Linear(784, 256)
self.layer2 = torch.nn.Linear(256, 128)
self.output = torch.nn.Linear(128, 10)
def forward(self, x):
x = torch.relu(self.layer1(x))
x = torch.dropout(x, 0.3, train=self.training)
x = torch.relu(self.layer2(x))
return torch.log_softmax(self.output(x), dim=1)
# 自动微分演示
x = torch.randn(32, 784, requires_grad=True)
loss_fn = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
TensorFlow静态图加速
model = tf.keras.Sequential([
tf.keras.layers.Flatten(input_shape=(28,28)),
tf.keras.layers.Dense(256, activation='relu', kernel_regularizer='l2'),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(128, activation='swish'),
tf.keras.layers.Dense(10, activation='softmax')
])
# XLA加速配置
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4),
loss='sparse_categorical_crossentropy',
jit_compile=True)
工业级训练技巧(提升10倍训练效率)
AutoML参数搜索
!pip install keras-tuner
import kerastuner as kt
def hyper_model(hp):
model = tf.keras.Sequential()
model.add(tf.keras.layers.Flatten(input_shape=(28,28)))
# 动态搜索层数与单元数
for i in range(hp.Int('num_layers', 2, 6)):
model.add(tf.keras.layers.Dense(
units=hp.Int(f'units_{i}', 32, 256, step=32),
activation=hp.Choice('activation', ['relu', 'swish']))
)
model.add(tf.keras.layers.Dense(10))
model.compile(optimizer=tf.keras.optimizers.Adam(
hp.Float('learning_rate', 1e-5, 1e-2, sampling='log')),
loss='sparse_categorical_accuracy')
return model
tuner = kt.BayesianOptimization(hyper_model,
objective='val_loss',
max_trials=50)
混合精度训练(V100 GPU加速)
policy = tf.keras.mixed_precision.Policy('mixed_float16')
tf.keras.mixed_precision.set_global_policy(policy)
# 注意最后层强制转float32
model.layers[-1].dtype_policy = tf.float32
模型解释与部署(关键工业考量)
激活热力图可视化
import tf_explain as explain
explainer = explain.core.activation_maximization.ActivationMaximization()
grid = explainer.explain(validation_data=test_images,
model=model,
target_layer='dense_3')
plt.imshow(grid, cmap='jet')
TensorRT加速推理
# TensorFlow到TensorRT转换
conversion_params = trt.TrtConversionParams(
precision_mode=trt.TrtPrecisionMode.FP16)
converter = trt.TrtGraphConverterV2(
input_saved_model_dir='saved_model',
conversion_params=conversion_params)
converter.convert()
converter.save('trt_model')