中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

寧波易通寧波網(wǎng)站建設(shè)優(yōu)化落實(shí)新十條措施

寧波易通寧波網(wǎng)站建設(shè),優(yōu)化落實(shí)新十條措施,企業(yè)網(wǎng)站模板 演示,深圳市企業(yè)網(wǎng)站建設(shè)哪家好原文:PyTorch Deep Learning Hands-On 協(xié)議:CC BY-NC-SA 4.0 譯者:飛龍 本文來自【ApacheCN 深度學(xué)習(xí) 譯文集】,采用譯后編輯(MTPE)流程來盡可能提升效率。 不要擔(dān)心自己的形象,只關(guān)心如何實(shí)現(xiàn)目…

原文:PyTorch Deep Learning Hands-On

協(xié)議:CC BY-NC-SA 4.0

譯者:飛龍

本文來自【ApacheCN 深度學(xué)習(xí) 譯文集】,采用譯后編輯(MTPE)流程來盡可能提升效率。

不要擔(dān)心自己的形象,只關(guān)心如何實(shí)現(xiàn)目標(biāo)。——《原則》,生活原則 2.3.c

六、生成網(wǎng)絡(luò)

生成網(wǎng)絡(luò)得到了加州理工學(xué)院理工學(xué)院本科物理學(xué)教授理查德·費(fèi)曼(Richard Feynman)和諾貝爾獎(jiǎng)獲得者的名言的支持:“我無法創(chuàng)造,就無法理解”。 生成網(wǎng)絡(luò)是擁有可以理解世界并在其中存儲(chǔ)知識(shí)的系統(tǒng)的最有前途的方法之一。 顧名思義,生成網(wǎng)絡(luò)學(xué)習(xí)真實(shí)數(shù)據(jù)分布的模式,并嘗試生成看起來像來自此真實(shí)數(shù)據(jù)分布的樣本的新樣本。

生成模型是無監(jiān)督學(xué)習(xí)的子類別,因?yàn)樗鼈兺ㄟ^嘗試生成樣本來學(xué)習(xí)基本模式。 他們通過推送低維潛向量和參數(shù)向量來了解生成圖像所需的重要特征,從而實(shí)現(xiàn)了這一目的。 網(wǎng)絡(luò)在生成圖像時(shí)獲得的知識(shí)本質(zhì)上是關(guān)于系統(tǒng)和環(huán)境的知識(shí)。 從某種意義上說,我們通過要求網(wǎng)絡(luò)做某事來欺騙網(wǎng)絡(luò),但是網(wǎng)絡(luò)必須在不了解自己正在學(xué)習(xí)的情況下學(xué)習(xí)我們的需求。

生成網(wǎng)絡(luò)已經(jīng)在不同的深度學(xué)習(xí)領(lǐng)域,特別是在計(jì)算機(jī)視覺領(lǐng)域顯示出了可喜的成果。 去模糊或提高圖像的分辨率,圖像修補(bǔ)以填充缺失的片段,對(duì)音頻片段進(jìn)行降噪,從文本生成語音,自動(dòng)回復(fù)消息以及從文本生成圖像/視頻是一些研究的活躍領(lǐng)域。

在本章中,我們將討論一些主要的生成網(wǎng)絡(luò)架構(gòu)。 更準(zhǔn)確地說,我們將看到一個(gè)自回歸模型和一個(gè)生成對(duì)抗網(wǎng)絡(luò)GAN)。 首先,我們將了解這兩種架構(gòu)的基本組成部分是什么,以及它們之間的區(qū)別。 除此說明外,我們還將介紹一些示例和 PyTorch 代碼。

定義方法

生成網(wǎng)絡(luò)現(xiàn)今主要用于藝術(shù)應(yīng)用中。 樣式遷移,圖像優(yōu)化,去模糊,分辨率改善以及其他一些示例。 以下是計(jì)算機(jī)視覺中使用的生成模型的兩個(gè)示例。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-Q9h8yUTo-1681786272898)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_06_01.jpg)]

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-n5HezrBS-1681786272899)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_06_02.jpg)]

圖 6.1:生成模型應(yīng)用示例,例如超分辨率和圖像修復(fù)

來源:《具有上下文注意的生成圖像修復(fù)》,余佳輝等人;《使用生成對(duì)抗網(wǎng)絡(luò)的照片級(jí)逼真的單圖像超分辨率》,Christian Ledig 等人

GAN 的創(chuàng)建者 Ian Goodfellow 描述了幾類生成網(wǎng)絡(luò):

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-OkSRb3lo-1681786272899)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_06_03.jpg)]

圖 6.2 生成網(wǎng)絡(luò)的層次結(jié)構(gòu)

我們將討論這兩個(gè)主要類別,它們?cè)谶^去已經(jīng)討論過很多并且仍然是活躍的研究領(lǐng)域:

  • 自回歸模型
  • GAN

自回歸模型是從先前的值推斷當(dāng)前值的模型,正如我們?cè)诘?5 章,“序列數(shù)據(jù)處理”中使用 RNN 所討論的那樣。 變分自編碼器VAE)是自編碼器的一種變體,由編碼器和解碼器組成,其中編碼器將輸入編碼為低維潛在空間向量, 解碼器解碼潛向量以生成類似于輸入的輸出。

整個(gè)研究界都同意,GAN 是人工智能世界中的下一個(gè)重要事物之一。 GAN 具有生成網(wǎng)絡(luò)和對(duì)抗網(wǎng)絡(luò),并且兩者相互競(jìng)爭(zhēng)以生成高質(zhì)量的輸出圖像。 GAN 和自回歸模型都基于不同的原理工作,但是每種方法都有其自身的優(yōu)缺點(diǎn)。 在本章中,我們將使用這兩種方法開發(fā)一個(gè)基本示例。

自回歸模型

自回歸模型使用先前步驟中的信息并創(chuàng)建下一個(gè)輸出。 RNN 為語言建模任務(wù)生成文本是自回歸模型的典型示例。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-XECPL5oO-1681786272899)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_06_04.jpg)]

圖 6.3:用于 RNN 語言建模的自回歸模型

自回歸模型獨(dú)立生成第一個(gè)輸入,或者我們將其提供給網(wǎng)絡(luò)。 例如,對(duì)于 RNN,我們將第一個(gè)單詞提供給網(wǎng)絡(luò),而網(wǎng)絡(luò)使用我們提供的第一個(gè)單詞來假設(shè)第二個(gè)單詞是什么。 然后,它使用第一個(gè)和第二個(gè)單詞來預(yù)測(cè)第三個(gè)單詞,依此類推。

盡管大多數(shù)生成任務(wù)都是在圖像上完成的,但我們的自回歸生成是在音頻上。 我們將構(gòu)建 WaveNet,它是 Google DeepMind 的研究成果,它是當(dāng)前音頻生成的最新實(shí)現(xiàn),尤其是用于文本到語音處理。 通過這一過程,我們將探索什么是用于音頻處理的 PyTorch API。 但是在查看 WaveNet 之前,我們需要實(shí)現(xiàn) WaveNet 的基礎(chǔ)模塊 PixelCNN,它基于自回歸卷積神經(jīng)網(wǎng)絡(luò)CNN)構(gòu)建。

自回歸模型已經(jīng)被使用和探索了很多,因?yàn)槊糠N流行的方法都有其自身的缺點(diǎn)。 自回歸模型的主要缺點(diǎn)是它們的速度,因?yàn)樗鼈冺樞蛏奢敵觥?由于正向傳播也是順序的,因此在 PixelRNN 中情況變得更糟。

PixelCNN

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-ag2ZODpW-1681786272900)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_06_05.jpg)]

圖 6.4:從 PixelCNN 生成的圖像

資料來源:《使用 PixelCNN 解碼器的條件圖像生成》,A?ronvan den Oord 和其他人

PixelCNN 由 DeepMind 引入,并且是 DeepMind 引入的三種自回歸模型之一。 在首次引入 PixelCNN 之后,已經(jīng)進(jìn)行了多次迭代以提高速度和效率,但是我們將學(xué)習(xí)基本的 PixelCNN,這是構(gòu)建 WaveNet 所需要的。

PixelCNN 一次生成一個(gè)像素,并使用該像素生成下一個(gè)像素,然后使用前兩個(gè)像素生成下一個(gè)像素。 在 PixelCNN 中,有一個(gè)概率密度模型,該模型可以學(xué)習(xí)所有圖像的密度分布并從該分布生成圖像。 但是在這里,我們?cè)噲D通過采用所有先前預(yù)測(cè)的聯(lián)合概率來限制在所有先前生成的像素上生成的每個(gè)像素。

與 PixelRNN 不同,PixelCNN 使用卷積層作為接收?qǐng)?#xff0c;從而縮短了輸入的讀取時(shí)間。 考慮一下圖像被某些東西遮擋了; 假設(shè)我們只有一半的圖像。 因此,我們有一半的圖像,并且我們的算法需要生成后半部分。 在 PixelRNN 中,網(wǎng)絡(luò)需要像圖像中的單詞序列一樣逐個(gè)獲取每個(gè)像素,并生成一半的圖像,而 PixelCNN 則通過卷積層一次獲取圖像。 但是,無論如何,PixelCNN 的生成都必須是順序的。 您可能想知道只有一半的圖像會(huì)進(jìn)行卷積。 答案是遮罩卷積,我們將在后面解釋。

“圖 6.5”顯示了如何對(duì)像素集應(yīng)用卷積運(yùn)算以預(yù)測(cè)中心像素。 與其他模型相比,自回歸模型的主要優(yōu)點(diǎn)是聯(lián)合概率學(xué)習(xí)技術(shù)易于處理,可以使用梯度下降進(jìn)行學(xué)習(xí)。 沒有近似值,也沒有解決方法。 我們只是嘗試在給定所有先前像素值的情況下預(yù)測(cè)每個(gè)像素值,并且訓(xùn)練完全由反向傳播支持。 但是,由于生成始終是順序的,因此我們很難使用自回歸模型來實(shí)現(xiàn)可伸縮性。 PixelCNN 是一個(gè)結(jié)構(gòu)良好的模型,在生成新像素的同時(shí),將各個(gè)概率的乘積作為所有先前像素的聯(lián)合概率。 在 RNN 模型中,這是默認(rèn)行為,但是 CNN 模型通過使用巧妙設(shè)計(jì)的遮罩來實(shí)現(xiàn)此目的,如前所述。

PixelCNN 捕獲參數(shù)中像素之間的依存關(guān)系分布,這與其他方法不同。 VAE 通過生成隱藏的潛在向量來學(xué)習(xí)此分布,該向量引入了獨(dú)立的假設(shè)。 在 PixelCNN 中,學(xué)習(xí)的依賴性不僅在先前的像素之間,而且在不同的通道之間; 在正常的彩色圖像中,它是紅色,綠色和藍(lán)色(RGB)。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-itEi73Co-1681786272900)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_06_06.jpg)]

圖 6.5:從周圍像素預(yù)測(cè)像素值

有一個(gè)基本問題:如果 CNN 嘗試使用當(dāng)前像素或?qū)淼南袼貋韺W(xué)習(xí)當(dāng)前像素怎么辦? 這也由掩碼管理,掩碼將自身的粒度也提高到了通道級(jí)別。 例如,當(dāng)前像素的紅色通道不會(huì)從當(dāng)前像素中學(xué)習(xí),但會(huì)從先前的像素中學(xué)習(xí)。 但是綠色通道現(xiàn)在可以使用當(dāng)前紅色通道和所有先前的像素。 同樣,藍(lán)色通道可以從當(dāng)前像素的綠色和紅色通道以及所有先前的像素中學(xué)習(xí)。

整個(gè)網(wǎng)絡(luò)中使用兩種類型的掩碼,但是后面的層不需要具有這種安全性,盡管它們?cè)谶M(jìn)行并行卷積操作時(shí)仍需要模擬順序?qū)W習(xí)。 因此,PixelCNN 論文[1]引入了兩種類型的蒙版:類型 A 和類型 B。

使 PixelCNN 與其他傳統(tǒng) CNN 模型脫穎而出的主要架構(gòu)差異之一是缺少池化層。 由于 PixelCNN 的目的不是以縮小尺寸的形式捕獲圖像的本質(zhì),并且我們不能承擔(dān)通過合并丟失上下文的風(fēng)險(xiǎn),因此作者故意刪除了合并層。

fm = 64net = nn.Sequential(MaskedConv2d('A', 1, fm, 7, 1, 3, bias=False),nn.BatchNorm2d(fm), nn.ReLU(True),MaskedConv2d('B', fm, fm, 7, 1, 3, bias=False),nn.BatchNorm2d(fm), nn.ReLU(True),MaskedConv2d('B', fm, fm, 7, 1, 3, bias=False),nn.BatchNorm2d(fm), nn.ReLU(True),MaskedConv2d('B', fm, fm, 7, 1, 3, bias=False),nn.BatchNorm2d(fm), nn.ReLU(True),MaskedConv2d('B', fm, fm, 7, 1, 3, bias=False),nn.BatchNorm2d(fm), nn.ReLU(True),MaskedConv2d('B', fm, fm, 7, 1, 3, bias=False),nn.BatchNorm2d(fm), nn.ReLU(True),MaskedConv2d('B', fm, fm, 7, 1, 3, bias=False),nn.BatchNorm2d(fm), nn.ReLU(True),MaskedConv2d('B', fm, fm, 7, 1, 3, bias=False),nn.BatchNorm2d(fm), nn.ReLU(True),nn.Conv2d(fm, 256, 1))

前面的代碼段是完整的 PixelCNN 模型,該模型包裝在順序單元中。 它由一堆MaskedConv2d實(shí)例組成,這些實(shí)例繼承自torch.nn.Conv2d,并使用了torch.nnConv2d的所有*args**kwargs。 每個(gè)卷積單元之后是批量規(guī)范層和 ReLU 層,這是與卷積層成功組合的。 作者決定不在普通層上使用線性層,而是決定使用普通的二維卷積,事實(shí)證明,該方法比線性層更好。

遮罩卷積

PixelCNN 中使用了遮罩卷積,以防止在訓(xùn)練網(wǎng)絡(luò)時(shí)信息從將來的像素和當(dāng)前的像素流向生成任務(wù)。 這很重要,因?yàn)樵谏上袼貢r(shí),我們無法訪問將來的像素或當(dāng)前像素。 但是,有一個(gè)例外,之前已描述過。 當(dāng)前綠色通道值的生成可以使用紅色通道的預(yù)測(cè),而當(dāng)前藍(lán)色通道的生成可以使用綠色和紅色通道的預(yù)測(cè)。

通過將所有不需要的像素清零來完成屏蔽。 將創(chuàng)建一個(gè)與張量相等的掩碼張量,其值為 1 和 0,對(duì)于所有不必要的像素,其值為 0。 然后,在進(jìn)行卷積運(yùn)算之前,此掩碼張量與權(quán)重張量相乘。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-k6olADRT-1681786272900)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_06_07.jpg)]

圖 6.6:左側(cè)是遮罩,右側(cè)是 PixelCNN 中的上下文

由于 PixelCNN 不使用池化層和反卷積層,因此隨著流的進(jìn)行,通道大小應(yīng)保持恒定。 遮罩 A 專門負(fù)責(zé)阻止網(wǎng)絡(luò)從當(dāng)前像素學(xué)習(xí)值,而遮罩 B 將通道大小保持為三(RGB),并通過允許當(dāng)前像素值取決于本身的值來允許網(wǎng)絡(luò)具有更大的靈活性。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-zJrzcEqZ-1681786272901)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_06_08.jpg)]

圖 6.7:遮罩 A 和遮罩 B

class MaskedConv2d(nn.Conv2d):def __init__(self, mask_type, *args, **kwargs):super(MaskedConv2d, self).__init__(*args, **kwargs)assert mask_type in ('A', 'B')self.register_buffer('mask', self.weight.data.clone())_, _, kH, kW = self.weight.size()self.mask.fill_(1)self.mask[:, :, kH // 2, kW // 2 + (mask_type == 'B'):] = 0self.mask[:, :, kH // 2 + 1:] = 0def forward(self, x):self.weight.data *= self.maskreturn super(MaskedConv2d, self).forward(x)

先前的類MaskedConv2dtorch.nn.Conv2d繼承,而不是從torch.nn.Module繼承。 即使我們從torch.nn.Module繼承來正常創(chuàng)建自定義模型類,但由于我們?cè)噲D使Conv2d增強(qiáng)帶掩碼的操作,我們還是從torch.nn.Conv2D繼承,而torch.nn.Conv2D則從torch.nn.Conv2D繼承 torch.nn.Module。 類方法register_buffer是 PyTorch 提供的方便的 API 之一,可以將任何張量添加到state_dict字典對(duì)象,如果嘗試將模型保存到磁盤,則該對(duì)象隨模型一起保存到磁盤。

添加有狀態(tài)變量(然后可以在forward函數(shù)中重用)的明顯方法是將其添加為對(duì)象屬性:

self.mask = self.weight.data.clone()

但這絕不會(huì)成為state_dict的一部分,也永遠(yuǎn)不會(huì)保存到磁盤。 使用register_buffer,我們可以確保我們創(chuàng)建的新張量將成為state_dict的一部分。 然后使用原地fill_操作將掩碼張量填充為 1s,然后向其添加 0 以得到類似于“圖 6.6”的張量,盡管該圖僅顯示了二維張量, 實(shí)際權(quán)重張量是三維的。 forward函數(shù)僅用于通過乘以遮罩張量來遮罩權(quán)重張量。 乘法將保留與掩碼具有 1 的索引對(duì)應(yīng)的所有值,同時(shí)刪除與掩碼具有 0 的索引對(duì)應(yīng)的所有值。然后,對(duì)父級(jí)Conv2d層的常規(guī)調(diào)用使用權(quán)重張量,并執(zhí)行二維卷積操作。

網(wǎng)絡(luò)的最后一層是 softmax 層,該層可預(yù)測(cè)像素的 256 個(gè)可能值中的值,從而離散化網(wǎng)絡(luò)的輸出生成,而先前使用的最先進(jìn)的自回歸模型將在網(wǎng)絡(luò)的最后一層上繼續(xù)生成值。

optimizer = optim.Adam(net.parameters())
for epoch in range(25):net.train(True)for input, _ in tr:target = (input[:,0] * 255).long()out = net(input)loss = F.cross_entropy(out, target)optimizer.zero_grad()loss.backward()optimizer.step()

訓(xùn)練使用具有默認(rèn)動(dòng)量速率的Adam優(yōu)化器。 另外,損失函數(shù)是從 PyTorch 的Functional模塊創(chuàng)建的。 除了創(chuàng)建target變量以外,其他所有操作均與常規(guī)訓(xùn)練操作相同。

到目前為止,我們一直在有監(jiān)督的學(xué)習(xí)中工作,其中明確給出了標(biāo)簽,但是在這種情況下,目標(biāo)與輸入相同,因?yàn)槲覀冊(cè)噲D重新創(chuàng)建相同的輸出。 torchvision包對(duì)像素應(yīng)用了轉(zhuǎn)換和歸一化,并將像素值范圍從 0 到 255 轉(zhuǎn)換為 -1 到 1。我們需要轉(zhuǎn)換回 0 到 255 的范圍,因?yàn)槲覀冊(cè)谧詈笠粚邮褂昧?softmax,并且會(huì)在 0 到 255 之間生成概率分布。

門控 PixelCNN

DeepMind 在 PixelCNN 的一篇迭代論文中成功地使用了門控 PixelCNN ,該方法通過用 Sigmoid 和 tanh 構(gòu)建的門代替 ReLU 激活函數(shù)。 PixelCNN [1]的介紹性論文提供了三種用于解決同一代網(wǎng)絡(luò)的不同方法,其中具有 RNN 的模型優(yōu)于其他兩種。 DeepMind 仍引入了基于 CNN 的模型來顯示與 PixelRNN 相比的速度增益。 但是,隨著 PixelCNN 中門控激活的引入,作者能夠?qū)⒈憩F(xiàn)與 RNN 變體相匹配,從而獲得更大的表現(xiàn)增益。 同一篇論文介紹了一種避免盲點(diǎn)并在生成時(shí)增加全局和局部條件的機(jī)制,這超出了本書的范圍,因?yàn)閷?duì)于 WaveNet 模型而言這不是必需的。

WaveNet

DeepMind 在另一篇針對(duì)其自回歸生成網(wǎng)絡(luò)的迭代論文[2]中引入了 WaveNet,其中包括 PixelCNN。 實(shí)際上,WaveNet 架構(gòu)是基于 PixelCNN 的構(gòu)建的,與 PixelRNN 相比,WaveNet 架構(gòu)使網(wǎng)絡(luò)能夠以相對(duì)更快的方式生成輸出。 借助 WaveNet,我們?cè)跁惺状翁剿髁酸槍?duì)音頻信號(hào)的神經(jīng)網(wǎng)絡(luò)實(shí)現(xiàn)。 我們對(duì)音頻信號(hào)使用一維卷積,這與 PixelCNN 的二維卷積不同,對(duì)于初學(xué)者而言,這是相當(dāng)復(fù)雜的。

