一如既往,我无法决定我做什么,但是我可以决定我不做什么。
吃晚饭前我不打游戏,不打炉石,不打跑跑狼,不打欧陆战争3,不玩手机。然后今天我要戒欲节制,要想戒欲节制,那就需要运动,今天我要去跑步。离了个大谱,chatgpt没法用了,我现在没法读代码了,我去读读论文吧
1
main.py和dataloader.py都看的差不多了,现在我要看train.py
train_process = use_train(
config['train'], model, opts, dataloaders, save_folder_name)
train_process.train()
传了五个参数进去,这五个参数到底是什么,我需要解释清楚。
config[‘train’]就是训练模型时候要用到的参数,随着代码的执行会不断优化参数
train:
lr: 1.0e-4
weight_decay: 1.0e-4
epochs: 500
pool_ratio: 0.7
optimizer: adam
stepsize: 200
group_loss: true
sparsity_loss: true
sparsity_loss_weight: 0.5e-4
log_folder: result
# uniform or pearson
pure_gnn_graph: pearson
model包括两部分,一个是abide_PLS_Net.yaml中的model部分,另一部分是节点大小node_size是100,节点特征大小node_feature_size是111,时间序列大小timeseries_size是111
我感觉还不错,我专门改写了一小段代码来得到节点的值,节点特征的值,时间序列的值
import yaml
import numpy as np
config_filename = 'abide_PLSNet.yaml'
with open(config_filename) as f:
config = yaml.load(f, Loader=yaml.Loader)
dataset_config = config['data']
data = np.load(dataset_config["time_seires"], allow_pickle=True).item()
final_fc = data["timeseires"]
final_pearson = data["corr"]
_, _, timeseries = final_fc.shape
_, node_size, node_feature_size = final_pearson.shape
print(timeseries)
print(node_size)
print(node_feature_size)
再看看abide_PLSNet.yaml中的model部分
model:
type: PLSNet
extractor_type: attention
embedding_size: 8
window_size: 4
cnn_pool_size: 16
num_gru_layers: 4
dropout: 0.5
这个model写明是使用了注意力机制,还有cnn神经网络
第三部分是opts,它是一个优化器,里面包含两个主属性,一个是学习率0.0001,另一个是权重衰减0.0001。
dataloader这部分蛮难讲的,因为它的数据量太大了,具体来说分成训练数据集,验证数据集和测试训练集。
save_folder_name这个是最简单的,就是结果的保存路径
2
我看着看着代码脑子里突然有了想法,我应该去准备行测申论了,毕竟论文写再好也只是毕业但不能找工作。
我回到学校了,这学期很重要,不多说了,我开了个网页版番茄钟,还有15分钟。
做事要有始有终,我要把train这部分代码看完。
def __init__(self, train_config, model, optimizers, dataloaders, log_folder) -> None:
self.logger = Logger()
self.model = model.to(device)
self.train_dataloader, self.val_dataloader, self.test_dataloader = dataloaders
self.epochs = train_config['epochs']
self.optimizers = optimizers
self.best_acc = 0
self.best_model = None
self.best_acc_val = 0
self.best_auc_val = 0
self.loss_fn = torch.nn.CrossEntropyLoss(reduction='mean')
self.pool_ratio = train_config['pool_ratio']
self.group_loss = train_config['group_loss']
self.sparsity_loss = train_config['sparsity_loss']
self.sparsity_loss_weight = train_config['sparsity_loss_weight']
self.save_path = log_folder
self.save_learnable_graph = True
self.init_meters()
这部分代码简单来说就是初始化,关键在于初始化的这些对象有什么用
self.model = model.to(device)
像是这一句,就是把PSL这个模型的参数赋值给self.model。这就要涉及到一个问题,到底什么是神经网络。神经网络是干嘛的,它可以用来判断,可以用来预测,可以用来分类。我们给模型的是文字,是图像,是语音等等,但是模型能处理的是数组,是向量。所以在此模型中,神经网络的第一步就是把输入表示为一个8维的向量。这就是嵌入层的维度。那么接下来就是对这若干个八维的向量进行处理。
那么接下来的问题就是神经网络中为什么要有滑动窗口的大小,举个简单的例子。对语言进行处理,有这么两句话分别是:我叫李有福,我吃了饺子。如果每次只分析一个输入,那就是分析:我,叫,李,有,福。这样分析起来效果就不好,因为这个句子应该分为两部分一部分是我叫,另一部分是我有福。那这个时候就要同时分析两个字,有的句子可能要同时分析三个字,这样就有了滑动窗口的概念。有些输入要当成一个输入来分析才有效果,如果分开来看就完全无法理解,比如成语这种东西。
接下来就是pooling layer池化层这个概念,其实这个概念很拗口。我查阅了一下资料,其实池化这个东西有两个作用,一个是降低开销,另一个是防止过拟合。神经网络这个东西,要处理的数据量实在是太大了,谁家也没有超算,更多的科研底层工作者,只有一台笔记本电脑,要是跑代码一次跑一天,这谁受得了。而且很多的模型也不需要那么精确的处理,比如一幅画,从左到右,颜色逐渐变深。即便是把数据进行池化处理了,这个大特征依然是不变的。第二个就是模型有一定的泛化能力。什么是泛化能力,就是模型在A数据集上有很好的性能,拿到B数据集上性能依然很好。
那么接下来就是GRU的问题,在说GRU之前,我想先说一下网络小说。我喜欢看网络小说,现在我经常想的一个问题是网络小说作家什么时候被AI替代,生成文字可是AI的强项,它只需要吃电就能干活,而且一天想写多少写多少。网文作家一天能写一万字那已经很肝了,AI一天写一千万字一亿字又如何。可是AI的一个问题就是写着写着逻辑崩溃了,牛头不对马嘴。网文里有个挖坑的说法,一个引子几十章几百章之后解密都是有可能的,可是AI写着写着自己都忘了,都不知道自己写的啥了。这就是梯度消失问题。GRU就是为了解决这个问题而提出的
self.train_dataloader, self.val_dataloader, self.test_dataloader = dataloaders
其实一开始我并不了解验证集是干嘛的,下面估计有对应的代码吧,根据验证集得到的结果对参数进行调整
self.loss_fn = torch.nn.CrossEntropyLoss(reduction='mean')
这句话是定义损失函数的,这个PLS神经网络是用来判断孤独症的,那就是一个二分类任务,yes or no。首先是输出,然后进行softmax处理,比如说yes的概率为0.7,no的概率为0.3。如果说它的真实标签是yes,那么损失函数就是-log(0.7)=0.1549,损失函数越小,代表准确率越高。
self.pool_ratio = train_config['pool_ratio']
关于池化,我已经理解了,这个池化比率可能是为了防止过拟合吧。
self.group_loss = train_config['group_loss']
self.sparsity_loss = train_config['sparsity_loss']
self.sparsity_loss_weight = train_config['sparsity_loss_weight']
原来组损失和稀疏性损失是完全不同的,稀疏性损失比较简单,比如在这个PLS神经网络模型中输入是一个八维的向量,那么有可能只有一到两个向量起到了关键作用,大部分的向量是无用的,那么花时间和算力去处理它就是无意义的,自然可以把这些无用向量忽略掉。组损失很玄乎,我不好解释
3 train如何执行
def train(self):
training_process = []
txt = ''
for epoch in range(self.epochs):
self.reset_meters()
self.train_per_epoch(self.optimizers[0])
val_result, _ = self.test_per_epoch(self.val_dataloader,
self.val_loss, self.val_accuracy)
test_result, con_matrix = self.test_per_epoch(self.test_dataloader,
self.test_loss, self.test_accuracy)
if self.best_acc <= self.test_accuracy.avg:
self.best_acc = self.test_accuracy.avg
self.best_model = self.model
if (con_matrix[0][0] + con_matrix[1][0]) != 0:
SEN = con_matrix[0][0] / (con_matrix[0][0] + con_matrix[1][0])
else:
SEN = 0
if (con_matrix[1][1] + con_matrix[0][1]) != 0:
SPE = con_matrix[1][1] / (con_matrix[1][1] + con_matrix[0][1])
else:
SPE = 0
self.logger.info(" | ".join([
f'Epoch[{epoch}/{self.epochs}]',
f'Train Loss:{self.train_loss.avg: .3f}',
f'Train Accuracy:{self.train_accuracy.avg: .3f}%',
# f'Edges:{self.edges_num.avg: .3f}',
# f'Test Loss:{self.test_loss.avg: .3f}',
f'Val Accuracy:{self.val_accuracy.avg: .3f}%',
f'Test Accuracy:{self.test_accuracy.avg: .3f}%',
f'Val AUC:{val_result[0]:.2f}',
f'Test AUC:{test_result[0]:.4f}',
f'Test SEN:{SEN:.4f}',
f'Test SPE:{SPE:.4f}'
]))
txt += f'Epoch[{epoch}/{self.epochs}] '+f'Train Loss:{self.train_loss.avg: .3f} '+f'Train Accuracy:{self.train_accuracy.avg: .3f}% '+f'Val Accuracy:{self.val_accuracy.avg: .3f}% '+f'Test Accuracy:{self.test_accuracy.avg: .3f}% '+f'Val AUC:{val_result[0]:.3f} '+f'Test AUC:{test_result[0]:.4f}'+f'Test SEN:{SEN:.4f}'+f'Test SPE:{SPE:.4f}'+'\n'
training_process.append([self.train_accuracy.avg, self.train_loss.avg,
self.val_loss.avg, self.test_loss.avg]
+ val_result + test_result)
now = datetime.now()
date_time = now.strftime("%m-%d-%H-%M-%S")
self.save_path = self.save_path/Path(f"{self.best_acc: .3f}%_{date_time}")
if self.save_learnable_graph:
self.generate_save_learnable_matrix()
self.save_result(training_process, txt)
training_process = []
txt = ''
看它这个意思,train_process和txt是配套使用的,用来记录每一轮训练的准确率和损失率
if self.best_acc <= self.test_accuracy.avg:
self.best_acc = self.test_accuracy.avg
self.best_model = self.model
avg就是accuracy,即准确率,准确率自然是越高越好,看这个代码的意思,每一轮的训练都会有一个准确率,都会有一个model,那么更好的model会被选取。
if (con_matrix[0][0] + con_matrix[1][0]) != 0:
SEN = con_matrix[0][0] / (con_matrix[0][0] + con_matrix[1][0])
else:
SEN = 0
if (con_matrix[1][1] + con_matrix[0][1]) != 0:
SPE = con_matrix[1][1] / (con_matrix[1][1] + con_matrix[0][1])
else:
SPE = 0
这个混淆矩阵是值得一说的,TP也就是[0][0],即为模型正确预测为正类的样本,FN也就是[0][1],即为模型错误预测为负类的正类样本数,FP也就是[1][0],即为模型错误预测为正类的负类样本数,TN即为模型正确预测为负类的样本数。