我们一般用深度学习做图片分类的入门教材都是MNIST或者CIFAR-10,因为数据都是别人准备好的,有的甚至是一个函数就把所有数据都load进来了,所以跑起来都很简单,但是跑完了,好像自己还没掌握图片分类的完整流程,因为他们没有经历数据处理的阶段,所以谈不上走过一遍深度学习的分类实现过程。今天我想给大家分享两个比较贴近实际的分类项目,从数据分析和处理说起,以Keras为工具,彻底掌握图像分类任务。

这两个分类项目就是:交通标志分类和票据分类。交通标志分类在无人驾驶或者与交通相关项目都有应用,而票据分类任务就更加贴切生活了,同时该项目也是我现在做的一个大项目中的子任务。这两个分类任务都是很贴近实际的练手好项目,希望经过这两个实际任务可以掌握好Keras这个工具,并且搭建一个用于图像分类的通用框架,以后做其他图像分类项目也可以得心应手。

先说配置环境:

  1. Python 3.5
  2. Keras==2.0.1,TesnsorFlow后端,CPU训练

一、交通标志分类

首先是观察数据,看看我们要识别的交通标志种类有多少,以及每一类的图片有多少。打开一看,这个交通标志的数据集已经帮我们分出了训练集和数据集。

每个文件夹的名字就是其标签。

每一类的标志图片数量在十来张到数十张,是一个小数据集,总的类别是62。

那我们开始以Keras为工具搭建一个图片分类器通用框架。

搭建CNN

用深度学习做图片分类选的网络肯定是卷积神经网络,但是现在CNN的种类这么多,哪一个会在我们这个标志分类任务表现最好?在实验之前,没有人会知道。一般而言,先选一个最简单又最经典的网络跑一下看看分类效果是的策略是明智的选择,那么LeNet肯定是最符合以上的要求啦,实现简单,又相当经典。那我们先单独写一个lenet.py的文件,然后实现改进版的LeNet类。

# import the necessary packages
from keras.models import Sequential
from keras.layers.convolutional import Conv2D
from keras.layers.convolutional import MaxPooling2D
from keras.layers.core import Activation
from keras.layers.core import Flatten
from keras.layers.core import Dense
from keras import backend as K

class LeNet:
@staticmethod
def build(width, height, depth, classes):
# initialize the model
model = Sequential()
inputShape = (height, width, depth)
# if we are using “channels last”, update the input shape
if K.image_data_format() == “channels_first”: #for tensorflow
inputShape = (depth, height, width)
# first set of CONV => RELU => POOL layers
model.add(Conv2D(20, (5, 5),padding=“same”,input_shape=inputShape))
model.add(Activation(“relu”))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
#second set of CONV => RELU => POOL layers
model.add(Conv2D(50, (5, 5), padding=“same”))
model.add(Activation(“relu”))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
# first (and only) set of FC => RELU layers
model.add(Flatten())
model.add(Dense(500))
model.add(Activation(“relu”))

    <span class="hljs-comment"># softmax classifier</span>model.add(Dense(classes))model.add(Activation(<span class="hljs-string">"softmax"</span>))<span class="hljs-comment"># return the constructed network architecture</span><span class="hljs-keyword">return</span> model</code></pre>

其中conv2d表示执行卷积,maxpooling2d表示执行最大池化,Activation表示特定的激活函数类型,Flatten层用来将输入“压平”,用于卷积层到全连接层的过渡,Dense表示全连接层(500个神经元)。

参数解析器和一些参数的初始化

首先我们先定义好参数解析器。

# set the matplotlib backend so figures can be saved in the background
import matplotlib
matplotlib.use("Agg")

# import the necessary packages
from keras.preprocessing.image import ImageDataGenerator
from keras.optimizers import Adam
from sklearn.model_selection import train_test_split
from keras.preprocessing.image import img_to_array
from keras.utils import to_categorical
from imutils import paths
import matplotlib.pyplot as plt
import numpy as np
import argparse
import random
import cv2
import os
import sys
sys.path.append(’…’)
from net.lenet import LeNet

def args_parse():
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-dtest", “–dataset_test”, required=True,
help=“path to input dataset_test”)
ap.add_argument("-dtrain", “–dataset_train”, required=True,
help=“path to input dataset_train”)
ap.add_argument("-m", “–model”, required=True,
help=“path to output model”)
ap.add_argument("-p", “–plot”, type=str, default=“plot.png”,
help=“path to output accuracy/loss plot”)
args = vars(ap.parse_args())
return args