WaveNet 取代了對(duì)音頻信號(hào)使用傅里葉變換的傳統(tǒng)方法。 它通過使神經(jīng)網(wǎng)絡(luò)找出要執(zhí)行的轉(zhuǎn)換來做到這一點(diǎn)。 因此,轉(zhuǎn)換可以反向傳播,原始音頻數(shù)據(jù)可以使用一些技術(shù)來處理,例如膨脹卷積,8 位量化等。 但是人們一直在研究將 WaveNet 方法與傳統(tǒng)方法相結(jié)合,盡管該方法將損失函數(shù)轉(zhuǎn)換為多元回歸,而不是 WaveNet 使用的分類。

PyTorch 向后公開了此類傳統(tǒng)方法的 API。 以下是對(duì)傅立葉變換的結(jié)果進(jìn)行快速傅立葉變換和傅立葉逆變換以獲取實(shí)際輸入的示例。 兩種操作都在二維張量上,最后一個(gè)維為 2,表示復(fù)數(shù)的實(shí)部和虛部。

PyTorch 提供了用于快速傅里葉變換(torch.fft),快速傅里葉逆變換(torch.ifft),實(shí)數(shù)到復(fù)雜傅里葉變換(torch.rfft),實(shí)數(shù)到復(fù)雜傅里葉變換(torch.irfft)的 API。 ),短時(shí)傅立葉變換(torch.stft)和幾個(gè)窗口函數(shù),例如 Hann 窗口,Hamming 窗口和 Bartlett 窗口。

>>> x = torch.ones(3,2)
>>> x1 11 11 1
[torch.FloatTensor of size (3,2)]>>> torch.fft(x, 1)3 30 00 0
[torch.FloatTensor of size (3,2)]>>> fft_x = torch.fft(x, 1)
>>> torch.ifft(fft_x, 1)1 11 11 1
[torch.FloatTensor of size (3,2)]

WaveNet 并不是第一個(gè)引入序列數(shù)據(jù)卷積網(wǎng)絡(luò)或擴(kuò)張的卷積網(wǎng)絡(luò)以加快操作速度的架構(gòu)。 但是 WaveNet 成功地將兩者結(jié)合使用,從而產(chǎn)生了可區(qū)分的音頻。 第一波 WaveNet 的作者發(fā)布了另一篇迭代論文,該論文極大地加速了的產(chǎn)生,稱為并行 WaveNet。 但是,在本章中,我們將重點(diǎn)關(guān)注普通的 WaveNet,這在很大程度上受到了戈?duì)栙e的資料庫(kù)的啟發(fā)[3]。

WaveNet 的基本構(gòu)件是膨脹卷積,它取代了 RNN 的功能來獲取上下文信息。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-poOWs95G-1681786272901)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_06_09.jpg)]

圖 6.8:沒有卷積卷積的 WaveNet 架構(gòu)

來源: 《WaveNet:原始音頻的生成模型》,Aaron van den Oord 等

“圖 6.8”顯示了 WaveNet 在進(jìn)行新值預(yù)測(cè)時(shí)如何提取有關(guān)上下文的信息。 輸入以藍(lán)色(圖片的底部)給出,它是原始音頻樣本。 例如,一個(gè) 16 kHz 的音頻樣本具有一秒鐘音頻的 16,000 個(gè)數(shù)據(jù)點(diǎn),如果與自然語言的序列長(zhǎng)度(每個(gè)單詞將是一個(gè)數(shù)據(jù)點(diǎn))相比,這是巨大的。 這些長(zhǎng)序列是為什么 RNN 對(duì)原始音頻樣本不太有效的一個(gè)很好的原因。

LSTM 網(wǎng)絡(luò)可以記住上下文信息的實(shí)際序列長(zhǎng)度為 50 到 100。上圖具有三個(gè)隱藏層,這些隱藏層使用來自上一層的信息。 第一層輸入經(jīng)過一維卷積層以生成第二層的數(shù)據(jù)。 卷積可以并行完成,這與 RNN 的情況不同,在卷積中,每個(gè)數(shù)據(jù)點(diǎn)都需要先前的輸入順序地傳遞。 為了使收集更多上下文,我們可以增加層數(shù)。 在“圖 6.8”中,位于第四層的輸出將從輸入層中的五個(gè)節(jié)點(diǎn)獲取上下文信息。 因此,每一層將另外一個(gè)輸入節(jié)點(diǎn)添加到上下文中。 也就是說,如果我們有 10 個(gè)隱藏層,則最后一層將從 12 個(gè)輸入節(jié)點(diǎn)獲取上下文信息。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-LRIC5RS1-1681786272901)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_06_10.jpg)]

圖 6.9:膨脹卷積

來源: 《WaveNet:原始音頻的生成模型》,Aaron van den Oord 等

到目前為止,應(yīng)該很明顯,要達(dá)到 LSTM 網(wǎng)絡(luò)的上下文保持能力為 50 到 100 的實(shí)際限制,該網(wǎng)絡(luò)需要 98 層,這在計(jì)算上是昂貴的。 這是我們使用膨脹卷積的地方。 使用膨脹卷積,我們將為每個(gè)層都有一個(gè)膨脹因子,并且以指數(shù)方式增加該膨脹因子將以對(duì)數(shù)形式減少任何特定上下文窗口寬度所需的層數(shù)。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-SsqWWVXY-1681786272901)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_06_11.jpg)]

圖 6.10:膨脹為 0、2 和 4 的卷積

資料來源:通過擴(kuò)散卷積進(jìn)行的多尺度上下文聚合,Fisher Yu 和 Vladlen Koltun

“圖 6.9”顯示了 WaveNet 中使用的膨脹卷積方案(盡管為了更好地理解膨脹卷積,我們?cè)谶@里使用的是二維圖片; WaveNet 使用一維卷積)。 盡管該實(shí)現(xiàn)方案跳過了中參數(shù)的日志,但最終節(jié)點(diǎn)仍然可以通過這種巧妙設(shè)計(jì)的方案從上下文中的所有節(jié)點(diǎn)獲取信息。 在具有擴(kuò)張卷積和三個(gè)隱藏層的情況下,先前的實(shí)現(xiàn)覆蓋了 16 個(gè)輸入節(jié)點(diǎn),而先前沒有擴(kuò)張卷積的實(shí)現(xiàn)僅覆蓋了五個(gè)輸入節(jié)點(diǎn)。

dilatedcausalconv = torch.nn.Conv1d(res_channels,res_channels,kernel_size=2,dilation=dilation,padding=0,bias=False)

可以用“圖 6.10”中給出的二維圖片直觀地解釋膨脹卷積的實(shí)現(xiàn)。 所有這三個(gè)示例均使用大小為 3x3 的核,其中最左邊的塊顯示的是正常卷積或膨脹卷積,其膨脹因子等于零。 中間塊具有相同的核,但膨脹因子為 2,最后一個(gè)塊的膨脹因子為 4。 擴(kuò)張卷積的實(shí)現(xiàn)技巧是在核之間添加零以擴(kuò)展核的大小,如圖“圖 6.11”所示:

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-kVQ6pAYr-1681786272901)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_06_12.jpg)]

圖 6.11:帶有核擴(kuò)展的膨脹卷積

PyTorch 通過使用戶能夠?qū)⑴蛎涀鳛殛P(guān)鍵字參數(shù)傳遞,從而使進(jìn)行膨脹卷積變得容易,如先前代碼塊中的DilatedCausalConv1d節(jié)點(diǎn)中所給出的。 如前所述,每一層具有不同的擴(kuò)張因子,并且可以為每一層的擴(kuò)張卷積節(jié)點(diǎn)創(chuàng)建傳遞該因子。 由于跨步為 1,所以填充保持為 0,目的不是上采樣或下采樣。 init_weights_for_test是通過將權(quán)重矩陣填充 1 來進(jìn)行測(cè)試的便捷函數(shù)。

PyTorch 提供的靈活性使用戶可以在線調(diào)整參數(shù),這對(duì)于調(diào)試網(wǎng)絡(luò)更加有用。 forward傳遞僅調(diào)用 PyTorch conv1d對(duì)象,該對(duì)象是可調(diào)用的并保存在self.conv變量中:

causalconv = torch.nn.Conv1d(in_channels,res_channels,kernel_size=2,padding=1,bias=False)

WaveNet 的完整架構(gòu)建立在膨脹卷積網(wǎng)絡(luò)和卷積后門控激活的基礎(chǔ)之上。 WaveNet 中的數(shù)據(jù)流從因果卷積運(yùn)算開始,這是一種正常的一維卷積,然后傳遞到膨脹的卷積節(jié)點(diǎn)。 WaveNet 圖片中的每個(gè)白色圓圈(“圖 6.9”)是一個(gè)擴(kuò)展的卷積節(jié)點(diǎn)。 然后,將正常卷積的數(shù)據(jù)點(diǎn)傳遞到膨脹的卷積節(jié)點(diǎn),然后將其獨(dú)立地通過 Sigmoid 門和 tanh 激活。 然后,兩個(gè)運(yùn)算的輸出通過逐點(diǎn)乘法運(yùn)算符和1x1卷積進(jìn)行。 WaveNet 使用剩余連接和跳躍連接來平滑數(shù)據(jù)流。 與主流程并行運(yùn)行的剩余線程通過加法運(yùn)算與1x1卷積的輸出合并。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-jdisx9PD-1681786272902)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_06_13.jpg)]

圖 6.12:WaveNet 架構(gòu)

來源: 《WaveNet:原始音頻的生成模型》,Aaron van den Oord 等

“圖 6.12”中提供的 WaveNet 的結(jié)構(gòu)圖顯示了所有這些小組件以及它們?nèi)绾芜B接在一起。 跳躍連接之后的部分在程序中稱為密集層,盡管它不是上一章介紹的密集層。 通常,密集層表示全連接層,以將非線性引入網(wǎng)絡(luò)并獲得所有數(shù)據(jù)的概覽。 但是 WaveNet 的作者發(fā)現(xiàn),正常的密集層可以由一串 ReLU 代替,并且1x1卷積可以通過最后的 softmax 層實(shí)現(xiàn)更高的精度,該層可以展開為 256 個(gè)單元(巨大扇出的 8 位μ律量化) 音頻)。

class WaveNetModule(torch.nn.Module):def __init__(self, layer_size, stack_size,in_channels, res_channels):super().__init__()self.causal = CausalConv1d(in_channels, res_channels)self.res_stack = ResidualStack(layer_size,stack_size,res_channels,in_channels)self.convdensnet = ConvDensNet(in_channels)def forward(self, x):output = self.causal(output)skip_connections = self.res_stack(output, output_size)output = torch.sum(skip_connections, dim=0)output = self.convdensnet(output)return output.contiguous()

前面的代碼塊中給出的程序是主要的父 WaveNet 模塊,該模塊使用所有子組件來創(chuàng)建圖。 init定義了三個(gè)主要成分,其中是第一個(gè)普通卷積,然后是res_stack(它是由所有膨脹卷積和 Sigmoid 正切門組成的殘差連接塊)。 然后,最后的convdensnet1x1卷積的頂部進(jìn)行。 forward引入一個(gè)求和節(jié)點(diǎn),依次執(zhí)行這些模塊。 然后,將convdensnet創(chuàng)建的輸出通過contiguous()移動(dòng)到存儲(chǔ)器的單個(gè)塊。 這是其余網(wǎng)絡(luò)所必需的。

ResidualStack是需要更多說明的模塊,它是 WaveNet 架構(gòu)的核心。 ResidualStackResidualBlock的層的棧。 WaveNet 圖片中的每個(gè)小圓圈都是一個(gè)殘差塊。 在正常卷積之后,數(shù)據(jù)到達(dá)ResidualBlock,如前所述。 ResidualBlock從膨脹的卷積開始,并且期望得到膨脹。 因此,ResidualBlock決定了架構(gòu)中每個(gè)小圓節(jié)點(diǎn)的膨脹因子。 如前所述,膨脹卷積的輸出然后通過類似于我們?cè)?PixelCNN 中看到的門的門。

在那之后,它必須經(jīng)歷兩個(gè)單獨(dú)的卷積以進(jìn)行跳躍連接和殘差連接。 盡管作者并未將其解釋為兩個(gè)單獨(dú)的卷積,但使用兩個(gè)單獨(dú)的卷積更容易理解。

class ResidualBlock(torch.nn.Module):def __init__(self, res_channels, skip_channels, dilation=1):
super().__init__()self.dilatedcausalconv = torch.nn.Conv1d(res_channels, res_channels, kernel_size=2,
dilation=dilation,padding=0, bias=False)
self.conv_res = torch.nn.Conv1d(res_channels, res_channels, 1)
self.conv_skip = torch.nn.Conv1d(res_channels, skip_channels, 1)
self.gate_tanh = torch.nn.Tanh()
self.gate_sigmoid = torch.nn.Sigmoid()
def forward(self, x, skip_size):x = self.dilatedcausalconv(x)# PixelCNN Gate# ---------------------------gated_tanh = self.gate_tanh(x)gated_sigmoid = self.gate_sigmoid(x)gated = gated_tanh * gated_sigmoid# ---------------------------x = self.conv_res(gated)x += x[:, :, -x.size(2):]skip = self.conv_skip(gated)[:, :, -skip_size:]return x, skip

ResidualStack使用層數(shù)和棧數(shù)來創(chuàng)建膨脹因子。 通常,每個(gè)層具有2 ^ l作為膨脹因子,其中l是層數(shù)。 從12 ^ l開始,每個(gè)棧都具有相同數(shù)量的層和相同樣式的膨脹因子列表。

方法stack_res_block使用我們前面介紹的ResidualBlock為每個(gè)棧和每個(gè)層中的每個(gè)節(jié)點(diǎn)創(chuàng)建一個(gè)殘差塊。 該程序引入了一個(gè)新的 PyTorch API,稱為torch.nn.DataParallel。 如果有多個(gè) GPU,則DataParallel API 會(huì)引入??并行性。 將模型制作為數(shù)據(jù)并行模型可以使 PyTorch 知道用戶可以使用更多 GPU,并且 PyTorch 從那里獲取了它,而沒有給用戶帶來任何障礙。 PyTorch 將數(shù)據(jù)劃分為盡可能多的 GPU,并在每個(gè) GPU 中并行執(zhí)行模型。

它還負(fù)責(zé)從每個(gè) GPU 收集回結(jié)果,并將其合并在一起,然后再繼續(xù)進(jìn)行。

class ResidualStack(torch.nn.Module):def __init__(self, layer_size, stack_size, res_channels,
skip_channels):super().__init__()self.res_blocks = torch.nn.ModuleList()for s in range(stack_size):for l in range(layer_size):dilation = 2 ** lblock = ResidualBlock(res_channels, skip_channels,dilation)self.res_blocks.append(block)def forward(self, x, skip_size):skip_connections = []for res_block in self.res_blocks:x, skip = res_block(x, skip_size)skip_connections.append(skip)
return torch.stack(skip_connections)

GAN

在許多深度學(xué)習(xí)研究人員看來,GAN 是過去十年的主要發(fā)明之一。 它們?cè)诒举|(zhì)上不同于其他生成網(wǎng)絡(luò),尤其是在訓(xùn)練方式上。 Ian Goodfellow 撰寫的第一篇有關(guān)對(duì)抗網(wǎng)絡(luò)生成數(shù)據(jù)的論文于 2014 年發(fā)表。 GAN 被認(rèn)為是一種無監(jiān)督學(xué)習(xí)算法,其中有監(jiān)督學(xué)習(xí)算法學(xué)習(xí)使用標(biāo)記數(shù)據(jù)y來推理函數(shù)y' = f(x)

這種監(jiān)督學(xué)習(xí)算法本質(zhì)上是判別式的,這意味著它學(xué)會(huì)對(duì)條件概率分布函數(shù)進(jìn)行建模,在此條件函數(shù)中,它說明了某事物的概率被賦予了另一事物的狀態(tài)。 例如,如果購(gòu)買房屋的價(jià)格為 100,000 美元,那么房屋位置的概率是多少? GAN 從隨機(jī)分布生成輸出,因此隨機(jī)輸入的變化使輸出不同。

GAN 從隨機(jī)分布中獲取樣本,然后由網(wǎng)絡(luò)將其轉(zhuǎn)換為輸出。 GAN 在學(xué)習(xí)輸入分布的模式時(shí)不受監(jiān)督,并且與其他生成網(wǎng)絡(luò)不同,GAN 不會(huì)嘗試明確地學(xué)習(xí)密度分布。 相反,他們使用博弈論方法來找到兩個(gè)參與者之間的納什均衡。 GAN 實(shí)現(xiàn)將始終擁有一個(gè)生成網(wǎng)絡(luò)和一個(gè)對(duì)抗網(wǎng)絡(luò),這被視為兩個(gè)試圖擊敗的參與者。 GAN 的核心思想在于從統(tǒng)一或高斯等數(shù)據(jù)分布中采樣,然后讓網(wǎng)絡(luò)將采樣轉(zhuǎn)換為真正的數(shù)據(jù)分布樣。 我們將實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 GAN,以了解 GAN 的工作原理,然后轉(zhuǎn)向名為 CycleGAN 的高級(jí) GAN 實(shí)現(xiàn)。

簡(jiǎn)單的 GAN

了解 GAN 的直觀方法是從博弈論的角度了解它。 簡(jiǎn)而言之,GAN 由兩個(gè)參與者組成,一個(gè)生成器和一個(gè)判別器,每一個(gè)都試圖擊敗對(duì)方。 生成器從分布中獲取一些隨機(jī)噪聲,并嘗試從中生成一些輸出分布。 生成器總是嘗試創(chuàng)建與真實(shí)分布沒有區(qū)別的分布; 也就是說,偽造的輸出應(yīng)該看起來像是真實(shí)的圖像。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-PtmfRrnW-1681786272902)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_06_14.jpg)]

Figure 6.13: GAN architecture

但是,如果沒有明確的訓(xùn)練或標(biāo)簽,生成器將無法確定真實(shí)圖像的外觀,并且其唯一的來源就是隨機(jī)浮點(diǎn)數(shù)的張量。 然后,GAN 將第二個(gè)玩家介紹給游戲,這是一個(gè)判別器。 判別器僅負(fù)責(zé)通知生成器生成的輸出看起來不像真實(shí)圖像,以便生成器更改其生成圖像的方式以使判別器確信它是真實(shí)圖像。 但是判別器總是可以告訴生成器圖像不是真實(shí)的,因?yàn)榕袆e器知道圖像是從生成器生成的。 這就是事情變得有趣的地方。 GAN 將真實(shí),真實(shí)的圖像引入游戲中,并將判別器與生成器隔離。 現(xiàn)在,判別器從一組真實(shí)圖像中獲取一個(gè)圖像,并從生成器中獲取一個(gè)偽圖像,并且判別器必須找出每個(gè)圖像的來源。 最初,判別器什么都不知道,只能預(yù)測(cè)隨機(jī)結(jié)果。

class DiscriminatorNet(torch.nn.Module):"""A three hidden-layer discriminative neural network"""def __init__(self):super().__init__()n_features = 784n_out = 1self.hidden0 = nn.Sequential(nn.Linear(n_features, 1024),nn.LeakyReLU(0.2),nn.Dropout(0.3))self.hidden1 = nn.Sequential(nn.Linear(1024, 512),nn.LeakyReLU(0.2),nn.Dropout(0.3))self.hidden2 = nn.Sequential(nn.Linear(512, 256),nn.LeakyReLU(0.2),nn.Dropout(0.3))self.out = nn.Sequential(torch.nn.Linear(256, n_out),torch.nn.Sigmoid())def forward(self, x):x = self.hidden0(x)x = self.hidden1(x)x = self.hidden2(x)x = self.out(x)return x

但是,可以將辨別器的任務(wù)修改為分類任務(wù)。 判別器可以將輸入圖像分類為原始生成的,這是二分類。 同樣,我們訓(xùn)練判別器網(wǎng)絡(luò)正確地對(duì)圖像進(jìn)行分類,最終,通過反向傳播,判別器學(xué)會(huì)了區(qū)分真實(shí)圖像和生成的圖像。

該會(huì)話中使用的示例將生成類似 MNIST 的輸出。 前面的代碼顯示了 MNIST 上的鑒別播放器,該播放器總是從真實(shí)源數(shù)據(jù)集或生成器中獲取圖像。 GAN 眾所周知非常不穩(wěn)定,因此使用LeakyReLU是研究人員發(fā)現(xiàn)比常規(guī)ReLU更好工作的黑客之一。 現(xiàn)在,LeakyReLU通過它泄漏了負(fù)極,而不是將所有內(nèi)容限制為零到零。 與正常的ReLU相比,這有助于使梯度更好地流過網(wǎng)絡(luò),對(duì)于小于零的值,梯度為零。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-oGC4ulzO-1681786272902)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_06_15.jpg)]

圖 6.14:ReLU 和泄漏的 ReLU

我們開發(fā)的的簡(jiǎn)單判別器具有三個(gè)連續(xù)層。 每個(gè)層都有一個(gè)線性層,泄漏的 ReLU 和一個(gè)夾在中間的漏失層,然后是一個(gè)線性層和一個(gè) Sigmoid 門。 通常,概率預(yù)測(cè)網(wǎng)絡(luò)使用 softmax 層作為最后一層; 像這樣的簡(jiǎn)單 GAN 最適合 Sigmoid 曲面。

