[深度学习] TensorFlow2.0简单方便易用使用笔记

本文详细介绍了在TensorFlow2.0中搭建深度学习网络的步骤,包括使用Sequential模型搭建层、选择激活函数、正则化约束、自定义层和网络。此外,还探讨了损失函数的选择,如MSE、二分类交叉熵、多分类交叉熵等,以及优化器的使用,如Adam、RMSprop和SGD。文章还介绍了预处理数据集的方法,强调了数据集的形状、数据格式和归一化处理的重要性。最后,提到了使用TensorBoard进行训练可视化和不使用fit方法进行网络训练的可能性。

我的TensorFlow代码写得并不好,在自定义一些网络训练场景的时候乱写会导致学习效率很低,因为tf本身的优化太棒了,恰当地使用tensor进行运算会让程序的算力消耗显著降低。所以可以记录一下,方便自己随时回顾。
因此,难免出现一些神奇难解的词语/形容/符号,可能只有我自己(甚至我自己也不能)看得懂。

内容的组织结构将按照以下方式进行(当你有了数据集和明确的任务目标,从算法上你需要做什么呢?):

  • 预处理数据集
  • 搭建网络结构
  • 确定损失函数
  • 确定优化器
  • 分析训练结果

数据集我感觉是挺麻烦的事儿,因此把它放在最后,其他按照上述逻辑顺序依次展开。
示例主要是使用fashion mnist数据集,不刻意区分全连接的神经网络/CNN/RNN。

1 搭建网络结构

1.1 Sequential的方式搭建层以及可用的层类型

keras.Sequential函数允许很方便地直接将你需要的层结构堆砌在列表中,直接生成网络。
列表中只需要放置很多的keras.layers中的你所需要的层。

import tensorflow.keras as keras
import tensorflow as tf
# 如果用from tensorflow import keras,会无法自动补全

model = keras.Sequential([
    keras.layers.Conv2D(1, 1),
    keras.layers.Conv2D(64, 3),
    keras.layers.MaxPooling2D(),
    keras.layers.BatchNormalization(),
    keras.layers.ReLU(),

    keras.layers.Conv2D(128, 3),
    keras.layers.Conv2D(128, 3),
    keras.layers.MaxPooling2D(),
    keras.layers.BatchNormalization(),
    keras.layers.ReLU(),

    keras.layers.GlobalAvgPool2D(),
    keras.layers.Dense(8192 ,activation='relu'),
    keras.layers.Dense(2048 ,activation='relu'),
    keras.layers.Dropout(0.5),
    keras.layers.Dense(10),
])
model(tf.zeros((1,28,28,1)))

基本常用的层结构都可以在keras.layers中找到,可以随意挑选设计网络结构,有些复杂一点的例如Resnet中残差块这种的就要自己定义,在下一部分会介绍。
有哪些常用的层

keras.layers.Dense全连接层 最基本的
keras.layers.Conv2D二维卷积层 包括kernel_size/strides等基本参数的设置
keras.layers.MaxPooling2D二维池化层 有最大池化/均值池化/全局最大/全局均值池化
keras.layers.Flatten我曾经将卷积层结果通过池化强行降维 直到发现有flatten
keras.layers.BatchNormalization批归一化 将层输出的每个维度变为均值为0 方差为1(它应该用在非线性层之前)很多时候会简称为BN
keras.layers.Dropout我也曾不知道竟然dropout也有可以调用的层 太方便了
keras.layers.SimpleRNN/LSTM/GRU常用的RNN基本单元
keras.layers.SimpleRNNCell/LSTMCell/GRUCell值得一提的是为了方便细节设计 还有单个cell的版本
keras.layers.LeakyReLU以此为代表的一系列激活函数层

有的地方会在Sequential的第一层指明网络输入向量的形状,如果网络对输入向量的形状未知,是无法计算每一层的形状的,模型无法被建立。
但也可以像我一样,在网络搭建完成后,给网络输入随便一个和输入向量同形状的向量(严格地说,应该叫张量了),网络也会自动初始化。

