Pytorch源码与运行原理浅析--网络篇(一)
前言
申請的專欄開通了,剛好最近閑下來了,就打算開這個(gè)坑了hhhhh
第一篇就先講一講pytorch的運(yùn)行機(jī)制好了。。。
記得當(dāng)時(shí)剛剛接觸的時(shí)候一直搞不明白,為什么自己只是定義了幾個(gè)網(wǎng)絡(luò),就可以完整的訓(xùn)練整個(gè)模型,它背后的機(jī)制又是如何,搞明白了這個(gè),才有可能去做更多的定制的更改,比如更改loss,反傳方式,梯度下降機(jī)制,甚至自定義參數(shù)更新速率(比如學(xué)習(xí)率隨著迭代輪數(shù)下降),文章比較淺顯,希望各位大神不吝賜教。
知識(shí)儲(chǔ)備
看此文章的前提,大概需要你寫過一個(gè)利用pytorch的訓(xùn)練程序,哪怕官網(wǎng)上的MNIST。
因?yàn)楸疚哪康氖歉嬖V你為什么這么寫
為什么不用TensorFlow
其實(shí)我之前是有用TF的,但是,emmmmmmmm.......
之后接觸了Pytorch,那一整天都在感嘆"還有這種操作?"
個(gè)人感覺TF不是一個(gè)易于理解和易于擴(kuò)展的框架。
比如說,我想實(shí)現(xiàn)學(xué)習(xí)率隨迭代輪數(shù)降低,需要修改哪些?
那么,讓我們開始吧
從MNIST說起
網(wǎng)絡(luò)定義篇
import torch.nn as nn
class Net(nn.Module):def __init__(self):super(Net, self).__init__()self.conv1 = nn.Conv2d(1, 10, kernel_size=5)self.conv2 = nn.Conv2d(10, 20, kernel_size=5)self.conv2_drop = nn.Dropout2d()self.fc1 = nn.Linear(320, 50)self.fc2 = nn.Linear(50, 10)def forward(self, x):x = F.relu(F.max_pool2d(self.conv1(x), 2))x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))x = x.view(-1, 320)x = F.relu(self.fc1(x))x = F.dropout(x, training=self.training)x = self.fc2(x)return F.log_softmax(x) 這一段是MNIST給的定義Net的代碼,那么,讓我們看一看,這一段代碼說明了什么,首先,__init__方法直接定義了你的網(wǎng)絡(luò),這就是你的模型中含有的全部的東西,你的模型本身也只有__init__ 中的屬性會(huì)被每一次訓(xùn)練的時(shí)候更改,可以說這個(gè)思路是十分的清晰了。
之后,是forward方法,這里定義了如何處理傳入的數(shù)據(jù)(就是那個(gè)x),返回這個(gè)神經(jīng)網(wǎng)絡(luò)的output
這里,我把它比作名詞和動(dòng)詞的關(guān)系,__init__()方法定義了網(wǎng)絡(luò)本身,或者說定義了一個(gè)個(gè)的名詞,而我們也需要一系列的"猜測"過程,猜出這些名詞是什么。而forward()方法,則是一個(gè)個(gè)的動(dòng)詞,它提供了如何處理這些名詞的方式。
而之后,我們來看看,運(yùn)行的時(shí)候,發(fā)生了什么
首先,我們看看torch.nn.Module,看看它是如何定義的。
torch.nn.Module
源代碼在此處
class Module(object):dump_patches = Falsedef __init__(self):self._backend = thnn_backendself._parameters = OrderedDict()self._buffers = OrderedDict()self._backward_hooks = OrderedDict()self._forward_hooks = OrderedDict()self._forward_pre_hooks = OrderedDict()self._modules = OrderedDict()self.training = Truedef forward(self, *input):raise NotImplementedError (代碼不完整,只截取了一段)
可以看到,Module類定義了一系列訓(xùn)練時(shí)使用的變量比如參數(shù)(感覺這是是緩存的參數(shù),用來之后做參數(shù)更新用的),buffers,幾個(gè)hooks(個(gè)人感覺這些hooks是之后與loss,反傳之類的步驟通訊數(shù)據(jù)用的)
反傳里面是有一個(gè)判斷的邏輯,判斷你的子類有沒有定義網(wǎng)絡(luò),沒有就報(bào)錯(cuò)(講真,這個(gè)想法很棒啊QwQ,子類重寫父類方法,沒有重寫就是個(gè)報(bào)錯(cuò)hhhhhh)
def register_buffer(self, name, tensor):self._buffers[name] = tensordef register_parameter(self, name, param):if '_parameters' not in self.__dict__:raise AttributeError("cannot assign parameter before Module.__init__() call")if param is None:self._parameters[name] = Noneelif not isinstance(param, Parameter):raise TypeError("cannot assign '{}' object to parameter '{}' ""(torch.nn.Parameter or None required)".format(torch.typename(param), name))elif param.grad_fn:raise ValueError("Cannot assign non-leaf Variable to parameter '{0}'. Model ""parameters must be created explicitly. To express '{0}' ""as a function of another variable, compute the value in ""the forward() method.".format(name))else:self._parameters[name] = param buffer和parameter的注冊,這里有一點(diǎn)需要提醒,在你自定義的網(wǎng)絡(luò)中,如果你用了類似
self.some_dict['keys'] = nn.Conv2d(10, 20, kernel_size=5) 這種語句的話,pytorch是沒有辦法這個(gè)變量的,也不會(huì)參與之后的傳參之類的
在定義了上面那句話之后你必須用類似
# method 1
setattr(self, 'some_name', self.some_dict['keys'])
# method 2
self.register_parameter('some_name', self.some_dict['keys']) 比如筆者自己的代碼
self.LocalConv1 = {i + 1: nn.Conv2d(32, 32, 3, stride=1, padding=0) for i in range(4)}for i in self.LocalConv1:setattr(self, 'LocalConvPart%d' % i, self.LocalConv1[i])self.GlobalFullConnect = nn.Linear(7 * 2 * 32, 400)self.LocalFullConnect = {i + 1: nn.Linear(32 * 23 * 16, 100) for i in range(4)}for i in self.LocalFullConnect:setattr(self, 'LocalFullConnectPart%d' % i, self.LocalFullConnect[i])
建議使用方法1,因?yàn)镸odule類重載了__setattr__()方法,如下
def __setattr__(self, name, value):def remove_from(*dicts):for d in dicts:if name in d:del d[name]params = self.__dict__.get('_parameters')if isinstance(value, Parameter):if params is None:raise AttributeError("cannot assign parameters before Module.__init__() call")remove_from(self.__dict__, self._buffers, self._modules)self.register_parameter(name, value)elif params is not None and name in params:if value is not None:raise TypeError("cannot assign '{}' as parameter '{}' (torch.nn.Parameter or None expected)".format(torch.typename(value), name))self.register_parameter(name, value)else:modules = self.__dict__.get('_modules')if isinstance(value, Module):if modules is None:raise AttributeError("cannot assign module before Module.__init__() call")remove_from(self.__dict__, self._parameters, self._buffers)modules[name] = valueelif modules is not None and name in modules:if value is not None:raise TypeError("cannot assign '{}' as child module '{}' ""(torch.nn.Module or None expected)".format(torch.typename(value), name))modules[name] = valueelse:buffers = self.__dict__.get('_buffers')if buffers is not None and name in buffers:if value is not None and not torch
總結(jié)
以上是生活随笔為你收集整理的Pytorch源码与运行原理浅析--网络篇(一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PyTorch 学习笔记(四):权值初始
- 下一篇: pytorch学习笔记(十二):详解 M