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)
时,进入divloss
的forward
函数,以masks
作为输入。
- 归一化操作的作用:
特征归一化:确保不同特征的尺度一致,有助于模型训练的稳定性和收敛速度。
文本处理:在处理词嵌入时,归一化词向量可以增加模型对角度距离的敏感性,有助于一些基于余弦相似度的计算。
18.跟着流程走到return loss
后,返回到第17步,用divloss
去接收多样性损失的计算结果。
19.我们可以查看各个部分的损失值,其中lamb=5
。return 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
方法,即SingleNet
的forward
操作。执行完后,观察x
和out
的维度,验证了
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个特征,但是此时x
与x_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_T
和masks
,x_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.model
的 forward
方法。因此,我们到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
关于模型参数的梯度。