各位同学好,今天和大家分享以下TensorFlow2.0深度学习中对神经网络的优化方法,包括动量、学习率、dropout、交叉验证、正则化。本节使用数学公式对网络进行优化,增加网络的灵活性。

以下代码按顺序组合在一起就是完整代码,没有缺失,可以直接运行。


1. 数据导入

导入系统自带的服装数据集,查看数据集的相关信息。

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import datasets, Sequential, layers, optimizers, metrics

#(1)获取数据
(x_com, y_com), (x_test, y_test) = datasets.fashion_mnist.load_data()

# 查看数据集合
print('x.shape: ',x_com.shape,'y.shape: ',y_com.shape)  # x.shape:  (60000, 28, 28) y.shape:  (60000,)
print('x_test.shape: ',x_test.shape,'y_test.shape: ',y_test.shape)  # x_test.shape:  (10000, 28, 28) y_test.shape:  (10000,)
print('y[:5]: ',y_com[:5])  # y[:5]:  [9 0 0 3 0]

# 绘制图像
import matplotlib.pyplot as plt
for i in range(10):
    plt.subplot(2,5,i+1)
    plt.imshow(x_com[i])
    plt.xticks([])
    plt.yticks([])
plt.show()

pytorch神经网络优化器 神经网络优化代码_神经网络


2. 网络构造

堆叠一个6层的网络,前两个连接层设置layers.Dropout(),在神经网络训练的过程中,在指定Dropout的层中,每个神经元都有20%概率被杀死,被杀的神经元不参与正反向传播,权重参数保持不变。每一次迭代都是选择一部分的神经元参与训练。设置dropout之间要注意训练部分的策略和验证部分的策略不一样,需要分别指定。训练部分 network(x, training=True),验证部分 network(x, training=False)

#(2)构造网络结构
# ==1== 全连接层,6层全连接层
# [b,784]=>[b,512]=>[b,256]=>[b,128]=>[b,64]=>[b,32]=>[b,10]
network = Sequential()
network.add(layers.Dense(512, activation='relu'))  # 堆叠网络层
network.add(layers.Dropout(0.2))  # 每次前向传播每条连接都有0.2的几率被断开,训练时需要指定参数network(x, training=True)
network.add(layers.Dense(256, activation='relu'))
network.add(layers.Dropout(0.2))
network.add(layers.Dense(128, activation='relu'))
network.add(layers.Dense(64, activation='relu'))
network.add(layers.Dense(32, activation='relu'))
network.add(layers.Dense(10))  # logits层
# ==2== 设置输入维度
network.build(input_shape=[None, 28*28])
# ==3== 查看网络结构
network.summary()
# ==4== 优化器
optimizer = optimizers.Adam(lr=0.01) # 使用动态学习率,刚训练时梯度下降速度较快,逐渐减慢
# CE变量接收交叉熵损失方法
CE = tf.losses.categorical_crossentropy

网络结构如图所示,param代表网络每一层的参数个数。以最后一层为例,330 = 权重参数(32*10) + 偏置参数(10)。

Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense (Dense)               (None, 512)               401920    
                                                                 
 dropout (Dropout)           (None, 512)               0         
                                                                 
 dense_1 (Dense)             (None, 256)               131328    
                                                                 
 dropout_1 (Dropout)         (None, 256)               0         
                                                                 
 dense_2 (Dense)             (None, 128)               32896     
                                                                 
 dense_3 (Dense)             (None, 64)                8256      
                                                                 
 dense_4 (Dense)             (None, 32)                2080      
                                                                 
 dense_5 (Dense)             (None, 10)                330       
                                                                 
=================================================================
Total params: 576,810
Trainable params: 576,810
Non-trainable params: 0
_________________________________________________________________

3. 划分数据集

使用tf.data.Dataset.from_tensor_slices()将测试数据组成一个数据集,使用.map()对整个数据集使用预处理函数,用于整个神经网络训练完成后的测试。在网络循环过程中,使用K折交叉验证法对训练集数据划分,每次循环从导入的图像数据(x_com, y_com)中随机划分出训练集和验证集

如果按固定比例划分训练集和验证集的话,会导致有一部分数据始终无法用于训练。先给每一个xy组合生成索引,使用tf.random.shuffle()打乱索引顺序,取50k张图片用于训练10k张图片用于验证,但不改变图像和标签之间的对应关系,充分利用有限的数据。将生成的y_train和y_val分别转换成one_hot编码,索引对应的数值转换为1,其他位置的值都变成0,便于后续计算损失。

#(3)划分数据集
# 预处理函数
def processing(x,y):
    x = 2 * tf.cast(x, dtype=tf.float32)/255.0 - 1
    y = tf.cast(y, dtype=tf.int32)
    return x,y

