计算机系统应用教程网站

网站首页 > 技术文章 正文

「周末AI课堂」SELU和ResNet(代码篇)机器学习你会遇到的“坑”

btikc 2024-10-12 11:38:54 技术文章 3 ℃ 0 评论




我们在上一节中介绍了较为前瞻的技术,一种叫做SELU的隐藏单元和更易于优化的ResNet,SELU将Batch Normaliztion所蕴含的层归一化思想进一步简化处理为神经元的行为,而ResNet则是添加恒等连接使得深层网络在训练过程中拥有较大的梯度流,缓解梯度消失。

我们首先来对fashion-MNIST数据分别搭建一个56层和20层简单神经网络,并采用我们在以前课程中所涉及到的通用技术,我们采用Adam作为优化算法,ReLU作为隐藏单元。注意,这里我们不使用卷积这一操作,只是因为我们希望看到这两者在优化过程中的作用,而非获得更好的表示。

在keras中,根据我们前面所学得的知识,实现这一点是很容易的,我们可以很快的搭建如下的两个网络:

import numpy as np

from keras.layers import Input

from keras.datasets import fashion_mnist,mnist

from keras import models,layers

import matplotlib.pyplot as plt

from keras.models import Model

from keras.models import Sequential

from keras import optimizers

from keras.layers import Dense,Dropout,Activation

from keras.utils import to_categorical

from keras.layers import BatchNormalization as BN

(X_test,y_test),(X_train,y_train)=fashion_mnist.load_data()

train_labels = to_categorical(y_train)

test_labels = to_categorical(y_test)

X_train = X_train.reshape(10000,28*28)

X_train = X_train.astype('float32') / 255

X_test = X_test.reshape(60000, 28*28)

X_test = X_test.astype('float32') / 255

def model(n):

model=models.Sequential()

model.add(Dense(512,activation='relu',input_shape=(28*28,)))

for i in range(n):

model.add(Dense(256,activation='relu'))

model.add(Dense(10,activation='softmax'))

model.compile(optimizer=optimizers.Adam(),loss='categorical_crossentropy',\

metrics=['accuracy'])

return(model)

model1=model(20)

his1=model1.fit(X_train,train_labels,batch_size=128,validation_split=0.3,verbose=1,epochs=10)

model2=model(56)

his2=model2.fit(X_train,train_labels,batch_size=128,validation_split=0.3,verbose=1,epochs=10)


我们在这里使用了一个循环来简单的实现层数的累加,(注意,这里是21层和57层,因为输入层本质上也是一个隐层,但不会影响到我们对效果的解读,原谅一下我的粗心)接下来观察训练集和测试集上的误差随着epochs的变化:

w1=his1.history

w2=his2.history

import matplotlib.pyplot as plt

import seaborn as sns

sns.set(style='white')

plt.plot(range(10),[1-i for i in w1['val_acc']],'y-',label='20-layer')

plt.plot(range(10),[1-i for i in w2['val_acc']],'r-',label='56-layer')

plt.xlabel('epochs')

plt.ylabel('test error')

plt.legend()




根据测试集上的误差表现来看,56层简单模型的性能远远比不上只有20层的模型,此时,我们需要观察训练集上误差表现,来判断这56层的简单模型是否得到了良好的训练,因为按照理论来说,神经网络的层数越多,模型的容量越大,单纯只看测试集我们无法判断出模型是否已经过拟合还是没有得到训练:

plt.figure()

plt.plot(range(10),[1-i for i in w1['acc']],'y-',label='20-layer')

plt.plot(range(10),[1-i for i in w2['acc']],'r-',label='56-layer')

plt.xlabel('epochs')

plt.ylabel('training error')

plt.legend()

plt.show()





我们可以发现,56层的模型几乎没有得到任何训练,神经网络的参数很大程度上都维持在初始化的水平,这正是我们需要利用SELU和ResNet来解决的优化问题。


SELU在keras中的使用


SELU在keras中使用较为简单,我们直接可以将层中的激活函数参数activation设置为“selu”(当然,我们也可以独立的添加自己编写的层,这将会在后续的课程讲解):

def lay56_model(n):

model=models.Sequential()

model.add(Dense(512,activation='selu',input_shape=(28*28,)))

for i in range(n):

model.add(Dense(256,activation='selu'))# 将relu改为selu

model.add(Dense(10,activation='softmax'))

model.compile(optimizer=optimizers.Adam(),loss='categorical_crossentropy',\

metrics=['accuracy'])

return(model)


并且重复上面的步骤对其进行训练,分别得到训练集上的误差和测试集上的误差:




