记一次竞赛题目有感——图像识别

比赛连接 sofasofa.io

形状识别:是方还是圆

我们给出四千张图像作为训练集。每个图像中只有一个图形,要么是圆形,要么是正方形。你的任务根据是根据这四千张图片训练出一个二元分类模型,并用它(不是用肉眼)在测试集上判断每个图像中的形状。

训练集中共有4000个灰度图像,预测集中有3550个灰度图像。每个图像中都会含有大量的噪点。
图像的分辨率为40x40,也就是40x40的矩阵,每个矩阵以行向量的形式被存放在train.csv和test.csv中。train.csv和test.csv中每行数据代表一个图像,也就是说每行都有1600个特征。

竞赛题目

最近迷上了深度学习,毅然抛弃了曾经学习已久的Java-Web(当然也不是完全抛弃,只是不那么放精力去研究了,偶尔研究下怎么原生代码实现Spring功能)。言归正传,我今天讲讲如何用深度学习来识别图像中的方形和圆形吧。

square.jpg

先放上训练的图像,我们从图中是不是很容易就看出左上和下面的图片中存在方形,右上角的图片中存在圆形,对于我们人来说,这不是小case吗?确实,稍微懂事一点的孩子,稍微教下,孩子马上就能知道方形和圆形的区别,但是,我们现在需要教计算机去学习方形和圆形的区别,这怎么做到?如果在前几年,那么可以用机器学习的分类算法,对这些图片进行分类,但是这个算法可能十分复杂。但是如果采用深度学习来做图像识别,那么会非常的快将一个图像识别模型构造出来,当然,模型构造的好坏还是需要一定的练习,但是代码量相比传统的机器学习,可以说少的是很多了。

那么我们如何用深度学习来完成这个图像识别呢?题目中已经明确了每一张图片的特征维度是1600,并且已经帮我们处理为了一维数组,那么,首先我们可以考虑使用向前传播的神经网络来学习图片特征,利用数个全连接层(Dense,用于特征学习,层层推导特征向量,然后将特征映射到标签)和(全连接层数-1)层的Dropout层(去除模型过拟合),那么我们将这个神经网络图示就如下面的图:

u672au547du540du6587u4ef6.png

那么全连接层是如何实现将特征向量映射到标签中的吗?首先,我们有维度为1600的关于图片特征的特征向量,这个就是整个神经网络的输入部分,然后,这个向量中的每一个值通过与全连接层中的所有神经元进行连接,怎么连接?每一个特征值与神经元之间的连接需要乘上一个权重wi,与权重进行相乘操作后产生的新数据作为该神经元的输入,然后在神经元内数据经过激活函数去线性之后将处理过后的数据往下一个神经元输入,就这样通过数层的神经网络层之后,最后一层全连接层会将上一个神经元输入的数据进行计算,得出一个估计答案,再把这个答案映射到标签空间中,与对应的标签进行比对,计算误差值,这就是全连接层的工作。这里有一个小小的难点,我们刚刚说的权重wi,这是怎么确定的?实际上,这个权重恰恰是整个神经网络所要学习的东西;我们说神经网络学习学什么?学的就是这个权重,那怎么学习呢?这里就要提到反向传播算法,我这里简要说下反向传播算法怎么去让神经网络学习权重:在最后一层全连接层中不是计算出了一个估计答案和与实际答案之间的误差吗?这个误差,就是重点,反向传播算法将这个误差带回到神经网络,通过误差去不断调整权重wi,知道这个误差在可以接受的范围内,于是,神经网络就学习到了东西——对于这个场景中整个神经网络适合的权重wi。

只用全连接层来做图片识别的最典型的就是tensorflow的入门教程——MNIST的手写字体图片的识别,这个是tensorflow采用全连接层来实现的文章。

但是呢,经过人们发现,这种神经网络的学习能力还是那么稍微不如意,于是,又诞生了卷积神经网络,相比于全连接层的将一张图的所有特征向量一次性输入神经网络;卷积神经网络是将图片进行分块学习,学习每一块的特征,从这图片中的每一块提取价值信息,最后进行汇总,从而实现学习整张图片的信息,形象的来说,就是在我们看到一辆车时,我们大脑接收到这张图片,堆这张图片的每一部分进行信息提取:提取了轮子信息、提取了车门信息、提取了形状信息……这些信息综合到一起,哦,于是我们大脑得出一个结论——这是一辆车。那么卷积网络模型是怎么样的呢?看下图

未命名文件

通过卷积操作对我们的图片进行特征的提取工作,提取方式不是对整张图进行特征提取,而是堆图中的一部分进行特征提取操作,然后通过最大值池化层进行计算,将该特征和区域的最大值作为抽样后的值,向下一个卷积层传递。这里可能会问,池化层具体有什么用呢?池化层的作用是为了在全连接层中,能够减少全连接的数量,全连接的数量下来了,参数(权重wi)个数也就下来了,而参数的个数与该神经网络是否过拟合是有一定联系的。

现在讲讲我们如何解决这个题目:
题目所给的图片特征是一维的,也就是说将图片原来具有的长宽降维处理,但是,在我们构建的模型中,采用的2D卷积操作,因此我们需要将一维的进行升维操作,使的图片的长宽通过矩阵的shape体现出来,升维操作我们采用的是numpy的reshape操作

data = np.array([np.reshape(i, (40, 40)) for i in data])

同时,2D卷积操作还有一个channel,就是颜色通道,所以,我们还需要对矩阵进行一次操作,由于是灰度图片,RGB颜色通道为1,所以我们再次用numpy的reshape进行操作
将矩阵重新塑形为 (40, 40, 1)的形状,以符合卷积神经网络的输入