def train_discriminator(optimizer, real_data, fake_data):optimizer.zero_grad()# 1.1 Train on Real Dataprediction_real = discriminator(real_data)# Calculate error and backpropagateerror_real = loss(prediction_real,real_data_target(real_data.size(0)))error_real.backward()# 1.2 Train on Fake Dataprediction_fake = discriminator(fake_data)# Calculate error and backpropagateerror_fake = loss(prediction_fake,fake_data_target(real_data.size(0)))error_fake.backward()# 1.3 Update weights with gradientsoptimizer.step()# Return errorreturn error_real + error_fake, prediction_real, prediction_fake

在前面的代碼塊中定義的函數(shù)train_generator接受optimizer對(duì)象,偽數(shù)據(jù)和實(shí)數(shù)據(jù),然后將它們傳遞給判別器。 函數(shù)fake_data_target(在下面的代碼塊中提供)創(chuàng)建一個(gè)零張量,該張量的大小與預(yù)測(cè)大小相同,其中預(yù)測(cè)是從判別器返回的值。 判別器的訓(xùn)練策略是使任何真實(shí)數(shù)據(jù)被歸類為真實(shí)分布的概率最大化,并使任何數(shù)據(jù)點(diǎn)被歸類為真實(shí)分布的概率最小化。 在實(shí)踐中,使用了來自判別器或生成器的結(jié)果的日志,因?yàn)檫@會(huì)嚴(yán)重?fù)p害網(wǎng)絡(luò)的分類錯(cuò)誤。 然后在應(yīng)用optimizer.step函數(shù)之前將誤差反向傳播,該函數(shù)將通過學(xué)習(xí)率以梯度更新權(quán)重。

接下來給出用于獲得真實(shí)數(shù)據(jù)目標(biāo)和偽數(shù)據(jù)目標(biāo)的函數(shù),這與前面討論的最小化或最大化概率的概念基本一致。 實(shí)際數(shù)據(jù)生成器返回一個(gè)張量為 1s 的張量,該張量是我們作為輸入傳遞的形狀。 在訓(xùn)練生成器時(shí),我們正在嘗試通過生成圖像來最大程度地提高其概率,該圖像看起來應(yīng)該是從真實(shí)數(shù)據(jù)分布中獲取的。 這意味著判別器應(yīng)將 1 預(yù)測(cè)為圖像來自真實(shí)分布的置信度分?jǐn)?shù)。

def real_data_target(size):'''Tensor containing ones, with shape = size'''return torch.ones(size, 1).to(device)def fake_data_target(size):'''Tensor containing zeros, with shape = size'''return torch.zeros(size, 1).to(device)

因此,判別器的實(shí)現(xiàn)很容易實(shí)現(xiàn),因?yàn)樗举|(zhì)上只是分類任務(wù)。 生成器網(wǎng)絡(luò)將涉及所有卷積上采樣/下采樣,因此有點(diǎn)復(fù)雜。 但是對(duì)于當(dāng)前示例,由于我們希望它盡可能簡(jiǎn)單,因此我們將在全連接網(wǎng)絡(luò)而不是卷積網(wǎng)絡(luò)上進(jìn)行工作。

def noise(size):n = torch.randn(size, 100)return n.to(device)

可以定義一個(gè)噪聲生成函數(shù),該函數(shù)可以生成隨機(jī)樣本(事實(shí)證明,這種采樣在高斯分布而非隨機(jī)分布下是有效的,但為簡(jiǎn)單起見,此處使用隨機(jī)分布)。 如果 CUDA 可用,我們會(huì)將隨機(jī)產(chǎn)生的噪聲從 CPU 內(nèi)存?zhèn)鬏數(shù)?GPU 內(nèi)存,并返回張量,其輸出大小為100。 因此,生成網(wǎng)絡(luò)期望輸入噪聲的特征數(shù)量為 100,而我們知道 MNIST 數(shù)據(jù)集中有 784 個(gè)數(shù)據(jù)點(diǎn)(28x28)。

對(duì)于生成器,我們具有與判別器類似的結(jié)構(gòu),但是在最后一層具有 tanh 層,而不是 Sigmoid。 進(jìn)行此更改是為了與我們對(duì) MNIST 數(shù)據(jù)進(jìn)行的歸一化同步,以將其轉(zhuǎn)換為 -1 到 1 的范圍,以便判別器始終獲得具有相同范圍內(nèi)數(shù)據(jù)點(diǎn)的數(shù)據(jù)集。 生成器中的三層中的每一層都將輸入噪聲上采樣到 784 的輸出大小,就像我們?cè)谂袆e器中下采樣以進(jìn)行分類一樣。

class GeneratorNet(torch.nn.Module):"""A three hidden-layer generative neural network"""def __init__(self):super().__init__()n_features = 100n_out = 784self.hidden0 = nn.Sequential(nn.Linear(n_features, 256),nn.LeakyReLU(0.2))self.hidden1 = nn.Sequential(nn.Linear(256, 512),nn.LeakyReLU(0.2))self.hidden2 = nn.Sequential(nn.Linear(512, 1024),nn.LeakyReLU(0.2))self.out = nn.Sequential(nn.Linear(1024, n_out),nn.Tanh())def forward(self, x):x = self.hidden0(x)x = self.hidden1(x)x = self.hidden2(x)x = self.out(x)return x

生成器訓(xùn)練器函數(shù)比判別器訓(xùn)練器函數(shù)簡(jiǎn)單得多,因?yàn)樗恍枰獜膬蓚€(gè)來源獲取輸入,也不必針對(duì)不同的目的進(jìn)行訓(xùn)練,而判別器則必須最大化將真實(shí)圖像分類為真實(shí)圖像的可能性。 圖像,并最小化將噪聲圖像分類為真實(shí)圖像的可能性。 此函數(shù)僅接受偽圖像數(shù)據(jù)和優(yōu)化器,其中偽圖像是生成器生成的圖像。 生成器訓(xùn)練器函數(shù)代碼可以在 GitHub 存儲(chǔ)庫(kù)中找到。

我們分別創(chuàng)建判別器和生成器網(wǎng)絡(luò)的實(shí)例。 到目前為止,我們所有的網(wǎng)絡(luò)實(shí)現(xiàn)都具有單個(gè)模型或單個(gè)神經(jīng)網(wǎng)絡(luò),但第一次,我們有兩個(gè)單獨(dú)的網(wǎng)絡(luò)在同一個(gè)數(shù)據(jù)集上工作,并具有不同的優(yōu)化目標(biāo)。 對(duì)于兩個(gè)單獨(dú)的網(wǎng)絡(luò),我們還需要?jiǎng)?chuàng)建兩個(gè)單獨(dú)的優(yōu)化器。 從歷史上看,Adam優(yōu)化器最適合學(xué)習(xí)速度非常慢的 GAN。

兩個(gè)網(wǎng)絡(luò)都使用判別器的輸出進(jìn)行訓(xùn)練。 唯一的區(qū)別是,在訓(xùn)練判別器時(shí),我們嘗試使偽造圖像被分類為真實(shí)圖像的可能性最小,而在訓(xùn)練生成器時(shí),我們?cè)噲D使偽造圖像被分類為真實(shí)圖像的可能性最大。 由于它始終是試圖預(yù)測(cè) 0 和 1 的二分類器,因此我們使用torch.nn中的BCELoss來嘗試預(yù)測(cè) 0 或 1:

discriminator = DiscriminatorNet().to(device)
generator = GeneratorNet().to(device)
d_optimizer = optim.Adam(discriminator.parameters(), lr=0.0002)
g_optimizer = optim.Adam(generator.parameters(), lr=0.0002)
loss = nn.BCELoss()

接下來是簡(jiǎn)單 GAN 在不同周期生成的輸出,該圖顯示了網(wǎng)絡(luò)如何學(xué)會(huì)將輸入隨機(jī)分布映射到輸出真實(shí)分布。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-8T5egWlM-1681786272902)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_06_16.jpg)]

圖 6.15:100 個(gè)周期后的輸出

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-615kjUa8-1681786272903)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_06_17.jpg)]

圖 6.16:200 個(gè)周期后的輸出

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-Y3bAuR76-1681786272903)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_06_18.jpg)]

圖 6.17:300 個(gè)周期后的輸出

CycleGAN

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-rTX5eXSp-1681786272903)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_06_19.jpg)]

圖 6.18:實(shí)踐中的 CycleGAN

資料來源:《使用周期一致的對(duì)抗性網(wǎng)絡(luò)的不成對(duì)圖像翻譯》,朱俊彥等

CycleGAN 是 GAN 類型的智能變體之一。 在同一架構(gòu)中,兩個(gè) GAN 之間巧妙設(shè)計(jì)的循環(huán)流可教導(dǎo)兩個(gè)不同分布之間的映射。 先前的方法需要來自不同分布的成對(duì)圖像,以便網(wǎng)絡(luò)學(xué)習(xí)映射。 對(duì)于示例,如果目標(biāo)是建立一個(gè)可以將黑白圖像轉(zhuǎn)換為彩色圖像的網(wǎng)絡(luò),則數(shù)據(jù)集在訓(xùn)練集中需要將同一圖像的黑白和彩色版本作為一對(duì)。 盡管很難,但在一定程度上這是可能的。 但是,如果要使冬天拍攝的圖像看起來像夏天拍攝的圖像,則訓(xùn)練集中的這對(duì)圖像必須是在冬天和夏天拍攝的具有相同對(duì)象和相同幀的完全相同的圖像。 這是完全不可能的,而那正是 CycleGAN 可以提供幫助的地方。

CycleGAN 學(xué)習(xí)每種分布的模式,并嘗試將圖像從一種分布映射到另一種分布。 “圖 6.19”中給出了 CycleGAN 的簡(jiǎn)單架構(gòu)圖。 上面的圖顯示了如何訓(xùn)練一個(gè) GAN,下面的圖顯示了如何使用正在工作的 CycleGAN 典型示例:馬和斑馬來訓(xùn)練另一個(gè)。

在 CycleGAN 中,我們不是從分布中隨機(jī)采樣的數(shù)據(jù)開始,而是使用來自集合 A(在本例中為一組馬)的真實(shí)圖像。 委托生成器 A 到 B(我們稱為 A2B)將同一匹馬轉(zhuǎn)換為斑馬,但沒有將成對(duì)的馬匹轉(zhuǎn)換為斑馬的配對(duì)圖像。 訓(xùn)練開始時(shí),A2B 會(huì)生成無意義的圖像。 判別器 B 從 A2B 生成的圖像或從集合 B(斑馬的集合)中獲取真實(shí)圖像。 與其他任何判別器一樣,它負(fù)責(zé)預(yù)測(cè)圖像是生成的還是真實(shí)的。 這個(gè)過程是正常的 GAN,它永遠(yuǎn)不能保證同一匹馬轉(zhuǎn)換為斑馬。 而是將馬的圖像轉(zhuǎn)換為斑馬的任何圖像,因?yàn)閾p失只是為了確保圖像看起來像集合 B 的分布; 它不需要與集合 A 相關(guān)。為了強(qiáng)加這種相關(guān)性,CycleGAN 引入了循環(huán)。

然后,從 A2B 生成的圖像會(huì)通過另一個(gè)生成器 B2A,以獲得Cyclic_A。 施加到Cyclic_A的損失是 CycleGAN 的關(guān)鍵部分。 在這里,我們嘗試減小Cyclic_AInput_A之間的距離。 第二個(gè)損失背后的想法是,第二個(gè)生成器必須能夠生成馬,因?yàn)槲覀冮_始時(shí)的分布是馬。 如果 A2B 知道如何將馬匹映射到斑馬而不改變圖片中的任何其他內(nèi)容,并且如果 B2A 知道如何將斑馬線映射到匹馬而不改變圖片中的其他任何東西,那么我們對(duì)損失所做的假設(shè)應(yīng)該是正確的。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-SkKhP9fu-1681786272903)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_06_20.jpg)]

圖 6.19:CycleGAN 架構(gòu)

當(dāng)判別器 A 獲得馬的真實(shí)圖像時(shí),判別器 B 從 A2B 獲得斑馬的生成圖像,當(dāng)判別器 B 獲得斑馬的真實(shí)圖像時(shí),判別器 A 從 B2A 獲得馬的生成圖像。 要注意的一點(diǎn)是,判別器 A 總是能夠預(yù)測(cè)圖像是否來自馬具,而判別器 B 總是能夠預(yù)測(cè)圖像是否來自斑馬具。 同樣,A2B 始終負(fù)責(zé)將馬集合映射到斑馬分布,而 B2A 始終負(fù)責(zé)將斑馬集合映射到馬分布。

生成器和判別器的這種周期性訓(xùn)練可確保網(wǎng)絡(luò)學(xué)會(huì)使用模式變化來映射圖像,但圖像的所有其他特征均保持不變。

Generator((model): Sequential((0): ReflectionPad2d((3, 3, 3, 3))(1): Conv2d(3, 64, kernel_size=(7, 7), stride=(1, 1))(2): InstanceNorm2d(64, eps=1e-05, momentum=0.1, affine=False,track_running_stats=False)(3): ReLU(inplace)(4): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2),padding=(1, 1))(5): InstanceNorm2d(128, eps=1e-05, momentum=0.1,affine=False, track_running_stats=False)(6): ReLU(inplace)(7): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2),padding=(1, 1))(8): InstanceNorm2d(256, eps=1e-05, momentum=0.1,affine=False, track_running_stats=False)(9): ReLU(inplace)(10): ResidualBlock()(11): ResidualBlock()(12): ResidualBlock()(13): ResidualBlock()(14): ResidualBlock()(15): ResidualBlock()(16): ResidualBlock()(17): ResidualBlock()(18): ResidualBlock()(19): ConvTranspose2d(256, 128, kernel_size=(3, 3), stride=(2,2), padding=(1, 1), output_padding=(1, 1))(20): InstanceNorm2d(128, eps=1e-05, momentum=0.1,affine=False, track_running_stats=False)(21): ReLU(inplace)(22): ConvTranspose2d(128, 64, kernel_size=(3, 3), stride=(2,2), padding=(1, 1), output_padding=(1, 1))(23): InstanceNorm2d(64, eps=1e-05, momentum=0.1,affine=False, track_running_stats=False)(24): ReLU(inplace)(25): ReflectionPad2d((3, 3, 3, 3))(26): Conv2d(64, 3, kernel_size=(7, 7), stride=(1, 1))(27): Tanh())
)

PyTorch 為用戶提供了進(jìn)入網(wǎng)絡(luò)并進(jìn)行操作的完全靈活性。 其中一部分是將模型打印到終端上,以顯示其中包含所有模塊的地形排序圖。

之前我們?cè)?CycleGAN 中看到了生成器的圖。 與我們探討的第一個(gè)簡(jiǎn)單 GAN 不同,A2B 和 B2A 都具有相同的內(nèi)部結(jié)構(gòu),內(nèi)部具有卷積。 整個(gè)生成器都包裝在以ReflectionPad2D開頭的單個(gè)序列模塊中。

反射填充涉及填充輸入的邊界,跳過批量尺寸和通道尺寸。 填充之后是典型的卷積模塊布置,即二維卷積。

實(shí)例歸一化分別對(duì)每個(gè)輸出批量進(jìn)行歸一化,而不是像“批量歸一化”中那樣對(duì)整個(gè)集合進(jìn)行歸一化。 二維實(shí)例歸一化確實(shí)在 4D 輸入上實(shí)例化歸一化,且批量尺寸和通道尺寸為第一維和第二維。 PyTorch 通過傳遞affine=True允許實(shí)例規(guī)范化層可訓(xùn)練。 參數(shù)track_running_stats決定是否存儲(chǔ)訓(xùn)練循環(huán)的運(yùn)行平均值和方差,以用于評(píng)估模式(例如歸一化)。 默認(rèn)情況下,它設(shè)置為False; 也就是說,它在訓(xùn)練和評(píng)估模式下都使用從輸入中收集的統(tǒng)計(jì)信息。

下圖給出了批量規(guī)范化和實(shí)例規(guī)范化的直觀比較。 在圖像中,數(shù)據(jù)表示為三維張量,其中C是通道,N是批量,D是其他維,為簡(jiǎn)單起見,在一個(gè)維中表示。 如圖中所示,批量歸一化對(duì)整個(gè)批量中的數(shù)據(jù)進(jìn)行歸一化,而實(shí)例歸一化則在兩個(gè)維度上對(duì)一個(gè)數(shù)據(jù)實(shí)例進(jìn)行歸一化,從而使批量之間的差異保持完整。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-H9u3E3so-1681786272903)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_06_21.jpg)]

圖 6.20:

Source: Group Normalization, Yuxin Wu and Kaiming He

原始 CycleGAN 的生成器在三個(gè)卷積塊之后使用九個(gè)殘差塊,其中每個(gè)卷積塊由卷積層,歸一化層和激活層組成。 殘差塊之后是幾個(gè)轉(zhuǎn)置卷積,然后是最后一層具有 tanh 函數(shù)的一個(gè)卷積層。 如簡(jiǎn)單 GAN 中所述,tanh 輸出的范圍是 -1 至 1,這是所有圖像的歸一化值范圍。

殘余塊的內(nèi)部是按順序排列的另一組填充,卷積,歸一化和激活單元。 但是forward方法與residueNet中的求和操作建立了殘余連接。 在以下示例中,所有內(nèi)部塊的順序包裝都保存到變量conv_block中。 然后,將經(jīng)過此塊的數(shù)據(jù)與加法運(yùn)算符一起輸入到網(wǎng)絡(luò)x。 此殘留連接通過允許信息更容易地雙向流動(dòng)來幫助網(wǎng)絡(luò)變得穩(wěn)定:

class ResidualBlock(nn.Module):def __init__(self, in_features):super().__init__()conv_block = [nn.ReflectionPad2d(1),nn.Conv2d(in_features, in_features, 3),nn.InstanceNorm2d(in_features),nn.ReLU(inplace=True),nn.ReflectionPad2d(1),nn.Conv2d(in_features, in_features, 3),nn.InstanceNorm2d(in_features)]self.conv_block = nn.Sequential(*conv_block)def forward(self, x):return x + self.conv_block(x)

總結(jié)

在本章中,我們學(xué)習(xí)了一系列全新的神經(jīng)網(wǎng)絡(luò),這些神經(jīng)網(wǎng)絡(luò)使人工智能世界發(fā)生了翻天覆地的變化。 生成網(wǎng)絡(luò)對(duì)我們始終很重要,但是直到最近我們才能達(dá)到人類無法比擬的準(zhǔn)確率。 盡管有一些成功的生成網(wǎng)絡(luò)架構(gòu),但在本章中我們僅討論了兩個(gè)最受歡迎的網(wǎng)絡(luò)。

生成網(wǎng)絡(luò)使用 CNN 或 RNN 之類的基本架構(gòu)作為整個(gè)網(wǎng)絡(luò)的構(gòu)建塊,但是使用一些不錯(cuò)的技術(shù)來確保網(wǎng)絡(luò)正在學(xué)習(xí)生成一些輸出。 到目前為止,生成網(wǎng)絡(luò)已在藝術(shù)中得到廣泛使用,并且由于模型必須學(xué)習(xí)數(shù)據(jù)分布以生成輸出,因此我們可以輕松地預(yù)測(cè)生成網(wǎng)絡(luò)將成為許多復(fù)雜網(wǎng)絡(luò)的基礎(chǔ)。 生成網(wǎng)絡(luò)最有前途的用途可能不是生成,而是通過生成學(xué)習(xí)數(shù)據(jù)分發(fā)并將該信息用于其他目的。

在下一章中,我們將研究最受關(guān)注的網(wǎng)絡(luò):強(qiáng)化學(xué)習(xí)算法。

參考

  1. 《使用 PixelCNN 解碼器的條件圖像生成》,Oord,A?ronvan den,Nal Kalchbrenner,Oriol Vinyals,Lasse Espeholt,Alex Graves 和 Koray Kavukcuoglu,NIPS,2016 年
  2. 《并行 WaveNet:快速高保真語音合成》,Oord,A?ronvan den,Yazhe Li,Igor Babuschkin,Karen Simonyan,Oriol Vinyals,Koray Kavukcuoglu,George van den Driessche,Edward Lockhart,Luis C. Cobo, Florian Stimberg,Norman Casagrande,Dominik Grewe,Seb Noury,Sander Dieleman,Erich Elsen,Nal Kalchbrenner,Heiga Zen,Alex Graves,Helen King,Tom Walters,Dan Belov 和 Demis Hassabis,ICML,2018
  3. 戈?duì)栙e的 WaveNet 存儲(chǔ)庫(kù)

七、強(qiáng)化學(xué)習(xí)

讓我們談?wù)剬W(xué)習(xí)的本質(zhì)。 我們不是天生就知道這個(gè)世界。 通過與世界互動(dòng),我們了解了行動(dòng)的效果。 一旦我們了解了世界的運(yùn)轉(zhuǎn)方式,我們就可以利用這些知識(shí)來做出可以將我們引向特定目標(biāo)的決策。

在本章中,我們將使用一種稱為強(qiáng)化學(xué)習(xí)的方法來制定這種計(jì)算學(xué)習(xí)方法。 它與本書中介紹的其他類型的深度學(xué)習(xí)算法非常不同,并且本身就是一個(gè)廣闊的領(lǐng)域。

