シリーズ記事ディレクトリ
PyTorch の学習 - テンソル、Variable、nn.Parameter()、リーフ ノード、非リーフ ノード、detach() 関数、ネットワーク層パラメーターの表示について
pytorch オプティマイザー - add_param_group() の概要と例、Yolov7 オプティマイザー コードの例
記事ディレクトリ
- シリーズ記事ディレクトリ
-
- PyTorch 学習 - テンソル、Variable、nn.Parameter()、リーフ ノード、非リーフ ノード、detach() 関数、ネットワーク層パラメーターの表示 [pytorch オプティマイザー - add_param_group() の紹介と例、Yolov7 オプティマイザー コード例] (http: //t.csdn.cn/WhKmC)
- バグのキーワード: pytorch モデルのカスタム パラメーターが更新されない、ネットワーク勾配がなし、パラメーターが更新されない、テンソル パラメーターに勾配があるが更新されない、nn.Parameter() パラメーターが更新されない。
- 1. バグの紹介
- 2. バグ再発とバグ解決の旅
- 3. コード全体を再現します。
- 4. 重みを更新せずにテンソル パラメーターの勾配を解決するためのいくつかのアイデアの要約:
- 要約する
バグのキーワード: pytorch モデルのカスタム パラメーターが更新されない、ネットワーク勾配がなし、パラメーターが更新されない、テンソル パラメーターに勾配があるが更新されない、nn.Parameter() パラメーターが更新されない。
1. バグの紹介
バグを記録します。
バグの説明: これは、Pytorch モデルのカスタム nn.Parameter() パラメーターが更新されないことに関するバグです。
nn.Parameter() を使用してパラメータ変数を定義するだけで、損失に応じてサイズを変更できます。
2. バグ再発とバグ解決の旅
プログラムは非常に単純ですが、定義した重み変数(self.w)が変化しないことがわかりました。
バグを再現するために、ここでは resnet18 ネットワークを使用してデジタル手書きミニ データ セットを実行しました。ネットワーク モデルは次のとおりです。
class resnet18(torch.nn.Module):
def __init__(self):
super(resnet18, self).__init__()
self.block1 = torch.nn.Sequential(
torch.nn.Conv2d(1, 10, 5),
torch.nn.MaxPool2d(2),
torch.nn.ReLU(True),
torch.nn.BatchNorm2d(10),
)
self.block2 = torch.nn.Sequential(
torch.nn.Conv2d(10, 20, 5),
torch.nn.MaxPool2d(2),
torch.nn.ReLU(True),
torch.nn.BatchNorm2d(20),
)
self.fc = torch.nn.Sequential(
torch.nn.Flatten(),
torch.nn.Linear(320, 10)
)
self.w = torch.nn.Parameter(torch.ones(2)) # 定义自学习参数
def forward(self, x):
x = self.block1(x)*self.w[0]
x = self.block2(x)*self.w[1]
x = self.fc(x)
return x
出力結果は以下の通りです。
上記プログラムから論理的には問題ありません。プログラムは正常に実行されます。つまり、self.w は変更されていません。インターネットで情報を調べると、定義した変数がリーフノードではないとか、勾配が計算されていないとか、ネットワーク層が追加されていないとか言う人が多いです。これらの不確実な理由をいくつかの出力関数を使用して出力しました。
結果:
リーフノードかどうか: 初期化における self.w の最初の 2 つの出力はリーフノードですが、後者はリーフノードではないことがわかります。
勾配: 1 つのグラフィックス カードで勾配を計算できますが、勾配は非常に小さく (たとえば、0.0001)、複数のカードの勾配出力は [なし] になります。
ここで問題が発生します。私自身の nn.Parameter() で定義された変数は通常、ネットワーク層の重みのようなリーフ ノードであり、勾配はデフォルトで計算されます。出力が非リーフ ノードであり、勾配が None になるのはなぜですか。それから私は目が見えなくなりました。
リーフ ノードと勾配の概要については、「PyTorch 学習 - テンソル、変数、nn.Parameter()、リーフ ノード、非リーフ ノード、detach() 関数について、およびネットワーク層パラメーターの表示」を参照してください。
となると、勾配が小さすぎて学習率が小さすぎるのではないかと思います。結果は0になります。次に、学習率オプティマイザーの方向に進み始めます。その結果、ここでバグが発生しました。そう考えるとバグの原因はほぼわかったのですが、学習率が小さすぎるということではありません。代わりに:
バグ: それ自体で定義された self.w パラメーターは、反復のオプティマイザーに追加されません。多くのネットワークはグローバルに直接定義されているため、ネットワーク自体が定義した self.w パラメータは自然にオプティマイザに追加されます。たとえば次のようになります。
optimizer = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.5)
そして、私はyoloネットワークを使用しています。オプティマイザー関数は次のとおりです。self.w
パラメータはオプティマイザーの反復ランクに追加されず、当然、損失に応じて調整されません。
#---------------------------------------------------------------------#
# 构造损失函数和优化函数
# 损失
criterion = torch.nn.CrossEntropyLoss()
pg0, pg1, pg2 ,pg3= [], [], [], []
for name, p in model.named_modules():
if hasattr(p, "bias") and isinstance(p.bias, nn.Parameter): # 把带有bias属性且性质为nn.Parameter的层选出来 添加到到pg2列表
pg2.append(p.bias)
if isinstance(p, nn.BatchNorm2d) or "bn" in name: # 把标准化层选出来 添加到到pg0列表
pg0.append(p.weight)
elif hasattr(p, "weight") and isinstance(p.weight, nn.Parameter): # 把带有weight属性且性质为nn.Parameter的层选出来 添加到到pg1列表
pg1.append(p.weight)
#print('22',name,p) # print打印出来 调试用
# hasattr() 函数用于判断对象是否包含对应的属性。
# isinstance()检查对象是否是指定的类型。
# append() 向列表末尾添加元素
optimizer = torch.optim.SGD(pg0, lr=0.1, momentum=0.5) #初始化优化器,定义一个参数组
optimizer.add_param_group({
"params": pg1}) # 增加一组参数 性质与pg0一样
optimizer.add_param_group({
"params": pg2}) # 增加一组参数 性质与pg0一样
# 可以看到,参数组是一个list,一个元素是一个dict,每个dict中都有lr, momentum等参数,这些都是可单独管理,单独设定。
#---------------------------------------------------------------------#
解決:
方法 1
上の最後の行にパラメーターのセットを追加し、反復のために self.w をオプティマイザーに追加します。
#optimizer.add_param_group({
"params": model.w,'lr': 0.12, 'momentum': 0.8}) # 这个是我网络中定义的自学习权重参数
ここで、optimizer.add_param_group の詳細な紹介を参照できます: pytorch オプティマイザー - add_param_group() の紹介と例、Yolov7 オプティマイザー コード例
3. コード全体を再現します。
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision import transforms
import torch.nn.functional as F
import matplotlib.pyplot as plt
import numpy as np
import os # 添加代码①
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE" # 添加代码②
batch_size = 256 #设置batch大小
transform = transforms.Compose([
transforms.ToTensor(), #转换为张量
transforms.Normalize((0.1307,), (0.3081,)) #设定标准化值
])
#训练集
train_dataset = datasets.MNIST(
root='../data/mnist',
train=True,
transform=transform,
download=True)
#测试集
test_dataset = datasets.MNIST(
root='../data/mnist',
train=False,
transform=transform,
download=True)
#训练集加载器
train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size,shuffle=True)
#测试集加载器
test_loader = DataLoader(dataset=test_dataset,batch_size=batch_size, shuffle=False)
class resnet18(torch.nn.Module):
def __init__(self):
super(resnet18, self).__init__()
self.block1 = torch.nn.Sequential(
torch.nn.Conv2d(1, 10, 5),
torch.nn.MaxPool2d(2),
torch.nn.ReLU(True),
torch.nn.BatchNorm2d(10),
)
self.block2 = torch.nn.Sequential(
torch.nn.Conv2d(10, 20, 5),
torch.nn.MaxPool2d(2),
torch.nn.ReLU(True),
torch.nn.BatchNorm2d(20),
)
self.fc = torch.nn.Sequential(
torch.nn.Flatten(),
torch.nn.Linear(320, 10)
)
self.w = torch.nn.Parameter(torch.ones(2)) # 定义自学习参数
def forward(self, x):
x = self.block1(x)*self.w[0]
x = self.block2(x)*self.w[1]
x = self.fc(x)
return x
model = resnet18()
device=torch.device("cuda:0"if torch.cuda.is_available()else"cpu")#使用GPU进行计算
model.to(device)#把model模型放进去
#---------------------------------------------------------------------#
# 构造损失函数和优化函数
# 损失
criterion = torch.nn.CrossEntropyLoss()
pg0, pg1, pg2 ,pg3= [], [], [], []
for name, p in model.named_modules():
if hasattr(p, "bias") and isinstance(p.bias, nn.Parameter): # 把带有bias属性且性质为nn.Parameter的层选出来 添加到到pg2列表
pg2.append(p.bias)
if isinstance(p, nn.BatchNorm2d) or "bn" in name: # 把标准化层选出来 添加到到pg0列表
pg0.append(p.weight)
elif hasattr(p, "weight") and isinstance(p.weight, nn.Parameter): # 把带有weight属性且性质为nn.Parameter的层选出来 添加到到pg1列表
pg1.append(p.weight)
#print('22',name,p) # print打印出来 调试用
# hasattr() 函数用于判断对象是否包含对应的属性。
# isinstance()检查对象是否是指定的类型。
# append() 向列表末尾添加元素
optimizer = torch.optim.SGD(pg0, lr=0.1, momentum=0.5) #初始化优化器,定义一个参数组
optimizer.add_param_group({
"params": pg1}) # 增加一组参数 性质与pg0一样
optimizer.add_param_group({
"params": pg2}) # 增加一组参数 性质与pg0一样
# 不加入下面这一行self.w是不更新的
optimizer.add_param_group({
"params": model.w,'lr': 0.12, 'momentum': 0.8}) # 这个是我网络中定义的自学习权重参数
# 可以看到,参数组是一个list,一个元素是一个dict,每个dict中都有lr, momentum等参数,这些都是可单独管理,单独设定。
#---------------------------------------------------------------------#
def train(epoch):
# adjust_learning_rate(optimizer, epoch, start_lr) # 动态调整学习率
# print("Lr:{}".format(optimizer.state_dict()['param_groups'][0]['lr'])) # 查看学习率
# print("Lr:{}".format(optimizer.state_dict()['param_groups'][1]['lr']))
# print("Lr:{}".format(optimizer.state_dict()['param_groups'][2]['lr']))
# print(optimizer.state_dict()["param_groups"]) # 查看优化器完整参数
running_loss = 0.0 #每一轮训练重新记录损失值
for batch_idx, data in enumerate(train_loader, 0): #提取训练集中每一个样本
inputs, target = data
inputs, target = inputs.to(device), target.to(device) # 这里的数据(原数据)也要迁移过去
# outputs输出为0-9的概率 256*10
outputs = model(inputs) #代入模型
loss = criterion(outputs, target) #计算损失值
loss.backward() #反向传播计算得到每个参数的梯度值
optimizer.step() #梯度下降参数更新
optimizer.zero_grad() #将梯度归零
running_loss += loss.item() #损失值累加
if batch_idx % 300 == 299: #每300个样本输出一下结果
print('[%d,%5d] loss: %.3f' % (epoch + 1, batch_idx + 1, running_loss / 300))
running_loss = 0.0 # (训练轮次, 该轮的样本次, 平均损失值)
return running_loss
def test():
correct = 0
total = 0
with torch.no_grad(): #执行计算,但不希望在反向传播中被记录
for data in test_loader: #提取测试集中每一个样本
images, labels = data
images, labels = images.to(device), labels.to(device)
# outputs输出为0-9的概率 256*10
outputs = model(images) #带入模型
# torch.max()这个函数返回的是两个值,第一个值是具体的value(我们用下划线_表示)
# 第二个值是value所在的index(也就是predicted)
_, pred = torch.max(outputs.data, dim=1) #获得结果中的最大值
total += labels.size(0) #测试数++
correct += (pred == labels).sum().item() #将预测结果pred与标签labels对比,相同则正确数++
print('%d %%' % (100 * correct / total)) #输出正确率
if __name__ == '__main__':
# 这两个数组主要是为了画图
lossy = [] #定义存放纵轴数据(损失值)的列表
epochx = [] #定义存放横轴数据(训练轮数)的列表
for epoch in range(10): #训练10轮
epochx.append(epoch) #将本轮轮次存入epochy列表
lossy.append(train(epoch)) #执行训练,将返回值loss存入lossy列表
test() #每轮训练完都测试一下正确率
print('w',model.w)
path = "D:/code/text/model2.pth"
#torch.save(model,path)
torch.save(model.state_dict(),path) # 保存模型
model = torch.load("D:/code/text/model2.pth") # 加载模型
#可视化一下训练过程
plt.plot(epochx, lossy)
plt.grid()
plt.show()
4. 重みを更新せずにテンソル パラメーターの勾配を解決するためのいくつかのアイデアの要約:
1. 変数の勾配が 0 または None であるかどうかを確認します。
2. 学習率が小さすぎるか大きすぎるか。
3. 最適化の最適化パラメータ列に追加されているかどうか。
4. Softmax によっても結果の勾配が失われるため、log_softmax に置き換えます。
5. torch.argmax() を使用すると、結果の勾配が失われます。
勾配出力、最適化パラメータ列を追加するかどうか、および葉ノードについては、このシリーズの他の 2 つの記事を参照してください。
要約する
このバグを振り返るのは非常に簡単ですが、私はそれを見つけられませんでした。まだ食べ物が多すぎます。