专注于做有价值的技术原创

0%

kaggle 图像分类竞赛实战(一):数据集下载和清洗

前言

本文集以 Kaggle 网站真实竞赛《dogs-vs-cats-redux-kernels-edition》为主线,讲解如何使用深度学习技术解决图像分类问题。本文作为文集第一篇,讲解图像数据集的下载和清洗。

1. kaggle 命令行

不熟悉 kaggle 命令行接口的读者可参考专栏 《Kaggle 命令行工具查看 AI 最新竞赛和下载数据集》

通过关键词搜索竞赛:

1
2
3
4
5
$ kaggle competitions list -s "dog cat"
ref deadline category reward teamCount userHasEntered
---------------------------------- ------------------- ---------- --------- --------- --------------
dogs-vs-cats 2014-02-01 23:59:00 Playground Swag 215 False
dogs-vs-cats-redux-kernels-edition 2017-03-02 23:59:00 Playground Knowledge 1314 True

下载竞赛数据:

1
2
3
4
$ kaggle competitions download -c dogs-vs-cats-redux-kernels-edition
Downloading dogs-vs-cats-redux-kernels-edition.zip to ~/dogs-vs-cats
100%|███████████████████████████████████████| 814M/814M [01:48<00:00, 7.37MB/s]
100%|███████████████████████████████████████| 814M/814M [01:48<00:00, 7.86MB/s]

查看数据:

1
2
$ ls -l | grep .zip
-rw-r--r-- 1 ken staff 853083283 10 6 10:32 dogs-vs-cats-redux-kernels-edition.zip

解压数据:

1
2
3
4
5
$ unzip dogs-vs-cats-redux-kernels-edition.zip -d datasets
Archive: dogs-vs-cats-redux-kernels-edition.zip
inflating: datasets/train.zip
inflating: datasets/sample_submission.csv
inflating: datasets/test.zip

查看解压数据:

1
2
$ cd datasets && ls
sample_submission.csv test.zip train.zip

2. 数据处理

2.1 查看数据集

2.1.1 解压数据集

1
$ unzip train.zip && unzip test.zip

2.1.2 查看训练集和测试集图片数量

1
2
3
4
5
6
7
# 训练集数量
$ ls train | wc -l
25000

# 测试集数量
$ ls test | wc -l
12500

2.1.3 加载训练集和测试集

1
2
3
4
5
6
7
8
9
10
11
12
13
import os

def load_datasets():
def load(d):
datasets = []
for fn in os.listdir(d):
fp = os.path.join(d, fn)
if os.path.isfile(fp):
datasets.append(fp)
return datasets
return load('datasets/train'), load('datasets/test')

train_datasets, test_datasets = load_datasets()

2.1.4 随机查看数据集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import random
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
%matplotlib inline

def show_img(img, ax=None, title=None):
if ax is not None:
ax.imshow(img)
ax.axis('off')
if title is not None:
ax.set_title(title)
else:
plt.title(title)
plt.imshow(img)
plt.axis('off')

def show_img_by_path(imgpath, **argkw):
show_img(Image.open(imgpath), title=os.path.basename(imgpath), **argkw)

def show_imgsets(imgsets, shape=(2, 5), **argkw):
if not isinstance(imgsets, list):
return

rows, cols = shape
size = rows * cols
imgsets = np.array(imgsets[:size]).reshape(shape)

fig, axs = plt.subplots(rows, cols, **argkw)

for row in range(rows):
for col in range(cols):
show_img_by_path(imgsets[row, col], ax=axs[row, col])

def randsample(imgsets, shape=(2, 5), **argkw):
imgsets = random.choices(imgsets, k=shape[0]*shape[1])

show_imgsets(imgsets, shape=shape, **argkw)
1
randsample(train_datasets, shape=(10, 10), figsize=(16, 16))

2.2 训练集图片清洗

2.2.1 搭建模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from keras.preprocessing import image
from keras.applications.resnet50 import preprocess_input
from keras.applications.resnet50 import ResNet50

class ImgModel:
def __init__(self, mc):
self.model = mc(weights='imagenet')
def _totensor(self, img_path):
img = image.load_img(img_path, target_size=(224, 224))
x = image.img_to_array(img)
tensor = np.expand_dims(x, axis=0)
return preprocess_input(tensor)
def predict(self, img_path):
tensor = self._totensor(img_path)
predict_label = self.model.predict(tensor)
return np.argmax(predict_label)

model = ImgModel(ResNet50)

2.2.2 找出模型无法识别出是狗和猫的图片

根据 ImageNet 图像标签 ,标签 151-268 是狗,标签 281-285 是猫。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def is_dog_or_cat(img_path):
prediction = model.predict(img_path)
if (prediction >= 151) and (prediction <= 268):
return True
if (prediction >= 281) and (prediction <= 285):
return True
return False

def find_unknown(datasets):
unknown = list()
for img_path in datasets:
if not is_dog_or_cat(img_path):
unknown.append(img_path)
return unknown
1
train_unknown = find_unknown(train_datasets)
1
randsample(train_unknown, shape=(10, 10), figsize=(16, 16))

经过模型的初步筛选,在原有 25,000 张图片的训练集中,有 3,430 张图片模型未识别出是狗或者猫。但通过对这些未识别出来的图片随机抽样观察,发现存在很多明显是猫的图,以及少量是狗的图片。说明我们前面的设定的标签范围并未涵盖所有猫狗的分类。为了进一步找出这些标签,我们不妨看看这些未识别的出来的标签是否真的是我们需要的。