建立好的模型可以使用模型的summary()方法,查看网络结构。
下面展示了部分输出,非常了然。

Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv2d (Conv2D)             (1, 28, 28, 1)            2         
                                                                 
 conv2d_1 (Conv2D)           (1, 26, 26, 64)           640       
                                                                 
 max_pooling2d (MaxPooling2D  (1, 13, 13, 64)          0         
 )                                                               
                                                                 
 batch_normalization (BatchN  (1, 13, 13, 64)          256       
 ormalization)                                                   
                                                                 
 re_lu (ReLU)                (1, 13, 13, 64)           0         
                                                                 
 conv2d_2 (Conv2D)           (1, 11, 11, 128)          73856     
                                                                 
 conv2d_3 (Conv2D)           (1, 9, 9, 128)            147584    
                                                                 
 max_pooling2d_1 (MaxPooling  (1, 4, 4, 128)           0         
 2D)                                                             
                                                                 
 batch_normalization_1 (Batc  (1, 4, 4, 128)           512       
 hNormalization)
.............................................
=================================================================
Total params: 18,079,372
Trainable params: 18,078,988
Non-trainable params: 384
_________________________________________________________________

1.2 选择合适的激活函数

如果对于每个层的作用都明确的话,它们的参数也是相当明确易懂的,这里谈论比较公共的参数,比如激活函数。

添加激活函数有两种方式,可以直接在层的activation参数中给出,例如:

# 设定一个激活函数是relu
keras.layers.Dense(8192 ,activation='relu')

# relu比较好记,但是leakyrelu我就不确定大小写或者下划线了,所以有的时候可以选择从keras.activations中选择,比如tanh
keras.layers.Dense(8192 ,activation=keras.activations.tanh)

除此之外也可以单独添加一个非线性激活层,比如有时候你希望在卷积层后先使用BN层,在添加激活层,就可以

# 卷积层后先池化,再归一化,最后过激活层
keras.layers.Conv2D(64, 3),
keras.layers.MaxPooling2D(),
keras.layers.BatchNormalization(),
keras.layers.ReLU(),

怎么选用合适的激活函数。
softmax ( x ( 1 × n ) ) = [ p 1 , . . . , p n ] , p i ∈ [ 0 , 1 ] , Σ i = 1 n p i = 1 \text{softmax}(\bold{x}_{(1\times n)})=[p_1,...,p_n], p_i\in[0,1],\Sigma_{i=1}^np_i=1 softmax(x(1×n))=[p1,...,pn],pi[0,1],Σi=1npi=1
softmax因为上述映射关系,常用于表达多分类的结果,每一个维度是对应种类的概率,所以不会用在层中。

sigmoid函数会将实数映射到[0,1],tanh函数将实数映射到[-1,1],都适合作为二分类的表达,也可以用到层中,但是tanh被证明训练更快。

但是最快的还是ReLU,有论文指出在某个任务中relu的训练速度比tanh快6倍,ReLU非常简单,但同样有效。

也有人认为ReLU在输入小于零的时候梯度是0,导致训练不生效,于是又有了LeakyReLU等变种的ReLU激活函数,现在给层添加激活函数一般都使用ReLU。不过在RNN中会经常用tanh,应该有特别的效果,我还没有具体了解。

1.3 正则化约束

L2约束通过给参数的二范数添加损失,来约束参数的暴涨,可以减少模型的过拟合。
L1约束通过给参数的一范数添加损失,可以让模型参数变得稀疏。
还有一些别的正则化约束,按照下面的方式添加到层参数中即可。

keras.layers.Dense(8192 ,activation=keras.activations.tanh, activity_regularizer='L2'),

我很喜欢TensorFlow说的,既然神经网络背后的原理这么简单,它的实现何必那么复杂呢。L1/L2的想法都很简单,但是自己写起来挺费劲的,他们不应该那么耽误时间。

1.4 自定义层与网络

有时候你需要一个更复杂的层,例如想要实现一个残差风格的层,即将输入数据和多层处理结果相加作为输出,给这个层添加“不表达”的能力。

继承keras.layers.Layer父类,添加你需要用到的参数在init中。
init中定义需要用到的基本层,
call中定义输入张量进入层之后,如何运算获得输出向量(一定要用TensorFlow里定义的运算方式运算张量啊!)

