文章來源 https://www.cnblogs.com/king-lps/p/8570021.html
1. PyTorch進行訓練和測試時指定實例化的model模式為:train/eval
eg:

eval即evaluation模式,train即訓練模式。僅僅當模型中有Dropout
和BatchNorm
是才會有影響。因為訓練時dropout和BN都開啟,而一般而言測試時dropout被關閉,BN中的參數也是利用訓練時保留的參數,所以測試時應進入評估模式。
(在訓練時,𝜇和𝜎2是在整個mini-batch 上計算出來的包含了像是64 或28 或其它一定數量的樣本,但在測試時,你可能需要逐一處理樣本,方法是根據你的訓練集估算𝜇和𝜎2,估算的方式有很多種,理論上你可以在最終的網絡中運行整個訓練集來得到𝜇和𝜎2,但在實際操作中,我們通常運用指數加權平均來追蹤在訓練過程中你看到的𝜇和𝜎2的值。還可以用指數加權平均,有時也叫做流動平均來粗略估算𝜇和𝜎2,然后在測試中使用𝜇和𝜎2的值來進行你所需要的隱藏單元𝑧值的調整。在實踐中,不管你用什么方式估算𝜇和𝜎2,這套過程都是比較穩健的,因此我不太會擔心你具體的操作方式,而且如果你使用的是某種深度學習框架,通常會有默認的估算𝜇和𝜎2的方式,應該一樣會起到比較好的效果) -- Deeplearning.ai
2. PyTorch權重初始化的幾種方法

