如果我们人为地给每一张美食图片打一个分,食材、摆盘、是否有滤镜(划重点)、口味等等,这些因素都会影响到人们打出分数的高低,每个人的审美各有不同,但是对美好的食物总是存在一致性追求的。因为主观的因素,我们对照片的评分不一定是完全相同的,那么能不能让机器来做这件事情,给出一个相对客观的打分呢~
数据来源
这些美食图片收集于一个叫做Flickr的开源网站,人们把自己拍下的食品图片上传上去。食物种类众多,且由于上传用户的偏好不同,照片的主体大小、颜色和构图都不尽相同。经过我们人工筛选,最终收集到了196张图片用于案例分析。关于打分数据,则是由五人组成的研究小组对美食照片进行1-5分的评分,1分为最低分,5分为最高分,最后选取平均值来作为照片的最终得分。
(196个样本用作深度学习实在太少,评分组也只有五个人,所以此案例仅作展示,专业人士请轻拍(,,•́.•̀,,))
读入数据:因变量
(这两段对程序的叙述较多,可当作Python简易数据分析教程阅读。)
在Python中,一般要用pandas包做数据分析,程序中第一行是import pandas as pd,这句话表示,在这个python程序中,我们要导入pandas这个包,给它起个简称叫pd,方便我们后续使用。接下来使用MasterFile=pd.read_csv('../case3-food/FoodScore.csv')语句读入数据,单引号里是数据存放的路径,使用pandas包里的read_csv函数读入,赋值给变量MasterFile。 MasterFile.shape是MasterFile这个实例的一个方法,名字叫做shape,顾名思义是指这个变量的“形状”,也就是维度,可以看到这个数据框有196行,对应196个数据,有2列,对应两个变量名。再使用MasterFile[0:5]索引得到前五行的数据,具体代码和结果如下所示。
import pandas as pdMasterFile=pd.read_csv('../case3-food/FoodScore.csv') print(MasterFile.shape)MasterFile[0:5]
可以看到,除了第3张图片,其他图片的得分都在2.5左右,正好是满分的一半,也有得到3.45分的图片。有人说,只看5张图片的得分不过瘾,想要看看所有得分的分布情况呢。众所周知,查看一个连续型变量的分布情况需要使用“直方图”,它描述了数据落在各个区间的个数。由于MasterFile已经是一个DataFrame实例,我们可以使用.hist()的方法查看该组数据的分布。
MasterFile.hist()
从上图可以看到平均分在3.5左右的图片较多,图片的得分以3.5为中心大致呈现出正态分布的情况(尾部在左侧,稍微有一些左偏),说明评委们还是倾向于给高分的,毕竟是美食图片,人人都爱~
之后是一件数据处理上的事情,我们需要把得分score单独分离出来,用到numpy包里的array格式。除了pandas以外,在Python中最重要的另一个数据处理包就是numpy,它提供了一种名为array(数组)的格式来储存数据。array的优势之一是可以储存多维度的数据。比如说一张照片,就涉及到三个维度,长宽以及通道数,彩色照片的通道数一般是3。
同样的,我们先import numpy as np,在这个程序里就使用np这个简称来替代numpy,接着FileNames=MasterFile['ID']是把ID这一列提取,得到图片的名字。N=len(FileNames)获取了FileName变量的长度,即图片的个数。然后要得到一个array格式的因变量。
Y=np.array(MasterFile['score']).reshape([N,1]),这句话的意思是说,将score这一列提取出来之后,使用np.array()转换为数组格式,再使用.reshape方法变成N行1列的形状,这就完成了因变量的数据处理。
import numpy as npFileNames=MasterFile['ID']N=len(FileNames)Y=np.array(MasterFile['score']).reshape([N,1])
读入数据:自变量
在Python中,我们可以用Image类读取图片数据(这又接触了一个新的包,像Python和R这类开源语言就很依赖各行各业的使用者的贡献,光是别人写的包就成千上万个T_T),我们使用from PIL import Image得到Image这个类。
以下代码的目的是,首先指定图片统一的像素大小。这里定义为IMSIZE=128,我们希望把大小不一致的图片都转换为长宽为128像素的图片。接下来先初始化一个X,这个X有四个维度,包括【储存数据个数,图片长度,图片宽度,通道数】,分别将【N, IMSIZE, IMSIZE, 3】的值传递给X,作为四个维度。np.zeros()是说这个X在各个位置上的值都是0。
from PIL import ImageIMSIZE=128X=np.zeros([N,IMSIZE,IMSIZE,3])for i in range(N): MyFile=FileNames[i] Im=Image.open('../case3-food/data/'+MyFile+'.jpg') Im=Im.resize([IMSIZE,IMSIZE]) Im=np.array(Im)/255 X[i,]=Im
接下来通过for循环一步步把图片存到X中去。MyFile=FileNames[i]是将FileNames的第i个变量赋值给MyFile,也就是第i个图片的名字。 Im=Image.open('../case3-food/data/'+MyFile+'.jpg')引号里面是图片储存的路径,在Python里可以用符号“+”来连接字符串。Image.open()也就是打开这张图片啦,赋值给IM这个变量。接着,使用IM.resize()来将图片变为统一的大小,也就是IMSIZE×IMSIZE大小的图片。最后将IM转换为array格式,在将这张图片每个像素上值的大小除以255。这是因为,在原始的图片中,这些图片的每个像素点大小的取值范围是0~255,而TensorFlow只能处理0~1之间的数据,所以我们把所有数都除以255以满足要求。最后,在X的第i个位置上放置好IM这张图片,使用X[i,]=Im语句就可以实现。
美食图片数据展示
现在让我们看看这些美食照片都长什么样子吧!在Python里,数据的可视化一般是使用matplotlib这个包。从这个包里导入pyplot这个类,取个简称叫plt。首先用plt.figure()方法创建一张画布。然后将其划分为两行五列,总共展示10张图片。把要展示的图片的高度和宽度分别设置为7.5和15,分别用fig.set_figheight(7.5)和fig.set_figwidth(15)实现。用flatten()将ax这个2×5的向量拉直为长度为10的向量,方便我们指定图片放置的位置。最后,用for循环一张张把图片放上去,ax[i].imshow(X[i,:,:,:])是说在画布ax的第i个位置展示X的第i张图片,ax[i].set_title(np.round(Y[i],2))表示把美食图片的评分设置为标题。np.around()是让这些分数只取到两位小数。
from matplotlib import pyplot as pltplt.figure()fig,ax=plt.subplots(2,5)fig.set_figheight(7.5)fig.set_figwidth(15)ax=ax.flatten()for i in range(10): ax[i].imshow(X[i,:,:,:]) ax[i].set_title(np.round(Y[i],2))
照片来啦!部分图片不太清晰,是因为有一些图片是经过压缩处理的。其中得分最高的是第2行第1个图片,得到了4.63分,这应该是一个食材丰富的披萨,在经过压缩后有些模糊,相信原始的图片还是很优秀的!得分最低的是第1行第4张图片,像是什么奇怪的炒菜,小编也没看明白是啥,这可能也是它得分比较低的原因。
建 模
在建模之前,需要把数据划分为训练集和测试集。在Python中有一个专门做机器学习的包,名为sklearn(或者scikit-learn),使用这个包里的一个函数train_test_split,可以把数据集随机划分为训练集和测试集。在这个函数里,从左到右四个参数分别是X,自变量;Y,因变量;test_size,也就是要百分之多少的数据作为测试集;random_state是一个随机数种子,可以任给一个数字,保证了结果是可重复的。
from sklearn.cross_validation import train_test_splitX0,X1,Y0,Y1=train_test_split(X,Y,test_size=0.5,random_state=0)
然后,使用TensorFlow建立线性回归模型,自变量是一张图片上所有像素点的值,用Flatten()函数把图片拉直成一列向量。首先把模型的框架搭好,用Input()函数告诉计算机传入的图片的大小维度,这里等于[IMSIZE, IMSIZE, 3],接着把图像拉直,再用Dense(1)全连接层把这个拉直的数据聚合到一个值上,即因变量Y,也就是图像的得分。最后,把这个模型取名为model,再用model.summary()查看每一层的具体情况,如下所示。
from keras.layers import Dense, Flatten, Inputfrom keras import Modelinput_layer=Input([IMSIZE,IMSIZE,3])x=input_layerx=Flatten()(x)x=Dense(1)(x)output_layer=xmodel=Model(input_layer,output_layer)model.summary()
模型设计好之后,接下来就是编译运行。使用MSE作为模型评价的标准,MSE的全称是Mean Square Error,中文名是均方误差(大部分情况下也就是预测值和实际值做差的平方和),在因变量是连续变量时这是很常用的一个评价指标,通过loss=`mse`来实现,我们的目的就是最小化这个均方误差,设置metrics=[`mse`],这是我们优化过程中要监督的指标,选取优化器optimizer=Adam,其中lr=0.001是学习率。
from keras.optimizers import Adammodel.compile(loss='mse',optimizer=Adam(lr=0.001),metrics=['mse'])
接下来通过model.fit()进行模型拟合,传入训练集数据X0,Y0,交叉验证的测试集数据是X1,Y1,batch_size是指我每次优化要使用多少张图片,epochs是要循环多少次,也就是遍历多少次数据集。
model.fit(X0,Y0, validation_data=[X1,Y1], batch_size=100, epochs=300)
这里补充一句,前面提到的lr, batch_size, epochs取值是需要调试的,我们一开始并不知道大小多少才合适(事实上写这篇推送的时候也不知道最优值是啥),这也是为什么说深度学习是一个调参的过程。
可能有人说,线性回归实在是太简单的模型了,用在这里效果是不是不太好?确实是这样的,当处理图像数据的时候,一般不适用线性回归模型,而是使用CNN相关的模型,CNN的全称是Convolutional Neural Network,中文名是卷积神经网络。关于卷积这个方法有太多需要讲的内容,这里我们就不展开讲啦,直接看代码吧~
from keras.layers import Input,Conv2D,MaxPooling2D,Dense,Flatten,Activationfrom keras import Modelinput_layer=Input((IMSIZE,IMSIZE,3)) x=input_layer x=Conv2D(100,(32,32))(x) x=Activation('relu')(x) x=MaxPooling2D((32,32))(x) x=Flatten()(x)output_layer=Dense(1)(x) modelextend=Model(input_layer,output_layer) modelextend.summary()
与之前不同的是,我们在输入层之后,紧跟的是一个Conv2D()层,也就是卷积层,之后是Activation(),激活函数层,然后是MaxPooling2D()层,即最大池化层。这些层都是深度学习里最基础的一些概念,要了解的话也需要花一些功夫,这里我们对概念性的内容也不多做讲解啦,欲了解详情可以自行检索~
modelextend.compile(loss='mse',optimizer=Adam(lr=0.001),metrics=['mse']) modelextend.fit(X0,Y0, batch_size=100, epochs=300, validation_data=(X1,Y1))
拟合的方法与之前是完全一样的。这里我们看一下结果,测试集的mse是0.7771,相比于使用线性回归的1.3018是一个不小的提升,看来CNN在这里还是更有优势的嘛。
模型预测:这张早餐多少分
我们来做一个有趣的尝试,当初静静老师读博士的时候,有一天在寝室吃早餐,拍了一张照片,自己也不知道拍的这是啥水平。于是我们读到Python里,让它来告诉我们。
首先给这张照片取名为mypic.jpg,使用Image.open方法打开,然后变形到128×128像素,像素点的数值除以255,再放到我们的model模型里,使用model.predict预测,看一看预测的结果如何。
MyPic=Image.open('mypic.jpg')MyPic=MyPic.resize((IMSIZE,IMSIZE)) MyPic=np.array(MyPic)/255 MyPic=MyPic.reshape((1,IMSIZE,IMSIZE,3)) model.predict(MyPic) modelextend.predict(MyPic)
2.76分!大概是中等稍微偏下的水平,对于自己随手拍的照片能拿到这样的分数还是很满意滴~各位看官们想知道自己拍的美食照片能得多少分吗?