12猫分类学习笔记一(配置篇)

0 背景介绍

飞桨PaddlePaddle大赛:猫的十二种分类问题。

本场比赛要求参赛选手对十二种猫进行分类,属于CV方向经典的图像分类任务。图像分类任务作为其他图像任务的基石,可以让大家更快上手计算机视觉。

结合深度学习框架Pytorch和AI平台Paddle,使用分类模型,对这十二种猫进行分类。从第二次实验课开始,全部实验课用来完成这个比赛。

比赛地址:

旧地址

2023新地址

官网有数据集和对应的baseline视频和博客,都可以参考一下。

1 环境安装

有同学问我,为什么不用飞桨的那个BML平台呀?

我的评价是:休想从我身上撸一毛钱,而且我用个conda虚拟环境+pycharm自己用的熟的工具多香啊~。要不是老师要求用那个什么AI平台Paddle,我都懒得搞它。

我的配置如下:

anaconda

pycharm

pytouch 1.12.0

Paddle

cuda+cudnn:11.6,8.0.0

pyecharts pip install pyecharts

pandas pip install pandas

1.1 conda 虚拟环境的建立与激活

可以参考我自己的博客,有基本的操作。

CJH’s blog - CJH’s blog (cjh0220.github.io)

1
conda create -n cat python=3.8
1
conda activate cat

这样就进入了这个虚拟环境并完成激活。

1.2* CUDA + cudnn(windows)

请注意,请注意,该内容非常的难受,要跟紧了!!!

CSDN博客

1.3 飞桨安装

官网

官网很详细,选择自己对应的内容在即可。尽量在自己的虚拟环境下。

结果如下。

ppNBhWR.md.png

1.4 Pytorch 安装

2019参考的老教程

pytorch历史版本

注意按着自己的CUDA版本下载,它官方给的代码是自动帮你建立一个虚拟环境。但是如果你已经在虚拟环境下的命令行里,只需要去除尾端的 -nvidia

可能会出现命令行报错,出现一堆大于号等于号,可能就是你自己的python版本不对应,只需要换一个pytorch版本即可。

1.5 pycharm 环境配置

在对应的文件夹中,使用pycharm以project方式打开文件夹。

点击左上角file或者crtl+alt+s。进入settings界面。

ppNB5S1.png

点Project 并点Project Interpreter,然后在左边下滑栏中 show all。+号添加Interpreter。选择conda Environment 然后Existing environment。找到自己的虚拟环境python文件即可。

ppNBIQx.png

ppNBTOK.md.png

建议点亮 Make available to all project.会使你的该项目所有python文件都按照这个环境来。

Jupyter notebook 添加conda虚拟环境

解决方法:

  • 首先激活之前创建好的虚拟环境:conda activate 环境名称
  • 安装ipykernel:conda install ipykernel
  • 将环境写入notebook的kernel中:
    python -m ipykernel install --user --name 环境名称 --display-name “Python (环境名称)”
  • 打开notebook服务器:jupyter notebook

博客

2 image库学习

python之PIL库(Image模块) - 噼里巴啦 - 博客园 (cnblogs.com)

https://blog.csdn.net/weixin_42475060/article/details/126175837

3 数据集的构建(文件夹)

3.1 下载文件

下载链接

在代码项目cat文件夹中,建立一个data文件夹,将下载内容解压至data文件夹内。

ppvDZyd.md.png

3.2 Data_processing.ipynb 代码

导包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 路径操作
import os
# 文件操作
import shutil
# torch相关
import torch
import torch.nn as nn
import torchvision
from torchvision import transforms
from torch.autograd import Variable
# 日志
import logging
import random
import numpy as np
import matplotlib.pyplot as plt # 绘图展示工具
# 计算机视觉cv2
import cv2
import math
# 图像处理
from PIL import Image, ImageEnhance

观察图片结构

1
2
3
4
5
6
7
8
# 使用Python的Pillow库来获取图片的大小信息,
folder_path = 'data/cat_12_train' # 文件夹路径
for file_name in os.listdir(folder_path):
if file_name.endswith('.jpg'):
file_path = os.path.join(folder_path, file_name)
with Image.open(file_path) as img:
width, height = img.size
print(f'{file_name}: {width} x {height} pixels')

预期结果:

