Tarea 3 de aprendizaje automático de Li Hongyi: clasificación de imágenes, integración de modelos, validación cruzada, TTA

Tabla de contenido

Tareas y conjuntos de datos

Tarea

conjunto de datos

Base

Paquete de guía

procesamiento de datos

Transforma

conjuntos de datos

función de carga de datos

modelo de clasificación

tren

función de entrenamiento

 Descargar datos

tren

predecir

producción

respuesta

aumento de datos

Usar modelo de código fuente

Conjunto+TTA

Conjunto de integración de modelos

Aumento del tiempo de prueba

Conjunto+TTA

Validación cruzada

tren

conversar

sin aumento de datos

Habilidades de entrenamiento de modelo único - CosineAnnealingWarmRestarts

Sobreadaptación de ResNet

Abandonar

transferir el aprendizaje

TTA

Ensamble

Validación cruzada

num_trabajos

np.array_split


Tareas y conjuntos de datos

Tarea

Objetivo - Clasificación de imágenes
1. Resolver la clasificación de imágenes con redes neuronales convolucionales.
2. Mejore el rendimiento con aumentos de datos.
3. Comprender las técnicas populares de modelos de imágenes, como el residual.

conjunto de datos

● Las imágenes se recopilan del conjunto de datos food-11 clasificado en 11 clases.
● Conjunto de entrenamiento: 9866 imágenes etiquetadas
● Conjunto de validación: 3430 imágenes etiquetadas
● Conjunto de prueba: 3347 imágenes

Dirección de descarga: ML2022Spring-HW3 | Kaggle

Base

Simple: 0,50099
Medio: 0,73207 Aumento de entrenamiento + Entrenamiento más largo
Fuerte: 0,81872 Aumento de entrenamiento + Diseño de modelo + Tren Looonger (+
Validación cruzada + Conjunto)
Jefe: 0,88446 Aumento de entrenamiento + Diseño de modelo + Aumento del tiempo de prueba
+ Tren Looonger (+ Validación cruzada + Conjunto) )

Paquete de guía

import numpy as np
import pandas as pd
import torch
import os
import torch.nn as nn
import torchvision.transforms as transforms
from PIL import Image
# "ConcatDataset" and "Subset" are possibly useful when doing semi-supervised learning.
from torch.utils.data import ConcatDataset, DataLoader, Subset, Dataset
from torchvision.datasets import DatasetFolder, VisionDataset

# This is for the progress bar.
from tqdm.auto import tqdm
from d2l import torch as d2l
import random

def same_seeds(seed):
    torch.manual_seed(seed)
    if torch.cuda.is_available():
        torch.cuda.manual_seed(seed)
        torch.cuda.manual_seed_all(seed)  
    np.random.seed(seed)  
    torch.backends.cudnn.benchmark = False
    torch.backends.cudnn.deterministic = True

procesamiento de datos

Transforma

# Normally, We don't need augmentations in testing and validation.
# All we need here is to resize the PIL image and transform it into Tensor.
test_tfm = transforms.Compose([
    transforms.Resize((128, 128)),
    transforms.ToTensor(),
])

# However, it is also possible to use augmentation in the testing phase.
# You may use train_tfm to produce a variety of images and then test using ensemble methods
train_tfm = transforms.Compose([
    # Resize the image into a fixed shape (height = width = 128)
    transforms.Resize((128, 128)),
    # You may add some transforms here.
    # ToTensor() should be the last one of the transforms.
    transforms.ToTensor(),
])

conjuntos de datos

class FoodDataset(Dataset):

    def __init__(self,path=None,tfm=test_tfm,files=None):
        super(FoodDataset).__init__()
        self.path = path
        if path:
            self.files = sorted([os.path.join(path, x) for x in os.listdir(path) if x.endswith(".jpg")])
        else:
            self.files = files
        self.transform = tfm
  
    def __len__(self):
        return len(self.files)
  
    def __getitem__(self,idx):
        fname = self.files[idx]
        im = Image.open(fname)
        im = self.transform(im)
        #im = self.data[idx]
        try:
            label = int(fname.split("/")[-1].split("_")[0])  # windows写成\\
        except:
            label = -1 # test has no label
        return im,label

función de carga de datos

def loadData(dataset_dir, batch_size, num_workers, train_tfm, test_tfm):
    # Construct datasets.
    # The argument "loader" tells how torchvision reads the data.
    train_set = FoodDataset(os.path.join(dataset_dir,"training"), tfm=train_tfm)
    train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True, num_workers=num_workers, pin_memory=True, drop_last = True)
    valid_set = FoodDataset(os.path.join(dataset_dir,"validation"), tfm=test_tfm)
    valid_loader = DataLoader(valid_set, batch_size=batch_size, shuffle=True, num_workers=num_workers, pin_memory=True, drop_last = True)
    print('训练集总长度是 {:d}, batch数量是 {:.2f}'.format(len(train_set), len(train_set)/ batch_size))
    print('验证集总长度是 {:d}, batch数量是 {:.2f}'.format(len(valid_set), len(valid_set)/ batch_size))
    return train_loader, valid_loader

modelo de clasificación

El modelo utilizado es un poco como VGG. El punto común es usar el núcleo de convolución 3X3, usar la agrupación, dimensión continua X2 y usar la capa lineal. La diferencia es que el modelo aquí es menos profundo y usa BatchNorm

