深度学习Day2 利用深度学习模型预测COVID-19阳性病例

今天的内容感觉好难,代码我跟着一行行写的还是运行不了,调了两个小时也不行,心态崩了我可能就不该初试考那么高。

破罐子破摔吧,把老师课上讲的代码尽量搞懂。

在开始学习代码之前我想先记录一下老师课上讲的概念。

首先我们要先明确机器学习的目的是什么?

答:为了得到一个好的模型,然后得到一个尽量正确的输出值y。

工作内容是什么?

答:我们有一批数据x,然后我们有准确的y,让x通过模型得到预测值y^,然后令y-y^作差后得到差距,然后训练模型,得到更好的预测值y^。

具体的训练过程为:对于所有数据,我们每次取一批出来

for batch_x,batch_y in trainloader,经过f得到y,y-y^=loss,然后更新模型,一个批次就更新一个模型,直到这次for循环把全部数据都取完一轮,这就完成了一轮epoch。

for epoch in epochs,这样我们就得到了一个很不错的模型

下面来解析代码。

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个最佳特征的函数
    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]                  # 返回选中列的特征和他们的下标。

get_feature_inportance的目的注释上有写,简单说下为什么定义这个函数。

是因为数据通常包含大量特征,但并非所有特征都有效,所以我们需要通过统计学中的卡方检验评估每个特征的重要性,然后返回对模型最有价值的特征子集,以此提升精准度。

class covidDataset(Dataset):
    def __init__(self, path, mode="train", feature_dim=5, all_feature=False):
        with open(path, 'r') as f:#将文件打开
            csv_data = list(csv.reader(f))#将读取到的文件转换成列表并存储
            column = csv_data[0]#提取列名
            x = np.array(csv_data)[1:,1:-1].astype(float)     # 1: 第一行后面的,   1:-1
            #定义特征:跳过首列和末列,因为都是些无用的编号
            y = np.array(csv_data)[1:,-1].astype(float)
            #定义标签:最后一列        
            if all_feature:#决定使用全部特征还是筛选后的特征
                col_indices = np.array([i for i in range(0,93)])                  # 若全选,则使用全部93个特征。
            else:
                _, col_indices = get_feature_importance(x, y, feature_dim, column)      # 选重要的dim列
            col_indices = col_indices.tolist()             # col_indices 从array 转为列表。
            csv_data = np.array(csv_data[1:])[:,1:].astype(float)       #取csvdata从第二行开始, 第二列开始的数据,并转为float
            #下面根据mode来划分数据集
            if mode == 'train':                                # 训练数据逢5选4, 记录他们的所在行
                indices = [i for i in range(len(csv_data)) if i % 5 != 0]       #1,2,3,4, 6,7,8,9
                self.y = torch.tensor(csv_data[indices,-1])      # 训练标签是csvdata的最后一列。 要转化为tensor型
            elif mode == 'val':               # 验证数据逢5选1, 记录他们的所在列
                indices = [i for i in range(len(csv_data)) if i % 5 == 0]
                # data = torch.tensor(csv_data[indices,col_indices])
                self.y = torch.tensor(csv_data[indices,-1])        # 验证标签是csvdata的最后一列。 要转化为tensor型
            else:
                indices = [i for i in range(len(csv_data))]     # 测试集只有数据
                # data = torch.tensor(csv_data[indices,col_indices])
            data = torch.tensor(csv_data[indices, :])           # 根据选中行取 X , 即模型的输入特征,转换为张量
            self.data = data[:, col_indices]                   #  col_indices 表示了重要的K列, 根据重要性, 选中k列。保留选中特征。
            self.mode = mode                                   # 表示当前数据集的模式

            self.data = (self.data - self.data.mean(dim=0,keepdim=True)) / self.data.std(dim=0,keepdim=True)  # 对数据进行列归一化 0正太分布
            assert feature_dim == self.data.shape[1]                   # 判断数据的列数是否为规定的dim列, 要不然就报错。

            print('Finished reading the {} set of COVID19 Dataset ({} samples found, each dim = {})'
                  .format(mode, len(self.data), feature_dim))             # 打印读了多少数据

covidDataset继承自dataset类,用于加载、预处理并划分COVID-19的数据。

作用:从csv中读取数据,并对数据进行加载预处理和划分

