MCM: Masked Cell Modeling for Anomaly Detection in Tabular Data(代码解读二)

Notice:此篇文章为代码解读二,内容为模型的训练过程。
Paper来源:点我跳转

代码结构布局:

在这里插入图片描述

main.py

import torch
import numpy as np
from Model.Trainer import Trainer
'''
此代码以Wbc数据集为例
'''
model_config = {
    'dataset_name': 'wbc',
    'data_dim': 30, # 数据维度,因数据集而异
    'epochs': 200,
    'learning_rate': 0.05,
    'sche_gamma': 0.98, # 学习率衰减系数
    'mask_num': 15, # 使用的遮罩(掩码)数量
    'lambda': 5,  # 不太确定,回头更新下,目前是正则化系数
    'device': 'cuda:0',
    'data_dir': 'Data/', # 数据存放的目录
    'runs': 1, # 运行次数
    'batch_size': 512, # 批处理大小
    'en_nlayers': 3, # 编码器的层数
    'de_nlayers': 3, # 解码器的层数
    'hidden_dim': 256, # 隐藏层的维度
    'z_dim': 128, # 中间向量维度
    'mask_nlayers': 3, # 掩码的层数
    'random_seed': 42, # 随机种子,用于结果复现
    'num_workers': 0 # 数据加载的线程数
}

if __name__ == "__main__":
		# 设置随机种子以确保结果的可复现性
    torch.manual_seed(model_config['random_seed'])
    torch.cuda.manual_seed(model_config['random_seed'])
    np.random.seed(model_config['random_seed'])
    

    if model_config['num_workers'] > 0:
		    # 'spawn'是一种启动进程的方式,它会在新的进程中重新启动Python解释器
        torch.multiprocessing.set_start_method('spawn')
    
    # result用于存储结果;
    # mse_rauc, mse_ap, mse_f1存储runs次运行中计算得到的AUC-ROC、AUC-PR、F1
    result = []
    runs = model_config['runs']
    mse_rauc, mse_ap, mse_f1 = np.zeros(runs), np.zeros(runs), np.zeros(runs)
    
    # 多次运行训练和评估过程。
    for i in range(runs):
		    # 构造训练器,传入模型参数——第1次跳转
        trainer = Trainer(run=i, model_config=model_config)
        # 训练
        trainer.training(model_config['epochs'])
        # 调用方法评估模型
        trainer.evaluate(mse_rauc, mse_ap, mse_f1)
    
    # 平均AUC-ROC、AUC-PR和f1分数,这是通过取之前存储的每次运行分数的均值来完成的。
    mean_mse_auc , mean_mse_pr , mean_mse_f1 = np.mean(mse_rauc), np.mean(mse_ap), np.mean(mse_f1)
    
    # 打印了模型平均性能指标
    print('##########################################################################')
    print("mse: average AUC-ROC: %.4f  average AUC-PR: %.4f"
          % (mean_mse_auc, mean_mse_pr))
    print("mse: average f1: %.4f" % (mean_mse_f1))
    
    # 保存实验结果,将结果保存在results目录下
    results_name = './results/' + model_config['dataset_name'] + '.txt'

    with open(results_name,'a') as file:
        file.write("epochs: %d lr: %.4f gamma: %.2f masks: %d lambda: %.1f " % (
            model_config['epochs'], model_config['learning_rate'], model_config['sche_gamma'], model_config['mask_num'], model_config['lambda']))
        file.write('\n')
        file.write("de_layer: %d  hidden_dim: %d z_dim: %d mask_layer: %d" % (model_config['de_nlayers'], model_config['hidden_dim'], model_config['z_dim'], model_config['mask_nlayers']))
        file.write('\n')
        file.write("mse: average AUC-ROC: %.4f  average AUC-PR: %.4f average f1: %.4f" % (
            mean_mse_auc, mean_mse_pr, mean_mse_f1))
        file.write('\n')