強(qiáng)化學(xué)習(xí)的應(yīng)用范圍從在數(shù)字環(huán)境中玩游戲到在現(xiàn)實(shí)環(huán)境中控制機(jī)器人的動(dòng)作。 它也恰好是您用來訓(xùn)練狗和其他動(dòng)物的技術(shù)。 如今,強(qiáng)化學(xué)習(xí)已被用于駕駛自動(dòng)駕駛汽車,這是一個(gè)非常受歡迎的領(lǐng)域。

當(dāng)計(jì)算機(jī)(AlphaGo)擊敗世界圍棋冠軍 Lee Sedol [1]時(shí),發(fā)生了最近的重大突破之一。 這是一個(gè)突破,因?yàn)閲逡恢币詠肀徽J(rèn)為是讓計(jì)算機(jī)掌握很長(zhǎng)時(shí)間的游戲圣杯。 這是因?yàn)閾?jù)說圍棋游戲中的配置數(shù)量大于我們宇宙中的原子數(shù)量。

在世界冠軍輸給 AlphaGo 之后,甚至有人說他已經(jīng)從計(jì)算機(jī)中學(xué)到了一些東西。 這聽起來很瘋狂,但這是事實(shí)。 聽起來更瘋狂的是,算法的輸入只不過是棋盤游戲當(dāng)前狀態(tài)的圖像,而 AlphaGo 則一遍又一遍地對(duì)自己進(jìn)行訓(xùn)練。 但在此之前,它從觀看世界冠軍的視頻中學(xué)習(xí)了數(shù)小時(shí)。

如今,強(qiáng)化學(xué)習(xí)已被用于使機(jī)器人學(xué)習(xí)如何走路。 在這種情況下,輸入將是機(jī)器人可以施加到其關(guān)節(jié)的力以及機(jī)器人將要行走的地面狀態(tài)。 強(qiáng)化學(xué)習(xí)也被用于預(yù)測(cè)股價(jià),并且在該領(lǐng)域引起了很多關(guān)注。

這些現(xiàn)實(shí)問題似乎非常復(fù)雜。 我們將需要對(duì)所有這些事情進(jìn)行數(shù)學(xué)公式化,以便計(jì)算機(jī)可以解決它們。 為此,我們需要簡(jiǎn)化環(huán)境和決策過程以實(shí)現(xiàn)特定目標(biāo)。

在強(qiáng)化學(xué)習(xí)的整個(gè)范式中,我們僅關(guān)注從交互中學(xué)習(xí),而學(xué)習(xí)器或決策者則被視為智能體。 在自動(dòng)駕駛汽車中,智能體是汽車,而在乒乓球中,智能體是球拍。 當(dāng)智能體最初進(jìn)入世界時(shí),它將對(duì)世界一無所知。 智能體將必須觀察其環(huán)境并根據(jù)其做出決策或采取行動(dòng)。 它從環(huán)境中返回的響應(yīng)稱為獎(jiǎng)勵(lì),可以是肯定的也可以是否定的。 最初,智能體將隨機(jī)采取行動(dòng),直到獲得正面獎(jiǎng)勵(lì)為止,并告訴他們這些決定可能對(duì)其有利。

這似乎很簡(jiǎn)單,因?yàn)橹悄荏w程序要做的就是考慮環(huán)境的當(dāng)前狀態(tài)進(jìn)行決策,但是我們還想要更多。 通常,座席的目標(biāo)是在其一生中最大化其累積獎(jiǎng)勵(lì),重點(diǎn)是“累積”一詞。 智能體不僅關(guān)心在下一步中獲得的報(bào)酬,而且還關(guān)心將來可能獲得的報(bào)酬。 這需要有遠(yuǎn)見,并將使智能體學(xué)習(xí)得更好。

這個(gè)元素使問題變得更加復(fù)雜,因?yàn)槲覀儽仨殭?quán)衡兩個(gè)因素:探索與利用。 探索將意味著做出隨機(jī)決策并對(duì)其進(jìn)行測(cè)試,而利用則意味著做出智能體已經(jīng)知道的決策將給其帶來積極的結(jié)果,因此智能體現(xiàn)在需要找到一種方法來平衡這兩個(gè)因素以獲得最大的累積結(jié)果。 。 這是強(qiáng)化學(xué)習(xí)中非常重要的概念。 這個(gè)概念催生了各種算法來平衡這兩個(gè)因素,并且是一個(gè)廣泛的研究領(lǐng)域。

在本章中,我們將使用 OpenAI 名為 Gym 的庫(kù)。 這是一個(gè)開放源代碼庫(kù),為強(qiáng)化學(xué)習(xí)算法的訓(xùn)練和基準(zhǔn)測(cè)試設(shè)定了標(biāo)準(zhǔn)。 體育館提供了許多研究人員用來訓(xùn)練強(qiáng)化學(xué)習(xí)算法的環(huán)境。 它包括許多 Atari 游戲,用于拾取物品的機(jī)器人仿真,用于步行和跑步的各種機(jī)器人仿真以及駕駛仿真。 該庫(kù)提供了智能體程序和環(huán)境之間相互交互所必需的參數(shù)。

問題

現(xiàn)在,我們已經(jīng)準(zhǔn)備好用數(shù)學(xué)公式來表達(dá)強(qiáng)化學(xué)習(xí)問題,因此讓我們開始吧。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-OL5ctMUt-1681786272904)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_07_01.jpg)]

圖 7.1:強(qiáng)化學(xué)習(xí)框架

在上圖中,您可以看到任何強(qiáng)化學(xué)習(xí)問題的設(shè)置。 通常,強(qiáng)化學(xué)習(xí)問題的特征在于,智能體試圖學(xué)習(xí)有關(guān)其環(huán)境的信息,如前所述。

假設(shè)時(shí)間以不連續(xù)的時(shí)間步長(zhǎng)演化,則在時(shí)間步長(zhǎng) 0 處,智能體查看環(huán)境。 您可以將這種觀察視為環(huán)境呈現(xiàn)給智能體的情況。 這也稱為觀察環(huán)境狀態(tài)。 然后,智能體必須為該特定狀態(tài)選擇適當(dāng)?shù)牟僮鳌?接下來,環(huán)境根據(jù)智能體采取的行動(dòng)向智能體提出了新的情況。 在同一時(shí)間步長(zhǎng)中,環(huán)境會(huì)給智能體提供獎(jiǎng)勵(lì),從而可以指示智能體是否做出了適當(dāng)?shù)捻憫?yīng)。 然后該過程繼續(xù)。 環(huán)境為坐席提供狀態(tài)和獎(jiǎng)勵(lì),然后坐席采取行動(dòng)。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-W5WrbM8T-1681786272904)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_07_02.jpg)]

圖 7.2:每個(gè)時(shí)間步驟都有一個(gè)狀態(tài),動(dòng)作和獎(jiǎng)勵(lì)

因此,狀態(tài),動(dòng)作和獎(jiǎng)勵(lì)的順序現(xiàn)在隨著時(shí)間而流動(dòng),在這個(gè)過程中,對(duì)智能體而言最重要的是其獎(jiǎng)勵(lì)。 話雖如此,智能體的目標(biāo)是使累積獎(jiǎng)勵(lì)最大化。 換句話說,智能體需要制定一項(xiàng)策略,以幫助其采取使累積獎(jiǎng)勵(lì)最大化的行動(dòng)。 這只能通過與環(huán)境交互來完成。

這是因?yàn)榄h(huán)境決定了對(duì)每個(gè)動(dòng)作給予智能體多少獎(jiǎng)勵(lì)。 為了用數(shù)學(xué)公式表述,我們需要指定狀態(tài),動(dòng)作和獎(jiǎng)勵(lì),以及環(huán)境規(guī)則。

情景任務(wù)與連續(xù)任務(wù)

在現(xiàn)實(shí)世界中,我們指定的許多任務(wù)都有明確定義的終點(diǎn)。 例如,如果智能體正在玩游戲,則當(dāng)智能體獲勝或失敗或死亡時(shí),劇集或任務(wù)便會(huì)結(jié)束。

在無人駕駛汽車的情況下,任務(wù)在汽車到達(dá)目的地或撞車時(shí)結(jié)束。 這些具有明確終點(diǎn)的任務(wù)稱為劇集任務(wù)。 智能體在每個(gè)劇集的結(jié)尾都會(huì)獲得獎(jiǎng)勵(lì),這是智能體決定自己在環(huán)境中做得如何的時(shí)候。 然后,智能體從頭開始但繼續(xù)擁有下一個(gè)劇集的先驗(yàn)信息,然后繼續(xù)執(zhí)行下一個(gè)劇集,因此效果更好。

隨著時(shí)間的流逝,在一段劇集中,智能體將學(xué)會(huì)玩游戲或?qū)⑵囬_到特定的目的地,因此將受到訓(xùn)練。 您會(huì)記得,智能體的目標(biāo)是在劇集結(jié)束時(shí)最大限度地提高累積獎(jiǎng)勵(lì)。

但是,有些任務(wù)可能永遠(yuǎn)持續(xù)下去。 例如,在股票市場(chǎng)上交易股票的機(jī)器人沒有明確的終點(diǎn),必須在每個(gè)時(shí)間步驟中學(xué)習(xí)和提高自己。 這些任務(wù)稱為連續(xù)任務(wù)。 因此,在那種情況下,獎(jiǎng)勵(lì)是在特定的時(shí)間間隔提供給業(yè)務(wù)代表的,但任務(wù)沒有盡頭,因此業(yè)務(wù)代表必須從環(huán)境中學(xué)習(xí)并同時(shí)進(jìn)行預(yù)測(cè)。

在本章中,我們將只關(guān)注情景任務(wù),但為連續(xù)任務(wù)制定問題陳述并不會(huì)有太大不同。

累積折扣獎(jiǎng)勵(lì)

為了使智能體最大化累積獎(jiǎng)勵(lì),可以考慮的一種方法是在每個(gè)時(shí)間步長(zhǎng)上最大化獎(jiǎng)勵(lì)。 這樣做可能會(huì)產(chǎn)生負(fù)面影響,因?yàn)樵诔跏紩r(shí)間步長(zhǎng)中最大化回報(bào)可能會(huì)導(dǎo)致智能體在將來很快失敗。 讓我們以步行機(jī)器人為例。 假定機(jī)器人的速度是獎(jiǎng)勵(lì)的一個(gè)因素,如果機(jī)器人在每個(gè)時(shí)間步長(zhǎng)上都最大化其速度,則可能會(huì)使其不穩(wěn)定并使其更快落下。

我們正在訓(xùn)練機(jī)器人走路; 因此,我們可以得出結(jié)論,智能體不能僅僅專注于當(dāng)前時(shí)間步長(zhǎng)來最大化報(bào)酬。 它需要考慮所有時(shí)間步驟。 所有強(qiáng)化學(xué)習(xí)問題都會(huì)是這種情況。 動(dòng)作可能具有短期或長(zhǎng)期影響,智能體需要了解動(dòng)作的復(fù)雜性以及環(huán)境帶來的影響。

在前述情況下,如果智能體將了解到其移動(dòng)速度不能超過某個(gè)可能會(huì)使它不穩(wěn)定并對(duì)其產(chǎn)生長(zhǎng)期影響的極限,則它將自行學(xué)習(xí)閾值速度。 因此,智能體將在每個(gè)時(shí)間步長(zhǎng)處獲得較低的報(bào)酬,但會(huì)避免將來跌倒,從而使累積報(bào)酬最大化。

假設(shè)在所有未來時(shí)間步長(zhǎng)處的獎(jiǎng)勵(lì)都由R[t]R[t + 1]R[t + 2]表示,依此類推:

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-g9vHVVKR-1681786272904)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_07_002.jpg)]

由于這些時(shí)間步伐是在將來,智能體無法確定地知道將來的回報(bào)是什么。 它只能估計(jì)或預(yù)測(cè)它們。 未來獎(jiǎng)勵(lì)的總和也稱為回報(bào)。 我們可以更明確地指定智能體的目標(biāo)是使期望收益最大化。

讓我們還考慮一下,未來回報(bào)中的所有回報(bào)并不那么重要。 為了說明這一點(diǎn),假設(shè)您想訓(xùn)練一只狗。 您給它命令,如果它正確地遵循了它們,則給它一種獎(jiǎng)賞。 您能期望狗像稱重從現(xiàn)在起數(shù)年可能獲得的獎(jiǎng)勵(lì)一樣,來權(quán)衡明天可能獲得的獎(jiǎng)勵(lì)嗎? 這似乎不可行。

為了讓狗決定現(xiàn)在需要采取什么行動(dòng),它需要更加重視可能早日獲得的獎(jiǎng)勵(lì),而不再重視可能會(huì)從現(xiàn)在開始獲得的獎(jiǎng)勵(lì)。 這也被認(rèn)為是合乎邏輯的,因?yàn)楣凡淮_定未來的把握,特別是當(dāng)狗仍在學(xué)習(xí)環(huán)境并改變其從環(huán)境中獲得最大回報(bào)的策略時(shí)。 因?yàn)榕c未來成千上萬步長(zhǎng)的獎(jiǎng)勵(lì)相比,未來數(shù)個(gè)時(shí)間步長(zhǎng)的獎(jiǎng)勵(lì)更可預(yù)測(cè),所以折扣收益的概念應(yīng)運(yùn)而生。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-rhNCppzz-1681786272904)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_07_003.jpg)]

可以看到,我們?cè)?code>Goal方程中引入了可變伽瑪。 接近 1 的Gamma表示您將來對(duì)每個(gè)獎(jiǎng)勵(lì)的重視程度相同。 接近 0 的Gamma表示只有最近的獎(jiǎng)勵(lì)才具有很高的權(quán)重。

一個(gè)良好的做法是將Gamma = 0.9,因?yàn)槟M悄荏w對(duì)未來有足夠的關(guān)注,但又不是無限遠(yuǎn)。 您可以在訓(xùn)練時(shí)設(shè)置Gamma,并且Gamma會(huì)保持固定,直到實(shí)驗(yàn)結(jié)束。 重要的是要注意,折扣在連續(xù)任務(wù)中非常有用,因?yàn)樗鼈儧]有盡頭。 但是,繼續(xù)執(zhí)行的任務(wù)不在本章范圍之內(nèi)。

馬爾可夫決策過程

讓我們通過學(xué)習(xí)稱為馬爾可夫決策過程MDP)的數(shù)學(xué)框架來完成對(duì)強(qiáng)化學(xué)習(xí)問題的定義。

MDP 定義有五件事:

  • 有限狀態(tài)集
  • 有限動(dòng)作集
  • 有限獎(jiǎng)勵(lì)集
  • 折扣率
  • 環(huán)境的單步動(dòng)態(tài)

我們已經(jīng)了解了如何指定狀態(tài),操作,獎(jiǎng)勵(lì)和折扣率。 讓我們找出如何指定環(huán)境的一步式動(dòng)態(tài)。

下圖描述了垃圾收集機(jī)器人的 MDP。 機(jī)器人的目標(biāo)是收集垃圾桶。 機(jī)器人將繼續(xù)尋找垃圾桶,并不斷收集垃圾桶,直到電池用完,然后再回到擴(kuò)展塢為電池充電。 可以將機(jī)器人的狀態(tài)定義為高和低,以表示其電池電量。 機(jī)器人可以執(zhí)行的一組操作是搜索垃圾桶,在自己的位置等待,然后返回對(duì)接站為電池充電。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-LNOG5jUJ-1681786272904)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_07_03.jpg)]

圖 7.3:垃圾收集機(jī)器人的 MDP

例如,假設(shè)機(jī)器人處于高電量狀態(tài)。 如果決定搜索垃圾桶,則狀態(tài)保持高狀態(tài)的概率為 70%,狀態(tài)變?yōu)榈蜖顟B(tài)的概率為 30%,每種狀態(tài)獲得的獎(jiǎng)勵(lì)為 4。

同樣,如果電池處于高電量狀態(tài),則決定在其當(dāng)前位置等待,電池處于高電量狀態(tài)的可能性為 100%,但是獲得的獎(jiǎng)勵(lì)也很低。

花一點(diǎn)時(shí)間瀏覽所有動(dòng)作和狀態(tài),以更好地了解它們。 通過詳細(xì)說明智能體可以處于的所有狀態(tài)以及智能體在其所有狀態(tài)下可以執(zhí)行的所有操作,并確定每個(gè)操作的概率,可以指定環(huán)境。 一旦指定了所有這些,就可以指定環(huán)境的一站式動(dòng)態(tài)。

在任何 MDP 中,智能體都會(huì)知道狀態(tài),操作和折扣率,而不會(huì)知道環(huán)境的回報(bào)和一步動(dòng)態(tài)。

現(xiàn)在,您了解了制定任何實(shí)際問題(通過強(qiáng)化學(xué)習(xí)解決)的所有知識(shí)。

解決方案

既然我們已經(jīng)學(xué)習(xí)了如何使用 MDP 來指定問題,那么智能體需要制定解決方案。 此策略也可以稱為策略。

策略和值函數(shù)

策略定義學(xué)習(xí)智能體在給定時(shí)間的行為方式。 保單用希臘字母Pi表示。 該策略不能用公式定義; 它更多是基于直覺的概念。

讓我們舉個(gè)例子。 對(duì)于需要在房間外尋找出路的機(jī)器人,它可能具有以下策略:

  • 隨機(jī)走
  • 沿著墻壁走
  • 找到通往門的最短路徑

為了使我們能夠數(shù)學(xué)地預(yù)測(cè)在特定狀態(tài)下要采取的行動(dòng),我們需要一個(gè)函數(shù)。 讓我們定義一個(gè)函數(shù),該函數(shù)將設(shè)為當(dāng)前狀態(tài),并輸出一個(gè)數(shù)字,該數(shù)字表示該狀態(tài)的值。例如,如果您要越過河流,那么靠近橋梁的位置的值將比遠(yuǎn)離目標(biāo)位置更大。 此函數(shù)稱為值函數(shù),也用V表示。

我們可以使用另一個(gè)函數(shù)來幫助我們度量事物:一個(gè)函數(shù),該函數(shù)為我們提供由所有可以采取的行動(dòng)所導(dǎo)致的所有未來狀態(tài)的值。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-HVyjH8jh-1681786272905)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_07_04.jpg)]

圖 7.4:MDP 中的狀態(tài)和動(dòng)作

讓我們舉個(gè)例子。 讓我們考慮通用狀態(tài)S0。 現(xiàn)在我們需要預(yù)測(cè)在a1a2a3之間要采取什么行動(dòng)才能獲得最大的回報(bào)(累積折扣獎(jiǎng)勵(lì))。 我們將此函數(shù)命名為Q。 我們的函數(shù)Q,將預(yù)測(cè)每個(gè)操作的預(yù)期收益(值(V))。 此Q函數(shù)也稱為動(dòng)作值函數(shù),因?yàn)樗紤]了狀態(tài)和動(dòng)作,并預(yù)測(cè)了它們各自的組合的預(yù)期收益。

我們通常會(huì)選擇最大值。 因此,這些最高限額將指導(dǎo)智能體到最后,這將是我們的策略。 請(qǐng)注意,我大部分時(shí)間都在說。 通常,在選擇非最大動(dòng)作值對(duì)時(shí),我們會(huì)保持很小的隨機(jī)機(jī)會(huì)。 我們這樣做是為了提高模型的可探索性。 該隨機(jī)探索機(jī)會(huì)的百分比稱為ε,該策略稱為 ε 貪婪策略。 這是人們用來解決強(qiáng)化學(xué)習(xí)問題的最常見策略。 如果我們一直都只選擇最大值,而不進(jìn)行任何探索,則該策略簡(jiǎn)稱為貪婪策略。 我們將在實(shí)現(xiàn)過程中同時(shí)使用這兩種策略。

但是起初,我們可能不知道最佳作用值函數(shù)。 因此,由此產(chǎn)生的策略也將不是最佳策略。 我們將需要遍歷動(dòng)作值函數(shù),并找到提供最佳回報(bào)的函數(shù)。 一旦找到它,我們將獲得最優(yōu)的Q。 最佳Q也稱為Q*。 因此,我們將能夠找到最優(yōu)的Pi,也稱為Pi*。

Q函數(shù)是智能體必須學(xué)習(xí)的函數(shù)。 我們將使用神經(jīng)網(wǎng)絡(luò)來學(xué)習(xí)此函數(shù),因?yàn)樯窠?jīng)網(wǎng)絡(luò)也是通用函數(shù)逼近器。 一旦有了行動(dòng)值函數(shù),座席就可以了解問題的最佳策略,我們就可以完成目標(biāo)。

貝爾曼方程

如果我們使用最近定義的 Q 函數(shù)重新定義目標(biāo)方程,則可以編寫:

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-se9KiQU0-1681786272905)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_07_004.jpg)]

現(xiàn)在讓我們遞歸定義相同的方程式。 我們將提出貝爾曼方程:

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-u0CJwmRP-1681786272905)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_07_005.jpg)]

簡(jiǎn)而言之,Bellman 等式指出,每個(gè)點(diǎn)的收益等于下一時(shí)間步長(zhǎng)的估計(jì)報(bào)酬加上隨后狀態(tài)的折扣報(bào)酬。 可以肯定地說,某些策略的任何值函數(shù)都遵循貝爾曼方程。

尋找最佳 Q 函數(shù)

現(xiàn)在我們知道,如果我們具有最優(yōu) Q 函數(shù),則可以通過選擇收益最高的操作來找到最優(yōu)策略。

深度 Q 學(xué)習(xí)

深度 Q 學(xué)習(xí)算法使用神經(jīng)網(wǎng)絡(luò)來解決 Q 學(xué)習(xí)問題。 它對(duì)于連續(xù)空間的強(qiáng)化學(xué)習(xí)問題非常有效。 也就是說,任務(wù)不會(huì)結(jié)束。

前面我們討論了值函數(shù)(V)和操作值函數(shù)(Q)。 由于神經(jīng)網(wǎng)絡(luò)是通用函數(shù)逼近器,因此我們可以假設(shè)它們中的任何一個(gè)都是神經(jīng)網(wǎng)絡(luò),具有可以訓(xùn)練的權(quán)重。

因此,值函數(shù)現(xiàn)在將接受網(wǎng)絡(luò)的狀態(tài)和權(quán)重,并輸出當(dāng)前狀態(tài)的值。 我們將需要計(jì)算某種誤差并將其反向傳播到網(wǎng)絡(luò),然后使用梯度下降進(jìn)行訓(xùn)練。 我們需要將網(wǎng)絡(luò)的輸出(值函數(shù))與我們認(rèn)為最佳的值進(jìn)行比較。

根據(jù)貝爾曼方程:

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-CYLH4NHS-1681786272905)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_07_005.jpg)]

我們可以通過考慮下一個(gè)狀態(tài)的值來計(jì)算預(yù)期的Q。 我們可以通過考慮到目前為止的累積獎(jiǎng)勵(lì)來計(jì)算當(dāng)前的Q。 在這些 Q 函數(shù)之間的差上使用均方誤差MSE)可能是我們的損失。 研究人員建議的一項(xiàng)改進(jìn)是,當(dāng)誤差較大時(shí),使用平均絕對(duì)誤差代替 MSE。 當(dāng) Q 函數(shù)的估計(jì)值非常嘈雜時(shí),這使它對(duì)異常值更加健壯。 這種損失稱為胡貝爾損失。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-Hqsyb0Oj-1681786272905)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_07_006.jpg)]

我們的代碼的訓(xùn)練循環(huán)如下所示:

  • 隨機(jī)初始化w, π <- ε
  • 對(duì)于所有劇集:
    • 觀察S
    • 雖然S并非在每個(gè)時(shí)間步都是終端:
    • 使用π, QS中選擇A
    • 觀察RS'
    • 更新Q
    • S <- S'

這里要注意的一件事是,我們將使用相同的 ε 貪婪策略在“步驟 6”中選擇動(dòng)作,并在“步驟 8”中更新相同的策略。 這種算法稱為策略上算法。 從某種意義上講,這是很好的,因?yàn)樵谖覀冇^察和更新同一策略時(shí),將更快地學(xué)習(xí)該策略。 它收斂非常快。 它也有一些缺點(diǎn),即所學(xué)習(xí)的策略和用于決策的策略彼此緊密地聯(lián)系在一起。 如果我們想要一個(gè)更具探索性的策略,以便在“步驟 6”中選擇觀察結(jié)果,并在“步驟 8”中更新更優(yōu)化的策略,該怎么辦? 這樣的算法被稱為非策略算法。

Q 學(xué)習(xí)是一種非策略算法,因此,在 Q 學(xué)習(xí)中,我們將有兩個(gè)策略。 我們用來推斷動(dòng)作的策略將是 ε 貪婪策略,并且我們將其稱為策略網(wǎng)絡(luò)。 我們將使用更新步驟更新的網(wǎng)絡(luò)將是我們的目標(biāo)網(wǎng)絡(luò)。 那只能由一個(gè)貪婪的策略來控制,這意味著我們將始終選擇ε等于零的最大值。 我們不會(huì)對(duì)此策略采取隨機(jī)措施。 我們這樣做是為了使我們更快地朝著更高的值前進(jìn)。 我們將通過不時(shí)復(fù)制策略網(wǎng)的權(quán)重(例如每隔一集一次)來更新目標(biāo)網(wǎng)的權(quán)重。

其背后的想法是不追逐一個(gè)移動(dòng)的目標(biāo)。 讓我們舉個(gè)例子:假設(shè)您想訓(xùn)練一頭驢走路。 如果您坐在驢上并在其嘴前懸掛胡蘿卜,驢可能會(huì)向前走,胡蘿卜仍與驢保持相同的距離。 但是,與普遍的看法相反,這并不那么有效。 胡蘿卜可能會(huì)隨機(jī)反彈,并可能使驢遠(yuǎn)離其路徑。 取而代之的是,通過從驢上下來并站在要驢來的地方使驢和胡蘿卜脫鉤,這似乎是一個(gè)更好的選擇。 它提供了一個(gè)更穩(wěn)定的學(xué)習(xí)環(huán)境。

經(jīng)驗(yàn)回放

我們可以對(duì)算法進(jìn)行的另一項(xiàng)改進(jìn)是添加有限的經(jīng)驗(yàn)和已保存交易記錄。 每筆交易都包含學(xué)習(xí)某些東西所需的所有相關(guān)信息。 它是狀態(tài),執(zhí)行的動(dòng)作,隨后的下一個(gè)狀態(tài)以及對(duì)該動(dòng)作給予的獎(jiǎng)勵(lì)的元組。

Transition = namedtuple('Transition', ('state', 'action', 'next_state', 'reward'))

我們將隨機(jī)采樣一些經(jīng)驗(yàn)或交易,并在優(yōu)化模型時(shí)向他們學(xué)習(xí)。

class ReplayMemory(object):def __init__(self, capacity):self.capacity = capacityself.memory = []self.position = 0def push(self, *args):if len(self.memory) < self.capacity:self.memory.append(None)self.memory[self.position] = Transition(*args)self.position = (self.position + 1) % self.capacitydef sample(self, batch_size):return random.sample(self.memory, batch_size)def __len__(self):return len(self.memory)memory = ReplayMemory(10000)

在這里,我們?yōu)榻灰锥x了一個(gè)存儲(chǔ)庫(kù)。 有一個(gè)稱為push的函數(shù)可將事務(wù)推送到內(nèi)存中。 還有另一個(gè)函數(shù)可以從內(nèi)存中隨機(jī)采樣。

Gym

我們將使用 OpenAI 的 Gym 從環(huán)境env中獲取參數(shù)。 環(huán)境變量很多,例如智能體的速度和位置。 我們將訓(xùn)練一個(gè)平衡點(diǎn)來平衡自己。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-KFSUfRrP-1681786272906)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_07_05.jpg)]

圖 7.5:卡特彼勒平衡環(huán)境

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-mwBs2cdE-1681786272906)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_07_06.jpg)]

圖 7.6:Gym 暴露的環(huán)境變量

在環(huán)境中的每個(gè)觀察值或狀態(tài)在 Cartpole 環(huán)境(env)中都有四個(gè)值。 上面的屏幕快照來自于 Cartpole 環(huán)境的 Gym 代碼。 每個(gè)觀測(cè)值在尖端都有位置,速度,極角和極速度。 您可以采取的行動(dòng)是向左或向右移動(dòng)。

env = gym.make('CartPole-v0').unwrapped
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")screen_width = 600def get_screen():screen = env.render(mode='rgb_array').transpose((2, 0, 1))  # transpose into torch order (CHW)screen = screen[:, 160:320]  # Strip off the top and bottom of the screen# Get cart locationworld_width = env.x_threshold * 2scale = screen_width / world_widthcart_location = int(env.state[0] * scale + screen_width / 2.0)  # MIDDLE OF CART# Decide how much to stripview_width = 320if cart_location < view_width // 2:slice_range = slice(view_width)elif cart_location > (screen_width - view_width // 2):slice_range = slice(-view_width, None)else:slice_range = slice(cart_location - view_width // 2,cart_location + view_width // 2)# Strip off the edges, so that we have a square image centered on a cartscreen = screen[:, :, slice_range]screen = np.ascontiguousarray(screen, dtype=np.float32) / 255screen = torch.from_numpy(screen)resize = T.Compose([T.ToPILImage(),T.Resize(40, interpolation=Image.CUBIC),T.ToTensor()])return resize(screen).unsqueeze(0).to(device)  # Resize, and add a batch dimension (BCHW)

在這里,我們定義了get_screen函數(shù)。 柱狀環(huán)境渲染并返回一個(gè)屏幕(3D 像素?cái)?shù)組)。 我們將要剪裁一個(gè)正方形的圖像,其中心是小刀。 我們從env.state[0]獲得了位置。 根據(jù)文檔,第一個(gè)參數(shù)是推車位置。 然后我們?nèi)サ繇敳?#xff0c;底部,左側(cè)和右側(cè),以使小柱位于中心。 接下來,我們將其轉(zhuǎn)換為張量,進(jìn)行一些轉(zhuǎn)換,添加另一個(gè)尺寸,然后返回圖像。

class DQN(nn.Module):def __init__(self):super(DQN, self).__init__()self.conv1 = nn.Conv2d(3, 16, kernel_size=5, stride=2)self.bn1 = nn.BatchNorm2d(16)self.conv2 = nn.Conv2d(16, 32, kernel_size=5, stride=2)self.bn2 = nn.BatchNorm2d(32)self.conv3 = nn.Conv2d(32, 32, kernel_size=5, stride=2)self.bn3 = nn.BatchNorm2d(32)self.head = nn.Linear(448, 2)def forward(self, x):x = F.relu(self.bn1(self.conv1(x)))x = F.relu(self.bn2(self.conv2(x)))x = F.relu(self.bn3(self.conv3(x)))return self.head(x.view(x.size(0), -1))policy_net = DQN().to(device)
target_net = DQN().to(device)
target_net.load_state_dict(policy_net.state_dict())
target_net.eval()

接下來,我們定義我們的網(wǎng)絡(luò)。 網(wǎng)絡(luò)采用當(dāng)前狀態(tài),對(duì)其進(jìn)行一些卷積運(yùn)算,最后收斂到線性層,并給出當(dāng)前狀態(tài)值的輸出,和表示在該狀態(tài)下有多大好處的值。

我們定義了兩個(gè)網(wǎng)絡(luò)policy_nettarget_net。 我們將policy_net的權(quán)重復(fù)制到target_net,以便它們代表相同的網(wǎng)絡(luò)。 我們將target_net設(shè)為評(píng)估模式,以便在反向傳播時(shí)不更新網(wǎng)絡(luò)的權(quán)重。 我們將在每個(gè)步驟中推斷policy_net,但會(huì)不時(shí)更新target_net。

EPS_START = 0.9
EPS_END = 0.05
EPS_DECAY = 200
steps_done = 0def select_action(state):global steps_doneeps_threshold = EPS_END + (EPS_START - EPS_END) * \math.exp(-1\. * steps_done / EPS_DECAY)steps_done += 1sample = random.random()if sample > eps_threshold:# freeze the network and get predictionswith torch.no_grad():return policy_net(state).max(1)[1].view(1, 1)else:# select random actionreturn torch.tensor([[random.randrange(2)]], device=device, dtype=torch.long)

接下來,我們定義一種使用 ε 貪婪策略為我們采取行動(dòng)的方法。 我們可以從策略網(wǎng)中推斷出一定時(shí)間百分比,但是也有eps_threshold的機(jī)會(huì),這意味著我們將隨機(jī)選擇操作。

num_episodes = 20
TARGET_UPDATE = 5for i_episode in range(num_episodes):env.reset()last_screen = get_screen()current_screen = get_screen()state = current_screen - last_screenfor t in count():  # for each timestep in an episode# Select action for the given state and get rewardsaction = select_action(state)_, reward, done, _ = env.step(action.item())reward = torch.tensor([reward], device=device)# Observe new statelast_screen = current_screencurrent_screen = get_screen()if not done:next_state = current_screen - last_screenelse:next_state = None# Store the transition in memorymemory.push(state, action, next_state, reward)# Move to the next statestate = next_state# Perform one step of the optimization (on the target network)optimize_model()if done:break# Update the target network every TARGET_UPDATE episodesif i_episode % TARGET_UPDATE == 0:target_net.load_state_dict(policy_net.state_dict())env.close()

讓我們看看我們的訓(xùn)練循環(huán)。 對(duì)于每個(gè)劇集,我們都會(huì)重置環(huán)境。 我們從環(huán)境中獲得了兩個(gè)屏幕,將當(dāng)前狀態(tài)定義為兩個(gè)屏幕之間的差異。 然后,對(duì)于劇集中的每個(gè)時(shí)間步,我們使用select_action函數(shù)選擇一個(gè)動(dòng)作。 我們要求環(huán)境采取該行動(dòng),并將獎(jiǎng)勵(lì)和done標(biāo)志歸還(它告訴我們劇集是否結(jié)束,也就是卡塔普爾跌倒了)。 我們觀察到已經(jīng)提出的新狀態(tài)。 然后,我們將剛剛經(jīng)歷的事務(wù)推入存儲(chǔ)體,并移至下一個(gè)狀態(tài)。 下一步是優(yōu)化模型。 我們將很快介紹該函數(shù)。

我們還將每五集使用policy_net權(quán)重的副本更新target_net。

BATCH_SIZE = 64
GAMMA = 0.999
optimizer = optim.RMSprop(policy_net.parameters())def optimize_model():# Dont optimize till atleast BATCH_SIZE memories are filledif len(memory) < BATCH_SIZE:returntransitions = memory.sample(BATCH_SIZE)batch = Transition(*zip(*transitions))# Get the actual Qstate_batch = torch.cat(batch.state)action_batch = torch.cat(batch.action)state_values = policy_net(state_batch)  # Values of States for all actions# Values of states for the selected actionstate_action_values = state_values.gather(1, action_batch)# Get the expected Q# # Mask to identify if next state is finalnon_final_mask = torch.tensor(tuple(map(lambda s: s is not None,batch.next_state)),device=device, dtype=torch.uint8)non_final_next_states = torch.cat([s for s in batch.next_state if s is not None])next_state_values = torch.zeros(BATCH_SIZE, device=device)  # init to zeros# predict next non final state values from target_net using next statesnext_state_values[non_final_mask] = target_net(non_final_next_states).max(1)[0].detach()reward_batch = torch.cat(batch.reward)# calculate the predicted values of states for actionsexpected_state_action_values = (next_state_values * GAMMA) + reward_batch# Compute Huber lossloss = F.smooth_l1_loss(state_action_values, expected_state_action_values.unsqueeze(1))# Optimize the modeloptimizer.zero_grad()loss.backward()for param in policy_net.parameters():param.grad.data.clamp_(-1, 1)optimizer.step()

然后是主要部分:優(yōu)化器步驟。 這是我們使用RMSProp找出損失和反向傳播的地方。 我們從存儲(chǔ)庫(kù)中提取了一些經(jīng)驗(yàn)。 然后,我們將所有狀態(tài),動(dòng)作和獎(jiǎng)勵(lì)轉(zhuǎn)換為批量。 我們通過policy_net傳遞狀態(tài)并獲得相應(yīng)的值。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-tN11Hw3Y-1681786272906)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_07_07.jpg)]

然后,我們收集與操作批量相對(duì)應(yīng)的值。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-yOKoNIjc-1681786272906)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_07_08.jpg)]

現(xiàn)在我們有了狀態(tài)動(dòng)作對(duì),以及與之相關(guān)的值。 這對(duì)應(yīng)于實(shí)際的 Q 函數(shù)。

接下來,我們需要找到期望的 Q 函數(shù)。 我們創(chuàng)建一個(gè)由 0 和 1 組成的掩碼,將非 0 狀態(tài)映射為 1,將 0 狀態(tài)(終端狀態(tài))映射為 0。通過算法的設(shè)計(jì),我們知道終端狀態(tài)將始終具有值 0。 狀態(tài)的值為正,但終端狀態(tài)的值為 0。掩碼如下所示:

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-6mUg3Ht5-1681786272906)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_07_09.jpg)]

在那批狀態(tài)中,置于 0 的 1 是終端狀態(tài)。 所有其他均為非最終狀態(tài)。 我們將所有非最終的下一個(gè)狀態(tài)連接到non_final_next_states中。 之后,我們將next_state_values初始化為全 0。 然后,我們將non_final_next_states傳遞給target_network,從中獲得最大值的操作值,并將其應(yīng)用于next_state_values[non_final_mask]。 我們將從非最終狀態(tài)預(yù)測(cè)的所有值都放入非最終next_state_values數(shù)組。 next_state_values的外觀如下:

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-EA3oChEC-1681786272907)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_07_10.jpg)]

最后,我們計(jì)算期望的 Q 函數(shù)。 根據(jù)我們先前的討論,它將是R + Gamma(下一個(gè)狀態(tài)值)。 然后,我們根據(jù)實(shí)際 Q 函數(shù)和預(yù)期 Q 函數(shù)計(jì)算損失,然后將誤差反向傳播到策略網(wǎng)絡(luò)(請(qǐng)記住target_net處于eval模式)。 我們還使用梯度鉗制來確保梯度較小且不會(huì)轉(zhuǎn)移得太遠(yuǎn)。

訓(xùn)練神經(jīng)網(wǎng)絡(luò)將花費(fèi)一些時(shí)間,因?yàn)樵撨^程將渲染每個(gè)幀并計(jì)算該誤差。 我們本可以使用一種更簡(jiǎn)單的方法,直接獲取速度和位置來表示損失函數(shù),并且由于不需要渲染每一幀,因此可以花費(fèi)更少的時(shí)間進(jìn)行訓(xùn)練。 它只會(huì)直接從env.state接受輸入。

此算法有許多改進(jìn),例如為智能體增加了想象力,以便可以更好地探索和想象其腦海中的動(dòng)作,并做出更好的預(yù)測(cè)。

總結(jié)

在本章中,我們學(xué)習(xí)了無監(jiān)督學(xué)習(xí)的一個(gè)全新領(lǐng)域:強(qiáng)化學(xué)習(xí)。 這是一個(gè)完全不同的領(lǐng)域,我們?cè)诒菊轮袃H涉及了這個(gè)主題。 我們學(xué)習(xí)了如何對(duì)問題進(jìn)行措辭以進(jìn)行強(qiáng)化學(xué)習(xí),然后我們訓(xùn)練了一個(gè)模型,該模型可以看到環(huán)境提供的一些測(cè)量結(jié)果,并且可以學(xué)習(xí)如何平衡赤字。 您可以應(yīng)用相同的知識(shí)來教機(jī)器人走路,駕駛汽車以及玩游戲。 這是深度學(xué)習(xí)的更多物理應(yīng)用之一。

在下一章和最后一章中,我們將著眼于生產(chǎn)我們的 PyTorch 模型,以便您可以在任何框架或語言上運(yùn)行它們,并擴(kuò)展您的深度學(xué)習(xí)應(yīng)用。

參考

  1. Google DeepMind 挑戰(zhàn)賽:Lee Sedol 與 AlphaGo

本章由 Sudhanshu Passi 貢獻(xiàn)。

八、生產(chǎn)中的 PyTorch

2017 年,當(dāng) PyTorch 發(fā)布其可用版本時(shí),它的承諾是成為研究人員的 Python 優(yōu)先框架。 PyTorch 社區(qū)對(duì)此嚴(yán)格了一年,但隨后看到了大量的生產(chǎn)要求,并決定將生產(chǎn)能力與 PyTorch 的第一個(gè)穩(wěn)定版本 1.0 合并,但又不影響其創(chuàng)建的可用性和靈活性。

PyTorch 以其干凈的框架而聞名,因此要獲得研究所需的生產(chǎn)能力和靈活性是一項(xiàng)艱巨的任務(wù)。 我認(rèn)為,將生產(chǎn)支持推向核心的主要障礙是擺脫 Python 的境界,并將 PyTorch 模型轉(zhuǎn)移到具有多線程功能的更快的線程安全語言中。 但是隨后,這違反了 PyTorch 當(dāng)時(shí)所遵循的 Python 優(yōu)先原則。

解決此問題的第一步是使開放式神經(jīng)網(wǎng)絡(luò)交換ONNX)格式穩(wěn)定,并與所有流行的框架兼容(至少與具有良好功能的框架兼容) 模塊)。 ONNX 定義了深度學(xué)習(xí)圖所需的基本運(yùn)算符和標(biāo)準(zhǔn)數(shù)據(jù)類型。 這引導(dǎo)了 ONNX 進(jìn)入 PyTorch 核心的道路,并且它與 ONNX 轉(zhuǎn)換器一起為流行的深度學(xué)習(xí)框架(例如 CNTK,MXNet,TensorFlow 等)構(gòu)建。

ONNX 很棒,并且每個(gè)人都喜歡它,但是 ONNX 的主要缺點(diǎn)之一是其腳本模式。 也就是說,ONNX 運(yùn)行一次圖以獲取有關(guān)圖的信息,然后將其轉(zhuǎn)換為 ONNX 格式。 因此,ONNX 無法遷移模型中的控制流(將for循環(huán)用于循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)模型的不同序列長(zhǎng)度)。