class myNet(nn.Module):
class myNet(nn.Module):#定义一个继承自Pytorch nn.Module的神经网络模型
    def __init__(self, inDim):#输入特征的维度
        super(myNet,self).__init__()
        self.fc1 = nn.Linear(inDim, 128)              # 全连接,输入indim维,输出128维,比如输入是 5 个特征,输出是 128 个神经元
        self.relu = nn.ReLU()                        # 激活函数 ,添加非线性,增强模型表达能力
        # self.fc3 = nn.Linear(128, 128)
        self.fc2 = nn.Linear(128,1)                     # 全连接         设计模型架构。 他没有数据输入128维,输出一维,适用于回归任务

    def forward(self, x):                     #forward, 即模型前向过程
        x = self.fc1(x)#输入通过全连接层1
        x = self.relu(x)#应用relu激活
        # x = self.fc3(x)
        x = self.fc2(x)#通过全连接层2
        if len(x.size()) > 1:统一模型输出的形状,确保与标签数据的形状匹配
            return x.squeeze(1)
        else:
            return x

def train_val(model, trainloader, valloader,optimizer, loss, epoch, device, save_):
    # 定义训练和验证函数,接受模型、数据加载器、优化器、损失函数等参数
    # trainloader = DataLoader(trainset,batch_size=batch,shuffle=True)
    # valloader = DataLoader(valset,batch_size=batch,shuffle=True)
    model = model.to(device)#将模型移动到指定设备(CPU/GPU)                # 模型和数据 ,要在一个设备上。  cpu - gpu
    plt_train_loss = []
    plt_val_loss = []#初始化列表,记录每轮训练和验证的平均损失。
    val_rel = []
    min_val_loss = 100000                 # 记录训练验证loss 以及验证loss和结果

    for i in range(epoch):                 # 训练epoch 轮
        start_time = time.time()             # 记录开始时间
        model.train()                         # 模型设置为训练状态      结构
        train_loss = 0.0
        val_loss = 0.0#初始化当前轮次的训练和验证损失
        for data in trainloader:                     # 从训练集取一个batch的数据
            optimizer.zero_grad()                   # 梯度清0,防止梯度累计
            x, target = data[0].to(device), data[1].to(device)       # 将数据放到设备上
            pred = model(x)                          # 用模型前向传播得到预测数据
            bat_loss = loss(pred, target)       # 计算loss
            bat_loss.backward()                        # 梯度回传, 反向传播,计算梯度。
            optimizer.step()                            #用优化器更新模型。  轮到SGD出手了
            train_loss += bat_loss.detach().cpu().item()             #记录loss和

        plt_train_loss. append(train_loss/trainloader.dataset.__len__())   #记录loss到列表。注意是平均的loss ,因此要除以数据集长度。

        model.eval()                 # 模型设置为验证状态,禁用dropout层的训练行为
        with torch.no_grad():                    # 模型不再计算梯度
            for data in valloader:                      # 从验证集取一个batch的数据
                val_x , val_target = data[0].to(device), data[1].to(device)          # 将数据放到设备上
                val_pred = model(val_x)                 # 用模型预测数据
                val_bat_loss = loss(val_pred, val_target)          # 计算loss
                val_loss += val_bat_loss.detach().cpu().item()                  # 计算loss
                val_rel.append(val_pred)                 #记录预测结果
        if val_loss < min_val_loss:
            torch.save(model, save_)               #如果loss比之前的最小值小, 说明模型更优, 保存这个模型

        plt_val_loss.append(val_loss/valloader.dataset.__len__())  #记录loss到列表。注意是平均的loss ,因此要除以数据集长度。
        #
        print('[%03d/%03d] %2.2f sec(s) TrainLoss : %.6f | valLoss: %.6f' % \
              (i, epoch, time.time()-start_time, plt_train_loss[-1], plt_val_loss[-1])
              )              #打印训练结果。 注意python语法, %2.2f 表示小数位为2的浮点数, 后面可以对应。


        # print('[%03d/%03d] %2.2f sec(s) TrainLoss : %3.6f | valLoss: %.6f' % \
        #       (i, epoch, time.time()-start_time, 2210.2255411, plt_val_loss[-1])
        #       )              #打印训练结果。 注意python语法, %2.2f 表示小数位为2的浮点数, 后面可以对应。
    plt.plot(plt_train_loss)              # 画图, 向图中放入训练loss数据
    plt.plot(plt_val_loss)                # 画图, 向图中放入训练loss数据
    plt.title('loss')                      # 画图, 标题
    plt.legend(['train', 'val'])             # 画图, 图例
    plt.show()                                 # 画图, 展示

参数解释如下:

  • model:待训练的神经网络模型。
  • trainloader:训练集的数据加载器(DataLoader)。
  • valloader:验证集的数据加载器。
  • optimizer:优化器(如 SGD、Adam)。
  • loss:损失函数(如 MSE Loss)。
  • epoch:训练的总轮数。
  • device:计算设备(cpu 或 cuda)。
  • save_:模型保存路径

 