1.开始模型训练过程,执行trainer.training(200) ,跳转到Trainer.training 方法中。

utils.py

DataSet

DataLoader.py

from torch.utils.data import DataLoader
from DataSet.MyDataset import CsvDataset, MatDataset, NpzDataset

def get_dataloader(model_config: dict):
    dataset_name = model_config['dataset_name']

    if dataset_name in ['arrhythmia', 'breastw', 'cardio', 'glass', 'ionosphere', 'mammography', 'pima', 'satellite', 'satimage-2', 'shuttle', 'thyroid', 'wbc']:
        train_set = MatDataset(dataset_name, model_config['data_dim'], model_config['data_dir'], mode='train')
        test_set = MatDataset(dataset_name, model_config['data_dim'], model_config['data_dir'], mode='eval')

    elif dataset_name in ['census', 'campaign', 'cardiotocography', 'fraud', 'nslkdd', 'optdigits', 'pendigits', 'wine']:
        train_set = NpzDataset(dataset_name, model_config['data_dim'], model_config['data_dir'], mode='train')
        test_set = NpzDataset(dataset_name, model_config['data_dim'], model_config['data_dir'], mode='eval')
        
    else:
        train_set = CsvDataset(dataset_name, model_config['data_dim'], model_config['data_dir'], mode='train')
        test_set = CsvDataset(dataset_name, model_config['data_dim'], model_config['data_dir'], mode='eval')

    train_loader = DataLoader(train_set,
                              batch_size=model_config['batch_size'],
                              num_workers=model_config['num_workers'],
                              shuffle=False)
    test_loader = DataLoader(test_set, batch_size=model_config['batch_size'], shuffle=False)
    return train_loader, test_loader

MyDataset.py

import os
import csv
import numpy as np
import pandas as pd
from scipy import io
import torch
from torch.utils.data import Dataset

class CsvDataset(Dataset):
    def __init__(self, dataset_name: str, data_dim: int, data_dir: str, mode: str = 'train'):
        super(CsvDataset, self).__init__()
        x = []
        labels = []
        path = os.path.join(data_dir, dataset_name+'.csv')
        with (open(path, 'r')) as data_from:
            csv_reader = csv.reader(data_from)
            for i in csv_reader:
                x.append(i[0:data_dim])
                labels.append(i[data_dim])

        for i in range(len(x)):
            for j in range(data_dim):
                x[i][j] = float(x[i][j])
        for i in range(len(labels)):
            labels[i] = float(labels[i])

        data = np.array(x)
        target = np.array(labels)
        inlier_indices = np.where(target == 0)[0]
        outlier_inices = np.where(target == 1)[0]
        train_data, train_label, test_data, test_label = train_test_split(data[inlier_indices], data[outlier_inices])
        if mode == 'train':
            self.data = torch.Tensor(train_data)
            self.targets = torch.Tensor(train_label)
        else:
            self.data = torch.Tensor(test_data)
            self.targets = torch.Tensor(test_label)
        print(len(self.data))

    def __getitem__(self, item):
        return self.data[item], self.targets[item]

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

class MatDataset(Dataset):
    def __init__(self, dataset_name: str, data_dim: int, data_dir: str, mode: str = 'train'):
		    # 调用了父类 Dataset 的构造函数,确保父类的初始化代码也被执行。
        super(MatDataset, self).__init__()
        
        # 数据集名称和数据目录路径组合成一个完整的文件路径:path='Data/wbc.mat'
        path = os.path.join(data_dir, dataset_name + '.mat')
        
        # 使用 io.loadmat 函数从MATLAB格式的文件中加载数据。
        data = io.loadmat(path)
        
        # 提取样本数据,数据在matlab文件中以 ‘X’ 键值对的形式存在
        samples = data['X']
        
        # 提取标签数据,将标签数据转换为整数类型并将其展平
        labels = ((data['y']).astype(int)).reshape(-1)

        # 提取内点(inliers),即标签为0的样本。
        inliers = samples[labels == 0]
        
        # 提取异常点(outliers),即标签为1的样本。
        outliers = samples[labels == 1]
        
        # 使用 train_test_split 函数将内点和异常点数据分割为训练集和测试集
        train_data, train_label, test_data, test_label = train_test_split(inliers, outliers)
        if mode == 'train':
		        
		        # 将训练数据,训练标签转换为 PyTorch 张量
            self.data = torch.Tensor(train_data)
            self.targets =torch.Tensor(train_label)
        else:
		        
		        # # 将测试数据,测试标签转换为 PyTorch 张量
            self.data = torch.Tensor(test_data)
            self.targets = torch.Tensor(test_label)

    def __getitem__(self, item):
        return self.data[item], self.targets[item]

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

