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 = True
    torch.backends.cudnn.benchmark = False
    np.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_size
    train_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 = y
        else:
            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_data

    if 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, 0

    for 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 += 1
            loss_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_loss
            torch.save(model.state_dict(), config['save_path']) # Save your best model
            print('Saving model with loss {:.3f}...'.format(best_loss))
            early_stop_count = 0
        else: 
            early_stop_count += 1

        if 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值