Li Hongyi Machine Learning Homework 2 - Predicción de clasificación de fonemas

Tabla de contenido

conjunto de datos

Paquete de guía

función auxiliar

establecer semilla

Preprocesamiento de datos

Carga de conjunto de datos

definir modelo

función de entrenamiento

Leer conjunto de datos y entrenar

leer conjunto de datos

tren

Eliminar datos en la memoria para ahorrar espacio

predecir

función de predicción

hacer predicciones

respuesta

capas_ocultas=7, dim_oculto=256

capas_ocultas=12, dim_ocultas=512

conversar

más ancho o más profundo

Efecto de la tasa de aprendizaje

El impacto del tamaño del lote


conjunto de datos

La clasificación de fonemas predice fonemas a través de los datos del habla. Un fonema es la unidad de sonido más pequeña que puede distinguir significados en un lenguaje humano, y es el concepto básico del análisis fonológico. Cada idioma tiene su propio sistema de fonemas.

Un cuadro se configura como un segmento de audio de 25 ms de duración y se corta un cuadro deslizándolo 10 ms cada vez. Cada cuadro es
procesado por MFCC y se convierte en un vector de longitud 39. Para cada vector de marco, el conjunto de datos proporciona una etiqueta. Hay 41 categorías de etiquetas, cada categoría representa un fonema 

Todo el conjunto de entrenamiento es un subconjunto del conjunto de datos train-clean-100 (LibriSpeech), con un total de cuadros 2644158. Después del preprocesamiento, estos cuadros se integran en archivos de 4268 pt

Por ejemplo, use la función load_feat en el código de trabajo para leer 19-198-0008.pt para obtener una variable de tensor cuya forma sea [284, 39], busque la línea 19-198-0008 en el archivo train_labels.txt, que contiene un total de 284 etiquetas numéricas.

Del mismo modo, el conjunto de prueba tiene un total de 646268 cuadros, que se integran en archivos de 1078 pt

Dirección: ML2022Spring-hw2 | Kaggle

Paquete de guía

import numpy as np
import os
import random
import pandas as pd
import torch
from tqdm import tqdm
import torch
from torch.utils.data import Dataset
from torch.utils.data import DataLoader, TensorDataset
from d2l import torch as d2l

función auxiliar

establecer semilla

#fix seed
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

Preprocesamiento de datos

Un fonema puede abarcar varios cuadros, por lo que los cuadros adyacentes deben fusionarse para predecir el fonema del cuadro central.

 Este trabajo lo realiza principalmente la función concat_feat

# 读取pt文件
def load_feat(path):
    feat = torch.load(path)
    return feat

def shift(x, n):
    if n < 0:
        left = x[0].repeat(-n, 1)
        right = x[:n]

    elif n > 0:
        right = x[-1].repeat(n, 1)
        left = x[n:]
    else:
        return x

    return torch.cat((left, right), dim=0)


def concat_feat(x, concat_n):
    assert concat_n % 2 == 1 # n must be odd
    if concat_n < 2:
        return x
    seq_len, feature_dim = x.size(0), x.size(1)
    x = x.repeat(1, concat_n) 
    x = x.view(seq_len, concat_n, feature_dim).permute(1, 0, 2) # concat_n, seq_len, feature_dim
    mid = (concat_n // 2)
    for r_idx in range(1, mid+1):
        x[mid + r_idx, :] = shift(x[mid + r_idx], r_idx)
        x[mid - r_idx, :] = shift(x[mid - r_idx], -r_idx)

    return x.permute(1, 0, 2).view(seq_len, concat_n * feature_dim)

def preprocess_data(split, feat_dir, phone_path, concat_nframes, train_ratio=0.8, train_val_seed=1337):
    class_num = 41 # NOTE: pre-computed, should not need change
    mode = 'train' if (split == 'train' or split == 'val') else 'test'

    label_dict = {}
    if mode != 'test':
      phone_file = open(os.path.join(phone_path, f'{mode}_labels.txt')).readlines()

      for line in phone_file:
          line = line.strip('\n').split(' ')
          label_dict[line[0]] = [int(p) for p in line[1:]]

    if split == 'train' or split == 'val':
        # split training and validation data
        usage_list = open(os.path.join(phone_path, 'train_split.txt')).readlines()  #获取标签列表
        random.seed(train_val_seed)  # 固定住seed,使得划分出的验证集和训练集没有交集
        random.shuffle(usage_list)
        percent = int(len(usage_list) * train_ratio)
        usage_list = usage_list[:percent] if split == 'train' else usage_list[percent:] # 划分出验证集
    elif split == 'test':
        usage_list = open(os.path.join(phone_path, 'test_split.txt')).readlines()
    else:
        raise ValueError('Invalid \'split\' argument for dataset: PhoneDataset!')

    usage_list = [line.strip('\n') for line in usage_list]
    print('[Dataset] - # phone classes: ' + str(class_num) + ', number of utterances for ' + split + ': ' + str(len(usage_list)))

    max_len = 3000000
    # X就是最终要得到的样本数据,其中每一行就是一个样本。每一行包含了concat_nframe个frame
    X = torch.empty(max_len, 39 * concat_nframes)  
    if mode != 'test':
      y = torch.empty(max_len, dtype=torch.long)  # 标签数据

    idx = 0
    for i, fname in tqdm(enumerate(usage_list)):
        feat = load_feat(os.path.join(feat_dir, mode, f'{fname}.pt'))  # 读取每一个pt文件,得到一个tensor变量
        cur_len = len(feat)  # 统计该tensor有多少行,后面对X进行截取
        feat = concat_feat(feat, concat_nframes)   # 得到以每个frame为中心,扩展了concat_nframes个邻近frame的向量。如果一个frame在边缘,则以他自身代替邻近frame进行扩展
        if mode != 'test':
          label = torch.LongTensor(label_dict[fname])  # 获取label

        X[idx: idx + cur_len, :] = feat  # 存入X
        if mode != 'test':
          y[idx: idx + cur_len] = label

        idx += cur_len
 
    X = X[:idx, :]  # X有3000000行,超出的部分不要
    if mode != 'test':
      y = y[:idx]

    print(f'[INFO] {split} set')
    print(X.shape)
    if mode != 'test':
      print(y.shape)
      return X, y
    else:
      return X

Pruebe la función concat_feat, y puede saber aproximadamente lo que está haciendo arriba

x = torch.tensor([[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13,14,15],[16,17,18]])
res = concat_feat(x, 3)
res
tensor([[ 1, 2, 3, 1, 2, 3, 4, 5, 6], [ 
        1, 2, 3, 4, 5, 6, 7, 8, 9], 
        [ 4, 5, 6, 7, 8, 9, 10, 11, 12], 
        [7, 8, 9, 10, 11, 12, 13, 14, 15], 
        [10, 11, 12, 13, 14, 15, 16, 17, 18], 
        [13, 14, 15, 16, 17, 18, 16, 17, 18]])

Carga de conjunto de datos

import gc

def loadData(concat_nframes, train_ratio, batch_size):
    # preprocess data
    train_X, train_y = preprocess_data(split='train', feat_dir='./libriphone/feat', phone_path='./libriphone', 
                                       concat_nframes=concat_nframes, train_ratio=train_ratio)
    val_X, val_y = preprocess_data(split='val', feat_dir='./libriphone/feat', phone_path='./libriphone', 
                                   concat_nframes=concat_nframes, train_ratio=train_ratio)

    # get dataset
    train_set = TensorDataset(train_X, train_y)
    val_set = TensorDataset(val_X, val_y)
    print('训练集总长度是 {:d}, batch数量是 {:.2f}'.format(len(train_set), len(train_set)/ batch_size))
    print('验证集总长度是 {:d}, batch数量是 {:.2f}'.format(len(val_set), len(val_set)/ batch_size))
    # remove raw feature to save memory
    del train_X, train_y, val_X, val_y
    gc.collect()

    # get dataloader
    train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True, drop_last=True)
    val_loader = DataLoader(val_set, batch_size=batch_size, shuffle=False, drop_last=True)
    
    return train_loader, val_loader

definir modelo

 Modelo de definición modular, Classifier puede especificar cuántas capas ocultas

import torch
import torch.nn as nn
import torch.nn.functional as F

class BasicBlock(nn.Module):
    def __init__(self, input_dim, output_dim):
        super(BasicBlock, self).__init__()

        self.block = nn.Sequential(
            nn.Linear(input_dim, output_dim),
            nn.ReLU(),
            nn.BatchNorm1d(output_dim),
            nn.Dropout(0.3)
        )

    def forward(self, x):
        x = self.block(x)
        return x


class Classifier(nn.Module):
    def __init__(self, input_dim, output_dim=41, hidden_layers=1, hidden_dim=256):
        super(Classifier, self).__init__()

        self.fc = nn.Sequential(
            BasicBlock(input_dim, hidden_dim),
            *[BasicBlock(hidden_dim, hidden_dim) for _ in range(hidden_layers)],
            nn.Linear(hidden_dim, output_dim)
        )

    def forward(self, x):
        x = self.fc(x)
        return x

función de entrenamiento

Use la función de optimización de AdamW y use CosineAnnealingWarmRestarts para ajustar lr. Use el kit de herramientas d2l en el libro "Aprendizaje profundo práctico" para dibujar la pérdida y la precisión durante el entrenamiento

def trainer(show_num, train_loader, val_loader, model, config, devices):  
    
    criterion = nn.CrossEntropyLoss() 
    optimizer = torch.optim.AdamW(model.parameters(), lr=config['learning_rate'])
    scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(optimizer, 
                                        T_0=2, T_mult=2, eta_min=config['learning_rate']/50)
    n_epochs, best_acc, early_stop_count = config['num_epoch'], 0.0, 0
    num_batches = len(train_loader)
    
    if not os.path.isdir('./models'):
        os.mkdir('./models') # 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
        count = 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()
            count += 1
        
            if (i + 1) % (num_batches // show_num) == 0:
                train_acc = train_acc / count / len(data)
                train_loss = train_loss / count
                print('train_acc {:.3f}'.format(train_acc))
                animator.add(epoch  + (i + 1) / num_batches, (train_loss, train_acc, None, None)) 
                train_acc, train_loss, count = 0.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}'.format(val_acc))
                animator.add(epoch + 1, (None, None, val_loss, val_acc))
                
                # if the model improves, save a checkpoint at this epoch
                if val_acc > best_acc:
                    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)))


    # 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')

Leer conjunto de datos y entrenar

leer conjunto de datos

concat_nframes es un parámetro importante. En la clase, el asistente de enseñanza mencionó que se puede configurar en 11. Vi que es más beneficioso configurarlo más grande.

Li Hongyi 2022 máquina aprendizaje tarea registro HW2 - Zhihu : Los parámetros de concat_nframes deben ajustarse tanto como sea posible, pero los parámetros aquí no deben ser demasiado grandes, demasiado grandes reducirán la precisión del modelo.

concat_nframes = 17             # the number of frames to concat with, n must be odd (total 2k+1 = n frames)
train_ratio = 0.9               # the ratio of data used for training, the rest will be used for validation    
batch_size = 8192*4                # batch size
train_loader, val_loader = loadData(concat_nframes, train_ratio, batch_size)

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': 3,                  # the number of training epoch
    'learning_rate': 1e-3,          # learning rate    

    # model parameters   
    'hidden_layers': 12,               # the number of hidden layers
    'hidden_dim': 256                  # the hidden dim     
}

config['model_path'] = './models/model' + str(config['learning_rate']) + '-' + str(config['hidden_layers']) 
        ... + '-' + str(config['hidden_dim']) + '.ckpt'  # the path where the checkpoint will be saved
input_dim = 39 * concat_nframes # the input dim of the model, you should not change the value
model = Classifier(input_dim=input_dim, hidden_layers=config['hidden_layers'], 
                   hidden_dim=config['hidden_dim'])
model = nn.DataParallel(model, device_ids = devices).to(devices[0])

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

Eliminar datos en la memoria para ahorrar espacio

del train_loader, val_loader
gc.collect()

predecir

función de predicción

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

    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
            preds.append(test_pred.cpu())
    preds = torch.cat(pred, dim=0).numpy()
    return preds

hacer predicciones

# load data
test_X = preprocess_data(split='test', feat_dir='./libriphone/feat', phone_path='./libriphone', concat_nframes=concat_nframes)
test_set = TensorDataset(test_X)
test_loader = DataLoader(test_set, batch_size=batch_size, shuffle=False)


# load model
model = Classifier(input_dim=input_dim, hidden_layers=config['hidden_layers'], 
                   hidden_dim=config['hidden_dim'])