class Classifier(nn.Module):
    def __init__(self):
        super(Classifier, self).__init__()
        # torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding)
        # torch.nn.MaxPool2d(kernel_size, stride, padding)
        # input 維度 [3, 128, 128]
        self.cnn = nn.Sequential(
            nn.Conv2d(3, 64, 3, 1, 1),  # [64, 128, 128]
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),      # [64, 64, 64]

            nn.Conv2d(64, 128, 3, 1, 1), # [128, 64, 64]
            nn.BatchNorm2d(128),
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),      # [128, 32, 32]

            nn.Conv2d(128, 256, 3, 1, 1), # [256, 32, 32]
            nn.BatchNorm2d(256),
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),      # [256, 16, 16]

            nn.Conv2d(256, 512, 3, 1, 1), # [512, 16, 16]
            nn.BatchNorm2d(512),
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),       # [512, 8, 8]
            
            nn.Conv2d(512, 512, 3, 1, 1), # [512, 8, 8]
            nn.BatchNorm2d(512),
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),       # [512, 4, 4]
        )
        self.fc = nn.Sequential(
            nn.Linear(512*4*4, 1024),
            nn.ReLU(),
            nn.Linear(1024, 512),
            nn.ReLU(),
            nn.Linear(512, 11)
        )

    def forward(self, x):
        out = self.cnn(x)
        out = out.view(out.size()[0], -1)
        return self.fc(out)

tren

función de entrenamiento

def trainer(train_loader, val_loader, model, config, devices):  
    
    criterion = nn.CrossEntropyLoss() 
    optimizer = torch.optim.AdamW(model.parameters(), lr=config['learning_rate'], weight_decay=config['weight_decay'])
    scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(optimizer, 
                T_0=config['T_0'], T_mult=config['T_mult'], 
                eta_min=config['learning_rate']/config['eta_min_ratio'])
    n_epochs, patience = config['num_epoch'], config['patience']
    num_batches = len(train_loader)
    show_batches = num_batches // config['show_num']
    
    if not os.path.isdir('./' + config['model_path'].split('/')[1]):
        os.mkdir('./' + config['model_path'].split('/')[1]) # Create directory of saving models.
    legend = ['train loss', 'train acc']
    
    if val_loader is not None:
        legend.append('valid loss')  
        legend.append('valid acc')  
    animator = d2l.Animator(xlabel='epoch', xlim=[0, n_epochs], legend=legend)       
        
    for epoch in range(n_epochs):
        train_acc, train_loss = 0.0, 0.0        
        
        # training
        model.train() # set the model to training mode
        for i, (data, labels) in enumerate(train_loader):
            data, labels = data.to(devices[0]), labels.to(devices[0])         

            optimizer.zero_grad() 
            outputs = model(data)             
            
            loss = criterion(outputs, labels)
            loss.backward() 
            optimizer.step() 

            _, train_pred = torch.max(outputs, 1) # get the index of the class with the highest probability
            train_acc += (train_pred.detach() == labels.detach()).sum().item()
            train_loss += loss.item()            
        
            if (i + 1) % show_batches == 0:
                train_acc = train_acc / show_batches / len(data)
                train_loss = train_loss / show_batches
                print('train_acc {:.3f}, train_loss {:.3f}'.format(train_acc, train_loss))
                animator.add(epoch  + (i + 1) / num_batches, (train_loss, train_acc, None, None)) 
                train_acc, train_loss = 0.0, 0.0               
                
        scheduler.step()
        # validation
        if val_loader != None:
            model.eval() # set the model to evaluation mode
            val_acc, val_loss = 0.0, 0.0  
            with torch.no_grad():
                for i, (data, labels) in enumerate(val_loader):
                    data, labels = data.to(devices[0]), labels.to(devices[0])
                    outputs = model(data)
                                        
                    loss = criterion(outputs, labels) 

                    _, val_pred = torch.max(outputs, 1) 
                    val_acc += (val_pred.cpu() == labels.cpu()).sum().item() # get the index of the class with the highest probability
                    val_loss += loss.item()                

                val_acc = val_acc / len(val_loader) / len(data)
                val_loss = val_loss / len(val_loader)
                print('val_acc {:.3f}, val_loss {:.3f} '.format(val_acc, val_loss))
                animator.add(epoch + 1, (None, None, val_loss, val_acc))
                
                # if the model improves, save a checkpoint at this epoch
                if val_acc > config['best_acc']:
                    config['best_acc'] = val_acc
                    torch.save(model.state_dict(),  config['model_path'])
                    # print('saving model with acc {:.3f}'.format(best_acc / len(val_loader) / len(labels)))
                    stale = 0
                else:
                    stale += 1
                    if stale > patience:
                        print(f"No improvment {patience} consecutive epochs, early stopping")
                        break

    # if not validating, save the last epoch
    if val_loader == None:
        torch.save(model.state_dict(), config['model_path'])
        # print('saving model at last epoch')      

 Descargar datos

batch_size = 256
num_workers= 4
dataset_dir = "data/food11"
train_loader, val_loader = loadData(dataset_dir, batch_size, num_workers, train_tfm, test_tfm)

tren

devices = d2l.try_all_gpus()
print(f'DEVICE: {devices}')

# fix random seed
seed = 0                        # random seed
same_seeds(seed)

config = {
    # training prarameters
    'num_epoch': 30,             # the number of training epoch
    'learning_rate': 1e-4,          # learning rate 
    'weight_decay': 1e-4, 
    'best_acc': 0.0,
    'T_0': 2,
    'T_mult': 2,    
    'eta_min_ratio':20,
    'patience': 300,      
    'show_num': 1  # 每个epoch打印几次loss
}

config['model_path'] = './models/foldmodel' + str(config['learning_rate'])   # the path where the checkpoint will be saved