class NpzDataset(Dataset):
    def __init__(self, dataset_name: str, data_dim: int, data_dir: str, mode: str = 'train'):
        super(NpzDataset, self).__init__()
        path = os.path.join(data_dir, dataset_name+'.npz')
        data=np.load(path)  
        samples = data['X']
        labels = ((data['y']).astype(np.int)).reshape(-1)

        inliers = samples[labels == 0]
        outliers = samples[labels == 1]
        train_data, train_label, test_data, test_label = train_test_split(inliers, outliers)
        if mode == 'train':
            self.data = torch.Tensor(train_data)
            self.targets =torch.Tensor(train_label)
        else:
            self.data = torch.Tensor(test_data)
            self.targets = torch.Tensor(test_label)

    def __getitem__(self, item):
        return self.data[item], self.targets[item]

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

    
def train_test_split(inliers, outliers):
		
		# 将每个数据集的正常数据随机分成两半。训练集由一半的正常数据组成,而测试数据集由另一半的正常数据和所有异常实例组成。
    num_split = len(inliers) // 2
    train_data = inliers[:num_split]
    train_label = np.zeros(num_split)
    test_data = np.concatenate([inliers[num_split:], outliers], 0)

    test_label = np.zeros(test_data.shape[0])
    test_label[num_split:] = 1
    return train_data, train_label, test_data, test_label

Model

Loss.py

import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F

class LossFunction(nn.Module):
    def __init__(self, model_config):
        super(LossFunction, self).__init__()
        self.mask_num = model_config['mask_num']
        self.divloss = DiversityMask()
        self.lamb = model_config['lambda']

    def forward(self, x_input, x_pred, masks):
		    # x_input.shape=torch.Size([178, 30])
		    # x_pred.shape=torch.Size([178, 15, 30])
		    # masks.shape=torch.Size([178, 15, 30])
		    
		    # x_input.unsqueeze(1):[178,30]——>[178, 1, 30]
		    # repeat(1, self.mask_num, 1):重复15次,直到x_input=[178,15,30]
        x_input = x_input.unsqueeze(1).repeat(1, self.mask_num, 1)
        
        # 重构-原数据,即差值
        sub_result = x_pred - x_input
        
        # p=2:使用 L2 范数计算范数
        # dim=2:沿着张量的第三个维度(axis=2)计算范数
        # mse是每个样本的每个预测值和实际值差的平方和的平方根。第三维30个差值先平方后相加再开根,得到一个值,mse.shape=torch.Size([178, 15])
        mse = torch.norm(sub_result, p=2, dim=2)
        
        # 沿着每个样本的所有预测计算MSE的平均值,keepdim=True保持结果的形状为[178, 1],每个样本15个mse合成一个。
        mse_score = torch.mean(mse, dim=1, keepdim=True)
        
        # 计算所有样本的MSE平均值,结果是一个标量。
        e = torch.mean(mse_score)
        
        # 调用自定义的divloss方法,将未经过处理的masks作为输入,计算多样性损失
        divloss = self.divloss(masks)
        
        # 计算最终的损失值,通常是均方误差的均值加上一个正则化项(如除法损失)的加权和
        loss = torch.mean(e) + self.lamb*torch.mean(divloss)
        return loss, torch.mean(e), torch.mean(divloss)

