pytorch學(xué)習(xí)

對教材《深度學(xué)習(xí)框架pytorch:入門與實(shí)踐》和一些技術(shù)博客及實(shí)踐過程中的總結(jié),比較精煉。

網(wǎng)絡(luò)模型定義

一般通過繼承pytoch實(shí)現(xiàn)的torch.nn.Module(以下簡稱Module)濒蒋,然后定義自己的網(wǎng)絡(luò)的每層。
這里我們先通過繼承Module構(gòu)建一個(gè)抽象的網(wǎng)絡(luò)BasicModule實(shí)現(xiàn)了對Module.save/.load的重載,方便保存和加載已訓(xùn)練完畢的模型(持久化)蹦掐。

class BasicModule(t.nn.Module):

    def __init__(self):
        super(BasicModule, self).__init__()     # 初始化父類
        self.model_name = 'model'   #
        print(self.model_name)

    def load(self, path):
        self.load_state_dict(t.load(path))

    def save(self, name=None):
        if name is None:
            abspath = os.getcwd()
            prefix = abspath + '/data/checkpoint/' + self.model_name + '_'
            name = time.strftime(prefix + '%m%d_%H:%M:%S.pth')
        else:
            abspath = os.getcwd()
            prefix = abspath + '/data/checkpoint/' + name + '_'
            name = time.strftime(prefix + '%m%d_%H:%M:%S.pth')
        t.save(self.state_dict(), name)
        return name

以上是對模型Module的持久化,對于tensor的持久化只需要t.load(obj,filename)/t.save(obj,filename)僵闯。而對于Module和Optimizer建議保存其state_dict()(以上就是這么做的)而不是直接保存整個(gè)模型或優(yōu)化器卧抗,可以保存其參數(shù)和動(dòng)量信息。

基于以上的BasicModule實(shí)現(xiàn)自己的Module就可以具有加載和保存參數(shù)的功能

class BriefNet(BM.BasicModule):
    def __init__(self):
        super(BriefNet, self).__init__()
        # nn.ReLU 和 functional.relu的差別(大部分nn.layer 都對應(yīng)一個(gè)nn.functional中的函數(shù)):
        # 前者是nn.Module的子類鳖粟,后者是nn.functional的子類
        # nn.中許多l(xiāng)ayer和激活函數(shù)都是繼承與nn.Module的可以自動(dòng)提取派生類中的可學(xué)習(xí)參數(shù)(通過繼承Module實(shí)現(xiàn)的__getattr__和__setattr__)
        # functional是沒有可學(xué)習(xí)參數(shù)的颗味,可以用在激活函數(shù)、池化等地方
        self.fc = nn.Sequential(
            nn.Linear(784, 256),  # 數(shù)據(jù)集圖片為28*28單通道牺弹,此處直接用多層感知機(jī)而不是用CNN
            nn.ReLU(),
            nn.Dropout(0.3),

            nn.Linear(256, 128),
            nn.ReLU(),
            nn.Dropout(0.3),

            nn.Linear(128, 64),
            nn.ReLU(),
            nn.Dropout(0.3),

            nn.Linear(64, 10),
            nn.LogSoftmax(dim=1)
        )

    def forward(self, x):
        # 定義前向傳播函數(shù)
        x = x.view(x.shape[0], -1)  # 將輸入圖片變成列向量輸入
        x = self.fc(x)
        return x

