HW1 code analysis (Machine Learning by Hung-yi Lee)
Download data
!gdown --id '1kLSW_-cW2Huj7bh84YTdimGBOJaODiOS' --output covid.train.csv
!gdown --id '1iiI5qROrAhZn-o4FPqsE97bMzDEFvIdg' --output covid.test.csv
这段代码是使用 gdown
命令行工具从 Google Drive 下载文件的命令,适用场景:Google Colab、Jupyter Notebook 或其他支持 !
执行终端命令的 Python 环境。
Import packages
# Numerical Operations
import math
import numpy as np# Reading/Writing Data
import pandas as pd
import os
import csv# For Progress Bar
from tqdm import tqdm# Pytorch
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader, random_split# For plotting learning curve
from torch.utils.tensorboard import SummaryWriter
-
numpy
是 Python 中用于科学计算的核心库,特别适合处理数组和矩阵运算。NumPy 的名字来源于“Numerical Python” -
它提供了高效的数值计算工具,支持多维数组和矩阵操作。
-
pandas
是 Python 中用于数据处理和分析的核心库,特别适合处理结构化数据(如表格数据)。 -
它提供了高效的数据结构(如
DataFrame
和Series
)和丰富的功能,用于数据读取、清洗、分析和可视化。 -
os
模块提供了与操作系统交互的功能,例如文件路径操作、目录管理、环境变量访问等。os是operating system的缩写 -
csv模块提供了读取和写入 CSV 文件的功能。
-
CSV(Comma-Separated Values,逗号分隔值)是一种常见的文件格式,用于存储表格数据,如电子表格或数据库中的数据。CSV 文件以纯文本形式存储数据,每行是一个数据记录,字段之间通过逗号分隔。它具有简单、通用性强的特点,广泛应用于数据交换和存储。
-
tqdm
是一个用于显示进度条的 Python 库,名字来源于阿拉伯语单词 "taqaddum"(تقدّم),意思是“进展”或“进度”。 -
它可以在循环或长时间运行的任务中显示一个动态的进度条,帮助用户直观地了解任务的完成情况。
-
torch
是 PyTorch 的核心模块,提供了张量(Tensor
)操作、自动求导、数学函数等功能。 -
torch.nn
提供了构建神经网络层和模型的工具。 -
它包含各种层(如全连接层、卷积层、循环神经网络层等)和损失函数。
-
Dataset
:用于定义自定义数据集。 -
DataLoader
:用于批量加载数据,支持多线程数据加载和数据打乱。 -
random_split
:用于将数据集随机划分为训练集和验证集。 -
SummaryWriter
是 PyTorch 提供的用于将数据写入 TensorBoard 日志的类。 -
通过它,你可以将训练过程中的标量(如损失、准确率)、图像、直方图等数据记录到日志文件中,然后在 TensorBoard 中查看。
Some Utility Functions
def same_seed(seed): '''Fixes random number generator seeds for reproducibility.'''torch.backends.cudnn.deterministic = Truetorch.backends.cudnn.benchmark = Falsenp.random.seed(seed)torch.manual_seed(seed)if torch.cuda.is_available():torch.cuda.manual_seed_all(seed)
目的:通过设置随机数种子,确保每次运行代码时生成的随机数相同,从而使实验结果可重复。
def train_valid_split(data_set, valid_ratio, seed):'''Split provided training data into training set and validation set'''valid_set_size = int(valid_ratio * len(data_set)) train_set_size = len(data_set) - valid_set_sizetrain_set, valid_set = random_split(data_set, [train_set_size, valid_set_size], generator=torch.Generator().manual_seed(seed))return np.array(train_set), np.array(valid_set)def predict(test_loader, model, device):model.eval() # Set your model to evaluation mode.preds = []for x in tqdm(test_loader):x = x.to(device) with torch.no_grad(): pred = model(x) preds.append(pred.detach().cpu()) preds = torch.cat(preds, dim=0).numpy() return preds
-
train_valid_split
将提供的数据集划分为训练集和验证集。 -
使用
random_split
函数进行随机划分,并通过设置随机种子确保结果可重复。 -
predit函数代码逐行解释
-
def predict(test_loader, model, device):
-
这是一个函数定义,函数名为
predict
,接受三个参数:-
test_loader
: 数据加载器,通常是一个DataLoader
对象,用于批量加载测试数据。 -
model
: 训练好的机器学习模型,通常是一个torch.nn.Module
的子类。 -
device
: 指定模型和数据所在的设备,通常是'cpu'
或'cuda'
(GPU)。
-
-
-
model.eval()
-
将模型设置为评估模式。在PyTorch中,模型有两种模式:训练模式(
model.train()
)和评估模式(model.eval()
)。在评估模式下,模型会关闭一些训练时特有的行为,比如Dropout和Batch Normalization的随机性,以确保推理结果的一致性。
-
-
preds = []
-
初始化一个空列表
preds
,用于存储每个批次的预测结果。
-
-
for x in tqdm(test_loader):
-
使用
tqdm
库对test_loader
进行迭代,tqdm
是一个进度条库,可以在循环中显示进度条,方便观察数据处理进度。 -
x
是当前批次的数据,通常是一个张量(Tensor),包含了输入特征。
-
-
x = x.to(device)
-
将当前批次的数据
x
转移到指定的设备(CPU或GPU)上。这一步是为了确保数据和模型在同一个设备上,以便进行后续的计算。
-
-
with torch.no_grad():
-
with
语句用于创建一个上下文管理器,torch.no_grad()
是PyTorch提供的一个上下文管理器,用于禁用梯度计算。 -
在推理阶段,我们不需要计算梯度,因为不会进行反向传播和参数更新。禁用梯度计算可以减少内存消耗并加速计算。
-
-
pred = model(x)
-
将输入数据
x
传递给模型model
,得到模型的输出pred
,即模型的预测结果。
-
-
preds.append(pred.detach().cpu())
-
pred.detach()
:将预测结果从计算图中分离,避免梯度计算。 -
.cpu()
:将预测结果从GPU转移到CPU(如果之前是在GPU上计算的)。 -
preds.append(...)
:将处理后的预测结果添加到preds
列表中。
-
-
preds = torch.cat(preds, dim=0).numpy()
-
torch.cat(preds, dim=0)
:将列表preds
中的所有预测结果沿着第0维度(通常是批次维度)拼接成一个大的张量。 -
.numpy()
:将PyTorch张量转换为NumPy数组,方便后续处理或保存。
-
-
return preds
-
返回最终的预测结果,通常是一个NumPy数组。
-
Dataset
class COVID19Dataset(Dataset):'''x: Features.y: Targets, if none, do prediction.'''def __init__(self, x, y=None):if y is None:self.y = yelse:self.y = torch.FloatTensor(y)self.x = torch.FloatTensor(x)def __getitem__(self, idx):if self.y is None:return self.x[idx]else:return self.x[idx], self.y[idx]def __len__(self):return len(self.x)
Neural Network Model
class My_Model(nn.Module):def __init__(self, input_dim):super(My_Model, self).__init__()# TODO: modify model's structure, be aware of dimensions. self.layers = nn.Sequential(nn.Linear(input_dim, 16),nn.ReLU(),nn.Linear(16, 8),nn.ReLU(),nn.Linear(8, 1))def forward(self, x):x = self.layers(x)x = x.squeeze(1) # (B, 1) -> (B)return x
Feature Selection
def select_feat(train_data, valid_data, test_data, select_all=True):'''Selects useful features to perform regression'''y_train, y_valid = train_data[:,-1], valid_data[:,-1]raw_x_train, raw_x_valid, raw_x_test = train_data[:,:-1], valid_data[:,:-1], test_dataif select_all:feat_idx = list(range(raw_x_train.shape[1]))else:feat_idx = [0,1,2,3,4] # TODO: Select suitable feature columns.return raw_x_train[:,feat_idx], raw_x_valid[:,feat_idx], raw_x_test[:,feat_idx], y_train, y_valid
-
返回筛选后的特征(
x_train, x_valid, x_test
)和目标值(y_train, y_valid
)
Training Loop
def trainer(train_loader, valid_loader, model, config, device):criterion = nn.MSELoss(reduction='mean') # Define your loss function, do not modify this.# Define your optimization algorithm. # TODO: Please check https://pytorch.org/docs/stable/optim.html to get more available algorithms.# TODO: L2 regularization (optimizer(weight decay...) or implement by your self).optimizer = torch.optim.SGD(model.parameters(), lr=config['learning_rate'], momentum=0.9) writer = SummaryWriter() # Writer of tensoboard.if not os.path.isdir('./models'):os.mkdir('./models') # Create directory of saving models.n_epochs, best_loss, step, early_stop_count = config['n_epochs'], math.inf, 0, 0for epoch in range(n_epochs):model.train() # Set your model to train mode.loss_record = []# tqdm is a package to visualize your training progress.train_pbar = tqdm(train_loader, position=0, leave=True)for x, y in train_pbar:optimizer.zero_grad() # Set gradient to zero.x, y = x.to(device), y.to(device) # Move your data to device. pred = model(x) loss = criterion(pred, y)loss.backward() # Compute gradient(backpropagation).optimizer.step() # Update parameters.step += 1loss_record.append(loss.detach().item())# Display current epoch number and loss on tqdm progress bar.train_pbar.set_description(f'Epoch [{epoch+1}/{n_epochs}]')train_pbar.set_postfix({'loss': loss.detach().item()})mean_train_loss = sum(loss_record)/len(loss_record)writer.add_scalar('Loss/train', mean_train_loss, step)model.eval() # Set your model to evaluation mode.loss_record = []for x, y in valid_loader:x, y = x.to(device), y.to(device)with torch.no_grad():pred = model(x)loss = criterion(pred, y)loss_record.append(loss.item())mean_valid_loss = sum(loss_record)/len(loss_record)print(f'Epoch [{epoch+1}/{n_epochs}]: Train loss: {mean_train_loss:.4f}, Valid loss: {mean_valid_loss:.4f}')writer.add_scalar('Loss/valid', mean_valid_loss, step)if mean_valid_loss < best_loss:best_loss = mean_valid_losstorch.save(model.state_dict(), config['save_path']) # Save your best modelprint('Saving model with loss {:.3f}...'.format(best_loss))early_stop_count = 0else: early_stop_count += 1if early_stop_count >= config['early_stop']:print('\nModel is not improving, so we halt the training session.')return
1. 初始化设置
-
损失函数:使用
MSELoss
(均方误差),适用于回归任务。 -
优化器:选择
SGD
(随机梯度下降)并设置动量 (momentum=0.9
),但缺少weight_decay
(L2正则化)。 -
可视化工具:通过
SummaryWriter()
记录训练过程(TensorBoard支持)。 -
模型保存目录:检查并创建
./models
文件夹
2. 训练循环
-
训练模式:
model.train()
启用Dropout/BatchNorm等训练专用层。 -
进度条:
tqdm
实时显示训练进度和当前损失。 -
梯度管理:
-
optimizer.zero_grad()
清空梯度。 -
loss.backward()
反向传播计算梯度。 -
optimizer.step()
更新参数。
-
-
设备转移:数据 (
x, y
) 显式移动到指定设备(如GPU)。
关键细节:
-
loss.detach().item()
确保损失值从计算图中分离并转为Python标量。 -
每个batch的损失记录到
loss_record
,最后计算平均训练损失。
3. 验证阶段
-
评估模式:
model.eval()
关闭Dropout/BatchNorm的随机性。 -
无梯度计算:
with torch.no_grad()
块内禁用自动求导,节省内存/计算资源。 -
验证损失:同训练损失计算方式,但仅作评估,不反向传播。
注意事项:
-
验证集应参与模型选择和早停,但不参与参数更新。
4. 模型保存与早停
-
最佳模型保存:当验证损失 (
mean_valid_loss
) 低于历史最佳时,保存模型到config['save_path']
。 -
早停机制:若验证损失连续
early_stop
次未改善,终止训练。
5. 日志与输出
-
控制台输出:打印每轮的训练/验证损失。
-
TensorBoard记录:通过
writer.add_scalar
保存损失变化,便于可视化分析。
Configurations
config
contains hyper-parameters for training and the path to save your model.
device = 'cuda' if torch.cuda.is_available() else 'cpu'
config = {'seed': 5201314, # Your seed number, you can pick your lucky number. :)'select_all': True, # Whether to use all features.'valid_ratio': 0.2, # validation_size = train_size * valid_ratio'n_epochs': 3000, # Number of epochs. 'batch_size': 256, 'learning_rate': 1e-5, 'early_stop': 400, # If model has not improved for this many consecutive epochs, stop training. 'save_path': './models/model.ckpt' # Your model will be saved here.
}
Dataloader
Read data from files and set up training, validation, and testing sets. You do not need to modify this part.
# Set seed for reproducibility
same_seed(config['seed'])# train_data size: 2699 x 118 (id + 37 states + 16 features x 5 days)
# test_data size: 1078 x 117 (without last day's positive rate)
train_data, test_data = pd.read_csv('./covid.train.csv').values, pd.read_csv('./covid.test.csv').values
train_data, valid_data = train_valid_split(train_data, config['valid_ratio'], config['seed'])# Print out the data size.
print(f"""train_data size: {train_data.shape}
valid_data size: {valid_data.shape}
test_data size: {test_data.shape}""")# Select features
x_train, x_valid, x_test, y_train, y_valid = select_feat(train_data, valid_data, test_data, config['select_all'])# Print out the number of features.
print(f'number of features: {x_train.shape[1]}')train_dataset, valid_dataset, test_dataset = COVID19Dataset(x_train, y_train), \COVID19Dataset(x_valid, y_valid), \COVID19Dataset(x_test)# Pytorch data loader loads pytorch dataset into batches.
train_loader = DataLoader(train_dataset, batch_size=config['batch_size'], shuffle=True, pin_memory=True)
valid_loader = DataLoader(valid_dataset, batch_size=config['batch_size'], shuffle=True, pin_memory=True)
test_loader = DataLoader(test_dataset, batch_size=config['batch_size'], shuffle=False, pin_memory=True)
Start training!
model = My_Model(input_dim=x_train.shape[1]).to(device) # put your model and data on the same computation device.
trainer(train_loader, valid_loader, model, config, device)
上述内容来自李宏毅教授Machine Learning课程与deepseek