class DiversityMask(nn.Module):
    def __init__(self,temperature=0.1):
        super(DiversityMask, self).__init__()
        self.temp = temperature
        
    # 有一个必须的参数z和一个可选的布尔参数eval
    def forward(self,z,eval=False):
		    
		    # 沿着最后一个维度(dim=-1)进行归一化(L2范数),意味着每个[178, 15]矩阵内部30个元素会被归一化,使每个元素的L2范数(平方和)为1。归一化后仍然为[178,15,30]
        z = F.normalize(z, p=2, dim=-1)
        
        # z.shape=torch.Size([178, 15, 30])
        batch_size, mask_num, z_dim = z.shape
        
        # z.permute(0, 2, 1)将第二维和第三维进行互换,除法可以看作是对特征向量的一种缩放。但是这里和原文的公式5有区别,按照原文的公式,这里的代码应该是:回头运行一下比较一下结果
        # sim_matrix = torch.exp(torch.matmul(z, z.permute(0, 2, 1)) / self.temp)
        # sim_matrix.shape=[178,15,15]
        sim_matrix = torch.exp(torch.matmul(z, z.permute(0, 2, 1) / self.temp))
        
        # torch.ones_like(sim_matrix):创建一个与sim_matrix形状相同的张量,初始值全为1,shape=[178,15,15],确保新创建的张量(无论是全1的张量还是单位矩阵)与输入张量z具有相同的设备。
        # torch.eye(mask_num):创建一个mask_num×mask_num的单位矩阵(对角线元素为1,其余元素为0)
        # unsqueeze(0):将单位矩阵增加一个维度,使其从 [15, 15] 变为 [1, 15, 15]
        # bool():将上三角矩阵中的元素转换为布尔类型,0变为False,1变为True
        # mask:用于排除相似度矩阵中的对角线元素。
        # a=torch.ones_like(sim_matrix).to(z)=[178,15,15];b=torch.eye(mask_num).unsqueeze(0).to(z)=[1,15,15],较小的张量b被广播到较大的张量的形状,达到互相匹配,mask是大小为[178,15,15]的布尔型张量
        mask = (torch.ones_like(sim_matrix).to(z) - torch.eye(mask_num).unsqueeze(0).to(z)).bool()
        
        # sim_matrix.masked_select(mask):值为True对应于sim_matrix中需要被选择的元素,而值为False对应的元素不会被选择。一共是178*15*14。.view重新调整张量的形状为[178,15,14]
        sim_matrix = sim_matrix.masked_select(mask).view(batch_size, mask_num, -1)
        
        # 对sim_matrix每一行求和,原先是[178,15,14],求和后结果存放到trans_matrix,trans_matrix.shape=[178,15]
        trans_matrix = sim_matrix.sum(-1)
        
        # 每个mask的非自身特征数量,即15-1=14
        K = mask_num - 1
        
        # 这里等会改一下看看,不太理解为什么要14,原文对M也没有做解释,可以把M设置为15看看
        scale = 1 / np.abs(K*np.log(1.0 / K))
        
        loss_tensor = torch.log(trans_matrix) * scale
        
        # 是否为评估阶段
        if eval:
            score = loss_tensor.sum(1)
            return score
        
        # 我们这里是训练阶段,因此跳转到这里
        else:
        
		        #计算总损失,得到的是一个[178]即每行数据的损失。然后返回。
            loss = loss_tensor.sum(1)
            return loss

16.forward中作为输入参数的维度:

x_input.shape
Out[2]: torch.Size([178, 30])
x_pred.shape
Out[3]: torch.Size([178, 15, 30])
masks.shape
Out[4]: torch.Size([178, 15, 30])