class Bottleneck(keras.layers.Layer):
    def __init__(self, output_channel, stride):
        super(Bottleneck, self).__init__()
        self.conv_11_64 = keras.layers.Conv2D(64, 1, (1,1))
        self.BN1 = keras.layers.BatchNormalization()
        self.conv_33 = keras.layers.Conv2D(output_channel, 3, (stride, stride))
        self.BN2 = keras.layers.BatchNormalization()
        self.conv_11_out = keras.layers.Conv2D(output_channel, 1, (1,1))
        self.BN3 = keras.layers.BatchNormalization()
        self.conv_11_res = keras.layers.Conv2D(output_channel, 3, (stride,stride))
        self.BN4 = keras.layers.BatchNormalization()
    def call(self, inputs):
        x = self.conv_11_64(inputs)
        x = self.BN1(x)
        x = keras.activations.relu(x)

        x = self.conv_33(x)
        x = self.BN2(x)
        x = keras.activations.relu(x)

        x = self.conv_11_out(x)
        x = self.BN3(x)
        x = keras.activations.relu(x)

        y = self.conv_11_res(inputs)
        y = self.BN4(y)
        return keras.activations.relu(tf.add(x,y))

正如所见,层可以由很多个层搭建而成,层可以越来越复杂,无限复杂,那么层和网络的差别是什么,就是网络可以被训练,层只能作为一种映射参与运算。

继承keras.Model父类,也就是网络,在init中定义需要的层,在call中定义对输入张量的处理。

class SimpleNet(keras.Model):
    def __init__(self):
        super(SimpleNet, self).__init__()
        self.L1 = keras.layers.Conv2D(32, 1, (2,2))
        self.L2 = Bottleneck(32, 1)
        self.L3 = Bottleneck(32, 2)
        self.L4 = keras.layers.AveragePooling2D((3,3))
        self.L5 = keras.layers.Flatten()
        self.L6 = keras.layers.Dense(128, activation='relu')
        self.L7 = keras.layers.Dense(128, activation='relu')
        self.L8 = keras.layers.Dense(10)

    def call(self, inputs):
        x = self.L1(inputs)
        x = self.L2(x)
        x = self.L3(x)
        x = self.L4(x)
        x = self.L5(x)
        x = self.L6(x)
        x = self.L7(x)
        x = self.L8(x)
        return x

之前用的Sequential就是Model类,这里自定义的Model类就可以与之前说的模型一样使用了。

至此,所需要的神经网络已经可以搭建完毕了

  • 使用summary()方法可以查看网络结构。
  • model(data)或者layer(data)可以像映射一样,获得输入data到模型/层后得到的输出,输出类型为tensor。
  • model.predict(data)也可以得到对应输出,但类型是numpy中的array,tensor类型适用于数学运算,numpy类型适合数据处理分析,根据需要选择用什么进行输出。

现在,在正式开始训练前,需要给网络指定训练所需的其他组件,例如,损失函数,优化器,输出参数等,用模型的compile函数传给模型。

model.compile(
    loss=tf.losses.SparseCategoricalCrossentropy(from_logits=True),
    optimizer=keras.optimizers.Adam(5E-4, decay = 0.001),
    metrics=keras.metrics.SparseCategoricalAccuracy()
)

前两个组件的选择将在下面继续说明。
metrics就是网络运行中要保存的参数,可以在训练完成后查看,比如说你可能很想知道随着训练的进行,模型的准确性是怎么变化的,损失是怎么变化的,这些都以列表的形式给到metrics参数即可。
很多地方都是用的字符串,比如“accuracy”,同样地,我因为记不住,所以都在keras.metrics中找。

2 确定损失函数

2.1 TensorFlow中可调用的损失函数

都在keras.losses里面,但是你会发现同样名字的损失函数竟然都有两个

keras.losses.SparseCategoricalCrossentropy
keras.losses.sparse_categorical_crossentropy

注意一下类型,TensorFlow里定义的损失函数都有“函数”和“类”两个版本,是一样的,只不过在model.compile中,如果用的是函数,就直接给出函数名,用的是类,需要在类名后面加括号(实例化一下)