model = Classifier().to(devices[0])

trainer(train_loader, val_loader, model, config, devices)

predecir

# 单个model
def pred(test_loader, model, devices):
    test_acc = 0.0
    test_lengths = 0
    pred = []

    model.eval()
    with torch.no_grad():
        for batch in tqdm(test_loader):
            features = batch[0].to(devices[0])
            outputs = model(features)
            _, test_pred = torch.max(outputs, 1) # get the index of the class with the highest probability
            pred.append(test_pred.cpu())
    pred = torch.cat(pred, dim=0).numpy()
    return pred

batch_size = 512
test_set = FoodDataset(os.path.join(dataset_dir,"test"), tfm=test_tfm)
test_loader = DataLoader(test_set, batch_size=batch_size, shuffle=False, pin_memory=True, num_workers=2)

model_best =  Classifier().to(devices[0])
model_best.load_state_dict(torch.load('models/model0.0005Fold_0_best'))
prediction = pred(test_loader, model_best, devices)

producción

#create test csv
def pad4(i):
    return "0"*(4-len(str(i)))+str(i)

df = pd.DataFrame()
df["Id"] = [pad4(i) for i in range(1,len(test_set)+1)]
df["Category"] = prediction
df.to_csv("submission best0.csv",index = False)

respuesta

aumento de datos

A través del aumento de datos, la misma muestra se alimenta a cada época de manera diferente durante el proceso de entrenamiento, el propósito es evitar que el modelo se sobreajuste.

# Normally, We don't need augmentations in testing and validation.
# All we need here is to resize the PIL image and transform it into Tensor.
test_tfm = transforms.Compose([
    transforms.Resize((128, 128)),
    transforms.ToTensor(),
])

# However, it is also possible to use augmentation in the testing phase.
# You may use train_tfm to produce a variety of images and then test using ensemble methods
train_tfm = transforms.Compose([
    # Resize the image into a fixed shape (height = width = 128)
    transforms.RandomResizedCrop((128, 128), scale=(0.5, 1), ratio=(0.5, 2)),
    # You may add some transforms here.
    transforms.RandomHorizontalFlip(0.5),
    transforms.RandomVerticalFlip(0.5),
    transforms.RandomGrayscale(0.2),
    transforms.RandomRotation(30),
    transforms.RandomAffine(30),
    transforms.RandomSolarize(threshold=192.0, p=0.2),
    transforms.ColorJitter(brightness=0.4,contrast=0.4, saturation=0.4),
    # ToTensor() should be the last one of the transforms.
    transforms.ToTensor(),
])

La siguiente es una versión más compleja, de hecho, algunos métodos de aumento de datos no se usan tan comúnmente.

# Normally, We don't need augmentations in testing and validation.
# All we need here is to resize the PIL image and transform it into Tensor.
test_tfm = transforms.Compose([
    transforms.Resize((128, 128)),
    transforms.ToTensor(),
])

# However, it is also possible to use augmentation in the testing phase.
# You may use train_tfm to produce a variety of images and then test using ensemble methods
train_tfm = transforms.Compose([
    # Resize the image into a fixed shape (height = width = 128)
    transforms.RandomResizedCrop((128, 128), scale=(0.5, 1), ratio=(0.5, 2)),
    # You may add some transforms here.
    transforms.RandomHorizontalFlip(0.5),
    transforms.RandomVerticalFlip(0.5),
    transforms.RandomGrayscale(0.2),
    transforms.RandomRotation(180),    
    transforms.ColorJitter(brightness=0.4,contrast=0.4, saturation=0.4),
    transforms.RandomGrayscale(p=0.2), 
    
    transforms.AutoAugment(transforms.AutoAugmentPolicy.IMAGENET),      
    transforms.RandomInvert(p=0.2),
    transforms.RandomAffine(30),
    transforms.RandomSolarize(threshold=192.0, p=0.2),
    transforms.RandomPosterize(bits=2),    
    transforms.RandomEqualize(p=0.2),  
    #transforms.RandomApply(torch.nn.ModuleList([]))
    
    # ToTensor() should be the last one of the transforms.
    transforms.ToTensor(),
])

Usar modelo de código fuente

Use directamente el modelo de clasificación proporcionado por el código original, más aumento de datos, entrenamiento cuidadoso, no se usan otras técnicas, la puntuación es 0.83764, superando fácilmente la línea fuerte

batch_size = 256
num_workers= 4
dataset_dir = "data/food11"
train_loader, val_loader = loadData(dataset_dir, batch_size, num_workers, train_tfm, test_tfm)

devices = d2l.try_all_gpus()
print(f'DEVICE: {devices}')

# fix random seed
seed = 0                        # random seed
same_seeds(seed)

config = {
    # training prarameters
    'num_epoch': 30,             # the number of training epoch
    'learning_rate': 1e-4,          # learning rate 
    'weight_decay': 1e-4, 
    'best_acc': 0.0,
    'T_0': 2,
    'T_mult': 2,    
    'eta_min_ratio':20,
    'patience': 300,      
    'show_num': 1  # 每个epoch打印几次loss
}

config['model_path'] = './models/foldmodel' + str(config['learning_rate'])   # the path where the checkpoint will be saved


model = Classifier().to(devices[0])

trainer(train_loader, val_loader, model, config, devices)

 

Conjunto+TTA

Conjunto de integración de modelos

Para cada lote de muestra, la matriz de predicción de cada modelo se calcula y se suma para obtener una matriz de predicción Para cada muestra, la categoría con el puntaje más alto se selecciona como la categoría pronosticada. Guarde los resultados de predicción de cada lote en la misma lista, este es el resultado final