我们还需要为训练设置一些参数,比如训练的epoches,batch_szie等。这些参数不是随便设的,比如batch_size的数值取决于你电脑内存的大小,内存越大,batch_size就可以设为大一点。又比如norm_size(图片归一化尺寸)是根据你得到的数据集,经过分析后得出的,因为我们这个数据集大多数图片的尺度都在这个范围内,所以我觉得32这个尺寸应该比较合适,但是不是最合适呢?那还是要通过实验才知道的,也许64的效果更好呢?

# initialize the number of epochs to train for, initial learning rate,
# and batch size
EPOCHS = 35
INIT_LR = 1e-3
BS = 32
CLASS_NUM = 62
norm_size = 32

载入数据

接下来我们需要读入图片和对应标签信息。

def load_data(path):print("[INFO] loading images...")data = []labels = []# grab the image paths and randomly shuffle themimagePaths = sorted(list(paths.list_images(path)))random.seed(42)random.shuffle(imagePaths)# loop over the input imagesfor imagePath in imagePaths:# load the image, pre-process it, and store it in the data listimage = cv2.imread(imagePath)image = cv2.resize(image, (norm_size, norm_size))image = img_to_array(image)data.append(image)
    <span class="hljs-comment"># extract the class label from the image path and update the</span><span class="hljs-comment"># labels list</span>label = int(imagePath.split(os.path.sep)[<span class="hljs-number">-2</span>])       labels.append(label)<span class="hljs-comment"># scale the raw pixel intensities to the range [0, 1]</span>
data = np.array(data, dtype=<span class="hljs-string">"float"</span>) / <span class="hljs-number">255.0</span>
labels = np.array(labels)<span class="hljs-comment"># convert the labels from integers to vectors</span>
labels = to_categorical(labels, num_classes=CLASS_NUM)
<span class="hljs-keyword">return</span> data,labels</code></pre>

函数返回的是图片和其对应的标签。

训练

