我的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((ytrue−ypred)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:L→eL∗=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写的很好,但是应该是可以从容地应对自己需要的神经网络模型了。
本文详细介绍了在TensorFlow2.0中搭建深度学习网络的步骤,包括使用Sequential模型搭建层、选择激活函数、正则化约束、自定义层和网络。此外,还探讨了损失函数的选择,如MSE、二分类交叉熵、多分类交叉熵等,以及优化器的使用,如Adam、RMSprop和SGD。文章还介绍了预处理数据集的方法,强调了数据集的形状、数据格式和归一化处理的重要性。最后,提到了使用TensorBoard进行训练可视化和不使用fit方法进行网络训练的可能性。
812

被折叠的 条评论
为什么被折叠?



