欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

手把手教你CIFAR数据集可视化

程序员文章站 2022-05-29 09:54:32
...

CIFAR数据集介绍与获取

如同从小到的父母教我们识别每个物体是什么一样,除了看到的画面,父母会在旁边告诉看到的画面是什么,这种学习方式叫做监督学习(与此对应还有无监督学习)。计算机也一样。数据集通常应该至少包含两部分内容,一个是图像,一个是该图像对应的物体是什么。

CIFAR-10是一个流行的小级别(与其他数据集相比,例如ImageNet,无论物体个数、单张图像的尺寸以及图像的数量都相对较小)的图像分类数据集。这个数据集由60000个高宽都为32像素的微小图像组成。每个图像都用10个类别中的一个来标记(飞机、汽车、鸟...)。这60000张图片被分成50000张图片的训练集和10000张图片的测试集。在图1中,可以看到10个类中每一个类的10个随机示例图像:

 

手把手教你CIFAR数据集可视化

图1 CIFAR数据示例

通过访问CIFAR官网 http://www.cs.toronto.edu/~kriz/cifar.html 即可下载该数据集,如图2所示,选择“CIFAR-10 python version”进行下载;Python已经成为人工智能领域应用最广泛的语言,所以,本章节的所有算法实现皆采用Python进行实现。

 

手把手教你CIFAR数据集可视化

图2 CIFAR官网下载部分截图

下载完成后,解压数据包,可以看到有以下文件,如图3所示。手把手教你CIFAR数据集可视化

 

图3 CIAR-10 python version解压后文件

前面讲过,CIAR包含60000张图片,被分成50000张图片的训练集和10000张图片的测试集。

50000张训练集图片分别存于“data_batch_1”、“data_batch_2”、“data_batch_3”、“data_batch_4”、“data_batch_5”中,10000张测试图像存于“test_batch”中。

“batches.meta”则存储了一个字典(请查阅相关资料学习Python的列表、字典和元祖三种常用数据结构/数据类型;在本书中,数据结构数据对象中数据元素之间的关系;也就是字典被认为是数据结构,而实例化的一个字典被认为是数据对象),稍后通过程序将该字典输出。

“readme.html”双击通过浏览器打开是CIFAR的官方网站,里面有CIFAR-10的下载链接和对于CIFAR-10的介绍和说明。

不过有些同学可能会疑惑,在这些文件里面没有看到一张图像,这60000张图像哪里来的?

接下来,通过Python编程将数据集内的部分图像可视化;一方面,捎带讲解Python的语法特点和相关常用模块,另一方面,可以直观看一看该数据集里面的图像。

 

open()函数、pickle模块以及with...as...的使用

首先看一看“batches.meta”存储的字典的内容,这里要用到Python的open()函数、pickle模块以及with...as语法,这里简单解释一下:

想把数据写入在文件中,首先需要open()函数进行打开文件(如果该文件不存在则自动先创建文件),然后通过write()函数进行写入。但是write()函数只支持写入字符串数据类型,而列表、字典直接使用write()写入将不被允许,如程序1所示。

程序1 使用open()以及write()写入数据

a = 'helloVision!'
f = open('E:/testOpen.txt','w')
f.write(a)
f.close()

#以下是错误示例,write()只能写入字符串,其他数据类型如列表、字典等不允许写入
"""
a = [12,34,56]
f = open('E:/testOpen.txt','w')
f.write(a)
f.close()
"""

程序将字符串”helloVision”写入到文件中,其中open()函数的第二个参数’w’表示清空文件内容后写入,如果想不清空文件内容并追加写入则用’a’。

三个双引号能够实现多行注释,并以三个双引号结束注释;如果取消注释,将错误示例进行运行,则报出以下错误,如图4所示:

手把手教你CIFAR数据集可视化

图4 写入列表数据类型报错

 

那么,当我们使用Python想把一些数据——例如字符串、列表、字典等,保存在电脑中(或者从电脑里读出保存在电脑里的Python数据内容),怎么办?

这就要用到pickle模块。该模块有两个常用函数——dump()和load(),简单来说,dump()函数将Python的数据对象通过特殊的形式转换为只有python语言认识的字符串,而load()函数则将转换后的字符串转换回Python数据对象,前者成为序列化,后者成为反序列化。在进行序列化之后,就可以通过write()进行写入。如程序2所示。

程序2 将列表写入文件中

import pickle
b = [12,34,56]
f3 = open('E:/listWrite.txt','wb')
pickle.dump(b,f3)
f3.close()

 

第1行输入pickle模块,这里有点类似于C/C++语言里面的#include;这样就可以在下面使用pickle模块的功能。

在使用pickle模块下的dump()和load()在进行写入读出之前,无论是读数据还是写数据,依然都需要先打开文件,所以需要先使用打开文件的open()函数,需要注意的是这里open()函数的第二个参数为wb,而不是w。’w’是以文本进行写入,而’wb’则是以二进制进行写入;两者有区别。可自行搜索文本与二进制方式打开文件的区别补充相关知识。

写入后,就可以将写入的列表读出来,如程序3所示。

程序3 将存储列表的文件内容读出

