卷积神经网络用于图像分类,最早可以追溯到Lenet-5,它最早被应用于手写数字的识别,并且取得了不错的分类效果。
因为通常数据集是要求我们自己收集,而且有些数据并不是特别容易收集的,会遇到采集仪器价格昂贵、样本可收集性不高等问题。
这就说明了一个潜在的问题,数据集要多大才能用于分类网络中。
下面,送给大家三句话:
训练集中每个类应有1000张图像;
所用图像应当是具有代表性的高质量图像;
如果图像数量不够,可采用数据增强方法。
1、迁移学习
因为你正在使用是已经查看过大量图像并且学会区分类的网络,所以通常可以教给模型同一领域的新类,只需要几十个数据样本即可。
2、数据增强
博主分享几个自己常用的数据增强代码。
"伽马变换"
img = cv2.imread(file_pathname+'/'+filename, 1)
# 图像归一化
fi = img / 255.0
# 伽马变换
gamma = 0.8 #自己设定
out = np.power(fi, gamma)
out = out * 255
"平移"
def affine(img, a, b, c, d, tx, ty): #平移
H, W, = img.shape
tem = img.copy()
img = np.zeros((H + 2, W + 2, ), dtype=np.float32)
img[1:H + 1, 1:W + 1] = tem
H_new = np.round(H * d).astype(np.int)
W_new = np.round(W * a).astype(np.int)
out = np.zeros((H_new + 1, W_new + 1, ), dtype=np.float32)
x_new = np.tile(np.arange(W_new), (H_new, 1))
y_new = np.arange(H_new).repeat(W_new).reshape(H_new, -1)
adbc = a * d - b * c
x = np.round((d * x_new - b * y_new) / adbc).astype(np.int) - tx + 1
y = np.round((-c * x_new + a * y_new) / adbc).astype(np.int) - ty + 1
# 避免目标图像对应的原图像中的坐标溢出
x = np.minimum(np.maximum(x, 0), W + 1).astype(np.int)
y = np.minimum(np.maximum(y, 0), H + 1).astype(np.int)
# assgin pixcel to new image
out[y_new, x_new] = img[y, x]
out = out[:H_new, :W_new]
out = out.astype(np.uint8)
return out
"旋转"
def remote(img, degree):
height, width = img.shape[:2]
radians = float(degree / 180 * pi)
heightNew = int(width * fabs(sin((radians))) + height * fabs(cos((radians))))
widthNew = int(height * fabs(sin((radians))) + width * fabs(cos((radians))))
# 得到二维矩阵的旋转的仿射矩阵
matRotation = cv2.getRotationMatrix2D((width / 2, height / 2), degree, 1)
# 中心位置的实际平移
matRotation[0, 2] += (widthNew - width) / 2
matRotation[1, 2] += (heightNew - height) / 2
imgRotation = cv2.warpAffine(img, matRotation,
(widthNew, heightNew), borderValue=(0,0,0))
return imgRotation
3、基于Keras的Data Augmentation方法
代码如下:
from keras.preprocessing.image import ImageDataGenerator
"""
parameters:
rotation_range:整数,数据提升时图片随机转动的角度
width_shift_range:浮点数,图片宽度的某个比例,数据提升时图片水平偏移的幅度
height_shift_range:浮点数,图片高度的某个比例,数据提升时图片竖直偏移的幅度
rescale: 重放缩因子,默认为None. 如果为None或0则不进行放缩,否则会将该数值乘到数据上(在应用其他变换之前)
shear_range:浮点数,剪切强度(逆时针方向的剪切变换角度)
zoom_range:浮点数或形如[lower,upper]的列表,随机缩放的幅度,若为浮点数,则相当于[lower,upper] = [1 - zoom_range, 1+zoom_range]
fill_mode:‘constant’‘nearest’,‘reflect’或‘wrap’之一,当进行变换时超出边界的点将根据本参数给定的方法进行处理
cval:浮点数或整数,当fill_mode=constant时,指定要向超出边界的点填充的值
channel_shift_range: Float. Range for random channel shifts.
horizontal_flip:布尔值,进行随机水平翻转
vertical_flip:布尔值,进行随机竖直翻转
"""
datagen = ImageDataGenerator(
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
rescale=1./255,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode='nearest',
cval=0,
channel_shift_range=0,
horizontal_flip=False,
vertical_flip=False,
rescale=None)
数据增强告一段落,接下来就正式进入卷积神经网络的学习。
alexnet实现手写数字的识别
"直接用代码的形式来表达网络结构"
model = Sequential()
model.add(Conv2D(96, (11, 11),
strides=(1, 1),
input_shape=(28, 28, 1),
padding='same',
activation='relu',
kernel_initializer='uniform'))
# 池化层
model.add(MaxPooling2D(pool_size=(3, 3), strides=(2, 2)))
# 第二层加边使用256个5x5的卷积核,加边,激活函数为relu
model.add(Conv2D(256, (5, 5),
strides=(1, 1),
padding='same',
activation='relu',
kernel_initializer='uniform'))
#使用池化层,步长为2
model.add(MaxPooling2D(pool_size=(3, 3), strides=(2, 2)))
# 第三层卷积,大小为3x3的卷积核使用384个
model.add(Conv2D(384, (3, 3),
strides=(1, 1),
padding='same',
activation='relu',
kernel_initializer='uniform'))
# 第四层卷积,同第三层
model.add(Conv2D(384, (3, 3),
strides=(1, 1),
padding='same',
activation='relu',
kernel_initializer='uniform'))
# 第五层卷积使用的卷积核为256个,其他同上
model.add(Conv2D(256, (3, 3),
strides=(1, 1),
padding='same',
activation='relu',
kernel_initializer='uniform'))
model.add(MaxPooling2D(pool_size=(3, 3), strides=(2, 2)))
model.add(Flatten())
model.add(Dense(4096, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(4096, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(10, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
model.summary()
一个5x5的卷积核用两个串联的3x3卷积核来代替
VGG11、13、16、19,SimpleVGG
"VGG13"
model = Sequential()
model.add(Conv2D(64, (3, 3), strides=(1, 1), input_shape=(32, 32, 3), padding='same', activation='relu',
kernel_initializer='uniform'))
model.add(Conv2D(64, (3, 3), strides=(1, 1), padding='same', activation='relu', kernel_initializer='uniform'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(128, (3, 2), strides=(1, 1), padding='same', activation='relu', kernel_initializer='uniform'))
model.add(Conv2D(128, (3, 3), strides=(1, 1), padding='same', activation='relu', kernel_initializer='uniform'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(256, (3, 3), strides=(1, 1), padding='same', activation='relu', kernel_initializer='uniform'))
model.add(Conv2D(256, (3, 3), strides=(1, 1), padding='same', activation='relu', kernel_initializer='uniform'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(512, (3, 3), strides=(1, 1), padding='same', activation='relu', kernel_initializer='uniform'))
model.add(Conv2D(512, (3, 3), strides=(1, 1), padding='same', activation='relu', kernel_initializer='uniform'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(512, (3, 3), strides=(1, 1), padding='same', activation='relu', kernel_initializer='uniform'))
model.add(Conv2D(512, (3, 3), strides=(1, 1), padding='same', activation='relu', kernel_initializer='uniform'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dense(4096, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(4096, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(10, activation='softmax'))
虽然深度只有22层,但大小却比AlexNet和VGG小很多,GoogleNet参数为500万个,AlexNet参数个数是GoogleNet的12倍,VGGNet参数又是AlexNet的3倍,因此在内存或计算资源有限时,GoogleNet是比较好的选择;从模型结果来看,GoogLeNet的性能却更加优越。
ResNet引入了残差网络结构(residual network),通过这种残差网络结构,可以把网络层弄的很深(据说目前可以达到1000多层),并且最终的分类效果也非常好。
密集连接:缓解梯度消失问题,加强特征传播,鼓励特征复用,极大的减少了参数量。
总结:上诉神经网络都经常用于图像分类中,并辅之迁移学习。