17.跟着forward中的步骤走就行了,执行到divloss = self.divloss(masks)时,进入divlossforward函数,以masks作为输入。

  • 归一化操作的作用:
    特征归一化:确保不同特征的尺度一致,有助于模型训练的稳定性和收敛速度。
    文本处理:在处理词嵌入时,归一化词向量可以增加模型对角度距离的敏感性,有助于一些基于余弦相似度的计算。

18.跟着流程走到return loss后,返回到第17步,用divloss去接收多样性损失的计算结果。

19.我们可以查看各个部分的损失值,其中lamb=5return loss, torch.mean(e), torch.mean(divloss)后,回到Trainer.py中。

torch.mean(divloss)
Out[40]: tensor(1.6865, device='cuda:0', grad_fn=<MeanBackward0>)
torch.mean(e)
Out[41]: tensor(1.2013, device='cuda:0', grad_fn=<MeanBackward0>)
loss
Out[42]: tensor(9.6339, device='cuda:0', grad_fn=<AddBackward0>)

MaskNets.py

import torch
import torch.nn as nn
import numpy as np 

class Generator(nn.Module):
    def __init__(self, model, config):
        super(Generator, self).__init__()
        self.masks = model._make_nets(config['data_dim'], config['mask_nlayers'], config['mask_num'])
        self.mask_num = config['mask_num']
        self.device = config['device']

    def forward(self, x):
    
		    # 将x转换为PyTorch张量,并将其移动到gpu上。张量类型为 float
        x = x.type(torch.FloatTensor).to(self.device)
        
        # 创建了一个与x 形状相似的新张量 x_T,它具有三个额外的维度。第一个维度的大小与 x 相同,第二个大小为15,第三个维度的大小与 x 最后一个维度相同。然后,将 x_T 移动到与 x 相同的设备上。
        x_T = torch.empty(x.shape[0], self.mask_num, x.shape[-1]).to(x)
        masks = []
        for i in range(self.mask_num):
		        # 第 i 个遮罩模块(self.masks[i])对输入数据 x 进行前向传播,并返回遮罩
            mask = self.masks[i](x)
            
            # 将mask添加到masks中,并将其展平为一个新的维度。
            masks.append(mask.unsqueeze(1))
            mask = torch.sigmoid(mask)
            
            # 将激活后的mask乘以原始输入数据x,并将结果存储在x_T的第i个维度中。
            x_T[:, i] = mask * x
            
        # 将列表 masks 中的所有张量沿着第二个维度(axis=1)进行拼接。第一维度不变。
        masks = torch.cat(masks, axis=1)
        return x_T, masks

class SingleNet(nn.Module):
    def __init__(self, x_dim, h_dim, num_layers):
        super(SingleNet, self).__init__()
        net = []
        input_dim = x_dim
        for _ in range(num_layers-1):
            net.append(nn.Linear(input_dim,h_dim,bias=False))
            net.append(nn.ReLU())
            input_dim= h_dim
        net.append(nn.Linear(input_dim,x_dim,bias=False))
        self.net = nn.Sequential(*net)

    def forward(self, x):
        out = self.net(x)
        return out

class MultiNets():
    def _make_nets(self, x_dim, mask_nlayers, mask_num):
        multinets = nn.ModuleList(
            [SingleNet(x_dim, x_dim, mask_nlayers) for _ in range(mask_num)])
        return multinets

6.把x_T拓展到[178, 15, 30]大小的张量,即包含 178 个批次的数据,每个批次有 15 个遮罩,每个遮罩处理的数据有 30 个特征。接下来,循环进行对每个遮罩的操作。如代码所示,mask = self.masks[i](x),对输入数据 x 进行前向传播,pytorch自动寻找self.masks[i] 实例的 forward 方法,即SingleNetforward操作。执行完后,观察xout的维度,验证了
self.net()的定义,输入是30维,输出也是30维,如下图:

在这里插入图片描述

