Clasificación del zodíaco chino
Requisitos de la tarea
Encontrar un algoritmo óptimo que permita a la máquina distinguir fotos de cada animal del zodíaco es una tarea de clasificación basada en imágenes .
Clasificación de imágenes
Idea de realización
Principios de clasificación de imágenes
preparación de datos
Descomprime el conjunto de datos
Cargamos el conjunto de datos obtenido en línea al conjunto de datos de aistudio en un paquete comprimido y lo cargamos en nuestro proyecto. Antes de usar, realizamos una descompresión del paquete de compresión del conjunto de datos (conjunto de datos del zodíaco chino
)
!unzip -q -o <压缩包路径>
Anotación de datos
Estructura del conjunto de datos:
.
├── test
│ ├── dog
│ ├── dragon
│ ├── goat
│ ├── horse
│ ├── monkey
│ ├── ox
│ ├── pig
│ ├── rabbit
│ ├── ratt
│ ├── rooster
│ ├── snake
│ └── tiger
├── train
│ ├── dog
│ ├── dragon
│ ├── goat
│ ├── horse
│ ├── monkey
│ ├── ox
│ ├── pig
│ ├── rabbit
│ ├── ratt
│ ├── rooster
│ ├── snake
│ └── tiger
└── valid
├── dog
├── dragon
├── goat
├── horse
├── monkey
├── ox
├── pig
├── rabbit
├── ratt
├── rooster
├── snake
└── tiger
El conjunto de datos se divide en tres carpetas, entrenamiento, válido y prueba.Cada carpeta contiene 12 carpetas de clasificación y cada carpeta de clasificación contiene imágenes de muestra específicas. Realizamos un proceso de anotación en estas muestras y finalmente generamos train.txt/valid.txt/test.txt
tres archivos de anotación de datos.
import io
import os
from PIL import Image
from config import get
# 数据集根目录
DATA_ROOT = 'signs'
# 标签List
LABEL_MAP = get('LABEL_MAP')
# 标注生成函数
def generate_annotation(mode):
# 建立标注文件
with open('{}/{}.txt'.format(DATA_ROOT, mode), 'w') as f:
# 对应每个用途的数据文件夹,train/valid/test
train_dir = '{}/{}'.format(DATA_ROOT, mode)
# 遍历文件夹,获取里面的分类文件夹
for path in os.listdir(train_dir):
# 标签对应的数字索引,实际标注的时候直接使用数字索引
label_index = LABEL_MAP.index(path)
# 图像样本所在的路径
image_path = '{}/{}'.format(train_dir, path)
# 遍历所有图像
for image in os.listdir(image_path):
# 图像完整路径和名称
image_file = '{}/{}'.format(image_path, image)
try:
# 验证图片格式是否ok
with open(image_file, 'rb') as f_img:
image = Image.open(io.BytesIO(f_img.read()))
image.load()
if image.mode == 'RGB':
f.write('{}\t{}\n'.format(image_file, label_index))
except:
continue
generate_annotation('train') # 生成训练集标注文件
generate_annotation('valid') # 生成验证集标注文件
generate_annotation('test') # 生成测试集标注文件
Definición de conjunto de datos
A continuación, usamos el archivo marcado para definir la clase del conjunto de datos para facilitar el entrenamiento posterior del modelo.
import paddle
import numpy as np
from config import get
# 导入数据集的定义实现
from dataset import ZodiacDataset
# 实例化数据集类
train_dataset = ZodiacDataset(mode='train')
valid_dataset = ZodiacDataset(mode='valid')
print('训练数据集:{}张;验证数据集:{}张'.format(len(train_dataset), len(valid_dataset)))
Importar dataset.py
archivo de conjunto de datos
import paddle
import paddle.vision.transforms as T
import numpy as np
from config import get
from PIL import Image
__all__ = ['ZodiacDataset']
# 定义图像的大小
image_shape = get('image_shape')
IMAGE_SIZE = (image_shape[1], image_shape[2])
class ZodiacDataset(paddle.io.Dataset):
"""
十二生肖数据集类的定义
"""
def __init__(self, mode='train'):
"""
初始化函数
"""
assert mode in ['train', 'test', 'valid'], 'mode is one of train, test, valid.'
self.data = []
with open('signs/{}.txt'.format(mode)) as f:
for line in f.readlines():
info = line.strip().split('\t')
if len(info) > 0:
self.data.append([info[0].strip(), info[1].strip()])
if mode == 'train':
self.transforms = T.Compose([
T.RandomResizedCrop(IMAGE_SIZE), # 随机裁剪大小
T.RandomHorizontalFlip(0.5), # 随机水平翻转
T.ToTensor(), # 数据的格式转换和标准化 HWC => CHW
T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) # 图像归一化
])
else:
self.transforms = T.Compose([
T.Resize(256), # 图像大小修改
T.RandomCrop(IMAGE_SIZE), # 随机裁剪
T.ToTensor(), # 数据的格式转换和标准化 HWC => CHW
T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) # 图像归一化
])
def __getitem__(self, index):
"""
根据索引获取单个样本
"""
image_file, label = self.data[index]
image = Image.open(image_file)
if image.mode != 'RGB':
image = image.convert('RGB')
image = self.transforms(image)
return image, np.array(label, dtype='int64')
def __len__(self):
"""
获取样本总数
"""
return len(self.data)
config.py
Elemento de configuración
__all__ = ['CONFIG', 'get']
CONFIG = {
'model_save_dir': "./output/zodiac",
'num_classes': 12,
'total_images': 7096,
'epochs': 20,
'batch_size': 32,
'image_shape': [3, 224, 224],
'LEARNING_RATE': {
'params': {
'lr': 0.00375
}
},
'OPTIMIZER': {
'params': {
'momentum': 0.9
},
'regularizer': {
'function': 'L2',
'factor': 0.000001
}
},
'LABEL_MAP': [
"ratt",
"ox",
"tiger",
"rabbit",
"dragon",
"snake",
"horse",
"goat",
"monkey",
"rooster",
"dog",
"pig",
]
}
def get(full_path):
for id, name in enumerate(full_path.split('.')):
if id == 0:
config = CONFIG
config = config[name]
return config
Modelo de desarrollo
La red ResNet50 se utiliza para construir el modelo. La estructura del modelo es más complicada, pero la API de alto nivel de PaddlePaddle tiene una paddle.vision.models.resnet50
interfaz incorporada , que nos permite generar un modelo con una línea de código, para que podamos experimentar el efecto más rápido.
La siguiente figura muestra la estructura de ResNet50: la configuración de los parámetros
paddle.vision.models.resnet50
internos pretrained
está True
diseñada para cargar los pesos previos al entrenamiento en el conjunto de datos de imagenet , es decir, entrenar iterativamente nuestro propio modelo basado en los parámetros que han sido bien entrenados por predecesores.
network = paddle.vision.models.resnet50(num_classes=get('num_classes'), pretrained=True)
model = paddle.Model(network)
model.summary((-1, ) + tuple(get('image_shape')))
Optimización del entrenamiento de modelos
EPOCHS = get('epochs')
BATCH_SIZE = get('batch_size')
def create_optim(parameters):
step_each_epoch = get('total_images') // get('batch_size')
lr = paddle.optimizer.lr.CosineAnnealingDecay(learning_rate=get('LEARNING_RATE.params.lr'),
T_max=step_each_epoch * EPOCHS)
return paddle.optimizer.Momentum(learning_rate=lr,
parameters=parameters,
weight_decay=paddle.regularizer.L2Decay(get('OPTIMIZER.regularizer.factor')))
# 模型训练配置
model.prepare(create_optim(network.parameters()), # 优化器
paddle.nn.CrossEntropyLoss(), # 损失函数
paddle.metric.Accuracy(topk=(1, 5))) # 评估指标
# 训练可视化VisualDL可视化工具的回调函数
visualdl = paddle.callbacks.VisualDL(log_dir='visuald_log')
#启动模型全流程训练
model.fit(train_dataset, # 训练数据集
valid_dataset, # 评估数据集
epochs=EPOCHS, # 总的训练轮次
batch_size=BATCH_SIZE, # 批次样本的样本量大小
shuffle=True, # 是否打乱样本集
verbose=1, # 日志展示格式
save_dir='./chk_points/', # 分阶段的训练模型存储路径
callbacks=[visualdl]) # 回调函数
Visualización VisualDL
Evaluación del modelo
Predicción por lotes
predict_dataset = ZodiacDataset(mode='test')
print('测试数据集样本量:{}'.format(len(predict_dataset)))
from paddle.static import InputSpec
# 网络结构
network = paddle.vision.models.resnet50(num_classes=get('num_classes'))
# 模型封装
model_2 = paddle.Model(network, inputs=[InputSpec(shape=[-1] + get('image_shape'), dtype='float32', name='image')])
# 模型文件加载
model_2.load(get('model_save_dir'))
# 模型配置
model_2.prepare()
# 执行预测
result = model_2.predict(predict_dataset)
# 样本映射
LABEL_MAP = get('LABEL_MAP')
# 随机取样本展示
indexs = [2, 38, 56, 92, 100, 303]
for idx in indexs:
predict_label = np.argmax(result[0][idx])
real_label = predict_dataset[idx][1]
print('样本ID:{}, 真实标签:{}, 预测值:{}'.format(idx, LABEL_MAP[real_label], LABEL_MAP[predict_label]))