import pickle
f3 = open('E:/listWrite.txt','rb')
b = pickle.load(f3)
print(b)
f3.close()

 

注意第二行open()函数的第二个参数是’rb’,以二进制形式打开文件

但是,在打开文件的时候,有可能发生文件读取数据异常,或者打开后忘记关闭文件。在Python中,可以使用try...except...finally....来处理异常;除此之外,with...as...也可以用来处理该问题,with...as...不仅语法更简洁,还可以更好的处理上下文环境产生的异常(具体的with...as...语法原理和使用可查阅相关资料)。程序3则可以写成如下形式。

程序4 使用with...as...打开文件

import pickle
with open('E:/listWrite.txt','rb') as f3:
    b = pickle.load(f3)
    print(b)

显示batches.meta的内容 

 

有了以上的准备工作,就可以在进行CIFAR部分数据可视化之前,先看一看batches.meta文件中的内容了。如程序5所示。

程序5 打印batches.meta的内容

import pickle

def load_file(filename):
    with open(filename, 'rb') as fo:
        data = pickle.load(fo, encoding='latin1')
    return data

data = load_file('E:/cifar/cifar-10-batches-py/batches.meta')
print(data.keys())
print(data.values())

在第3行至第6行,实现了一个名为load_file的函数,该函数通过传入一个存有Python数据对象的文件路径,最终返回该数据对象。第4行通过with...as...以及open()函数将文件打开,open函数的第一个参数比较简单,很容易看出是文件的路径;第二个参数是打开的方式,‘rb’是二进制读取(除此之外还有其他一些打开方式,可自行学习)。打开后,就可以使用pickle的load()函数对文件内容进行读入;最后,返回读取到的内容。

latin1是ISO-8859-1的别名,向下兼容ASCII码,这里将’latin1’换成’ASCII’码也可以。

第8行调用load_file读取存在”E:/cifar/cifar-10-batches-py”文件夹下面的batches.meta文件,并将该文件存储的Python数据对象赋值给data。

因为data是一个字典,字典内包含键和值两部分,keys()和values()两个函数能够返回该字典的键和值。第9行与第10行分别输出data的键与值。如图5下所示:

手把手教你CIFAR数据集可视化

 

图5 batches.meta的内容显示

这里需要注意的是图5中dict_values的['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck'],后面  data_batch_1, data_batch_2, ..., data_batch_5文件中的图片中的目标物体标签为0至9,0-9按顺序对应该列表的物体'airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck’。

 

CIFAR数据集部分图像可视化

这一小节将CIFAR数据集的一部分图像进行可视化,也就是打开数据集,将里面的图像数据读取出来保存为常用的图像格式,如png格式。

这时先看一下CIFAR官网对于 data_batch_1, data_batch_2, ..., data_batch_5文件的介绍。如图6所示。

手把手教你CIFAR数据集可视化

图6 CIFAR官网Dataset layout部分截图

该部分内容提供了unpickle()函数,通过该函数能够对数据集文件进行读取,并返回数据集文件中存储的字典;并对该字典的data和labels元素内容进行了详细说明。

先写一个程序看一看该字典的键值内容,如程序6所示。

程序6 打印data_batch_1的键和值

# 读取文件
def unpickle(file):
    import pickle
    with open(file, 'rb') as fo:
        dict = pickle.load(fo, encoding='bytes')
    return dict

x = unpickle("E:/cifar/cifar-10-batches-py/data_batch_1")
print(x.keys())
print(x.values())

运行以上程序,可以看到该字典的键和值(分别用红框圈出),如图7所示。手把手教你CIFAR数据集可视化

图7 打印data_batch_1的键和值

其中比较重要的是键b'labels'、键b'data',以及对应的值。因为b'data'对应的是图像数据,而b'labels'对应的是图像的标签。这里解释一下b'labels'、b'data'...因为pickle.load()是以bytes形式进行读取的,所以这里的键并非是字符串(字符串使用单引号,而bytes对象则是b’’),而是bytes对象。

那么,我们关心的图像和对应的标签是如何存储的?

首先,一副尺寸为32*32的彩色图像需要多少个数值?32行32列共计32*32个像素,即1024个像素,每个像素需要三个数据来存储红Red、绿Green、蓝Blue对应的数值,所以共需32*32*3=3072。

可以看到图6中对于data的解释——a 10000x3072 numpy array of uint8s。即一个10000行3072列的numpy数组,每个元素为uint8类型。而numpy则是Python中处理数组(也可以认为是线性代数中的矩阵)的强大工具,后面会大量用到。可以联想到,每行代表一副图像,共一万行,即一万张;每行3072个元素,即每幅32*32彩色图像对应的RGB颜色数值。

接下来,终于可以将CIFAR数据集的部分图像进行可视化;如程序7所示,该程序将data_batch_1文件中存储图像的numpy数组的前200行转换为相应的200副图像文件,并分别放在以对应标签命名的文件夹中。

程序7 CIFAR数据集图像可视化

import pickle
import os
from scipy.misc import imsave
import numpy as np

def load_file(filename):
    with open(filename, 'rb') as fo:
        data = pickle.load(fo, encoding='latin1')
    return data