# 构造测试集
ds_test = tf.data.Dataset.from_tensor_slices((x_test, y_test)) 
ds_test = ds_test.map(processing).batch(128)

# 迭代器查看数据是否正确
sample = next(iter(ds_test))
print('x_batch:', sample[0].shape, 'y_batch:', sample[1].shape)
# x_batch: (128, 28, 28)  y_batch: (128,)

# 网络循环
for epochs in range(20): # 循环20次
    print('-----------------------------')    
    print('epoch:',epochs)    

# (5)K折交叉验证,每次循环使用不同的验证集和训练集
    idx = tf.range(60000) # 训练集x一共有60k张图像,生成60k个索引
    idx = tf.random.shuffle(idx) # 每次迭代都随机打乱所有的索引    
    
    # 使用打乱的索引收集加载进来的x_com和y_com数据
    x_train, y_train = tf.gather(x_com, idx[:50000]), tf.gather(y_com, idx[:50000])  # 训练集
    x_val, y_val = tf.gather(x_com, idx[-10000:]), tf.gather(y_com, idx[-10000:]) # 验证集
    
    # 构造训练集数据集
    y_train = tf.one_hot(y_train, depth=10)  # 对y进行onehot编码,索引对应的值为1,其他为0
    ds_train = tf.data.Dataset.from_tensor_slices((x_train, y_train))
    ds_train = ds_train.map(processing).shuffle(10000).batch(128) # 每次迭代取128组数据,打乱数据
    
    # 构造验证集数据集
    y_val = tf.one_hot(y_val, depth=10)  # 计算验证集的损失,看是否出现过拟合现象
    ds_val = tf.data.Dataset.from_tensor_slices((x_val, y_val))
    ds_val = ds_val.map(processing).batch(128)

4.  网络训练

使用交叉熵计算模型预测值和真实值之间的损失,交叉熵损失函数自动将logits层的输出通过softmax函数转换到0-1之间,并且10个分类的概率和为1。为了防止过拟合发生,对损失函数使用L2正则化,在原来的损失函数基础上加上权重参数的平方和。L2正则化可以产生趋近0的解。使用tf.nn.l2_loss()对网络的所有权重参数计算二范数,设置正则化惩罚系数为0.0001,根据公式:

pytorch神经网络优化器 神经网络优化代码_python_02

正则化之后,以损失为因变量,权重和偏置为自变量计算梯度。使用学习率动态调整的方法调整网络权重偏置,optimizer.learning_rate = 0.01*(20-epochs)/20,刚开始训练时梯度下降较快,最快速度接近最优点,随着网络循环的次数增加,梯度下降逐渐缓慢,更较好地逼近最优点。

#(6)网络训练
    # 每次迭代一个batch
    for step, (x,y) in enumerate(ds_train):
        # 将x的shape从[128,28,28]=>[128,28*28]
        x = tf.reshape(x, [-1,28*28])
        
        # 跟踪梯度
        with tf.GradientTape() as tape:
            # 网络训练,得到logits层
            logits = network(x, training=True)
            # 计算平均交叉熵损失,from_logits自动将实数转化为概率
            loss = tf.reduce_mean(CE(y, logits, from_logits=True))
         
            # 计算二范数
            w_regular = []  # 存放权重的二范数
            # 对所有的权重计算二范数, 正则化项保存为[w1,b1,w2,b2,w3,b3,...]
            for w in network.trainable_variables[::2]: # 6组权重
                w_regular.append(tf.nn.l2_loss(w))  
                
            # loss是求的整个batch的loss,也要求整个batch的w和b二范数
            loss_regular = tf.reduce_sum(w_regular)
            
            # 对损失正则化,正则化惩罚项设置为0.0001
            loss = loss + 0.0001 * loss_regular
        
            # 动态学习率调整,每次迭代都递减,刚开始准确率为0.01
            optimizer.learning_rate = 0.01*(20-epochs)/20
            
        # 梯度计算,因变量为loss,自变量为所有的权重和偏置
        grads = tape.gradient(loss, network.trainable_variables)
        # 原地更新权重和偏置
        optimizer.apply_gradients(zip(grads, network.trainable_variables))
        
        # 每200个batch打印一次结果
        if step%200 == 0:
            print('train', 'batch_loss:', loss.numpy()) # 每一个batch的平均损失

5. 网络验证

在上面已经对验证集的目标y_val进行了onehot编码,在和预测值计算完损失值后,需要将one_hot类型转回标签类型,和预测值比较结果是否一致,来计算准确率。验证集最后一层的输出结果logits经过tf.nn.softmax()函数转换成相应的概率值,tf.argmax()找出最大概率所在的下标,即所属的分类。看真实值和预测值是否相同来统计预测对了的个数。

