本周CS230的课程主要是介绍了GAN相关的一些东西,但是这个需要深入了解,所以本次博客主要对coursera上的课程以及作业进行总结。
目录
一、一些重要的概念
1、深度学习中的超参数
重要性排序:
- 学习率 α \alpha α,学习率是需要调试的最重要的超参数;
- 动量法中的 β \beta β、小批量的大小、隐层的单元数等;
- 网络的层数、学习率衰减等;
- 其他几乎不用太大调整,例如Adam中的 β 1 , β 2 , ϵ \beta_1,\ \beta_2,\ \epsilon β1, β2, ϵ等
调参时尽量不要用网格搜索,在相同的搜索次数下,使用随机搜索会让更重要的参数尝试更多的值。
2、Batch Normalization
Batch Norm其实是一种归一化手段,但是它使用在隐层单元的激活函数之前,对输入这个单元的数据做归一化,使得在神经网络训练过程中每一层神经网络的输入保持相同的分布。
-
具体来说,假设训练集有 n n n个数据,那么对于输入第 [ l ] [l] [l]层已经经过线性映射之后的数据 Z [ l ] Z^{[l]} Z[l],计算他们的样本均值和样本方差,并做归一化处理(其中 ϵ \epsilon ϵ是很小的稳定性常数): μ = 1 n ∑ i z ( i ) , σ 2 = 1 n ∑ i ( z ( i ) − μ ) 2 z n o r m ( i ) = z ( i ) − μ σ 2 + ϵ \mu={1\over n}\sum_i z^{(i)},\ \sigma^2={1\over n}\sum_i (z^{(i)}-\mu)^2\\ z_{norm}^{(i)}={z^{(i)}-\mu\over \sqrt{\sigma^2+\epsilon}} μ=n1i∑z(i), σ2=n1i∑(z(i)−μ)2znorm(i)=σ2+ϵz(i)−μ然后经过归一化的数据就是0为均值、1为方差的数据了,然后再对 Z [ l ] Z^{[l]} Z[l]的分布进行微调,希望它能有一些自己的偏移,即 z ~ ( i ) = γ z n o r m ( i ) + β \tilde{z}^{(i)}=\gamma z_{norm}^{(i)}+\beta z~(i)=γznorm(i)+β其中, γ , β \gamma,\beta γ,β是可训练的参数。最后用 z ~ ( i ) \tilde{z}^{(i)} z~(i)来带入这一层的激活函数进行计算。
-
为什么要用Batch Norm?
如果不使用Batch Norm,由于数据可能经过一层层网络之后变得比较极端,也就是说其分布可能逐渐发生偏移或者变动,会导致训练收敛较慢,甚至可能因为数据分布太靠近激活函数定义域中上下限两端而出现梯度消失的情况,这种在隐层中数据的分布不断发生变化的情况就称为“Internal Covariate Shift”。通过对每层数据做归一化,可以让经过每个单元的数据都是比较稳定的,增强了反向传播信息流动性,加快训练收敛速度。但是我们又不希望失掉每层数据的特殊性,导致网络表达能力下降,所以又进行微调让分布有独特的偏移,使得网络表达能力增强。 -
优点
1、将每层的输入数据调整在一个量级上,减少了隐层输入值变化所产生的Internal Covariate Shift问题,提升了训练速度;
2、允许更高的学习率从而加速收敛,调参也更加容易;
2、有轻微的类似dropout的正则化作用,因为均值和方差往每个隐藏层的激活值上增加了噪音,使得每层的单元不是单一地依赖某一上层的单元; -
测试数据如何进行Batch Norm
实际使用Batch Norm时经常都会使用小批量的优化算法,在训练时会用指数加权滑动平均EWMA来累积计算并储存出各自的均值和方差,测试集进入网络只需要使用训练集的均值和方差即可。
3、数据分布不匹配时,偏差与方差的分析
- 实际中可能存在数据分布不匹配的问题,即训练集的分布和开发集、测试集的分布不属于同一个分布,这种情况下可以将数量较小的目标数据集分一部分到训练集中,然后其余部分拆分为开发集和测试集,因为开发集的作用是选出目标最优的模型,而且开发集需要和测试集保持同样的分布。
- 对于这种情况,可以将训练集分出一小部分作为训练-开发集(train-dev set)来查看数据的不匹配程度。
- 贝叶斯错误率是一个问题所能达到的最小错误率,人类水平错误率(human level error)小于或等于贝叶斯错误。
- 人类水平错误率和训练集错误率(train set error)之间的差称为可避免偏差(avoidable error)。
- 训练集错误率和训练-开发集错误率(train-dev set error)的差别度量了模型的方差,如果模型在训练集上表现很好,却在训练-开发集上较差则说明模型存在高方差问题,过拟合了训练集。
- 训练-开发集错误率和开发集错误率(dev set error)之间的差别度量了数据分布的不匹配程度。
- 比较开发集错误率和测试集错误率(test set error)可以看出模型对开发集是否过拟合,若测试集错误率明显较大,说明模型可能对开发集存在过拟合。
- 人工检查开发集上的错误样例,进行误差分析,或者,进行人工合成数据,使得训练集更相似于实际的开发集和测试集,可以处理这种数据分布不匹配带来的问题。
4、迁移学习、多任务学习、端到端的深度学习
-
迁移学习
神经网络对于一个问题学到的知识(模型),可以应用到另外一个独立的问题上,通常来说这两个问题是比较类似的,且大多数情况另外一个独立问题的训练数据不会特别多,这种情况使用类似的知识来帮助解决新的问题。如果新的问题只有一个小数据集,那可以只训练输出层前的最后一层或者最后两层。但是如果有很多数据,那么可以重新训练网络中的所有参数。
对于重新训练神经网络中的所有参数,之前模型的训练阶段被称为预训练(pre-training),相当于之前的训练过的模型参数来给新的模型做了初始化;而这个新模型的训练过程称为微调(fine tuning)。 -
多任务学习
在一个网络中并行学习多个任务,希望每个任务都能帮到其他所有任务。例如一个模型需要检测图片中是否有红绿灯、是否有停车标志、是否有车辆、是否有行人等,这是多个任务在同一个模型中一起学习,此时模型的损失可以用多个任务的损失求平均或加权平均等来计算。
当训练的一组任务,可以共用低层次特征、每个任务的数据量很接近、可以训练一个足够大的神经网络,则多任务学习会比多个单任务模型性能更优。 -
端到端的深度学习
端到端的深度学习就是忽略人工设计的一些多个阶段的数据处理系统或者学习系统,直接用一个神经网络,从输入到输出,自行学习。
但是一般来说,端到端的神经网络需要大量的数据,以及一个巨大的网络,所以意味着需要更好的计算资源来训练这个网络。当数据集较小的时候,传统流水线方法通常做得更好。
端到端深度学习系统表现可以很好,也可以简化系统架构,它不需要搭建那么多手工设计的单独组件,但它也不是灵丹妙药,并不是每次都能成功。
二、TensorFlow2.1
安装
我是在Mac下Anaconda中自带的Spyder中进行Python3.7的编译,在参考了列出的参考资料2中视频教程以及Mac下Anaconda通过清华镜像安装TensorFlow2.X、TensorFlow2.1.0安装教程,并经历了无数次失败、重装Anaconda后,我来谈一谈成功安装的步骤:
1、首先打开终端创建一个名为TF2.1的新的conda环境
conda create -n TF2.1
2、激活这个环境,这时终端会显示当前所在的环境为(TF2.1)而不是(base)
source activate TF2.1
3、使用清华镜像(其他路径很慢而且成功率不高)安装CPU版本(若计算机有NVIDIA的GPU显卡可以安装性能更强的GPU版本),在这之前按照参考资料1官方指南中确认pip版本等,安装过程中可能有的包会因为网的关系没装好,可以多试几次
pip install -U tensorflow -i https://pypi.tuna.tsinghua.edu.cn/simple
4、终端中输入python进行Python环境,如果能import的话可以查看下安装的版本是否为2.1.0,没问题的话就可以退出并关掉终端了
import tensorflow as tf
tf.__version__
5、进入Anaconda,在TF2.1的环境下重新安装Spyder,安装好之后进入并测试,基本上就完成了。(如果在这个环境下import其他模块例如matplotlib等,需要在这个环境下重新导入,这个也可以用终端,也可以直接在Anaconda中添加)
作业
作业中使用的是TF1的版本,在TF1中主要是静态的需要使用会话Session来完成执行,但是TF2之后使用动态计算图即Eager Excution即可执行模式,所见即所得,也很符合我们一般编程的习惯,所以直接学习TF2。学习的话可以参考文末的参考资料进行学习,视频、书都有,基本上了解了TensorFlow之后再完成作业。
作业前面部分是tensor的操作、常用的函数等,这部分比较基础就没放在本文中。作业后面主要是对一个手势数据集建立一个单元数量为[25,12,6]的神经网络,识别出0、1、2、3、4、5六种手势。作业中主要是以基于原生代码进行搭建,所以我在完成的时候首先也是用原生代码搭建模型,然后又用tf.keras的主要的三种构建模型的方式又做了一遍。下面为作业代码:
1、原生代码搭建模型,基于tf.GradientTape
import numpy as np
import h5py
import matplotlib.pyplot as plt
import tensorflow as tf
from tf_utils import load_dataset, random_mini_batches, convert_to_one_hot, predict
# 查看即可执行是否启用
tf.executing_eagerly() # 默认为True
'''========= 1.原生代码搭建模型,基于tf.GradientTape =============='''
# 导入数据集,0-5的手势图片
X_train_orig, Y_train_orig, X_test_orig, Y_test_orig, classes = load_dataset()
# 传入数据
x_train = X_train_orig.reshape(X_train_orig.shape[0], -1) / 255 # (1080, 12288)
x_test = X_test_orig.reshape(X_test_orig.shape[0], -1) / 255 # (120, 12288)
y_train = Y_train_orig.T # (1080, 1)
y_test = Y_test_orig.T # (120, 1)
# model
def model(x_train, y_train, x_test, y_test, epochs=200, batchsize=32, lr=0.0001, verbose=False):
# 定义后续要用到的变量
loss_process = [] # 用来储存每次epoch的损失
loss_all = 0 # 记录每次batch的累积损失
test_acc = [] # 用来记录每次epoch在测试集上的精度
# 训练集分批,注意数据的维数
train_bd = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(batchsize)
# 初始化参数,将参数设为可训练的变量
# 三层网络,分别为25、12、6个单元,隐层为ReLu,输出层为softmax
W1 = tf.Variable(tf.random.normal([12288, 25], stddev=0.01, dtype=tf.float64))
b1 = tf.Variable(tf.zeros([25], dtype=tf.float64))
W2 = tf.Variable(tf.random.normal([25, 12], stddev=0.01, dtype=tf.float64))
b2 = tf.Variable(tf.zeros([12], dtype=tf.float64))
W3 = tf.Variable(tf.random.normal([12, 6], stddev=0.01, dtype=tf.float64))
b3 = tf.Variable(tf.zeros([6], dtype=tf.float64))
# 训练部分
for epoch in range(epochs):
for batch, (x, y) in enumerate(train_bd):
with tf.GradientTape() as tape:
# 前向传播
z1 = tf.add(tf.matmul(x, W1), b1)
a1 = tf.nn.relu(z1)
z2 = tf.add(tf.matmul(a1, W2), b2)
a2 = tf.nn.relu(z2)
z3 = tf.add(tf.matmul(a2, W3), b3)
# 独热编码标签
y = tf.one_hot(y, depth=6)
# 计算损失
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=z3, labels=y))
loss_all += loss.numpy() # 累积这个batch的损失,numpy函数将输出格式从tensor转换为numpy.ndarray
# 计算梯度
grads = tape.gradient(loss, [W1, b1, W2, b2, W3, b3])
# 更新参数
W1.assign_sub(lr * grads[0])
b1.assign_sub(lr * grads[1])
W2.assign_sub(lr * grads[2])
b2.assign_sub(lr * grads[3])
W3.assign_sub(lr * grads[4])
b3.assign_sub(lr * grads[5])
# 将一次epoch的训练loss记录
ave_loss = loss_all / (1080//batchsize+1) # 一次epoch包含(1080//batchsize+1)次batch
loss_process.append(ave_loss)
loss_all = 0 # 为下一次epoch做准备
# 测试部分
# 前向传播
z1 = tf.add(tf.matmul(x_test, W1), b1)
a1 = tf.nn.relu(z1)
z2 = tf.add(tf.matmul(a1, W2), b2)
a2 = tf.nn.relu(z2)
z3 = tf.add(tf.matmul(a2, W3), b3)
a3 = tf.nn.softmax(z3)
# 得到预测标签
y_pred = tf.argmax(a3, axis=1)
# 计算精度
correct = tf.reduce_sum(tf.cast(tf.equal(y_pred, y_test.flatten()), dtype=tf.int32))
acc = correct / 120
test_acc.append(acc)
# 打印信息
if verbose==True and epoch%100==0:
print ("Loss after epoch %d: %.6f, test ACC is %.6f" % (epoch, ave_loss, acc))
# 画图
# loss曲线
plt.title('loss function curve')
plt.xlabel('epoch')
plt.ylabel('average loss')
plt.plot(loss_process, label='loss')
plt.legend()
plt.show()
# acc曲线
plt.title('test accuracy')
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.plot(test_acc, label='accuracy')
plt.legend()
plt.show()
paras = {'W1':W1, 'b1':b1, 'W2':W2, 'b2':b2, 'W3':W3, 'b3':b3}
return paras
# 这里用的是SGD,而且初始化以及lr都和作业中有所不同,lr太小收敛过慢,太大会发散
parameters = model(x_train, y_train, x_test, y_test, epochs=1000, lr=0.004, verbose=True)
# 通过参数可以计算出来训练集精度为0.8556, 测试集精度为0.73333
z1 = tf.add(tf.matmul(x_test, parameters['W1']), parameters['b1'])
a1 = tf.nn.relu(z1)
z2 = tf.add(tf.matmul(a1, parameters['W2']), parameters['b2'])
a2 = tf.nn.relu(z2)
z3 = tf.add(tf.matmul(a2, parameters['W3']), parameters['b3'])
a3 = tf.nn.softmax(z3)
y_pred = tf.argmax(a3, axis=1)
correct = tf.reduce_sum(tf.cast(tf.equal(y_pred, y_test.flatten()), dtype=tf.int32))
print((correct / 120).numpy())
上述代码的运行结果:
模型在训练集上精度为0.8556, 测试集精度为0.73333。上面两个图是在单纯的梯度下降法下的模型,可以看出在迭代较早时效果还是比较可以的,损失下降也还行,但是后期效果很不稳定,这时主要是受学习率的影响,因为这里没有采取学习率衰减策略,导致后期学习率太大,模型震荡得太厉害。
而且这个效果还是我已经尝试了很多次学习率之后才出现的,太小的话基本上loss一直没什么变化,太大这个发散出现的更让人头疼。反正多试几次就会体会到有其他的优化方法、early stop这些是多么有用而且必要的了。
在后面用tf.Keras来构建模型之后就会觉得这种做法又耗时,结构也不好调整,不推荐。
2、Sequential按层顺序构建模型
Sequential按照一层层的顺序构建模型。使用这样封装好的代码,较为便捷,但无法写出带有跳连的非顺序网络结构。
'''=========== 2.Sequential按层顺序构建模型 ==========='''
del model #删除现有模型
# 一层层建立模型
model = tf.keras.Sequential()
model.add(tf.keras.layers.Dense(25, activation='relu'))
model.add(tf.keras.layers.Dense(12, activation='relu'))
model.add(tf.keras.layers.Dense(6, activation='softmax'))
# 编译
model.compile(optimizer=tf.keras.optimizers.Adam(lr=0.0001),
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False), # 输出前经过softmax概率变换为False,没有经过变换为True
metrics=['sparse_categorical_accuracy'] # 标签为数值,输出为独热码形式
)
# 训练模型
history = model.fit(x_train, y_train,
batch_size=32, epochs=200,
validation_data=(x_test, y_test),
validation_split=1 # 每次epoch测试一次
)
# 查看模型
model.summary()
# 评估模型
def plot_metric(history, metric):
train_metrics = history.history[metric] # 训练集的loss或者acc
val_metrics = history.history['val_'+metric] #测试集的loss或acc
epochs = range(1, len(train_metrics) + 1) # epoch从1开始
plt.plot(epochs, train_metrics, 'b-')
plt.plot(epochs, val_metrics, 'r-')
plt.title('Training and test '+ metric)
plt.xlabel("Epochs")
plt.ylabel(metric)
plt.legend(["train_"+metric, 'test_'+metric])
plt.show()
# 画出loss曲线
plot_metric(history,"loss")
# 画出acc曲线
plot_metric(history,"sparse_categorical_accuracy")
# 输出loss和精度
model.evaluate(x_train, y_train)
model.evaluate(x_test, y_test)
下图是200次epoch,学习率为0.0001,SGD下的loss曲线和acc曲线,最后的模型精度还不到0.5,可以说是很差,需要更多的迭代次数,但是这所有条件均保持不变,只是把优化器换成Adam,模型精度最后可以达到0.8以上
下面是换成Adam之后的曲线,很明显收敛快
3、函数式API构建模型
函数式API可以构建任意结构的模型。通过将每一层作为一个函数来传递给下一层,可以自定义每层结构,也可以进行多个输入或者输出的融合。
'''============ 3、函数式API构建模型 ============ '''
del model #删除现有模型
# 建立输入,shape就是输入的特征数
inputs = tf.keras.Input(shape=(12288,))
# 第一层调用带ReLu的Dense层,将inputs作为自变量,输入因变量x
x = tf.keras.layers.Dense(25, activation='relu')(inputs)
# 第二层类似,上一层的输出作为这一层的输入
x = tf.keras.layers.Dense(12, activation='relu')(x)
# 输出层用softmax
outputs = tf.keras.layers.Dense(6, activation='softmax')(x)
# 建立model
model = tf.keras.Model(inputs=inputs, outputs=outputs)
# 查看模型
model.summary()
# 后面类似
# 编译
model.compile(optimizer=tf.keras.optimizers.Adam(lr=0.0001),
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False), # 输出前经过softmax概率变换为False,没有经过变换为True
metrics=['sparse_categorical_accuracy'] # 标签为数值,输出为独热码形式
)
# 训练模型
history = model.fit(x_train, y_train,
batch_size=32, epochs=200,
validation_data=(x_test, y_test),
validation_split=1 # 每次epoch测试一次
)
# 画出loss曲线
plot_metric(history,"loss")
# 画出acc曲线
plot_metric(history,"sparse_categorical_accuracy")
# 输出loss和精度
model.evaluate(x_train, y_train)
model.evaluate(x_test, y_test)
4、Model基类封装自定义网络结构
继承Model基类构建自定义模型。用Model类的结构来自定义每层,并封装好函数,最后调用这个函数。
'''============ 4、Model基类封装自定义网络结构 ============ '''
del model #删除现有模型
# 使用tf.keras.Model的结构来自定义每层封装好函数,最后调用这个函数
# 定义一个MyModel的类,它继承了tf.keras.Model类
class MyModel(tf.keras.Model):
# 定义网络结构块
def __init__(self):
super(MyModel, self).__init__()
self.d1 = tf.keras.layers.Dense(25, activation='relu')
self.d2 = tf.keras.layers.Dense(12, activation='relu')
self.d3 = tf.keras.layers.Dense(6, activation='softmax')
# 调用网络块,实现前向传播
def call(self, x):
x = self.d1(x)
x = self.d2(x)
y = self.d3(x)
return y
# 建立model
model = MyModel()
# 后面类似
# 编译
model.compile(optimizer=tf.keras.optimizers.Adam(lr=0.0001),
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False), # 输出前经过softmax概率变换为False,没有经过变换为True
metrics=['sparse_categorical_accuracy'] # 标签为数值,输出为独热码形式
)
# 训练模型
history = model.fit(x_train, y_train,
batch_size=32, epochs=200,
validation_data=(x_test, y_test),
validation_split=1 # 每次epoch测试一次
)
# 查看模型
model.summary()
# 画出loss曲线
plot_metric(history,"loss")
# 画出acc曲线
plot_metric(history,"sparse_categorical_accuracy")
# 输出loss和精度
model.evaluate(x_train, y_train)
model.evaluate(x_test, y_test)
'''============ 在自己的图片上试试效果 ============ '''
from skimage.transform import resize
# 读入图片并显示
my_image = plt.imread('image/myimage4.jpg')
plt.imshow(my_image)
# 显示图片大小
my_image.shape
# 调整像素
my_image = resize(my_image, output_shape=(64,64))
# 模型预测类别
# model.predict_classes(my_image.reshape(1, -1))
# 用第一个模型
z1 = tf.add(tf.matmul(my_image.reshape(1, -1), parameters['W1']), parameters['b1'])
a1 = tf.nn.relu(z1)
z2 = tf.add(tf.matmul(a1, parameters['W2']), parameters['b2'])
a2 = tf.nn.relu(z2)
z3 = tf.add(tf.matmul(a2, parameters['W3']), parameters['b3'])
a3 = tf.nn.softmax(z3)
# 得到预测标签
tf.argmax(a3, axis=1)
最后在自己的图片上实现的效果,试了几个,好像只有这一个是预测正确的,效果不太好(感谢达哥出镜的手哈哈)。
上述都是采用内置的fit方法来训练模型,除了上述fit这样训练模型的方法外,还有其他训练方法,例如:内置train_on_batch方法、自定义训练循环、单GPU训练模型、多GPU训练模型、TPU训练模型等。
还有在我实际运行的过程中这些结果并不是那么稳健的,有时候模型在测试集上精度很高,有时候没那么高,有时候甚至都不收敛,我认为应该是和初始化有关,可能存在梯度消失的情况。
总之,神经网络模型的调优有太多可以学习的了,很多因素都会影响最终的模型,路漫漫其修远兮,吾将上下而求索~
参考资料
1、TensorFlow官方指南
2、视频:北大曹健老师的《人工智能实践-TensorFlow笔记2 》(TF2.1版)
3、《动手学深度学习》(TF2.0版)
4、Keras中文文档
5、视频:tensorflow2.0入门与实战
6、从一个公众号了解到的书:《30天吃掉那只 TensorFlow2.0 》