生產(chǎn) PyTorch 的第二種方法是在 PyTorch 本身中構(gòu)建高性能后端。 Caffe2 的核心與 PyTorch 核心合并在一起,而不是從頭開始構(gòu)建一個(gè),但 Python API 保持不變。 但是,這并不能解決 Python 語言所具有的問題。

接下來是 TorchScript 的引入,它可以將本機(jī) Python 模型轉(zhuǎn)換為可以在高性能 Universe 中加載的序列化形式,例如 C++ 線程。 PyTorch 的后端 LibTorch 可以讀取 TorchScript,這使 PyTorch 高效。 有了它,開發(fā)人員可以對(duì)模型進(jìn)行原型設(shè)計(jì),甚至可以使用 Python 本身對(duì)其進(jìn)行訓(xùn)練。 訓(xùn)練后,可以將模型轉(zhuǎn)換為到中間表示IR)。 目前,僅開發(fā)了 C++ 后端,因此可以將 IR 作為 C++ 對(duì)象加載,然后可以從 PyTorch 的 C++ API 中讀取。 TorchScript 甚至可以在 Python 程序中轉(zhuǎn)換控制流,這在生產(chǎn)支持的情況下使其優(yōu)于 ONNX 方法。 TorchScript 本身是 Python 語言中可能的操作的子集,因此不允許任何 Python 操作用 TorchScript 編寫。 官方文檔本身提供了非常詳細(xì)的說明,并討論了可能的情況和不可能的情況,以及許多示例[1]。

在本章中,我們將從使用 Flask(流行的 Python Web 框架)提供普通的 Python PyTorch 模型開始。 這樣的設(shè)置通常就足夠了,特別是如果您要設(shè)置示例 Web 應(yīng)用或滿足您個(gè)人需求或類似用例的東西。 然后,我們將探索 ONNX 并將 PyTorch 模型轉(zhuǎn)換為 MXNet,然后可以使用 MXNet 模型服務(wù)器提供服務(wù)。 從那里,我們將轉(zhuǎn)到 TorchScript,這是 PyTorch 社區(qū)的新東西。 使用 TorchScript,我們將制作 C++ 可執(zhí)行文件,然后可以在 LibTorch 的幫助下從 C++ 執(zhí)行該可執(zhí)行文件。 然后,可以從穩(wěn)定,高性能的 C++ 服務(wù)器甚至使用 cgo 的 Go 服務(wù)器提供高效的 C++ 可執(zhí)行文件。 對(duì)于所有份量,我們將使用在第 2 章,“簡(jiǎn)單神經(jīng)網(wǎng)絡(luò)”中構(gòu)建的 fizzbuzz 網(wǎng)絡(luò)。

與 Flask 一起使用

在 Python 本身中提供 PyTorch 模型是在生產(chǎn)環(huán)境中提供模型的最簡(jiǎn)單方法。 但是在解釋如何完成之前,讓我們快速看一下 Flask 是什么。 完全解釋 Flask 不在本章的討論范圍內(nèi),但我們?nèi)詫⒔榻B Flask 的最基本概念。

Flask 簡(jiǎn)介

Flask 是的微框架,已被 Python 領(lǐng)域的多家大公司用于生產(chǎn)。 即使 Flask 提供了可用于將 UI 推送到客戶端的模板引擎,我們也沒有使用它。 相反,我們將制作一個(gè)提供 API 的 RESTful 后端。

可以使用pip來安裝 Flask ,就像其他任何 Python 包一樣:

pip install Flask

這將安裝其他依賴項(xiàng) Werkzeug(應(yīng)用和服務(wù)器之間的 Python 接口),Jinga(作為模板引擎),其危險(xiǎn)(用于安全簽名數(shù)據(jù))和 Click(作為 CLI 構(gòu)建器)。

安裝后,用戶將可以訪問 CLI,并使用flask run調(diào)用我們的腳本將啟動(dòng)服務(wù)器:

from flask import Flask
app = Flask(__name__)@app.route("/")
def hello():return "Hello World!"

該示例包含四個(gè)部分:

  • 第一行是我們導(dǎo)入 Flask 包的位置。
  • 我們創(chuàng)建一個(gè) Flask 對(duì)象,這是我們的大型 Web 應(yīng)用對(duì)象,Flask 服務(wù)器將使用該對(duì)象來運(yùn)行我們的服務(wù)器。
  • 有了應(yīng)用對(duì)象后,我們需要存儲(chǔ)有關(guān)對(duì)象應(yīng)對(duì)其執(zhí)行操作的 URL 的信息。 為此,應(yīng)用對(duì)象帶有route方法,該方法接受所需的 URL 并返回裝飾器。 這是我們希望應(yīng)用現(xiàn)在提供的 URL。
  • 由應(yīng)用對(duì)象返回的裝飾器對(duì)一個(gè)函數(shù)進(jìn)行裝飾,當(dāng) URL 命中時(shí),將觸發(fā)該函數(shù)。 我們將其命名為hello。 函數(shù)的名稱在這里并不重要。 在前面的示例中,它只是檢查輸入并做出相應(yīng)的響應(yīng)。 但是對(duì)于我們的模型服務(wù)器,我們使此函數(shù)稍微復(fù)雜一點(diǎn),以便它可以接受輸入并將該輸入提供給我們構(gòu)建的模型。 然后,我們模型的返回值將作為 HTTP 響應(yīng)推回給用戶。

我們通過建立flask_trial目錄開始實(shí)現(xiàn),并將該文件另存為app.py在該目錄中:

mkdir flask_trial
cd flask_trial

然后,我們執(zhí)行 Flask 隨附的 CLI 命令來啟動(dòng)服務(wù)器。 執(zhí)行后,如果未提供自定義參數(shù),您將看到http://127.0.0.1:5000正在為服務(wù)器提供服務(wù)。

flask run

我們可以通過向服務(wù)器位置發(fā)出 HTTP 請(qǐng)求來測(cè)試簡(jiǎn)單的 Flask 應(yīng)用。 如果一切正常,我們應(yīng)該得到一個(gè)“你好,世界!” 來自服務(wù)器的消息。

-> curl "http://127.0.0.1:5000"
-> Hello World!

我們已經(jīng)建立了簡(jiǎn)單的 Flask 應(yīng)用。 現(xiàn)在,將 fizzbuzz 模型引入我們的應(yīng)用。 以下代碼片段顯示了與第 2 章和“簡(jiǎn)單神經(jīng)網(wǎng)絡(luò)”相同的模型,供您參考。 該模型將從路由函數(shù)中調(diào)用。 我們已經(jīng)在第 2 章和“一個(gè)簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò)”中對(duì)模型進(jìn)行了訓(xùn)練,因此,我們將在這里加載訓(xùn)練后的模型,而不是再次對(duì)其進(jìn)行訓(xùn)練:

import torch.nn as nn
import torchclass FizBuzNet(nn.Module):"""2 layer network for predicting fiz or buzparam: input_size -> intparam: output_size -> int"""def __init__(self, input_size, hidden_size, output_size):super(FizBuzNet, self).__init__()self.hidden = nn.Linear(input_size, hidden_size)self.out = nn.Linear(hidden_size, output_size)def forward(self, batch):hidden = self.hidden(batch)activated = torch.sigmoid(hidden)out = self.out(activated)return out

用于 Flask 的模型

下面的屏幕快照給出了我們應(yīng)用的目錄結(jié)構(gòu)。 assets文件夾具有訓(xùn)練好的模型,在加載模型時(shí),controller.py文件將使用該模型。 根目錄中的app.py是 Flask 應(yīng)用的入口。 Flask 首選app.py作為入口點(diǎn)文件的默認(rèn)名稱。

當(dāng)您執(zhí)行flask run時(shí),Flask 將在當(dāng)前目錄中查找app.py文件并執(zhí)行該文件。 controller.py文件是我們從model.py文件加載模型的地方。 然后,加載的模型將等待用戶通過 HTTP 端點(diǎn)輸入。 app.py將用戶輸入重定向到controller,然后將其轉(zhuǎn)換為 Torch 張量。

張量對(duì)象將通過神經(jīng)網(wǎng)絡(luò)傳遞,并且controller將神經(jīng)網(wǎng)絡(luò)的結(jié)果傳遞給后處理操作后,從神經(jīng)網(wǎng)絡(luò)返回結(jié)果。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-QEhgxf1S-1681786272907)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_08_01.jpg)]

圖 8.1:當(dāng)前目錄

目錄中有四個(gè)組件用于制作 Flask 應(yīng)用。 assets文件夾是我們保留模型的地方。 其他三個(gè)文件是代碼所在的位置。 讓我們研究一下每個(gè)。 我們將從入口文件app.py開始。 它是先前提供的簡(jiǎn)單 Flask 應(yīng)用的擴(kuò)展版本。 該文件教我們?nèi)绾味x URL 端點(diǎn),以及如何將 URL 端點(diǎn)映射到 Python 函數(shù)。 我們的擴(kuò)展app.py文件顯示在以下代碼塊中:

import jsonfrom flask import Flask
from flask import requestimport controllerapp = Flask('FizBuzAPI')@app.route('/predictions/fizbuz_package', methods=['POST'])
def predict():which = request.get_json().get('input.1')if not which:return "InvalidData"try:number = int(which) + 1prediction = controller.run(number)out = json.dumps({'NextNumber': prediction})except ValueError:out = json.dumps({'NextNumber': 'WooHooo!!!'})return out

Flask 為我們提供了request工具,它是一個(gè)全局變量,但對(duì)于存儲(chǔ)有關(guān)當(dāng)前請(qǐng)求信息的當(dāng)前線程而言是局部的。 我們使用request對(duì)象的get_json函數(shù)從request對(duì)象獲取主體POST參數(shù)。 然后,將通過 HTTP 傳入的字符串?dāng)?shù)據(jù)轉(zhuǎn)換為整數(shù)。 這個(gè)整數(shù)是我們從前端傳遞的數(shù)字。 我們應(yīng)用的任務(wù)是預(yù)測(cè)下一個(gè)數(shù)字的狀態(tài)。 那將是下一個(gè)數(shù)字本身還是嘶嘶聲,嗡嗡聲或嘶嘶聲? 但是,如果您還記得,我們會(huì)訓(xùn)練我們的網(wǎng)絡(luò)來預(yù)測(cè)我們通過的號(hào)碼的狀態(tài)。 但是,我們需要下一個(gè)號(hào)碼的狀態(tài)。 因此,我們將一個(gè)加到當(dāng)前數(shù)上,然后將結(jié)果傳遞給我們的模型。

我們的下一個(gè)導(dǎo)入是controller,我們?cè)谄渲屑虞d了模型文件。 我們正在調(diào)用run方法并將數(shù)字傳遞給模型。 然后,將controller的預(yù)測(cè)值作為字典傳遞回。 Flask 會(huì)將其轉(zhuǎn)換為響應(yīng)正文并將其發(fā)送回用戶。

在繼續(xù)之前,我們可以從以前的簡(jiǎn)單 Flask 應(yīng)用的擴(kuò)展版本中看到兩個(gè)主要差異。 一種是 URL 路由:/predictions/fizbuz_package。 如前所述,Flask 允許您將任何 URL 端點(diǎn)映射到您選擇的函數(shù)。

其次,我們?cè)谘b飾器中使用了另一個(gè)關(guān)鍵字參數(shù):methods。 這樣,我們告訴 Flask,不僅需要通過 URL 規(guī)則來調(diào)用此函數(shù),而且還需要在對(duì)該 URL 的POST方法調(diào)用上進(jìn)行調(diào)用。 因此,我們像以前一樣使用flask run運(yùn)行該應(yīng)用,并使用curl命令對(duì)其進(jìn)行測(cè)試。

-> curl -X POST http://127.0.0.1:5000/predictions/fizbuz_package \-H "Content-Type: application/json" \-d '{"input.1": 14}'-> {"NextNumber": "FizBuz"}

在 HTTP POST請(qǐng)求中,我們傳遞了輸入數(shù)字為14的 JSON 對(duì)象,我們的服務(wù)器返回了下一個(gè)數(shù)字FizBuz。 所有這些魔術(shù)都發(fā)生在我們的app.py調(diào)用的controller.run()方法中。 現(xiàn)在,讓我們看看該函數(shù)在做什么。

接下來是使用run()方法的controller文件。 在這里,我們將輸入數(shù)字轉(zhuǎn)換為 10 位二進(jìn)制數(shù)(請(qǐng)記住,在第 2 章,“簡(jiǎn)單神經(jīng)網(wǎng)絡(luò)”中,這是我們作為輸入傳遞給 fizzbuzz 網(wǎng)絡(luò)的東西),將其變?yōu)?Torch 張量。 然后將二進(jìn)制張量傳遞給我們模型的正向函數(shù),以得到具有預(yù)測(cè)的1 x 4張量。

通過從加載了保存的.pth文件的模型文件中調(diào)用FizBuz類來創(chuàng)建我們的模型。 我們使用 Torch 的load_state_dict方法將參數(shù)加載到初始化的模型中。 之后,我們將模型轉(zhuǎn)換為eval()模式,這將模型設(shè)置為評(píng)估模式(它在評(píng)估模式下關(guān)閉了batchnorm丟棄層)。 模型的輸出是運(yùn)行max并確定哪個(gè)索引具有最大值,然后將其轉(zhuǎn)換為可讀輸出的概率分布。

為生產(chǎn)準(zhǔn)備的服務(wù)器

這是關(guān)于如何使用 Flask 將 PyTorch 模型部署到服務(wù)器的非常基本的演練。 但是 Flask 的內(nèi)置服務(wù)器尚未投入生產(chǎn),只能用于開發(fā)目的。 開發(fā)完成后,我們應(yīng)該使用其他服務(wù)器包在生產(chǎn)中為 Flask 應(yīng)用提供服務(wù)。

Gunicorn 是 Python 開發(fā)人員使用的最受歡迎的服務(wù)器包之一,將其與 Flask 應(yīng)用綁定非常容易。 您可以使用pip安裝 Gunicorn,就像我們安裝 Flask 一樣:

pip install gunicorn

Gunicorn 需要我們傳遞模塊名稱,以便它能夠拾取模塊并運(yùn)行服務(wù)器。 但是 Gunicorn 希望應(yīng)用對(duì)象具有名稱application,而我們的項(xiàng)目則不是這樣。 因此,我們需要顯式傳遞應(yīng)用對(duì)象名稱和模塊名稱。 Gunicorn 的命令行工具有很多選擇,但是我們正在嘗試使其盡可能簡(jiǎn)單:

gunicorn app:app
import torch
from model import FizBuzNetinput_size = 10
output_size = 4
hidden_size = 100def binary_encoder():def wrapper(num):ret = [int(i) for i in '{0:b}'.format(num)]return [0] * (input_size - len(ret)) + retreturn wrappernet = FizBuzNet(input_size, hidden_size, output_size)
net.load_state_dict(torch.load('assets/fizbuz_model.pth'))
net.eval()
encoder = binary_encoder()def run(number):with torch.no_grad():binary = torch.Tensor([encoder(number)])out = net(binary)[0].max(0)[1].item()return get_readable_output(number, out)

ONNX

建立 ONNX 協(xié)議是為了創(chuàng)建不同框架之間的互操作性。 這可以幫助 AI 開發(fā)人員和組織選擇合適的框架來開發(fā)他們花費(fèi)大部分時(shí)間的 AI 模型。 一旦開發(fā)和訓(xùn)練階段結(jié)束,他們便可以將模型遷移到他們選擇的任何框架中,以在生產(chǎn)中提供服務(wù)。

可以針對(duì)不同目的優(yōu)化不同的框架,例如移動(dòng)部署,可讀性和靈活性,生產(chǎn)部署等。 有時(shí)將模型轉(zhuǎn)換為不同的框架是不可避免的,手動(dòng)轉(zhuǎn)換很耗時(shí)。 這是 ONNX 試圖通過互操作性解決的另一個(gè)用例。

讓我們以任何框架示例為例,看看 ONNX 適合什么地方。框架將具有語言 API(供開發(fā)人員使用),然后是由他們開發(fā)的模型的圖形表示。 然后,該 IR 進(jìn)入高度優(yōu)化的運(yùn)行時(shí)以執(zhí)行。 ONNX 為此 IR 提供了統(tǒng)一的標(biāo)準(zhǔn),并使所有框架都了解 ONNX 的 IR。 借助 ONNX,開發(fā)人員可以使用 API??制作模型,然后將其轉(zhuǎn)換為框架的 IR。 ONNX 轉(zhuǎn)換器可以將該 IR 轉(zhuǎn)換為 ONNX 的標(biāo)準(zhǔn) IR,然后可以將其轉(zhuǎn)換為其他框架的 IR。

這是 PyTorch 的 Fizzbuzz 網(wǎng)絡(luò)的 IR 的可讀表示:

graph(%input.1 : Float(1, 10)%weight.1 : Float(100, 10)%bias.1 : Float(100)%weight : Float(4, 100)%bias : Float(4)) {%5 : Float(10!, 100!) = aten::t(%weight.1),scope: FizBuzNet/Linear[hidden]%6 : int = prim::Constant[value=1](),scope: FizBuzNet/Linear[hidden]%7 : int = prim::Constant[value=1](),scope: FizBuzNet/Linear[hidden]%hidden : Float(1, 100) = aten::addmm(%bias.1, %input.1, %5, %6,%7), scope: FizBuzNet/Linear [hidden]%input : Float(1, 100) = aten::sigmoid(%hidden),scope: FizBuzNet%10 : Float(100!, 4!) = aten::t(%weight),scope: FizBuzNet/Linear[out]%11 : int = prim::Constant[value=1](),scope: FizBuzNet/Linear[out]%12 : int = prim::Constant[value=1](),scope: FizBuzNet/Linear[out]%13 : Float(1, 4) = aten::addmm(%bias, %input, %10, %11, %12),scope: FizBuzNet/Linear[out]return (%13);
}

表示清楚地表明了整個(gè)網(wǎng)絡(luò)的結(jié)構(gòu)。 前五行顯示參數(shù)和輸入張量,并為每一個(gè)標(biāo)記一個(gè)名稱。 例如,整個(gè)網(wǎng)絡(luò)將輸入張量定為input.i,它是形狀為1 x 10的浮點(diǎn)張量。然后,它顯示了我們第一層和第二層的權(quán)重和偏差張量。

從第六行開始,顯示了圖的結(jié)構(gòu)。 每行的第一部分(以%符號(hào)開頭的全冒號(hào)之前的字符)是每行的標(biāo)識(shí)符,這是其他行中用來引用這些行的標(biāo)識(shí)符。 例如,以%5作為標(biāo)識(shí)符的線對(duì)aten::t(%weight.i)表示的第一層的權(quán)重進(jìn)行轉(zhuǎn)置,從而輸出形狀為10 x 100的浮點(diǎn)張量。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-2IIaWnvc-1681786272907)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_08_02.jpg)]

圖 8.2:另一個(gè) IR 轉(zhuǎn)換為 ONNX 的 IR,然后又轉(zhuǎn)換為另一個(gè) IR

PyTorch 具有內(nèi)置的 ONNX 導(dǎo)出器,它可以幫助我們創(chuàng)建 ONNX IR,而無需離開 PyTorch。 在此處給出的示例中,我們將 fizbuzz 網(wǎng)絡(luò)導(dǎo)出到 ONNX,然后由 MXNet 模型服務(wù)器提供服務(wù)。 在以下代碼段中,我們使用 PyTorch 的內(nèi)置export模塊將 fizzbuzz 網(wǎng)絡(luò)轉(zhuǎn)換為 ONNX 的 IR:

>>> import torch
>>> dummy_input = torch.Tensor([[0, 0, 0, 0, 0, 0, 0, 0, 1, 0]])
>>> dummy_inputtensor([[O., 0., 0., 0., 0., 0., 0., O., 1., 0.]])
>>> net = FizBuzNet(input_size, hidden_size, output_size)
>>> net.load_state_dict(torch.load('assets/fizbuz_model.pth'))
>>> dummy_input = torch.Tensor([[0, 0, 0, 0, 0, 0, 0, 0, 1, 0]])
>>> torch.onnx.export(net, dummy_input, "fizbuz.onnx", verbose=True)

在最后一行,我們調(diào)用export模塊,并傳遞 PyTorch 的net,虛擬輸入和輸出文件名。 ONNX 通過跟蹤圖進(jìn)行轉(zhuǎn)換; 也就是說,它使用我們提供的虛擬輸入執(zhí)行一次圖。

在執(zhí)行圖時(shí),它會(huì)跟蹤我們執(zhí)行的 PyTorch 操作,然后將每個(gè)操作轉(zhuǎn)換為 ONNX 格式。 鍵值參數(shù)verbose=True在導(dǎo)出時(shí)將輸出寫入到終端屏幕。 它為我們提供了 ONNX 中相同圖的 IR 表示:

graph(%input.1 : Float(1, 10)%1 : Float(100, 10)%2 : Float(100)%3 : Float(4, 100)%4 : Float(4)) {%5 : Float(1, 100) = onnx::Gemm[alpha=1, beta=1,transB=1](%input.1, %1, %2),scope: FizBuzNet/Linear[hidden]%6 : Float(1, 100) = onnx::Sigmoid(%5), scope: FizBuzNet%7 : Float(1, 4) = onnx::Gemm[alpha=1, beta=1,transB=1](%6, %3, %4),scope: FizBuzNet/Linear[out]return (%7);
}