class discriminator(nn.Module):</span><span style="color: #0000ff">def</span> <span style="color: #800080">__init__</span>(self, dataset = <span style="color: #800000">'</span><span style="color: #800000">mnist</span><span style="color: #800000">'</span><span style="color: #000000">): super(discriminator, self).</span><span style="color: #800080">__init__</span><span style="color: #000000">() 。... self.conv </span>=<span style="color: #000000"> nn.Sequential( nn.Conv2d(self.input_dim, </span>64, 4, 2, 1<span style="color: #000000">), nn.ReLU(), ) ... self.fc </span>=<span style="color: #000000"> nn.Sequential( nn.Linear(</span>32, 64 * (self.input_height // 2) * (self.input_width // 2<span style="color: #000000">)), nn.BatchNorm1d(</span>64 * (self.input_height // 2) * (self.input_width // 2<span style="color: #000000">)), nn.ReLU(), ) self.deconv </span>=<span style="color: #000000"> nn.Sequential( nn.ConvTranspose2d(</span>64, self.output_dim, 4, 2, 1<span style="color: #000000">), </span><span style="color: #008000">#</span><span style="color: #008000">nn.Sigmoid(), # EBGAN does not work well when using Sigmoid().</span>
)
utils.initialize_weights(self)</span><span style="color: #0000ff">def</span><span style="color: #000000"> forward(self, input): ...
def initialize_weights(net):
for m in net.modules():
if isinstance(m, nn.Conv2d):
m.weight.data.normal_(0, 0.02)
m.bias.data.zero_()
elif isinstance(m, nn.ConvTranspose2d):
m.weight.data.normal_(0, 0.02)
m.bias.data.zero_()
elif isinstance(m, nn.Linear):
m.weight.data.normal_(0, 0.02)
m.bias.data.zero_()

def init_weights(m): print(m) if type(m) == nn.Linear: m.weight.data.fill_(1.0) print(m.weight)net = nn.Sequential(nn.Linear(2, 2), nn.Linear(2, 2))
net.apply(init_weights)

def weights_init(m): classname = m.__class__.__name__ if classname.find('Conv') != -1: m.weight.data.normal_(0.0, 0.02) elif classname.find('BatchNorm') != -1: m.weight.data.normal_(1.0, 0.02) m.bias.data.fill_(0)net.apply(weights_init)
class torch.nn.Module 是所有神經網絡的基類。
modules()返回網絡中所有模塊的迭代器。
add_module(name, module) 將一個子模塊添加到當前模塊。 該模塊可以使用給定的名稱作為屬性訪問。
apply(fn) 適用fn
遞歸到每個子模塊(如返回.children(),
以及自我。
3. PyTorch 中Variable的重要屬性
class torch.autograd.Variable
為什么要引入Variable?首先回答為什么引入Tensor。僅僅利用numpy也可以實現前向反向操作,但numpy不支持GPU運算。而Pytorch為Tensor提供多種操作運算,此外Tensor支持GPU。問題來了,兩三層網絡可以推公式寫反向傳播,當網絡很復雜時需要自動化。autograd可以幫助我們,當利用autograd時,前向傳播會定義一個計算圖,圖中的節點就是Tensor。圖中的邊就是函數。當我們將Tensor塞到Variable時,Variable就變為了節點。若x為一個Variable,那x.data即為Tensor,x.grad也為一個Variable。那x.grad.data就為梯度的值咯。總結:PyTorch Variables與PyTorch Tensors有着相同的API,Tensor上的所有操作幾乎都可用在Variable上。兩者不同之處在於利用Variable定義一個計算圖,可以實現自動求導!
重要的屬性如下:
requires_grad
指定要不要更新這個變數,對於不需要更新的變數可以把他設定成False
,可以加快運算。
Variable默認是不需要求導的,即requires_grad
屬性默認為False,如果某一個節點requires_grad被設置為True,那么所有依賴它的節點requires_grad
都為True。
在用戶手動定義Variable時,參數requires_grad默認值是False。而在Module中的層在定義時,相關Variable的requires_grad參數默認是True。
在計算圖中,如果有一個輸入的requires_grad是True,那么輸出的requires_grad也是True。只有在所有輸入的requires_grad都為False時,輸出的requires_grad才為False。
volatile
指定需不需要保留紀錄用的變數。指定變數為True
代表運算不需要記錄,可以加快運算。如果一個變數的volatile是True
,則它的requires_grad一定是False
。
簡單來說,對於需要更新的Variable記得將requires_grad
設成True
,當只需要得到結果而不需要更新的Variable可以將volatile
設成True
加快運算速度。 參考:PyTorch 基礎篇
variable的volatile
屬性默認為False,如果某一個variable的volatile
屬性被設為True,那么所有依賴它的節點volatile
屬性都為True。volatile屬性為True的節點不會求導,volatile的優先級比requires_grad
高。
當有一個輸入的volatile=True時,那么輸出的volatile=True。volatile=True推薦在模型的推理過程(測試)中使用,這時只需要令輸入的voliate=True,保證用最小的內存來執行推理,不會保存任何中間狀態。在使用volatile=True
的時候,變量是不存儲 creator
屬性的,這樣也減少了內存的使用。
參考:自動求導機制 、『PyTorch』第五彈_深入理解autograd_上:Variable屬性方法
PyTorch學習系列(十)——如何在訓練時固定一些層?、Pytorch筆記01-Variable和Function(自動梯度計算)
detach()
返回一個新變量,與當前圖形分離。結果將永遠不需要漸變。如果輸入是易失的,輸出也將變得不穩定。返回的 Variable 永遠不會需要梯度。
根據GAN的代碼來看:
方法1. 利用detach階段梯度流:(代碼片段:DCGAN)

# train with real netD.zero_grad() real_cpu, _ = data batch_size = real_cpu.size(0) if opt.cuda: real_cpu = real_cpu.cuda() input.resize_as_(real_cpu).copy_(real_cpu) label.resize_(batch_size).fill_(real_label) inputv = Variable(input) labelv = Variable(label)output </span>=<span style="color: #000000"> netD(inputv) errD_real </span>=<span style="color: #000000"> criterion(output, labelv) errD_real.backward() D_x </span>=<span style="color: #000000"> output.data.mean() </span><span style="color: #008000">#</span><span style="color: #008000"> train with fake</span> noise.resize_(batch_size, nz, 1, 1).normal_(0, 1<span style="color: #000000">) noisev </span>=<span style="color: #000000"> Variable(noise) fake </span>=<span style="color: #000000"> netG(noisev) labelv </span>=<span style="color: #000000"> Variable(label.fill_(fake_label)) output </span>=<span style="color: #000000"> netD(fake.detach()) errD_fake </span>=<span style="color: #000000"> criterion(output, labelv) errD_fake.backward() D_G_z1 </span>=<span style="color: #000000"> output.data.mean() errD </span>= errD_real +<span style="color: #000000"> errD_fake optimizerD.step() </span><span style="color: #008000">#</span><span style="color: #008000">###########################</span> <span style="color: #008000">#</span><span style="color: #008000"> (2) Update G network: maximize log(D(G(z)))</span> <span style="color: #008000">#</span><span style="color: #008000">##########################</span>
netG.zero_grad()
labelv = Variable(label.fill_(real_label)) # fake labels are real for generator cost
output = netD(fake)
errG = criterion(output, labelv)
errG.backward()
D_G_z2 = output.data.mean()
optimizerG.step()
首先在用fake更新D的時候,給G的輸出加了detach,是因為我們希望更新時只更新D的參數,而不需保留G的參數的梯度。其實這個detach也是可以不用加的,因為直到netG.zero_grad()
被調用G的梯度是不會被用到的,optimizerD.step()只更新D的參數。
然后在利用fake更新G的時候,卻沒有給G的輸出加detach,因為你本身就是需要更新G的參數,所以不能截斷它。
參考:stackoverflow 、github_issue(why is detach necessary)
方法2.利用 volatile = True 來凍結G的梯度:(代碼片段:WGAN

# train with real real_cpu, _ = data netD.zero_grad() batch_size = real_cpu.size(0)</span><span style="color: #0000ff">if</span><span style="color: #000000"> opt.cuda: real_cpu </span>=<span style="color: #000000"> real_cpu.cuda() input.resize_as_(real_cpu).copy_(real_cpu) inputv </span>=<span style="color: #000000"> Variable(input) errD_real </span>=<span style="color: #000000"> netD(inputv) errD_real.backward(one) </span><span style="color: #008000">#</span><span style="color: #008000"> train with fake</span> noise.resize_(opt.batchSize, nz, 1, 1).normal_(0, 1<span style="color: #000000">) noisev </span>= Variable(noise, volatile = True) <span style="color: #008000">#</span><span style="color: #008000"> totally freeze netG</span> fake =<span style="color: #000000"> Variable(netG(noisev).data) inputv </span>=<span style="color: #000000"> fake errD_fake </span>=<span style="color: #000000"> netD(inputv) errD_fake.backward(mone) errD </span>= errD_real -<span style="color: #000000"> errD_fake optimizerD.step() </span><span style="color: #008000">#</span><span style="color: #008000">###########################</span> <span style="color: #008000">#</span><span style="color: #008000"> (2) Update G network</span> <span style="color: #008000">#</span><span style="color: #008000">##########################</span> <span style="color: #0000ff">for</span> p <span style="color: #0000ff">in</span><span style="color: #000000"> netD.parameters(): p.requires_grad </span>= False <span style="color: #008000">#</span><span style="color: #008000"> to avoid computation</span>
netG.zero_grad()
# in case our last batch was the tail batch of the dataloader,
# make sure we feed a full batch of noise
noise.resize_(opt.batchSize, nz, 1, 1).normal_(0, 1)
noisev = Variable(noise)
fake = netG(noisev)
errG = netD(fake)
errG.backward(one)
optimizerG.step()
gen_iterations += 1
凍結G的梯度,即在更新D的時候,反向傳播計算梯度時不會計算G的參數的梯度。作用與方法1相同。
eg:
如果我們有兩個網絡 A,B, 兩個關系是這樣的 y=A(x),z=B(y). 現在我們想用 z.backward()來為 B 網絡的參數來求梯度,但是又不想求 A 網絡參數的梯度。我們可以這樣:

# y=A(x), z=B(y) 求B中參數的梯度,不求A中參數的梯度 # 第一種方法 y = A(x) z = B(y.detach()) z.backward()# 第二種方法
y = A(x)
y.detach_()
z = B(y)
z.backward()
參考: pytorch: Variable detach 與 detach_ 、Pytorch入門學習(九)---detach()的作用(從GAN代碼分析)
另一個簡單說明detach用法的github issue demo:

fc1 = nn.Linear(1, 2) fc2 = nn.Linear(2, 1) opt1 = optim.Adam(fc1.parameters(),lr=1e-1) opt2 = optim.Adam(fc2.parameters(),lr=1e-1)x = Variable(torch.FloatTensor([5]))
z = fc1(x)
x_p = fc2(z)
cost = (x_p - x) ** 2
'''
print (z)
print (x_p)
print (cost)
'''
opt1.zero_grad()
opt2.zero_grad()cost.backward()
for n, p in fc1.named_parameters():
print (n, p.grad.data)for n, p in fc2.named_parameters():
print (n, p.grad.data)opt1.zero_grad()
opt2.zero_grad()z = fc1(x)
x_p = fc2(z.detach())
cost = (x_p - x) ** 2cost.backward()
for n, p in fc1.named_parameters():
print (n, p.grad.data)for n, p in fc2.named_parameters():
print (n, p.grad.data)結果:
weight
12.0559
-8.3572
[torch.FloatTensor of size 2x1]bias
2.4112
-1.6714
[torch.FloatTensor of size 2]weight
-33.5588 -19.4411
[torch.FloatTensor of size 1x2]bias
-9.9940
[torch.FloatTensor of size 1]================================================
weight
0
0
[torch.FloatTensor of size 2x1]bias
0
0
[torch.FloatTensor of size 2]weight
-33.5588 -19.4411
[torch.FloatTensor of size 1x2]bias
-9.9940
[torch.FloatTensor of size 1]
grad_fn
梯度函數圖跟蹤。每一個變量在圖中的位置可通過其grad_fn
屬性在圖中的位置推測得到。
is_leaf
查看是否為葉子節點。即如果由用戶創建。

x = V(t.ones(1)) b = V(t.rand(1), requires_grad = True) w = V(t.rand(1), requires_grad = True) y = w * x # 等價於y=w.mul(x) z = y + b # 等價於z=y.add(b) x.requires_grad, b.requires_grad, w.requires_grad (False, True, True)x.is_leaf, w.is_leaf, b.is_leaf
(True, True, True)z.grad_fn
<AddBackward1 object at 0x7f615e1d9cf8>z.grad_fn.next_functions
((<MulBackward1 object at 0x7f615e1d9780>, 0), (<AccumulateGrad object at 0x7f615e1d9390>, 0))
#next_functions保存grad_fn的輸入,是一個tuple,tuple的元素也是Function第一個是y,它是乘法(mul)的輸出,所以對應的反向傳播函數y.grad_fn是MulBackward
第二個是b,它是葉子節點,由用戶創建,grad_fn為None
autograd.grad、register_hook
在反向傳播過程中非葉子節點的導數計算完之后即被清空。若想查看這些變量的梯度,有兩種方法:
- 使用autograd.grad函數
- 使用register_hook

x = V(t.ones(3), requires_grad=True) w = V(t.rand(3), requires_grad=True) y = x * w # y依賴於w,而w.requires_grad = True z = y.sum() x.requires_grad, w.requires_grad, y.requires_grad (True, True, True)

# 非葉子節點grad計算完之后自動清空,y.grad是None z.backward() (x.grad, w.grad, y.grad)(Variable containing:
0.1636
0.3563
0.6623
[torch.FloatTensor of size 3], Variable containing:
1
1
1
[torch.FloatTensor of size 3], None)
此時y.grad為None,因為backward()只求圖中葉子的梯度(即無父節點),如果需要對y求梯度,則可以使用autograd_grad或`register_hook`
使用autograd.grad:

# 第一種方法:使用grad獲取中間變量的梯度 x = V(t.ones(3), requires_grad=True) w = V(t.rand(3), requires_grad=True) y = x * w z = y.sum() # z對y的梯度,隱式調用backward() t.autograd.grad(z, y)(Variable containing:
1
1
1
[torch.FloatTensor of size 3],)
使用hook:

# 第二種方法:使用hook # hook是一個函數,輸入是梯度,不應該有返回值 def variable_hook(grad): print('y的梯度: \r\n',grad)x = V(t.ones(3), requires_grad=True)
w = V(t.rand(3), requires_grad=True)
y = x * w
# 注冊hook
hook_handle = y.register_hook(variable_hook)
z = y.sum()
z.backward()# 除非你每次都要用hook,否則用完之后記得移除hook
hook_handle.remove()y的梯度:
Variable containing:
1
1
1
[torch.FloatTensor of size 3]
參考:pytorch-book/chapter3-Tensor和autograd/
關於梯度固定與優化設置:
model = nn.Sequential(*list(model.children())) for p in model[0].parameters(): p.requires_grad=False
for i in m.parameters(): i.requires_grad=False
optimizer.SGD(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-3)
可以在中間插入凍結操作,這樣只凍結之前的層,后續的操作不會被凍結:

class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 6, 5) self.conv2 = nn.Conv2d(6, 16, 5)</span><span style="color: #0000ff">for</span> p <span style="color: #0000ff">in</span><span style="color: #000000"> self.parameters(): p.requires_grad</span>=<span style="color: #000000">False self.fc1 </span>= nn.Linear(16 * 5 * 5, 120<span style="color: #000000">) self.fc2 </span>= nn.Linear(120, 84<span style="color: #000000">) self.fc3 </span>= nn.Linear(84, 10)</pre>

count = 0 para_optim = [] for k in model.children(): # model.modules(): count += 1 # 6 should be changed properly if count > 6: for param in k.parameters(): para_optim.append(param) else: for param in k.parameters(): param.requires_grad = False optimizer = optim.RMSprop(para_optim, lr)################
another way
<span style="color: #0000ff">for</span> idx,m <span style="color: #0000ff">in</span><span style="color: #000000"> enumerate(model.modules()): </span><span style="color: #0000ff">if</span> idx >50<span style="color: #000000">: </span><span style="color: #0000ff">for</span> param <span style="color: #0000ff">in</span><span style="color: #000000"> m.parameters(): param.requires_grad </span>=<span style="color: #000000"> True </span><span style="color: #0000ff">else</span><span style="color: #000000">: </span><span style="color: #0000ff">for</span> param <span style="color: #0000ff">in</span><span style="color: #000000"> m.parameters(): param.requires_grad </span>= False</pre>
對特定層的權重進行限制:
def clamp_weights(self): for module in self.net.modules(): if(hasattr(module, 'weight') and module.kernel_size==(1,1)): module.weight.data = torch.clamp(module.weight.data,min=0)
參考:github
載入權重后發現錯誤率或正確率不正常,可能是學習率已改變,而保存和載入時沒有考慮優化器:所以保存優化器:

save_checkpoint({ 'epoch': epoch + 1, 'arch': args.arch, 'state_dict': model.state_dict(), 'optimizer': optimizer.state_dict(), 'prec1': prec1, }, save_name) # saveif args.resume:
if os.path.isfile(args.resume):
print("=> loading checkpoint '{}'".format(args.resume))
checkpoint = torch.load(args.resume)
args.start_epoch = checkpoint['epoch']
model.load_state_dict(checkpoint['state_dict'])
optimizer.load_state_dict(checkpoint['optimizer'])
print("=> loaded checkpoint '{}' (epoch {})"
.format(args.resume, checkpoint['epoch']))
else:
print("=> no checkpoint found at '{}'".format(args.resume)) # load
對特定的層學習率設置:

params = [] for name, value in model.named_parameters(): if 'bias' in name: if 'fc2' in name: params += [{'params':value, 'lr': 20 * args.lr, 'weight_decay': 0}] else: params += [{'params':value, 'lr': 2 * args.lr, 'weight_decay': 0}] else: if 'fc2' in name: params += [{'params':value, 'lr': 10 * args.lr}] else: params += [{'params':value, 'lr': 1 * args.lr}]optimizer </span>=<span style="color: #000000"> torch.optim.SGD(params, args.lr, momentum</span>=<span style="color: #000000">args.momentum, weight_decay</span>=args.weight_decay)</pre>
或者:

class net(nn.Module): def __init__(self): super(net, self).__init__() self.conv1 = nn.Conv2d(3, 64, 1) self.conv2 = nn.Conv2d(64, 64, 1) self.conv3 = nn.Conv2d(64, 64, 1) self.conv4 = nn.Conv2d(64, 64, 1) self.conv5 = nn.Conv2d(64, 64, 1) def forward(self, x): out = conv5(conv4(conv3(conv2(conv1(x))))) return out我們希望conv5學習率是其他層的100倍,我們可以:
net = net()
lr = 0.001conv5_params = list(map(id, net.conv5.parameters()))
base_params = filter(lambda p: id(p) not in conv5_params,
net.parameters())
optimizer = torch.optim.SGD([
{'params': base_params},
{'params': net.conv5.parameters(), 'lr': lr * 100},
, lr=lr, momentum=0.9)如果多層,則:
conv5_params = list(map(id, net.conv5.parameters()))
conv4_params = list(map(id, net.conv4.parameters()))
base_params = filter(lambda p: id(p) not in conv5_params + conv4_params,
net.parameters())
optimizer = torch.optim.SGD([
{'params': base_params},
{'params': net.conv5.parameters(), 'lr': lr * 100},
{'params': net.conv4.parameters(), 'lr': lr * 100},
, lr=lr, momentum=0.9)
</div>