定義起來還是很簡單的浦马,網(wǎng)絡(luò)定義有兩種比較常見的格式(無性能差別)

  • 一種是使用nn.Sequantial(推薦),其也是Module的子類张漂,相對于ModuleList而言可以直接輸入如 self.fc(x)晶默。nn.Sequantial中定義了一些列的nn.Module的子類如:Linear(全連接、仿射層)航攒、ReLu(激活函數(shù))磺陡、Dropout、SoftMax漠畜、Conv等常用的layers币他,使用Sequential可以很方便的把這些layer的輸入輸出自動(dòng)連接起來,自己來定義layer間的組合而得到一個(gè)復(fù)合layer(代碼結(jié)構(gòu)意義上的一個(gè)憔狞,只需要給定整個(gè)sequential一個(gè)輸入蝴悉,就能得出一個(gè)輸出,而不需要一層一層的輸入輸出和第二種方法有差別)瘾敢,這樣的好處是定義簡潔拍冠、且通過對本model的控制如models.train()尿这、models.eval()就可以自動(dòng)的設(shè)置其內(nèi)部那些訓(xùn)練和驗(yàn)證具有不同表現(xiàn)的layer所處的狀態(tài)或model.zero_grad()實(shí)現(xiàn)對所有葉節(jié)點(diǎn)參數(shù)梯度清空操作。更有一體感庆杜,方便宏觀掌控射众。

  • 另一種是通過在__init__中自己聲明一些nn中實(shí)現(xiàn)的layer的實(shí)例屬性(相較于之前這是比較分散的)。

    class Classifier(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(784, 256)
        self.fc2 = nn.Linear(256, 128)
        self.fc3 = nn.Linear(128, 64)
        self.fc4 = nn.Linear(64, 10)
        
    def forward(self, x):
        # make sure input tensor is flattened
        x = x.view(x.shape[0], -1)
        
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = F.relu(self.fc3(x))
        x = F.log_softmax(self.fc4(x), dim=1)
        
        return x
    

    nn中實(shí)現(xiàn)的大多數(shù)layers在nn.functional中也有對應(yīng)的實(shí)現(xiàn)(一般為全小寫函數(shù)如nn.functinal.relu晃财、softmax叨橱、F.conv)。介紹到這断盛,注意一下代碼中注釋提示:用Module實(shí)現(xiàn)的子類layer能自動(dòng)提取內(nèi)部的類型為nn.parameter可學(xué)習(xí)參數(shù)(需要變化的參數(shù))罗洗,而nn.functional中的函數(shù)更像是純函數(shù)調(diào)用時(shí)必須給輸入如F.relu(input)、F.linear(input,model.weight,model.bias)郑临。且functional這種純函數(shù)的用法更適合那些沒有可學(xué)習(xí)參數(shù)的層如pooling栖博、ReLU、sigmoid厢洞、tanh仇让,他們參數(shù)都是固定的,不需要輸入需要學(xué)習(xí)的參數(shù)到functional.xxx()的輸入中躺翻,可以保持完整感丧叽。但是對于dropout建議使用Module,否則需要手動(dòng)給出當(dāng)前訓(xùn)練狀態(tài)F.dropout(input,training=self.training)公你。

  • 然后就是實(shí)現(xiàn)forward踊淳。給網(wǎng)絡(luò)一個(gè)輸入即output=model(input)其內(nèi)部會(huì)調(diào)用model.__call__(nn.Module實(shí)現(xiàn)),call中主要是調(diào)用了forward陕靠。forward主要負(fù)責(zé)計(jì)算圖的構(gòu)建迂尝,然后我們就能根據(jù)計(jì)算圖進(jìn)行自動(dòng)求導(dǎo)計(jì)算。

  • 這里說一下以上內(nèi)容的實(shí)現(xiàn)原理:

    1. 為何nn.Module可以自動(dòng)提取子類內(nèi)部的可學(xué)習(xí)參數(shù):
      因?yàn)?code>nn.Module中實(shí)現(xiàn)了兩個(gè)魔法方法.__getattr__和.__setattr__剪芥。而obj.name=value 等價(jià)于setattr(obj,'name',value)obj.name 等價(jià)于 getattr(obj,'name')垄开,當(dāng)定義了自定義方法后getattr和setattr會(huì)調(diào)用自定義的.__getattr__和.__setattr__。然后税肪,當(dāng)用戶在子類定義實(shí)例屬性時(shí)候就會(huì)觸發(fā).__setattr__(obj,'name',value)溉躲,在此函數(shù)中會(huì)判斷obj類型是否是Parameter或nn.Module對象,如果是就存放到_parameters和_models兩個(gè)私有字典中益兄;如果是其他對象锻梳,則調(diào)用默認(rèn)操作:添加到__dict__字典中。而_models中的都是子models净捅,他們在定義的時(shí)候也會(huì)在其內(nèi)部的._models字典中存放其子models疑枯。最終可能是會(huì)對本自定義models內(nèi)的所有子models(含迭代)的parameter進(jìn)行一些管理。

    2. 是如何實(shí)現(xiàn)自動(dòng)求導(dǎo)的:
      在這之前先了解一下torch的一些數(shù)據(jù)結(jié)構(gòu):nn.Parameter灸叼、autograd.variable神汹、torch.tensor庆捺。
      a. 其中tensor是numpy中array類似的多維數(shù)組古今。
      b.variable是以tensor作為核心data對象并進(jìn)行了一些 擴(kuò)充(如grad梯度值(目標(biāo)函數(shù)對本變量的偏導(dǎo)屁魏,也是variable類)、grad_fn梯度計(jì)算函數(shù)捉腥、requires_grad是否需要自動(dòng)求導(dǎo)功能)氓拼,使其能進(jìn)行自動(dòng)求導(dǎo)功能。且model(網(wǎng)絡(luò)模型)的輸入都是以variable形式輸入的抵碟。如果數(shù)據(jù)集經(jīng)過變換to_tensor在輸入前還需要input=Variable(input)桃漾。注釋:autograd只實(shí)現(xiàn)了標(biāo)量目標(biāo)函數(shù)對葉節(jié)點(diǎn)求導(dǎo),對目標(biāo)函數(shù)進(jìn)行backward()后葉節(jié)點(diǎn)變量x就可以得到目標(biāo)函數(shù)對x偏導(dǎo)x.grad拟逮,但是葉節(jié)點(diǎn)有標(biāo)記AccumulateGrad會(huì)自動(dòng)累加歷次backward的梯度值撬统。因此調(diào)用反向傳播時(shí)需要清零梯度x.grad.data.zero_()inplace操作清零
      c. Parametervariable的子類敦迄,且默認(rèn)是requires_grad=True恋追。能在定義Model時(shí)候自動(dòng)被基類nn.Module管理。

    3. 什么是計(jì)算圖(pytorch是動(dòng)態(tài)圖):計(jì)算圖是一種有向無環(huán)圖且圖中有兩種節(jié)點(diǎn):

      • 一種是○表示的變量(variable)罚屋;
      • 另一種是□表示的算子(如已實(shí)現(xiàn)重載的+苦囱、-、*脾猛、/撕彤、**n等.sum()、t.exp等已實(shí)現(xiàn)函數(shù)基于Function類自定義運(yùn)算符)猛拴。

      其中羹铅,用戶聲明的Variable為葉節(jié)點(diǎn)(需要requires_grad的)是依賴于其他變量了;其他節(jié)點(diǎn)都是通過對葉節(jié)點(diǎn)及其運(yùn)算結(jié)果進(jìn)行復(fù)合運(yùn)算得到的一些中間Variable和最終的目標(biāo)函數(shù)愉昆。我們定義用戶Variable职员,拿他們他們之間運(yùn)算結(jié)果做運(yùn)算時(shí),會(huì)自動(dòng)根據(jù)重載或以實(shí)現(xiàn)的運(yùn)算函數(shù)(都是算子)進(jìn)行邊的連接(輸入輸出關(guān)系撼唾,是有向的)(并在算子內(nèi)儲存一份輸入輸出拷貝用來梯度計(jì)算時(shí)使用)廉邑,然后生成一個(gè)圖。圖中任意一點(diǎn)都可以利用鏈?zhǔn)椒▌t實(shí)現(xiàn)5构取蛛蒙!因此當(dāng)對requires_grad的葉子節(jié)點(diǎn)求導(dǎo)時(shí),中間變量Variable都會(huì)自動(dòng).requires_grad=True渤愁,但非葉子節(jié)點(diǎn)計(jì)算完后會(huì)清空梯度牵祟。此外是利用以下幾個(gè)規(guī)則來進(jìn)行鏈?zhǔn)椒▌t的:當(dāng)y是標(biāo)量的時(shí)候可以進(jìn)行y.backward(),但是y是向量(認(rèn)為是中間variable)其求梯度必須輸入一個(gè)參數(shù)y.backward(grad_y)否則會(huì)提示只能進(jìn)行標(biāo)量對向量求梯度抖格,其中grad_y=標(biāo)量目標(biāo)函數(shù)z對向量y求偏導(dǎo)诺苹。

    4. 目前大多數(shù)算子都可以使用autograd的反向求導(dǎo)咕晋,自己寫的復(fù)雜函數(shù)沒有,如何解決:自己實(shí)現(xiàn)的復(fù)雜函數(shù)(算子)不具備自動(dòng)求導(dǎo)功能(比如y=x**2+x*2支持收奔,但y=func(x)的func不支持)掌呜。可以利用autograd.Function對autograd功能進(jìn)行拓展坪哄,其中Function對應(yīng)計(jì)算圖中的□质蕉。需要自己實(shí)現(xiàn)一個(gè)繼承與autograd.Function的算子,并自己實(shí)現(xiàn)求導(dǎo)翩肌。這樣就可以在計(jì)算圖使用它并使得鏈?zhǔn)椒▌t進(jìn)行下去了模暗。其中forward輸入輸出都是tensor,而backward的輸入和輸出都是variable念祭。backward函數(shù)的輸入對應(yīng)forwad的輸出兑宇,backward的輸出對應(yīng)forward的輸入。然后用Function.apply(variable)即可調(diào)用實(shí)現(xiàn)的Function粱坤。如z=func(V(...),V(...),...)會(huì)自動(dòng)調(diào)用forward并提取Variable中的data(tensor類型)作為輸入然后把輸出的tensor包裝成Variable隶糕,z.backward()就會(huì)自動(dòng)調(diào)用func.backward()

      class func(Function):
        @staticmethod
        def forward(ctx,input1,input2,...):
            ctx.save_for_backward(input1,input2,...,)
            output = # some opertation with implemently factor
            return output
        @staticmethod
        def backward(ctx,grad_z_input) #grad_z_input對應(yīng)y.backward()中輸入的前鏈dz/dy
            input1,input2,... = ctx.saved_variables
            grad_input1_x = grad_z_input * (...)
            grad_input2_x = grad_z_input * (...)
            return grad_input1_x,grad_input2_x,...
      

