Pytorch源碼與運行原理淺析--網絡篇(一)


前言

申請的專欄開通了,剛好最近閑下來了,就打算開這個坑了hhhhh

第一篇就先講一講pytorch的運行機制好了。。。

記得當時剛剛接觸的時候一直搞不明白,為什么自己只是定義了幾個網絡,就可以完整的訓練整個模型,它背后的機制又是如何,搞明白了這個,才有可能去做更多的定制的更改,比如更改loss,反傳方式,梯度下降機制,甚至自定義參數更新速率(比如學習率隨着迭代輪數下降),文章比較淺顯,希望各位大神不吝賜教。

知識儲備

看此文章的前提,大概需要你寫過一個利用pytorch的訓練程序,哪怕官網上的MNIST

因為本文目的是告訴你為什么這么寫

為什么不用TensorFlow

其實我之前是有用TF的,但是,emmmmmmmm.......

之后接觸了Pytorch,那一整天都在感嘆"還有這種操作?"

個人感覺TF不是一個易於理解和易於擴展的框架。

比如說,我想實現學習率隨迭代輪數降低,需要修改哪些?

那么,讓我們開始吧

從MNIST說起

網絡定義篇

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__方法直接定義了你的網絡,這就是你的模型中含有的全部的東西,你的模型本身也只有__init__ 中的屬性會被每一次訓練的時候更改,可以說這個思路是十分的清晰了。

之后,是forward方法,這里定義了如何處理傳入的數據(就是那個x),返回這個神經網絡的output

這里,我把它比作名詞和動詞的關系,__init__()方法定義了網絡本身,或者說定義了一個個的名詞,而我們也需要一系列的"猜測"過程,猜出這些名詞是什么。而forward()方法,則是一個個的動詞,它提供了如何處理這些名詞的方式。

而之后,我們來看看,運行的時候,發生了什么

首先,我們看看torch.nn.Module,看看它是如何定義的。

torch.nn.Module

源代碼在此處

class Module(object): dump_patches = False def __init__(self): self._backend = thnn_backend self._parameters = OrderedDict() self._buffers = OrderedDict() self._backward_hooks = OrderedDict() self._forward_hooks = OrderedDict() self._forward_pre_hooks = OrderedDict() self._modules = OrderedDict() self.training = True def forward(self, *input): raise NotImplementedError

(代碼不完整,只截取了一段)

可以看到,Module類定義了一系列訓練時使用的變量比如參數(感覺這是是緩存的參數,用來之后做參數更新用的),buffers,幾個hooks(個人感覺這些hooks是之后與loss,反傳之類的步驟通訊數據用的)

反傳里面是有一個判斷的邏輯,判斷你的子類有沒有定義網絡,沒有就報錯(講真,這個想法很棒啊QwQ,子類重寫父類方法,沒有重寫就是個報錯hhhhhh)

def register_buffer(self, name, tensor): self._buffers[name] = tensor def 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] = None elif 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的注冊,這里有一點需要提醒,在你自定義的網絡中,如果你用了類似

self.some_dict['keys'] = nn.Conv2d(10, 20, kernel_size=5)

這種語句的話,pytorch是沒有辦法這個變量的,也不會參與之后的傳參之類的

在定義了上面那句話之后你必須用類似

# 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,因為Module類重載了__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] = value elif 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] = value else: buffers = self.__dict__.get('_buffers') if buffers is not None and name in buffers: if value is not None and not torch.is_tensor(value): raise TypeError("cannot assign '{}' as buffer '{}' " "(torch.Tensor or None expected)" .format(torch.typename(value), name)) buffers[name] = value else: object.__setattr__(self, name, value)

其實差別不大,可以看到加了很多判斷。

然后之后apply()方法

def _apply(self, fn): for module in self.children(): module._apply(fn) for param in self._parameters.values(): if param is not None: param.data = fn(param.data) if param._grad is not None: param._grad.data = fn(param._grad.data) for key, buf in self._buffers.items(): if buf is not None: self._buffers[key] = fn(buf) return self def apply(self, fn): for module in self.children(): module.apply(fn) fn(self) return self

這兩個方法就是更新參數的核心過程了,pytorch的更新參數最底層的方法都是這兩個方法定義的。

之后的cpu(),cuda()之類的方法大家都知道是干什么的,我就不贅述了,啊,順帶提一句,這個cuda()方法是對每個變量都covert to cuda的,十分的方便。

def register_backward_hook(self, hook): handle = hooks.RemovableHandle(self._backward_hooks) self._backward_hooks[handle.id] = hook return handle def register_forward_pre_hook(self, hook): handle = hooks.RemovableHandle(self._forward_pre_hooks) self._forward_pre_hooks[handle.id] = hook return handle def register_forward_hook(self, hook): handle = hooks.RemovableHandle(self._forward_hooks) self._forward_hooks[handle.id] = hook return handle

訓練過程的參數傳遞,這些方法完成了神經網絡,Loss,梯度下降等算法等等一系列計算的之間的數據通信。

結語

先寫到這里,nn.Module大概寫了一半左右吧,希望各位大佬們給出建議QwQ


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM