Tabla de contenido
Leer conjunto de datos y entrenar
Eliminar datos en la memoria para ahorrar espacio
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
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:
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.