7.此时out[i=0]是我们的第一个遮罩。返回到Generator的前向传播函数中,用mask去接收,得到的结果也与上图的形状一致,178个数据,每个数据30个特征,然后使用mask.unsqueeze(1)把原先大小为[178,30]的张量拓宽为[178,1,30] ,然后将其追加到masks列表中。随后用一个sigmoid函数激活。我们再来看masks的维度以及激活前后mask[0]的第0个值。注意:masks里存放的mask是未经激活的。

在这里插入图片描述

8.我们先来回顾一下所有数据格式的形状。x_T = torch.empty(x.shape[0], self.mask_num, x.shape[-1]).to(x)中,x_T一共是178个三维训练数据,每个数据有15个mask,每个mask对应30个特征,但是此时xx_T数值不对应,只是一个初始化过程,需要执行x_T[:, i] = mask * x后才有联系。

在这里插入图片描述

9.维度相同的张量才可执行元素乘积,当执行x_T[:, i] = mask * x时,每个元素的大小如下图所示。此步对应上一篇3.2节:G从X中学习信息,并输出一个与X同维度的遮罩矩阵M,即代码中的x_T

在这里插入图片描述

mask.shape
Out[29]: torch.Size([178, 30])
x.shape
Out[30]: torch.Size([178, 30])
x_T[0,0,0].shape
Out[32]: torch.Size([178, 30])
mask[0][0]
Out[33]: tensor(0.4984, device='cuda:0', grad_fn=<SelectBackward0>)
x[0][0]
Out[34]: tensor(0.3104, device='cuda:0')
x_T[:,0][0][0]
Out[35]: tensor(0.1547, device='cuda:0', grad_fn=<SelectBackward0>)
mask[0][1]
Out[36]: tensor(0.4995, device='cuda:0', grad_fn=<SelectBackward0>)
x[0][1]
Out[37]: tensor(0.1573, device='cuda:0')
x_T[:,0][0][1]
Out[38]: tensor(0.0785, device='cuda:0', grad_fn=<SelectBackward0>)

10.for i in range(self.mask_num):直到循环结束,遍历15次。masks列表里存放着15个未激活的mask,使用masks = torch.cat(masks, axis=1)进行拼接,随后返回x_Tmasksx_T是元素乘积后的结果,masks是经过Generator 后未处理的遮罩矩阵。返回到第5步。

Model.py

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
from Model.MaskNets import MultiNets, Generator

class MCM(nn.Module):
    def __init__(self, model_config):
        super(MCM, self).__init__()
        self.data_dim = model_config['data_dim']
        self.hidden_dim = model_config['hidden_dim']
        self.z_dim = model_config['z_dim']
        self.mask_num = model_config['mask_num']
        self.en_nlayers = model_config['en_nlayers']
        self.de_nlayers = model_config['de_nlayers']
        self.maskmodel = Generator(MultiNets(), model_config)

        encoder = []
        encoder_dim = self.data_dim
        for _ in range(self.en_nlayers-1):
            encoder.append(nn.Linear(encoder_dim,self.hidden_dim,bias=False))
            encoder.append(nn.LeakyReLU(0.2, inplace=True))
            encoder_dim = self.hidden_dim

        encoder.append(nn.Linear(encoder_dim,self.z_dim,bias=False))
        self.encoder = nn.Sequential(*encoder)

        decoder = []
        decoder_dim = self.z_dim
        for _ in range(self.de_nlayers-1):
            decoder.append(nn.Linear(decoder_dim,self.hidden_dim,bias=False))
            decoder.append(nn.LeakyReLU(0.2, inplace=True))
            decoder_dim = self.hidden_dim

        decoder.append(nn.Linear(decoder_dim,self.data_dim,bias=False))
        self.decoder = nn.Sequential(*decoder)

    def forward(self, x_input):
		    
		    # 接收参数x_T,masks。x_mask.shape=torch.Size([178, 15, 30])
        x_mask, masks = self.maskmodel(x_input)
        
        # B=178;T=15,D=30
        B, T, D = x_mask.shape
        
        # 将x_mask重塑为一个二维张量,第一个维大小是178*15,第二个维度的大小是30。这样做是为了使x_mask的形状与self.encoder方法的输入形状匹配。
        x_mask = x_mask.reshape(B*T, D)
        
        # encoder后的特征表示/特征向量,存储到z。
        z = self.encoder(x_mask)
        
        # 重构结果存储到x_pred。
        x_pred = self.decoder(z)
        
        # 将z重塑为一个三维张量,使其形状与x_input匹配。第一个维度是x_input.shape[0],第二个维度是self.mask_num,第三个维度是z.shape[-1]。
        z = z.reshape(x_input.shape[0], self.mask_num, z.shape[-1])
        
        # 
        x_pred = x_pred.reshape(x_input.shape[0], self.mask_num, x_input.shape[-1])
        return x_pred, z, masks

    def print_weight(self, x_input):
        x_input = Variable(x_input, requires_grad=False)
        z = self.encoder(x_input)
        fea_mem = self.fea_mem(z)
        fea_att_w = fea_mem['att']
        out = torch.max(fea_att_w, dim=0).view(8, 8).detach().cpu().numpy()
        return out