它還顯示了圖執(zhí)行所需的所有操作,但比 PyTorch 的圖形表示要小。 雖然 PyTorch 向我們顯示了每個(gè)操作(包括轉(zhuǎn)置操作),但 ONNX 會(huì)在高級(jí)功能(例如onnx:Gemm)下抽象該粒度信息,前提是其他框架的import模塊可以讀取這些抽象。

PyTorch 的export模塊將 ONNX 模型保存在fizbuz.onnx文件中。 可以從 ONNX 本身或其他框架中內(nèi)置的 ONNX 導(dǎo)入程序中加載。 在這里,我們將 ONNX 模型加載到 ONNX 本身并進(jìn)行模型檢查。 ONNX 還具有由 Microsoft 管理的高性能運(yùn)行時(shí),這超出了本書的解釋范圍,但可在這個(gè)頁面上獲得。

由于 ONNX 已成為框架之間互操作性的規(guī)范,因此圍繞它構(gòu)建了其他工具。 最常用/最有用的工具可能是 Netron,它是 ONNX 模型的可視化工具。 盡管 Netron 不像 TensorBoard 那樣具有交互性,但 Netron 足以用于基本可視化。

擁有.onnx文件后,您可以將文件位置作為參數(shù)傳遞給 Netron 命令行工具,該工具將構(gòu)建服務(wù)器并在瀏覽器中顯示該圖:

pip install netron
netron -b fizbuz.onnx

前面的命令將使用 Fizzbuzz 網(wǎng)絡(luò)的圖可視化來啟動(dòng) Netron 服務(wù)器,如下圖所示。 除了可縮放的圖外,Netron 還可以可視化其他基本信息,例如版本,生成器,圖的生成方式等等。 另外,每個(gè)節(jié)點(diǎn)都是可單擊的,它將顯示有關(guān)該特定節(jié)點(diǎn)的信息。 當(dāng)然,這還不夠復(fù)雜,無法滿足可視化工具所需的所有要求,但足以讓我們對(duì)整個(gè)網(wǎng)絡(luò)有所了解。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-P3yJJ009-1681786272907)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_08_03.jpg)]

圖 8.3:Fizzbuzz 網(wǎng)絡(luò)的 Netron 可視化

從成為 ONNX 可視化工具開始,Netron 逐漸接受所有流行框架的導(dǎo)出模型。 目前,根據(jù)官方文件,Netron 接受 ONNX,Keras,CoreML,Caffe2,MXNet,TensorFlow Lite,TensorFlow.js,TensorFlow,Caffe,PyTorch,Torch,CNTK,PaddlePaddle,Darknet 和 scikit-learn 的模型。

MXNet 模型服務(wù)器

現(xiàn)在我們離開了 PyTorch 世界。 我們現(xiàn)在有不同的模型服務(wù)器,但我們選擇了 MXNet 模型服務(wù)器。 MXNet 模型服務(wù)器由社區(qū)維護(hù),由亞馬遜團(tuán)隊(duì)領(lǐng)導(dǎo),也稱為 MMS。 從這里開始,我將交替使用 MMS 和 MXNet 模型服務(wù)器。

MXNet 比其他服務(wù)模塊更好。 在撰寫本文時(shí),TensorFlow 與 Python 3.7 不兼容,并且 MXNet 的服務(wù)模塊已與內(nèi)置的 ONNX 模型集成,這使開發(fā)人員可以輕松地以很少的命令行為模型提供服務(wù),而無需了解分布式或高度可擴(kuò)展的部署的復(fù)雜性。

其他模型服務(wù)器,例如 TensorRT 和 Clipper,不像 MXNet 服務(wù)器那樣易于設(shè)置和管理。 而且,MXNet 附帶了另一個(gè)名為 MXNet 存檔器的工具,該工具將所有必需的文件打包成一個(gè)捆綁包,這些文件可以獨(dú)立部署,而不必?fù)?dān)心其他依賴項(xiàng)。 除了 MXNet 模型服務(wù)器具備的所有這些很酷的功能之外,最大的好處是能夠自定義預(yù)處理和后處理步驟。 我們將在接下來的部分中介紹如何完成所有這些操作。

整個(gè)過程的流程從我們嘗試使用模型存檔器創(chuàng)建具有.mar格式的單個(gè)存檔文件的位置開始。 單個(gè)捆綁包文件需要 ONNX 模型文件signature.json,該文件提供有關(guān)輸入大小,名稱等的信息。 認(rèn)為它是可以隨時(shí)更改的配置文件。 如果您決定將所有值硬編碼到代碼中,而不是從配置中讀取,則它甚至不必成為存檔的一部分。 然后,您需要服務(wù)文件,您可以在其中定義預(yù)處理,推理功能,后處理功能和其他工具函數(shù)。

制作完模型檔案后,我們可以調(diào)用模型服務(wù)器,并將位置作為輸入傳遞給我們的模型檔案。 而已; 您現(xiàn)在可以從超級(jí)性能模型服務(wù)器提供模型。

MXNet 模型存檔器

我們將通過安裝 MXNet 模型存檔器開始我們的旅程。 MXNet 模型服務(wù)器隨附的默認(rèn)模型存檔器不支持 ONNX,因此我們需要單獨(dú)安裝。 ONNX 的模型存檔器依賴于協(xié)議緩沖區(qū)和 MXNet 包本身。 官方文檔中提供了為每個(gè)操作系統(tǒng)安裝 protobuf 編譯器的指南。 可以通過pip來安裝 MXNet 包,就像我們已經(jīng)安裝了其他包一樣(對(duì)于 GPU,MXNet 還有另一個(gè)包,但是這里我們正在安裝 MXNet 的基本版本):

pip install mxnet
pip install model-archiver[onnx]

現(xiàn)在,我們可以安裝 MXNet 模型服務(wù)器。 它基于 Java 虛擬機(jī)JVM)構(gòu)建,因此從 JVM 調(diào)用了運(yùn)行有我們模型實(shí)例的多個(gè)線程。 利用 JVM 支持的復(fù)雜性,可以將 MXNet 服務(wù)器擴(kuò)展為處理數(shù)千個(gè)請(qǐng)求的多個(gè)進(jìn)程。

MXNet 服務(wù)器帶有管理 API,該 API 通過 HTTP 提供。 這有助于生產(chǎn)團(tuán)隊(duì)根據(jù)需要增加/減少資源。 除了處理工作器規(guī)模之外,管理 API 還具有其他選項(xiàng)。 但是我們不會(huì)在這里深入探討。 由于模型服務(wù)器在 JVM 上運(yùn)行,因此我們需要安裝 Java8。此外,MXNet 模型服務(wù)器在 Windows 上仍處于試驗(yàn)?zāi)J?#xff0c;但在 Linux 風(fēng)味和 Mac 上穩(wěn)定。

pip install mxnet-model-server

現(xiàn)在,在安裝了所有前提條件之后,我們可以開始使用 MXNet 模型服務(wù)器對(duì)可用于生產(chǎn)的 PyTorch 模型進(jìn)行編碼。 首先,我們創(chuàng)建一個(gè)新目錄,以保存所有需要的文件以供模型存檔器創(chuàng)建捆綁文件。 然后,我們移動(dòng)在上一步中創(chuàng)建的.onnx文件。

MMS 的一項(xiàng)強(qiáng)制性要求是其中包含服務(wù)類的服務(wù)文件。 MMS 執(zhí)行服務(wù)文件中唯一可用類的initialize()handle()函數(shù)。 在下一節(jié)中,我們將逐一進(jìn)行介紹,但這是我們可以用來制作服務(wù)文件的框架。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-r30J2qLE-1681786272907)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_08_04.jpg)]

圖 8.4:fizbuz_package的目錄結(jié)構(gòu)

class MXNetModelService(object):def __init__(self):...def initialize(self, context):...def preprocess(self, batch):...def inference(self, model_input):...def postprocess(self, inference_output):...def handle(self, data, context):...

然后,我們需要一個(gè)簽名文件。 正如我們之前所看到的,簽名文件只是配置文件。 我們可以通過將值硬編碼到腳本本身來避免發(fā)生這種情況,但是 MMS 人士也建議這樣做。 我們?yōu)?fizzbuzz 網(wǎng)絡(luò)制作了最小的簽名文件,如下所示:

{"inputs": [{"data_name": "input.1","data_shape": [1,10]}],"input_type": "application/json"
}

在簽名文件中,我們描述了數(shù)據(jù)名稱,輸入形狀和輸入類型。 當(dāng)通過 HTTP 讀取數(shù)據(jù)流時(shí),這就是我們的服務(wù)器假定的數(shù)據(jù)信息。 通常,我們可以通過在簽名文件中進(jìn)行配置來使我們的 API 接受任何類型的數(shù)據(jù)。 但是然后我們的腳本也應(yīng)該能夠處理這些類型。 讓我們完成服務(wù)文件,然后將其與 MMS 捆綁在一起。

如您先前所見,MMS 調(diào)用服務(wù)文件中唯一可用的單個(gè)類的initialize()方法。 如果服務(wù)文件中存在更多類,那就完全是另一回事了,但是讓我們足夠簡(jiǎn)單地理解它。 顧名思義,initialize()文件初始化所需的屬性和方法:

def initialize(self, context):properties = context.system_propertiesmodel_dir = properties.get("model_dir")gpu_id = properties.get("gpu_id")self._batch_size = properties.get('batch_size')signature_file_path = os.path.join(model_dir, "signature.json")if not os.path.isfile(signature_file_path):raise RuntimeError("Missing signature.json file.")with open(signature_file_path) as f:self.signature = json.load(f)data_names = []data_shapes = []input_data = self.signature["inputs"][0]data_name = input_data["data_name"]data_shape = input_data["data_shape"]data_shape[0] = self._batch_sizedata_names.append(data_name)data_shapes.append((data_name, tuple(data_shape)))self.mxnet_ctx = mx.cpu() if gpu_id is None elsemx.gpu(gpu_id)sym, arg_params, aux_params = mx.model.load_checkpoint(checkpoint_prefix, self.epoch)self.mx_model = mx.mod.Module(symbol=sym, context=self.mxnet_ctx,data_names=data_names, label_names=None)self.mx_model.bind(for_training=False, data_shapes=data_shapes)self.mx_model.set_params(arg_params, aux_params,allow_missing=True, allow_extra=True)self.has_initialized = True

MMS 在調(diào)用initialize()時(shí)傳遞上下文參數(shù),該參數(shù)具有在解壓縮存檔文件時(shí)獲取的信息。 當(dāng)首先使用存檔文件路徑作為參數(shù)調(diào)用 MMS 時(shí),在調(diào)用服務(wù)文件之前,MMS 解壓縮存檔文件并安裝模型,并收集信息,其中存儲(chǔ)模型,MMS 可以使用多少個(gè)內(nèi)核,它是否具有 GPU 等。 所有這些信息都作為上下文參數(shù)傳遞給initialize()。

initialize()的第一部分是收集此信息以及來自簽名 JSON 文件的信息。 函數(shù)的第二部分從第一部分中收集的信息中獲取與輸入有關(guān)的數(shù)據(jù)。 然后,該函數(shù)的第三部分是創(chuàng)建 MXNet 模型并將訓(xùn)練后的參數(shù)加載到模型中。 最后,我們將self.has_initialized變量設(shè)置為True,然后將其用于檢查服務(wù)文件其他部分的初始化狀態(tài):

def handle(self, data, context):try:if not self.has_initialized:self.initialize()preprocess_start = time.time()data = self.preprocess(data)inference_start = time.time()data = self.inference(data)postprocess_start = time.time()data = self.postprocess(data)end_time = time.time()metrics = context.metricsmetrics.add_time(self.add_first())metrics.add_time(self.add_second())metrics.add_time(self.add_third())return dataexcept Exception as e:request_processor = context.request_processorrequest_processor.report_status(500, "Unknown inference error")return [str(e)] * self._batch_size

MMS 被編程為在每個(gè)請(qǐng)求上調(diào)用相同類的handle()方法,這是我們控制流程的地方。 initialize()函數(shù)只會(huì)在啟動(dòng)線程時(shí)被調(diào)用一次; 每個(gè)用戶請(qǐng)求都將調(diào)用handle()函數(shù)。 由于handle()函數(shù)是針對(duì)每個(gè)用戶請(qǐng)求被調(diào)用的,以及上下文信息,因此它也將在參數(shù)中獲取當(dāng)前數(shù)據(jù)。 但是,為了使程序模塊化,我們沒有在handle()中進(jìn)行任何操作; 取而代之的是,我們正在調(diào)用其他僅指定做一件事的函數(shù):該函數(shù)應(yīng)該做什么。

我們將整個(gè)流分為四個(gè)部分:預(yù)處理,推理,后處理和矩陣記錄。 在handle()的第一行中,我們驗(yàn)證是否正在使用上下文和數(shù)據(jù)信息初始化線程。 完成后,我們將進(jìn)入流程。 現(xiàn)在,我們將逐步完成流程。

我們首先使用data作為參數(shù)調(diào)用self.preprocess()函數(shù),其中data將是 HTTP 請(qǐng)求的POST正文內(nèi)容。 preprocess函數(shù)以與我們?cè)?code>signature.json文件中配置的名稱相同的名稱獲取傳遞的數(shù)據(jù)。 一旦有了數(shù)據(jù),這就是我們需要系統(tǒng)預(yù)測(cè)下一個(gè)數(shù)字的整數(shù)。 由于我們已經(jīng)訓(xùn)練了模型來預(yù)測(cè)當(dāng)前號(hào)碼的嘶嘶聲狀態(tài),因此我們將在數(shù)據(jù)中為號(hào)碼添加一個(gè)嗡嗡聲,然后在新號(hào)碼的二進(jìn)制文件上創(chuàng)建一個(gè) MXNet 數(shù)組:

def preprocess(self, batch):param_name = self.signature['inputs'][0]['data_name']data = batch[0].get('body').get(param_name)if data:self.input = data + 1tensor = mx.nd.array([self.binary_encoder(self.input, input_size=10)])return tensorself.error = 'InvalidData'

handle()函數(shù)獲取已處理的數(shù)據(jù),并將其傳遞給inference()函數(shù),該函數(shù)將使用已處理的數(shù)據(jù)調(diào)用保存在initialize()函數(shù)上的 MXNet 模型。 inference()函數(shù)返回大小為1 x 4的輸出張量,然后將其返回到handle()函數(shù)。

def inference(self, model_input):if self.error is not None:return Noneself.mx_model.forward(DataBatch([model_input]))model_output = self.mx_model.get_outputs()return model_output

然后將張量傳遞給postprocess()函數(shù),以將其轉(zhuǎn)換為人類可讀的輸出。 我們具有self.get_readable_output()函數(shù),可根據(jù)需要將模型的輸出轉(zhuǎn)換為嘶嘶聲,嗡嗡聲,嘶嘶聲嗡嗡聲或下一個(gè)數(shù)字。

然后,后處理的數(shù)據(jù)返回到handle()函數(shù),在其中進(jìn)行矩陣創(chuàng)建。 之后,數(shù)據(jù)將返回到handle()函數(shù)的被調(diào)用方,該函數(shù)是 MMS 的一部分。 MMS 將該數(shù)據(jù)轉(zhuǎn)換為 HTTP 響應(yīng),并將其返回給用戶。 MMS 還記錄矩陣的輸出,以便操作可以實(shí)時(shí)查看矩陣并基于此做出決策:

def postprocess(self, inference_output):if self.error is not None:return [self.error] * self._batch_sizeprediction = self.get_readable_output(self.input,int(inference_output[0].argmax(1).asscalar()))out = [{'next_number': prediction}]return out

一旦將所有文件包含在前面給出的目錄中,就可以創(chuàng)建.mar存檔文件:

model-archiver \--model-name fizbuz_package \--model-path fizbuz_package \--handler fizbuz_service -f

這將在當(dāng)前目錄中創(chuàng)建一個(gè)fizbuz_package.mar文件。 然后可以將其作為 CLI 參數(shù)傳遞給 MMS:

mxnet-model-server \--start \--model-store FizBuz_with_ONNX \--models fizbuz_package.mar

現(xiàn)在,我們的模型服務(wù)器已啟動(dòng)并在端口 8080 上運(yùn)行(如果您尚未更改端口)。 我們可以嘗試執(zhí)行與 Flask 應(yīng)用相同的curl命令(顯然,我們必須更改端口號(hào))并檢查模型。 我們應(yīng)該獲得與 Flask 應(yīng)用完全相同的結(jié)果,但是現(xiàn)在我們可以根據(jù)需要?jiǎng)討B(tài)地動(dòng)態(tài)擴(kuò)展或縮減工作器的數(shù)量。 MMS 為此提供了管理 API。 管理 API 帶有幾個(gè)可配置的選項(xiàng),但是這里我們只關(guān)注于增加或減少工作器的數(shù)量。

除了在端口 8080 上運(yùn)行的服務(wù)器之外,還將在 8081 上運(yùn)行管理 API 服務(wù),我們可以對(duì)其進(jìn)行調(diào)用和控制配置。 使用簡(jiǎn)單的GET請(qǐng)求命中該端點(diǎn)將為您提供服務(wù)器的狀態(tài)。 但是在探究這一點(diǎn)之前,我們將工作器數(shù)量設(shè)為 1(默認(rèn)情況下為 4)。 API 端點(diǎn)是適當(dāng)?shù)?REST 端點(diǎn); 我們?cè)诼窂街兄付P兔Q,并傳遞參數(shù)max_worker=1以使工作器數(shù)為 1。 我們也可以通過min_worker=<number>來增加工作器數(shù)量。 官方文檔[2]中詳細(xì)介紹了管理 API 上可能的配置。

-> curl -v -X PUT "http://localhost:8081/models/fizbuz_package?max_worker=1"
...
{"status": "Processing worker updates..."
}
...

一旦減少了工作器的數(shù)量,我們就可以命中端點(diǎn)來確定服務(wù)器的狀態(tài)。 示例輸出(在我們減少了工作器數(shù)量之后)如下:

-> curl "http://localhost:8081/models/fizbuz_package"
{"modelName": "fizbuz_package","modelUrl": "fizbuz_package.mar","runtime": "python","minWorkers": 1,"maxWorkers": 1,"batchSize": 1,"maxBatchDelay": 100,"workers": [{"id": "9000","startTime": "2019-02-11T19:03:41.763Z","status": "READY","gpu": false,"memoryUsage": 0}]
}

我們已經(jīng)設(shè)置了模型服務(wù)器,現(xiàn)在我們知道如何根據(jù)比例配置服務(wù)器。 讓我們使用 Locust 對(duì)服務(wù)器進(jìn)行負(fù)載測(cè)試,并檢查服務(wù)器的負(fù)載情況,以及根據(jù)我們的需求增加/減少資源有多容易。 將 AI 模型部署到生產(chǎn)環(huán)境并非易事。

負(fù)載測(cè)試

隨后是示例蝗蟲腳本,應(yīng)將其另存為locust.py在當(dāng)前目錄中。 如果已安裝 Locust(可以使用pip進(jìn)行安裝),則調(diào)用locust將打開 Locust 服務(wù)器并打開 UI,我們可以在其中輸入要測(cè)試的比例尺。 我們可以逐步提高規(guī)模,并檢查服務(wù)器在什么時(shí)候開始崩潰,然后點(diǎn)擊管理 API 以增加工作量并確保我們的服務(wù)器可以容納規(guī)模:

import random
from locust import HttpLocust, TaskSet, taskclass UserBehavior(TaskSet):def on_start(self):self.url = "/predictions/fizbuz_package"self.headers = {"Content-Type": "application/json"}@task(1)def success(self):data = {'input.1': random.randint(0, 1000)}self.client.post(self.url, headers=self.headers, json=data)class WebsiteUser(HttpLocust):task_set = UserBehaviorhost = "http://localhost: 8081"

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-vNQiSfFx-1681786272908)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/pt-dl-handson/img/B09475_08_05.jpg)]

圖 8.5:Locust UI,我們可以在其中配置用戶數(shù)量以模擬生產(chǎn)負(fù)載

TorchScript 的效率

我們已經(jīng)設(shè)置了簡(jiǎn)單的 Flask 應(yīng)用服務(wù)器來為我們的模型提供服務(wù),并且已經(jīng)使用 MXNet 模型服務(wù)器實(shí)現(xiàn)了相同的模型,但是如果我們需要擺脫 Python 的世界,并使用 C++ 或 Go 創(chuàng)建高效的服務(wù)器 ,或使用其他有效的語言,PyTorch 提出了 TorchScript,它可以生成模型中最有效的形式,并且可以在 C++ 中讀取。

現(xiàn)在的問題是:這不是我們對(duì) ONNX 所做的嗎? 也就是說,從 PyTorch 模型創(chuàng)建另一個(gè) IR? 是的,過程相似,但區(qū)別在于 ONNX 使用跟蹤創(chuàng)建了優(yōu)化的 IR; 也就是說,它通過模型傳遞虛擬輸入,并在執(zhí)行模型時(shí)記錄 PyTorch 操作,然后將這些操作轉(zhuǎn)換為中間 IR。