data = np.array([np.reshape(i, (i.shape[0], i.shape[1], 1)) for i in data])

这样,我们就完成了神经网络输入数据的数据处理部分操作

数据加载代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split

FILE_PATH_train = "/media/chuntaojun/Mylinux/code/PythonProject/DeepingLearning/sofasofa/Prac_1/data/train.csv"

FILE_PATH_test = "/media/chuntaojun/Mylinux/code/PythonProject/DeepingLearning/sofasofa/Prac_1/data/test.csv"

def load_train_test_data():
data_train = pd.read_csv(FILE_PATH_train)
data_test = pd.read_csv(FILE_PATH_test)
del data_train['id']
del data_test['id']
data_train = np.array(data_train.values)
np.random.shuffle(data_train)
labels = data_train[:, -1]
data_test = np.array(data_test.values)
data, data_test = data_modify_suitable_train(data_train, True), data_modify_suitable_train(np.array(data_test),False)
train_x, test_x, train_y, test_y = train_test_split(data, labels, test_size=0.7)
return train_x, train_y, test_x, test_y, data_test

def data_modify_suitable_train(data_set=None, type=True):
if data_set isnotNone:
data = []
if typeisTrue:
np.random.shuffle(data_set)
data = data_set[:, 0: data_set.shape[1] - 1]
else:
data = data_set
data = np.array([np.reshape(i, (40, 40)) for i in data])
data = np.array([np.reshape(i, (i.shape[0], i.shape[1], 1)) for i in data])
return data

if __name__ == '__main__':
load_train_test_data()

对于CNN模型构建部分,我们采用的keras深度学习框架,因此我们可以很快速的构建起CNN模型,通过Sequential()获取模型对象,然后通过mode.add()方法不断的为模型添加网络层:

  • Convolution2D :2维卷积层
  • MaxPooling2D :与2维卷积层操作对应的2维池化层
  • Flatten :从卷积层到全连接层,需要将矩阵维度降为一维
  • Dense :全连接层,通过全连接层进行计算,然后将结果映射到标签空间
  • activation=’sigmoid’ :因为这个图片识别其实就是二分类的操作,因此我们的激活函数采用sigmoid,这个函数了能够将结果映射到[0,1]区间,以0.5为阀值,进行二分类操作

CNN卷积神经网络模型构建代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
from keras.callbacks import TensorBoard
from keras.layers import Dense, Dropout, MaxPooling2D, Flatten, Convolution2D
from keras.models import Sequential
from keras import backend as K
import load_data as ld
import matplotlib.pyplot as plt
import csv
import numpy as np

def f1(y_true, y_pred):
def recall(y_true,y_pred):
true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
possible_positives = K.sum(K.round(K.clip(y_true, 0, 1)))
recall = true_positives / (possible_positives + K.epsilon())
return recall

def precision(y_true,y_pred):
true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
predicted_positives = K.sum(K.round(K.clip(y_pred, 0, 1)))
precision = true_positives / (predicted_positives + K.epsilon())
return precision
precision = precision(y_true, y_pred)
recall = recall(y_true, y_pred)
return 2* ((precision * recall) / (precision + recall))

def built_model():
model = Sequential()
model.add(Convolution2D(filters=8,
kernel_size=(5, 5),
input_shape=(40, 40, 1),
activation='relu'))
model.add(Convolution2D(filters=16,
kernel_size=(3, 3),
activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Convolution2D(filters=16,
kernel_size=(3, 3),
activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dense(units=128,
activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(units=1,
activation='sigmoid'))
model.compile(loss='binary_crossentropy',
optimizer='adam',
metrics=['accuracy', f1])
model.summary()
return model

def train_model(batch_size=64, epochs=20, model=None):
train_x, train_y, test_x, test_y, t = ld.load_train_test_data()
if model is None:
model = built_model()
history = model.fit(train_x, train_y,
batch_size=batch_size,
epochs=epochs,
verbose=2,
validation_split=0.1)
print"刻画损失函数在训练与验证集的变化"
plt.plot(history.history['loss'], label='train')
plt.plot(history.history['val_loss'], label='valid')
plt.legend()
plt.show()
predicted = model.predict(t,
batch_size=batch_size,
verbose=1)
predicted = np.array([round(w) for w in predicted])
score = model.evaluate(test_x, test_y,
batch_size=batch_size)
print score
print"刻画预测结果与测试集结果"
model.save('my_model.h5')
return predicted

if __name__ == '__main__':
predicted = train_model()
num = 4000
csvFile = open('test_y.csv', 'w')
write = csv.writer(csvFile)
write.writerow(['id','y'])
for i in predicted:
write.writerow([num,int(i)])
num += 1
print predicted

然后通过训练我们的神经网络,得到了一个损失函数值在训练过程的变化图(损失函数就是神经网络模型优化的目标)

loss.png

最后,将我们模型预测的结果提交到sofasofa.io竞赛平台对应的题目中,看看排名成绩

Screenshot from 2017-12-27 03-01-10

可以看出我们的模型构建的还是蛮不错的(第二个是我)

YouTube视频展示模型构建情况

[youtube https://www.youtube.com/watch?v=lYVTVgtSTnw&w=560&h=315]

哔哩哔哩站点展示模型构建情况

https://www.bilibili.com/video/av17661456/

项目代码

https://github.com/chuntaojun/Deeplearning/tree/master/Prac_1