原文地址:智能单元

图像分类:所谓图像分类问题,就是已有固定的分类标签集合,然后对于输入的图像按照标签类别,将其打上标签。

下面先介绍一下一个简单的图像如何利用计算机进行分类:

例子:以下图为例,图像分类模型读取该图片,并生成该图片属于集合 {cat, dog, hat, mug}中各个标签的概率。需要注意的是,对于计算机来说,图像是一个由数字组成的巨大的3维数组。在这个例子中,猫的图像大小是宽248像素,高400像素,有3个 v颜色通道,分别是红、绿和蓝(简称RGB)。如此,该图像就包含了248X400X3=297600个数字,每个数字都是在范围0-255之间的整型,其中0表示全黑,255表示全白。我们的任务就是把这些上百万的数字变成一个简单的标签,比如“猫”。

图像分类的任务,就是对于一个给定的图像,预测它属于的那个分类标签(或者给出属于一系列不同标签的可能性)。图像是3维数组,数组元素是取值范围从0到255的整数。数组的尺寸是宽度x高度x3,其中这个3代表的是红、绿和蓝3个颜色通道。

困难和挑战:对于我们人来说,识别一张图中的图像是很简单的,然而在计算机的角度就没有那么简单了。下面就是在计算机视觉算法在图像识别方面遇到的一些困难,要记住图像是以3维数组来表示的,数组中的元素是亮度值(见下图)。

      视角变化:同一个物体,摄像机可以通过不同的角度来展现。

      大小变化:物体可视的大小在摄像机下是会变化的

      形变:很多东西的形状并不是一定的,会有很大的变化

      遮挡:在视觉效果下,目标物体可能会被一些东西遮挡,不能看到目标的全貌

      光照条件:在像素层面上,光照的影响非常大

      背景干扰:物体可能混入背景之中,可能难以辨别

      类内差异:同一种物品的不同品种差异也会很大

      面对以上所有变化及其组合,好的图像分类模型能够在维持分类结论稳定的同时,保持对类间差异足够敏感。

数据驱动方法:如何写一个图像分类的算法呢?这和写个排序算法可是大不一样。怎么写一个从图像中认出猫的算法?搞不清楚。因此,与其在代码中直接写明各类物体到底看起来是什么样的,倒不如说我们采取的方法和教小孩儿看图识物类似:给计算机很多数据,然后实现学习算法,让计算机学习到每个类的外形。这种方法,就是数据驱动方法。既然该方法的第一步就是收集已经做好分类标注的图片来作为训练集,那么下面就看看数据库到底长什么样:

一个有4个视觉分类的训练集。在实际中,我们可能有上千的分类,每个分类都有成千上万的图像。

图像分类流程:在课程视频中已经学习过,图像分类就是输入一个元素为像素值的数组,然后给它分配一个分类标签。流程如下:

    输入:输入是包含N个图像的集合,每个图像的标签是k种分类标签中的一种。这个集合称为训练集。

    学习:这一步的任务是使用训练集来学习每个类到底长什么样子。一般该步骤叫做训练分类器或者学习一个模型。

    评价:让分类器来预测它未曾见过的图像的分类标签,并以此来评价分类器的质量。我们会把分类器预测的标签和图像真正的分类标签对比。毫无疑问,分类器预测的分类标签和图像真正的分类标签如果一致,那就是好事,这样的情况越多越好。

Nearest Neighbor分类器

作为课程介绍的第一个方法,我们来实现一个Nearest Neighbor分类器。虽然这个分类器和卷积神经网络没有任何关系,实际中也极少使用,但通过实现它,可以让读者对于解决图像分类问题的方法有个基本的认识。

图像分类数据集:CIFAR-10。一个非常流行的图像分类数据集是CIFAR-10。这个数据集包含了60000张32X32的小图像。每张图像都有10种分类标签中的一种。这60000张图像被分为包含50000张图像的训练集和包含10000张图像的测试集。在下图中你可以看见10个类的10张随机图片。

左边:从CIFAR-10数据库来的样本图像。右边:第一列是测试图像,然后第一列的每个测试图像右边是使用Nearest Neighbor算法,根据像素差异,从训练集中选出的10张最类似的图片。

以下就是下载了的CIFAR-10:

通过下面的代码的可以看到CIFAR-10的图片:

  1. # -*- coding:utf-8 -*-
  2. import pickle as p
  3. import numpy as np
  4. import matplotlib.image as plimg
  5. from PIL import Image
  6. def load_CIFAR_batch(filename):
  7. """ load single batch of cifar """
  8. with open(filename, 'rb')as f:
  9. datadict = p.load(f)
  10. X = datadict['data']
  11. Y = datadict['labels']
  12. X = X.reshape(10000, 3, 32, 32)
  13. Y = np.array(Y)
  14. return X, Y
  15.  
  16. def load_CIFAR_Labels(filename):
  17. with open(filename, 'rb') as f:
  18. lines = [x for x in f.readlines()]
  19. print(lines)
  20.  
  21. if __name__ == "__main__":
  22. load_CIFAR_Labels("cifar-10-batches-py/batches.meta")
  23. imgX, imgY = load_CIFAR_batch("cifar-10-batches-py/data_batch_1")
  24. print imgX.shape
  25. print "正在保存图片:"
  26. for i in xrange(imgX.shape[0]):
  27. imgs = imgX[i - 1]
  28. if i < 100:#只循环100张图片,这句注释掉可以便利出所有的图片,图片较多,可能要一定的时间
  29. img0 = imgs[0]
  30. img1 = imgs[1]
  31. img2 = imgs[2]
  32. i0 = Image.fromarray(img0)
  33. i1 = Image.fromarray(img1)
  34. i2 = Image.fromarray(img2)
  35. img = Image.merge("RGB",(i0,i1,i2))
  36. name = "img" + str(i)+ ".png"
  37. img.save("images/"+name,"png")#文件夹下是RGB融合后的图像
  38. for j in xrange(imgs.shape[0]):
  39. img = imgs[j - 1]
  40. name = "img" + str(i) + str(j) + ".png"
  41. print "正在保存图片" + name
  42. plimg.imsave("image/" + name, img)#文件夹下是RGB分离的图像
  43.  
  44. print "保存完毕."

假设现在我们有CIFAR-10的50000张图片(每种分类5000张)作为训练集,我们希望将余下的10000作为测试集并给他们打上标签。Nearest Neighbor算法将会拿着测试图片和训练集中每一张图片去比较,然后将它认为最相似的那个训练集图片的标签赋给这张测试图片。上面右边的图片就展示了这样的结果。请注意上面10个分类中,只有3个是准确的。比如第8行中,马头被分类为一个红色的跑车,原因在于红色跑车的黑色背景非常强烈,所以这匹马就被错误分类为跑车了。

那么具体如何比较两张图片呢?在本例中,就是比较32x32x3的像素块。最简单的方法就是逐个像素比较,最后将差异值全部加起来。换句话说,就是将两张图片先转化为两个向量,然后计算他们的L1距离:

这里的求和是针对所有的像素。下面是整个比较流程的图例:

以图片中的一个颜色通道为例来进行说明。两张图片使用L1距离来进行比较。逐个像素求差值,然后将所有差值加起来得到一个数值。如果两张图片一模一样,那么L1距离为0,但是如果两张图片很是不同,那L1值将会非常大。

  1. #-*- coding:utf-8 -*-
  2.  
  3. import numpy as np
  4.  
  5. def unpickle(file):
  6. import cPickle
  7. fo = open(file,'rb')
  8. dict = cPickle.load(fo)
  9. fo.close()
  10. return dict
  11.  
  12. #依次返回:训练的数据 训练的标题 测试的数据 测试的标题
  13. def load_CIFAR10(file):
  14. #get the training data
  15. dataTrain = []
  16. labelTrain = []
  17. for i in range(1,6):
  18. dic = unpickle(file+"\\data_batch_"+str(i))
  19. for item in dic["data"]:
  20. dataTrain.append(item)
  21. for item in dic["labels"]:
  22. labelTrain.append(item)
  23. #get test data
  24. dataTest = []
  25. labelTest = []
  26. dic = unpickle(file+"\\test_batch")
  27. for item in dic["data"]:
  28. dataTest.append(item)
  29. for item in dic["labels"]:
  30. labelTest.append(item)
  31. return (dataTrain,labelTrain,dataTest,labelTest)
  32.  
  33. # 下面,让我们看看如何用代码来实现这个分类器。
  34. # 首先,我们将CIFAR-10的数据加载到内存中,并分成4个数组:训练数据和标签,测试数据和标签。
  35. # 在下面的代码中,Xtr(大小是50000x32x32x3)存有训练集中所有的图像,
  36. # Ytr是对应的长度为50000的1维数组,存有图像对应的分类标签(从0到9):
  37. datatr, labeltr, datate, labelte = load_CIFAR10("D:/python_projects/NN/cifar-10-batches-py")
  38. Xtr = np.asarray(datatr)
  39. Xte = np.asarray(datate)
  40. Ytr = np.asarray(labeltr)
  41. Yte = np.asarray(labelte)
  42. Xtr_rows = Xtr.reshape(Xtr.shape[0], 32 * 32 * 3) # Xtr_rows becomes 50000 x 3072
  43. Xte_rows = Xte.reshape(Xte.shape[0], 32 * 32 * 3) # Xte_rows becomes 10000 x 3072
  44. print Xtr.shape
  45. print Xte.shape
  46. print Ytr.shape
  47. print Yte.shape
  48. print type(Xtr)
  49.  
  50. # 作为评价标准,我们常常使用准确率,它描述了我们预测正确的得分。
  51. # 请注意以后我们实现的所有分类器都需要有这个API:train(X, y)函数。
  52. # 该函数使用训练集的数据和标签来进行训练。
  53. # 从其内部来看,类应该实现一些关于标签和标签如何被预测的模型。
  54. # 这里还有个predict(X)函数,它的作用是预测输入的新数据的分类标签。
  55. # 现在还没介绍分类器的实现,下面就是使用L1距离的Nearest Neighbor分类器的实现套路:
  56. class NearestNeighbor(object):
  57. def __init__(self):
  58. pass
  59.  
  60. def train(self, X, y):
  61. """ X is N x D where each row is an example. Y is 1-dimension of size N """
  62. # the nearest neighbor classifier simply remembers all the training data
  63. self.Xtr = X
  64. self.ytr = y
  65.  
  66. def predict(self, X):
  67. """ X is N x D where each row is an example we wish to predict label for """
  68. num_test = X.shape[0]
  69. # lets make sure that the output type matches the input type
  70. Ypred = np.zeros(num_test, dtype = self.ytr.dtype)
  71. num=0
  72.  
  73. # loop over all test rows
  74. for i in xrange(num_test):
  75. # find the nearest training image to the i'th test image
  76. # using the L1 distance (sum of absolute value differences)
  77. distances = np.sum(np.abs(self.Xtr - X[i,:]), axis = 1)
  78. min_index = np.argmin(distances) # get the index with smallest distance
  79. Ypred[i] = self.ytr[min_index] # predict the label of the nearest example
  80. num = num+1
  81. if num%10==0:
  82. print "num=",num/10
  83.  
  84. return Ypred
  85.  
  86. # 现在我们得到所有的图像数据,并且把他们拉长成为行向量了。接下来展示如何训练并评价一个分类器:
  87. nn = NearestNeighbor() # create a Nearest Neighbor classifier class
  88. nn.train(Xtr_rows, Ytr) # train the classifier on the training images and labels
  89. Yte_predict = nn.predict(Xte_rows) # predict labels on the test images
  90. # and now print the classification accuracy, which is the average number
  91. # of examples that are correctly predicted (i.e. label matches)
  92. print 'accuracy: %f' % ( np.mean(Yte_predict == Yte) )

如果你用这段代码跑CIFAR-10,你会发现准确率能达到38.6%。这比随机猜测的10%要好,但是比人类识别的水平(据研究推测是94%)和卷积神经网络能达到的95%还是差多了。点击查看基于CIFAR-10数据的Kaggle算法竞赛排行榜

距离选择:计算向量间的距离有很多种方法,另一个常用的方法是L2距离,从几何学的角度,可以理解为它在计算两个向量间的欧式距离。L2距离的公式如下:

换句话说,我们依旧是在计算像素间的差值,只是先求其平方,然后把这些平方全部加起来,最后对这个和开方。在Numpy中,我们只需要替换上面代码中的1行代码就行:

  1. distances = np.sqrt(np.sum(np.square(self.Xtr - X[i,:]), axis = 1))

注意在这里使用了np.sqrt,但是在实际中可能不用。因为求平方根函数是一个单调函数,它对不同距离的绝对值求平方根虽然改变了数值大小,但依然保持了不同距离大小的顺序。所以用不用它,都能够对像素差异的大小进行正确比较。如果你在CIFAR-10上面跑这个模型,正确率是35.4%,比刚才低了一点。

L1和L2比较。比较这两个度量方式是挺有意思的。在面对两个向量之间的差异时,L2比L1更加不能容忍这些差异。也就是说,相对于1个巨大的差异,L2距离更倾向于接受多个中等程度的差异。L1和L2都是在p-norm常用的特殊形式。

