202206026 -
0. 引言
最近在看开源代码论文的时候,看到很多作者都是采用torch来进行编码,但是我平时都是使用keras进行编码,所以我自己也准备学习一下。
但是因为平时学习的时候,都是有问题就查询一下,毕竟再完整系统的学习太浪费时间了,我也只是想看到作者代码而已。
所以这篇文章就来记录下自己学习的过程。
1. 相关学习记录
1)Understanding PyTorch with an example: a step-by-step tutorial
这篇文章算是我学习过程中,第一篇比较基础的文章了,算是非常全面了,通过这篇文章,差不多就懂了。而且这篇文章我也看了好几遍。
2)分类softmax如何进行编程
torch也算是用了很久了,但是一直没有接触过这种普通的MLP,就是最后的分类过程。此前都是弄自编码器的形式。即使对于是MLP其实也是没什么区别的,大框架没有什么变化,但是对于最终部分的细节,不太清楚。
在进行keras编程的时候,所有一层的softmax就是直接Dense(activation="softmax")
就完事了。可能在torch中也可以,但是我还是希望能够看到跟此前使用方法一样的形式,也就是输出logits,然后我利用交叉熵来进行计算。首先,如果是这种形式,那么最后一个输出层,应该是具备与类比数据相同神经元个数的线性层,在之后,他的输出将于one-hot编码的标签数据进行比较,比较的函数是使用交叉熵。例如文章[1]中给出的代码:
# Define the loss function and optimizer
loss_function = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(mlp.parameters(), lr=1e-4)
# Run the training loop
for epoch in range(0, 5): # 5 epochs at maximum
# Print epoch
print(f'Starting epoch {epoch+1}')
# Set current loss value
current_loss = 0.0
# Iterate over the DataLoader for training data
for i, data in enumerate(trainloader, 0):
# Get inputs
inputs, targets = data
# Zero the gradients
optimizer.zero_grad()
# Perform forward pass
outputs = mlp(inputs)
# Compute loss
loss = loss_function(outputs, targets)
除了这部分,还需要度量这个准确度,其实这部分你只要利用torch.max
这个函数就可以了,但是要注意参数。具体代码可以见文章[2]。但他的acc计算的话,最好还是统计整个epoch来计算,比较清楚。
(20230302)
在前面的内容中,具体阐述了分类模型的损失函数如何构建,我已经得到了最后与类别数量相同的神经元的最后一层输出,但实际上要在计算最后的交叉熵,还需要别的计算,例如softmax
。前面的代码中,并不知道target
是什么格式,在keras中一般这个一般都是one-hot格式,但是根据torch和文章[7]中指出,这部分并非是,而是直接的label,那么言下之意,是不是说,nn.CrossEntropyLoss
是不是帮忙做了一些处理呢?看了一下,文章[7]说法,实际上,他是直接利用这个部分作为索引来进行计算,那这样的话,应该是必须保证label
是连续,而且是和类别数量相同的。
而最后,在对这部分的信息进行一下分析,可以查看文章[8],其实nn.CrossEntropyLoss
做了多件事情,包括softmax
,log
还有最后的一个NLLLoss
,才得到了交叉熵。
3)打印模型信息
在使用keras编程神经网络的时候,可以直接用model.summary来打印模型的各层信息,包括参数数量等。但是torch中却没有直接的api。按照需求查找到了这部分内容[6],问答中给出的库是pytorch-summary,但实际上他的github已经更名为torchinfo。
from torchinfo import summary
model = ConvNet()
batch_size = 16
summary(model, input_size=(batch_size, 1, 28, 28))
上述命令是他官方给出的用法,但是在我用这个函数来打印我自己模型的时候(利用Sequential构建),会报错,只有把这个input_size参数删掉之后才能输出。暂时就先这么使用吧。后续看看是为什么。
4)batch-normalization
machine-learning-articles/batch-normalization-with-pytorch.md
2. 遇到的问题
1)同样的模型,keras和torch结果相差非常大
一般来说,两种不同的模型,你可能有一定差距,都是可以理解的,但是在我的数据上,居然相差了10个点,这就很尴尬了,这就没办法理解了。
首先,网络结构什么的,我都是固定好的,这个肯定是要一样的,同时里面的激活函数什么的,肯定也不能有所变化,这些简答的内容肯定都完全没什么问题。
但是效果就是不一样。然后我突然意识到,很有可能是这个神经网络权值初始化的问题。然后搜索了搜索这部分内容,的确是这部分问题。然后修改了之后,就能差不多的效果了,虽然并没有完全一致,毕竟还有一些随机性的问题。
2)随意层数的torch网络
在之前的使用中,因为都是比较简单的网络,五层,十层的样子,没有使用太深层次的网络,所以在编程中,都是直接把这个部分给写死到模型中。但其实我之前的时候,使用keras进行编程的时候,采用的形式都是变长的,利用一个字符串的形式,然后在定义网络的时候,注意增加到网络中。所以如何定义torch框架下的变长网络呢?!
在文章[3]的方法中,就是定义了了self.hidden = nn.ModuleList()
这个变量,在增加这个神经元的时候,形式是
self.hidden = nn.ModuleList()
for k in range(len(h_sizes)-1):
self.hidden.append(nn.Linear(h_sizes[k], h_sizes[k+1]))
同时另外的一个人提出了另外的方案,
import torch
from torch import nn, optim
from torch.nn.modules import Module
from implem.settings import settings
class MLP(nn.Module):
def __init__(self, input_size, layers_data: list, learning_rate=0.01, optimizer=optim.Adam):
super().__init__()
self.layers = nn.ModuleList()
self.input_size = input_size # Can be useful later ...
for size, activation in layers_data:
self.layers.append(nn.Linear(input_size, size))
input_size = size # For the next layer
if activation is not None:
assert isinstance(activation, Module), \
"Each tuples should contain a size (int) and a torch.nn.modules.Module."
self.layers.append(activation)
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
self.to(self.device)
self.learning_rate = learning_rate
self.optimizer = optimizer(params=self.parameters(), lr=learning_rate)
def forward(self, input_data):
for layer in self.layers:
input_data = layer(input_data)
return input_data
下面这种形式,更符合我现在的一个需求。但是我觉得还是应该看看更多的代码,来看看更精简,更清晰的代码形式是什么样子。这种,应该是属于比较普遍的需求了。之前的时候,我也是看了一篇顶会上的论文,采用了那种从字符串逐步读取的形式来定义Keras模型,这里后面多看看代码应该也很好找到。
另外,torch还有另外一种形式,就是nn.Sequantial
这种,跟我之前Keras下使用的一种形式应该是一样的,在问答[4]中说明了两种形式的不同。
(20230223 - 增加)
今天需要编写变长的自编码器,就专门搜索了这部分内容,重点在于怎么使用sequanntial
或者nn.ModuleList
这种,然后在这篇文章[5]中找到了答案:
不过对我来说,重点是为了得到某个部分的输出,我最后是选择了利用ModuleList的形式,但实际上,我按照Sequential
先定义多个块也是可以的。这样看来结果更加方便。
参考
[1]creating-a-multilayer-perceptron-with-pytorch-and-lightning
[2]1 - Multilayer Perceptron.ipynb
[3]How to create MLP model with arbitrary number of hidden layers
[4]When should I use nn.ModuleList and when should I use nn.Sequential?
[5]FrancescoSaverioZuppichini/Pytorch-how-and-when-to-use-Module-Sequential-ModuleList-and-ModuleDict
[6]How do I print the model summary in PyTorch?
[7]Is One-Hot Encoding required for using PyTorch’s Cross Entropy Loss Function?
[8]Pytorch详解NLLLoss和CrossEntropyLoss