# 类要加括号
model.compile(
    loss=tf.losses.SparseCategoricalCrossentropy(from_logits=True),
)
# 函数可以不加括号,除非有参数要设定,实际上这里我应该加个括号把from_logits设定为True
model.compile(
    loss=keras.losses.sparse_categorical_crossentropy,
)

2.1.1 MSE 均方误差

l o s s = m e a n ( ( y t r u e − y p r e d ) 2 ) loss=mean((y_{true}-y_{pred})^2) loss=mean((ytrueypred)2)
简单直白到无法解释

2.1.2 binary_crossentropy 二分类交叉熵

l o s s = − Σ i = 1 N p ( y t r u e ) l n ( p ( y p r e d ) ) loss=-\Sigma_{i=1}^Np(y_{true})ln(p(y_{pred})) loss=Σi=1Np(ytrue)ln(p(ypred))
KL散度可以衡量两个概率分布的相似程度,最大化概率似然等效于最小化KL散度,等效于最小化交叉熵(cross entropy)。
然而TensorFlow里有三个crossentropy命名的损失函数,还有个from_logits的参数。

binary_crossentropy用于二分类场景,即y_true只有[0,1]两种结果。

from_logits的含义?
from_logits=True表示网络的输出为logits,而不是[0,1]区间内的一个概率。
什么是logits
logits就是可以从负无穷取到正无穷的一个任意实数,他可以被sigmoid映射成概率,如果是多维实数向量,就可以被softmax映射成多分类概率。
如果网络的输出是logits,那么网络就可以在整个实数范围内训练输出,降低训练难度。
不妨返回去看看Sequential实例网络的输出,就是10个实数(没有在输出层使用softmax激活函数把它映射成概率),所以响应地后续我都选择了from_logits=True。

2.1.3 categorical_crossentropy

用于多分类,且y_true即标签数据为向量形式。
举个例子,如果一个分类问题要求把数据集分成4类,且每个标签数据长这样 [ 0 , 0 , 0 , 1 ] [0,0,0,1] [0,0,0,1],而不是3(这里认为四个类分别是0,1,2,3),就用这个损失函数。
同样地,如果你的网络输出是四个实数,那么from_logits应该是True。

2.1.4 sparse_categorical_crossentropy

相反地,可能你的标签数据直接给出了类别,例如给出了3而不是 [ 0 , 0 , 0 , 1 ] [0,0,0,1] [0,0,0,1],那么就要使用这个损失函数。

2.1.5 kl_divergence

l o s s = Σ i = 1 N p ( y t r u e ) l n ( p ( y t r u e ) p ( y p r e d ) ) loss = \Sigma_{i=1}^N p(y_{true})ln(\frac{p(y_{true})}{p(y_{pred})}) loss=Σi=1Np(ytrue)ln(p(ypred)p(ytrue))
KL散度可以衡量两个概率分布的相似程度,如果用KL散度表达距离的概念,可以引申出自然梯度下降的概念,在深度强化学习中会用到。

2.2 自定义损失函数

其实我觉得上面的损失函数已经够我用的了,但是如果你需要自定义损失函数的话,有两种方法。
对应TensorFlow中用函数/类表达损失函数的方式
第一种是直接写一个函数,比如自己写一个均方误差(一定要用TensorFlow的运算去计算啊!),model.compile中直接使用这个函数名即可。

def newMSE(y_true, y_pred):
    loss = tf.reduce_mean(tf.square(y_true-y_pred))
    loss = tf.sqrt(loss)
    return loss

另一种方法是继承keras.losses.Loss类。

3 确定优化器

3.1 TensorFlow可调用的优化器

都定义在keras.optimizers中,写入model.compile的时候同样是可以用字符串即优化器的名称,但是因为我记不住名字,所以直接引用优化器类。
常用的无非是

  • Adam
  • RMSprop
  • SGD

前两种都是经过momentum优化过的一阶优化器,效果差不多好,因为这些优化器除了学习率还有其他的可调参数,根据自己对优化器的原理熟悉程度挑选Adam或者RMSprop就可以。

3.2 使用GradientTape求解微分

tf.GradientTape的作用可以自动求解微分,无论是神经网络参数还是一般方程,省去了手动计算函数导数的过程。