數(shù)據(jù)集加載

訓(xùn)練

訓(xùn)練除了需要數(shù)據(jù)集,還需要損失函數(shù)/目標(biāo)函數(shù)比规、優(yōu)化器若厚。其中損失函數(shù)是標(biāo)量函數(shù),將網(wǎng)絡(luò)的輸出(多維向量)進(jìn)行某種評估得分蜒什。我們訓(xùn)練的方法就是BP即通過求取目標(biāo)函數(shù)對輸入(葉節(jié)點(diǎn))的梯度测秸,然后通過優(yōu)化器來求去網(wǎng)絡(luò)可學(xué)習(xí)參數(shù)的變化量的。

損失函數(shù)

torch.nn中實(shí)現(xiàn)了許多損失函數(shù)如:NLLLoss灾常、CrossEntropyLoss等常用損失函數(shù)霎冯。
如果網(wǎng)絡(luò)訓(xùn)練需要用GPU則需要使用對應(yīng)的損失函數(shù)即.cuda()。因?yàn)闉榱擞肎PU進(jìn)行訓(xùn)練钞瀑,網(wǎng)絡(luò)參數(shù)在GPU上沈撞、輸入也需要在GPU上才能進(jìn)行前向傳播、網(wǎng)絡(luò)輸出的向量也是在GPU上的雕什,為了評估輸出缠俺,損失函數(shù)計(jì)算時(shí)候的參數(shù)也是GPU上的才能和網(wǎng)絡(luò)輸出進(jìn)行運(yùn)算,因此損失函數(shù)求出的score也是GPU上的贷岸。

    if option.use_gpu:
        criterion = t.nn.NLLLoss().cuda()
    else:
        criterion = t.nn.NLLLoss()