可以看出,虽然比起relu好了不少,但是优化过程仍然很差,这里面可能的原因是迭代的轮数太少,因为容量大的模型收敛速度一般都是较慢的,我们将epochs扩大为50次,期待会在后面的轮次上收敛到较为满意的水平:





但是从这张图中可以看出,56层模型总体的测试误差和训练误差都在20层模型的总体误差之上,那么这是因为SELU不管用了么?它是否无法构建出自归一化神经网络呢?

这里我们需要注意SELU的两个使用条件:

  • 它假设了输入的数据X是服从标准的高斯分布,我们原先采用的标准化方式,是简单的缩放:

X_train = X_train.astype('float32') / 255


但我们要将其更改为标准化的高斯分布,灰度图片本身是一个二阶张量,很难用传统的手段做标准化,强行标准化会破坏掉图片本身的结构,但是对于我们这样不采用局部相关的神经网络,把所有的数据铺平,就可以采用原先的标准化手段:

from sklearn.preprocessing import StandardScaler

X_train = X_train.reshape(10000,28*28)

scale=StandardScaler().fit(X_train)

X_train=scale.transform(X_train)


  • 它假设了权重系数服从均值为零,方差为输入维度的倒数,我们很难保证在训练过程中,每一个SELU的权值系数服从该分布,但我们至少可以在初始化的过程中做到这一点,keras提供了lecun_normal初始化器,我们可以方便的对每一层设置初始化:

def lay56_model(n):

model=models.Sequential()

model.add(Dense(512,activation='selu',\

kernel_initializer=lecun_normal(),\

input_shape=(28*28,)))

for i in range(n):

model.add(Dense(256,activation='selu',\

kernel_initializer=lecun_normal()))

model.add(Dense(10,kernel_initializer=lecun_normal(),activation='softmax'))

model.compile(optimizer=optimizers.Adam(),loss='categorical_crossentropy',\

metrics=['accuracy'])

return(model)


接下来,我们对修改以后的模型进行训练,结果如下:




如图,我们可以看到更深的网络在数据归一化到标准高斯分布和权值进行特定初始化的帮助下,优化过程比浅层更佳,在很少的步长上就将误差降低到了更低的水平。


在Keras中搭建残差模块

我们需要搭建一个带有恒等映射功能的网络,它的作用是将前几层接受的输入与现有的输出相加,在keras中我们需要将其写作一个函数:

def identity_block(input,units,act):

x = layers.Dense(units,activation=act)(input)

x = layers.Activation('relu')(x)

x = layers.Dense(units,activation=act)(x)

x = layers.Activation('relu')(x)

x = layers.Dense(units,activation=act)(x)

x = layers.Activation('relu')(x)

x = layers.Dense(units,activation=act)(x)

x = layers.add([x, input])

x = layers.Activation('relu')(x)

return x


其中需要注意,我们采用了[x,input]的简单形式将两个张量相加,事实上还可以采用lambda函数定义好运算。然后将其再放入激活函数中,这里的顺序最好不要反过来,因为在最后一层连接输出单元的时候,我们希望仍然是非线性的。

接着我们采用函数式模型将identity_block添加进去,因为函数式模型是更佳灵活的:

def res_model():

input_tensor= Input(shape=(28*28,))

x=Dense(100,activation='relu')(input_tensor)

for i in range(14):

x= identity_block(x,100,'relu')

y=Dense(10,activation='softmax')(x)

model = Model(inputs=input_tensor, outputs=y)

model.compile(optimizer=optimizers.Adam(),loss='categorical_crossentropy',\

metrics=['accuracy'])

return(model)


因为每个残差模块都包括了4层,所以我们这里只循环了14次,但在循环开始前,我们多加了一层,总共有57层,但是不影响,因为57层应该比56层更难优化;为操作方便,隐层的神经元都设置为100(当然,对于20层的网络,隐藏单元数也要设置为100);为了解释方便,我们并没有使用Batch normalization,因为即使效果变好,我们就无法说明到底是残差模块的作用还是BN的作用。

我门直接对其进行训练,可以得到:







原本更难训练的56层神经网络,简单添加残差模块后,训练收敛效果远远高于20层的神经网络!第一次见识到ResNet威力的同学们请慢慢地冷静下来,喝杯咖啡,此时,我们残差模块并没有采取太过复杂的结构,每个残差模块都是简单的排列,换而言之,恒等映射之间互不关联,在很多任务中所采用的残差模块的联系还可以变得非常复杂,但基本道理都是相同的。



作者:唐僧不用海飞丝

如需转载,请后台留言,遵守转载规范

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表