DL_class
学堂在线《深度学习》实验课代码+报告(其中实验1和实验6有配套PPT),授课老师为胡晓林老师。课程链接:https://www.xuetangx.com/training/DP080910033751/619488?channel=i.area.manual_search。
持续更新中。
所有代码为作者所写,并非最后的“标准答案”,只有实验6被扣了1分,其余皆是满分。仓库链接:https://github.com/W-caner/DL_classs。 此外,欢迎关注我的CSDN:https://blog.csdn.net/Can__er?type=blog。
部分数据集由于过大无法上传,我会在博客中给出下载链接。如果对代码有疑问,有更好的思路等,也非常欢迎在评论区与我交流~
实验2:构建自己的多层感知机
实现代码
保留了实验一中的学习率衰减部分,在Q1~Q4的过程中为了绘图方便,没有使用早停法。分别使用numpy将不同层中的前向传播和反向传播方法实现即可。
损失函数
-
欧氏距离:
def forward(self, logit, gt): """ 输入: (minibatch) - logit: 最后一个全连接层的输出结果, 尺寸(batch_size, 10) - gt: 真实标签, 尺寸(batch_size, 10) """ ############################################################################ # TODO: # 在minibatch内计算平均准确率和损失 # 分别保存在self.accu和self.loss里(将在solver.py里自动使用) # 只需要返回self.loss # self.y_: (batch_size, 10) self.y_ = logit self.y = gt # loss: (batch_size, 1) L = np.sqrt(np.sum(np.square(self.y_ - self.y), axis=1)) self.loss = np.average(L) # pre: (batch_size, ) # pre = np.argmax(self.y_, axis=1) self.accu = accuracy_score(np.argmax(self.y_, axis=1), np.argmax(self.y,axis=1)) ############################################################################ return self.loss def backward(self): ############################################################################ # TODO: # 计算并返回梯度(与logit具有同样的尺寸) return self.y_-self.y ############################################################################
-
交叉熵损失函数:
def forward(self, logit, gt): """ 输入: (minibatch) - logit: 最后一个全连接层的输出结果, 尺寸(batch_size, 10) - gt: 真实标签, 尺寸(batch_size, 10) """ ############################################################################ # TODO: # 在minibatch内计算平均准确率和损失,分别保存在self.accu和self.loss里(将在solver.py里自动使用) # 只需要返回self.loss # self.y_: (batch_size, 10) self.y_ = np.exp(logit) / np.sum(np.exp(logit), axis=1, keepdims=True) self.y = gt # L: (batch_size, 1) L = -np.sum(np.log(self.y_)*self.y, axis=1) self.loss = np.average(L) # pre: (batch_size, ) # pre = np.argmax(self.y_, axis=1) self.accu = accuracy_score(np.argmax(self.y_, axis=1), np.argmax(self.y, axis=1)) ############################################################################ return self.loss def backward(self): ############################################################################ # TODO: # 计算并返回梯度(与logit具有同样的尺寸) return self.y_-self.y ############################################################################
正向/反向传播
-
全连接层:
def forward(self, Input): ########################################################################### # TODO: # 对输入计算Wx+b并返回结果. self.Input = Input self.batch_zise = Input.shape[0] return np.dot(Input, self.W) + self.b ############################################################################ def backward(self, delta): # 输入的delta由下一层计算得到 ############################################################################ # TODO: # 根据delta计算梯度 # grad_W: (128, batch_size)*(batch_size,10) -> (128, 10) self.grad_W = np.dot(self.Input.T, delta)/self.batch_zise # grad_b: (batch_size, 10) -> (1, 10) self.grad_b = np.average(delta, axis=0) # delta: (batch_size, 10)*(10,128) -> (batch_size, 128) return np.dot(delta, self.W.T) ############################################################################
-
relu激活层:
def forward(self, Input): ############################################################################ # TODO: # 对输入应用ReLU激活函数并返回结果 self.Input = Input return np.maximum(Input, 0) ############################################################################ def backward(self, delta): ############################################################################ # TODO: # 根据delta计算梯度 delta[self.Input<0]=0 return delta ############################################################################
-
Sigmoid激活层:
def forward(self, Input): ############################################################################ # TODO: # 对输入应用Sigmoid激活函数并返回结果 self.out = 1 / (1+np.exp(-Input)) return self.out ############################################################################ def backward(self, delta): ############################################################################ # TODO: # 根据delta计算梯度 return delta * self.out *(1-self.out) ############################################################################
参数更新
这里不知道是不是写的公式有问题,我也没有搜到 “带权重衰减的动量梯度” 真正公式是什么, 当我指定梯度为0.8的时候,训练到后期反而准确率会下降。默认梯度0.0即不会出现问题。
# 一步反向传播,逐层更新参数
def step(self, model):
layers = model.layerList
for layer in layers:
if layer.trainable:
############################################################################
# TODO:
# 使用layer.grad_W和layer.grad_b计算diff_W and diff_b.
# 注意weightDecay项.
if self.momentum:
layer.W_velocity = self.momentum * layer.W_velocity + \
self.learningRate * \
(layer.grad_W + self.weightDecay*layer.W)
layer.b_velocity = self.momentum * layer.b_velocity + \
self.learningRate*layer.grad_b
layer.diff_W = -layer.W_velocity
layer.diff_b = -layer.b_velocity
else:
# Weight update without momentum
layer.diff_W = -self.learningRate * \
(layer.grad_W+self.weightDecay* layer.W)
layer.diff_b = -self.learningRate * layer.grad_b
############################################################################
# Weight update
layer.W += layer.diff_W
layer.b += layer.diff_b
报告问题
Q1
记录训练和测试准确率,绘制损失函数和准确率曲线图;
下图是使用原始参数,两种方法训练了30个周期的loss和accuracy的结果。
欧式距离损失:
softmax交叉熵损失:
Q2
比较分别使用 Sigmoid 和 ReLU 激活函数时的结果,可以从收敛情况、准确率等方面比较。
- 收敛情况:无论使用哪个作为激活函数,只要模型正确,在足够的周期下都可以收敛,但是使用relu收敛速度(斜率)更快,推测是因为relu函数大于0的时候,导数为恒定值计算较快。
- 准确率:在每个周期纵向对比,使用sigmoid准确率更高,且最后能达到的效果也更好,推测是因为relu导致了网络的稀疏性,对于这种简单数据集而言丢失了一些参数。但同时能看出,relu对于复杂数据集过拟合给出了比较好的解决方案。
Q3
比较分别使用欧式距离损失和交叉熵损失时的结果;
在使用同一种激活函数时,交叉熵损失函数在测试集上有着更好的表现,并且收敛的周期数少(速度快),而在训练集上的准确率来看则是欧氏距离更胜一筹。
感觉损失函数的影响并不绝对,需要搭配恰到好处的应用实例,恰好的激活函数和网络结构才能得到更好的表现。比如在模型输出与真实值的误差服从高斯分布的假设下,最小化均方差损失函数与极大似然估计本质上是一致的,因此在这个假设能被满足的场景中(比如回归),均方差损失是一个很好的损失函数选择;而交叉熵损失可能会更适用于本例(分类问题)。
Q4
构造具有两个隐含层的多层感知机,自行选取合适的激活函数和损失函数,与只有一个隐含层的结果相比较;
这里尝试了sigmoid+crossentropy,这二者的梯度有较为天然的结合,仿照该数据集上的经典模型“LeNet5”的结构,搭建了一个类似的网络(728,128,30,10)。以同样的参数训练和测试,得到对比图:
发现多层网络甚至不如一层网络效果好,推测是因为多层网络较为复杂,又都是全连接,随着学习率的衰减导致陷入局部最优的问题,所以更换激活函数为relu,网络结构如下所示:
multMLP = Network()
# 使用FCLayer和ReLULayer构建多层感知机
# 128, 30为隐含层的神经元数目
multMLP.add(FCLayer(784, 128))
multMLP.add(ReLULayer())
multMLP.add(FCLayer(128, 30))
multMLP.add(ReLULayer())
multMLP.add(FCLayer(30, 10))
对比图如下所示,无论是在收敛速度上,还是训练的准确率上,多个隐含层的网络结构有着更好的表现:
Q5
本案例中给定的超参数可能表现不佳,请自行调整超参数尝试取得更好的结果,记录下每组超参数的结果,并作比较和分析。
从上面的训练过程中明显可以看出,存在较为严重的局部最优和过拟合现象,所以调大学习率,加入早停法,还是按照先批次,再学习率,最后调节权重衰减和周期的方法进行调节,对比结果基本和实验一报告中的情况(就是过大会怎么样,过小会怎么样,为什么最终选择这个参数)吻合,这里不再给出相同参数具体分析,只给对比图了。
-
Batch_size:选择100即可
-
learning_rate:收敛较快,可以直接从0.05开始衰减,每周期衰减1.1~1.5都可以。
-
weight_decay:
可以发现,正则项的加入一定要适度,其起到的是“调节作用”,在超过0.1的时候已经对模型效果产生了负面影响,也就是稍微加一点正则化(甚至这样的简单模型可以不加)为最优。
最终使用batch_size为100,learning_rate从0.08开始,每周期衰减1.2,权重衰减为0.005,早停法进行25个周期的训练,基本上训练5~6个周期即可到达饱和。
此时得到测试集上的最好的结果为96.61%,