#(7)验证,前向传播
    total_correct = 0  # 每次迭代有多少预测对了
    total_num = 0  # 每次迭代一共有多少参与预测
    
    for step,(x,y) in enumerate(ds_val):  # 每次验证一个batch
        # x的shape由[128,28,28]变成[b,28*28]
        x = tf.reshape(x, [-1,28*28])
        # x从第一层传播到最后一层
        logits = network(x, training=False)  #得出的是实数,需转换成概率
        
        # 计算验证集交叉熵损失
        loss = tf.reduce_mean(CE(y, logits, from_logits=True))
        
        # 计算完之后,将one_hot类型转换回标签类型,进行准确度计算
        labels = [] # 存放转换后的标签
        for label in y:
            labels.append(tf.argmax(label))
        labels = tf.convert_to_tensor(labels, dtype=tf.int32) # 转变数据类型int32
                
        # 将图片属于每一层的实数结果转换为概率
        prob = tf.nn.softmax(logits, axis=1)
        # 找到概率最大值所在的索引位置,指定axis=1,转变每一张图片属于10个分类的概率
        predict = tf.argmax(prob, axis=1, output_type=tf.int32)  # 默认输出tf.int64,而y是tf.int32类型

        # 对比预测值和真实值是否相同,计算有几个相同的
        correct = tf.equal(labels, predict)
        correct = tf.reduce_sum(tf.cast(correct, tf.int32))  #True代表1
        
        # 记录预测对了的概率
        total_correct += int(correct)  #从tensor类型变成数值类型
        total_num += x.shape[0]
        
        # 每100个batch打印一次准确率和损失
        if step%100 == 0:
            accuracy = total_correct/total_num
            print('val', 'batch_accuracy:', accuracy)
            print('val', 'batch_loss:', loss.numpy())
            
    # 每一次循环打印一次验证结果
    accuracy = total_correct/total_num
    print('val', 'eopch_accuracy:', accuracy)
    print('-----------------------------')

6. 网络测试

测试方法和验证方法基本相同,实数转概率,找下标值,统计相同个数,不过多介绍。

#(8)网络完成后测试,记录的是最后一次的权重
# 前向传播
test_correct = 0
test_num = 0

for step, (x, y) in enumerate(ds_test):
    # 改变输入x的shape
    x = tf.reshape(x,shape=[-1, 28*28])
    # x从[128,28*28]=>[128,10]
    logits = network(x) # 得到每张图片属于10个分类的实数
    prob = tf.nn.softmax(logits, axis=1) # 将每一个张图片属于10分类的实数变成概率,且和为1
    predict = tf.argmax(prob, axis=1, output_type=tf.int32) # 找到概率最大的下标
    # 计算准确率
    correct = tf.reduce_sum(tf.cast(tf.equal(y, predict), tf.int32))  #有多少预测对了
    # 计算概率
    test_correct += int(correct)
    test_num += x.shape[0]
    
# 整个test集的准确率
accuracy = test_correct / test_num
print('test_accuracy: ', accuracy)

7. 预测

这里采用测试集中的x数据来做预测,next(iter(ds_test)),设置一个迭代器,每次运行从中取出一个batch的测试数据。同样也是将网络最后一层logits层的输出的实数,转为概率,再找概率最大的下标,得到预测结果。最后将预测的结果和真实值的前10个数比较一下,结果相同。

#(9)预测,这里使用的ds_test中的特征值x来预测
sample = next(iter(ds_test))
predict = []
for step,x in enumerate(sample[0]):  #sampel[0]中保存x数据
    # 改变输入x的shape
    x = tf.reshape(x,shape=[-1, 28*28])
    # x从[128,28*28]=>[128,10]
    logits = network(x) # 得到每张图片属于10个分类的实数
    prob = tf.nn.softmax(logits, axis=1) # 将每一个张图片属于10分类的实数变成概率,且和为1
    pred = tf.argmax(prob, axis=1) # 找到概率最大的下标
    predict.append(int(pred))
    
print('预测值:', predict[:10])
print('真实值:', sample[1][:10].numpy())

8. 结果展示

这里只迭代20次,随着次数增加,准确率会不断提高。

-----------------------------
epoch: 19
train batch_loss: 0.4036927
train batch_loss: 0.29839486
val batch_accuracy: 0.9140625
val batch_loss: 0.27389172
val eopch_accuracy: 0.898
-----------------------------
test_accuracy:  0.8713

预测值: [9, 2, 1, 1, 6, 1, 4, 6, 5, 7]
真实值: [9 2 1 1 6 1 4 6 5 7]