Python社区  »  Python

让Python为你的美食图片打分?朋友圈食物拍照水平大赏 | 精品案例

狗熊会 • 1 周前 • 26 次点击  
点击上方蓝字关注我们


背景介绍
今天,狗熊会将和大家分享一个美食评分案例,下面这张图是用各种各样眼花缭乱的美食图片拼凑而成,不知道各位看官对这些图片的感受怎么样?有没有被其中一些菜品吸引到呢~

如果我们人为地给每一张美食图片打一个分,食材、摆盘、是否有滤镜(划重点)、口味等等,这些因素都会影响到人们打出分数的高低,每个人的审美各有不同,但是对美好的食物总是存在一致性追求的。因为主观的因素,我们对照片的评分不一定是完全相同的,那么能不能让机器来做这件事情,给出一个相对客观的打分呢~
数据来源
这些美食图片收集于一个叫做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 pd
MasterFile=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 np
FileNames=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 Image
IMSIZE=128
X=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 plt
plt.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_split
X0,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, Input
from keras import Model
input_layer=Input([IMSIZE,IMSIZE,3])
x=input_layer
x=Flatten()(x)
x=Dense(1)(x)
output_layer=x
model=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 Adam
model.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,Activation
from keras import Model
input_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分!大概是中等稍微偏下的水平,对于自己随手拍的照片能拿到这样的分数还是很满意滴~各位看官们想知道自己拍的美食照片能得多少分吗?

✦ 案例来源:本案例改编自中国人民大学统计学院2016级本科生统计软件课程小组作业

✦ 小组成员:王蕾 沈雨薇 牛聆宇 韩雨锦 李卜诺

✦ 案例的后期制作:漆岱峰


Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/49278
 
26 次点击  
分享到微博