斯坦福视频地址链接:http://pan.baidu.com/s/1cnplZg 密码:21ex

CS231n学习笔记-图像分类笔记(上篇)的更多相关文章

  1. CS231n学习笔记-图像分类笔记(下篇)

    原文地址:智能单元 K-Nearest Neighbor分类器 大家可能注意到了,为什么只用最相似的一张图片的标签来作为测试图像的标签呢?这不是很奇怪吗!是的,使用K-Nearest Neighbor ...

  2. CS231n课程笔记翻译2:图像分类笔记

    译者注:本文智能单元首发,译自斯坦福CS231n课程笔记image classification notes,由课程教师Andrej Karpathy授权进行翻译.本篇教程由杜客翻译完成.Shiqin ...

  3. hadoop2.5.2学习及实践笔记(二)—— 编译源代码及导入源码至eclipse

    生产环境中hadoop一般会选择64位版本,官方下载的hadoop安装包中的native库是32位的,因此运行64位版本时,需要自己编译64位的native库,并替换掉自带native库. 源码包下的 ...

  4. Python学习的个人笔记(基础语法)

    Python学习的个人笔记 题外话: 我是一个大二的计算机系的学生,这份python学习个人笔记是趁寒假这一周在慕课网,w3cschool,还有借鉴了一些博客,资料整理出来的,用于自己方便的时候查阅, ...

  5. 开始记录学习java的笔记

    今天开始记录学习java的笔记,加油

  6. 菜鸟教程之学习Shell script笔记(上)

    菜鸟教程之学习Shell script笔记 以下内容是,学习菜鸟shell教程整理的笔记 菜鸟教程之shell教程:http://www.runoob.com/linux/linux-shell.ht ...

  7. hadoop2.5.2学习及实践笔记(四)—— namenode启动过程源码概览

    对namenode启动时的相关操作及相关类有一个大体了解,后续深入研究时,再对本文进行补充 >实现类 HDFS启动脚本为$HADOOP_HOME/sbin/start-dfs.sh,查看star ...

  8. 深度学习Keras框架笔记之AutoEncoder类

    深度学习Keras框架笔记之AutoEncoder类使用笔记 keras.layers.core.AutoEncoder(encoder, decoder,output_reconstruction= ...

  9. 深度学习Keras框架笔记之TimeDistributedDense类

    深度学习Keras框架笔记之TimeDistributedDense类使用方法笔记 例: keras.layers.core.TimeDistributedDense(output_dim,init= ...

随机推荐

  1. 继承方法-->最终模式

    function inherit(Target,Origin){ function F(){}; F.prototype = Origin.prototype; // Targrt.prototype ...

  2. IP之NCO仿真

    NCO仿真要用.vo仿真模型,不能用.v文件 /**************************************************************************** ...

  3. SceneControl+AE+鼠标滚轮缩放

    要为SceneControl设置鼠标滚轮缩放必须定义委托,因为SceneControl没有Wheel事件,所以委托From的Wheel事件 public Form1() { InitializeCom ...

  4. webuploader 上传传自定义参数

    全局设置 // 初始化的时候直接添加 var uploader = new WebUploader.Uploader({     ...     formData: {         uid: 12 ...

  5. TableView编辑状态下跳转页面的崩溃处理

    29down votefavorite 12 I have a viewController with a UITableView, the rows of which I allow to edit ...

  6. post同步请求

    // http://api.hudong.com/iphonexml.do?type=focus-c     //post请求中url不带请求参数,请求参数在参数HTTPBody中设置, 需要创建可变 ...

  7. Gridview的RowDataBound事件可以做很多事情

    protected void gvTest_RowDataBound(object sender, GridViewRowEventArgs e)        {            //如果是绑 ...

  8. Google Summer of Code礼包

    这个暑假参加google summer of code, 给Google的分布式容器管理系统kubernates开发新的特性,希望从中学习更多的分布式的技术,锻炼自己的编程技巧. 中午在学校的图书馆吗 ...

  9. MVC4 项目开发日志(1)

    最近一直在定义一个功能全面,层次结构分明的框架.一边学习一边应用.

  10. (原创)PetaPoco使用小记(2014-5-5更新)

    接触PetaPoco已经有一段时间了,为了全面了解一下PetaPoco,刚好结合目前在做的一个项目,对常用的几个业务操作用PetaPoco进行改写,如增删改查.分页以及存储过程的调用,在文章的最后附上 ...