图像分类之:经典机器学习 Battle 深度学习

本文写于2018.08.31, 生日前夕。

前段时间,有个朋友和我提到,自己最近正打算用机器来判别图片中的场景是古镇还是园林,所以我这一期特地写了一篇文章,来描述图像的分类算法。由于最近工作略忙,所以文章断断续续写了好久,终于在自己生日前夕完成,希望可以有所帮助,这样我就可以安心回家吃蛋糕了。

 

用机器来做图片分类简单来讲就是给机器输入一张图片,机器会输出这幅图片里面的内容。机器学习中提供了很多对数据分类的算法,像KNN(最近邻)、Adaboost、Naive Bayes(朴素贝叶斯)、SVM(支持向量机)、ANN(人工神经网络)等以及最近几年兴起的CNN(卷积神经网络)

 

在这篇文章的实作部分,我会挑出KNN、SVM、ANN来实现,使用的是使用经典的scikit-learn(sklearn)库,CNN则使用Keras来实现。


编程的环境需求:

  • 系统:windows / linux

  • 解释器:python 3.6

  • 依赖库:numpy、opencv-python 3、tensorflow、keras、scikit-learn

     


数据集选择:

由于这几天无法穿越到苏州去采集大量园林和古镇的图片,而且本文还会去实验多分类的情况,但是既然同是图片的分类问题,我决定去网上搜集一些和风景相关的数据集。

突然有一天,在我逛GitHub的时候,它就这样出现了,

在我的世界里

带给我惊喜

情不自已

 

为此,我给作者写了一封信:

 

作者在我晚上吃饭的时候给了回复:

这。。。。。。。

 

 

 