Varias formas de integración:

● Promedio de logits o probabilidad: necesita guardar resultados detallados, menos ambiguos
● Votación: más fácil de implementar, necesita romper empates
● Codificación: operaciones matemáticas básicas con numpy o antorcha

# 模型集成
prediction = []
with torch.no_grad():
    for data in test_loader:
        test_preds = [] 
        for model_best in models:
            model_pred = model_best(data[0].to(devices[0])).cpu().numpy()
            test_preds.append(model_pred)
        
        test_preds = sum(test_preds)
        test_label = np.argmax(test_preds, axis=1)
        
        prediction += test_label.squeeze().tolist()

Aumento del tiempo de prueba

Cree un conjunto de prueba usando test_tfm y 5 conjuntos de prueba usando train_tfm, y obtenga la lista preds, que contiene elementos 6. El primero es la matriz de predicción obtenida usando test_tfm en el conjunto de prueba, y los últimos se obtienen usando train_tfm en el conjunto de prueba La matriz de predicción, la forma de cada matriz es (3347,11), 3347 es el número de muestras y 11 es el número de categorías.

 

# TTA
batch_size = 512
test_set = FoodDataset(os.path.join(dataset_dir,"test"), tfm=test_tfm)
test_loader = DataLoader(test_set, batch_size=batch_size, shuffle=False, pin_memory=True, num_workers=2)
test_loaders = []
for i in range(5):    
    test_set_i = FoodDataset(os.path.join(dataset_dir,"test"), tfm=train_tfm)
    test_loader_i = DataLoader(test_set_i, batch_size=batch_size, shuffle=False, num_workers=2, pin_memory=True)
    test_loaders.append(test_loader_i)

model_best = Classifier().to(devices[0])
model_best.load_state_dict(torch.load('models23/model0.0005Fold_0_best'))
model_best.eval()

preds = [[], [], [], [], [], []] 
with torch.no_grad():
    for data, _ in test_loader:
        batch_preds = model_best(data.to(devices[0])).cpu().data.numpy()     
        preds[0].extend(batch_preds)    
    
    for i, loader in enumerate(test_loaders):
        for data, _ in loader:
            batch_preds = model_best(data.to(devices[0])).cpu().data.numpy()           
            preds[i+1].extend(batch_preds)

Se obtiene el promedio ponderado de los resultados de predicción de los 6 conjuntos de prueba para obtener una matriz (3347,11). El resultado es mejor no sobrescribir preds, sino guardarlo como una nueva variable, para que sea conveniente probar diferentes coeficientes de peso y obtener mejores resultados.

preds_np = np.array(preds, dtype=object)
print(preds_np.shape)
bb = 0.6* preds_np[0] + 0.1 * preds_np[1] + 0.1 * preds_np[2] + 0.1 * preds_np[3] + 0.1 * preds_np[4] + 0.1 * preds_np[5]
print(bb.shape)
prediction = np.argmax(bb, axis=1)

Conjunto+TTA

escribir estas dos piezas juntas

test_loaders = []
batch_size = 256
for i in range(5):    
    test_set_i = FoodDataset(os.path.join(dataset_dir,"test"), tfm=train_tfm)
    test_loader_i = DataLoader(test_set_i, batch_size=batch_size, shuffle=False, num_workers=0, pin_memory=True)
    test_loaders.append(test_loader_i)

preds = [[], [], [], [], [], []] 
test_set = FoodDataset(os.path.join(dataset_dir,"test"), tfm=test_tfm)
test_loader = DataLoader(test_set, batch_size=batch_size, shuffle=False, num_workers=0, pin_memory=True)

with torch.no_grad():
    for data, _ in test_loader:
        batch_preds = [] 
        for model_best in models:
            batch_preds.append(model_best(data.to(devices[0])).cpu().data.numpy())
        batch_preds = sum(batch_preds)
        preds[0].extend(batch_preds.squeeze().tolist())    
    
    for i, loader in enumerate(test_loaders):
        for data, _ in loader:
            batch_preds = []
            for model_best in models:
                batch_preds.append(model_best(data.to(devices[0])).cpu().data.numpy())
            batch_preds = sum(batch_preds)
            preds[i+1].extend(batch_preds.squeeze().tolist())

El resultado está muy cerca de la línea del jefe.

Validación cruzada

 

Divida el conjunto de entrenamiento en 5 partes y realice una validación cruzada de 5 veces. Mezcle el conjunto de entrenamiento y el conjunto de verificación para obtener la lista de muestra total total_files, luego use la función np.array_split para obtener 5 conjuntos de datos, y use el conjunto de entrenamiento y el conjunto de verificación a su vez en 5 ciclos. Use config['model_path'] para registrar la ruta de guardado y use config['best_accs'] para registrar el mejor rendimiento de cada modelo en el conjunto de validación

