李哥深度学习三:回归项目实战

本期的任务是完成对于kaggle上一个回归项目的实战并进行了适当优化,链接:https://www.kaggle.com/competitions/ml2021spring-hw1/submissions

1. 分析需求并导入相关库:

由项目给出的covid.train.csv,covid.test.csv以及任务要求可知,我们的目标是根据前两天的地区感染情况进而推测出第三天的地区感染情况并以合适的格式输出(.csv文件大概可以理解为是一个table表格)

导入可能会用到的库

import time
import torch
import numpy as np
import csv
import pandas as pd
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
import matplotlib
from matplotlib import pyplot as plt
from torch.utils.data import DataLoader, Dataset
import torch.nn as nn
from torch import optim
from sklearn.feature_selection import SelectKBest, chi2

2. 数据清洗与处理:

class CovidDataset(Dataset):
    def __init__(self,file_path,mode="train",all_feature=False, feature_dim=6):
        with open(file_path,"r") as f:
            ori_data = list(csv.reader(f))
            column = ori_data[0]
            csv_data = np.array(ori_data[1:])[:,1:].astype(float)

        feature = np.array(ori_data[1:])[:,1:-1]
        label_data = np.array(ori_data[1:])[:,-1]
        if all_feature: #抽取相关系数最好的特征
            col = np.array([i for i in range(0, 93)])
        else:
            _, col = get_feature_importance(feature, label_data, feature_dim, column)
            # X_new = get_feature_importance_with_pca(feature, feature_dim, column)
        col = col.tolist() #化为列表

        if mode == "train": #逢5取1的方式分割训练集与验证集
            indices = [i for i in range(len(csv_data)) if i%5!=0]
            self.y = torch.tensor(csv_data[indices,-1])
            data = torch.tensor(csv_data[indices, :-1])
        elif mode == "val":
            indices = [i for i in range(len(csv_data)) if i%5==0]
            self.y = torch.tensor(csv_data[indices,-1])
            data = torch.tensor(csv_data[indices, :-1])
        else:
            indices = [i for i in range(len(csv_data))]
            data = torch.tensor(csv_data[indices])

        data = data[:,col]
        self.data = (data - data.mean(dim = 0, keepdim = True))/data.std(dim = 0, keepdim = True)             #归一化,统一量纲
        self.mode = mode

    def __getitem__(self,idx): #取数
        if self.mode != "test":
            return self.data[idx].float(), self.y[idx].float() #32位,加速运算
        else:
            return self.data[idx].float()

    def __len__(self):
        return len(self.data)

3. 读取输入

train_file = "covid.train.csv" #训练集路径
test_file = "covid.test.csv" #测试集路径
#读取训练,验证,测试数据
train_dataset = CovidDataset(train_file,"train",all_feature=all_feature,feature_dim=feature_dim)
val_dataset = CovidDataset(train_file,"val",all_feature=all_feature,feature_dim=feature_dim)
test_dataset = CovidDataset(test_file,"test",all_feature=all_feature,feature_dim=feature_dim)

batchsize = 16
train_loader = DataLoader(train_dataset,batch_size=batchsize ,shuffle = True) #随机打乱取一批
val_loader = DataLoader(val_dataset,batch_size=batchsize ,shuffle = True)
test_loader = DataLoader(test_dataset,batch_size=1 ,shuffle = False)#测试集要一一对应,选批次为1训练

4. 定义模型

        注意,激活函数是神经网络中的关键组件,其作用是引入非线性。若没有激活函数,神经网络无论多少层,都只能表示线性变换,无法拟合复杂的非线性数据。

class MyModel(nn.Module):
    def __init__(self,inDim):
        super(MyModel, self).__init__() # 继承类
        self.fc1 = nn.Linear(inDim,64) #全连接
        self.relu1 = nn.ReLU() #选取ReLU函数作为激活函数
        self.fc2 = nn.Linear(64,1) #全连接

    def forward(self,x): #模型前向过程
        x = self.fc1(x)
        x = self.relu1(x)
        x = self.fc2(x)

        if len(x.size()) > 1:
            return x.squeeze(1) #调整向量大小

        return x

模型中一般含有初始化与前向传播两个操作

5. 训练模型:

def train_val(model, train_loader, val_loader, device, epochs, optimizer, loss, save_path):
    model = model.to(device) #模型挂上设备
    plt_train_loss = []  #记录所有轮次的loss
    plt_val_loss = []

    min_val_loss = 9999999999999

    for epoch in range(epochs):     #开始训练
        train_loss = 0.0
        val_loss = 0.0
        start_time = time.time() #记录开始时间

        model.train()   #模型调整为训练模式
        for batch_x, batch_y in train_loader:
            x, target = batch_x.to(device), batch_y.to(device)
            pred = model(x)
            train_batch_loss = loss(pred, target,model)
            train_batch_loss.backward()
            optimizer.step() #更新模型
            optimizer.zero_grad() #清零
            train_loss += train_batch_loss.cpu().item()
        plt_train_loss.append(train_loss/train_loader.__len__()) #记录平均的loss

        model.eval() #模型进入验证模式
        with torch.no_grad(): #验证集不计算梯度
            for batch_x, batch_y in val_loader: #批次处理数据
                x, target = batch_x.to(device), batch_y.to(device) #将数据挂上设备
                pred = model(x) #预测数据
                val_batch_loss = loss(pred, target,model) #计算loss
                val_loss += val_batch_loss.cpu().item()
        plt_val_loss.append(val_loss/val_loader.__len__())
        if val_loss < min_val_loss:
            torch.save(model,save_path) #保存更优模型
            min_val_loss = val_loss

        print("[%03d/%03d]  %2.2f secs Trainloss: %.6f Valloss: %.6f"% \
                  (epoch,epochs,time.time()-start_time,plt_train_loss[-1],plt_val_loss[-1])) #打印训练结果

    plt.plot(plt_train_loss) #画图
    plt.plot(plt_val_loss)
    plt.title("loss") #图的标题
    plt.legend(["train","val"]) #图例
    plt.show()

6. 定义测试函数

def evaluate(save_path,test_loader,device,rel_path):     #得出测试结果文件
    model = torch.load(save_path).to(device)
    rel = []
    with torch.no_grad(): #测试时也不计算梯度
        for x in test_loader:
            pred = model(x.to(device))
            rel.append(pred.cpu().item()) #记录预测结果
        print(rel)
        with open(rel_path,"w",newline='') as f:
            csvWriter = csv.writer(f)
            csvWriter.writerow(["id","tested_positive"]) #写文件
            for i, value in enumerate(rel): #enumerate返回索引及其对应值
                csvWriter.writerow([str(i),str(value)])
        print("文件已经保存到{}".format(rel_path))

7. 参数及设备设定

all_feature = False  #抽取主要特征
if all_feature:
    feature_dim = 93
else:
    feature_dim = 6

device = "cuda" if torch.cuda.is_available() else "cpu" #选择用cpu还是gpu计算
print(device)

config = { #相关参数放在一个字典中
    "lr":0.001,
    "epochs":20,
    "momentum":0.9,
    "save_path":"model_save/best_model.pth",
    "rel_path":"pred.csv"
}

8. 实例化模型,训练与测试

model = MyModel(inDim=feature_dim).to(device)  #实例化模型
loss = mseLoss_with_reg #获得正则化后的loss
optimizer = optim.SGD(model.parameters(), lr = config["lr"], momentum = config["momentum"] ) #定义优化器,momentum表示动量,可理解为惯性

train_val(model, train_loader, val_loader,device,config["epochs"],optimizer,loss,config["save_path"]) #训练

evaluate(config["save_path"],test_loader,device,config["rel_path"]) #测试

值得注意的是,第一版我的代码(此博客中不显示)中损失函数我用的是相关库中自带的loss函数

9. 提交结果并反思:

将预测结果pred.csv上交到kaggle上,得到如下结果:

而在kaggle上设有strong baseline的界线,可以帮助我们分析自己的模型性能

同时,李哥也提出了该模型在深度学习过程中属于入门级别,但是我们可以考虑几个优化项进而提高模型泛化能力

反思与优化:

A. 正则化处理损失函数

B. 相关系数、线性相关的选取(SelectKBest操作)

C. 主成分分析PCA

 A.正则化:

        所谓正则化,实际上是对处理损失函数的一种通过添加正则化项来限制模型复杂度、防止过拟合的技术。常见的正则化方法包括 L1 正则化、L2 正则化和 Elastic Net 正则化。选择合适的正则化方法和系数可以显著提升模型的泛化能力。

我们可以通过定义mseloss_with_reg函数替代原先库中自带的loss函数,进而优化我们的模型

代码实现:

def mseLoss_with_reg(pred, target, model): #正则化处理,通过在损失函数中添加一个正则化项防止过拟合,提高模型泛化能力
    loss = nn.MSELoss(reduction='mean')
    ''' Calculate loss '''
    regularization_loss = 0                    # 正则项
    for param in model.parameters():
        # TODO: you may implement L1/L2 regularization here
        # 使用L2正则项
        # regularization_loss += torch.sum(abs(param))
        regularization_loss += torch.sum(param ** 2)                  # 计算所有参数平方
    return loss(pred, target) + 0.00075 * regularization_loss             # 返回损失。

B. SelectKbest操作:

        它是一种简单有效的特征选择方法,通过统计测试筛选出对目标变量最有影响的特征。它适用于分类和回归问题,常用的统计测试方法包括卡方检验、F 检验和互信息等。通过减少特征维度,SelectKBest 可以提高模型的训练效率和泛化能力。