举个例子:求解一个4*4的矩阵,要求矩阵对角线元素为所对应行的元素负总和,且矩阵特征值为[0.36,1.22,3.6,6.28]
设这种特别构造的矩阵为L,特征值向量为e,问题可以描述如下
f : L → e L ∗ = a r g L m i n   l o s s ( e t r u e , f ( L ) ) f:L\rightarrow e \\ L^*=arg_Lmin\ loss(e_{true}, f(L)) f:LeL=argLmin loss(etrue,f(L))
这里的loss就可以取为MSE。
问题在于,损失对L求微分涉及到f对L求微分,但是连f都很难表达,更别说求微分(也许可以求,但是我不会求)。但是用tf.GradientTape就可以轻松解决。

eig_true = tf.Variable([0.36, 1.22, 3.6, 6.28])
# trainable=True可以让gradientTape自动追踪它,不需要用watch
M = tf.Variable(tf.random.uniform((4,4)), trainable=True)

# persistent=True的含义是,一个tape只能求单次微分,如果你需要求多次,就可以设置它为True
with tf.GradientTape(persistent=True) as tape:
   		# watch的含义是追踪这个变量,只能够对追踪的变量求微分
   		# 既然M已经是trainable的,为什么还要watch?
   		# 因为更新M的时候我不是用的optimizer更新的,而是直接数值运算了,这似乎会导致接下来对M微分会失效,总之,如果没有加watch导致梯度求不出来了报错了,就回来加个watch
        tape.watch(M)
        # 计算目标矩阵,先将对角线减成0,再加上每行的负总和
        L = M - tf.linalg.diag(tf.linalg.diag_part(M))
        L = L - tf.linalg.diag(tf.reduce_sum(M, 1))
        # 计算目标矩阵的特征值向量
        eig_pred = tf.linalg.eigvals(L)
        # 用MSE计算损失函数
        loss = keras.losses.mse(eig_true, eig_pred)
        # 用tape获得loss关于M的梯度
    gradient = tape.gradient(loss, M)

可以预想到loss关于M的梯度,应该也是个4*4的矩阵,某一次我的求解结果如下:

<tf.Tensor: shape=(4, 4), dtype=float32, numpy=
array([[4.061111 , 5.2805605, 5.0363827, 4.5502386],
       [3.998496 , 2.6664422, 2.626386 , 3.7821217],
       [2.060357 , 1.7272003, 1.1037377, 1.1576421],
       [2.7792683, 2.8052883, 2.1635182, 2.1099963]], dtype=float32)>

确实是梯度矩阵应该有的形状(因为M的初始值是随机数,所以这个结果应该每个人运行的都不太一样,形状是对的就行)

有时候你会希望使用二阶的优化算法,涉及到求解海森矩阵,那么就相当于求解二阶微分,可以嵌套使用gradientTape,下例中使用牛顿法更新M。

with tf.GradientTape() as tape1:
        tape1.watch(M)
        with tf.GradientTape() as tape2:
            # 因为M是trainable的,所以不需要tape.watch
            tape2.watch(M)
            # 计算目标矩阵,先将对角线减成0,再加上每行的负总和
            L = M - tf.linalg.diag(tf.linalg.diag_part(M))
            L = L - tf.linalg.diag(tf.reduce_sum(M, 1))
            # 计算目标矩阵的特征值向量
            eig_pred = tf.linalg.eigvals(L)
            # 用MSE计算损失函数
            loss = keras.losses.mse(eig_true, eig_pred)
            # 用tape获得loss关于M的梯度
        g = tape2.gradient(loss, M)
    H = tape1.gradient(g,M)
    # 使用牛顿法更新M
    M = M + 5E-5*tf.matmul(tf.linalg.inv(H), g)
    print(f'loss={loss}')

为什么不用persistent=True而选择嵌套?因为我发现使用persistent=True的话会报warning,提示你求解高阶微分这样效率很低,所以用嵌套的tape。

当你会使用GradientTape之后就可以自己设计梯度下降的优化过程了。依然是求解奇怪矩阵的问题,迭代过程可以如下表示

import tensorflow as tf
import tensorflow.keras as keras
import numpy as np