def train(aug,trainX,trainY,testX,testY,args):# initialize the modelprint("[INFO] compiling model...")model = LeNet.build(width=norm_size, height=norm_size, depth=3, classes=CLASS_NUM)opt = Adam(lr=INIT_LR, decay=INIT_LR / EPOCHS)model.compile(loss="categorical_crossentropy", optimizer=opt,metrics=["accuracy"])
<span class="hljs-comment"># train the network</span>
print(<span class="hljs-string">"[INFO] training network..."</span>)
H = model.fit_generator(aug.flow(trainX, trainY, batch_size=BS),validation_data=(testX, testY), steps_per_epoch=len(trainX) // BS,epochs=EPOCHS, verbose=<span class="hljs-number">1</span>)<span class="hljs-comment"># save the model to disk</span>
print(<span class="hljs-string">"[INFO] serializing network..."</span>)
model.save(args[<span class="hljs-string">"model"</span>])<span class="hljs-comment"># plot the training loss and accuracy</span>
plt.style.use(<span class="hljs-string">"ggplot"</span>)
plt.figure()
N = EPOCHS
plt.plot(np.arange(<span class="hljs-number">0</span>, N), H.history[<span class="hljs-string">"loss"</span>], <span class="hljs-keyword">label</span><span class="bash">=<span class="hljs-string">"train_loss"</span>)

plt.plot(np.arange(0, N), H.history[“val_loss”], label=“val_loss”)
plt.plot(np.arange(0, N), H.history[“acc”], label=“train_acc”)
plt.plot(np.arange(0, N), H.history[“val_acc”], label=“val_acc”)
plt.title(“Training Loss and Accuracy on traffic-sign classifier”)
plt.xlabel(“Epoch #”)
plt.ylabel(“Loss/Accuracy”)
plt.legend(loc=“lower left”)
plt.savefig(args[“plot”])

在这里我们使用了Adam优化器,由于这个任务是一个多分类问题,可以使用类别交叉熵(categorical_crossentropy)。但如果执行的分类任务仅有两类,那损失函数应更换为二进制交叉熵损失函数(binary cross-entropy)

主函数


#python train.py --dataset_train ../../traffic-sign/train --dataset_test ../../traffic-sign/test --model traffic_sign.model
if __name__=='__main__':args = args_parse()train_file_path = args["dataset_train"]test_file_path = args["dataset_test"]trainX,trainY = load_data(train_file_path)testX,testY = load_data(test_file_path)# construct the image generator for data augmentationaug = ImageDataGenerator(rotation_range=30, width_shift_range=0.1,height_shift_range=0.1, shear_range=0.2, zoom_range=0.2,horizontal_flip=True, fill_mode="nearest")train(aug,trainX,trainY,testX,testY,args)

在正式训练之前我们还使用了数据增广技术(ImageDataGenerator)来对我们的小数据集进行数据增强(对数据集图像进行随机旋转、移动、翻转、剪切等),以加强模型的泛化能力。

训练代码已经写好了,接下来开始训练(图片归一化尺寸为32,batch_size为32,epoches为35)。

python train.py --dataset_train ../../traffic-sign/train --dataset_test ../../traffic-sign/test --model traffic_sign.model

训练过程:

Loss和Accuracy:

从训练效果看来,准确率在94%左右,效果不错了。

预测单张图片

现在我们已经得到了我们训练好的模型traffic_sign.model,然后我们编写一个专门用于预测的脚本predict.py。

# import the necessary packages
from keras.preprocessing.image import img_to_array
from keras.models import load_model
import numpy as np
import argparse
import imutils
import cv2

norm_size = 32

def args_parse():
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-m", “–model”, required=True,
help=“path to trained model model”)
ap.add_argument("-i", “–image”, required=True,
help=“path to input image”)
ap.add_argument("-s", “–show”, action=“store_true”,
help=“show predict image”,default=False)
args = vars(ap.parse_args())
return args

def predict(args):
# load the trained convolutional neural network
print("[INFO] loading network…")
model = load_model(args[“model”])

<span class="hljs-comment">#load the image</span>
image = cv2.imread(args[<span class="hljs-string">"image"</span>])
orig = image.copy()<span class="hljs-comment"># pre-process the image for classification</span>
image = cv2.resize(image, (norm_size, norm_size))
image = image.astype(<span class="hljs-string">"float"</span>) / <span class="hljs-number">255.0</span>
image = img_to_array(image)
image = np.expand_dims(image, axis=<span class="hljs-number">0</span>)<span class="hljs-comment"># classify the input image</span>
result = model.predict(image)[<span class="hljs-number">0</span>]
<span class="hljs-comment">#print (result.shape)</span>
proba = np.max(result)
label = str(np.where(result==proba)[<span class="hljs-number">0</span>])
label = <span class="hljs-string">"{}: {:.2f}%"</span>.format(label, proba * <span class="hljs-number">100</span>)
print(label)<span class="hljs-keyword">if</span> args[<span class="hljs-string">'show'</span>]:   <span class="hljs-comment"># draw the label on the image</span>output = imutils.resize(orig, width=<span class="hljs-number">400</span>)cv2.putText(output, label, (<span class="hljs-number">10</span>, <span class="hljs-number">25</span>),cv2.FONT_HERSHEY_SIMPLEX,<span class="hljs-number">0.7</span>, (<span class="hljs-number">0</span>, <span class="hljs-number">255</span>, <span class="hljs-number">0</span>), <span class="hljs-number">2</span>)       <span class="hljs-comment"># show the output image</span>cv2.imshow(<span class="hljs-string">"Output"</span>, output)cv2.waitKey(<span class="hljs-number">0</span>)

#python predict.py --model traffic_sign.model -i …/2.png -s
if name == ‘main’:
args = args_parse()
predict(args)

预测脚本中的代码编写思路是:参数解析器-》载入训练好的模型-》读入图片信息-》预测-》展示预测效果。值得注意的是,参数-s是用于可视化结果的,加上他的话我们就可以看出我们输入的图片以及模型预测的分类结果,很直观。如果只需要得到分类结果,不加-s就可以了。

单张图片的预测:

python predict.py --model traffic_sign.model -i ../2.png -s

至此,交通分类任务完成。

这里分享一下这个项目的数据集来源:
你可以点击这里下载数据集。在下载页面上面有很多的数据集,但是你只需要下载 BelgiumTS for Classification (cropped images) 目录下面的两个文件:

  • BelgiumTSC_Training (171.3MBytes)
  • BelgiumTSC_Testing (76.5MBytes)

值得注意的是,原始数据集的图片格式是ppm,这是一种很老的图片保存格式,很多的工具都已经不支持它了。这也就意味着,我们不能很方便的查看这些文件夹里面的图片。

为了解决这个问题,我用opencv重新将这些图片转换为png格式,这样子我们就能很直观地看到数据图片了。

转换脚本在这里

同时我也把转换好的数据集传到百度云了,不想自己亲自转换的童鞋可以自行获取。

二、票据分类

先分析任务和观察数据。我们这次的分类任务是票据分类,现在我们手头上的票据种类一共有14种,我们的任务就是训练一个模型将他们一一分类。先看看票据的图像吧。

票据种类一共14种,其图片名字就是其label。

票据是以下面所示的文件夹排布存储的。

然后我们再看一下每类图片数据的情况,看一下可利用的数据有多少。

有的票据数据比较少,也就十来张

有的票据比较多,有上百张

这样的数据分布直接拿去去训练的话,效果可能不会太好(这就是不平衡问题),但是这是后期模型调优时才需要考虑的问题,现在先放一边。那我们继续使用上面的图片分类框架完成本次的票据分类任务。

这次的数据集的存储方式与交通标志分类任务的数据存储不太一样,这个数据集没有把数据分成train和test两个文件夹,所以我们在代码中读取数据时写的函数应作出相应修改:我们先读取所有图片,再借助sklearn的“train_test_split”函数将数据集以一定比例分为训练集和测试集。

我写了个load_data2()函数来适应这种数据存储。

def load_data2(path):print("[INFO] loading images...")data = []labels = []# grab the image paths and randomly shuffle themimagePaths = sorted(list(paths.list_images(path)))random.seed(42)random.shuffle(imagePaths)# loop over the input imagesfor imagePath in imagePaths:# load the image, pre-process it, and store it in the data listimage = cv2.imread(imagePath)image = cv2.resize(image, (norm_size, norm_size))image = img_to_array(image)data.append(image)
    <span class="hljs-comment"># extract the class label from the image path and update the</span><span class="hljs-comment"># labels list</span>label = int(imagePath.split(os.path.sep)[<span class="hljs-number">-2</span>])       labels.append(label)  <span class="hljs-comment"># scale the raw pixel intensities to the range [0, 1]</span>
data = np.array(data, dtype=<span class="hljs-string">"float"</span>) / <span class="hljs-number">255.0</span>
labels = np.array(labels)<span class="hljs-comment"># partition the data into training and testing splits using 75% of</span>
<span class="hljs-comment"># the data for training and the remaining 25% for testing</span>
(trainX, testX, trainY, testY) = train_test_split(data,labels, test_size=<span class="hljs-number">0.25</span>, random_state=<span class="hljs-number">42</span>)<span class="hljs-comment"># convert the labels from integers to vectors</span>
trainY = to_categorical(trainY, num_classes=CLASS_NUM)
testY = to_categorical(testY, num_classes=CLASS_NUM)
<span class="hljs-keyword">return</span> trainX,trainY,testX,testY</code></pre>

我们使用了sklearn中的神器train_test_split做了数据集的切分,非常方便。可以看出,load_data2()的返回值就是训练集图片和标注+测试集图片和标注。

在主函数也只需做些许修改就可以完成本次票据分类任务。

if __name__=='__main__':args = args_parse()file_path = args["dataset"]trainX,trainY,testX,testY = load_data2(file_path)# construct the image generator for data augmentationaug = ImageDataGenerator(rotation_range=30, width_shift_range=0.1,height_shift_range=0.1, shear_range=0.2, zoom_range=0.2,horizontal_flip=True, fill_mode="nearest")train(aug,trainX,trainY,testX,testY,args)

然后设定一些参数,比如图片归一化尺寸为64*64,训练35个epoches。设定完参数后我们开始训练。

python train.py --dataset ../../invoice_all/train  --model invoice.model

训练的过程不算久,大概十来分钟。训练过程如下:

绘制出Loss和Accuracy曲线,可以看出,我们训练后的模型的准确率可以达到97%。直接使用一个LeNet网络就可以跑出这个准确率还是让人很开心的。

最后再用训练好的模型预测单张票据,看看效果:

预测正确,deep learning 票据分类任务完成!

三、总结

我们使用了Keras搭建了一个基于LeNet的图片分类器的通用框架,并用它成功完成两个实际的分类任务。最后再说说我们现有的模型的一些改进的地方吧。第一,图片归一化的尺寸是否合适?比如票据分类任务中,图片归一化为64,可能这个尺寸有点小,如果把尺寸改为128或256,效果可能会更好;第二,可以考虑更深的网络,比如VGG,GoogLeNet等;第三,数据增强部分还可以再做一做。

完整代码和测试图片可以在我的github上获取。

参考资料:

https://www.pyimagesearch.com/2017/12/11/image-classification-with-keras-and-deep-learning/

http://www.jianshu.com/p/3c7f329b29ee

两个实际任务掌握图像分类【Keras】(转)相关推荐

  1. 【Keras】从两个实际任务掌握图像分类

    我们一般用深度学习做图片分类的入门教材都是MNIST或者CIFAR-10,因为数据都是别人准备好的,有的甚至是一个函数就把所有数据都load进来了,所以跑起来都很简单,但是跑完了,好像自己还没掌握图片 ...

  2. CNN图像分类Keras代码转换pytorch思路与实现

    tags: Python DL 写在前面 前几天改了一份代码, 是关于深度学习中卷积神经网络的Python代码, 用于解决分类问题. 代码是用TensorFlow的Keras接口写的, 需求是转换成p ...

  3. TensorFlow图像分类:如何构建分类器

    导言 图像分类对于我们来说是一件非常容易的事情,但是对于一台机器来说,在人工智能和深度学习广泛使用之前,这是一项艰巨的任务.自动驾驶汽车能够实时检测物体并采取相应必要的行动,并且由于TensorFlo ...

  4. 【Keras】学习笔记(一)

    传送门:Keras 中文文档 文章目录 一.准备工作 1.概述 2.安装 3.GPU设置 (1)单GPU运行 (2)多GPU运行 二.顺序模型 简单示例 1.整体流程 (1)顺序模型的构建--Sequ ...

  5. 利用keras搭建CNN完成图片分类

    文章目录 一.简介 二.流程 1.数据处理 2.神经网络搭建 3.训练 4.预测 三.参考 一.简介 本文旨在通过一些简单的案例,学习如何通过keras搭建CNN.从数据读取,数据处理,神经网络搭建, ...

  6. Keras vs PyTorch:谁是第一深度学习框架?

    「第一个深度学习框架该怎么选」对于初学者而言一直是个头疼的问题.本文中,来自 deepsense.ai 的研究员给出了他们在高级框架上的答案.在 Keras 与 PyTorch 的对比中,作者还给出了 ...

  7. 【图像分类】分类专栏正式上线啦!初入CV、AI你需要一份指南针!

    大家好,今天我将在有三AI开设新专栏<图像分类>.在这个专栏中,我将全方位介绍图像分类相关知识,并结合不同任务需求进行实战,和大家一同走近这看似简单却包罗万象的基础性技术,同时指导计算机视 ...

  8. 深度学习(莫烦 神经网络 lecture 3) Keras

    神经网络 & Keras 目录 神经网络 & Keras 目录 1.Keras简介 1.1 科普: 人工神经网络 VS 生物神经网络 1.2 什么是神经网络 (Neural Netwo ...

  9. keras中文文档_【DL项目实战02】图像识别分类——Keras框架+卷积神经网络CNN(使用VGGNet)

    版权声明:小博主水平有限,希望大家多多指导. 目录: [使用传统DNN] BG大龍:[DL项目实战02]图像分类--Keras框架+使用传统神经网络DNN​zhuanlan.zhihu.com [使用 ...

最新文章

  1. NOIP 2017 提高组 K: 奶酪 (SPFA || 并查集)
  2. 前端inline元素间隙问题解决办法
  3. Python | 深入浅出字符串
  4. 395. Longest Substring with At Least K Repeating Characters
  5. hotspot虚拟机的调试
  6. orgmode导出html,含有python代码块的ORG-MODE导出为HTML时出错
  7. linux c 文件映射,linuxc-共享内存
  8. 最简单的嵌套循环,一句话即可。适合repeater,dropdownlist,gridview等等
  9. python爬虫爬取网易云音乐歌曲_如何用Python网络爬虫爬取网易云音乐歌曲
  10. python分段函数图像画法_特殊分段函数的图像画法
  11. Shader编程教程_Shader新手入门视频教程_Shader编程从入门到精通
  12. php中字符串分割函数是,php分割字符串的函数是什么
  13. 【西窗】2019杭州交通限行规定(最新地图详情)
  14. [系统安全] 一.什么是逆向分析、逆向分析基础及经典扫雷游戏逆向
  15. win10下装黑苹果双系统_Ubuntu 18.04.3+Windows10双系统安装全教程
  16. 【题解】洛谷 P1957 口算练习题
  17. background简写方式
  18. PDF文件提取单独页面
  19. GWAS数据分析流程—SNP、Indel注释
  20. 一文搞懂 STL 中 deque 与 hashtab 的底层实现

热门文章

  1. 设计模式 总揽 通过这篇随笔可以访问所需要了解的设计模式
  2. 3使用技巧_办公小技巧:3+2 灵活使用WPS集成环境
  3. Linux之web服务
  4. android开发:Android 中自定义属性(attr.xml,TypedArray)的使用
  5. centos通过yum的方式快速安装jdk1.8
  6. 介绍如何隐藏JDataGridBean的SplashScreen?
  7. 喜马拉雅 xm文件转m4a_录音转文字:学习/办公/生活都极为实用!
  8. 2022-03-13 转载 Dockerfile 高阶使用指南及镜像优化
  9. 关于GDAL180中文路径不能打开的问题分析与解决
  10. c# mysql 连接类_c#中连接数据库的类怎么写呀?