def trainer_k_folds(config, dataset_dir, batch_size, train_tfm, test_tfm, devices):
    train_dir = os.path.join(dataset_dir,"training")
    val_dir = os.path.join(dataset_dir,"validation")
    train_files = [os.path.join(train_dir, x) for x in os.listdir(train_dir) if x.endswith('.jpg')]
    val_files = [os.path.join(val_dir, x) for x in os.listdir(val_dir) if x.endswith('.jpg')]
    total_files = np.array(train_files + val_files)
    random.shuffle(total_files)
    num_folds = config['num_folds']   
    train_folds = np.array_split(np.arange(len(total_files)), num_folds)
    train_folds = np.array(train_folds, dtype=object) # 防止因为数组维度不整齐而报错
        
    for i in range(num_folds):
        print(f'\n\nStarting Fold: {i} ********************************************')  
        train_data = total_files[np.concatenate(np.delete(train_folds, i)) ] 
        val_data = total_files[train_folds[i]]        
    
        train_set = FoodDataset(tfm=train_tfm, files=train_data)
        train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True, num_workers=4, pin_memory=True, drop_last = True)    
        valid_set = FoodDataset(tfm=test_tfm, files=val_data)
        valid_loader = DataLoader(valid_set, batch_size=batch_size, shuffle=True, num_workers=4, pin_memory=True, drop_last = True)
        print('训练集总长度是 {:d}, batch数量是 {:.2f}'.format(len(train_set), len(train_set)/ batch_size))
        print('验证集总长度是 {:d}, batch数量是 {:.2f}'.format(len(valid_set), len(valid_set)/ batch_size))
        
        tep = config['model_path']
        config['model_path'] += f"Fold_{i}_best"
        config['best_acc'] = 0.0
        model = Classifier().to(devices[0])
        # model.load_state_dict(torch.load('models/foldmodel0.0001')) 提前训练几个epoch,可能加快后面每一个模型的训练
        trainer(train_loader, valid_loader, model, config, devices)
        config['best_accs'].append(config['best_acc'])
        config['model_path'] = tep

tren

batch_size = 256
dataset_dir = "data/food11"

devices = d2l.try_all_gpus()
print(f'DEVICE: {devices}')

# fix random seed
seed = 0                        # random seed
same_seeds(seed)

config = {
    # training prarameters
    'num_epoch': 300,             # the number of training epoch
    'learning_rate': 5e-4,          # learning rate 
    'weight_decay': 1e-4, 
    'best_acc': 0.0,
    'T_0': 16,
    'T_mult': 1,    
    'eta_min_ratio':50,
    'patience': 32, 
    'num_folds':5,
    'show_num': 1,
    'best_accs': []
}
config['model_path'] = './models22/model' + str(config['learning_rate'])   # the path where the checkpoint will be saved

# Initialize a model, and put it on the device specified.
model = Classifier().to(devices[0])
trainer_k_folds(config, dataset_dir, batch_size, train_tfm, test_tfm, devices)

Después de la validación cruzada de 5 modelos, realice la integración del modelo y TTA, el resultado se muestra en la figura, que es peor que la línea principal

conversar

sin aumento de datos

Probó varias tasas de aprendizaje y entrenó sin aumento de datos, los resultados son similares a la figura a continuación.

Se puede ver que el error de entrenamiento cae rápidamente a 0, pero el error de verificación todavía está en un nivel alto, pero dado que el error de entrenamiento es 0, el gradiente de retropropagación es 0 y el modelo no puede seguir mejorando. sobreajuste típico.
La regularización puede evitar el ajuste excesivo del modelo. CS231n enumera el aumento de datos como uno de los medios de regularización (consulte 2022 Cs231n PPT notes-training CNN_iwill323 blog-CSDN blog ), al principio no entendí por qué el aumento de datos también pertenece a regularización, y ahora lo entiendo después de terminar esta tarea. Después de una serie de cambios aleatorios en las imágenes de entrenamiento, se generan muestras de entrenamiento similares pero diferentes, aumentando de hecho el tamaño del conjunto de entrenamiento. El cambio aleatorio de muestras de entrenamiento puede reducir la dependencia del modelo de ciertas propiedades, mejorando así la capacidad de generalización del modelo.

Habilidades de entrenamiento de modelo único - CosineAnnealingWarmRestarts

Use learning_rate = 1e-4, weight_decay = 5e-3, T_0 = 2, T_mult = 2, y entrene al clasificador() del código original. La curva de entrenamiento inicial es como se muestra a continuación.

Puede ver la forma de onda obvia, esto se debe a que CosineAnnealingWarmRestarts ajusta la tasa de aprendizaje de acuerdo con el intervalo de la secuencia geométrica, es decir, de acuerdo con la configuración del parámetro de T_0 = 2, T_mult = 2, cada 2, 4, 8, 16 ... tasa de aprendizaje de ajuste de ciclo de época.

Permítanme hablar sobre mi comprensión de CosineAnnealingWarmRestarts:

La curva de aprendizaje ondulada es realmente problemática. La pérdida disminuyó en el ciclo anterior, en el siguiente ciclo, porque la tasa de aprendizaje vuelve a un punto alto, la pérdida aumenta repentinamente y el próximo ciclo nuevo tardará muchas épocas en caer al nivel de pérdida del ciclo anterior. que sin duda es muy poco eficiente. Entonces, ¿por qué interrumpir el proceso de disminución de la pérdida y dejar que la pérdida fluctúe, en lugar de mantener la pérdida en una tendencia a la baja?

La razón es que la tendencia a la baja de la tasa de aprendizaje en realidad no se puede mantener. Si la tasa de aprendizaje no se ajusta a tiempo, la disminución de la pérdida eventualmente entrará en un "período plano" y la pérdida disminuirá gradualmente. Durante el proceso de entrenamiento, es difícil saber qué tipo de tasa de aprendizaje usar en diferentes etapas.El algoritmo CosineAnnealingWarmRestarts continuamente "revuelve" la tasa de aprendizaje, de modo que la pérdida sigue probando nuevos ciclos de tasa de aprendizaje, y es más probable que aprender un buen resultado de optimización.