dic = load_file('E:/cifar/cifar-10-batches-py/batches.meta')
labels_item = dic['label_names']

#创建文件夹
for i in range(10):
    if not os.path.exists("E:/images/" + labels_item[i]):
        os.makedirs("E:/images/" + labels_item[i])

def unpickle(file):
    with open(file, 'rb') as fo:
        dict = pickle.load(fo, encoding='bytes')
    return dict

dic = unpickle("E:/cifar/cifar-10-batches-py/data_batch_1")
dict_image_data = dic[b'data']
dict_image_labels = dic[b'labels']

for i in range(200):
    imgs = dict_image_data[i]
    labels = dict_image_labels[i]
    imgs_array = np.reshape(imgs,(3,32,32))
    imgs_array = imgs_array.transpose(1, 2, 0)
    imsave("E:/images/" + labels_item[labels] + "/" + str(i) + '.jpg', imgs_array)

#RGB图像的数据存储顺序
img1 = dict_image_data[0]

print("data_batch1中以单行形式存储的第一个幅RGB图像数据:")
print(img1)

print("将该行转换成3*32*32的数组形式:")
img1_array = np.reshape(img1,(3,32,32))
print(img1_array)

print("将每个像素的RGB值放在一起")
img2_array = img1_array.transpose(1, 2, 0)
print(img2_array)

这个程序需要用到四个模块,分别是pickle、os、numpy以及scipy,其中pickle已经接触过了;os在后面用来在计算机中创建文件夹;numpy前面提起过,用来做数组处理;scipy的模块misc中有imsave()函数,用来将读取到的图像信息保存为常见的图像格式。

代码from scipy.misc import imsave表示从scipy.misc模块中使用imsave()函数,后面就可以直接使用imsave()函数,如果程序比较大,存在命名冲突的可能性增加,则不建议这么写。可以写成import scipy.misc,后面在使用imsave()函数的时候,前面需要加限定:scipy.misc.imsave(),即 scipy.misc模块下的imsave()。

而import numpy as np则表示使用名称np替代名称numpy。

第6至9行前面已经讲过,是为了读取batches.meta而写的函数;11行调用该函数完成读取,并文件内的字典赋值给dic;接下来将该字典的键'label_names'对应的值(该值是一个列表)赋值给列表labels_item,这个值在前面也已经讲过,即:['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck’]。

读取这个值是为了创建10个文件夹,分别为airplane, automobile, bird, cat, deer, dog, frog, horse, ship, truck;后面将图像按照标签分别存放在对应的文件夹里面。

15行至17行实现了10个文件夹的创建。全部程序运行后,可以看到E盘有了一个images文件夹,该文件夹中有十个文件夹,如图8所示。

手把手教你CIFAR数据集可视化

8 文件夹创建结果

19行至22行前面讲过,为了读取data_batches文件实现了unpickle函数。

24行,调用函数并将文件中的字典赋值给dic;25行将dic字典的b’data’对应的值赋值给dict_image_data;26行将b'labels'对应的值赋值给dict_image_labels。这里再次提示:dict_image_data的每一行表示一副图像,dict_image_labels则按照顺序对应dict_image_data的每一行表示图像的标签。

28至33行, dict_image_data的第i行数据赋值给imgs ,而dict_image_labels将第i个标签赋值给labels,这个时候还不能直接将imgs保存为图像,31、32行代码数据进行了整理(稍后讲解如何整理的),以便在33行的imsave()函数进行保存。Imsave()函数第一个参数为保存路径(含该文件的名称),第二个参数为保存的数组变量(因为名称的后缀为jpg,Python自动识别后按照该文件类型的格式进行了保存)。完整程序运行后,可以看到airplane文件夹所保存的图像,如图9所示。

手把手教你CIFAR数据集可视化

图9 airplace文件夹中图像示例

38至47行代码打印显示出了“整理数据”的过程。

36行将第一幅图像的数据复制给了img1;然后打印了data_batch1的第一行数据,即img1如图10所示。手把手教你CIFAR数据集可视化

图5-2-10 data_batch1的第一行数据print结果

这里解释一下这一行的数据,CIFAR的数据按照RGB三色依次存储,也就是说,一幅图像虽然每个像素点分RGB三个值,但是CIFAR的数据一次性存储完R后,再存G,再存B。32*32的彩色图像,存储R值需要32*32=1024个值,也就是img1的前1024个数表示第一幅图像的R值;其后1024个值表示G值;最后1024个数值表示B值。

42行将图像转置为3*32*32的数组形式,如图11所示。

手把手教你CIFAR数据集可视化

图11 CIFAR第一幅图像的3*32*32数组表示

注意图11中红框圈出的三个数值59,62,63,这三个数值分别表示第一个像素点的RGB三个值。但是,图像的保存需要的数组每个像素的RGB值应该是在一起排列的,所以按照第46行代码进行数组的转置。此时,数组的排列满足了图像存储的要求,如图12所示,打印该数组,可以看到第1个像素的RGB依次排列,第2,3...像素也按照该次序进行排列。

手把手教你CIFAR数据集可视化

图12 符合图像保存的数组格式