最近做 李飞飞 CS231N 2019 的作业时遇到了问题,具体是assignment1中的features.ipynb中的Neural Network on image features小节,训练一个 用于图片分类的两层神经网络 (结构不重要所以不再展示),要求测试集准确率达到55%(作业提示最高可达60%)。
之前要求训练模型的作业都会预先给出 学习率 α 和 正则化强度 λ 的几个参考值或范围,但这次没有给。我刚开始因为设置了错误的超参数范围,模型的验证集准确率一直在10%左右(该模型的任务是分辨图片属于10个给定类别中的哪一个,10%相当随机选择,已经相当差了),代价曲线也没看出问题,直到参考了其他人的代码才知道 α 和 λ 的范围错了,于是我就开始思考 如何才能快速确定超参数的合适范围?
模型的结构、功能和数据集不同,会导致 合适的超参数范围相差很大 (几个数量级),如果只是 随机尝试或者凭感觉走 ,效率会很低。可视化工具 + 清晰的思路 对于简单和复杂模型的调试都很重要。
python代码实现如下,其中results是一个字典,索引是 lr 和 reg ,键值是训练集和验证集的准确率;best_point存储了最优模型的 lr、 reg坐标
# Visualize validation accuracies
# results[(lr, reg)] = (train_acc, val_acc)
# best_point = (math.log10(lr), math.log10(reg)) # for labeling
x_scatter = [math.log10(x[0]) for x in results]
y_scatter = [math.log10(x[1]) for x in results]
marker_size = 100 # default: 20
colors = [results[x][1] for x in results] # depend color on val_acc
plt.figure()
plt.scatter(x_scatter, y_scatter, marker_size, c=colors, cmap=plt.cm.coolwarm)
plt.annotate('(%.2f,%.2f,%.2f%%)'% (best_point[0],best_point[1],best_acc*100),
xy=best_point, xytext=(-30, 30), textcoords='offset pixels',
bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.5),
arrowprops=dict(arrowstyle = '->', connectionstyle='arc3,rad=0'))
plt.colorbar()
plt.xlabel('10^lr')
plt.ylabel('10^reg')
plt.title('CIFAR-10 validation accuracy')
plt.show()
关于用法:plt.scatter 中可以通过改变颜色样式和点的大小等等, plt.annotate中xytext、textcoords决定标签的位置、bbox决定边框、arrowprops决定箭头等等,具体的用法自行查阅即可。
“浅迭代”
为了加快速度,一开始尽量将迭代次数设置的少一点,等找到合适的超参数范围之后再增加迭代次数来 “ 冲刺 ” 到更高的准确率,这里我设置为300(即每个模型执行300次梯度下降)
“广撒网”
由于刚开始不知道 α 和 λ 的大概范围,因此 范围要足够大 ,这里在 [ 1 0 − 10 10^{-10} 10−10 , 1 0 10 10^{10} 1010] 之间取出 5 个数(建议奇数,为了速度先取少一点)组成 等比数列 作为 α 和 λ 的候选值
lr_range = np.logspace(-10, 10, num=5)
reg_range = np.logspace(-10, 10, num=5)
上图中,从 x轴 (代表学习率 α ,程序中的 lr )方向上看: 红点 的分布范围很窄,因此考虑将 x 轴范围缩小到 [ 1 0 − 5 10^{-5} 10−5 , 1 0 5 10^{5} 105] ;从 y 轴(正则化强度 λ ,程序中的 reg )方向看:红点的分布出现 触底 ,可以在收缩上限的同时扩展下限,即将 y 轴改为 [ 1 0 − 12 10^{-12} 10−12 , 1 0 0 10^{0} 100]
lr_range = np.logspace(-5, 5, num=5)
reg_range = np.logspace(-12, 0, num=5)
lr_range = np.logspace(-3, 2, num=5)
reg_range = np.logspace(-12, 0, num=7)
接下来,按照上述思路交替使用 缩小范围 和 增加候选点 的方法,使得红色和蓝色各自代表的准确率的差值尽可能小,此时说明找到了合适的超参数范围,如图:
找到超参数合适的取值范围之后,将迭代次数从 300 增加到 2000 ,此时最高验证集准确率达到了 60.60% ,并且最好的和最差的之间的差距仍然不大:
这次调试的过程让我对 梯度下降 过程有了 更直观的理解——如果把 梯度下降类比成下山 的过程(当然真实情况是比三维复杂的多的高维空间),我们的目的就是到达整片山岭中 海拔最低 的位置。散点图中每个点都是一个 山谷(局部最优解),训练的目的就是 在这些山谷中找出最深的那个 。迭代次数300时的最优解不一定是1500时的最优解,这是因为迭代次数相当于朝着山下走的步数 ,在没有到达山谷的底部之前我们并不知道山谷有多深。
虽然本文针对的是简单的两层神经网络,并且散点图有一些缺点(例如因为是二维图像,只能同时调节两个参数),但我相信 可视化工具 和 清晰的思路 这两个核心思想对于层数更多、结构更复杂、参数更多、训练时间更长的 深层神经网络 来说也将会是很重要的。
最后,这是我的第一篇博客,处女座的处女作。欢迎评论、转载和收藏,希望能帮到更多的人!