# 求解一个4*4的矩阵
# 要求矩阵的对角线元素为对应行所有元素和的负数
# 要求矩阵的特征值为[0.36, 1.22, 3.6, 6.28]

eig_true = tf.Variable([0.36, 1.22, 3.6, 6.28])
M = tf.Variable(tf.random.uniform((4,4)), trainable=True)

for _ in range(5000):
    with tf.GradientTape(persistent=True) as tape:
        # 因为M是trainable的,所以不需要tape.watch
        tape.watch(M)
        # 计算目标矩阵,先将对角线减成0,再加上每行的负总和
        L = M - tf.linalg.diag(tf.linalg.diag_part(M))
        L = L - tf.linalg.diag(tf.reduce_sum(M, 1))
        # 计算目标矩阵的特征值向量
        eig_pred = tf.linalg.eigvals(L)
        # 用MSE计算损失函数
        loss = keras.losses.mse(eig_true, eig_pred)
        # 用tape获得loss关于M的梯度
    gradient = tape.gradient(loss, M)
    M = M - 0.001*gradient
    print(f'loss = {loss}')

L = M - tf.linalg.diag(tf.linalg.diag_part(M))
L = L - tf.linalg.diag(tf.reduce_sum(M, 1))

经过5000次迭代后,损失函数下降到6.27E-12,结果为

L=<tf.Tensor: shape=(4, 4), dtype=float32, numpy=
array([[ 1.3509786 , -0.62936515, -0.5123901 , -0.16779563],
       [-0.04930454,  0.74879396,  0.06027164, -0.40845823],
       [-1.2807546 , -0.76512206,  3.935327  , -1.3969783 ],
       [-1.3157498 , -1.6247269 , -1.3110836 ,  5.4248962 ]],
      dtype=float32)>
eig=<tf.Tensor: shape=(4,), dtype=complex64, numpy=
array([0.35999656+0.j, 1.2199994 +0.j, 3.6000035 +0.j, 6.28      +0.j],
      dtype=complex64)>

可以说和期望值已经相当接近了。

4 预处理数据集

至此距离开始训练只差准备一套数据集了。

4.1 数据集的形状

首先你必须很清楚你的神经网络输(也就是数据集)的形状是什么样的。
这里谈论的形状可以理解成张量的维度了,他和通常说的向量的维度不一样,一个n维向量从tensor的角度看,它仍然是一维的,为了区分,后面用形状(shape)表述张量,它既表达了张量的维度,也表达了每一维度的向量的维度。
神经网络的实际输入张量形状,比数据形状多一维,例如一个色彩图像的形状一般是(rows, cols, 3),3是三个色彩通道,但是实际输入到神经网络的时候,需要的张量形状是(batch_size, rows, cols, 3)。
因为网络在做随机梯度下降,每一次扔进网络的数据都不是单独的一张图片,而是一批图片,第一个增加的维度就是数据的量。

因此,可以发现之前在初始化神经网络的时候,图像的形状是(28,28,3),但是我给神经网络的输入是(1,28,28,3),多给了一个维度。

加入输入的是一个3维向量,那么神经网络的输入张量形状应该是(batch_size, 3)对吧。

所以如果你观察fashion mnist数据集的形状,你会发现
x_train.shape = (60000, 28, 28, 3)
y_train.shape = (60000, 1)
即总共有60000个数据样本。

形状很重要,形状没有明确的话,张量运算就是一堆报错。

4.2 使用datasets获取数据集

完全可以在网上搜索数据,保存到csv里,然后读取并且打包成合适形状的numpy或者tensor数据。
但是TensorFlow有官方的数据集库,可以直接下载使用。
可用的数据集在tf.keras.datasets中,下面的方式可以直接获得需要数据集的训练集和测试集。

f_mnist = tf.keras.datasets.fashion_mnist
(x_train, y_train),(x_test, y_test) = f_mnist.load_data()

有哪些数据集可以使用?在这儿查看
TensorFlow官方很支持把数据打包成dataset格式使用,但是我还没有养成这个好习惯,我就直接这么用了。

4.3 预处理数据集与训练