def evaluate(model_path, testset, rel_path ,device):#加载训练好的模型,对测试集进行预测,并将结果保存到CSV文件上
    model = torch.load(model_path).to(device)                     # 模型放到设备上。  加载模型
    testloader = DataLoader(testset, batch_size=1, shuffle=False)         # 将验证数据放入loader 验证时, 一般batch为1
    val_rel = []               #存储所有样本的预测值
    model.eval()               # 模型设置为验证状态
    with torch.no_grad():               # 模型不再计算梯度
        for data in testloader:                 # 从测试集取一个batch的数据
            x = data.to(device)                # 将数据放到设备上
            pred = model(x)                        # 用模型预测数据
            val_rel.append(pred.item())                #记录预测结果并存入列表val_rel
    print(val_rel)                                     #打印预测结果
    with open(rel_path, 'w') as f:                        #打开保存的文件
        csv_writer = csv.writer(f)                           #初始化一个写文件器 writer
        csv_writer.writerow(['id','tested_positive'])         #在第一行写上 “id” 和 “tested_positive”
        for i in range(len(testset)):                           # 把测试结果的每一行放入输出的excel表中。
            csv_writer.writerow([str(i),str(val_rel[i])])
    print("rel已经保存到"+ rel_path)

参数解释如下:

  • model_path:保存的模型文件路径(如 model.pth)。
  • testset:测试数据集(covidDataset 实例)。
  • rel_path:预测结果保存路径(如 pred.csv)。
  • device:计算设备(cpu 或 cuda)。

all_col = False            #是否使用所有的列
device = 'cuda' if torch.cuda.is_available() else 'cpu'       #选择使用cpu还是gpu计算。
print(device)
train_path = 'covid.train.csv'                     # 训练数据路径(包含特征和标签)
test_path = 'covid.test.csv'              # 测试数据路径(仅特征)
file = pd.read_csv(train_path)
file.head()                    # 用pandas 看看数据长啥样

if all_col == True:
    feature_dim = 93#选取全部93个特征
else:
    feature_dim = 6              #是否使用所有的列
#加载训练集、验证集和测试集
trainset = covidDataset(train_path,'train', feature_dim=feature_dim, all_feature=all_col)
valset = covidDataset(train_path,'val', feature_dim=feature_dim, all_feature=all_col)
testset = covidDataset(test_path,'test', feature_dim=feature_dim, all_feature=all_col)   #读取训练, 验证,测试数据

 作用:构建了预测模型,并且通过特征选择和神经网络的训练生成测试集预测结果存储到CSV文件中


loss =  nn.MSELoss()          # 定义mseloss 即 计算预测值与真实值平方差损失,

config = {                          #配置训练参数
    'n_epochs': 50,                # maximum number of epochs
    'batch_size': 32,               # mini-batch size for dataloader
    'optimizer': 'SGD',              # optimization algorithm (optimizer in torch.optim)
    'optim_hparas': {                # hyper-parameters for the optimizer (depends on which optimizer you are using)
        'lr': 0.0001,                 # learning rate of SGD
        'momentum': 0.9              # momentum for SGD
    },
    'early_stop': 200,               # early stopping epochs (the number epochs since your model's last improvement)
    'save_path': 'model_save/model.pth',  # your model will be saved here
}

model = myNet(feature_dim).to(device)                      # 实例化模型,输入维度为feature_dim,并将模型移到GPU/CPU

optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)             # 定义优化器  动量0.09,优化目标位模型参数
trainloader = DataLoader(trainset, batch_size=config['batch_size'], shuffle=True)#创建训练集和验证集的 DataLoader,批量大小 32,训练集数据随机打乱(shuffle=True)
valloader = DataLoader(valset, batch_size=config['batch_size'], shuffle=True)  # 将数据装入loader 方便取一个batch的数据

train_val(model, trainloader, valloader, optimizer, loss, config['n_epochs'], device,save_=config['save_path'])  # 训练

#执行模型训练与验证
evaluate(config['save_path'], testset, 'pred.csv', device)           # 加载最佳模型

作用:配置并训练了一个基于神经网络的COVID-19的阳性病例预测模型,通过SGD优化器进行参数更新,使用均方误差作为损失函数,最终保存最优模型并生成预测集预测结果

几个听课和分析代码时遇到的问题:

为什么训练要清除梯度累积?

答:每个训练批次的梯度应该独立反应当前数据对模型参数的调整方向,如果不清除梯度,多个批次的梯度会累积相加,导致参数偏离当前批次的实际梯度方向

为什么验证状态时不再计算梯度?

答:梯度的计算设计复杂的反向传播,占用大量内存和计算时间,而验证模型仅需要评测模型的性能并不需要更新参数,所以禁止计算梯度可以减少资源的损耗

梯度为什么会累积?

梯度累积其实是深度学习框架(pytorch和tensorflow)的设计特性,框架的默认行为是每次反向传播时,计算的梯度会累加到模型参数的grad属性中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值