5.一进来就是一个嵌套,把训练数据放入遮罩生成器Generator,对应代码self.maskmodel 中,打开Model/MaskNets.py观察其前向传播函数(此时输入仍然为178个30维的数据)。续第10步,使用x_mask,masks去接收maskmodel传递回来的参数x_T,masks

11.x_mask = x_mask.reshape(B*T, D):观察维度。

x_mask.shape
Out[51]: torch.Size([2670, 30])

12.z = z.reshape(x_input.shape[0], self.mask_num, z.shape[-1])

x_pred = x_pred.reshape(x_input.shape[0], self.mask_num, x_input.shape[-1]):观察数值。

z.shape
Out[51]: torch.Size([2670, 128])
x_input.shape[0]
Out[52]: 178
self.mask_num
Out[53]: 15
z.shape[-1]
Out[54]: 128
z.shape
Out[55]: torch.Size([178, 15, 128])

13.return x_pred, z, masks 结束!返回到第3步。

x_pred.shape
Out[58]: torch.Size([178, 15, 30])
z.shape
Out[59]: torch.Size([178, 15, 128])
masks.shape
Out[60]: torch.Size([178, 15, 30])

Score.py

Trainer.py

import torch
import torch.optim as optim
from DataSet.DataLoader import get_dataloader
from Model.Model import MCM
from Model.Loss import LossFunction
from Model.Score import ScoreFunction
from utils import aucPerformance, get_logger, F1Performance

