深度学习涂鸦! 我们的草图识别之旅

Doodling with Deep Learning!

Our Journey with Sketch recognition

在这篇博客文章中,我们描述了我们的流程理解,拟合模型,并找到了Google Quick,Draw的有趣应用程序!数据集。 与我们一起走过这段旅程,看看我们如何应对成功分类“可以说是世界上最可爱的研究数据集”的挑战!

该项目由Akhilesh Reddy,Vincent Kuo,Kirti Pande,Tiffany Sung和Helena Shi建造。 要查看使用的完整代码,请找到我们的github(https://github.com/QuickDraw-sketchRecognition/Sketch_Recognition/):

I. Background

2016年,谷歌发布了一款名为“快速,画图!”的在线游戏 - 一项人工智能实验,教育公众了解神经网络,并建立了超过10亿张图纸的庞大数据集。 游戏本身很简单。 它提示玩家在某个类别中涂鸦图像,当玩家正在绘画时,神经网络猜测图像在Pictionary的人机对话游戏中描绘的内容。 您可以在这里找到有关游戏的更多信息或自己玩游戏!

自从数据集中发布了5000万张图纸以来,ML社区已经开始探索数据应用以改进手写识别,训练Sketch RNN模型以教授机器绘制等等。 值得注意的是,它在OCR(光学字符识别),ASR(自动语音识别)和NLP(自然语言处理)方面具有强大的潜力,并且揭示了世界各地人们如何不同但又相同的可见见解。在这里插入图片描述

II. The Data

我们的总数据量为73GB,包含340种标签类别的5000万张图纸。 每张图纸都带有特定的变量:

  • “word” - 该图纸的类别标签
  • “国家代码” - 抽屉的原产国
  • “timestamp” - 绘图的时间戳
  • “已识别” - 表示应用程序的成功预测
  • “绘图” - 特定于涂鸦图像的笔画基础数据; 每个绘图由矩阵形式的多个笔划组成

在这里插入图片描述
如此处所示,笔划中的每个点对应于x坐标,y坐标和时间点

III. Approach

我们首先了解构成草图和预处理数据的数组结构。 然后,我们深入研究拟合一些简单的分类器和一个基本的卷积神经网络,或CNN。 从那里,我们处理CNN架构,如ResNet和MobileNet。 最后,我们通过参加Kaggle比赛(https://www.kaggle.com/c/quickdraw-doodle-recognition)与全世界分享了我们的成果。

由于数据量大且需要更高容量的GPU,我们在Google云平台上实施了CNN结构,该平台有300美元的免费试用。 要了解我们是如何做到这一点,请点击此处的链接(https://medium.com/@alamhanz/jupyter-notebook-on-gcp-for-pythoners-18a8e7a73a56)和此处(https://medium.com/google-云/使用-A-GPU-tensorflow-上谷歌云平台-1a2458f42b0)。

IV. Data Preprocessing

数据以单独的CSV文件格式存在,用于每个类标签的图纸。 因此,我们首先对CSV进行混洗,使用来自所有类的数据创建100个新文件,以确保模型接收随机的图像样本作为输入并消除偏差。

##As every image has a key_id, the shuffling is done by dividing the key_id by 100 and taking the remainder as the
#number of new files the image will be shuffled into. Since the files are large, they are also compressed as .gz (gzip).

for y, cat in tqdm(enumerate(categories)):
  df = s.read_training_csv(cat, nrows=None)
  df['y'] = y
  df['cv'] = (df.key_id // 10 ** 7) % NCSVS
  for k in range(NCSVS):
      filename = 'train_k{}.csv'.format(k)
      chunk = df[df.cv == k]
      chunk = chunk.drop(['key_id'], axis=1)
      if y == 0:
          chunk.to_csv(filename, index=False)
      else:
          chunk.to_csv(filename, mode='a', header=False, index=False)
#write to .gz
for k in tqdm(range(NCSVS)):
  filename = 'train_k{}.csv'.format(k)
  if os.path.exists(filename):
      df = pd.read_csv(filename)
      df['rnd'] = np.random.rand(len(df))
      df = df.sort_values(by='rnd').drop('rnd', axis=1)
      df.to_csv(filename + '.gz', compression='gzip', index=False)
      os.remove(filename)
print(df.shape)
end = dt.datetime.now()
print('Latest run {}.\nTotal time {}s'.format(end, (end - start).seconds))

大多数人以类似的方式画涂鸦。 例如,如果我要求你绘制一个太阳,你将从一个圆圈开始,然后以顺时针顺序从中心辐射的虚线。 为了捕获这些信息,我们使用灰度/颜色编码处理在构建CNN时利用RGB通道,因此模型可以识别每个笔划之间的差异。 我们为涂鸦的每个时间顺序笔划指定了一种颜色,从而允许模型获得有关各个笔划的信息,而不仅仅是整个图像。
在这里插入图片描述
Violin (left) in black & white; Mermaid (right) color-encoded
我们还通过随机翻转,旋转或阻挡部件来增强图像,从而将噪声引入图像并增加模型处理噪声的能力。 在游戏过程中,一些玩家没有完成他们的涂鸦或绘制不同的角度。 在这些情况下,增强可以为模型提供信息。

colors = [(255, 0, 0) , (255, 255, 0),  (128, 255, 0),  (0, 255, 0), (0, 255, 128), (0, 255, 255),
        (0, 128, 255), (0, 0, 255), (128, 0, 255), (255, 0, 255)]
def draw_cv2(raw_strokes, size=256, lw=6):
  img = np.zeros((BASE_SIZE, BASE_SIZE,3), np.uint8)
  for t, stroke in enumerate(raw_strokes):
      for i in range(len(stroke[0]) - 1):
          color = colors[min(t, len(colors)-1)]
          _ = cv2.line(img, (stroke[0][i], stroke[1][i]), (stroke[0][i + 1], stroke[1][i + 1]),/
                       color, lw, lineType=cv2.LINE_AA)
  if np.random.rand()>0.5:
      img = np.fliplr(img)
  if np.random.rand()>0.75:
      if np.random.rand()>0.50:
          img = img[ 4:, 4: ,:]
      else:
          img = img[ :-4, :-4 ,:]
  if np.random.rand()>0.50:
      img2 = cv2.resize(img, (200, 200))
      img = np.zeros((BASE_SIZE, BASE_SIZE,3), np.uint8)
      img[18:218,18:218, :] = img2

  if size != BASE_SIZE:
      return cv2.resize(img, (size, size))
  else:
      return img

灰度/颜色编码和图像增强都使用来自keras的OpenCV和ImageGenerator,它们从csv文件加载批量原始数据并将它们转换为图像。

V. Building Models

在完成所有数据收集和预处理步骤之后,是时候开始使用项目中最有趣的部分 - 模型构建!

在我们进入CNN之前,我们尝试了一些基本的分类器来比较不同的机器学习算法并熟悉数据。 我们从Google云端存储中提取了数据的numpy文件。 此数据已经过预处理,并以numpy .npy格式呈现为28x28灰度位图。 由于整个数据集包含超过345个类别,我们最终选择了一个仅包含以下5个类别的小子集:飞机,闹钟,救护车,天使和动物迁移。

Random Forest

我们首先开始使用随机森林分类器。 我们使用GridSearchCV来交叉验证模型并优化参数。 我们发现在100棵树之后精度趋于稳定,因此我们使用n_estimators = 100作为我们的最终模型,返回精度为0.8291。

%%time
parameters = {'n_estimators': [10,20,40,60,80,100,120,140,160]}

clf_rf = RandomForestClassifier(n_jobs=-1, random_state=0)
rf = GridSearchCV(clf_rf, parameters, n_jobs=-1)
rf.fit(X_train, y_train)

results = pd.DataFrame(rf.cv_results_)
results.sort_values('mean_test_score', ascending = False)
results.plot('param_n_estimators','mean_test_score')

KNN

其次,我们尝试了k-Nearest Neighbor(kNN)分类器,可以说是最简单,最容易理解的模型。 它的算法通过找到k个最接近的例子中最常见的类来对未知数据点进行分类。 我们交叉验证了n_neighbors并发现给出的最佳模型是k = 5,它返回0.8752的准确度。

%%time

tuning_parameters = {'n_neighbors': [1,3,5,7,9,11]}

clf_knn = KNeighborsClassifier(n_jobs=-1)
knn = GridSearchCV(clf_knn, tuning_parameters, n_jobs=-1)
knn.fit(X_train, y_train)

results_knn = pd.DataFrame(knn.cv_results_)
results_knn.sort_values('mean_test_score', ascending = False)

Multi-Layer Perceptron (MLP)

最后,我们尝试了scikit-learn的多层感知器(MLP)。 我们通过不同的隐藏层大小和学习率进行交叉验证,确定隐藏层大小(784,)和学习率alpha = 0.001,精确度为0.8654。

%%time
tuning_parameaters = {'hidden_layer_sizes' : [(50,), (100,), (784,), (50,50), (100,100), (784,784), (50,50,50), (100,100,100)],
                    'alpha' : list(10.0 ** -np.arange(1, 7))}

clf_mlp = MLPClassifier(random_state=0)
mlp = GridSearchCV(clf_mlp, param_grid=tuning_parameaters, n_jobs=-1)
mlp.fit(X_train, y_train)
results=pd.DataFrame(mlp.cv_results_)
results.sort_values('mean_test_score', ascending = False)

Convolutional Neural Network

然后,我们转向简单的CNN模型,为模型性能设置较低的阈值,并了解模型的细微差别和执行时间。 在此模型中,我们使用数据中的绘图信息使用OpenCV创建所需大小的图像。 在这里,我们尝试了一堆不同的参数,如下所示:在这里插入图片描
这是我们的参数设置背后的一些直觉。 首先,较大的批量大小将有助于解决由于错误标记的训练数据引起的噪声。 尺寸参数表示图像尺寸/分辨率,对精度有显着影响。 例如,32x32和128x128的比较向我们显示,32x32的大小太像素化,无法实现精确的模型。

在这里插入图片描
第一个模型使用两个卷积层,每个卷的深度为128.然而,图像尺寸的这种增加需要更大的感受野场转换层或额外的转换层。 因此,当训练具有更大的图像尺寸时,我们还包括一个层。 下面是我们创建的自定义CNN模型,它在构建模型时使用卷积层,密集层,丢失和大小作为参数。

def custom_single_cnn(size, conv_layers=(8, 16, 32, 64), dense_layers=(512, 256), conv_dropout=0.2,
                     dense_dropout=0.2):
    model = Sequential()
    model.add(
       Conv2D(conv_layers[0], kernel_size=(3, 3), padding='same', activation='relu', input_shape=(size, size, 1)))
    model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
    if conv_dropout:
       model.add(Dropout(conv_dropout))
for conv_layer_size in conv_layers[1:]:
       model.add(Conv2D(conv_layer_size, kernel_size=(3, 3), activation='relu'))
       model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
       if conv_dropout:
           model.add(Dropout(conv_dropout))
model.add(Flatten())
for dense_layer_size in dense_layers:
       model.add(Dense(dense_layer_size, activation='relu'))
       model.add(Activation('relu'))
       if dense_dropout:
           model.add(Dropout(dense_dropout))
model.add(Dense(NCATS, activation='softmax'))
    return model

<Model Declaration>
model = custom_single_cnn(size=size,
                         conv_layers=[128, 128, 256],
                         dense_layers=[2048],
                         conv_dropout=False,
                         dense_dropout=0.10 )
model.compile(optimizer=Adam(lr=0.002), loss='categorical_crossentropy',
             metrics=[categorical_crossentropy, categorical_accuracy, top_3_accuracy])
print(model.summary())

由于我们仍在决定进一步进行分析的最佳模型,因此我们在初始步骤使用了有限的数据来减少执行时间。

Selecting an Optimizer

继续培训之前的关键步骤是决定使用哪个优化器。 在参考了文献并从各个Kaggle论坛的专家那里获得建议之后,我们决定将Adam优化器和SGD优化器的性能与我们的数据进行比较。 在多次迭代之后,我们选择了Adam优化器,因为我们发现它显示出稍微好一些的结果并且比SGD收敛得更快。

在每班25000张图像上运行模型约3小时后,我们在Kaggle公共排行榜上获得了0.76的MAP @ 3得分 - 每个班级仅有25000张图像,这不是一个糟糕的结果! 从透视角度来看,数据集中的平均类包含150000个图像。 然而,当我们增加模型复杂性时,准确性略有下降,这导致我们进入下一步:ResNet。

SE-ResNet-34, SE-ResNet-50:

当增加模型的深度时,可能会遇到消失梯度和退化等问题; 相对而言,更深层次的模型比简单模型表现更差。 残余网络,或ResNet是一种神经网络架构,通过使用深度残差学习以最简单的方式解决梯度和退化问题消失的问题。

简单来说,在反向传播期间,当信号向后发送时,渐变总是必须通过f(x)(其中f(x)是我们的卷积,矩阵乘法或批量归一化等),这可能导致麻烦到期 涉及的非线性。

最后的“+ x”是快捷方式。 它允许渐变直接向后传递。 通过堆叠这些层,理论上梯度可以“跳过”所有中间层并到达底部而不会减少。
在这里插入图片描
您可以参考原始论文(https://arxiv.org/pdf/1512.03385.pdf)以进一步了解34层普通网络和34层剩余网络之间的比较。

在此过程的这一步中,我们将SE-ResNet-34和50作为简单CNN的一步进行了培训。 术语SE指的是挤压和激励网; 有了它,一个额外的块为不同的通道提供权重。 事实证明,SE模块通过使重量仅增加不到总参数的10%来提供额外的准确性。 有关挤压和激励网络的更多信息,请访问此处(https://arxiv.org/pdf/1709.01507.pdf)。

在训练SE-ResNet-50时,我们尝试了50到60个时期的不同参数。
在这里插入图片
最后,在所有组合中,批量大小为512,图像大小为128x128,得分最佳,将其提升至0.9093。 值得注意的是,更改批量大小和图像大小是基于我们使用的GPU,这些是我们数据的特斯拉k80上的最大可能参数。

MobileNet

在与SE-ResNet进行多次迭代并且竞争截止日期即将来临之后,我们决定探索MobileNet,它提供了相当的准确性,但执行速度更快。

Google推出了MobileNet,可以随时随地为客户提供对象,徽标和文本识别等最新技术,无论互联网连接如何。 MobileNets基于流线型架构,使用深度可分离卷积来构建轻量级深度神经网络。

为了更好地理解这一点,标准卷积在所有输入通道中应用滤波器,并在一个步骤中组合这些值。 相比之下,深度可分离卷积执行两个不同的步骤:

  • 深度卷积将单个滤波器应用于每个输入通道
  • 点状卷积,一个简单的1×1卷积,然后用于创建深度层输出的线性组合

在这里插入图片描

这种因式分解大大减少了计算和模型大小,因为它打破了输出通道数量和内核大小之间的相互作用。 根据MobileNet上的原始论文,MobileNet显示计算成本降低了至少9倍。 有关详细信息,请参阅MobileNet上的原始论文(https://arxiv.org/pdf/1704.04861.pdf)。

为简单起见,我们在keras中使用了MobileNet的简单2行标准模块。


model = MobileNet(input_shape=(size, size, 1), alpha=1., weights=None, classes=NCATS)
model.compile(optimizer=Adam(lr=0.002), loss='categorical_crossentropy',
             metrics=[categorical_crossentropy, categorical_accuracy, top_3_accuracy])
print(model.summary())

在训练模式之前,我们确保使用所有50 MM图像训练模型,并通过每个笔划的灰度梯度包括笔划信息的顺序。 经过多次迭代后,我们将以下参数作为最佳参数。在这里插入图片描述

VI. Results

我们开始使用Google Cloud平台提供的Tesla P100 GPU上的50MM图纸和340个课程对模型进行培训。 经过20小时的训练并花费50美元的GCP积分后,我们最终在Kaggle排行榜上使用MobileNet获得了0.9211的分数,这帮助我们在参加比赛的1316支球队中获得了268分!

在这里插入图片描述
在这里,您可以看到我们模型的参数和结果的总体摘要:在这里插入图片描述
在这里插入图片描述
以下是我们预测的一些例子!在这里插入图片描述

A Fun Bonus!

如果你坚持下去并阅读到这一点,为你加分! 作为一种有趣的享受,我们还决定加入一些东西并创建一个可以通过网络摄像头捕捉涂鸦的应用程序! 该应用程序使用我们的模型实时进行预测。 利用OpenCV功能从网络摄像头捕获视频并提取绘制的图像。

我们使用第一个简单的CNN模型作为我们的后端,因为它是我们运行的最轻的模型。 该模型在15个类别(下面列出)上进行了训练,并且在检测绘制的涂鸦方面达到了87%的准确度。

在这里插入图片描述

VII. Conclusion

作为结论,我们将总结在这个蜿蜒的旅程中采取的步骤:

  • 1了解我们的大脑以了解涂鸦数据的独特结构,并找出如何与Google Cloud Platform建立联系以运行模型

  • 2通过改组csvs和使用笔划信息扩充图像等执行数据清理和预处理

  • 3在本地系统上的三个简单分类器上减少五个类的数据集

  • 4从简单的CNN到ResNets和MobileNets实施深度学习模型

  • 5提交给比赛的结果,给了我们一个大的拍拍,并通过创建一个应用程序庆祝项目的结束!
    以下是培训深度学习网络时的一些经验教训:

  • 获取算法的一个基本实现,并首先在较小的数据集上对其进行测试,以节省执行时间

  • 探索所有可用的GPU选项,并根据任务所需的计算强度在它们之间进行切换

  • 随着时代的增加,降低学习率。我们有一个名为ReduceLRonplateau的内置函数来执行此操作。这会影响高原期间模型的学习

总的来说,这个项目非常有益! 作为研究生,我们抓住机会(希望!)给我们的教授留下深刻印象并参加着名的比赛。 我们首次通过处理图像分类来挑战自我,并取得了令人满意的结果。

猜你喜欢

转载自blog.csdn.net/weixin_41697507/article/details/89288714