Por supuesto, todavía queremos que la pérdida disminuya en línea recta, no en oleadas. Si el valor máximo del nuevo ciclo aumenta demasiado de repente y lleva mucho tiempo "recuperarse", entonces la tasa de aprendizaje máxima puede ser demasiado grande y la tasa de aprendizaje máxima puede reducirse; si la pérdida al final de cada ciclo cae demasiado suavemente, luego el mínimo La tasa de aprendizaje es demasiado pequeña, puede reducir eta_min.

Continúe entrenando y descubra que la curva de pérdida no es ondulada y muestra una tendencia a la baja, lo cual es ideal. Guarde el punto de control y envíe el resultado de la predicción con una puntuación de 0,80478.

 

 

Del final de la figura anterior, se puede encontrar que la pérdida puede seguir disminuyendo, por lo que la tasa de aprendizaje se ajusta a 5e-5 para continuar entrenando. Al principio, la tasa de aprendizaje inicial vuelve a un punto alto, por lo que la pérdida aumenta. El resultado final es bueno, parece que todavía hay margen de mejora

Referencia: torch.optim.lr_scheduler: descripción del parámetro CosineAnnealingWarmRestarts() para ajustar la tasa de aprendizaje_qq_37612828's Blog-CSDN Blog_cosineannealingwarmrestarts

Durante el proceso de entrenamiento, los puntos de control se guardan en diferentes puntos, y los puntajes son como se muestra en la figura a continuación

Modelo Puntaje
modelo2 0.81872
modelo3 0.83764
modelo5 0.78685
modelo6 0.76792
modelo7 0.80478
modelo8 0.80577
modelo5e-05 0.83167

Model3 es la puntuación más alta en su lugar. Esto se debe a que cada vez que reinicia desde un punto de interrupción, best_accuracy es 0 al principio, por lo que después de la primera época, el modelo se sobrescribe, pero la naturaleza ondulada del método CosineAnnealingWarmRestarts no puede garantizar que los resultados posteriores sean mejores. Entonces, cada vez que reinicie desde un punto de interrupción, modifique best_accuracy of config

Sobreadaptación de ResNet

class Residual_Block(nn.Module):  
    def __init__(self, ic, oc, stride=1):
        super().__init__()
        self.conv1 = nn.Sequential(
            nn.Conv2d(ic, oc, kernel_size=3, padding=1, stride=stride),
            nn.BatchNorm2d(oc),
            nn.ReLU(inplace=True)
        )
            
        self.conv2 = nn.Sequential(
            nn.Conv2d(oc, oc, kernel_size=3, padding=1),
            nn.BatchNorm2d(oc)
        )
        
        self.relu = nn.ReLU(inplace=True)
        
        if stride != 1 or (ic != oc):  # 对于resnet18,可以不需要stride != 1这个条件
            self.conv3 = nn.Sequential(
                nn.Conv2d(ic, oc, kernel_size=1, stride=stride),
                nn.BatchNorm2d(oc)
            )
        else:
            self.conv3 = None

    def forward(self, X):
        Y = self.conv1(X)
        Y = self.conv2(Y)
        if self.conv3:
            X = self.conv3(X)
        Y += X
        return self.relu(Y)

    
class ResNet(nn.Module):
    def __init__(self, block = Residual_Block, num_layers = [2,2,2,2], num_classes=11):
        super().__init__()
        self.preconv = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3),
            nn.BatchNorm2d(64), 
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        )     

        self.layer0 = self.make_residual(block, 64, 64,  num_layers[0])
        self.layer1 = self.make_residual(block, 64, 128, num_layers[1], stride=2)
        self.layer2 = self.make_residual(block, 128, 256, num_layers[2], stride=2)
        self.layer3 = self.make_residual(block, 256, 512, num_layers[3], stride=2)
        
        self.postliner = nn.Sequential(
                    nn.AdaptiveAvgPool2d((1,1)),
                    nn.Flatten(), 
                    nn.Linear(512, num_classes)
        )
        
    def make_residual(self, block, ic, oc, num_layer, stride=1):
        layers = []
        layers.append(block(ic, oc, stride))
        for i in range(1, num_layer):
            layers.append(block(oc, oc)) 
        return nn.Sequential(*layers)

    def forward(self, x):
        out = self.preconv(x)
        out = self.layer0(out) # [64, 32, 32]
        out = self.layer1(out) # [128, 16, 16]
        out = self.layer2(out) # [256, 8, 8]
        out = self.layer3(out) # [512, 4, 4]
        out = self.postliner(out)
        return out  

Escribí un RenNet18 a mano. Para obtener más información, consulte "Learning Deep Learning by Hands" de Li Mu. Establezca learning_rate = 1e-4, puede ver que el aprendizaje es relativamente normal al principio

A medida que avanzaba el aprendizaje, se volvió anormal. El error de entrenamiento continuó disminuyendo, pero el error de verificación permaneció básicamente sin cambios. Al final, el error de verificación se "aplanó", lo que fue un poco sobreajustado.

Abandonar

2022 Machine Learning HW3 Analysis_Machine Learning Craftsman's Blog-CSDN Blog de Li Hongyi adopta un diseño de abandono al final de la red residual.