1
2
3
def find_unknown_labels(unknown):
return [model.predict(img_path) for img_path in unknown]
unknown_labels = find_unknown_labels(train_unknown)

为了能直观查看图片和标签,我们需要对前文查看图片的函数 show_img_by_path() 稍作修改,以便在查看图片的同时,在标题栏显示标签。

1
2
def show_img_by_path2(imgpath, **argkw):
show_img(Image.open(imgpath), **argkw)

编写一个生成器,实现依次查看未识别出来的图像标签。

1
2
3
4
5
6
7
8
9
def unknown_iter():
for img_path, label in zip(train_unknown, unknown_labels):
yield img_path, label

unknown = unknown_iter()

def show_unknown(unknown, **argkw):
img_path, label = next(unknown)
show_img_by_path2(img_path, title=label, **argkw)

从第一张未识别图片开始,每调用一次函数 show_unknown(),依次显示下一张图片,直到看完所有图片。

1
show_unknown(unknown)

如果觉得一次看一张图片效率太低,可以再编写一个函数实现一次看多张图片。注意,为了从第一张开始看,我们需要重新初始化 unknown 变量。

1
2
3
4
5
6
7
8
def show_unknowns(unknown, shape=(10, 10), **argkw):
rows, cols = shape
fig, axs = plt.subplots(rows, cols, **argkw)
for row in range(rows):
for col in range(cols):
show_unknown(unknown, ax=axs[row, col])

unknown = unknown_iter()

每调用一次函数 show_unknowns() 顺序显示下一百张图片。

1
show_unknowns(unknown, figsize=(16, 16))

如果对数字标签不满意,想看文本分类名称(只有英文)。可以参考专栏 《Keras 手动搭建 VGG 卷积神经网络识别 ImageNet 1000 种常见分类》第 3.5 小节 。这里直接使用相关代码获取数字标签到文本标签的映射字典。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def get_imagenet_classes():
import pickle

with open('imagenet1000_clsid_to_human.pkl', 'rb') as f:
classes = pickle.load(f)

return classes

imagenet_classes = get_imagenet_classes()

def unknown_iter2():
labels = [str(i)+'-'+imagenet_classes[i] for i in unknown_labels]
for img_path, label in zip(train_unknown, labels):
yield img_path, label

unknown2 = unknown_iter2()

一次显示 20 张图片。

1
show_unknowns(unknown2, shape=(5, 4), figsize=(16, 16))

2.2.3 改进筛选模型

通过上一小节的观察,我们发现,简单对图片预测单一分类(概率最高的),会因为图片中存在其他物体干扰,而导致无法识别出猫和狗。因此,我们需要改进一一下模型,在概率最高的前10个分类中,查看是否有猫或者狗。

ImgModel 基本一样,仅仅在 predict() 方法返回时,不是返回概率最大的标签,而是按概率倒序排序,即概率最大的排在前面,返回标签列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ImgModel2:
def __init__(self, mc):
self.model = mc(weights='imagenet')
def _totensor(self, img_path):
img = image.load_img(img_path, target_size=(224, 224))
x = image.img_to_array(img)
tensor = np.expand_dims(x, axis=0)
return preprocess_input(tensor)
def predict(self, img_path):
tensor = self._totensor(img_path)
predict_labels = self.model.predict(tensor)
sort_labels = sorted(list(zip(range(1000), predict_labels.flatten())), key=lambda x:x[1], reverse=True)
return [label[0] for label in sort_labels]

model2 = ImgModel2(ResNet50)

改进了模型,同时也要对应调整一下函数 is_dog_or_cat2() 和函数 find_unknown2()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def is_dog_or_cat2(img_path):
predictions = model2.predict(img_path)[:10]
for prediction in predictions:
if (prediction >= 151) and (prediction <= 268):
return True
if (prediction >= 281) and (prediction <= 285):
return True
return False

def find_unknown2(datasets):
unknown = list()
for img_path in datasets:
if not is_dog_or_cat2(img_path):
unknown.append(img_path)
return unknown

在前文未识别出猫狗的 3,430 张图片中,使用改进的模型重新筛选不能识别的图片。

1
train_unknown2 = find_unknown2(train_unknown)
1
len(train_unknown2)
281

经过第二次改进模型后的筛选,由原来不能识别 3,430 张图片降到 281 张。再来看看这剩下的 281 张图片到底是“何方神圣”,为何如此难以识别。

1
randsample(train_unknown2, shape=(10, 10), figsize=(16, 16))

Fuck ! (我想你和我一样,已经忍不住破口而出了…)不难看出,这些图片确实狗糟糕的。简直无 fuck 说,我有100个理由把这些图片排除在训练集之外。

3. 将不能识别出来的图片移出训练集

1
import shutil
1
2
3
4
5
6
7
def move_train_unknown(train_unknown):
train_unknown_dir = 'datasets/train_unknown'
if not os.path.isdir(train_unknown_dir):
os.makedirs(train_unknown_dir)
for img_path in train_unknown:
shutil.move(img_path, train_unknown_dir)
print('{} pictures have been moved.'.format(len(train_unknown)))
1
move_train_unknown(train_unknown2)
281 pictures have been moved.

重新加载训练集

1
2
3
4
5
train_datasets = load_datasets()[0]

print('{} images left after cleaning.'.format(len(train_datasets)))

randsample(train_datasets, shape=(10, 10), figsize=(16, 16))
24719 images left after cleaning.

可以看到,经过清洗后的训练集,图片“干净”了不少,这为后续模型的训练提供了良好的开始。

青笔 wechat
我是一条小青蛇 我有很多小秘密