1
2
3
4
5
02WsuJX3pImwcyEKjFvkQqDSAaV8HoG5.jpg: 333 x 500 pixels
03j9aZ5Gkq7vMDRnVQFwfbrHx8TEeoch.jpg: 500 x 402 pixels
04Iv3QNKtu2DAfRTgs9XZwBMb1Cm7l6P.jpg: 500 x 327 pixels
07FlA9MgYrTXDSaoQZuOzf6NWLyI3mR4.jpg: 200 x 200 pixels
......

S1:图片大小不一,需要进行图像处理。

Q1:如何确定该将图像缩放/裁剪,处理到 几x几?

A1:缩放或裁剪?

图像缩放的基本原理就是根据原图像的像素值通过一定的规则计算得到目标图像的像素值。
在图像缩放的过程中,最重要的就是确定下面两个问题:

  • 一是计算目的图像中的每一个像素值时,应该选取原图像中哪些像素值;
  • 二是这些选取出的像素值在计算目的像素时权重如何确定。

数据增广(Data Augmentation)指的是在训练模型时,对原始数据进行一定的变换,以产生新的训练样本,从而扩大数据集的规模,有效地提高模型的泛化能力和鲁棒性。数据增广是一种常用的预处理方法,可以用于图像分类、目标检测、语音识别等任务。
常见的数据增广操作包括旋转、平移、缩放、裁剪、翻转、加噪声等。这些操作可以使样本具有不变性,提高模型的泛化能力。例如,对于图像分类任务,可以通过随机旋转、水平翻转、随机裁剪等操作,产生多个不同的图像样本,从而提高模型的鲁棒性和准确率。
需要注意的是,数据增广应该根据具体任务来选择合适的操作,不能过度增广。过度增广可能会导致数据样本的多样性过大,使得模型难以从中学到有用的特征,反而影响模型的泛化性能。

这里面操作方法很多,可以再下一篇章具体讲解并优化,pytorch里面就是transform 这一个东东。可以调用来,不用自己手写。

观察是否出现样本不平衡问题

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
## 统计训练集各类猫的数目,防止样本不平衡问题。
import matplotlib.pyplot as plt
import pandas as pd

with open("data/train_list.txt", "r") as f:
labels = f.readlines()
labels = [int(i.split()[-1]) for i in labels]


counts = pd.Series(labels).value_counts().sort_index().to_list()
names = [str(i) for i in list(range(12))]
data = list(zip(counts, names))
source = [list(i) for i in data]
source # 每个都是180个数据,不存在样本不平衡问题
'''
[[180, '0'],
[180, '1'],
[180, '2'],
[180, '3'],
[180, '4'],
[180, '5'],
[180, '6'],
[180, '7'],
[180, '8'],
[180, '9'],
[180, '10'],
[180, '11']]
'''

数据集的构建

1
2
3
4
5
6
7
# 导包
import torchvision.transforms as transforms
from torchvision.datasets import ImageFolder
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import random
import os
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# pytorch的手册及源码有讲解。
torchvision.datasets.ImageFolder()要求根目录下建立分类标签子文件夹,每个子文件夹下归档对应标签的图片,因此需要给每个标签建立文件夹,并且遍历样本,将每个样本复制到对应的文件夹中。
"""A generic data loader where the images are arranged in this way by default: ::

root/dog/xxx.png
root/dog/xxy.png
root/dog/[...]/xxz.png

root/cat/123.png
root/cat/nsdf3.png
root/cat/[...]/asd932_.png

This class inherits from :class:`~torchvision.datasets.DatasetFolder` so
the same methods can be overridden to customize the dataset."""

生成文件夹,使用ImageFolder来构建数据库

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# 转换文件夹格式
train_ratio = 0.9 # 训练集占0.9,验证集占0.1


folder_path = 'data/cat_12_train' # 原文件夹路径
train_list_path = 'data/train_list.txt' # 分割集txt路径

train_paths, train_labels = [], []
train_paths_new = []
# 读取分割集,并进行分割
with open(train_list_path, 'r') as f:
lines = f.readlines()
for line in lines:
train_paths.append(line.split(' ')[0])
train_paths_new.append(line.split(' ')[0].split('/')[1])
label = line.split(' ')[1]
train_labels.append(int(line.split(' ')[1]))

# 先新建一个文件夹,防止数据错乱
newfolder_name = 'cat_12_train_new' # 新文件夹名称
newfolder_path = 'data/' # 新文件夹路径
os.makedirs(os.path.join(newfolder_path, newfolder_name), exist_ok=True)

