Consejos para aumentar tu puntuación en el concurso de clasificación de imágenes
I. Introducción
Antes de leer este artículo, lea: Línea de base de la competencia de clasificación de imágenes: tomando como ejemplo el desafío de clasificación de imágenes de tiempo y frecuencia del control de voz de hardware inteligente . Este artículo presenta los antecedentes del evento y el formato del conjunto de datos, y proporciona una línea de base basada en paletas voladoras. Todas las operaciones en este artículo se realizan sobre la base de la línea de base. A continuación solo publicaré los procedimientos clave para estos consejos para mejorar las puntuaciones. , los específicos La implementación en la capacitación debe ser explorada por todos.
Mi puntuación actual es 0,94203 , ocupando el sexto lugar a largo plazo.
2. Consejos para mejorar puntos
2.1 Segmentación de datos
En la línea de base, mezclamos los datos originales y tomamos los últimos doscientos datos como conjunto de validación. Dado que el número de muestras de cada tipo en los datos originales es básicamente el mismo, este método generará grandes diferencias en las muestras de cada tipo en el conjunto de entrenamiento, lo que hará que el modelo esté "sesgado"; al mismo tiempo, el Las diferencias en las muestras de cada tipo en el conjunto de verificación también serán grandes, lo que provocará una pérdida de verificación y la acc no puede reflejar correctamente el rendimiento del modelo. Por lo tanto, debemos tomar la misma cantidad de muestras de los datos originales que del conjunto de validación.
#每一类抽取十张作为验证集
val_df = pd.DataFrame()
for i in range(24):
val_df = val_df.append(train_df[train_df['label']==i][-10:])
train_df = train_df.drop(labels=train_df[train_df['label']==i][-10:].index)
val_df = val_df.reset_index(drop=True)
train_df = train_df.reset_index(drop=True)
2.2 Mejora de datos
Cuando los datos originales son insuficientes, el aumento de datos es una forma muy eficaz de mejorar la diversidad de datos. En esta competencia trabajé principalmente en este aspecto.
paddle.vision.tranforms
Proporciona muchos métodos comunes de mejora de datos, incluido recorte aleatorio, volteo aleatorio, rotación aleatoria, normalización, etc. El método de uso también es muy simple: simplemente llame a la API directamente. Pero además, hay muchos métodos de mejora de datos muy útiles que no proporcionan API. Aquí proporciono tres métodos efectivos de mejora de datos que utilizo: borrado aleatorio, mejora de clases mixtas y recorte y mezcla .
2.2.1 Borrado aleatorio
Durante el entrenamiento, se selecciona aleatoriamente una región rectangular de la imagen y sus píxeles se borran utilizando valores aleatorios. Generar imágenes de entrenamiento con niveles de oclusión reducirá el riesgo de sobreajuste y hará que el modelo sea resistente a las oclusiones.
# 随机擦除
def random_erase(img,n_holes,length,rate): #输入img为PIL图片格式的图片
if np.random.rand(1)[0]<rate:
img = np.array(img)
h = img.shape[0] #图片的高
w = img.shape[1] #图片的宽
n_holes = np.random.randint(n_holes)
mask = np.ones((h, w), np.float32) #32*32w*h的全1矩阵
for n in range(n_holes): #n_holes=2,length=4 选择2个区域;每个区域的边长为4
y = np.random.randint(h) #0~31随机选择一个数 y=4
x = np.random.randint(w) #0~31随机选择一个数 x=24
y1 = np.clip(y - length // 2, 0, h) #2,0,32 ->2
y2 = np.clip(y + length // 2, 0, h) #6,0,32 ->6
x1 = np.clip(x - length // 2, 0, w) #24-2,0,32 ->22
x2 = np.clip(x + length // 2, 0, w) #24+2,0,32 ->26
mask[y1: y2, x1: x2] = 0. #将这一小块区域去除
img[:,:,0] = img[:,:,0] * mask
img[:,:,1] = img[:,:,1] * mask
img[:,:,2] = img[:,:,2] * mask
return Image.fromarray(img)
else:
return img
Parámetros de entrada:
- img : Imagen en formato PIL;
- n_holes : el número máximo de cuadrados, es decir, el número es un valor aleatorio de 0 a n_holes;
- longitud : la longitud del lado del cuadrado;
- tasa : probabilidad de borrado aleatorio;
Parámetros de salida:
- Imágenes en formato PIL después de un borrado aleatorio
A continuación se muestra una imagen para mostrar el efecto:
img = Image.open(train_df['path'].values[0]).convert('RGB')
img2 = random_erase(img,100,10,0.2)
2.2.2 Mejora mixta (Mixup)
Mezclar las imágenes y etiquetas de las dos muestras en proporción expande la distribución de la muestra y hace que el modelo entrenado sea más sólido.
def random_mixup(img ,label, mixup_img, mixup_label):#输入img和mixup为IMG格式的图片,label和mixup_label为int类型
img = np.array(img)
mixup_img = np.array(mixup_img)
label_onehot = np.zeros(24)
label_onehot[label] = 1
mixup_label_onehot = np.zeros(24)
mixup_label_onehot[mixup_label] = 1
alpha = 1
lam = np.random.beta(alpha,alpha) #混合比例
img_new = lam*img + (1-lam)*mixup_img
label_new = lam*label_onehot + (1-lam)*mixup_label_onehot
return Image.fromarray(np.uint8(img_new)), paddle.to_tensor(np.float32(label_new))
Parámetros de entrada:
- img & mixup_img : Dos imágenes en formato PIL para mezclar;
- label & mixup_label : Las etiquetas correspondientes a las dos imágenes, en formato int
Parámetros de salida:
- Imágenes mixtas en formato PIL.
- Etiqueta de tipo tensor mixto, codificada como one_hot
A continuación se muestran dos imágenes para mostrar el efecto:
img1 = Image.open(train_df['path'].values[0]).convert('RGB')
label1 = train_df['label'].values[0]
img2 = Image.open(train_df['path'].values[1]).convert('RGB')
label2 = train_df['label'].values[1]
img_new, label_new = random_mixup(img1, label1, img2, label2)
⭐Cabe señalar que para usar la confusión, la etiqueta de datos debe tener la codificación one_hot y la función de pérdida debe configurarse en criterion = nn.CrossEntropyLoss(soft_label=True)
, lo que indica el uso de la codificación one_hot.
2.2.3 Mezcla cortada
Una determinada parte de una imagen se recorta y se superpone a otra imagen como una nueva imagen de entrada y se coloca en la red para entrenamiento. Las etiquetas se ponderan y suman de acuerdo con la proporción de área de los dos tipos de elementos en la imagen.
def rand_bbox(size, lam):
if len(size) == 4:
W = size[2]
H = size[3]
elif len(size) == 3 or len(size) == 2:
W = size[0]
H = size[1]
else:
raise Exception
cut_rat = np.sqrt(1. - lam)
cut_w = np.int(W * cut_rat)
cut_h = np.int(H * cut_rat)
# uniform
cx = np.random.randint(W)
cy = np.random.randint(H)
bbx1 = np.clip(cx - cut_w // 2, 0, W)
bby1 = np.clip(cy - cut_h // 2, 0, H)
bbx2 = np.clip(cx + cut_w // 2, 0, W)
bby2 = np.clip(cy + cut_h // 2, 0, H)
return bbx1, bby1, bbx2, bby2
def cutmix(img ,label, cutmix_img, cutmix_label):
#int转化为one_hot
label_onehot = np.zeros(24)
label_onehot[label] = 1
cutmix_label_onehot = np.zeros(24)
cutmix_label_onehot[cutmix_label] = 1
alpha = 1
lam = np.random.beta(alpha,alpha)
bbx1, bby1, bbx2, bby2 = rand_bbox(img.size, lam)
img_new = img.copy()
img_new.paste(cutmix_img.crop((bbx1, bby1, bbx2, bby2)),(bbx1, bby1, bbx2, bby2))
# 计算 1 - bbox占整张图像面积的比例
lam = 1 - ((bbx2 - bbx1) * (bby2 - bby1) / (img_new.size[0] * img_new.size[1]))
label_new = lam*label_onehot + (1-lam)*cutmix_label_onehot
return img_new,paddle.to_tensor(np.float32(label_new))
Parámetros de entrada:
- img & cutmix_img : Dos imágenes en formato PIL para mezclar;
- label & cutmix_label : Las etiquetas correspondientes a las dos imágenes, en formato int
Parámetros de salida:
- Imágenes mixtas en formato PIL.
- Etiqueta de tipo tensor mixto, codificada como one_hot
A continuación se muestran dos imágenes para mostrar el efecto:
img1 = Image.open(train_df['path'].values[0]).convert('RGB')
label1 = train_df['label'].values[0]
img2 = Image.open(train_df['path'].values[1]).convert('RGB')
label2 = train_df['label'].values[1]
img_new, label_new = cutmix(img1, label1, img2, label2)
2.2.4 Normalizar
La función de la normalización es limitar el valor de los datos que deben procesarse a un cierto rango mediante un determinado método de procesamiento. En el procesamiento de imágenes de aprendizaje profundo, después del procesamiento de normalización, los datos pueden responder mejor a la función de activación, mejorar la expresividad de los datos y reducir la aparición de explosión y desaparición de gradientes. El procesamiento de normalización común consiste en procesar los datos en una distribución gaussiana con un valor medio de 0 y una varianza de 1, y paddle.vision.transforms.Normalize()
esta función se puede lograr.
Cuando la imagen pasa paddle.vision.transforms.ToTensor()
, la imagen se convierte al formato Tensor. Después de la conversión, la forma es (CxHxW) y el rango de valores es [0,1]. Dado que la media y la desviación estándar son diferentes para diferentes datos, necesitamos calcular la media y la desviación estándar de los tres canales en todo el conjunto de datos.
#获取所有图片三通道的均值和方差
all_df = train_df.append(test_df,ignore_index=True).append(val_df, ignore_index=True)
all_dataset = XunFeiDataset(all_df['path'].values, all_df['label'].values,
transforms.Compose([
transforms.RandomCrop((450,750)),
transforms.ToTensor()
]),mode='test')
def getStat(train_data):
'''
Compute mean and variance for training data
:param train_data: 自定义类Dataset(或ImageFolder即可)
:return: (mean, std)
'''
print('Compute mean and variance for training data.')
print(len(train_data))
train_loader = DataLoader(
train_data, batch_size=1, shuffle=False, num_workers=0)
mean = np.zeros(3)
std = np.zeros(3)
for X, _ in train_loader:
for d in range(3):
mean[d] += X[:, d, :, :].mean().cpu().numpy()[0]
std[d] += X[:, d, :, :].std().cpu().numpy()[0]
mean = mean/len(train_data)
std = std/len(train_data)
return list(mean), list(std)
print(getStat(all_dataset))
El resultado después de ejecutar es:
Compute mean and variance for training data.
3163
([0.6766141943829066, 0.06216619949979672, 0.2686088090203644], [0.24656723146663978, 0.14537001843179825, 0.17407946023116036])
Simplemente complete los datos calculados con Normalize
los parámetros:
Normalize((0.677, 0.062, 0.268), (0.246, 0.145, 0.174))
2.2.5 Etiqueta suave
Como técnica de regularización simple, el suavizado de etiquetas puede mejorar el rendimiento de generalización y la precisión del modelo en tareas de clasificación y aliviar el problema de la distribución de datos desequilibrada. Después del suavizado de etiquetas, one_label ya no solo tiene 0 y 1, sino que puede entenderse como la forma de probabilidad de esta clase. El suavizado de etiquetas puede reducir la confianza del modelo y evitar que el modelo caiga en las profundas grietas de pérdida que ocurren con el sobreajuste.
Paddle proporciona Label Smooth API.
label_onehot = paddle.to_tensor(np.float32([0,0,1,0]))
nn.functional.label_smooth(label_onehot)
La salida es:
Tensor(shape=[4], dtype=float32, place=Place(cpu), stop_gradient=True,
[0.02500000, 0.02500000, 0.92499995, 0.02500000])
2.2.6 Conjunto de datos modificado
Después de agregar estos métodos de mejora de datos, nuestro conjunto de datos también necesita realizar algunas modificaciones:
# 定义数据集读取方法
class XunFeiDataset(Dataset):
def __init__(self, img_path, label, transforms=None, mode='train'):
self.img_path = img_path
self.label = label
self.transforms = transforms
self.mode = mode
def __getitem__(self, index):
img = Image.open(self.img_path[index]).convert('RGB')
#将label转化为one_hot编码
label_onehot = np.zeros(24)
label_onehot[self.label[index]] = 1
label_onehot = paddle.to_tensor(np.float32(label_onehot))
if self.mode == 'train': #训练时才做数据增强
#随机擦除 100代表100个正方形,10代表每个正方形边长为10,0.2代表20%的概率
img = random_erase(img,100,10,0.2)
#mixup,0.2的概率
if np.random.rand(1)[0]<0.2:
mixup_idx = np.random.randint(0, len(self.img_path)-1)
mixup_img = Image.open(self.img_path[mixup_idx]).convert('RGB')
mixup_label = self.label[mixup_idx]
img, label_onehot = random_mixup(img, self.label[index], mixup_img, mixup_label)
#cutmix,0.2的概率
if np.random.rand(1)[0]<0.2:
cutmix_idx = np.random.randint(0, len(self.img_path)-1)
cutmix_img = Image.open(self.img_path[cutmix_idx]).convert('RGB')
cutmix_label = self.label[cutmix_idx]
img, label_onehot = cutmix(img, self.label[index], cutmix_img, cutmix_label)
if self.transforms is not None:
img = self.transforms(img)
label_onehot = nn.functional.label_smooth(label_onehot)
return img, label_onehot
def __len__(self):
return len(self.img_path)
2.3 Tasa de aprendizaje y función de optimización
El método de disminución de la tasa de aprendizaje puede mejorar efectivamente la precisión del modelo, y la función de optimización utiliza AdamW.
Adamw es Adam + decate de peso. El efecto es el mismo que la regularización Adam + L2, pero la eficiencia de cálculo es mayor, porque la regularización L2 requiere agregar términos regulares a la pérdida, luego calcular el gradiente y finalmente propagar hacia atrás, mientras que Adamw regulariza directamente El gradiente del término se agrega a la fórmula de retropropagación, eliminando la necesidad de agregar manualmente términos regulares a la pérdida.
scheduler = paddle.optimizer.lr.StepDecay(0.0001,15,gamma=0.1,verbose=False)
optimizer = paddle.optimizer.AdamW(learning_rate=scheduler, parameters=model.parameters())
Para obtener más métodos de atenuación de la tasa de aprendizaje, consulte: Métodos de ajuste de la tasa de aprendizaje en Pytorch y las paletas voladoras API que contiene también están básicamente disponibles.
2.4 Métodos de entrenamiento
Después de la prueba, el método de validación cruzada quíntuple puede mejorar en gran medida la precisión de la predicción. Para una implementación específica, consulte la línea de base. Al predecir el conjunto de prueba, podemos usar los parámetros que funcionan mejor en el conjunto de validación para predecir los resultados de cada pliegue.
for i in range(k_fold):
······
for epoch in range(epoches):
······
if val_acc>max_acc:
test_pred = predict(test_loader, model, criterion)
max_acc = val_acc
3. Mi camino hacia puntuaciones más altas
Preprocesamiento de datos/ingeniería de características | Modelo | Fracción |
---|---|---|
Cambiar tamaño (256), recorte aleatorio (224), normalización (parámetro de referencia) | resnet18 | 0,80-0,86 |
Recorte aleatorio (450,750), normalización (parámetro de referencia) | resnet18 | 0.91126 |
Recorte aleatorio (450,750), borrado aleatorio, normalización (parámetros de referencia) | resnet18 | 0.91533 |
Volver a dividir el conjunto de validación, recorte aleatorio (450,750), borrado aleatorio, confusión, normalización (parámetros de actualización) | resnet34 | 0.92375 |
Volver a dividir el conjunto de validación, recorte aleatorio (450,750), borrado aleatorio, confusión, normalización (actualización de parámetros), validación cruzada quíntuple | resnet34 | 0.93375 |
Volver a dividir el conjunto de validación, recorte aleatorio (450,750), borrado aleatorio, confusión, etiqueta_smooth, normalización (parámetros de actualización), validación cruzada quíntuple | resnet34 | 0.93518 |
Volver a dividir el conjunto de validación, recorte aleatorio (450,750), borrado aleatorio, confusión, cutmix, label_smooth, normalización (actualizar parámetros) | resnet34 | 0.93509 |
Volver a dividir el conjunto de validación, recorte aleatorio (450,750), borrado aleatorio, mezcla, mezcla de corte, etiqueta_smooth, normalización (parámetros de actualización), validación cruzada quíntuple | resnet34 | 0.94203 |
4. Finalmente
Este debería ser el final del camino hacia los puntos altos en esta competencia. Esta vez comencé principalmente con la ingeniería de características de datos y otros aspectos, y no hice demasiados cambios en el modelo. Puede considerar mejorar la estructura del modelo sobre esta base. Optimice aún más para obtener una puntuación más alta.