class Residual_Block(nn.Module):
    def __init__(self, ic, oc, stride=1):
        # torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding)
        # torch.nn.MaxPool2d(kernel_size, stride, padding)
        super().__init__()
        self.conv1 = nn.Sequential(
            nn.Conv2d(ic, oc, kernel_size=3, stride=stride, padding=1),
            nn.BatchNorm2d(oc),
            nn.ReLU(inplace=True)
        )
        
        self.conv2 = nn.Sequential(
            nn.Conv2d(oc, oc, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(oc),
        )
        
        self.relu = nn.ReLU(inplace=True)
    
        self.downsample = None
        if stride != 1 or (ic != oc):
            self.downsample = nn.Sequential(
                nn.Conv2d(ic, oc, kernel_size=1, stride=stride),
                nn.BatchNorm2d(oc),
            )
        
    def forward(self, x):
        residual = x
        out = self.conv1(x)
        out = self.conv2(out)
        
        if self.downsample:
            residual = self.downsample(x)
            
        out += residual
        return self.relu(out)
        
class Classifier(nn.Module):
    def __init__(self, block=Residual_Block, num_layers=[2,2,2,2], num_classes=11):
        super().__init__()
        self.preconv = nn.Sequential(
            nn.Conv2d(3, 32, kernel_size=7, stride=2, padding=3, bias=False),
            nn.BatchNorm2d(32),
            nn.ReLU(inplace=True),
        )
        
        self.layer0 = self.make_residual(block, 32, 64,  num_layers[0], stride=2)
        self.layer1 = self.make_residual(block, 64, 128, num_layers[1], stride=2)
        self.layer2 = self.make_residual(block, 128, 256, num_layers[2], stride=2)
        self.layer3 = self.make_residual(block, 256, 512, num_layers[3], stride=2)
        
        #self.avgpool = nn.AvgPool2d(2)
        
        self.fc = nn.Sequential(            
            nn.Dropout(0.4),
            nn.Linear(512*4*4, 512),
            nn.BatchNorm1d(512),
            nn.ReLU(inplace=True),
            nn.Dropout(0.2),
            nn.Linear(512, num_classes),
        )
        
        
    def make_residual(self, block, ic, oc, num_layer, stride=1):
        layers = []
        layers.append(block(ic, oc, stride))
        for i in range(1, num_layer):
            layers.append(block(oc, oc))
        return nn.Sequential(*layers)
    
    def forward(self, x):
        # [3, 128, 128]
        out = self.preconv(x)  # [32, 64, 64]
        out = self.layer0(out) # [64, 32, 32]
        out = self.layer1(out) # [128, 16, 16]
        out = self.layer2(out) # [256, 8, 8]
        out = self.layer3(out) # [512, 4, 4]
        #out = self.avgpool(out) # [512, 2, 2]
        out = self.fc(out.view(out.size(0), -1)) 
        return out

No hubo una mejora significativa en el entrenamiento. 

transferir el aprendizaje

Use el resnet18 que viene con la antorcha y use los pesos previamente entrenados, y luego entrene como un todo en el conjunto de entrenamiento de este trabajo. Todo el proceso es fácil y eficiente. El poder del aprendizaje de transferencia se refleja. Parece que si resnet18 adopta algunas técnicas de entrenamiento, se puede utilizar en este conjunto de entrenamiento del trabajo.

TTA

Realice TTA para cada modelo (entrenando 5 conjuntos de prueba de train_tfm), la mayoría de ellos han mejorado significativamente.

Modelo Puntaje TTA
modelo2 0.81872 0.82569
modelo3 0.83764 0.83565
modelo5 0.78685 0.81274
modelo6 0.76792 0.7998
modelo7 0.80478 0.80876
modelo8 0.80577 0.8227

Después de que el modelo 3 haya hecho TTA, su rendimiento básicamente no cambia, probablemente porque su rendimiento ya es mejor y el potencial de TTA no es grande.

Ensamble

Los conjuntos de modelos no siempre mejoran el rendimiento del modelo. Integrar model5e-5 con otros modelos, con una puntuación de 0,82569, e integrar model3 con otros modelos, con una puntuación de 0,83366, los cuales no son tan buenos como antes de la integración, por lo que no es posible integrar modelos excelentes con modelos con una gran brecha, y dejar que se integren modelos con un rendimiento similar Solo entonces será efectivo.

  • Integración del mismo modelo

Los cuatro modelos del modelo 5 al modelo 8 están integrados y la puntuación es de 0,8237, lo que se puede decir que ha mejorado mucho. Cabe señalar aquí que estos modelos son en realidad el mismo modelo en el mismo conjunto de entrenamiento, y diferentes puntos de control del mismo proceso de entrenamiento también pueden tener un efecto integrado, lo que muestra el poder de Ensamble.

Los cuatro modelos del modelo 5 al modelo 8 están integrados + TTA, en comparación con solo integración, hay pocos cambios. Puede ser que el coeficiente de peso de TTA no esté configurado correctamente.

  •  Integración entre modelos

El modelo de resnet entrenado desde cero (puntuación de 0,71414) y el modelo 6 (puntuación de 0,76792) se integran en todos los modelos y el resultado es 0,78685, que es más obvio.

Continúe haciendo TTA sobre la base de la integración, y solo haga un conjunto de prueba de train_tfm, que tiene un gran efecto de mejora. El siguiente es el puntaje de las diferentes proporciones de preds_np[0] y preds_np[1]:

Proporción 1:1 2:1 6:1 9:1
Puntaje 0.77788  0.80776 0.81274 0.81573
  • Resultado Final - Integración de Gran Hermano

De acuerdo con la experiencia anterior, los tres modelos con el mejor efecto (modelo 3, puntaje 0.83565; modelo 5e-05, puntaje 0.83175; modelo de aprendizaje de migración, puntaje 0.84163) fueron integrados y TTA.

Solo haz la integración:

 Integración + TTA

Se puede ver que el efecto de integración ha mejorado mucho, y TTA puede mejorar aún más el efecto.Si está ajustado, no es un problema exceder la línea de jefe 0.88446.

Validación cruzada

Se realizó una validación cruzada de 5 veces y se obtuvieron 5 modelos, los resultados de cada modelo son los siguientes:

Integrando estos 5 modelos + TTA (se realizaron 5 conjuntos de pruebas de train_tfm), las puntuaciones son las siguientes. Los coeficientes de preds_np[0] son ​​1,5, 0,9, 0,6 respectivamente.

Se puede ver que el puntaje no es demasiado alto, no tan bueno como el "ejército sin nombre" anterior. Esto se debe a que las puntuaciones de varios modelos no son altas, por lo que la mejora del conjunto es limitada. Parece que el modelo de integración sigue la ley de "Yo soy un héroe y mi hijo es un héroe".

¿Por qué el modelo de validación cruzada anterior no obtiene una buena puntuación? Esto se debe a que el proceso de entrenamiento del modelo es automático y los hiperparámetros se detienen sin un ajuste cuidadoso, por lo que la puntuación no es alta.

 Teniendo en cuenta que el puntaje del modelo 0 es relativamente bajo, elimínelo y use los 4 modelos restantes para hacer integración + TTA, y hay una cierta mejora

 Luego elimine el modelo1 con una puntuación más baja, la puntuación integrada es 0,86254, sin mejora.

Luego, transforme el coeficiente de preds_np[0] en TTA, y la puntuación del modelo es la siguiente. Se puede encontrar que no hay una regularidad obvia y la variación de los resultados es limitada. Combinado con el ejemplo anterior, 0,9 parece ser mejor que 0,6.

Coeficiente de preds_np[0] 0.2 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1
Puntaje  0.8645 0.8695 0.8665 0.8625 0.8635 0.8655 0.8655 0.8635 0.8645

num_trabajos

No presté atención al impacto de num_workers en la velocidad de cálculo del modelo antes. Usando num_workers=0 para el cálculo, se encontró que la tasa de utilización de GPU era 0, mientras que la tasa de utilización de CPU era extremadamente alta. Los siguientes resultados son el tiempo de ejecución del entrenamiento del modelo de clasificación del código original en la tarjeta gráfica Nvidia 3090 con diferentes num_workers. Cada vez que reinicie el kernel, pruebe el siguiente num_workers

num_trabajadores Consume mucho tiempo cuando epoch=5 (s) Consume mucho tiempo cuando epoch=15 (s)
0     1236.3717732429504
1
312.47620368003845

2
168.62319421768188

4
107.80014061927795
324.39282393455505
8
89.33517622947693
294.2875111103058
dieciséis
123.81631231307983

fallar

32
172.18559384346008

La siguiente figura es el diagrama de operación de GPU y CPU cuando se calculan 5 épocas cuando se utilizan diferentes num_workers. Cuando num_workers=0, la GPU no se utiliza y la utilización de la memoria también es muy baja. De hecho, la máquina está utilizando la CPU para el cálculo. Cuando num_workers = 1, la tasa de utilización de la CPU cae en picado y la tasa de utilización de la GPU aumenta un poco, pero aún no es alta. Después de algunos cálculos, se convierte en 0. Esto se debe a que la GPU completa la tarea rápidamente, pero está limitada por el velocidad de transmisión de datos. , el suministro de datos no se ha mantenido. A medida que aumenta num_workers, aumenta la tasa de utilización de la memoria y la CPU se intensifica para transferir datos a la GPU, y la tasa de utilización de la GPU aumenta. Cuando num_workers es igual a 4/8/16, la utilización de GPU es alta y el tiempo de cálculo es bajo. Cuando num_workers es igual a 32, debido a que la CPU usa demasiados subprocesos para transferir datos, la coordinación de datos lleva mucho tiempo solo, por lo que la tasa de utilización de GPU vuelve a caer; el frente de cálculo se alarga (cálculo de 15 épocas) y el kernel muere directamente.

El uso de la memoria de video es relativamente estable de principio a fin, principalmente afectado por el tamaño del lote.

Kaggle también fue probado y calculado para 5 épocas:

num_trabajadores 0 2 4 8
Consumidor de tiempo (s) 774 475 434 467

Al usar más de 2 num_workers, tanto kaggle como colab informarán un recordatorio:

Este DataLoader creará 4 procesos de trabajo en total. Nuestro número máximo sugerido de trabajadores en el sistema actual es 2, que es más pequeño que lo que creará este DataLoader. Tenga en cuenta que la creación excesiva de trabajadores puede hacer que DataLoader funcione lentamente o incluso se congele, reduzca el número de trabajadores para evitar una posible lentitud/congelación si es necesario.

Es decir, recomiendan usar num_workers=2 funciones que necesitan atención

np.array_split

Se puede usar para dividir el conjunto de entrenamiento y el conjunto de validación en K-fold

import numpy as np
X_train = np.arange(23)
num_folds = 5
X_train_folds = np.array_split(X_train, num_folds)
X_train_folds = np.array(X_train_folds, dtype=object)
print(X_train_folds)
[matriz([0, 1, 2, 3, 4]) matriz([5, 6, 7, 8, 9]) matriz([10, 11, 12, 13, 14]) matriz([15, 16 
 , 17, 18]) matriz ([19, 20, 21, 22])]
a = np.delete(X_train_folds, 3)
print(a)
concat = np.concatenate(a)
print(concat)
[matriz([0, 1, 2, 3, 4]) matriz([5, 6, 7, 8, 9]) matriz([10, 11, 12, 13, 14]) matriz([19, 20 
 , 21, 22])] 
[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 19 20 21 22]

 

Supongo que te gusta

Origin blog.csdn.net/iwill323/article/details/127894848
Recomendado
Clasificación