看来这条路没走顺,不如去看看一些知名的数据集里有没有自己需要的东西吧,不过有承诺在先,我还是会标注上数据集作者的GitHub地址:(https://github.com/yuweiming70/Landscape-Dataset),毕竟他送了我这么多精美壁纸

 

牛津大学的17 Category Flower Dataset

(http://www.robots.ox.ac.uk/~vgg/data/flowers/17/index.html)很漂亮,看起来就是我在寻找的数据集(没错,我判断一个东西是不是自己需要的标准就是漂不漂亮)。这个数据集总共17种花,每种花有80张图片,整个数据集有1360张图片,为了既达到实验的目的又不在训练上耗费太多的时间,我在同一种算法上选取了前两种花和前四种花做对比实验:

 

 


由于SVM和ANN的原理会占用太多的篇幅,并且这篇文章的主要目的是为了讲解代码实现,所以这里只介绍下机器学习中最简单的KNN分类器:

 

KNN是数据挖掘分类技术中最简单的方法之一,k最近邻,从名字大致就可以看出它的含义,就是找出K个离自己最近(相似)的数据,在这K个找到的数据中,看看那个类别最多,那么就认为自己是属于哪一个类别。

 

关于相似度度量的方法有很多,常见的有欧氏距离、曼哈顿距离、切比雪夫距离(切比雪夫兄跨界实在太多)、汉明距离、余弦相似度(夹角余弦值)等等。

 

KNN用到的是:欧氏距离(L2)

 

在数据(向量)只有二维的情况下,两个数据之间的欧氏距离就是两个点在二维坐标系下的直线距离,用初中一年级的数学公式就可以算出来:

 

当数据推广到多维的时候,两个数据之间的欧式距离就变成了:

 

距离越小表示两个向量相似度越大。

 

KNN有着实现方法简单、无需训练的优点,但是由于每次分类都要计算和所有数据之间的相似度,所以当数据维度很大或者数据数量很大的时候,计算会很耗时。

 

 


实验(KNN、SVM、ANN):

 

现在我要使用sklearn中的KNeighborsClassifier( KNN )、SVC( SVM )、MLPClassifier( 多层感知机分类器:ANN ),来实现这三个算法,由于sklearn中的算法模型高度统一化,所以三个程序可以写在同一个例子中,只是在创建分类器模型的时候略有不同:

 

引入KNeighborsClassifier、SVC、MLPClassifier模块:

from sklearn.neighbors import  KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.neural_network import MLPClassifier

 

引入训练样本分割函数:

from sklearn.model_selection import train_test_split

 

引入numpy 和 opencv:

import cv2
import numpy as np

 

读取图像函数,返回图像列表和标签列表:

IMAGE_SIZE = 100

def resize_without_deformation(image, size = (IMAGE_SIZE, IMAGE_SIZE)):
    height, width, _ = image.shape
    longest_edge = max(height, width)
    top, bottom, left, right = 0, 0, 0, 0
    if height < longest_edge:
        height_diff = longest_edge - height
        top = int(height_diff / 2)
        bottom = height_diff - top
    elif width < longest_edge:
        width_diff = longest_edge - width
        left = int(width_diff / 2)
        right = width_diff - left

    image_with_border = cv2.copyMakeBorder(image, top , bottom, left, right, cv2.BORDER_CONSTANT, value=[0, 0, 0])

    resized_image = cv2.resize(image_with_border, size)
    return resized_image

def read_image(size = None):
    data_x, data_y = [], []
    #for i in range(1, 1361):
    for i in range(1, 241):
        try:
            im = cv2.imread('17flowers/image_%s.jpg' % str(i).zfill(4))

            if size is None:
                size = (IMAGE_SIZE, IMAGE_SIZE)
            im = resize_without_deformation(im, size)
            im = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
            data_x.append(np.asarray(im, dtype = np.int8))
            data_y.append(str(int((i-1)/80.0)))
        except IOError as e:
            print(e)
        except:
            print('Unknown Error!')

    return data_x, data_y

raw_images, raw_labels = read_image(size = (IMAGE_SIZE, IMAGE_SIZE))
raw_images, raw_labels = np.asarray(raw_images, dtype = np.float32), np.asarray(raw_labels, dtype = np.int32)

 

由于这三种分类器只接受一维向量的输入,所以将图片拍扁:

raw_images = raw_images.reshape((-1, IMAGE_SIZE * IMAGE_SIZE))

 

分割训练集和测试集(训练集:测试集 = 8 : 2):

train_images, test_images, train_labels, test_labels = train_test_split(raw_images, raw_labels,
                                                                        test_size = 0.2)

 

图片数据归一化:

train_images /= 255.0
test_images /= 255.0

 

创建分类器模型:

classifier_model = KNeighborsClassifier(n_neighbors = 7)

'''
classifier_model = SVC(C = 1.0,
                       kernel = 'rbf',
                       max_iter = 10000,
                       class_weight = 'balanced')
'''

'''
classifier_model = MLPClassifier(hidden_layer_sizes=(20, 100, 70), activation = 'relu',
                                 solver = 'sgd', batch_size = 5,
                                 learning_rate_init = 0.001, max_iter = 1000,
                                 alpha=1e-4, tol=1e-4, 
                                 random_state=1, shuffle = True,
                                 momentum = 0.8)
'''

 

训练:

classifier_model.fit(train_images, train_labels)

 

计算准确率:

accuracy = classifier_model.score(test_images, test_labels)
print('Accuracy: %s' % str(accuracy))

 

 

实验了几次,计算准确率平均值大致得到:

  • KNN:     73.8%

  • SVM:     78.5%

  • ANN:     78.9%

 

上面只是2分类的情况,现在取4种花来训练,得到准确率:

  • KNN:     40.1%

  • SVM:     40.6%

  • ANN:     41.5%

 

并且ANN在训练集上的正确率表现为100%, 很明显,已经过拟合,即模型已经呈现了记忆效应。

 

 


实验(CNN):

 

现在来试下卷积神经网络(由于上篇文章已经讲解过卷积神经网络的构建过程,这篇文章就不再赘述):

 

引入相关模块:

import  keras
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dense, Dropout, Flatten
from keras.optimizers import SGD
from keras.utils import np_utils
from sklearn.model_selection import train_test_split
import cv2
import numpy as np

 

读取图像函数,返回图像列表和标签列表:

IMAGE_SIZE = 100

def resize_without_deformation(image, size = (IMAGE_SIZE, IMAGE_SIZE)):
    height, width, _ = image.shape
    longest_edge = max(height, width)
    top, bottom, left, right = 0, 0, 0, 0
    if height < longest_edge:
        height_diff = longest_edge - height
        top = int(height_diff / 2)
        bottom = height_diff - top
    elif width < longest_edge:
        width_diff = longest_edge - width
        left = int(width_diff / 2)
        right = width_diff - left

    image_with_border = cv2.copyMakeBorder(image, top , bottom, left, right, cv2.BORDER_CONSTANT, value=[0, 0, 0])

    resized_image = cv2.resize(image_with_border, size)

    return resized_image

def read_image(size = None):
    data_x, data_y = [], []
    #for i in range(1, 1361):
    for i in range(1, 161):
        try:
            im = cv2.imread('17flowers/image_%s.jpg' % str(i).zfill(4))
            #im = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
            if size is None:
                size = (IMAGE_SIZE, IMAGE_SIZE)
            im = resize_without_deformation(im, size)
            data_x.append(np.asarray(im, dtype = np.int8))
            data_y.append(str(int((i-1)/80.0)))
        except IOError as e:
            print(e)
        except:
            print('Unknown Error!')

    return data_x, data_y

raw_images, raw_labels = read_image(size = (IMAGE_SIZE, IMAGE_SIZE))
raw_images, raw_labels = np.asarray(raw_images, dtype = np.float32), np.asarray(raw_labels, dtype = np.int32)

 

One-Hot编码:

ont_hot_labels = np_utils.to_categorical(raw_labels)

 

分割训练集和测试集(训练集:测试集 = 8 : 2):

train_images, test_images, train_labels, test_labels = train_test_split(raw_images, ont_hot_labels,
                                                                        test_size = 0.2)

 

图片数据归一化:

train_images /= 255.0
test_images /= 255.0

 

构建CNN:

采用了VGG19结构的CNN:

 

深度很深,训练时间很长,特别耗内存和处理器(训练的时候记得在电脑下面垫冰块):

image_classification_model = keras.Sequential()

image_classification_model.add(Conv2D(64,(3,3),strides=(1,1),input_shape=(IMAGE_SIZE, IMAGE_SIZE, 3),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(Conv2D(64,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(MaxPooling2D(pool_size=(2,2)))
image_classification_model.add(Conv2D(128,(3,2),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(Conv2D(128,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(MaxPooling2D(pool_size=(2,2)))
image_classification_model.add(Conv2D(256,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(Conv2D(256,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(Conv2D(256,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(MaxPooling2D(pool_size=(2,2)))
image_classification_model.add(Conv2D(512,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(Conv2D(512,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(Conv2D(512,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(MaxPooling2D(pool_size=(2,2)))
image_classification_model.add(Conv2D(512,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(Conv2D(512,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(Conv2D(512,(3,3),strides=(1,1),padding='same',activation='relu',kernel_initializer='uniform'))
image_classification_model.add(MaxPooling2D(pool_size=(2,2)))
image_classification_model.add(Flatten())
image_classification_model.add(Dense(4096,activation='relu'))
image_classification_model.add(Dropout(0.5))
image_classification_model.add(Dense(4096,activation='relu'))
image_classification_model.add(Dropout(0.5))
image_classification_model.add(Dense(len(ont_hot_labels[0]),activation='softmax'))

image_classification_model.summary()

 

如果想要快速训练,也可以使用下面的简化模型,效果也还不错:

image_classification_model = keras.Sequential()

image_classification_model.add(Conv2D(32, 3, 3, border_mode='valid',
                                  subsample = (1, 1),
                                  dim_ordering = 'tf',
                                  input_shape = (IMAGE_SIZE, IMAGE_SIZE, 3),
                                  activation='relu'))

image_classification_model.add(Conv2D(32, 3, 3,border_mode='valid',
                                  subsample = (1, 1),
                                  dim_ordering = 'tf',
                                  activation = 'relu'))

image_classification_model.add(MaxPooling2D(pool_size=(2, 2)))
image_classification_model.add(Dropout(0.25))

image_classification_model.add(Conv2D(64, 3, 3, border_mode='valid',
                                  subsample = (1, 1),
                                  dim_ordering = 'tf',
                                  activation = 'relu'))

image_classification_model.add(Conv2D(64, 3, 3, border_mode='valid',
                                  subsample = (1, 1),
                                  dim_ordering = 'tf',
                                  activation = 'relu'))

image_classification_model.add(MaxPooling2D(pool_size=(2, 2)))
image_classification_model.add(Dropout(0.25))

image_classification_model.add(Flatten())
image_classification_model.add(Dense(512, activation = 'relu'))
image_classification_model.add(Dropout(0.5))
image_classification_model.add(Dense(len(ont_hot_labels[0]), activation = 'sigmoid'))

image_classification_model.summary()

 

设置SGD优化器并编译模型:

learning_rate = 0.01
decay = 1e-6
momentum = 0.9
nesterov = True
sgd_optimizer = SGD(lr = learning_rate, decay = decay,
                momentum = momentum, nesterov = nesterov)
image_classification_model.compile(loss = 'categorical_crossentropy',
                               optimizer = sgd_optimizer,
                               metrics = ['accuracy'])

 

训练,这里只训练30次:

batch_size = 20
epochs = 30
image_classification_model.fit(train_images, train_labels,
                           epochs = epochs,
                           batch_size = batch_size,
                           shuffle = True,
                           validation_data = (test_images, test_labels))

 

看看这个模型在测试集上的表现:

score = image_classification_model.evaluate(test_images, test_labels, verbose=0)
print("%s: %.2f%%" % (image_classification_model.metrics_names[1], score[1] * 100))

 

 

 

准确率96.88%

 

再试下四种花的情况,在测试集上正确率为 70%,在训练集上正确率为 99.6%,虽然也过拟合,但是比三种经典分类器效果要好很多。

 

这种感觉就好像CNN把经典机器学习分类器的脸按在地上疯狂的摩擦,不放润滑油的那种。

 

不过反思一下,如果在进SVM、KNN、ANN之前,可以做一些特征提取,效果应该会更好一些。毕竟CNN训练和运行起来实在是太耗硬件了。

 

 

这是我的微信公众号二维码:

欢迎关注!   这是我的微信号二维码,扫一扫可以和我交流: 

 

 

微信公众号原文链接:https://mp.weixin.qq.com/s/3dzlEsFkBPE8bXl3jwPy3Q

  • 7
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值