優(yōu)化器

優(yōu)化器根據(jù)criterion.backward()得到model.parameters().grad壹士。然后得到的梯度進(jìn)行一些優(yōu)化策略從而計(jì)算出網(wǎng)絡(luò)中可學(xué)習(xí)參數(shù)的更新值。并能通過model.zero_grad()或optimizer.zero_grad()清空對葉節(jié)點(diǎn)(網(wǎng)絡(luò)可學(xué)習(xí)參數(shù))梯度偿警。
torch.optim中實(shí)現(xiàn)了許多常用的優(yōu)化器如:SGD(根據(jù)參數(shù)覺得是否用動(dòng)量)躏救、Adam...
這里注意:學(xué)習(xí)率和weight_decay(可能導(dǎo)致學(xué)習(xí)率為0)要小心設(shè)計(jì)否則優(yōu)化結(jié)果導(dǎo)致訓(xùn)練發(fā)散或不變。

optimizer = optim.SGD(model.parameters(), lr=option.lr, momentum=0.8)  # , weight_decay=option.lr_decay) # 慎用,
訓(xùn)練

網(wǎng)絡(luò)設(shè)置為訓(xùn)練模式 -> 梯度清零 -> 輸入網(wǎng)絡(luò)(Variable輸入) -> 根據(jù)實(shí)際值和輸出值計(jì)算損失函數(shù)輸出評分 -> 求評分對網(wǎng)絡(luò)可學(xué)習(xí)參數(shù)梯度 -> 優(yōu)化器更新網(wǎng)絡(luò)可學(xué)習(xí)參數(shù)

            model.train()
            optimizer.zero_grad()
            ps = model(x)
            loss = criterion(ps, target)
            loss.backward()
            optimizer.step()

訓(xùn)練完需要model.save(path)保存網(wǎng)絡(luò)

測試

需要先model.load(path)加載訓(xùn)練后的參數(shù)
把網(wǎng)絡(luò)設(shè)置成測試模型 -> 輸入 -> (optional:計(jì)算損失函數(shù) -> ) 獲得輸出 -> 輸出和已知標(biāo)簽對比 -> 計(jì)算準(zhǔn)確度

        model.eval()
        ps = model(val_input)
        test_loss = criterion(ps, val_labels)
        top_p, top_class = t.exp(ps).topk(1, dim=1)
        equals = top_class == val_labels.view(*top_class.shape)
        accuracy += t.mean(equals.type(t.FloatTensor))

結(jié)語

至此盒使,就簡要的記完了這幾天學(xué)習(xí)pytorch的結(jié)果了崩掘。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市少办,隨后出現(xiàn)的幾起案子苞慢,更是在濱河造成了極大的恐慌,老刑警劉巖凡泣,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枉疼,死亡現(xiàn)場離奇詭異皮假,居然都是意外死亡鞋拟,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門惹资,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贺纲,“玉大人,你說我怎么就攤上這事褪测『锾埽” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵侮措,是天一觀的道長懈叹。 經(jīng)常有香客問我,道長分扎,這世上最難降的妖魔是什么澄成? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮畏吓,結(jié)果婚禮上墨状,老公的妹妹穿的比我還像新娘。我一直安慰自己菲饼,他們只是感情好肾砂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宏悦,像睡著了一般镐确。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上饼煞,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天源葫,我揣著相機(jī)與錄音,去河邊找鬼派哲。 笑死臼氨,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的芭届。 我是一名探鬼主播储矩,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼感耙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了持隧?” 一聲冷哼從身側(cè)響起即硼,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎屡拨,沒想到半個(gè)月后只酥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡呀狼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年裂允,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哥艇。...
    茶點(diǎn)故事閱讀 39,722評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡绝编,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出貌踏,到底是詐尸還是另有隱情十饥,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布祖乳,位于F島的核電站逗堵,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏眷昆。R本人自食惡果不足惜蜒秤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望隙赁。 院中可真熱鬧垦藏,春花似錦、人聲如沸伞访。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽厚掷。三九已至弟灼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間冒黑,已是汗流浹背田绑。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留抡爹,地道東北人掩驱。 一個(gè)月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親欧穴。 傳聞我的和親對象是個(gè)殘疾皇子民逼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容