问题描述
笔者在参考http://zh.gluon.ai/chapter_deep-learning-basics/mlp-scratch.html
实现多层感知机的时候,遇到了一个问题
那就是,如果使用ReLU作为激活函数,模型的准确率非常低(只有0.1)
但是如果把那个网站上的代码下载下来运行,准确率能达到80%
这就很奇怪了,我们使用的训练方法都是随机梯度下降,学习率,网络参数也是一样的,结果却相差很大
问题排查
经过断点调试,笔者发现,网站上的代码执行时,它的损失函数值很小(只有50多),而且下降地很快,而我的代码损失函数有200多,而且一直降不下去
在确认代码没写错之后,自然就怀疑是数据处理的问题
网站上的数据集获取是通过网站作者自己实现的d2l.load_data_fashion_mnist(batch_size)
方法,而我是直接调用gdata.vision.FashionMNIST
获取的数据集
经过查看源代码,发现网站作者在加载数据集时,用了一个transformer,也就是gdata.vision.transforms.ToTensor()
,通过查资料,发现这是一个归一化的操作
所以大致可以确定,这个问题是由于数据没进行归一化
实验验证
在计算前,把features除以255也可以达到归一化效果,把代码修改为
for X,y in data_iter:#X=X/255with autograd.record():X=X/255y_hat=net(X)l=loss(y_hat,y).sum()l.backward()
之后,准确率正常了
理论分析
经过查阅资料,笔者形成了两种猜想
1.发生了梯度消失的现象
2.发生了Dead ReLU现象
其中梯度消失先暂时排除,因为网络层数太少了,主要考虑是Dead ReLU
关于Dead ReLU,可以参考一下这篇文章
大致原理就是输入的负数太多,导致它们全都被ReLU函数变成了0,从而影响了学习
具体成因可能是输入的数据太大,并且学习率也比较大,导致进入ReLU函数时负数过多,具体是否是这个成因,需要通过实验验证
实验验证
这里的主要思想是,记录每次ReLU函数中被变成0的元素的占比,并绘图观察
完整代码会放在文章最末
这里就只展示最终结果了
(由于篇幅限制,本来有5轮训练的,这里就每种情况只放第一轮训练的数据图了,剩余轮次的结果差不多)
未做归一化,学习率为0.1的情况,其中relu_rate表示的是被ReLU函数变成0的元素的占比
可以看见,被很快,几乎所有的元素都被ReLU函数变成0了,所以Dead ReLU的猜想可以认为是正确的了
接下来再来看看改进后的图像
首先是学习率调低至0.001时的情况
这是第一轮,后面几轮也都是在0.8上下震荡,这是一个比较正常的情况了
再看一下进行了归一化,学习率为0.5的图像
情况和第二种情况类似,也是比较正常
结论
数据如果过多而且过大,且不做归一化,使用ReLU作为激活函数时就可能出现Dead ReLU的情况,而解决办法有降低学习率和进行归一化两种
下期预告
在研究这一问题的时候,笔者还发现几个有趣的现象
在不进行归一化的时候
1.把激活函数换成sigmoid之后,准确率会大幅提升(大概能到50%)
2.把激活函数换成tanh之后,准确率依然很低(大概17%),但是比ReLU要高
下一篇文章会更多的从理论上去分析这两个问题的成因
写在最后
作为刚入门深度学习还不到一个月的萌新,遇到这类问题是挺无语的
路漫漫其修远兮,吾将上下而求索,能从错误中总结经验也是件好事
以上就是个人对这个问题的见解,如果其中有错误,欢迎各位大佬批评指正
完整代码
from mxnet import nd,autograd
from mxnet.gluon import data as gdatafrom mxnet.gluon import loss as glossimport numpy as npfrom matplotlib import pyplot as pltbatch_size = 256def get_data(train=True):train_data = gdata.vision.FashionMNIST(train=train)features = train_data[:][0]features = features.astype("float32")labels = train_data[:][1]return features,labelsdef get_data_iter(train=True,to_tensor=False):features,labels=get_data(train)dataset = gdata.ArrayDataset(features, labels)if(to_tensor):return gdata.DataLoader(dataset.transform_first(gdata.vision.transforms.Compose([gdata.vision.transforms.ToTensor()])),batch_size,shuffle=True)else:return gdata.DataLoader(dataset, batch_size, shuffle=True)def test():test_features,test_labels=get_data(False)y_predict=net(test_features).argmax(axis=1)accuracy=(nd.array(test_labels) == nd.array(y_predict,dtype="float32")).mean().asscalar()print("Accuracy: "+str(accuracy))hidden_num=300
output_num=10w1=nd.random.normal(scale=0.01,shape=(784,hidden_num))
b1=nd.zeros(hidden_num)
w2=nd.random.normal(scale=0.01,shape=(hidden_num,output_num))
b2=nd.zeros(output_num)params=[w1,b1,w2,b2]for param in params:param.attach_grad()relu_rate=0def relu(x):global relu_raterelu_rate=(x<0).mean().asscalar()return nd.sigmoid(x)def net(x):x=x.reshape((-1,784))result=relu(nd.dot(x,w1)+b1)return nd.dot(result,w2)+b2loss=gloss.SoftmaxCrossEntropyLoss()lr=0.5
data_iter=get_data_iter(to_tensor=False)for i in range(5):record_count=(int)(60000/batch_size)graph_x = np.arange(0, record_count, 1)graph_ys = np.zeros((4, record_count))relu_record=np.zeros((record_count,))graph_index = 0for X,y in data_iter:#X=X/255with autograd.record():X=X/255y_hat=net(X)l=loss(y_hat,y).sum()l.backward()j=0for p in params:grad=p.gradp[:]=p-lr*grad/batch_size#record gradient# record relu_rateif(graph_index<record_count):graph_ys[j][graph_index]=grad.sum().asscalar()relu_record[graph_index] = relu_ratej+=1graph_index+=1print("Loss: "+str(l.sum()))test()plt.subplot(2, 2, 1)plt.title("w1")plt.plot(graph_x,graph_ys[0])plt.subplot(2,2,2)plt.title("b1")plt.plot(graph_x,graph_ys[1])plt.subplot(2, 2, 3)plt.title("w2")plt.plot(graph_x,graph_ys[2])plt.subplot(2, 2, 4)plt.title("b2")plt.plot(graph_x,graph_ys[3])#plt.show()plt.savefig("gradient_"+str(i)+".png")plt.clf()plt.title("relu_rate")plt.plot(graph_x, relu_record)#plt.show()plt.savefig("relu_rate_" + str(i) + ".png")plt.clf()