# 一种猫类建立一个文件夹
train_labels_new = list(set(train_labels))
train_labels_new.sort(key=train_labels.index)
for i in train_labels_new:
newfolder_name = str(i)
newfolder_path = 'data/cat_12_train_new/' # 新文件夹路径
os.makedirs(os.path.join(newfolder_path, newfolder_name), exist_ok=True)

# 先新建一个文件夹,防止数据错乱
newfolder_name = 'cat_12_val_new' # 新文件夹名称
newfolder_path = 'data/' # 新文件夹路径
os.makedirs(os.path.join(newfolder_path, newfolder_name), exist_ok=True)

# 一种猫类建立一个文件夹
train_labels_new = list(set(train_labels))
train_labels_new.sort(key=train_labels.index)
for i in train_labels_new:
newfolder_name = str(i)
newfolder_path = 'data/cat_12_val_new/' # 新文件夹路径
os.makedirs(os.path.join(newfolder_path, newfolder_name), exist_ok=True)

# 将对应猫片复制入对应猫文件夹
for i in range(0,len(train_paths)):
if random.uniform(0, 1) < train_ratio:
src_img_path = 'data/' + str(train_paths[i]) # 源图片路径
cat_type = str(train_labels[i])
dst_folder_path = "data/cat_12_train_new/" + cat_type #目标文件路径
img_name = os.path.basename(src_img_path) # 获取图片文件名
dst_path = os.path.join(dst_folder_path, img_name) # 构造目标文件路径
shutil.copy(src_img_path, dst_path) # 复制
else:
src_img_path = 'data/' + str(train_paths[i]) # 源图片路径
cat_type = str(train_labels[i])
dst_folder_path = "data/cat_12_val_new/" + cat_type #目标文件路径
img_name = os.path.basename(src_img_path) # 获取图片文件名
dst_path = os.path.join(dst_folder_path, img_name) # 构造目标文件路径
shutil.copy(src_img_path, dst_path) # 复制

请注意请注意请注意,听说这个分类如果你以数字去分类,且使用了ImageFolder。会有奇怪的bug。我这里参考了网上的博客进行了分类的名字调整。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import os
path = 'data\cat_12_val_new'
file_list = os.listdir(path)

for file in file_list:
# 补0 4表示补0后名字共4位 针对imagnet-1000足以
filename = file.zfill(4)
# print(filename)
new_name = ''.join(filename)
os.rename(path + '\\' + file, path + '\\' + new_name)
path = 'data\cat_12_train_new'
file_list = os.listdir(path)

for file in file_list:
# 补0 4表示补0后名字共4位 针对imagnet-1000足以
filename = file.zfill(4)
# print(filename)
new_name = ''.join(filename)
os.rename(path + '\\' + file, path + '\\' + new_name)

ppzgBpq.md.png

数据观察

代码续着Data_processing.ipynb里头

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
# 定义transform
transform = transforms.Compose([
transforms.ColorJitter(brightness=0.05, contrast=0.05, saturation=0.05, hue=0.05),
# transforms.ColorJitter 改变图像的属性:亮度(brightness)、对比度(contrast)、饱和度(saturation)和色调(hue)。
# 数值表示上下浮动的百分比。比如:0.05,原亮度100 ->(95,105)范围内。
transforms.Resize((256, 256)), # 缩放到指定大小
transforms.CenterCrop(224), # 中心随机裁剪
transforms.RandomHorizontalFlip(p=0.5), # 0.5 概率水平翻转
transforms.RandomVerticalFlip(p=0.5), # 0.5 概率垂直翻转
transforms.ToTensor(), # 转换为张量
transforms.Normalize(mean=0, std=1), # 归一化
])

# 加载数据集
folder_path = 'data\cat_12_train_new'# 文件夹路径
dataset = ImageFolder(folder_path, transform=transform)
print(dataset)
dataloader = DataLoader(dataset, batch_size=1, shuffle=True)
# 随机展示3张图
fig = plt.figure(figsize=(16, 16))
for i, (img, label) in enumerate(dataloader):
if i >= 3:
break
img = img.squeeze().numpy().transpose((1, 2, 0)) # 将张量转换为图像
ax = fig.add_subplot(1, 3, i+1)
ax.imshow(img)
ax.set_title(f'Label: {dataset.classes[label.item()]}')
plt.show()

ppvsWRO.md.png

看起来猫模猫样的,凑合着先

当然后续的训练代码和预测代码,可能会对transform有所改变,只要保证维度一样其实问题不大。