class Trainer(object):
    def __init__(self, run: int, model_config: dict):
        self.run = run
        self.sche_gamma = model_config['sche_gamma']
        self.device = model_config['device']
        self.learning_rate = model_config['learning_rate']
        self.model = MCM(model_config).to(self.device)
        self.loss_fuc = LossFunction(model_config).to(self.device)
        self.score_func = ScoreFunction(model_config).to(self.device)
        self.train_loader, self.test_loader = get_dataloader(model_config)

    def training(self, epochs):
				# 创建了一个日志记录器 train_logger,它将日志信息写入到名为 'train_log.log' 的文件中 		    
        train_logger = get_logger('train_log.log')
        
        # 创建了一个优化器 optimizer,使用 Adam 算法来更新模型参数。weight_decay是权重衰减,用于防止过拟合。
        optimizer = optim.Adam(self.model.parameters(), lr=self.learning_rate, weight_decay=1e-5)
        
        # 创建了一个学习率调度器 scheduler,使用指数衰减策略来调整学习率。gamma 是一个超参数,决定了学习率减少的速度。
        scheduler = optim.lr_scheduler.ExponentialLR(optimizer, gamma=self.sche_gamma)
        
        # 设置模型的训练模式
        self.model.train()
        print("Training Start.")
        
        # 初始值设置的损失通常远大于实际的最小损失值。
        min_loss = 100
        for epoch in range(epochs):
            for step, (x_input, y_label) in enumerate(self.train_loader):
            
		            # 把获取到的x_input移动到gpu上
                x_input = x_input.to(self.device)
                
                # 开始训练,,把训练数据输入到模型里面去。这是训练的起点。
                x_pred, z, masks = self.model(x_input)
                
                # 计算损失
                loss, mse, divloss = self.loss_fuc(x_input, x_pred, masks)
                
                # 将优化器中所有梯度清零。反向传播过程中,梯度是用来更新模型参数的。在每次迭代之前,将梯度归零可以确保梯度只包含当前批次的数据信息,而不受之前的批次数据的影响。
                optimizer.zero_grad()
                
                # 计算loss关于模型参数的梯度
                loss.backward()
                
                # 使用计算出的梯度来更新模型的参数。
                optimizer.step()
                
            # 在每个epoch结束后更新学习率调度器scheduler的状态,根据预定的策略调整学习率。
            scheduler.step()
            
            # 记录训练信息
            info = 'Epoch:[{}]\t loss={:.4f}\t mse={:.4f}\t divloss={:.4f}\t'
            
            # 使用info模板记录当前epoch的训练信息,并使用 train_logger 将信息输出到日志中。.cpu() 方法用于将数据从计算设备移动到CPU,以便打印或存储。
            train_logger.info(info.format(epoch,loss.cpu(),mse.cpu(),divloss.cpu()))
            
            # 如果当前loss小于之前的min_loss,保存当前模型参数到‘model.pth’并更新min_loss
            if loss < min_loss:
                torch.save(self.model, 'model.pth')
                min_loss = loss
        print("Training complete.")
        
        # 清除train_logger的所有处理器,释放资源或避免日志重复
        train_logger.handlers.clear()

    def evaluate(self, mse_rauc, mse_ap, mse_f1):
        model = torch.load('model.pth')
        model.eval()
        mse_score, test_label = [], []
        for step, (x_input, y_label) in enumerate(self.test_loader):
            x_input = x_input.to(self.device)
            x_pred, z, masks = self.model(x_input)
            mse_batch = self.score_func(x_input, x_pred)
            mse_batch = mse_batch.data.cpu()
            mse_score.append(mse_batch)
            test_label.append(y_label)
        mse_score = torch.cat(mse_score, axis=0).numpy()
        test_label = torch.cat(test_label, axis=0).numpy()
        mse_rauc[self.run], mse_ap[self.run] = aucPerformance(mse_score, test_label)
        mse_f1[self.run] = F1Performance(mse_score, test_label)

2.执行training方法,代码解释看代码段,执行到for step, (x_input, y_label) in enumerate(self.train_loader):的时候,step代表循环的索引,(x_input, y_label)是获取到的元组,大小如下图所示,:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3.执行x_pred, z, masks = self.model(x_input),开始模型训练,返回x_pred, z, masks预测结果,编码器解构特征和掩码。

4.我们已知模型的输入是x_input,由于前面的初始化已经把模型参数定义完成,执行self.model 实际上是调用了模型self.modelforward 方法。因此,我们到Model/Model.py里观看模型前向传播过程

14.使用x_pred, z, masks去接收从整个模型训练后返回的参数。与第3步形成闭环。第一批次的训练到这里就结束了。

15.接下来深入到loss_fuc板块去观察损失函数计算过程。已知传递的参数是输入的原数据x_input、激活了的mask和原数据x相乘后得到的x_mask去作为encoder的输入,从而decoder出的x_prev,以及没经过任何处理的masks矩阵。

16.使用loss, mse, divloss去接收参数。继续往下执行,使用loss.backward()执行反向传播,计算 loss 关于模型参数的梯度。

  • 12
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值