形状处理: 有时候拿到的数据形状是不太对的,例如fashion mnist数据集的形状是(60000, 28, 28),但是卷积神经网络的输入应该是图像数据,所以应该还有一维的灰度通道。
帮他加一个维度。

x_train = x_train[:,:,:,tf.newaxis]

数据格式修改: 为了能够运算,输入数据格式应该是float32或者更高位的类型,但是由于mnist的数据是图像,默认是8通道灰度图,也就是数据格式是uint8,这没法训练,需要修改一下格式。

x_test = x_test.astype("float32")

归一化处理: 俗称把量纲的作用排除掉,有很多种方式可以做到,归一化处理能让训练更简单一点,因为知道灰度图的范围就是8位[0,255],所以直接

x_train = x_train/255
x_test = x_test/255

处理完成后使用model的fit方法进行模型训练

history = model.fit(
    x_train,
    y_train,
    batch_size=128,
    epochs=20,
    validation_data=(x_test, y_test)
)
  • batch_size: 每一次利用多少数据进行梯度运算。
  • epoch:用一个batch做一次优化记为一个iteration,在一个epoch中会随机抽batch_size的mini_batch进行训练,并且每次都不重复地抽,当把整个训练集都抽完了,就经过了一个epoch。所以一个epoch包含了[train_size/batch_size]+1个iteration。别管那么多了就当他是训练多少次就完事儿了。
  • validation_data:给出测试集

除此以外,TensorFlow官方是更推荐将数据集打包成dataset进行训练的,dataset可以指定shuffle_size和batch_size,但不用dataset的话也会默认shuffle打乱,所以我还没有适应使用dataset进行训练。

fit方法的返回值是什么?
我知道的不是很详细,我只知道这个返回值是个类,他有个history属性,里面存放了整个训练过程中在metrics中设定的参数的变化过程。
通常在训练结束后我会读取history并且plot出来分析训练过程是否合理,是否出现了过拟合或者欠拟合,分析可以如何调整网络模型或者训练参数。

5 使用tensorboard进行训练可视化查看

这个太多了。
官方文档
可以很方便地回顾训练过程以针对性调整网络结构或者训练参数。
官方文档写的非常详细易懂,还写了那么长,我觉得我写不清楚。

6 不使用fit进行网络训练

当TensorFlow已经如此方便与集成化之后,如果还需要更加自由的个性的细节,可能就要自己编写训练过程。

// 通过这里的代码,你拥有了预处理后的训练集和测试集
// 通过这里的代码,你搭建完成了需要的神经网络结构并取名为model

def get_batch(batch_size):
	// 通过这个函数,你实现了从训练集里随机抽取一个batch的数据进行训练
	// 并且还可以或者这个batch的y_true标签
	return x_batch, y_true

// 你选择了Adam作为优化器,并且设置学习率为5E-4,并且设置了衰减率0.001
optimizer = tf.keras.optimizers.Adam(5E-4, decay = 0.001)

// 你认为迭代1000次比较合理,并且开始了训练
for _ in range(1000):
	with tf.GradientTape() as tape:
		// 你获取了一个batch的训练集数据,并且计算出了y的预测值
	    inputs, y_true = get_batch(32)
	    y_pred = model(inputs)
	    
	    // 你决定使用MSE作为损失函数,并且计算了损失
	    loss = keras.losses.mean_squared_error(y_true, y_pred)
	    
	// 你计算出了梯度,并且通过之前定义的优化器对网络参数进行了优化
	// 忘了说,你可以通过model.trainable_variables查看网络的参数
	gradient = tape.gradient(loss, model.trainable_variables)
	optimizer.apply_gradients(zip(gradient, model.trainable_variables))

	// 此时网络已经可以进行优化,但是它不能像fit函数一样在训练过程中打印出漂亮的训练参数情况,于是你准备多写一点代码,让训练过程中能可视化一些数据,例如损失或者正确率

例如当我写深度强化学习的代码的时候,他的训练过程包含了很多乱七八糟的细节,此时用fit方法就无法实现这些细节,我可能会选择用上述的方式自己定义训练的过程该如何运算。

好了结束了。我相信这一篇的内容可能仍然不能让你把TensorFlow写的很好,但是应该是可以从容地应对自己需要的神经网络模型了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值