代码实现:

def get_feature_importance(feature_data, label_data, k =4,column = None):
    """
    此处省略 feature_data, label_data 的生成代码。
    如果是 CSV 文件,可通过 read_csv() 函数获得特征和标签。
    这个函数的目的是, 找到所有的特征中, 比较有用的k个特征, 并打印这些列的名字。
    """
    model = SelectKBest(chi2, k=k)      #定义一个选择k个最佳特征的函数
    feature_data = np.array(feature_data, dtype=np.float64)
    # label_data = np.array(label_data, dtype=np.float64)
    X_new = model.fit_transform(feature_data, label_data)   #用这个函数选择k个最佳特征
    #feature_data是特征数据,label_data是标签数据,该函数可以选择出k个特征
    print('x_new', X_new)
    scores = model.scores_                # scores即每一列与结果的相关性
    # 按重要性排序,选出最重要的 k 个
    indices = np.argsort(scores)[::-1]        #[::-1]表示反转一个列表或者矩阵。
    # argsort这个函数, 可以矩阵排序后的下标。 比如 indices[0]表示的是,scores中最小值的下标。

    if column:                            # 如果需要打印选中的列
        k_best_features = [column[i+1] for i in indices[0:k].tolist()]         # 选中这些列 打印
        print('k best features are: ',k_best_features)
    return X_new, indices[0:k]                  # 返回选中列的特征和他们的下标。

C. 主成分分析PCA:

        PCA(Principal Component Analysis,主成分分析)是一种常用的降维技术,它通过将高维数据投影到低维空间,保留数据的主要特征,同时减少数据的维度。PCA 的核心思想是找到数据中方差最大的方向(主成分),并将数据投影到这些方向上。

代码实现:

def get_feature_importance_with_pca(feature_data, k=4, column=None):
    """
    使用PCA进行特征降维,并找出对前k个主成分影响最大的原始特征。

    参数:
    feature_data (pd.DataFrame or np.ndarray): 特征数据。
    k (int): 选择的主成分数目,默认为4。
    column (list, optional): 特征名称列表。如果feature_data是DataFrame,则可以省略此参数。

    返回:
    X_new (np.ndarray): 降维后的特征数据。
    selected_features (list of lists): 对每个主成分影响最大的原始特征及其载荷。
    """
    # 如果提供了列名或feature_data是DataFrame,获取列名
    if column is None and hasattr(feature_data, 'columns'):
        column = feature_data.columns.tolist()
    elif column is None:
        raise ValueError("Column names must be provided if feature_data is not a DataFrame.")

    # 数据标准化
    scaler = StandardScaler()
    feature_data_scaled = scaler.fit_transform(feature_data)

    # 应用PCA
    pca = PCA(n_components=k)
    X_new = pca.fit_transform(feature_data_scaled)
    return X_new

同时附上在kaggle上的提交结果(依次使用了A+B, A+B+C)

由上图可知,A、B、C三种手段确实优化了我们模型的泛化能力

10. 总结:

        该项目的总体流程个人大概理解,但对于部分代码的编写能力还需要提升,希望日后回顾此博客时会有新的收获

### 关于深度学习图片分类的教程和资源 #### 神经网络基础与应用实例 对于初学者来说,了解神经网络的基础知识至关重要。专项课程中的第一门课涵盖了神经网络的核心概念以及构建方法,包括如何创建并训练一个用于识别猫的小型深度神经网络案例[^1]。 #### 传统机器学习对比 在探讨深度学习之前,理解其相对于传统机器学习的优势也很重要。传统上,为了使计算机能够识别特定对象(如狗),开发者需手动定义一系列复杂的特征提取过程;而在现代深度学习框架下,则无需如此繁琐的人工干预,模型可以直接从原始像素值中自动学到有用的表示形式[^2]。 #### 实际操作指南 当准备动手实践时,可以从加载预训练好的GoogLeNet模型入手。这不仅提供了快速入门的机会,而且有助于熟悉常用的工具和技术栈。具体而言,在MATLAB环境中启动“深度网络设计器”,并通过该平台导入所需的架构文件[^3]。 #### 数据预处理的重要性 值得注意的是,在正式开始训练前还应重视数据的质量控制环节。特别是针对图像类任务,建议利用Python库PIL来进行初步筛选——任何无法正常打开或是存在明显缺陷的照片都应当被排除在外,以免影响最终效果[^4]。 ```python from PIL import Image def check_image_quality(image_path): try: img = Image.open(image_path) # 尝试打开图片 img.verify() # 验证图片完整性 return True # 如果成功则返回True except (IOError, SyntaxError) as e: print(f'Bad file: {image_path}') # 打印错误信息 return False # 否则返回False ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值