這種方法有一個(gè)問題:如果模型是數(shù)據(jù)相關(guān)的,例如 RNN 中的循環(huán),或者if/else條件是基于輸入的,那么跟蹤就不能真正做到這一點(diǎn)。 跟蹤將僅發(fā)現(xiàn)在特定執(zhí)行周期中發(fā)生的情況,而忽略其他情況。 例如,如果我們的虛擬輸入是 10 個(gè)單詞的句子,而我們的模型是基于循環(huán)的 RNN,則跟蹤的圖將對(duì) RNN 單元的 10 次執(zhí)行進(jìn)行硬編碼,如果句子的長(zhǎng)度大于 10,或者較短的句子帶有更少的單詞,則它將中斷。 考慮到這一點(diǎn)引入了 TorchScript。

TorchScript 支持此類 Python 控制流的一個(gè)子集,唯一要做的就是將現(xiàn)有程序轉(zhuǎn)換為所有控制流都是 TorchScript 支持的控制流的階段。 LibTorch 可以讀取 TorchScript 創(chuàng)建的中間階段。 在此會(huì)話中,我們將創(chuàng)建 TorchScript 輸出并編寫一個(gè) C++ 模塊以使用 LibTorch 加載它。

即使 TorchScript 是 PyTorch 早期版本的 JIT 包的一部分,它仍在 PyTorch 1.0 中引入了可用且穩(wěn)定的 TorchScript 版本。 TorchScript 可以序列化和優(yōu)化用 PyTorch 編寫的模型。

與 ONNX 一樣,TorchScripts 可以作為 IR 保存到磁盤中,但是與 ONNX 不同,該 IR 經(jīng)過優(yōu)化可在生產(chǎn)環(huán)境中運(yùn)行。 保存的 TorchScript 模型可以在不依賴 Python 的環(huán)境中加載。 由于性能和多線程原因,Python 一直是生產(chǎn)部署的瓶頸,即使 Python 可以帶給您的擴(kuò)展能力足以滿足現(xiàn)實(shí)世界中的大多數(shù)使用情況。

避免這種基本的瓶頸是所有可用于生產(chǎn)環(huán)境的框架的主要任務(wù),這就是為什么靜態(tài)計(jì)算圖統(tǒng)治框架世界的原因。 PyTorch 通過引入具有高級(jí) API 的基于 C++ 的運(yùn)行庫(kù)來解決此問題,如果開發(fā)人員希望使用 C++ 進(jìn)行編程,則可以使用這些 API。

通過將 TorchScript 推到核心,PyTorch 可以投入生產(chǎn)了。 TorchScript 可以將用 Python 編寫的模型轉(zhuǎn)換為高度優(yōu)化的 IR,然后可由 LibTorch 讀取。 然后,可以將 LibTorch 加載的模型保存為 C++ 對(duì)象,并可以在 C++ 程序或其他高效編程語言(例如 Go)中運(yùn)行。

PyTorch 允許您通過兩種方法制作 TorchScript IR。 最簡(jiǎn)單的是通過跟蹤,就像 ONNX 一樣。 您可以通過虛擬輸入將模型(甚至函數(shù))傳遞給torch.jit.trace。 PyTorch 通過模型/函數(shù)運(yùn)行虛擬輸入,并在運(yùn)行輸入時(shí)跟蹤操作。

然后,可以將跟蹤的函數(shù)(PyTorch 操作)轉(zhuǎn)換為優(yōu)化的 IR,也稱為靜態(tài)單分配 IR。 像 ONNX 圖一樣,該圖中的指令也具有張量庫(kù)(ATen,PyTorch 的后端)可以理解的原始運(yùn)算符。

這確實(shí)很容易,但是要付出代價(jià)。 基于跟蹤的推理具有 ONNX 的基本問題:它無法處理依賴于數(shù)據(jù)的模型結(jié)構(gòu)更改,即if/else條件檢查或循環(huán)(序列數(shù)據(jù))。 為了處理這種情況,PyTorch 引入了腳本模式。

可以通過使用torch.jit.script裝飾器(用于常規(guī)函數(shù))和torch.jit.script_method(用于 PyTorch 模型上的方法)來啟用腳本模式。 通過此裝飾器,函數(shù)/方法中的內(nèi)容將直接轉(zhuǎn)換為 TorchScript。 在對(duì)模型類使用torch.jit.script_method時(shí)要記住的另一件重要事情是關(guān)于父類。 通常,我們從torch.nn.Module繼承,但是為了制作 TorchScript,我們從torch.jit.ScriptModule繼承。 這有助于 PyTorch 避免使用無法轉(zhuǎn)換為 TorchScript 的純 Python 方法。 目前,TorchScript 不支持所有 Python 函數(shù),但具有支持?jǐn)?shù)據(jù)相關(guān)張量操作的所有必需函數(shù)。

我們將首先將模型導(dǎo)出到ScriptModule IR,以此開始 fizzbuzz 模型的 C++ 實(shí)現(xiàn),就像我們對(duì) ONNX 導(dǎo)出所做的一樣:

net = FizBuzNet(input_size, hidden_size, output_size)
traced = torch.jit.trace(net, dummy_input)
traced.save('fizbuz.pt')

可以通過torch.load()方法將保存的模型加載回 Python,但是我們將使用 C++ 中引入的類似 API LibTorch 將模型加載到 C++。 在討論邏輯之前,讓我們將所需的標(biāo)頭導(dǎo)入當(dāng)前作用域:

#include <torch/script.h>
#include <iostream>
#include <memory>
#include <string>

最重要的頭是torch/script.h,它帶來了 LibTorch 所需的所有方法和函數(shù)。 我們決定將模型名稱和示例輸入作為命令行參數(shù)傳遞。 因此,主程序的第一部分是讀取命令行參數(shù)并將其解析為程序的其余部分:

std::string arg = argv[2];
int x = std::stoi(arg);
float array[10];int i;
int j = 9;
for (i = 0; i < 10; ++i) {array[j] = (x >> i) & 1;j--;
}

程序讀取第二個(gè)命令行參數(shù),這是用戶給出的用于獲取預(yù)測(cè)的編號(hào)。 從命令行讀取時(shí),該數(shù)字為string類型。 我們將其轉(zhuǎn)換為int。 對(duì)于stringint轉(zhuǎn)換后的循環(huán),我們需要將其轉(zhuǎn)換為二進(jìn)制數(shù)組。 這是 LibTorch 執(zhí)行開始的地方:

std::shared_ptr<torch::jit::script::Module> module = torch::jit::load(argv[1]);
auto options = torch::TensorOptions().dtype(torch::kFloat32);
torch::Tensor tensor_in = torch::from_blob(array, {1, 10},options);
std::vector<torch::jit::IValue> inputs;
inputs.push_back(tensor_in);
at::Tensor output = module->forward(inputs).toTensor();

在第一行中,我們從路徑加載模型,該路徑作為第一個(gè)命令行參數(shù)傳遞(我們將變量聲明為ScriptModule)。 在第三行,我們使用from_blob方法將二進(jìn)制數(shù)組轉(zhuǎn)換為二維 LibTorch 張量。 在最后一行,我們使用我們制作的張量執(zhí)行模型的forward方法,并將輸出返回給用戶。 這可能是我們可以實(shí)現(xiàn)以展示 TorchScript 實(shí)際操??作的最基本示例。 官方文檔中有許多示例,它們顯示了腳本模式(與跟蹤模式不同)的功能,可以理解 Python 控制流并將模型推向 C++ 世界。

探索 RedisAI

我們已經(jīng)看到可以通過 TorchScript 獲得的優(yōu)化,但是優(yōu)化的二進(jìn)制文件將如何處理? 是的,我們可以在 C++ 世界中加載它,并制作 Go 服務(wù)器,然后在其中加載它,但這仍然很痛苦。

Redis Labs 和 Orobix 為我們帶來了另一個(gè)名為 RedisAI 的解決方案。 它是基于 LibTorch 構(gòu)建的高度優(yōu)化的運(yùn)行時(shí),可以接受已編譯的 TorchScript 二進(jìn)制文件,以通過 Redis 協(xié)議提供服務(wù)。 對(duì)于沒有 Redis 經(jīng)驗(yàn)的人, 這里有很好的文檔,那里的介紹文檔[3]應(yīng)該是一個(gè)好的開始。

RedisAI 帶有三個(gè)選項(xiàng)來配置三個(gè)后端:PyTorch,TensorFlow 和 ONNX 運(yùn)行時(shí)。 它并不僅限于此:RedisAI 在后端使用 DLPack 來使張量能夠通過不同的框架,而無需花費(fèi)很多轉(zhuǎn)換成本。

那有什么意思? 假設(shè)您有一個(gè) TensorFlow 模型,該模型將人臉轉(zhuǎn)換為 128 維嵌入(這是 FaceNet 所做的)。 現(xiàn)在,您可以使 PyTorch 模型使用此 128 維嵌入進(jìn)行分類。 在正常情況下,將張量從 TensorFlow 傳遞到 PyTorch 需要深入了解事物在幕后的工作方式,但是使用 RedisAI,您可以使用幾個(gè)命令來完成。

RedisAI 是作為 Redis 服務(wù)器(loadmodule開關(guān))的模塊構(gòu)建的。 通過 RedisAI 提供模型的好處不僅在于擁有多個(gè)運(yùn)行時(shí)以及它們之間的互操作性。 實(shí)際上,這對(duì)于生產(chǎn)部署來說是最不重要的。 RedisAI 附帶的最重要的功能是故障轉(zhuǎn)移和分布式部署選項(xiàng)已經(jīng)嵌入到 Redis 服務(wù)器中。

借助 Redis Sentinel 和 Redis Cluster,我們可以在多集群,高可用性設(shè)置中部署 RedisAI,而無需對(duì) DevOps 或基礎(chǔ)架構(gòu)建設(shè)有足夠的了解。 另外,由于 Redis 擁有所有流行語言的客戶端,因此,通過 RedisAI 部署 TorchScript 模型后,您基本上可以使用 Redis 的任何語言客戶端與服務(wù)器通信以運(yùn)行模型,將輸入傳遞給模型,從模型獲取輸出,以及更多。

使用 RedisAI 的下一個(gè)亮點(diǎn)是 Redis 整個(gè)大型生態(tài)系統(tǒng)的可用性,例如 RedisGears(可將任何 Python 函數(shù)作為管道的一部分運(yùn)行),RedisTimeSeries,Redis Streams 等。

讓我們開始將使用 TorchScript 編譯的 fizzbuzz 網(wǎng)絡(luò)模型加載到 RedisAI。 首先,我們需要安裝 Redis 服務(wù)器和 RedisAI 來設(shè)置環(huán)境。 installation.sh文件包含三個(gè)部分來執(zhí)行此操作:

sudo apt update
sudo apt install -y build-essential tcl libjemalloc-dev
sudo apt install -y git cmake unzipcurl -O http://download.redis.io/redis-stable.tar.gz
tar xzvf redis-stable.tar.gz
cd redis-stable
make
sudo make install
cd ~
rm redis-stable.tar.gzgit clone https://github.com/RedisAI/RedisAI.git
cd RedisAl
bash get_deps.sh cpu
mkdir build
cd build
cmake -DDEPS_PATH=../deps/install ..
make
cd ~

第一部分是我們安裝所需依賴項(xiàng)的位置。 第二部分是我們下載 Redis 服務(wù)器二進(jìn)制文件并進(jìn)行安裝的地方。 第三部分是克隆 RedisAI 服務(wù)器并使用make進(jìn)行構(gòu)建。 安裝完成后,我們可以運(yùn)行run_server.sh文件以將 RedisAI 作為已加載的模塊來構(gòu)建 Redis 服務(wù)器。

cd redis-stable
redis-server redis.conf --loadmodule ../RedisAI/build/redisai.so

現(xiàn)在,我們的 Redis 服務(wù)器已全部就緒。 設(shè)置 RedisAI 服務(wù)器就這么簡(jiǎn)單。 現(xiàn)在,使用 Sentinel 或 Cluster 對(duì)其進(jìn)行擴(kuò)展也并不可怕。 官方文檔具有足夠的信息供您入門。

在這里,我們從最小的 Python 腳本開始,以使用 RedisAI 運(yùn)行 fizzbuzz 示例。 我們正在使用 Python 包Redis與 Redis 服務(wù)器通信。 RedisAI 已經(jīng)建立了一個(gè)正式的客戶端,但是在撰寫本文時(shí)還不能使用它。

r = redis.Redis()
MODEL_PATH = 'fizbuz_model.pt'
with open(MODEL_PATH,'rb') as f:model_pt = f.read()
r.execute_command('AI.MODELSET', 'model', 'TORCH', 'CPU',model_pt)

上面的腳本首先打開與本地主機(jī)的 Redis 連接。 它讀取以前使用 TorchScript 保存的二進(jìn)制模型,并使用命令AI.MODELSET在 RedisAI 中設(shè)置 Torch 模型。 該命令需要我們?yōu)榉?wù)器中的模型傳遞所需的名稱,無論是要使用 CPU 還是 GPU,我們都想使用該后端,然后是二進(jìn)制模型文件本身。 模型設(shè)置命令返回一條正常消息,然后循環(huán)瀏覽并等待用戶輸入。 如前所述,用戶輸入通過編碼器傳遞,以將其轉(zhuǎn)換為二進(jìn)制編碼格式。

while True:number = int(input('Enter number, press CTRL+c to exit: ')) + 1inputs = encoder(number)r.execute_command('AI. TENSORSET', 'a', 'FLOAT', *inputs.shape, 'BLOB',inputs.tobytes())r.execute_command('AI.MODELRUN', 'model', 'INPUTS', 'a','OUTPUTS', 'out')typ, shape, buf = r.execute_command('AI.TENSORGET', 'out','BLOB')prediction = np.frombuffer(buf, dtype=np.float32).argmax()print(get_readable_output(number, prediction))

然后,我們使用AI.TENSORSET來設(shè)置張量并將其映射到關(guān)鍵點(diǎn)。 您可能已經(jīng)看到了我們將輸入 NumPy 數(shù)組傳遞給后端的方式。 NumPy 有一個(gè)方便的函數(shù)tobytes(),它給出了如何將數(shù)據(jù)存儲(chǔ)在內(nèi)存中的字符串格式。 我們明確告訴命令我們需要將模型另存為BLOB。 保存模型的另一個(gè)選項(xiàng)是VALUES,當(dāng)您要保存更大的數(shù)組時(shí),它不是很有用。

我們還必須傳遞數(shù)據(jù)類型和輸入張量的形狀。 做張量集時(shí),我們應(yīng)該考慮的一件事是數(shù)據(jù)類型和形狀。 由于我們將輸入作為緩沖區(qū)傳遞,因此 RedisAI 嘗試使用我們傳遞的形狀和數(shù)據(jù)類型信息將緩沖區(qū)轉(zhuǎn)換為 DLPack 張量。 如果這與我們傳遞的字節(jié)串的長(zhǎng)度不匹配,RedisAI 將拋出錯(cuò)誤。

設(shè)置張量后,我們將模型保存在名為model的鍵中,并將張量保存在名為a的鍵中。 現(xiàn)在,我們可以通過傳遞模型鍵名稱和張量鍵名稱來運(yùn)行AI.MODELRUN命令。

如果有多個(gè)輸入要傳遞,我們將使用張量集不止一次,并將所有鍵作為INPUTS傳遞給MODELRUN命令。 MODELRUN命令將輸出保存到OUTPUTS下提到的鍵,然后AI.TENSORGET可以讀取。

在這里,我們像保存了一樣將張量讀為BLOB。 張量命令為我們提供類型,形狀和自身的緩沖。 然后將緩沖區(qū)傳遞給 NumPy 的frombuffer()函數(shù),該函數(shù)為我們提供了結(jié)果的 NumPy 數(shù)組。

一旦我們從 RedisAI 中獲得了數(shù)據(jù),那么其他章節(jié)中的內(nèi)容將相同。 RedisAI 似乎是當(dāng)前市場(chǎng)上可用于 AI 開發(fā)人員的最有前途的生產(chǎn)部署系統(tǒng)。 它甚至還處于早期階段,并于 4 月在 RedisConf 2019 上發(fā)布。 我們可以在不久的將來看到 RedisAI 帶來的許多驚人功能,這使其成為大部分 AI 社區(qū)事實(shí)上的部署機(jī)制。

總結(jié)

在本章中,我們從最簡(jiǎn)單但性能最低的方法開始,使用了三種不同的方法將 PyTorch 投入生產(chǎn):使用 Flask。 然后,我們轉(zhuǎn)移到 MXNet 模型服務(wù)器,這是一個(gè)預(yù)先構(gòu)建的,優(yōu)化的服務(wù)器實(shí)現(xiàn),可以使用管理 API 進(jìn)行管理。 MXNet 模型服務(wù)器對(duì)不需要太多復(fù)雜性但需要可以根據(jù)需要擴(kuò)展的高效服務(wù)器實(shí)現(xiàn)的人很有用。

最后,我們嘗試使用 TorchScript 創(chuàng)建模型的最有效版本,并將其導(dǎo)入 C++ 中。 對(duì)于那些準(zhǔn)備承擔(dān)構(gòu)建和維護(hù) C++,Go 或 Rust 等底層語言服務(wù)器的復(fù)雜性的人,可以采用這種方法并構(gòu)建自定義服務(wù)器,直到我們有可以讀取腳本模塊的更好的運(yùn)行時(shí)為止,就像 MXNet 在 ONNX 模型上一樣。

2018 年是模型服務(wù)器的一年; 有許多來自不同組織的模型服務(wù)器,它們具有不同的觀點(diǎn)。 但是未來是光明的,我們可以看到越來越多的模型服務(wù)器每天都在問世,這可能會(huì)使所有前面提到的方法過時(shí)。

參考

  1. https://pytorch.org/docs/stable/jit.html
  2. https://github.com/awslabs/mxnet-model-server/blob/master/docs/management_api.md
  3. https://redis.io/topics/introduction
http://www.risenshineclean.com/news/46356.html

相關(guān)文章:

  • 專門教做甜品的網(wǎng)站微信營(yíng)銷軟件手機(jī)版
  • 建設(shè)網(wǎng)站如何贏利企業(yè)網(wǎng)站cms
  • 廣州 網(wǎng)站開發(fā) 公司電話百度seo優(yōu)化排名
  • dw企業(yè)網(wǎng)站設(shè)計(jì)品牌營(yíng)銷包括哪些內(nèi)容
  • 網(wǎng)站加載效果怎么做的百度推廣代運(yùn)營(yíng)
  • wordpress mysql重啟資源網(wǎng)站優(yōu)化排名軟件公司
  • 成華網(wǎng)站制作為什么中國(guó)禁止谷歌瀏覽器
  • 標(biāo)題優(yōu)化方法郴州seo快速排名
  • 今日濮陽重大新聞seo優(yōu)化服務(wù)是什么意思
  • asp.net做的網(wǎng)站要放到網(wǎng)上空間去_要放哪些文件上去網(wǎng)站建網(wǎng)站建設(shè)網(wǎng)站
  • 房山新農(nóng)村建設(shè)網(wǎng)站深圳百度seo公司
  • 免費(fèi)代理ip的網(wǎng)站百度搜索推廣操作簡(jiǎn)要流程
  • 如何進(jìn)行網(wǎng)站運(yùn)營(yíng)與規(guī)劃打開百度網(wǎng)頁
  • 國(guó)外做批發(fā)配件的 在哪個(gè)網(wǎng)站百度葷seo公司
  • 傳奇網(wǎng)站怎么制作教程查關(guān)鍵詞
  • windows做網(wǎng)站服務(wù)器杭州推廣公司
  • 什么是網(wǎng)站功能需求推推蛙品牌策劃
  • 有服務(wù)器可以做網(wǎng)站嗎站長(zhǎng)工具是什么意思
  • 商業(yè)網(wǎng)站建立搜索引擎優(yōu)化seo應(yīng)用
  • 中國(guó)三農(nóng)建設(shè)工作委員會(huì)官方網(wǎng)站深圳網(wǎng)絡(luò)推廣最新招聘
  • 云服務(wù)器可以做網(wǎng)站嗎網(wǎng)絡(luò)營(yíng)銷的一般流程
  • 知名企業(yè)網(wǎng)站建設(shè)哈爾濱網(wǎng)站制作軟件
  • 做鋼材的網(wǎng)站有哪些網(wǎng)站的網(wǎng)站建設(shè)
  • 個(gè)人網(wǎng)站畢業(yè)設(shè)計(jì)搜索關(guān)鍵詞然后排名怎樣提升
  • 國(guó)外兒童社區(qū)網(wǎng)站模板外鏈信息
  • 做微網(wǎng)站迅宇科技網(wǎng)店推廣是什么
  • 做網(wǎng)站的不給做robots文件百度推廣登錄后臺(tái)
  • 在百度上做網(wǎng)站多少錢百度收錄提交
  • 杭州旅游 網(wǎng)站建設(shè)必應(yīng)搜索引擎地址
  • 一個(gè)網(wǎng)站可以做多少個(gè)小程序營(yíng)銷推廣方案