model = nn.DataParallel(model, device_ids = devices).to(devices[0])
model.load_state_dict(torch.load(config['model_path']))
pred = pred(test_loader, model, devices)


# output
with open('prediction.csv', 'w') as f:
    f.write('Id,Class\n')
    for i, y in enumerate(pred):
        f.write('{},{}\n'.format(i, y))

respuesta

Arreglé concat_nframes a 17 y principalmente ajusté los dos parámetros de hidden_layers y hidden_dim. Si desea llegar a la línea principal, debe usar un modelo más complejo. Puede consultar el análisis HW2 de aprendizaje automático 2022 de Li Hongyi_Blog del artesano de aprendizaje automático-CSDN Blog_Tarea 2 de aprendizaje automático de Li Hongyi

capas_ocultas=7, dim_oculto=256

Proceso de entrenamiento:

 resultado:

Este ejemplo no está hecho en serio, ya está relativamente cerca de la línea de base media: 0.69747

capas_ocultas=12, dim_ocultas=512

Probé hidden_layers=12, hidden_dim=256, y el resultado fue una mejora limitada, así que aumenté el ancho cada vez y me convertí en 512

Puede ver líneas onduladas obvias durante el proceso de entrenamiento, esto se debe al uso de CosineAnnealingWarmRestarts para ajustar lr

La diferencia entre el resultado y la línea base fuerte: 0.75028 es aproximadamente 0.3 %. Si continúa entrenando, debería poder superar la línea base fuerte.

 

conversar

más ancho o más profundo

Al cambiar los parámetros del modelo, hay dos opciones, ya sea para profundizar o ampliar

Li Hongyi 2022 Análisis HW2 de aprendizaje automático_Blog del artesano de aprendizaje automático-CSDN Blog_Li Hongyi Tarea de aprendizaje automático 2

Con concat_nframes=19, hidden_layers=3, hidden_dim=1024, la cantidad de parámetros del modelo es 3 958 825, lo que también supera la línea de base fuerte.

Probé concat_nframes=17, hidden_layers=12, hidden_dim=512, y la cantidad de parámetros del modelo fue 3 526 185, y la puntuación fue un 0,7 % más baja que la del modelo anterior. El número de capas ha cambiado de 3 a 12, ya que cada vez es más estrecho, el número de parámetros se reduce un 11%.

Hay muchos artículos que mencionan que la red debe ser más profunda, no más amplia (puede consultar las Notas de aprendizaje profundo de Goodfellow: Arquitectura de red neuronal_Blog de iwill323-CSDN Blog_Aprendizaje profundo de goodfellow ), la comparación anterior se puede usar como confirmación.

Intenté que el modelo fuera estrecho y profundo, hidden_layers=18, hidden_dim=256, pero el resultado no es bueno.

 

Se puede ver que el error de entrenamiento es relativamente grande y la optimización no es lo suficientemente buena. Puede ser porque después de que el modelo se vuelve más profundo, la dificultad de optimización aumenta.

Efecto de la tasa de aprendizaje

Como novato, siento que es demasiado importante probar una tasa de aprendizaje al principio, y puedes verla después de ejecutar algunas épocas. Como se puede ver a partir de lo siguiente, 1e-3 a 1e-2 es más apropiado

1e-1
1e-2

1e-3
1e-4     
1e-5   

El impacto del tamaño del lote

El profesor dejó muy claro este contenido en clase, así que pon la foto directamente:

Puedes referirte a:

Notas de estudio del árbol de flores de Goodfellow: modelo de optimización en profundidad: se busca programador

A continuación, uso concat_nframes=5, hidden_layers=1, hidden_dim=64 y ejecuto 3 epochs en la CPU

tamaño del lote Consumidor de tiempo (s)
8 2553
64 449
128 272 
256 187  

Cuando el tamaño del lote aumenta n veces, la reducción en el consumo de tiempo es menor que n veces y mayor que n/2 veces.

Para calcular más rápido, batch_size = 8192 * 4, que se considera muy grande. Una vez configuré el tamaño del lote muy grande y descubrí que len(val_dataloader)=0, no entendía por qué, y luego descubrí que el tamaño del lote era mayor que 264570 y se eliminó en último lugar. Después del entrenamiento, es muy importante encontrar un tamaño de lote adecuado.

  

Supongo que te gusta

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