1、卷积神经网络CNN (Convolutional Neural Network):被广泛应用于图像识别、语音识别等场合,在图像识别比赛中,基于深度学习的方法几乎都以CNN为基础。CNN中新增卷积层(Convolution层)和池化层(Pooling层),连接顺序变成了“Convolution-Relu-Pooling(有时可以省略)”,而输出层的前一层依旧使用“Affine-ReLU”组合。
2、全连接:神经网络相邻层的所有神经元之间都有连接,输出的数量可以任意决定,比如Affine层。全连接层所存在的问题是忽视了数据的形状,无法利用与形状相关的信息,例如三维数据被拉平成一维数据,图像是3维形状包含着重要的空间信息(空间上邻近的像素为相似的值、RGB的各个通道之间分别有密切联系、相距较远的像素之间没有什么关联),3维形状中可能隐藏有值得提取的本质模式。
3、特征图:卷积层的输入输出数据称为特征图,输入数据称为输入特征图,输出数据称为输出特征图。卷积层会以3维数据的形式接收输入数据,并同样以3维数据的形式输出至下一层。
4、卷积运算:卷积层进行的处理就是卷积运算,相当于图像处理中的“滤波器运算”,对输入数据应用滤波器进行处理,输入数据和滤波器都有高长方向上的维度,卷积运算时以一定间隔滑动滤波器的窗口并应用,将滤波器各个位置的元素与输入的对应元素相乘,然后再进行求和(乘积累加运算),最后再将这个结果保存到输出对应位置,将这个过程在所有位置都进行一遍就可以得到卷积运算的最终结果。
在全连接的神经网络中,除了权重参数,还存在偏置,CNN中滤波器的参数对应之前的权重,然后再将结果加上偏置(通常只有1个,会被加到应用了滤波器的所有元素上)得到输出结果。
5、填充:在进行卷积层处理之前,有时要向输入数据的周围填入固定的数据(比如0),进行输出大小的调整,一般是为了保持输入输出数据的空间大小一致,是卷积运算中经常会用到的处理,“幅度为1的填充”是指用幅度为1像素的0填充周围的空间。
6、步幅:应用滤波器的位置间隔,通常增大步幅后,输出大小会变小,而增大扩充后,输出大小会变大。假设输入大小为(H,W),滤波器大小为(FH,FW),输出大小为(OH,OW),填充为P,步幅为S,输出大小可以用以下公式计算。
虽然只要代入值就可以计算输出大小,但是所设定的值需使公式分别可以除尽,无法除尽时要采取报错等对策,根据深度学习框架的不同,有时也会向最接近的整数四舍五入。
7、3维数据的卷积运算:除了高、长方向之外,还需要处理通道方向,滤波器的通道数只能设定为和输入数据的通道数相同的值。当通道方向上有多个特征图时,会按照通道进行输入数据和滤波器的卷积运算,并将结果相加得到最终输出。
如果要在通道方向上拥有多个卷积运算的输出,要考虑使用多个滤波器(权重),通过应用FN个滤波器(权重数据按照(output_channel,input_channel,height,width)顺序书写),输出特征图也生成FN个,再将这FN个特征图汇集到一起,就得到形状为(FN,OH,OW)的输出方块,最后传给下一层,这个过程表示CNN的处理流。进行追加偏置的加法运算处理时,偏置的形状是(FN,1,1),可以基于Numpy的广播功能加以实现。
8、批处理:能够实现高效化和学习时对mini-batch的对应,卷积运算与批处理结合需要将各层间传递的数据保存为4维数据(batch_num,channel,height,width),在批处理版的数据流中,各个数据的开头会添加批用的维度,数据作为4维形状在各层间传递,批处理将N次处理汇总成1次进行。
9、池化层:池化是缩小高、长方向上空间的运算,比如有一个4x4的原空间,进行2x2区域集约成1个元素,缩小空间大小,一般来说,池化的窗口大小会和步幅设定成相同的值,步幅设为2,“2x2”表示目标区域的大小,“Max池化”是获取目标区域最大值的运算,取值结束向前移动步幅为2继续取值。
池化层的特征:
- 没有要学习的参数——池化只是从目标区域中取最大值或者平均值(Max池化/Average池化),不存在要学习的参数;
- 通道数不发生变化——经过池化运算,输入数据和输出数据的通道数不会发生变化,计算按照通道独立进行;
- 对微小位置变化具有鲁棒性(健壮性)——输入数据发生微小偏差时,池化仍会返回相同的结果。
10、卷积层的实现:CNN中各层间传递的数据是4维数据,比如10个高为28、长为28,通道数为1的数据形状是(10,1,28,28),看上去比较复杂,这时使用im2col技巧可以使问题变简单。im2col是一个函数,功能是将输入数据展开以适合滤波器(权重),对于输入数据,会将应用滤波器的区域(3维方块)横向展开为1列,这个展开处理会在所有应用滤波器的地方进行。
im2col(input_data,filter_h,filter_w,stride=1,pad=0)
- input_data——由(数据量N,通道C,高H,长W)4维数组构成的输入数据
- filter_h——滤波器的高
- filter_w——滤波器的长
- stride——步幅
- pad——填充
在实际运算中,滤波器的应用区域几乎是重叠的,使用im2col展开后,展开后的元素个数会多于原方块的元素个数,因此im2col的实现比普通实现消耗更多的内存,但是汇总成一个大矩阵进行计算又比较方便(比如线性代数库中矩阵计算的实现已经高度最优化)。使用im2col展开输入数据后,只需将卷积层的滤波器纵向展开为1列,并计算两个矩阵的乘积,最后输出结果是2维矩阵,还需要进行进一步转换,转换成4维数组。
class Convolution: #卷积层
def __init__(self,W,b,stride=1,pad=0):
self.W = W
self.b = b
self.stride = stride
self.pad = pad
def forward(self,x):
FN,C,FH,FW = self.W.shape
N,C,H,W = x.shape
out_h = int(1 + (H + 2*self.pad - FH) / self.stride)
out_w = int(1 + (W + 2*self.pad - FW) / self.stride)
col = im2col(x,FH,FW,self.stride,self.pad)
col_W = self.W.reshape(FN,-1).T #滤波器展开为2维数组
out = np.dot(col,col_W) + self.b
out = out.reshape(N,out_h,out_w,-1).transpose(0,3,1,2)
return out
通过reshape(FN,-1)将参数指定为-1,会自动计算-1维度上的元素个数,以使多维数组的元素个数前后一致;transpose会更改多维数组的轴的顺序,可以将输出大小转换为合适的形状。另外在进行卷积层的反向传播时,要注意必须进行im2col的逆处理。
11、池化层的实现:池化层的实现也使用im2col展开输入数据,池化情况下在通道方向上是独立的,按通道单独展开,展开之后只需求各行的最大值并转换为合适的形状即可。
步骤:
①展开输入数据;
②求各行的最大值(可以使用np.max方法,指定axis参数并在这个参数指定的各个轴方向上求最大值);
③转换为合适的输出大小。
class Pooling:
def __init__(self,pool_h,pool_w,stride=1,pad=0):
self.pool_h = pool_h
self.pool_w = pool_w
self.stride = stride
self.pad = pad
def forward(self,x):
N,C,H,W = x.shape
out_h = int(1 + (H - self.pool_h) / self.stride)
out_w = int(1 + (W - self.pool_w) / self.stride)
#展开
col = im2col(x,self.pool_h,self.pool_w,self.stride,self.pad)
col = col.reshape(-1,self.pool_h*self.pool_w)
#最大值
out = np.max(col,axis=1)
#转换
out = out.reshape(N,out_h,out_w,C).transpose(0,3,1,2)
return out
池化层的backward处理可以参考ReLU层的实现中使用的max的反向传播。
12、CNN的实现:网络构成为“Convolution-ReLU-Pooling-Affine-ReLU-Affine-Softmax”,实现为名为SimpleConvNet的类,超参数通过名为conv_param的字典传入。
参数:
- input_dim——输入数据的维度(通道,高,长);
- conv_param——卷积层的超参数(字典),关键字为(filter_num,filter_size,stride,pad);
- hidden_size——隐藏层(全连接)的神经元数量;
- output_size——输出层(全连接)的神经元数量;
- weight_int_std——初始化时权重的标准差。
class SimpleConvnet:
def __init__(self,input_dim=(1,28,28),conv_param={'filter_num':30,'filter_size':5,'pad':0,'stride':1},hidden_size=100,output_size=10,weight_init_std=0.01):
filter_num = conv_param['filter_num']
filter_size = conv_param['filter_size']
filter_pad = conv_param['pad']
filter_stride = conv_param['stride']
input_size = input_dim[1]
conv_output_size = (input_size - filter_size + 2*filter_pad) / filter_stride + 1
pool_output_size = int(filter_num * (conv_output_size/2)**2)
#权重参数的初始化部分
self.params = {}
self.params['W1'] = weight_init_std * np.random.randn(filter_num,input_dim[0],filter_size,filter_size)
self.params['b1'] = np.zeros(filter_num)
self.params['W2'] = weight_init_std * np.random.randn(pool_output_size,hidden_size)
self.params['b2'] = np.zeros(hidden_size)
self.params['W3'] = weight_init_std * np.random.randn(hidden_size,output_size)
self.params['b2'] = np.zeros(output_size)
#生成层
self.layers = OrderedDict()
self.layers['Conv1'] = Convolution(self.params['W1'],self.params['W2'],
conv_param['stride'],conv_param['pad'])
self.layers['Relu1'] = Relu()
self.layers['Pool1'] = Pooling(pool_h=2,pool_w=2,stride=2)
self.layers['Affine1'] = Affine(self.params['W2'],self.params['b2'])
self.layers['Relu2'] = Relu()
self.layers['Affine2'] = Affine(self.params['W3'],self.params['b3'])
self.last_layer = SoftmaxWithLoss()
def predict(self,x):
for layer in self.layers.value():
x = layer.forward(x)
return x
def loss(self,x,t):
y = self.predict(x)
return self.lastLayer.forward(y,t)
def gradient(self,x,t):
self.loss(x,t) #forward
#backward
dout = 1
dout = self.lastLayer.backward(dout)
layers = list(self.layers.values())
layers.reverse()
for layer in layers:
dout = layer.backward(dout)
#设定
grads = {}
grads['W1'] = self.layers['Conv1'].dW
grads['b1'] = self.layers['Conv1'].db
grads['W2'] = self.layers['Affine1'].dW
grads['b2'] = self.layers['Affine1'].db
grads['W3'] = self.layers['Affine2'].dW
grads['b3'] = self.layers['Affine2'].db
return grads
参数的梯度通过误差反向传播法(将正向传播和反向传播组装在一起)求出,各层已经实现了正向传播和反向传播功能,所以只需要以合适顺序调用即可,最后把各个权重参数的梯度保存在grads字典中。卷积层和池化层是图像识别中必备的模块,CNN可以有效读取图像中的某种特性从而实现高精度识别。
13、CNN的可视化:学习前的滤波器是随机进行初始化的,所以黑白浓淡没有规律可循,通过学习,使得滤波器变成了有规律的图像,这些有规律的滤波器通过观察边缘(颜色变化的分界线)和斑块(局部的块状区域)从而做出响应。
在堆叠了多层的CNN中,随着层次加深,提取的信息也会越来越抽象,神经元从简单的形状向高级信息变化,比如AlexNet网络结构堆叠了多层卷积层和池化层,最后经过全连接层输出结果,对于中间数据会连续应用卷积运算。
14、代表性CNN:
- LeNet——1998年首次提出的CNN元祖,是进行手写数字识别的网络,有着连续的卷积层和池化层,最后经过全连接层输出结果。激活函数采用sigmoid函数,并且使用子采样(subsampling)缩小中间数据的大小;现在主流CNN主要采用ReLU函数并进行Max池化。
- AlexNet——是引发深度学习热潮的导火索,网络结构叠加多个卷积层和池化层,最后经过全连接层输出结果,区别主要是:①激活函数使用ReLU、②使用局部正规化的LRN层、③使用Dropout。