達州住房和城鄉(xiāng)建設廳網(wǎng)站中國十大熱門網(wǎng)站排名
原文:Intelligent Projects Using Python
協(xié)議:CC BY-NC-SA 4.0
譯者:飛龍
本文來自【ApacheCN 深度學習 譯文集】,采用譯后編輯(MTPE)流程來盡可能提升效率。
不要擔心自己的形象,只關心如何實現(xiàn)目標。——《原則》,生活原則 2.3.c
一、人工智能系統(tǒng)的基礎
人工智能(AI)在過去幾年中一直處于技術(shù)的最前沿,并已進入主流應用,例如專家系統(tǒng),移動設備上的個性化應用, 自然語言處理中的機器翻譯,聊天機器人,自動駕駛汽車等。 但是,AI 的定義在很長一段時間以來一直是一個爭論的主題。 這主要是因為所謂的 AI 效應將過去已經(jīng)通過 AI 解決的工作歸類為非 AI。 根據(jù)一位著名的計算機科學家的說法:
智能是機器尚未完成的一切。
–拉里·特斯勒(Larry Tesler)
在 1996 年 IBM 電腦 Deep Blue 擊敗 Gary Kasparov 之前,一直認為構(gòu)建能下象棋的智能系統(tǒng)是 AI。類似地,曾經(jīng)將視覺,言語和自然語言方面的問題視為復雜問題,但是由于 AI 的影響,它們現(xiàn)在僅被視為計算而非真正的 AI。 近來,人工智能已經(jīng)能夠解決復雜的數(shù)學問題,創(chuàng)作音樂和創(chuàng)作抽象繪畫,并且人工智能的這些功能正在不斷增加。 科學家將 AI 系統(tǒng)在未來等同于人類智能水平的點稱為 AI 奇點。 機器是否會真正達到人類的智能水平這個問題非常令人著迷。
許多人會認為機器永遠無法達到人類的智能水平,因為用來學習或執(zhí)行智能任務的 AI 邏輯是由人類編程的,并且它們?nèi)狈θ祟愃鶕碛械囊庾R和自我意識。 但是,一些研究人員提出了另一種想法,即人類意識和自我意識就像無限循環(huán)程序,可以通過反饋從周圍的環(huán)境中學習。 因此,也有可能將意識和自我意識編程到機器中。 但是,就目前而言,我們將把 AI 的這一哲學方面再留一天,并簡單地討論我們所知道的 AI。
簡而言之,AI 可以定義為機器(通常是計算機或機器人)以類人的智力執(zhí)行任務的能力,例如具有推理,學習經(jīng)驗,歸納,破譯含義和擁有的能力等屬性。 視覺感知。 我們將堅持這個更實際的定義,而不是關注 AI 效應所帶來的哲學內(nèi)涵以及 AI 奇異性的前景。 盡管可能存在關于 AI 可以實現(xiàn)和不能實現(xiàn)的爭論,但基于 AI 的系統(tǒng)的最新成功故事卻令人矚目。 下圖描述了 AI 的一些較新的主流應用:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-hORVU4dY-1681653992465)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/623d9065-122c-4066-bfed-634f7ac3cd75.png)]
圖 1.1:人工智能的應用
本書將涵蓋來自 AI 的所有核心學科的項目的詳細實現(xiàn),概述如下:
- 基于遷移學習的 AI 系統(tǒng)
- 基于自然語言的 AI 系統(tǒng)
- 基于生成對抗網(wǎng)絡(GAN)的應用
- 專家系統(tǒng)
- 視頻到文本翻譯應用
- 基于 AI 的推薦系統(tǒng)
- 基于 AI 的移動應用
- 基于 AI 的聊天機器人
- 強化學習應用
在本章中,我們將簡要介紹實現(xiàn)機器學習和深度學習的概念,這些概念是實現(xiàn)以下各章中涉及的項目所必需的。
神經(jīng)網(wǎng)絡
神經(jīng)網(wǎng)絡是受人腦啟發(fā)的機器學習模型。 它們由神經(jīng)處理單元組成,它們以分層的方式相互連接。 這些神經(jīng)處理單元稱為人工神經(jīng)元,它們在人腦中執(zhí)行與軸突相同的功能。 在人腦中,樹突接收來自鄰近神經(jīng)元的輸入,并在將輸入傳輸?shù)缴窠?jīng)元的體細胞之前減弱或放大輸入。 在神經(jīng)元的軀體中,這些修飾的信號被加在一起并傳遞到神經(jīng)元的軸突。 如果軸突的輸入超過指定的閾值,則信號將傳遞到相鄰神經(jīng)元的樹突。
人工神經(jīng)元松散地工作可能與生物神經(jīng)元在相同的邏輯上起作用。 它接收來自鄰近神經(jīng)元的輸入。 通過神經(jīng)元的輸入連接來縮放輸入,然后將它們加在一起。 最后,求和的輸入通過激活函數(shù)傳遞,該函數(shù)的輸出傳遞到下一層的神經(jīng)元。
下圖說明了生物神經(jīng)元和人工神經(jīng)元,以進行比較:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Aa6arHAs-1681653992466)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/6527d133-7918-4ab3-96e8-36fa4181c4dc.png)]
圖 1.2:生物神經(jīng)元
下圖說明了人工神經(jīng)元:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-9f2bukQd-1681653992467)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/ed578913-a219-4604-be04-530487b57afa.png)]
圖 1.3:人工神經(jīng)元
現(xiàn)在,讓我們看一下人工神經(jīng)網(wǎng)絡的結(jié)構(gòu),如下圖所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZlRfPnxB-1681653992467)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/b767da6e-2a08-4790-8e59-3081c2ab9651.png)]
圖 1.4:人工神經(jīng)網(wǎng)絡
輸入x ∈ R^N
穿過神經(jīng)單元的連續(xù)層,這些層以分層方式排列。 特定層中的每個神經(jīng)元都接收來自先前層的神經(jīng)元的輸入,這些輸入被它們之間的連接權(quán)重所衰減或放大。 權(quán)重w[ij]^(l)
對應于l
層第i
個神經(jīng)元與l + 1
層第j
個神經(jīng)元。 同樣,每個神經(jīng)元單元i
在特定層1
中都有一個偏置b[i]^(l)
。 神經(jīng)網(wǎng)絡為輸入向量x ∈ R^N
預測輸出y_hat
。 如果數(shù)據(jù)的實際標簽是y
,其中y
取連續(xù)值,則神經(jīng)元網(wǎng)絡將預測誤差最小化(y - y_hat)^2
來學習權(quán)重和偏差。 當然,對于所有標記的數(shù)據(jù)點,必須將誤差最小化:(xi, yi), i ∈ 1, 2, ..., m
。
如果我們用一個公共向量W
表示一組權(quán)重和偏差,并且預測中的總誤差由C
表示,則在訓練過程中,估計的W
可以表示為:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-JqsHEvIU-1681653992468)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/973e52ab-5ebb-40a0-941c-20665d118f10.png)]
同樣,預測輸出y_hat
可以由輸入x
的函數(shù)表示,并由權(quán)重向量W
進行參數(shù)化,如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-P8reOrtv-1681653992468)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/9e08ed5e-7e12-46d1-a2cb-a2e343d9c8d2.png)]
這種用于預測輸出連續(xù)值的公式稱為回歸問題。
對于兩類二分類,交叉熵損失最小,而不是平方誤差損失,并且網(wǎng)絡輸出正類的概率而不是輸出。 交叉熵損失可以表示為:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-HmQaswLT-1681653992468)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/c0fb9371-3b94-4ffa-9443-f51299bb5cb8.png)]
此處,p[i]
是給定輸入x
的輸出類別的預測概率,并且可以表示為輸入x
的函數(shù)。由權(quán)重向量參數(shù)化,如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Z6x7kCN0-1681653992469)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/90eddcaa-84e7-46c8-8b42-d34e3fd7ef91.png)]
通常,對于多類別分類問題(例如n
類),交叉熵損失可通過以下方式給出:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-CHT2Av4g-1681653992469)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/b8e45aea-221b-44e3-bbc3-0d9e88a4cf2d.png)]
在這里,y[i]^(j)
是第i
個數(shù)據(jù)點的第j
類的輸出標簽。
神經(jīng)激活單元
取決于架構(gòu)和當前的問題,在神經(jīng)網(wǎng)絡中使用了幾種神經(jīng)激活單元。 我們將討論最常用的激活函數(shù),因為它們在確定網(wǎng)絡架構(gòu)和表現(xiàn)方面起著重要作用。 線性和 Sigmoid 單元激活函數(shù)主要用于人工神經(jīng)網(wǎng)絡,直到 Hinton 等人發(fā)明的整流線性單元(ReLUs)徹底改變了神經(jīng)網(wǎng)絡的表現(xiàn)。
線性激活單元
線性激活單元將總輸入輸出到衰減的神經(jīng)元,如下圖所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-w0OzUxtx-1681653992469)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/d1c95a3f-f648-44c6-88be-744da68e0aee.png)]
圖 1.5:線性神經(jīng)元
如果x
是線性激活單元的總輸入,則輸出y
可以表示如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-l3A5Jn0N-1681653992470)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/7db1c93b-6ac7-4b2c-b464-dd69836c6854.png)]
Sigmoid 激活單元
Sigmoid 激活單元,y
的輸出是其總輸入x
的函數(shù),表示如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Bq7DSl5f-1681653992470)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/e72ceed8-eaaa-4d3e-bc39-256e36d389e2.png)]
由于 Sigmoid 激活單元響應是非線性函數(shù),如下圖所示,它可用于在神經(jīng)網(wǎng)絡中引入非線性:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-H3o5Suub-1681653992470)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/f54ce333-0cdb-400f-b9d1-22d60475291f.png)]
圖 1.6:Sigmoid 激活函數(shù)
自然界中任何復雜的過程通常在輸入輸出關系上都是非線性的,因此,我們需要非線性激活函數(shù)通過神經(jīng)網(wǎng)絡對其進行建模。 兩類分類的神經(jīng)網(wǎng)絡的輸出概率通常由 Sigmoid 神經(jīng)單元的輸出給定,因為它的輸出值從零到一。 輸出概率可以表示如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Cg6Zf2uo-1681653992470)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/4e502901-d686-46cf-ba1c-e0b11d2c2707.png)]
在這里,x
代表輸出層中 Sigmoid 單元的總輸入量。
雙曲正切激活函數(shù)
給出了雙曲正切激活函數(shù)(tanh)的輸出y
作為其總輸入的函數(shù)x
如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZG9VpHxc-1681653992471)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/ff09283d-c4ae-42d3-8b20-5c6cb9523899.png)]
tanh 激活函數(shù)輸出的值在[-1, 1]
范圍內(nèi),如下圖所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-dktR3vy1-1681653992482)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/58f98f0c-6a81-4acc-bafb-9b0f683ad9c9.png)]
圖 1.7:Tanh 激活函數(shù)
需要注意的一件事是 Sigmoid 和 tanh 激活函數(shù)在很小的輸入范圍內(nèi)都是線性的,超過該范圍輸出就會飽和。 在飽和區(qū),激活函數(shù)(相對于輸入)的梯度非常小或接近零; 這意味著它們非常容易消失梯度問題。 正如您將在后面看到的那樣,神經(jīng)網(wǎng)絡將從反向傳播方法中學習,在該方法中,層的梯度取決于后續(xù)層中直至最終輸出層中激活單元的梯度。 因此,如果激活單元中的單元在飽和區(qū)域中工作,則將更少的誤差反向傳播到神經(jīng)網(wǎng)絡的早期層。 神經(jīng)網(wǎng)絡通過利用梯度來最小化預測誤差,以學習權(quán)重和偏差(W
)。 這意味著,如果梯度很小或消失為零,則神經(jīng)網(wǎng)絡將無法正確學習這些權(quán)重。
整流線性單元(ReLU)
當神經(jīng)元的總輸入大于零時,ReLU 的輸出為線性,而當神經(jīng)元的總輸入為負時,ReLU 的輸出為零。 這個簡單的激活函數(shù)為神經(jīng)網(wǎng)絡提供了非線性,同時,它相對于總輸入提供了一個恒定的梯度。 這個恒定的梯度有助于防止神經(jīng)網(wǎng)絡出現(xiàn)飽和或消失的梯度問題,如激活函數(shù)(如 Sigmoid 和 tanh 激活單元)所示。 ReLU 函數(shù)輸出(如圖“圖 1.8”所示)可以表示如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-FPRC1BGK-1681653992482)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/f3c8b630-6ebc-4f71-903a-d192d27621f7.png)]
ReLU 激活函數(shù)可以繪制如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-uOmPDWkN-1681653992483)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/56ed1747-01f7-48fa-9a00-db7d1b05a8f9.png)]
圖 1.8:ReLU 激活函數(shù)
ReLU 的限制條件之一是其輸入負值的零梯度。 這可能會減慢訓練速度,尤其是在初始階段。 LReLU 激活函數(shù)(如圖 1.9 所示)在這種情況下非常有用,即使輸出和梯度不為零,即使輸入為負值。 ReLU 輸出函數(shù)泄漏可以表示如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-S4gi2H4B-1681653992483)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/048015a0-395a-46f0-bf04-524539aea052.png)]
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-uzFolLb7-1681653992483)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/66329819-1528-4067-afc8-bf3e505ecb09.png)]
將為 LReLU 激活函數(shù)提供α
參數(shù),而對于參數(shù) ReLU,α
是神經(jīng)網(wǎng)絡將通過訓練學習的參數(shù)。 下圖顯示了 LReLU 激活函數(shù)的輸出:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-RjfbfOa5-1681653992483)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/908a6fec-a34a-4e27-a0a4-27dd3b5d84cc.png)]
圖 1.9:LReLU 激活函數(shù)
softmax 激活單元
在多類別分類問題的情況下, softmax 激活單元通常用于輸出類別概率。 假設我們正在處理n
類分類問題,并且與這些類相對應的總輸入如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-yu30DXdv-1681653992483)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/1e6529dc-c80f-4914-b540-4a7a23382feb.png)]
在這種情況下,softmax 激活單元的第k
類的輸出概率由以下公式給出:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-mcGlA4Kd-1681653992484)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/2c4f3f2d-fb61-4823-bad9-632d16200fe2.png)]
還有其他幾種激活函數(shù),大多數(shù)是這些基本版本的變體。 我們將在以下各章介紹的不同項目中討論它們時,對它們進行討論。
訓練神經(jīng)網(wǎng)絡的反向傳播方法
在反向傳播方法中,神經(jīng)網(wǎng)絡通過梯度下降技術(shù)進行訓練,其中組合權(quán)重向量W
迭代更新,如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-bmuexJBs-1681653992484)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/80a9c3b5-27aa-462b-a309-dec4fdbf49cb.png)]
這里,η
是學習率, W^(t + 1)
和W^(t)
分別是迭代t + 1
和t
時的權(quán)重向量,?C(W^(t))
是迭代t
時的成本函數(shù)或誤差函數(shù)對于權(quán)重向量W
的梯度。 先前由w ∈ W
概括的單個權(quán)重或偏差的算法可以表示為:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Xc2rEHFh-1681653992484)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/2a2d7a65-e5d6-4570-8400-3895193259bb.png)]
從前面的表達式中可以得出,梯度下降學習方法的核心在于針對每個權(quán)重計算成本函數(shù)或誤差函數(shù)的梯度。
從微分的鏈式規(guī)則中,我們知道如果我們有 y = f(x), z = f(y)
,那么以下是正確的:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Nrl36B3Q-1681653992484)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/c53a0c79-d20d-4f73-9d61-f2f8e93e8f00.png)]
該表達式可以推廣為任意數(shù)量的變量。 現(xiàn)在,讓我們看一個非常簡單的神經(jīng)網(wǎng)絡,如下圖所示,以了解反向傳播算法:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-EI6rXhcX-1681653992485)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/98bc8f7e-872a-46ef-a018-c9ad01d911f8.png)]
圖 1.10:說明反向傳播的網(wǎng)絡
令網(wǎng)絡輸入為二維向量, x = [x1, x2]^T
, 輸出標簽和預測分別為 [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-0kiIoJeW-1681653992485)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/df5f56eb-23da-40f8-900d-1da3eecfb2d3.png)] 和 [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-aviFHxTN-1681653992485)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/b3cffb09-935e-4bc2-a6d1-330919797695.png)] 。 另外,我們假設神經(jīng)網(wǎng)絡中的所有激活單元都是 Sigmoid。 讓廣義權(quán)重將層l-1
中的任何單元i
連接到層l
中的單元j
表示為 [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-V8aKG1ZB-1681653992485)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/29efb244-a597-41d7-baee-9826abbacd0b.png)] ,而l
層中任何單元i
的偏置應表示為 [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Yvhqi7My-1681653992486)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/99d2725a-1b33-4aea-a0b1-7631b21edbc1.png)] 。 讓我們得出一個數(shù)據(jù)點的梯度; 總梯度可以計算為訓練(或小批量)中使用的所有數(shù)據(jù)點的總和。 如果輸出是連續(xù)的,則可以選擇損失函數(shù)C
作為預測誤差的平方:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-qc99igvJ-1681653992486)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/bb6a924b-9414-4008-b7a8-ee6cc0d11649.png)]
可以通過將相對于W
向量的成本函數(shù)最小化來確定由集合W
表示的網(wǎng)絡的權(quán)重和偏差,如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-YFX6SHpO-1681653992486)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/7de557d6-4617-45f4-8e7f-aeb5523343d5.png)]
為了通過梯度下降迭代地執(zhí)行成本函數(shù)的最小化,我們需要針對每個權(quán)重計算成本函數(shù)的梯度w ∈ W
,如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-gB7GxHNr-1681653992486)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/c4b40a1e-3fc2-4c2c-b5be-1d20c9b34f17.png)]
現(xiàn)在我們有了所需的一切,讓我們計算成本函數(shù)C
相對于權(quán)重 [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-l5URAIhr-1681653992486)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/8ef28fd7-2ae7-4473-bfcd-9020f9bb8903.png)] 的梯度。 使用差分的鏈式規(guī)則,我們得到以下信息:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-CG1dH99x-1681653992487)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/0b0d48f2-769e-4477-bc38-ba07495015a2.png)]
現(xiàn)在讓我們看下面的公式:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-APMV9m59-1681653992487)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/8c25c79b-a37b-455f-95cc-e0501608846b.png)]
正如您在前面的表達式中看到的那樣,導數(shù)不過是預測中的誤差。 通常,在存在回歸問題的情況下,輸出單元激活函數(shù)是線性的,因此適用以下表達式:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-3ws5Qsrs-1681653992487)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/714abd7d-0c22-40db-80f2-bfc0e479ef6a.png)]
因此,如果我們要計算成本函數(shù)相對于輸出單元總輸入的梯度,則為?C/?s[1]^(3)
。 這仍然等于輸出預測中的誤差。
根據(jù)輸入權(quán)重和激活,輸出單元上的總輸入可以表示為:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-MCFBo4Tk-1681653992487)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/153c33ee-f998-4650-be8d-a77f9ef8ffbe.png)]
這意味著?s[1]^(3)/?w[21]^(3) = z[2]^(3)
以及成本函數(shù)相對于權(quán)重w[21]^(3)
的導數(shù)通過以下方式得出:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-pAZnpRvL-1681653992487)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/34c1605c-eb43-4d09-b029-b8177ecd1dc6.png)]
如您所見,相對于最終輸出層之前的層中的權(quán)重,該誤差在計算成本函數(shù)的梯度時反向傳播。 當我們計算成本函數(shù)相對于廣義權(quán)重的梯度w[jk]^(2)
時,這變得更加明顯。 取對應于j = 1
和k = 2
的權(quán)重; 即w[jk]^(2)
。 成本函數(shù)C
相對于該權(quán)重的梯度可以表示為:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4UL9Pc2c-1681653992488)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/474751ea-71f8-4c57-9d16-1679ed2fbea5.png)]
現(xiàn)在,?s[2]^(2)/?w[12]^(2) = z[1]^(2)
表示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-h4VpsziP-1681653992488)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/d5f34043-fdeb-48ca-bf1b-d1a1915ba77d.png)]
因此,一旦我們確定了成本函數(shù)相對于神經(jīng)元總輸入的梯度為?C/?s
,則任何權(quán)重的梯度w
貢獻了總輸入量s
,可以通過簡單地乘以與權(quán)重相關的激活z
來獲得。
現(xiàn)在,成本函數(shù)相對于總輸入的梯度s[2]^(2)
可以再次通過鏈式法則得出,如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-84Gyc7zq-1681653992488)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/ff5d7314-4a37-458c-933b-25a883f492bd.png)]
由于神經(jīng)網(wǎng)絡的所有單元(輸出單元除外)均為 Sigmoid 激活函數(shù),因此情況如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-PJpkobLk-1681653992488)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/665d5aaa-1fa7-4ab0-a7a8-5961d7faaf77.png)]
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-dQxHDysg-1681653992489)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/b43669e7-d10c-4ad9-bc67-b404ec992de1.png)]
結(jié)合(1), (2), (3)
,我們得到以下信息:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-IxfJEsf0-1681653992489)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/ed1832d2-e65e-4568-8594-f5ded856cfe3.png)]
在前面的派生梯度表達式中,您可以看到預測誤差(y_hat - y)
通過與相關的激活和權(quán)重(根據(jù)微分鏈規(guī)則)組合以計算每一層權(quán)重的梯度而向后傳播 ,因此是 AI 術(shù)語中的反向傳播名稱。
卷積神經(jīng)網(wǎng)絡
卷積神經(jīng)網(wǎng)絡(CNN)利用卷積運算從具有關聯(lián)拓撲的數(shù)據(jù)中提取有用信息。 這最適合圖像和音頻數(shù)據(jù)。 輸入圖像在通過卷積層時會生成多個輸出圖像,稱為輸出特征映射。 輸出特征映射將檢測特征。 初始卷積層中的輸出特征映射可以學習檢測基本特征,例如邊緣和顏色成分變化。
第二卷積層可以檢測到稍微復雜的特征,例如正方形,圓形和其他幾何結(jié)構(gòu)。 隨著神經(jīng)網(wǎng)絡的發(fā)展,卷積層學會了檢測越來越復雜的特征。 例如,如果我們有一個 CNN 可以對圖像是貓還是狗進行分類,則神經(jīng)網(wǎng)絡底部的卷積層可能會學會檢測諸如頭部,腿部等特征。
“圖 1.11”顯示了 CNN 的架構(gòu)圖,該 CNN 處理貓和狗的圖像以對其進行分類。 圖像通過卷積層,該卷積層有助于檢測相關特征,例如邊緣和顏色組合。 ReLU 激活會增加非線性。 激活層之后的合并層匯總本地鄰居信息,以提供一定數(shù)量的平移不變性。 在理想的 CNN 中,此卷積激活池操作在網(wǎng)絡進入密集連接之前執(zhí)行了幾次:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-heHr3WWB-1681653992489)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/332c2f90-aae5-4b98-b417-ce942177e184.png)]
圖 1.11:CNN 架構(gòu)
當我們經(jīng)過具有多個卷積激活池操作的網(wǎng)絡時,圖像的空間分辨率會降低,而輸出特征映射的數(shù)量在每一層中都會增加。 卷積層中的每個輸出特征映射都與過濾器核相關聯(lián),該過濾器核的權(quán)重是通過 CNN 訓練過程學習的。
在卷積操作中,將過濾器核的翻轉(zhuǎn)版本放置在整個圖像或特征映射上,并為過濾器上每個位置計算過濾器核輸入值與相應圖像像素或特征映射值的點積。 輸入圖像或特征映射。 已經(jīng)習慣了普通圖像處理的讀者可能已經(jīng)使用了不同的過濾器核,例如高斯過濾器,Sobel 邊緣檢測過濾器等,其中許多過濾器的權(quán)重已預定義。 卷積神經(jīng)網(wǎng)絡的優(yōu)點是通過訓練過程確定不同的過濾器權(quán)重。 這意味著,針對卷積神經(jīng)網(wǎng)絡正在處理的問題,可以更好地定制過濾器。
當卷積運算涉及在輸入的每個位置上覆蓋過濾器核時,該卷積被稱為跨度為 1。 如果我們選擇在覆蓋過濾器核時跳過一個位置,那么卷積將以兩個步幅執(zhí)行。 通常,如果將n
位置跳過而將過濾器核覆蓋在輸入上,則表示卷積以n + 1
的步幅執(zhí)行。 大于 1 的步幅會減小卷積輸出的空間大小。
通常,卷積層之后是池化層,池化層基本上總結(jié)了由池化的接收場確定的鄰域中的輸出特征映射激活。 例如,一個2 x 2
的接收場將收集四個相鄰的輸出特征映射激活的本地信息。 對于最大池操作,將選擇四個激活的最大值作為輸出,而對于平均池化,將選擇四個激活的平均值。 合并降低了特征映射的空間分辨率。 例如,對于具有2 x 2
接收場的224 x 224
大小的特征映射池化操作,特征映射的空間大小將減小為112 x 112
。
要注意的一件事是,卷積運算減少了每層要學習的權(quán)重數(shù)。 例如,如果我們有一個空間大小為224 x 224
的輸入圖像,而下一層的期望輸出為大小為224 x 224
的大小,那么對于具有完整連接的傳統(tǒng)神經(jīng)網(wǎng)絡來說,要學習的權(quán)重數(shù)是224 x 224 x 224 x 224
。對于具有相同輸入和輸出大小的卷積層,我們需要學習的只是過濾器核的權(quán)重。 因此,如果我們使用3 x 3
過濾器核,我們只需要學習 9 個權(quán)重即可,而不是224 x 224 x 224 x 224
權(quán)重。 這種簡化是有效的,因為局部空間鄰域中的圖像和音頻之類的結(jié)構(gòu)之間具有高度相關性。
輸入圖像經(jīng)過多層卷積和池化操作。 隨著網(wǎng)絡的發(fā)展,特征映射的數(shù)量增加,而圖像的空間分辨率降低。 在卷積池層的末端,特征映射的輸出被饋送到全連接層,然后是輸出層。
輸出單元取決于手頭的任務。 如果執(zhí)行回歸,則輸出激活單元是線性的,而如果是二分類問題,則輸出單元是 Sigmoid 的。 對于多類別分類,輸出層是 softmax 單元。
在本書的所有圖像處理項目中,我們都將使用一種或另一種形式的卷積神經(jīng)網(wǎng)絡。
循環(huán)神經(jīng)網(wǎng)絡(RNN)
循環(huán)神經(jīng)網(wǎng)絡(RNN)在處理順序或時間數(shù)據(jù)時非常有用,其中給定實例或位置的數(shù)據(jù)與先前時間步長或位置中的數(shù)據(jù)高度相關。 RNN 在處理文本數(shù)據(jù)方面已經(jīng)非常成功,因為給定實例中的單詞與它前面的單詞高度相關。 在 RNN 中,網(wǎng)絡在每個時間步執(zhí)行相同的函數(shù),因此名稱中的術(shù)語為循環(huán)。 下圖說明了 RNN 的架構(gòu):
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-AM4GFnDP-1681653992489)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/0c426b93-140c-48a7-b982-e649cbaac4e3.png)]
圖 1.12:RNN 架構(gòu)
在每個給定的時間步長t
處,計算記憶狀態(tài)h[t]
,基于步驟t-1
處的之前的狀態(tài)h[t-1]
,以及時間步長t
處的輸入x[t]
。 新狀態(tài)h[t]
用于在步驟t
處預測輸出o[t]
。 控制 RNN 的方程式如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-GoEDN5yz-1681653992489)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/438f6f9e-0b82-497a-a27a-b6908e54889f.png)]
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Lxi7Xx6U-1681653992490)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/4ce6cd6d-4a3d-4c78-9aa6-364d288f20c7.png)]
如果我們要預測句子中的下一個單詞,則函數(shù)f[2]
通常是詞匯表中單詞的 softmax 函數(shù)。 根據(jù)當前問題,f[1]
函數(shù)可以是任何激活函數(shù)。
在 RNN 中,步驟t
中的輸出誤差會嘗試糾正先前時間步中的預測,并通過k ∈ 1, 2, ..., t-1
來概括。 通過傳播先前時間步長中的誤差來實現(xiàn)。 這有助于 RNN 了解彼此相距較遠的單詞之間的長期依賴性。 實際上,由于梯度問題的消失和爆炸,并非總是可能通過 RNN 學習這么長的依賴關系。
如您所知,神經(jīng)網(wǎng)絡通過梯度下降來學習,并且可以通過以下步驟來學習單詞在時間步t
與在先序列步k
之間的關系。 記憶狀態(tài)h[t]^(i)
相對于記憶狀態(tài)h[i]^(t)
的梯度。 用以下公式表示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-bzimxzLw-1681653992490)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/4b851df6-5d8f-4c92-861e-2c0066c19254.png)]
如果從序列步驟k
的存儲狀態(tài)h[k]^(i)
到序列步驟k + 1
的存儲狀態(tài)h[k + 1]^(i)
的權(quán)重連接由*給出u[ii] ∈ W[hh]
,則以下是正確的:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-WyXz6D1w-1681653992490)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/94bf384a-86e7-413d-ae16-9b3f5249eef1.png)]
在前面的等式中,s[k + 1]^(i)
是在時間步k + 1
時存儲狀態(tài)i
的總輸入,因此情況如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-aYLRtsB1-1681653992490)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/82fd4549-3b28-4289-9145-320579f8f364.png)]
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-FgUnIgYI-1681653992491)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/6034762c-e89b-43a9-823a-588dae6f05fd.png)]
既然我們已經(jīng)準備就緒,那么就很容易理解為什么 RNN 中可能會出現(xiàn)消失的梯度問題。 從前面的等式(3))
和(4)
得到以下結(jié)果:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Ub9UQ4mh-1681653992491)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/a5818e53-89c6-4a7a-8f79-f71fdfc2535d.png)]
對于 RNN,函數(shù)f[2]
通常為 Sigmoid 或 tanh,其飽受飽和度的困擾,即具有超出指定輸入值范圍的低梯度。 現(xiàn)在,由于f[2]
的導數(shù)彼此相乘,因此,如果激活函數(shù)的輸入在飽和區(qū)工作,則?h[t]^(i)/?h[k]^(i)
的斜率可以變?yōu)榱?#xff0c;即使相對tk
的中等值。 即使f[2]
函數(shù)在飽和區(qū)中不起作用,但 Sigmoids 的f[2]
函數(shù)的梯度始終比1
小,因此很難學習序列中單詞之間的遠距離依存關系。 同樣,可能會由于u[ii]^(t - k)
因子而出現(xiàn)爆炸性梯度問題。 假設步t
和k
之間的距離約為10
,而權(quán)重u[ii]
,大約兩點。 在這種情況下,梯度將被放大兩倍,即2 ^ 10 = 1024
,從而導致爆炸梯度問題。
長短期記憶(LSTM)單元
消失的梯度問題在很大程度上由 RNN 的改進版本(稱為長短期記憶(LSTM)單元)解決。 長短期存儲單元的架構(gòu)圖如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-KUtKQAcn-1681653992491)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/eab0d93b-3c6d-4fb8-a9ac-d49481457c76.png)]
圖 1.13:LSTM 架構(gòu)
除了記憶狀態(tài)h[t]
之外,LSTM 還介紹了 RNN 單元狀態(tài)C[t]
。 單元狀態(tài)由三個門控制:遺忘門,更新門和輸出門。 遺忘門確定從先前的單元狀態(tài)C[t-1]
保留多少信息,其輸出表示如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Ff2vOGa8-1681653992491)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/00985346-4740-4212-97ff-0506b23e7d21.png)]
更新門的輸出表示如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-F8QssJ8b-1681653992492)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/16cfb5c1-3e84-497e-8725-fc5c0e571a30.png)]
潛在的新候選單元狀態(tài)C_tilde[t]
表示如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-kr3grJGt-1681653992492)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/d10b017e-3e19-4c63-8ece-90ca7b3d2dfe.png)]
基于先前的電池狀態(tài)和當前的潛在電池狀態(tài),通過以下方式提供更新的電池狀態(tài)輸出:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Fru6Aby0-1681653992492)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/e4d692b3-3335-41cb-a793-1d6504dda376.png)]
并非單元狀態(tài)的所有信息都傳遞到下一步,并且應由輸出門確定應釋放多少單元狀態(tài)到下一步。 輸出門的輸出通過以下方式給出:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZTgakVbP-1681653992492)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/959381ee-2245-42ab-92a9-fd26d61d3756.png)]
根據(jù)當前單元狀態(tài)和輸出門,通過以下方式給出傳遞給下一步的更新后的內(nèi)存狀態(tài):
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-xxAdrkW8-1681653992492)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/55a8e2c1-d673-4016-b4f2-a2baf36f73ed.png)]
現(xiàn)在出現(xiàn)了一個大問題:LSTM 如何避免消失的梯度問題? LSTM 中?h[t]^(i)/?h[k]^(i)
的等效項由?C[t]^(i)/?C[k]^(i)
給出,可以用以下產(chǎn)品形式表示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-0ZfZ735y-1681653992493)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/0467935c-2261-4019-86e5-d5cb976fdcfb.png)]
現(xiàn)在,單元狀態(tài)單元的循環(huán)由以下給出:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-AJTm0B03-1681653992493)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/eb7d80c6-f2eb-44c2-8272-555b5611da6d.png)]
由此,我們得到以下內(nèi)容:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-xPcQopsH-1681653992493)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/636e59ba-65ee-410a-8dce-c19ce20565c6.png)]
結(jié)果,梯度表達式?C[t]^(i)/?C[k]^(i)
變?yōu)橐韵?#xff1a;
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-0hjN3MvM-1681653992493)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/78f1eac4-0f48-4f0f-8521-344d3a317e9d.png)]
如您所見,如果我們可以將遺忘單元格狀態(tài)保持在一個附近,則梯度將幾乎不衰減地流動,并且 LSTM 不會遭受梯度消失的困擾。
我們將在本書中看到的大多數(shù)文本處理應用將使用 RSTM 的 LSTM 版本。
生成對抗網(wǎng)絡
生成對抗網(wǎng)絡,通常稱為 GAN ,是通過生成器G
學習特定概率分布的生成模型。 生成器G
與判別器D
進行零和極小極大游戲,并且兩者都會隨著時間的流逝而逐漸達到納什均衡。 生成器嘗試生成類似于給定概率分布P(x)
生成的樣本,而判別器D
嘗試區(qū)分生成器生成的那些假數(shù)據(jù)樣本。G
來自原始分布的數(shù)據(jù)樣本。 生成器G
嘗試通過轉(zhuǎn)換樣本z
來生成與P(x)
相似的樣本。 噪聲分布P(z)
。 判別器D
在假冒時學會將生成器G
生成的樣本標記為G(z)
;x
原始時屬于P(x)
。 在 minimax 游戲的平衡狀態(tài)下,生成器將學習生成與原始分布P(x)
相似的樣本,因此以下是正確的:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-lnVDmihC-1681653992494)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/0bb90a7f-37ac-4079-8896-baa81ed7b81d.png)]
下圖說明了學習 MNIST 數(shù)字的概率分布的 GAN 網(wǎng)絡:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NrXcumpk-1681653992494)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/c8c7095f-4435-4ed0-a46b-3b89ed954f90.png)]
Figure 1.14: GAN architecture
判別器最小化的成本函數(shù)是二進制交叉熵,用于區(qū)分生成器生成的假數(shù)據(jù),和屬于概率分布P(x)
的真實數(shù)據(jù)點z
:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Z1PCNgah-1681653992494)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/b9cb9aa1-7eac-4174-983d-cb5be41b1eff.png)]
生成器將嘗試最大化由(1)
給出的相同成本函數(shù)。 這意味著,優(yōu)化問題可以表示為具有效用函數(shù) U(G, D)
的 minimax 玩家,如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Eb2q4UoS-1681653992494)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/56db8673-21ba-456d-94a5-f78f8d33506b.png)]
通常,要測量給定概率分布與給定分布的匹配程度,請使用f
-發(fā)散度量,例如 Kullback-Leibler(KL)散度,詹森·香農(nóng)散度和 Bhattacharyya 距離。 例如,以下給出兩個概率分布P
和Q
之間的 KL 散度,其中對分布的期望是P
:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-FJEiF1UR-1681653992494)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/a28bf1ff-ec23-480a-9740-060b00b8ab64.png)]
類似地,P
和Q
之間的詹森香農(nóng)散度給出如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zjWhhkTr-1681653992495)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/17bdf041-b5b0-442c-aea2-d56561c1f54e.png)]
現(xiàn)在,回到(2)
,表達式可以編寫如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-LQcVg71T-1681653992495)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/0c60cfa5-eb0a-4114-aa84-beab07d5a6cc.png)]
在這里,G(x)
是生成器的概率分布。 將期望擴展到其不可或缺的形式,我們得到以下內(nèi)容:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-B7w8bhJD-1681653992495)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/f5932e0c-dc8c-4041-abe9-24e33bcfc95f.png)]
對于固定的生成器分配,如果滿足以下條件,則G(x)
對于判別器的效用函數(shù)將最小。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-YjWXgDBr-1681653992495)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/ec1949f8-e469-4633-9aaf-d857c5f0831c.png)]
用(5)
替換為(3)
中的D(x)
,我們得到以下信息:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZAT085Jx-1681653992495)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/9dd0ea39-6a35-49d3-aa07-47e8242fd453.png)]
現(xiàn)在,生成器的任務是最大化工具V(G, D_hat)
或最小化工具-V(G, D_hat)
。 -V(G, D_hat)
的表達式可以重新安排如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-lStPBxdx-1681653992496)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/2e348883-3d4b-421d-940e-8c0c8a593af0.png)]
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-kBbNXj8H-1681653992496)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/63acc434-0231-447f-a331-5afab842bd9a.png)]
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-EU1QRmtA-1681653992496)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/8893fadf-3fbf-4c42-bb8d-7a27f14dcb76.png)]
因此,我們可以看到生成器最小化-V(G, D_hat)
等于最小化實際分布P(x)
與生成器生成的樣本分布之間的 Jensen Shannon 散度G
(即G(x)
)。
訓練 GAN 并不是一個簡單的過程,在訓練這樣的網(wǎng)絡時我們需要考慮幾個技術(shù)方面的考慮。 我們將使用高級 GAN 網(wǎng)絡在第 4 章“使用 GANs 的時裝行業(yè)中的風格遷移”中構(gòu)建跨域風格遷移應用。
強化學習
強化學習是機器學習的一個分支,它使機器和/或智能體可以通過采取特定行動在特定上下文中最大化某種形式的獎勵。 強化學習不同于監(jiān)督學習和無監(jiān)督學習。 強化學習廣泛用于博弈論,控制系統(tǒng),機器人技術(shù)和其他新興的人工智能領域。 下圖說明了強化學習問題中智能體與環(huán)境之間的交互:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-W9NI3aWx-1681653992496)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/13aa6565-1619-42b6-a595-a2bf486f0a6e.png)]
圖 1.15:強化學習模型中的智能體與環(huán)境交互
Q 學習
現(xiàn)在,我們將研究一種流行的強化學習算法,稱為 Q 學習。 Q 學習用于確定給定的有限馬爾可夫決策過程的最佳動作選擇策略。 馬爾可夫決策過程由狀態(tài)空間S
; 一個動作空間A
; 立即獎勵集R
; 給定當前狀態(tài)s[t]
的下一個狀態(tài)的概率S[t + 1]
; 當前動作a[t]
; P(S[t+1]/S[t];r[t])
; 和折扣系數(shù)γ
定義。 下圖說明了馬爾可夫決策過程,其中下一個狀態(tài)取決于當前狀態(tài)以及在當前狀態(tài)下執(zhí)行的任何操作:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-dsO4giP1-1681653992497)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/83330458-c0b9-4660-a1e3-6cd4927244a6.png)]
圖 1.16:馬爾可夫決策過程
假設我們有一系列狀態(tài),動作和相應的獎勵,如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-HmnmBxhv-1681653992497)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/3c565184-9ac9-488b-8caa-6136105ef059.png)]
如果我們考慮長期獎勵R[t]
,則在步驟t
處,它等于從t
開始的每一步直到最后的立即獎勵總和,如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-XliTxXRi-1681653992497)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/94a7a7da-3528-4fb2-9e42-1af592e0c024.png)]
現(xiàn)在,馬爾可夫決策過程是一個隨機過程,無法每次基于S[t]
和a[t]
進行相同的下一步S[t + 1]
; 因此,我們對未來的獎勵應用了折扣系數(shù)γ
。 這意味著長期獎勵可以更好地表示為:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-QX2NRjRy-1681653992498)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/4a4b48fe-0a74-4d0f-92ad-bad7fdc7fb35.png)]
由于在時間步t
上已經(jīng)實現(xiàn)了即時獎勵,為了最大化長期獎勵,我們需要最大化時間步t + 1
的長期獎勵(即R[t + 1]
),方法是選擇最佳操作。 通過采取行動a[t]
的狀態(tài)S[t]
所期望的最大長期回報,由以下 Q 函數(shù)表示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-uGpvkR0X-1681653992498)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/b3ecdb7c-d4cf-48a2-af93-c969b62fb41e.png)]
在每個狀態(tài)s ∈ S
,Q 學習中的主體嘗試采取行動α ∈ A
,以最大化其長期回報。 Q 學習算法是一個迭代過程,其更新規(guī)則如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-0sjIfFQw-1681653992498)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/b6e4a367-e0aa-406d-bd8b-5e1b2cb8c1d7.png)]
如您所見,該算法受(1)
中表達的長期獎勵概念的啟發(fā)。
處于狀態(tài)s[t]
的采取行動a[t]
的總累積獎勵Q(s[t], a[t])
取決于即時獎勵r[t]
以及在新步驟s[t+1]
處的,我們希望的最大長期回報。 在馬爾可夫決策過程中,新狀態(tài)s[t + 1]
隨機依賴于當前狀態(tài),即s[t]
,然后通過P(S[t+1]/S[t];r[t])
形式的概率密度/質(zhì)量函數(shù)選取的動作a[t]
。
該算法通過根據(jù)α
的值對舊期望值和新長期獎勵值進行加權(quán)平均,來不斷更新期望長期累積獎勵。
通過迭代算法構(gòu)建了Q(s, a)
函數(shù)后,在基于給定狀態(tài)s
進行游戲時,我們可以采取最佳措施a_hat
, 作為最大化 Q 函數(shù)的策略:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-B5LfNIjr-1681653992498)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/88e6ee5c-d64d-4d70-a9cf-ee3eb0e6c32a.png)]
深度 Q 學習
在 Q 學習中,我們通常會處理一組有限的狀態(tài)和動作。 這意味著,表格足以容納 Q 值和獎勵。 但是,在實際應用中,狀態(tài)和適用動作的數(shù)量大多是無限的,并且需要更好的 Q 函數(shù)近似器來表示和學習 Q 函數(shù)。 深度神經(jīng)網(wǎng)絡是通用函數(shù)近似器,因此在這里就應運而生。 我們可以用神經(jīng)網(wǎng)絡表示 Q 函數(shù),該神經(jīng)網(wǎng)絡將狀態(tài)和動作作為輸入并提供相應的 Q 值作為輸出。 或者,我們可以只使用狀態(tài)來訓練神經(jīng)網(wǎng)絡,然后將輸出作為與所有動作相對應的 Q 值。 下圖說明了這兩種情況。 由于 Q 值是獎勵,因此我們在以下網(wǎng)絡中處理回歸:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-oD6OIG5e-1681653992499)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/673b706b-7aa2-488e-851d-860dffe3abfb.png)]
圖 1.17:深度 Q 學習函數(shù)近似器網(wǎng)絡
在本書中,我們將使用強化學習來訓練賽車,以通過深度 Q 學習自行駕駛。
遷移學習
通常,遷移學習是指使用在一個領域中獲得的知識來解決另一領域中的相關問題的概念。 但是,在深度學習中,它專門指的是將針對特定任務訓練的神經(jīng)網(wǎng)絡重新用于不同領域中的相似任務的過程。 新任務使用從先前任務中學到的特征檢測器,因此我們不必訓練模型就可以學習它們。
由于不同層之間的連接模式的性質(zhì),深度學習模型傾向于具有大量參數(shù)。 要訓??練這么大的模型,需要大量的數(shù)據(jù); 否則,模型可能會過擬合。 對于許多需要深度學習解決方案的問題,將無法獲得大量數(shù)據(jù)。 例如,在用于對象識別的圖像處理中,深度學習模型提供了最新的解決方案。 在這種情況下,可以基于從現(xiàn)有的經(jīng)過訓練的深度學習模型中學習到的特征檢測器,使用遷移學習來創(chuàng)建特征。 然后,這些特征可用于使用可用數(shù)據(jù)構(gòu)建簡單模型,以解決當前的新問題。 因此,新模型需要學習的唯一參數(shù)是與構(gòu)建簡單模型有關的參數(shù),從而減少了過擬合的機會。 通常在大量數(shù)據(jù)上訓練預訓練的模型,因此,它們具有作為特征檢測器的可靠參數(shù)。
當我們在 CNN 中處理圖像時,初始層會學會檢測非常通用的特征,例如卷曲,邊緣,顏色組成等。 隨著網(wǎng)絡的深入發(fā)展,更深層次的卷積層將學會檢測與特定種類的數(shù)據(jù)集相關的更復雜特征。 我們可以使用預訓練的網(wǎng)絡,并選擇不訓練前幾層,因為它們會學習非常通用的函數(shù)。 相反,我們可以只專注于訓練最后幾層的參數(shù),因為它們將學習針對當前問題的復雜函數(shù)。 這樣可以確保我們需要訓練的參數(shù)較少,并且可以明智地使用數(shù)據(jù),僅訓練所需的復雜參數(shù),而不訓練通用特征。
遷移學習已廣泛應用于通過 CNN 進行圖像處理的過程,其中過濾器充當特征檢測器。 用于遷移學習的最常見的預訓練 CNN 是AlexNet
,VGG16
,VGG19
,Inception V3
和ResNet
等。 下圖說明了用于遷移學習的預訓練VGG16
網(wǎng)絡:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-g0Sdz8Wj-1681653992499)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/04ca3848-86ab-45e6-81f2-1c1d1011ef88.png)]
圖 1.18:使用預訓練的 VGG 16 網(wǎng)絡進行遷移學習
以x
表示的輸入圖像被饋送到預訓練的 VGG 16 網(wǎng)絡,以及4096
維輸出特征向量x'
,是從最后一個全連接層中提取的。 提取的特征x'
以及相應的類別標簽y
用于訓練簡單的分類網(wǎng)絡,從而減少解決問題所需的數(shù)據(jù)。
我們將通過使用第 2 章,“遷移學習”中的遷移學習來解決醫(yī)療保健領域中的圖像分類問題。
受限玻爾茲曼機
受限玻爾茲曼機(RBM)是一門無監(jiān)督的機器學習算法,用于學習數(shù)據(jù)的內(nèi)部表示。 RBM 具有可見層v ∈ R^m
,以及隱藏層h ∈ R^n
。 RBM 學習在可見層中將輸入呈現(xiàn)為隱藏層中的低維表示。 給定可見層輸入,所有隱藏層單元在條件上都是獨立的。 類似地,給定隱藏層輸入,所有可見層在條件上都是獨立的。 給定隱藏層輸入,這使得 RBM 可以獨立地對可見單元的輸出進行采樣,反之亦然。
下圖說明了 RBM 的架構(gòu):
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-RhhZCbR1-1681653992499)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/0c7517cc-ffec-4988-956f-e2258a4c314d.png)]
圖 1.19:受限玻爾茲曼機
權(quán)重w[ij] ∈ W
將可見單元i
連接到隱藏單元j
,其中W ∈ R^(mxn)
是所有這些權(quán)重的集合,從可見單元到隱藏單元。 可見單元的偏差由b[i] ∈ b
表示,而隱藏單元的偏差由c[j] ∈ c
表示。
受統(tǒng)計物理學中玻耳茲曼分布的思想啟發(fā),可見層向量v
和隱藏層向量h
的聯(lián)合分布正比于配置的負能量的指數(shù):
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-1pQSgnK4-1681653992499)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/1aea1d38-4ac6-4663-b3a2-31b6849a03a0.png)](1)
配置的能量由以下給出:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-FldV2zZc-1681653992500)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/e92fb4ee-090a-4067-b628-b1f0dcf9eac0.png)](2)
給定可見輸入向量v
的隱藏單元j
的概率可以表示為:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-z30lC2kF-1681653992500)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/7e50218c-8aa9-4d4b-8d38-761ba1efb7ff.png)](2)
類似地,給出隱藏輸入向量h
的可見單元i
的概率由以下公式給出:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-AbpDiiex-1681653992500)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/7bab8697-ec6f-46d1-8e2f-9ec1cf2bf5cc.png)](3)
因此,一旦我們通過訓練了解了 RBM 的權(quán)重和偏差,就可以在給定隱藏狀態(tài)的情況下對可見表示進行采樣,而在給定可見狀態(tài)的情況下可以對隱藏狀態(tài)進行采樣。
類似于主成分分析(PCA),RBM 是一種方法,將一個維度(由可見層v
提供)中的數(shù)據(jù)表示為不同的維度(由隱藏層h
提供)。 當隱藏層的大小小于可見層的大小時,RBM 執(zhí)行減小大小的任務。 RBM 通常在二進制數(shù)據(jù)上訓練。
通過最大化訓練數(shù)據(jù)的可能性來訓練 RBM。 在成本函數(shù)相對于權(quán)重和偏差的梯度下降的每次迭代中,都會出現(xiàn)采樣,這會使訓練過程變得昂貴并且在計算上有些棘手。 一種名為對比發(fā)散的聰明采樣方法(使用吉布斯采樣)用于訓練 RBM。
在第 6 章,“智能推薦系統(tǒng)”中,我們將使用 RBM 構(gòu)建推薦系統(tǒng)。
自編碼器
與 RBM 十分相似,自編碼器是一類無監(jiān)督的學習算法,旨在發(fā)現(xiàn)數(shù)據(jù)中的隱藏結(jié)構(gòu)。 在主成分分析(PCA)中,我們嘗試捕獲輸入變量之間的線性關系,并嘗試通過(輸入變量的)線性組合來在降維空間中表示數(shù)據(jù),這說明了數(shù)據(jù)的大部分差異。 但是,PCA 無法捕獲輸入變量之間的非線性關系。
自編碼器是一種神經(jīng)網(wǎng)絡,可以捕獲輸入變量之間的非線性相互作用,同時在隱藏層中以不同維度表示輸入。 在大多數(shù)情況下,隱藏層的大小小于輸入的大小。 假設存在高維數(shù)據(jù)固有的低維結(jié)構(gòu),我們跳過了這一點。 例如,高維圖像可以由低維流形表示,并且自編碼器通常用于發(fā)現(xiàn)該結(jié)構(gòu)。 下圖說明了自編碼器的神經(jīng)架構(gòu):
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Xf0sw8RB-1681653992500)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/594d3d11-05f5-4367-a760-726135cf225b.png)]
圖 1.20:自編碼器架構(gòu)
自編碼器有兩個部分:編碼器和解碼器。 編碼器嘗試將輸入數(shù)據(jù)x
投影到隱藏層h
中。 解碼器嘗試從隱藏層h
重構(gòu)輸入。 通過最小化重構(gòu)誤差,即來自解碼器和原始輸入的重構(gòu)輸入x_tilde
之間的誤差,可以訓練伴隨此類網(wǎng)絡的權(quán)重。 如果輸入是連續(xù)的,則最小化重構(gòu)誤差的平方和,以學習自編碼器的權(quán)重。
如果我們用函數(shù)f[W](x)
表示編碼器,而解碼器則用f[U](x)
表示,其中W
和U
是與編碼器和解碼器關聯(lián)的權(quán)重矩陣,那么情況如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Q5Z9O7hu-1681653992500)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/40f56474-8d03-49d3-9567-3a30fce2337b.png)](1)
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-bZCXnQuW-1681653992501)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/7e6dae4f-91dc-43af-a351-d93afff1ecd7.png)](2)
訓練集上的重構(gòu)誤差C
,x[i], i ∈ 1, 2, 3, ..., m
可以表示如下 :
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8dl0bT6y-1681653992501)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/5ecc227c-e830-4604-898b-f08d92be4d0a.png)](3)
通過最小化(3)
的成本函數(shù),可以學習自編碼器的最佳權(quán)重W_hat, U_hat
,如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-WfR1FzyM-1681653992501)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/1a645659-bc76-482d-9d08-7c790e29013e.png)](4)
自編碼器用于多種目的,例如學習數(shù)據(jù)的潛在表示,降噪和特征檢測。 降噪自編碼器將實際輸入的噪聲版本作為其輸入。 他們嘗試構(gòu)建實際的輸入,以作為重建的標簽。 類似地,自編碼器可以用作生成模型。 可以用作生成模型的一類這樣的自編碼器稱為變分自編碼器。 當前,變分自編碼器和 GAN 作為圖像處理的生成模型非常受歡迎。
總結(jié)
現(xiàn)在,我們到了本章的結(jié)尾。 我們已經(jīng)研究了人工神經(jīng)網(wǎng)絡的幾種變體,包括用于圖像處理目的的 CNN 和用于自然語言處理目的的 RNN。 此外,我們將 RBM 和 GAN 視為生成模型,將自編碼器視為無監(jiān)督方法,可以解決許多問題,例如降噪或解密數(shù)據(jù)的內(nèi)部結(jié)構(gòu)。 此外,我們還談到了強化學習,這對機器人技術(shù)和 AI 產(chǎn)生了重大影響。
您現(xiàn)在應該熟悉本書其余各章中構(gòu)建智能 AI 應用時將要使用的核心技術(shù)。 在構(gòu)建應用時,我們將在需要時進行一些技術(shù)上的改動。 建議不熟悉深度學習的讀者探索更多有關本章涉及的核心技術(shù)的信息,以便更全面地理解。
在隨后的章節(jié)中,我們將討論實用的 AI 項目,并使用本章中討論的技術(shù)來實現(xiàn)它們。 在第 2 章,“遷移學習”中,我們將從使用遷移學習實現(xiàn)醫(yī)療保健應用進行醫(yī)學圖像分析開始。 我們希望您期待您的參與。
二、遷移學習
遷移學習是將特定領域中一項任務中獲得的知識遷移到相似領域中相關任務中的過程。 在深度學習范例中,遷移學習通常是指將預訓練模型的重用作為另一個問題的起點。 計算機視覺和自然語言處理中的問題需要大量數(shù)據(jù)和計算資源,才能訓練出有意義的深度學習模型。 遷移學習在視覺和文本領域已變得非常重要,因為它減輕了對大量訓練數(shù)據(jù)和訓練時間的需求。 在本章中,我們將使用遷移學習來解決醫(yī)療保健問題。
我們將在本章中涉及的與遷移學習有關的一些關鍵主題如下:
- 使用遷移學習來檢測人眼中的糖尿病性視網(wǎng)膜病變狀況,并確定視網(wǎng)膜病變的嚴重程度
- 探索可用于訓練可檢測人眼眼底圖像中的糖尿病性視網(wǎng)膜病變的卷積神經(jīng)網(wǎng)絡(CNN)的高級預訓練卷積神經(jīng)架構(gòu)
- 查看實際實現(xiàn) CNN 所需的不同圖像預處理步驟
- 學習制定適合當前問題的成本函數(shù)
- 定義適當?shù)亩攘繕藴蕘砗饬坑柧毮P偷谋憩F(xiàn)
- 使用仿射變換生成其他數(shù)據(jù)
- 與適當?shù)膶W習率,優(yōu)化器的選擇等有關的訓練錯綜復雜
- 查看端到端 Python 實現(xiàn)
技術(shù)要求
您將需要具備 Python 3,TensorFlow,Keras 和 OpenCV 的基礎知識。
本章的代碼文件可以在 GitHub 上找到
觀看以下視頻,查看運行中的代碼。
遷移學習簡介
在傳統(tǒng)的機器學習范例中(請參見“圖 2.1”),每個用例或任??務都是根據(jù)手頭的數(shù)據(jù)獨立建模的。 在遷移學習中,我們使用從特定任務中獲得的知識(以架構(gòu)和模型參數(shù)的形式)來解決不同(但相關)的任務,如下圖所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-64XnqN9d-1681653992501)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/07387bba-04ab-4758-9ac4-8740ea2f1bea.png)]
圖 2.1:傳統(tǒng)機器學習與遷移學習
吳安德(Andrew Ng)在其 2016 年 NIPS 教程中表示,遷移學習將成為機器學習商業(yè)成功的第二大推動力(在監(jiān)督學習之后); 這一說法日新月異。 遷移學習現(xiàn)已廣泛用于需要使用人工神經(jīng)網(wǎng)絡解決的問題中。 因此,最大的問題是為什么會這樣。
從頭開始訓練人工神經(jīng)網(wǎng)絡是一項艱巨的任務,主要是由于以下兩個原因:
- 人工神經(jīng)網(wǎng)絡的成本面是非凸的; 因此,它需要一組良好的初始權(quán)重才能實現(xiàn)合理的收斂。
- 人工神經(jīng)網(wǎng)絡具有很多參數(shù),因此,它們需要大量數(shù)據(jù)進行訓練。 不幸的是,對于許多項目而言,可用于訓練神經(jīng)網(wǎng)絡的特定數(shù)據(jù)不足,而該項目旨在解決的問題非常復雜,需要神經(jīng)網(wǎng)絡解決方案。
在這兩種情況下,遷移學習都可以解決。 如果我們使用在大量標記數(shù)據(jù)上訓練的預訓練模型,例如 ImageNet 或 CIFAR,則涉及遷移學習的問題將具有很好的初始權(quán)重來開始訓練; 然后可以根據(jù)現(xiàn)有數(shù)據(jù)對這些權(quán)重進行微調(diào)。 同樣,為了避免在較少的數(shù)據(jù)量上訓練復雜的模型,我們可能希望從預訓練的神經(jīng)網(wǎng)絡中提取復雜的特征,然后使用這些特征來訓練相對簡單的模型,例如 SVM 或邏輯回歸模型。 舉個例子,如果我們正在研究圖像分類問題,并且已經(jīng)有一個預訓練的模型(例如,基于 1,000 個 ImageNet 的VGG16
網(wǎng)絡),我們可以通過VGG16
的權(quán)重遷移訓練數(shù)據(jù),并從最后一個池化層提取特征。 如果我們有m
個訓練數(shù)據(jù)點,則可以使用等式(xi, yi), i = 1 -> m
,其中x
是特征向量,y
是輸出類。 然后,我們可以從預訓練的VGG16
網(wǎng)絡中得出復雜的特征,例如向量h
,如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Xxt36aFH-1681653992502)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/bb55e155-7677-4ae6-8bf9-9998e72422ab.png)]
此處W
是經(jīng)過預訓練的VGG16
網(wǎng)絡的權(quán)重集,直到最后一個池化層。
然后我們可以使用經(jīng)過轉(zhuǎn)換的訓練數(shù)據(jù)點集(hi, yi), i = 1 -> m
來構(gòu)建相對簡單的模型。
檢測糖尿病性視網(wǎng)膜病變的遷移學習
在本章中,我們將使用遷移學習來構(gòu)建一個模型,以檢測人眼中的糖尿病性視網(wǎng)膜病變。 糖尿病性視網(wǎng)膜病通常在糖尿病患者中發(fā)現(xiàn),其中高血糖水平導致視網(wǎng)膜血管的損害。 下圖左側(cè)為正常視網(wǎng)膜,右側(cè)為糖尿病性視網(wǎng)膜病變:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-AbF3opW5-1681653992502)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/01145ccc-ee1f-48be-8662-d9fcd40381a7.png)]
圖 2.2:正常人視網(wǎng)膜與患有糖尿病性視網(wǎng)膜病的視網(wǎng)膜
在醫(yī)療保健中,糖尿病性視網(wǎng)膜病變的檢測通常是一個手動過程,需要受過訓練的醫(yī)師檢查彩色眼底視網(wǎng)膜圖像。 這導致診斷過程的延遲,通常導致延遲的治療。 作為我們項目的一部分,我們將建立一個強大的人工智能系統(tǒng),該系統(tǒng)可以拍攝視網(wǎng)膜的彩色眼底圖像,并對糖尿病性視網(wǎng)膜病變的視網(wǎng)膜狀況進行嚴重性分類。 我們將視網(wǎng)膜圖像分類的不同條件如下:
*0
:無糖尿病性視網(wǎng)膜病變
*1
:輕度糖尿病性視網(wǎng)膜病變
*2
:中度糖尿病性視網(wǎng)膜病變
*3
:嚴重的糖尿病視網(wǎng)膜病變
*4
:糖尿病性視網(wǎng)膜增生病
糖尿病視網(wǎng)膜病變數(shù)據(jù)集
用于構(gòu)建糖尿病性視網(wǎng)膜病變檢測應用的數(shù)據(jù)集可從 Kaggle 獲得,可從以下鏈接下載。
訓練和保留測試數(shù)據(jù)集都存在于train_dataset.zip
文件中,該文件可在前面的鏈接中找到。
我們將使用標記的訓練數(shù)據(jù)通過交叉驗證來構(gòu)建模型。 我們將在保持數(shù)據(jù)集上評估模型。
由于我們正在處理類別預測,因此準確率將是有用的驗證指標。 精度定義如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-CYPF3RnS-1681653992502)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/654757ad-de20-47fc-9b3f-a320446f607c.png)]
此處,c
是正確分類的樣本數(shù),N
是評估樣本的總數(shù)。
與 Kaggle 標準相比,我們還將使用二次加權(quán) kappa 統(tǒng)計信息確定模型的質(zhì)量,并確定模型的表現(xiàn)基準。 二次加權(quán) kappa 定義如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-YssPrdil-1681653992502)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/54dadcbf-9122-4ff6-89f4-a98ca577f4b8.png)]
二次加權(quán) kappa 表達式中的權(quán)重w[ij]
如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-wjLwPn5l-1681653992502)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/e9f8f87c-4ebe-4065-8787-997893e22c33.png)]
在上式中,以下內(nèi)容適用:
N
代表類別數(shù)O[ij]
表示已被預測為i
類的圖像數(shù)量,其中圖像的預測類為i
而實際類別為j
E[ij]
表示預測類別為i
的預期觀測個數(shù),預測類為i
而實際類別為j
,假設預測類和實際類是獨立的
為了更好地了解 kappa 指標組件,讓我們看一下蘋果和橘子的二分類。 假設預測類和實際類的混淆矩陣如下圖所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-r9EpNA9X-1681653992503)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/0c2ae574-b0cc-4cc5-8dc7-51ddcce904f8.png)]
圖 2.3:Kappa 指標項
真實標簽為橙子,假設標簽之間具有獨立性時,預測蘋果的預期計數(shù)由以下公式給出:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ebWWURHy-1681653992503)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/81cdd503-0856-413d-806e-2d844b0dae16.png)] [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-vNIYnuT4-1681653992503)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/01ae53b3-037d-4047-a4fe-b41a148af503.png)] [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NHmHEOP4-1681653992503)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/be17d2d1-dd00-4ba9-9165-571866d133b2.png)]
鑒于沒有模型,此預期計數(shù)是您可能犯的最嚴重的錯誤。
如果您熟悉兩個類別變量之間的獨立性卡方檢驗,則假設類別變量之間具有獨立性,則列聯(lián)表中每個單元格中的預期計數(shù)都是基于相同的公式計算的。
可以從混淆矩陣中直接追蹤模型的觀測計數(shù),該模型預測的真實標簽為橙子時,預測的蘋果等于5
,如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-jbKJzY9S-1681653992503)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/f6d73ecf-a072-4064-9b92-8e880c0080e6.png)]
因此,我們可以看到模型在預測橙子為蘋果時所產(chǎn)生的誤差小于我們不使用模型時所獲得的誤差。 與沒有模型的預測相比,Kappa 通常衡量我們的表現(xiàn)如何。
如果我們觀察二次權(quán)重的表達式w[ij]
,我們可以看到,當實際標簽與預測標簽之間的差異較大時,權(quán)重的值會更大。 由于類的序數(shù)性質(zhì),這是有道理的。 例如,讓我們用類別標簽 0 表示完美狀態(tài)的眼睛; 患有輕度糖尿病性視網(wǎng)膜病的患者; 中度糖尿病視網(wǎng)膜病變伴兩個; 并伴有嚴重的糖尿病性視網(wǎng)膜病變。 當輕度糖尿病性視網(wǎng)膜病被錯誤地分類為嚴重糖尿病性視網(wǎng)膜病而不是中度糖尿病性視網(wǎng)膜病時,這個二次項權(quán)重w[ij]
將會更高。 這是有道理的,因為即使我們沒有設法預測實際類別,我們也希望預測一個盡可能接近實際狀況的狀況。
我們將使用sklearn.metrics.cohen_kappa_score
和weights= "quadratic"
來計算 kappa 得分。 權(quán)重越高,kappa 得分越低。
定義損失函數(shù)
該用例的數(shù)據(jù)分為五類,分別與無糖尿病性視網(wǎng)膜病變,輕度糖尿病性視網(wǎng)膜病變,中度糖尿病性視網(wǎng)膜病變,嚴重糖尿病性視網(wǎng)膜病變和增生性糖尿病性視網(wǎng)膜病變有關。 因此,我們可以將其視為分類問題。 對于我們的分類問題,需要對輸出標簽進行一次熱編碼,如下所示:
- 無糖尿病性視網(wǎng)膜病變:
[1 0 0 0 0]^T
- 輕度糖尿病性視網(wǎng)膜病變:
[0 1 0 0 0]^T
- 中度糖尿病性視網(wǎng)膜病變:
[0 0 1 0 0]^T
- 嚴重糖尿病性視網(wǎng)膜病變:
[0 0 0 1 0]^T
- 增生糖尿病性視網(wǎng)膜病變:
[0 0 0 0 1]^T
Softmax 將是表示輸出層中不同類別的概率的最佳激活函數(shù),而每個數(shù)據(jù)點的分類交叉熵損失之和將是優(yōu)化的最佳損失。 對于具有輸出標簽向量y
和p
的預測概率的單個數(shù)據(jù)點,交叉熵損失由以下公式給出:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-vaGBbUxv-1681653992504)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/8058232c-897d-4cec-9a2d-768e01646985.png)]
這里是y = [y1, ..., y5]^T
和p = [p1, ..., p5]^T
。
同樣,M
訓練數(shù)據(jù)點上的平均損失可以表示為:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-2EnyCsTA-1681653992504)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/79a945ed-f72e-486f-9b76-d6a9a3ec21f2.png)]
在訓練過程中,小批量的梯度基于(2)
給出的平均對數(shù)損失,其中M
是所選的批量大小。 對于我們將結(jié)合驗證準確率進行監(jiān)視的驗證日志損失,M
是驗證集數(shù)據(jù)點的數(shù)量。 由于我們將在每個折疊中進行 K 折交叉驗證,因此在每個折疊中我們將具有不同的驗證數(shù)據(jù)集。
現(xiàn)在我們已經(jīng)定義了訓練方法,損失函數(shù)和驗證指標,讓我們繼續(xù)進行數(shù)據(jù)探索和建模。
請注意,輸出類別中的分類具有序數(shù)性質(zhì),因為嚴重性隨類別而增加。 因此,回歸可能會派上用場。 我們還將嘗試通過回歸來代替分類,以了解運氣如何。 回歸的挑戰(zhàn)之一是將原始分數(shù)轉(zhuǎn)換為類。 我們將使用一個簡單的方案,將分數(shù)散列到最接近的整數(shù)嚴重性類別。
考慮類別失衡
在分類方面,類別失衡是一個主要問題。 下圖描述了五個嚴重性類別的類別密度:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-pRsVpACt-1681653992504)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/5969eeb1-ca28-4fbb-b96c-12d2f4088362.png)]
圖 2.4:五個嚴重性類別的類別密度
從上圖可以看出,將近 73% 的訓練數(shù)據(jù)屬于 0 類,這代表沒有糖尿病性視網(wǎng)膜病。 因此,如果我們碰巧將所有數(shù)據(jù)點標記為 0 類,那么我們將具有 73% 的準確率。 在患者健康狀況下這是不希望的。 我們寧愿有一個測試說患者沒有時有某種健康狀況(假陽性),而有一項測試卻漏診時沒有發(fā)現(xiàn)某種健康狀況(假陰性)。 如果模型學會將所有點分類為屬于 0 類,則 73% 的準確率可能毫無意義。
在嚴重性類別 0 上,檢測較高的嚴重性類別比做好工作更為重要。 使用對數(shù)損失或交叉熵成本函數(shù)的分類模型的問題在于它偏愛多數(shù)類。 這是因為交叉熵誤差是從最大似然原理中得出的,該似然原理傾向于將較高的概率分配給多數(shù)類別。 我們可以做兩件事:
- 丟棄具有更多樣本的類別中的數(shù)據(jù),或者丟棄低頻類別的上樣本,以保持樣本在各個類別之間的分布均勻。
- 在損失函數(shù)中,權(quán)重與類別的密度成反比。 這將確保當模型無法對低頻類別進行分類時,對成本函數(shù)施加更高的懲罰。
我們將使用方案二,因為它不需要生成更多數(shù)據(jù)或丟棄現(xiàn)有數(shù)據(jù)。 如果我們將類別權(quán)重與類別頻率的倒數(shù)成正比,則會得到以下類別權(quán)重:
嚴重類別 | 類權(quán)重 |
---|---|
Class 0 | 0.0120353863 |
Class 1 | 0.1271350558 |
Class 2 | 0.0586961973 |
Class 3 | 0.3640234214 |
Class 4 | 0.4381974727 |
我們將在訓練分類網(wǎng)絡時使用這些權(quán)重。
預處理圖像
不同類別的圖像將存儲在不同的文件夾中,因此可以輕松標記其類別。 我們將使用Opencv
函數(shù)讀取圖像,并將其調(diào)整為不同的大小,例如224 x 224 x3
。我們將基于 ImageNet 數(shù)據(jù)集從每個圖像的通道方向上減去平均像素強度。 這意味著減法將使糖尿病性視網(wǎng)膜病變圖像達到與在其上訓練了預訓練模型的處理過的 ImageNet 圖像相同的強度范圍。 提出每個圖像后,它們將存儲在numpy
數(shù)組中。 圖像預處理函數(shù)可以定義如下:
def get_im_cv2(path,dim=224):img = cv2.imread(path)resized = cv2.resize(img, (dim,dim), cv2.INTER_LINEAR)return resizeddef pre_process(img):img[:,:,0] = img[:,:,0] - 103.939img[:,:,1] = img[:,:,0] - 116.779img[:,:,2] = img[:,:,0] - 123.68return img
通過opencv
函數(shù)imread
讀取圖像,然后使用線性插值方法將其調(diào)整為(224,224,3)
或任何給定大小。 ImageNet 圖像的紅色,綠色和藍色通道中的平均像素強度分別為103.939
,116.779
和123.68
; 從圖像中減去這些平均值之后,對預訓練模型進行訓練。 均值減法的這種活動用于使數(shù)據(jù)居中。 將數(shù)據(jù)定為零附近有助于解決消失和爆炸的梯度問題,進而幫助模型收斂更快。 同樣,對每個通道進行歸一化有助于保持梯度均勻地流入每個通道。 由于我們將為此項目使用預訓練的模型,因此在將圖像饋入預訓練的網(wǎng)絡之前,有必要根據(jù)通道平均像素值校正圖像。 但是,并非必須要根據(jù)預訓練網(wǎng)絡所基于的 ImageNet 的平均值來校正圖像。 您可以通過該項目的訓練語料庫的平均像素強度很好地進行歸一化。
同樣,您可以選擇對整個圖像進行均值歸一化,而不是對通道進行均值歸一化。 這需要從自身中減去每個圖像的平均值。 想象一下 CNN 識別的對象在不同的??光照條件下(例如白天和晚上)曝光的場景。 無論光照條件如何,我們都希望對物體進行正確的分類,但是不同的像素強度將以不同方式激活神經(jīng)網(wǎng)??絡的神經(jīng)元,從而導致錯誤分類物體的可能性。 但是,如果我們從自身中減去每個圖像的平均值,則對象將不再受到不同光照條件的影響。 因此,根據(jù)我們使用的圖像的性質(zhì),我們可以為自己選擇最佳的圖像標準化方案。 但是,任何默認的標準化方法都傾向于提供合理的表現(xiàn)。
使用仿射變換生成其他數(shù)據(jù)
我們將在圖像像素坐標上使用仿射變換,使用 keras ImageDataGenerator
生成其他數(shù)據(jù)。 我們將主要使用的轉(zhuǎn)換是旋轉(zhuǎn),平移和縮放。 如果像素空間坐標由x = [x[1] x[2]]^T ∈ R^2
定義 ,則像素的新坐標可以通過以下方式給出:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-eA1GAfKp-1681653992504)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/48a4bf27-bee5-46b1-8b5b-9fe1f96d8a52.png)]
這里, M = R^(2x2)
是仿射變換矩陣, b = [b[1], b[2]]^T ∈ R^2
是翻譯向量。
項b[1]
指定沿一個空間方向的翻譯,而b[2]
提供沿另一空間維度的翻譯。
這些轉(zhuǎn)換是必需的,因為神經(jīng)網(wǎng)絡通常不是平移不變,旋轉(zhuǎn)不變或尺度不變。 合并操作確實提供了一些平移不變性,但是通常這還不夠。 神經(jīng)網(wǎng)絡不會將圖像中特定位置的一個對象和另一圖像中平移位置的相同對象視為同一事物。 這就是為什么我們需要在不同平移位置的圖像的多個實例,以使神經(jīng)網(wǎng)絡更好地學習。 相同的解釋適用于旋轉(zhuǎn)和縮放。
旋轉(zhuǎn)
以下是旋轉(zhuǎn)的仿射變換矩陣,其中θ
表示旋轉(zhuǎn)角度:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-rkPpu0qW-1681653992505)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/1fe912fc-2dba-41b7-abe9-03443e5a0be2.png)]
在這種情況下,轉(zhuǎn)換向量b
為零。 通過選擇一個非零b
,我們可以得到旋轉(zhuǎn)和平移。
例如,下圖顯示了視網(wǎng)膜的照片,然后是旋轉(zhuǎn) 90 度的同一張照片:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-fbVEJZ5Q-1681653992505)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/8e268fb7-87ef-4dc7-b478-4ae01ced9521.png)]
圖 2.5:視網(wǎng)膜的旋轉(zhuǎn)照片
平移
對于平移,仿射變換矩陣是單位矩陣,平移向量b
具有非零值:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-35LTxOEZ-1681653992505)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/53b85765-1bcb-4f23-b8f3-60b223dbf0f7.png)]
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4U6ESGbU-1681653992505)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/73d49d1a-f974-40f0-9638-6ee27f574016.png)]
例如,對于垂直方向上五個像素位置和水平方向上三個像素位置的平移,我們可以使用b = [5 3]^T
和M
作為單位矩陣。
以下是沿圖像的寬度和高度按 24 個像素位置對視網(wǎng)膜進行的圖像平移:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-UrNDQy7s-1681653992505)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/d8fe4d81-04d8-481f-afdd-3a8295ab2a1c.png)]
圖 2.5:視網(wǎng)膜的圖像平移
縮放比例
縮放可以通過對角矩陣M ∈ R^(2x2)
執(zhí)行,如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-G7kclgrL-1681653992506)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/f7892eff-50fc-4cf8-a629-271ad0cd18e3.png)]
這里,S[v]
表示沿垂直方向的比例因子,S[h]
表示沿水平方向的比例因子(請參見 “圖 2.6”為插圖)。 我們還可以選擇通過具有非零轉(zhuǎn)換向量b
來跟隨轉(zhuǎn)換的縮放:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Fe1dRnnM-1681653992506)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/3b758265-dbb1-48fb-a744-4a08fef9ce08.png)]
圖 2.6 視網(wǎng)膜的圖像縮放
反射
可以通過變換矩陣T ∈ R^(2x2)
獲得關于一條線L
與水平角度為θ
的反射,如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-W719IUbz-1681653992506)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/a5bd446b-cfaa-46c9-9797-36893efc4d5b.png)]
下圖顯示了視網(wǎng)膜照片的水平翻轉(zhuǎn):
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-HKWKtLIA-1681653992506)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/380a724a-5cef-4a07-948a-f07da269bf10.png)]
圖 2.7:視網(wǎng)膜照片的水平翻轉(zhuǎn)
通過仿射變換產(chǎn)生更多圖像
keras
圖像生成器將使用以下類來完成我們的任務:
datagen = ImageDataGenerator(horizontal_flip = True,vertical_flip = True,width_shift_range = 0.1,height_shift_range = 0.1,channel_shift_range=0,zoom_range = 0.2,rotation_range = 20)
從定義的生成器中可以看到,我們啟用了水平和垂直翻轉(zhuǎn),這僅是圖像沿水平和垂直軸的反射。 同樣,我們將沿寬度和高度的圖像平移定義為沿這些方向的像素位置的 10% 以內(nèi)。 旋轉(zhuǎn)范圍限制為20
度的角度,而比例因子定義為原始圖像的0.8
至1.2
之內(nèi)。
網(wǎng)絡架構(gòu)
現(xiàn)在,我們將對經(jīng)過預訓練的ResNet50
,InceptionV3
和VGG16
網(wǎng)絡進行實驗,并找出哪種網(wǎng)絡效果最好。 每個預訓練模型的權(quán)重均基于 ImageNet。 我提供了 ResNet,InceptionV3
和VGG16
架構(gòu)的原始論文的鏈接,以供參考。 建議讀者仔細閱讀這些文章,以深入了解這些架構(gòu)及其之間的細微差別。
VGG 論文鏈接如下:
《用于大型圖像識別的超深度卷積網(wǎng)絡》
ResNet 論文鏈接如下:
《用于圖像識別的深度殘差學習》
InceptionV3 論文鏈接如下:
《重新思考計算機視覺的起始架構(gòu)》
簡而言之,VGG16
是一個 16 層的 CNN,使用3 x 3
過濾器和2 x 2
接收場進行卷積。 整個網(wǎng)絡使用的激活函數(shù)都是 ReLU。 由 Simonyan 和 Zisserman 開發(fā)的 VGG 架構(gòu)是 2014 年 ILSVRC 競賽的亞軍。 VGG16
網(wǎng)絡由于其簡單性而廣受歡迎,它是從圖像中提取特征的最受歡迎的網(wǎng)絡。
ResNet50
是一種深層的 CNN,它實現(xiàn)了殘余塊的概念,與VGG16
網(wǎng)絡的概念完全不同。 經(jīng)過一系列的卷積激活池操作之后,該塊的輸入再次被反饋到輸出。 ResNet 架構(gòu)是由 Kaiming He 等人開發(fā)的,盡管具有 152 層,但它不如 VGG 網(wǎng)絡復雜。 該架構(gòu)通過實現(xiàn) 3.57% 的前五位錯誤率贏得了 2015 年 ILSVRC 競賽,這比該競賽數(shù)據(jù)集上的人類水平表現(xiàn)要好。 通過檢查目標是否在概率最高的五類預測中來計算前五個錯誤率。 原則上,ResNet 網(wǎng)絡嘗試學習殘差映射,而不是直接從輸出映射到輸入,如下面的殘差框圖所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-x56964Rx-1681653992507)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/6e52be2c-3618-4411-8f03-b372b21e42c7.png)]
圖 2.8:ResNet 模型的殘差塊
InceptionV3 是 Google 提供的最新的 CNN。 InceptionV3 架構(gòu)沒有在每一層使用固定大小的卷積過濾器,而是使用不同大小的過濾器來提取不同粒度級別的特征。 下圖說明了 InceptionV3 層的卷積塊:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-hrXOlwsz-1681653992507)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/afb2d8e3-f919-49c3-9ede-13e707b6d72e.png)]
圖 2.9:InceptionV3 卷積模塊
Inception V1(GoogleNet)是 2014 年 ILSVRC 競賽的獲勝者。 它的最高 5% 錯誤率非常接近人類水平的表現(xiàn),為 6.67%。
VGG16 遷移學習網(wǎng)絡
我們將從預訓練的VGG16
網(wǎng)絡中的最后一個合并層中獲取輸出,并添加一對全連接層,每個層 512 個單元,然后是輸出層。 最終池化層的輸出是從全局平均池化操作傳遞到全連接層之前的。 我們只可以展平池化層的輸出,而不是執(zhí)行全局平均池化-其思想是確保池化的輸出不是二維晶格格式,而是一維數(shù)組格式, 非常像一個全連接層。 下圖說明了基于預訓練的VGG16
的新VGG16
的架構(gòu):
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ElY6rDAa-1681653992507)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/c2ef9608-eaff-46fd-990c-75a189d2463b.png)]
圖 2.10:VGG16 遷移學習網(wǎng)絡
如上圖所示,我們將從預訓練網(wǎng)絡中的最后一個最大池化層提取輸出,并在最終輸出層之前附加兩個全連接層。 基于先前的架構(gòu),可以使用keras
如下代碼塊所示定義 VGG 定義函數(shù):
def VGG16_pseudo(dim=224,freeze_layers=10,full_freeze='N'):# model_save_dest = {}model = VGG16(weights='imagenet',include_top=False)x = model.outputx = GlobalAveragePooling2D()(x)x = Dense(512, activation='relu')(x)x = Dropout(0.5)(x)x = Dense(512, activation='relu')(x)x = Dropout(0.5)(x)out = Dense(5,activation='softmax')(x)model_final = Model(input = model.input,outputs=out)if full_freeze != 'N':for layer in model.layers[0:freeze_layers]:layer.trainable = Falsereturn model_final
我們將使用 ImageNet 上訓練的VGG16
的權(quán)重作為模型的初始權(quán)重,然后對模型進行微調(diào)。 我們還凍結(jié)了前幾層的權(quán)重(默認設置為10
),因為在 CNN 中,前幾層學會了檢測通用特征,例如邊緣,顏色成分等。 因此,特征在各個域之間不會有太大變化。 凍結(jié)層是指不訓練該層特定的權(quán)重。 我們可以嘗試凍結(jié)的層數(shù),并選擇提供最佳驗證分數(shù)的層。 由于我們正在執(zhí)行多類分類,因此已為輸出層選擇了 softmax 激活函數(shù)。
InceptionV3 遷移學習網(wǎng)絡
在以下代碼塊中定義了用于我們?nèi)蝿盏?code>InceptionV3網(wǎng)絡。 需要注意的一件事是,由于InceptionV3
是一個更深的網(wǎng)絡,因此我們可以擁有更多的初始層。 在數(shù)據(jù)可用性方面,不訓練每個模型中的所有層的想法還有另一個優(yōu)勢。 如果我們使用較少的數(shù)據(jù)訓練,則整個網(wǎng)絡的權(quán)重可能會導致過擬合。 凍結(jié)層會減少要訓練的權(quán)重數(shù),因此提供了一種形式的規(guī)則化。
由于初始層學習通用特征,而與問題的范圍無關,因此它們是凍結(jié)的最佳層。 我們還在全連接層中使用了丟棄,以防止過擬合:
def inception_pseudo(dim=224,freeze_layers=30,full_freeze='N'):model = InceptionV3(weights='imagenet',include_top=False)x = model.outputx = GlobalAveragePooling2D()(x)x = Dense(512, activation='relu')(x)x = Dropout(0.5)(x)x = Dense(512, activation='relu')(x)x = Dropout(0.5)(x)out = Dense(5,activation='softmax')(x)model_final = Model(input = model.input,outputs=out)if full_freeze != 'N':for layer in model.layers[0:freeze_layers]:layer.trainable = Falsereturn model_final
ResNet50 遷移學習網(wǎng)絡
可以類似于VGG16
和InceptionV3
網(wǎng)絡定義用于遷移學習的ResNet50
模型,如下所示:
def resnet_pseudo(dim=224,freeze_layers=10,full_freeze='N'):# model_save_dest = {}model = ResNet50(weights='imagenet',include_top=False)x = model.outputx = GlobalAveragePooling2D()(x)x = Dense(512, activation='relu')(x)x = Dropout(0.5)(x)x = Dense(512, activation='relu')(x)x = Dropout(0.5)(x)out = Dense(5,activation='softmax')(x)model_final = Model(input = model.input,outputs=out)if full_freeze != 'N':for layer in model.layers[0:freeze_layers]:layer.trainable = Falsereturn model_final
優(yōu)化器和初始學習率
Adam 優(yōu)化器(自適應矩估計器)用于實現(xiàn)隨機梯度下降高級版本的訓練。 Adam 優(yōu)化器會考慮成本函數(shù)中的曲率,同時使用動量來確保朝著良好的局部最小值穩(wěn)定發(fā)展。 對于眼前的問題,由于我們正在使用遷移學習,并且希望使用從預訓練的網(wǎng)絡中獲得的盡可能多的先前學習的特征,因此我們將使用較小的初始學習率0.00001
。 這將確保網(wǎng)絡不會丟失經(jīng)過預訓練的網(wǎng)絡學習到的有用特征,并根據(jù)當前問題的新數(shù)據(jù)將其微調(diào)至較不激進的最佳點。 Adam 優(yōu)化器可以定義如下:
adam = optimizers.Adam(lr=0.00001, beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=0.0)
beta_1
參數(shù)控制動量計算中當前梯度的貢獻,而beta_2
參數(shù)控制梯度歸一化中梯度平方的貢獻,這有助于解決成本函數(shù)中的曲率。
交叉驗證
由于訓練數(shù)據(jù)集很小,我們將執(zhí)行五重交叉驗證,以更好地了解模型對新數(shù)據(jù)進行泛化的能力。 我們還將在訓練中使用交叉驗證不同折中的所有五個模型進行推斷。 屬于類別標簽的測試數(shù)據(jù)點的概率將是所有五個模型的平均概率預測,其表示如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-MtrmfUDO-1681653992507)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/f22caa67-e998-4298-96ca-80c28ffa53a8.png)]
由于目的是預測實際類別而不是概率,因此我們將選擇具有最大概率的類別。 當我們使用基于分類的網(wǎng)絡和成本函數(shù)時,此方法有效。 如果我們將問題視為回歸問題,則該過程會有一些更改,我們將在后面討論。
根據(jù)驗證日志損失對檢查點進行建模
它始終是一個很好的做法,以保存模型時所選擇的評估驗證分數(shù)提高。 對于我們的項目,我們將跟蹤驗證日志損失,并隨著驗證得分在不同周期的提高而保存模型。 這樣,在訓練之后,我們將保存提供最佳驗證分數(shù)的模型權(quán)重,而不是保存我們停止訓練后的最終模型權(quán)重。 訓練將繼續(xù)進行,直到達到為訓練定義的最大周期數(shù),或者直到連續(xù)10
個周期的驗證日志損失都沒有減少為止。 當3
周期的驗證日志損失沒有改善時,我們還將降低學習率。 以下代碼塊可用于執(zhí)行學習率降低和檢查點操作:
reduce_lr = keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.50,patience=3, min_lr=0.000001)callbacks = [EarlyStopping(monitor='val_loss', patience=10, mode='min', verbose=1),CSVLogger('keras-5fold-run-01-v1-epochs_ib.log', separator=',', append=False),reduce_lr,ModelCheckpoint('kera1-5fold-run-01-v1-fold-' + str('%02d' % (k + 1)) + '-run-' + str('%02d' % (1 + 1)) + '.check',monitor='val_loss', mode='min', # mode must be set to max or keras will be confusedsave_best_only=True,verbose=1)]
如您在前面的代碼塊中所見,如果在3
(patience=3
)周期中驗證損失沒有改善,則學習率降低到一半(0.50
)。 同樣,如果在10
(patience = 10
)周期沒有減少驗證損失,我們將停止訓練(通過執(zhí)行EarlyStopping
)。 每當驗證日志損失減少時,都會保存模型,如以下代碼片段所示:
'kera1-5fold-run-01-v1-fold-' + str('%02d' % (k + 1)) + '-run-' + str('%02d' % (1 + 1)) + '.check'
在keras-5fold-run-01-v1-epochs_ib.log
日志文件中跟蹤訓練過程的每個周期的驗證日志損失,如果驗證日志損失有所改善,為了保存模型,請參考該文件, 或決定何時降低學習率或停止訓練。
通過使用keras save
函數(shù)在用戶定義的路徑中保存每個折疊中的模型,而在推理過程中,使用keras.load_model
函數(shù)將模型加載到內(nèi)存中。
Python 實現(xiàn)訓練過程
以下 Python 代碼塊顯示了訓練過程的端到端實現(xiàn)。 它由前面各節(jié)中討論的所有函數(shù)塊組成。 讓我們首先調(diào)用所需的所有 Python 包,如下所示:
import numpy as np
np.random.seed(1000)import os
import glob
import cv2
import datetime
import pandas as pd
import time
import warnings
warnings.filterwarnings("ignore")
from sklearn.model_selection import KFold
from sklearn.metrics import cohen_kappa_score
from keras.models import Sequential,Model
from keras.layers.core import Dense, Dropout, Flatten
from keras.layers.convolutional import Convolution2D, MaxPooling2D, ZeroPadding2D
from keras.layers import GlobalMaxPooling2D,GlobalAveragePooling2D
from keras.optimizers import SGD
from keras.callbacks import EarlyStopping
from keras.utils import np_utils
from sklearn.metrics import log_loss
import keras
from keras import __version__ as keras_version
from keras.applications.inception_v3 import InceptionV3
from keras.applications.resnet50 import ResNet50
from keras.applications.vgg16 import VGG16
from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers
from keras.callbacks import EarlyStopping, ModelCheckpoint, CSVLogger, Callback
from keras.applications.resnet50 import preprocess_input
import h5py
import argparse
from sklearn.externals import joblib
import json
導入所需的庫后,我們可以定義TransferLearning
類:
class TransferLearning:def __init__(self):parser = argparse.ArgumentParser(description='Process the inputs')parser.add_argument('--path',help='image directory')parser.add_argument('--class_folders',help='class images folder names')parser.add_argument('--dim',type=int,help='Image dimensions to process')parser.add_argument('--lr',type=float,help='learning rate',default=1e-4)parser.add_argument('--batch_size',type=int,help='batch size')parser.add_argument('--epochs',type=int,help='no of epochs to train')parser.add_argument('--initial_layers_to_freeze',type=int,help='the initial layers to freeze')parser.add_argument('--model',help='Standard Model to load',default='InceptionV3')parser.add_argument('--folds',type=int,help='num of cross validation folds',default=5)parser.add_argument('--outdir',help='output directory')args = parser.parse_args()self.path = args.pathself.class_folders = json.loads(args.class_folders)self.dim = int(args.dim)self.lr = float(args.lr)self.batch_size = int(args.batch_size)self.epochs = int(args.epochs)self.initial_layers_to_freeze = int(args.initial_layers_to_freeze)self.model = args.modelself.folds = int(args.folds)self.outdir = args.outdir
接下來,讓我們定義一個函數(shù),該函數(shù)可以讀取圖像并將它們調(diào)整為合適的大小,如下所示:
def get_im_cv2(self,path,dim=224):img = cv2.imread(path)resized = cv2.resize(img, (dim,dim), cv2.INTER_LINEAR)return resized# Pre Process the Images based on the ImageNet pre-trained model Image transformationdef pre_process(self,img):img[:,:,0] = img[:,:,0] - 103.939img[:,:,1] = img[:,:,0] - 116.779img[:,:,2] = img[:,:,0] - 123.68return img# Function to build X, y in numpy format based on the train/validation datasetsdef read_data(self,class_folders,path,num_class,dim,train_val='train'):print(train_val)train_X,train_y = [],[] for c in class_folders:path_class = path + str(train_val) + '/' + str(c)file_list = os.listdir(path_class) for f in file_list:img = self.get_im_cv2(path_class + '/' + f)img = self.pre_process(img)train_X.append(img)train_y.append(int(c.split('class')[1]))train_y = keras.utils.np_utils.to_categorical(np.array(train_y),num_class) return np.array(train_X),train_y
接下來,我們現(xiàn)在將定義三個用于遷移學習的模型,從InceptionV3
開始:
def inception_pseudo(self,dim=224,freeze_layers=30,full_freeze='N'):model = InceptionV3(weights='imagenet',include_top=False)x = model.outputx = GlobalAveragePooling2D()(x)x = Dense(512, activation='relu')(x)x = Dropout(0.5)(x)x = Dense(512, activation='relu')(x)x = Dropout(0.5)(x)out = Dense(5,activation='softmax')(x)model_final = Model(input = model.input,outputs=out)if full_freeze != 'N':for layer in model.layers[0:freeze_layers]:layer.trainable = Falsereturn model_final
然后,我們將定義用于遷移學習的ResNet50
模型:
def resnet_pseudo(self,dim=224,freeze_layers=10,full_freeze='N'):model = ResNet50(weights='imagenet',include_top=False)x = model.outputx = GlobalAveragePooling2D()(x)x = Dense(512, activation='relu')(x)x = Dropout(0.5)(x)x = Dense(512, activation='relu')(x)x = Dropout(0.5)(x)out = Dense(5,activation='softmax')(x)model_final = Model(input = model.input,outputs=out)if full_freeze != 'N':for layer in model.layers[0:freeze_layers]:layer.trainable = Falsereturn model_final
最后,我們將定義VGG16
模型:
def VGG16_pseudo(self,dim=224,freeze_layers=10,full_freeze='N'):model = VGG16(weights='imagenet',include_top=False)x = model.outputx = GlobalAveragePooling2D()(x)x = Dense(512, activation='relu')(x)x = Dropout(0.5)(x)x = Dense(512, activation='relu')(x)x = Dropout(0.5)(x)out = Dense(5,activation='softmax')(x)model_final = Model(input = model.input,outputs=out)if full_freeze != 'N':for layer in model.layers[0:freeze_layers]:layer.trainable = Falsereturn model_final
現(xiàn)在,讓我們定義訓練函數(shù),如下所示:
def train_model(self,train_X,train_y,n_fold=5,batch_size=16,epochs=40,
dim=224,lr=1e-5,model='ResNet50'):model_save_dest = {}k = 0kf = KFold(n_splits=n_fold, random_state=0, shuffle=True)for train_index, test_index in kf.split(train_X):k += 1 X_train,X_test = train_X[train_index],train_X[test_index]y_train, y_test = train_y[train_index],train_y[test_index]if model == 'Resnet50':model_final = self.resnet_pseudo(dim=224,freeze_layers=10,full_freeze='N')if model == 'VGG16':model_final = self.VGG16_pseudo(dim=224,freeze_layers=10,full_freeze='N') if model == 'InceptionV3':model_final = self.inception_pseudo(dim=224,freeze_layers=10,full_freeze='N')datagen = ImageDataGenerator(horizontal_flip = True,vertical_flip = True,width_shift_range = 0.1,height_shift_range = 0.1,channel_shift_range=0,zoom_range = 0.2,rotation_range = 20)adam = optimizers.Adam(lr=lr, beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=0.0)model_final.compile(optimizer=adam, loss= ["categorical_crossentropy"],metrics=['accuracy'])reduce_lr = keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.50, patience=3, min_lr=0.000001)callbacks = [EarlyStopping(monitor='val_loss', patience=10, mode='min', verbose=1),CSVLogger('keras-5fold-run-01-v1-epochs_ib.log', separator=',', append=False),reduce_lr,ModelCheckpoint('kera1-5fold-run-01-v1-fold-' + str('%02d' % (k + 1)) + '-run-' + str('%02d' % (1 + 1)) + '.check',monitor='val_loss', mode='min',save_best_only=True,verbose=1)]model_final.fit_generator(datagen.flow(X_train,y_train, batch_size=batch_size),steps_per_epoch=X_train.shape[0]/batch_size, epochs=epochs,verbose=1, validation_data= (X_test,y_test),callbacks=callbacks, class_weight= {0:0.012,1:0.12,2:0.058,3:0.36,4:0.43})model_name = 'kera1-5fold-run-01-v1-fold-' + str('%02d' % (k + 1)) + '-run-' + str('%02d' % (1 + 1)) + '.check'del model_finalf = h5py.File(model_name, 'r+')del f['optimizer_weights']f.close()model_final = keras.models.load_model(model_name)model_name1 = self.outdir + str(model) + '___' + str(k) model_final.save(model_name1)model_save_dest[k] = model_name1return model_save_dest
我們還將為保持數(shù)據(jù)集定義一個inference
函數(shù),如下所示:
def inference_validation(self,test_X,test_y,model_save_dest,n_class=5,folds=5):pred = np.zeros((len(test_X),n_class))for k in range(1,folds + 1):model = keras.models.load_model(model_save_dest[k])pred = pred + model.predict(test_X)pred = pred/(1.0*folds) pred_class = np.argmax(pred,axis=1) act_class = np.argmax(test_y,axis=1)accuracy = np.sum([pred_class == act_class])*1.0/len(test_X)kappa = cohen_kappa_score(pred_class,act_class,weights='quadratic')return pred_class,accuracy,kappa
現(xiàn)在,讓我們調(diào)用main
函數(shù),以觸發(fā)訓練過程,如下所示:
def main(self):start_time = time.time()self.num_class = len(self.class_folders)if self.mode == 'train':print("Data Processing..")file_list,labels= self.read_data(self.class_folders,self.path,self.num_class,self.dim,train_val='train')print(len(file_list),len(labels))print(labels[0],labels[-1])self.model_save_dest = self.train_model(file_list,labels,n_fold=self.folds,batch_size=self.batch_size,epochs=self.epochs,dim=self.dim,lr=self.lr,model=self.model)joblib.dump(self.model_save_dest,f'{self.outdir}/model_dict.pkl')print("Model saved to dest:",self.model_save_dest)else:model_save_dest = joblib.load(self.model_save_dest)print('Models loaded from:',model_save_dest)# Do inference/validationtest_files,test_y = self.read_data(self.class_folders,self.path,self.num_class,self.dim,train_val='validation')test_X = []for f in test_files:img = self.get_im_cv2(f)img = self.pre_process(img)test_X.append(img)test_X = np.array(test_X)test_y = np.array(test_y)print(test_X.shape)print(len(test_y))pred_class,accuracy,kappa = self.inference_validation(test_X,test_y,model_save_dest,n_class=self.num_class,folds=self.folds)results_df = pd.DataFrame()results_df['file_name'] = test_filesresults_df['target'] = test_yresults_df['prediction'] = pred_classresults_df.to_csv(f'{self.outdir}/val_resuts_reg.csv',index=False)print("-----------------------------------------------------")print("Kappa score:", kappa)print("accuracy:", accuracy) print("End of training")print("-----------------------------------------------------")print("Processing Time",time.time() - start_time,' secs')
我們可以更改幾個參數(shù),例如學習率,批量大小,圖像大小等,并且我們可以進行實驗以得出一個不錯的模型。 在訓練階段,模型位置保存在model_save_dest
字典中,該字典被寫入dict_model
文件中。
在推理階段,該模型僅基于訓練后的模型對新的測試數(shù)據(jù)進行預測。
可以如下調(diào)用名為TransferLearning.py
的用于遷移學習的腳本:
python TransferLearning.py --path '/media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/book AI/Diabetic Retinopathy/Extra/assignment2_train_dataset/' --class_folders '["class0","class1","class2","class3","class4"]' --dim 224 --lr 1e-4 --batch_size 16 --epochs 20 --initial_layers_to_freeze 10 --model InceptionV3 --folds 5 --outdir '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/'
腳本的輸出日志如下:
Model saved to dest: {1: '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/categorical/InceptionV3___1', 2: '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/categorical/InceptionV3___2', 3: '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/categorical/InceptionV3___3', 4: '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/categorical/InceptionV3___4', 5: '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/categorical/InceptionV3___5'}
validation
-----------------------------------------------------
Kappa score: 0.42969781637876836
accuracy: 0.5553973227000855
End of training
-----------------------------------------------------
Processing Time 26009.3344039917 secs
從日志中的結(jié)果可以看出,我們達到了不錯的交叉驗證精度,約為56%
,二次 Kappa 約為0.43
。
在此腳本中,我們將所有數(shù)據(jù)加載到內(nèi)存中,然后將增強圖像從ImageDataGenerator
饋送到模型進行訓練。 如果訓練圖像集很少和/或大小適中,那么將數(shù)據(jù)加載到內(nèi)存中可能不會引起太大關注。 但是,如果圖像語料庫很大和/或我們資源有限,那么將所有數(shù)據(jù)加載到內(nèi)存中將不是可行的選擇。 由于運行這些實驗的計算機具有 64 GB RAM,因此我們能夠毫無問題地訓練這些模型。 即使是 16 GB 的 RAM 計算機也可能不足以通過將所有數(shù)據(jù)加載到內(nèi)存中來運行這些實驗,并且您可能會遇到內(nèi)存錯誤。
問題是,我們是否需要一次將所有數(shù)據(jù)加載到內(nèi)存中?
由于神經(jīng)網(wǎng)絡適用于小型批量,因此我們只需要與一個小型批量對應的數(shù)據(jù)即可一次通過反向傳播訓練模型。 類似地,對于下一個反向傳播,我們可以丟棄與當前批量相對應的數(shù)據(jù),然后處理下一個批量。 因此,以某種方式,每個小型批量中的內(nèi)存需求僅是與該批量相對應的數(shù)據(jù)。 因此,我們可以在訓練時創(chuàng)建動態(tài)批量,從而避免在內(nèi)存較少的機器上訓練深度學習模型。 Keras 具有在訓練時創(chuàng)建動態(tài)批量的良好功能,我們將在下一節(jié)中討論。
訓練期間動態(tài)創(chuàng)建小批量
僅加載與小批量對應的數(shù)據(jù)的一種方法是通過從其位置隨機處理圖像來動態(tài)創(chuàng)建小批量。 小批量處理的圖像數(shù)量將等于我們指定的小批量大小。 當然,由于在訓練期間會動態(tài)創(chuàng)建小批量生產(chǎn),因此在訓練過程中會遇到一些瓶頸,但是這一瓶頸可以忽略不計。 特殊的包,例如keras
,具有有效的動態(tài)批量創(chuàng)建機制。 我們將在訓練過程中利用 keras 中的flow_from_directory
函數(shù)動態(tài)創(chuàng)建迷你批,以減少訓練過程的內(nèi)存需求。 我們?nèi)詫⒗^續(xù)使用ImageDataGenerator
進行圖像增強。 可以如下定義訓練生成器和驗證生成器。
通過將pre_process
函數(shù)作為輸入輸入到ImageDataGenerator
的preprocessing_function
中,完成從三個通道中減去平均圖像像素強度的圖像預處理步驟:
def pre_process(img):img[:,:,0] = img[:,:,0] - 103.939img[:,:,1] = img[:,:,0] - 116.779img[:,:,2] = img[:,:,0] - 123.68return imgtrain_file_names = glob.glob(f'{train_dir}/*/*')val_file_names = glob.glob(f'{val_dir}/*/*')train_steps_per_epoch = len(train_file_names)/float(batch_size)val_steps_per_epoch = len(val_file_names)/float(batch_size)train_datagen = ImageDataGenerator(horizontal_flip =True,vertical_flip =True,width_shift_range =0.1,height_shift_range = 0.1,channel_shift_range=0,zoom_range = 0.2,rotation_range = 20,preprocessing_function=pre_process)val_datagen =ImageDataGenerator(preprocessing_function=pre_process)train_generator =train_datagen.flow_from_directory(train_dir,target_size=(dim,dim),batch_size=batch_size,class_mode='categorical')val_generator =val_datagen.flow_from_directory(val_dir,target_size=(dim,dim),batch_size=batch_size,class_mode='categorical')print(train_generator.class_indices)joblib.dump(train_generator.class_indices,f'{self.outdir}/class_indices.pkl')
flow_from_directory
函數(shù)將一個圖像目錄作為輸入,并期望一個與該圖像目錄中的類有關的文件夾。 然后,它從文件夾名稱推斷類標簽。 如果圖像目錄的圖像目錄具有以下結(jié)構(gòu),則將類別推斷為0
,1
,2
,3
,4
,與類別文件夾'class0'
,'class1'
有關 ,'class2'
,'class3'
和'class4'
。
flow_from_directory
函數(shù)的其他重要輸入是batch_size
,target_size
和class_mode
。 target_size
用于指定要饋送到神經(jīng)網(wǎng)絡的圖像的大小,而class_mode
用于指定問題的性質(zhì)。 對于二分類,將class_mode
設置為二進制,而對于多分類,將其設置為categorical
。
接下來,我們將通過創(chuàng)建動態(tài)批量來訓練同一模型,而不是一次將所有數(shù)據(jù)加載到內(nèi)存中。 我們只需要使用flow_from_directory
選項創(chuàng)建一個生成器,然后將其綁定到數(shù)據(jù)擴充對象即可。 數(shù)據(jù)生成器對象可以如下生成:
# Pre processing for channel wise mean pixel subtraction
def pre_process(img):img[:,:,0] = img[:,:,0] - 103.939img[:,:,1] = img[:,:,0] - 116.779img[:,:,2] = img[:,:,0] - 123.68return img# Add the pre_process function at the end of the ImageDataGenerator,
#rest all of the data augmentation options
# remain the same. train_datagen =
ImageDataGenerator(horizontal_flip = True,vertical_flip = True,width_shift_range = 0.1,height_shift_range = 0.1,channel_shift_range=0,zoom_range = 0.2,rotation_range = 20,preprocessing_function=pre_process)# For validation no data augmentation on image mean subtraction preprocessing
val_datagen = ImageDataGenerator(preprocessing_function=pre_process)# We build the train generator using flow_from_directory
train_generator = train_datagen.flow_from_directory(train_dir,target_size=(dim,dim),batch_size=batch_size,class_mode='categorical')# We build the validation generator using flow_from_directory
val_generator = val_datagen.flow_from_directory(val_dir,target_size=(dim,dim),batch_size=batch_size,class_mode='categorical')
在前面的代碼中,我們將ImageDataGenerator
傳遞給執(zhí)行均值像素減法的附加任務,因為我們沒有控制將圖像數(shù)據(jù)加載到內(nèi)存中并通過pre_process
函數(shù)傳遞的任何控制權(quán)。 在preprocessing_function
選項中,我們可以為任何特定的預處理任務傳遞任何所需的自定義函數(shù)。
通過train_dir
和val_dir
,我們將訓練和驗證目錄傳遞給使用flow_with_directory
選項創(chuàng)建的訓練和驗證生成器。 生成器通過查看傳遞的訓練數(shù)據(jù)目錄(此處為train_dir
)中的類別文件夾數(shù)來識別類別數(shù)。 在基于batch_size
的訓練時間內(nèi),圖像根據(jù)指定的batch_size
讀入內(nèi)存
class_mode
幫助生成器識別其二分類還是多分類('categotical'
)。
詳細的實現(xiàn)在 GitHub 上的TransferLearning_ffd.py
文件夾中列出。
Python 腳本TransferLearning_ffd.py
可以按以下方式調(diào)用:
python TransferLearning_ffd.py --path '/media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/book AI/Diabetic Retinopathy/Extra/assignment2_train_dataset/' --class_folders '["class0","class1","class2","class3","class4"]' --dim 224 --lr 1e-4 --batch_size 32 --epochs 50 --initial_layers_to_freeze 10 --model InceptionV3 --outdir '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/'
作業(yè)運行的輸出日志結(jié)尾如下:
Validation results saved at : /home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/val_results.csv
[0 0 0 ... 4 2 2]
[0 0 0 ... 4 4 4]
Validation Accuracy: 0.5183708345200797
Validation Quadratic Kappa Score: 0.44422008110380984
如我們所見,通過重用現(xiàn)有網(wǎng)絡并在同一網(wǎng)絡上執(zhí)行遷移學習,我們能夠?qū)崿F(xiàn)不錯的0.44
二次方 Kappa。
分類結(jié)果
通過使用所有三個神經(jīng)網(wǎng)絡架構(gòu)VGG16
,ResNet50
和InceptionV3
進行分類。 對于該糖尿病性視網(wǎng)膜病用例,使用遷移學習網(wǎng)絡的InceptionV3
版本可獲得最佳結(jié)果。 如果是分類分類,我們只是將具有最大預測分類概率的分類轉(zhuǎn)換為預測嚴重性標簽。 但是,由于問題中的類別具有序數(shù)意義,因此我們可以利用 softmax 概率的方法之一是針對 softmax 概率對類別嚴重性進行期望并得出預期分數(shù)y_hat
如下 :
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zzLPKsCq-1681653992508)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/13040376-dbdd-411b-85da-5ae7e4231683.png)]
我們可以對分數(shù)進行排序,并確定三個閾值來確定圖像屬于哪個類別。 這些閾值可以通過將這些預期得分作為特征訓練輔助模型來選擇。 建議讀者按照這些思路進行試驗,看看是否有任何好處。
作為該項目的一部分,我們正在使用遷移學習來解決一個難題。 通過在給定的數(shù)據(jù)集上從頭開始訓練網(wǎng)絡,模型表現(xiàn)可能會更好。
測試時推斷
以下代碼可用于對未標記的測試數(shù)據(jù)進行推斷:
import keras
import numpy as np
import pandas as pd
import cv2
import os
import time
from sklearn.externals import joblib
import argparse# Read the Image and resize to the suitable dimension size
def get_im_cv2(path,dim=224):img = cv2.imread(path)resized = cv2.resize(img, (dim,dim), cv2.INTER_LINEAR)return resized# Pre Process the Images based on the ImageNet pre-trained model Image transformation
def pre_process(img):img[:,:,0] = img[:,:,0] - 103.939img[:,:,1] = img[:,:,0] - 116.779img[:,:,2] = img[:,:,0] - 123.68return img# Function to build test input data
def read_data_test(path,dim):test_X = [] test_files = []file_list = os.listdir(path) for f in file_list:img = get_im_cv2(path + '/' + f)img = pre_process(img)test_X.append(img)f_name = f.split('_')[0]test_files.append(f_name)return np.array(test_X),test_files
讓我們定義推理:
def inference_test(test_X,model_save_dest,n_class):folds = len(list(model_save_dest.keys()))pred = np.zeros((len(test_X),n_class))for k in range(1,folds + 1):model = keras.models.load_model(model_save_dest[k])pred = pred + model.predict(test_X)pred = pred/(1.0*folds) pred_class = np.argmax(pred,axis=1) return pred_class def main(path,dim,model_save_dest,outdir,n_class):test_X,test_files = read_data_test(path,dim)pred_class = inference_test(test_X,model_save_dest,n_class)out = pd.DataFrame()out['id'] = test_filesout['class'] = pred_classout['class'] = out['class'].apply(lambda x:'class' + str(x))out.to_csv(outdir + "results.csv",index=False)if __name__ == '__main__':parser = argparse.ArgumentParser(description='arguments')parser.add_argument('--path',help='path of images to run inference on')parser.add_argument('--dim',type=int,help='Image dimension size to process',default=224)parser.add_argument('--model_save_dest',help='location of the trained models')parser.add_argument('--n_class',type=int,help='No of classes')parser.add_argument('--outdir',help='Output DIrectory')args = parser.parse_args()path = args.pathdim = args.dimmodel_save_dest = joblib.load(args.model_save_dest)n_class = args.n_classoutdir = args.outdirmain(path,dim,model_save_dest,outdir,n_class)
執(zhí)行回歸而不是分類
我們在“損失函數(shù)公式”部分中討論的一件事是,類別標簽不是獨立的分類類別,但隨著糖尿病性視網(wǎng)膜病變情況的嚴重性增加,它們確實具有序數(shù)意義。 因此,值得通過定義的遷移學習網(wǎng)絡進行回歸,而不是進行分類,并觀察結(jié)果如何。 我們唯一需要更改的是輸出單元,從 softmax 到線性單元。 實際上,我們將其更改為 ReLU,因為我們希望避免出現(xiàn)負分數(shù)。 以下代碼塊顯示了回歸網(wǎng)絡的InceptionV3
版本:
def inception_pseudo(dim=224,freeze_layers=30,full_freeze='N'):model = InceptionV3(weights='imagenet',include_top=False)x = model.outputx = GlobalAveragePooling2D()(x)x = Dense(512, activation='relu')(x)x = Dropout(0.5)(x)x = Dense(512, activation='relu')(x)x = Dropout(0.5)(x)out = Dense(1,activation='relu')(x)model_final = Model(input = model.input,outputs=out)if full_freeze != 'N':for layer in model.layers[0:freeze_layers]:layer.trainable = Falsereturn model_final
與其像分類網(wǎng)絡中那樣使分類交叉熵(對數(shù)損失)最小化,不如使回歸網(wǎng)絡的均方誤差最小。 對于回歸問題最小化的成本函數(shù)如下,其中y_hat
是預測的標簽:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-aMdVRjDn-1681653992508)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/8e955dd6-0917-4280-936d-2df33cbe371e.png)]
一旦我們預測了回歸分數(shù),就將其舍入到最接近的嚴重性類別(零到四)。
使用 keras utils.sequence
作為生成器
Keras 具有一個名為keras.utils.sequence()
的優(yōu)秀批量生成器,可幫助您以極大的靈活性自定義批量創(chuàng)建。 實際上,使用keras.utils.sequence()
可以設計整個周期流水線。 我們將在此回歸問題中使用此工具以習慣該工具。 對于遷移學習問題,我們可以使用keras.utils.sequence()
設計生成器類,如下所示:
class DataGenerator(keras.utils.Sequence):'Generates data for Keras'def __init__(self,files,labels,batch_size=32,n_classes=5,dim=(224,224,3),shuffle=True):'Initialization'self.labels = labelsself.files = filesself.batch_size = batch_sizeself.n_classes = n_classesself.dim = dimself.shuffle = shuffleself.on_epoch_end()def __len__(self):'Denotes the number of batches per epoch'return int(np.floor(len(self.files) / self.batch_size))def __getitem__(self, index):'Generate one batch of data'# Generate indexes of the batchindexes = self.indexes[index*self.batch_size:(index+1)*self.batch_size]# Find list of files to be processed in the batchlist_files = [self.files[k] for k in indexes]labels = [self.labels[k] for k in indexes]# Generate dataX, y = self.__data_generation(list_files,labels)return X, ydef on_epoch_end(self):'Updates indexes after each epoch'self.indexes = np.arange(len(self.files))if self.shuffle == True:np.random.shuffle(self.indexes)def __data_generation(self,list_files,labels):'Generates data containing batch_size samples' # X : (n_samples, *dim, n_channels)# InitializationX = np.empty((len(list_files),self.dim[0],self.dim[1],self.dim[2]))y = np.empty((len(list_files)),dtype=int)# print(X.shape,y.shape)# Generate datak = -1for i,f in enumerate(list_files):# print(f)img = get_im_cv2(f,dim=self.dim[0])img = pre_process(img)label = labels[i]#label =keras.utils.np_utils.to_categorical(label,self.n_classes)X[i,] = imgy[i,] = label# print(X.shape,y.shape) return X,y
在前面的代碼中,我們使用keras.utils.Sequence
定義了DataGenerator
類。
我們定義數(shù)據(jù)生成器以接受圖像文件名,標簽,批量大小,類數(shù)以及我們希望將圖像調(diào)整大小的大小。 另外,我們指定是否希望將圖像在一個周期中的處理順序進行混排。
我們指定的函數(shù)是從keras.utils.Sequence
繼承的,因此,這些函數(shù)中每個函數(shù)的特定活動都無法在其他位置指定。len
函數(shù)用于計算一個周期中的批量數(shù)。
類似地,在on_epoch_end
函數(shù)中,我們可以指定在周期結(jié)束時要執(zhí)行的活動,例如打亂周期中要處理輸入的順序。 我們可以在每個周期創(chuàng)建一組不同的數(shù)據(jù)集進行處理。 當我們有大量數(shù)據(jù)并且我們不想在每個周期處理所有數(shù)據(jù)時,這通常很有用。 __getitem__
函數(shù)通過提取與特定于批量的所有數(shù)據(jù)點索引相對應的數(shù)據(jù)來幫助創(chuàng)建批量。 如果數(shù)據(jù)創(chuàng)建過程更復雜,則可以利用__data_generation
函數(shù)具有特定于批量中每個單獨數(shù)據(jù)點提取的邏輯。 例如,我們將與批量中的數(shù)據(jù)點索引相對應的文件名傳遞給__data_generation
函數(shù),以使用opencv
讀取每個圖像,并使用preprocess
函數(shù)對其進行預處理,我們必須進行平均像素減法。
基于回歸的遷移學習的訓練函數(shù)可以編碼如下:
def train_model(self,file_list,labels,n_fold=5,batch_size=16,
epochs=40,dim=224,lr=1e-5,model='ResNet50'):model_save_dest = {}k = 0kf = KFold(n_splits=n_fold, random_state=0, shuffle=True)for train_index,test_index in kf.split(file_list):k += 1file_list = np.array(file_list)labels = np.array(labels)train_files,train_labels = file_list[train_index],labels[train_index]val_files,val_labels = file_list[test_index],labels[test_index]if model == 'Resnet50':model_final = self.resnet_pseudo(dim=224,freeze_layers=10,full_freeze='N')if model == 'VGG16':model_final = self.VGG16_pseudo(dim=224,freeze_layers=10,full_freeze='N')if model == 'InceptionV3':model_final = self.inception_pseudo(dim=224,freeze_layers=10,full_freeze='N')adam = optimizers.Adam(lr=lr, beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=0.0)model_final.compile(optimizer=adam, loss=["mse"],metrics=['mse'])reduce_lr = keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.50,patience=3, min_lr=0.000001)early = EarlyStopping(monitor='val_loss', patience=10, mode='min', verbose=1)logger = CSVLogger('keras-5fold-run-01-v1-epochs_ib.log', separator=',', append=False)checkpoint = ModelCheckpoint('kera1-5fold-run-01-v1-fold-' + str('%02d' % (k + 1))+ '-run-' + str('%02d' % (1 + 1)) + '.check',monitor='val_loss', mode='min',save_best_only=True,verbose=1)callbacks = [reduce_lr,early,checkpoint,logger]train_gen = DataGenerator(train_files,train_labels,batch_size=32,n_classes=len(self.class_folders),dim=(self.dim,self.dim,3),shuffle=True)val_gen = DataGenerator(val_files,val_labels,batch_size=32,n_classes=len(self.class_folders),dim=(self.dim,self.dim,3),shuffle=True)model_final.fit_generator(train_gen,epochs=epochs,verbose=1,validation_data=(val_gen),callbacks=callbacks)model_name ='kera1-5fold-run-01-v1-fold-' + str('%02d' % (k + 1)) + '-run-' + str('%02d' % (1 + 1)) + '.check'del model_finalf = h5py.File(model_name, 'r+')del f['optimizer_weights']f.close()model_final = keras.models.load_model(model_name)model_name1 = self.outdir + str(model) + '___' + str(k)model_final.save(model_name1)model_save_dest[k] = model_name1return model_save_dest
從前面的代碼中我們可以看到,訓練生成器和驗證生成器是使用DataGenerator
類創(chuàng)建的,該類繼承了keras.utils.sequence
類。 推理函數(shù)可以編碼如下:
def inference_validation(self,test_X,test_y,model_save_dest,n_class=5,
folds=5):print(test_X.shape,test_y.shape)pred = np.zeros(test_X.shape[0])for k in range(1,folds + 1):print(f'running inference on fold: {k}')model = keras.models.load_model(model_save_dest[k])pred = pred + model.predict(test_X)[:,0]pred = predprint(pred.shape)print(pred)pred = pred/float(folds)pred_class = np.round(pred)pred_class = np.array(pred_class,dtype=int)pred_class = list(map(lambda x:4 if x > 4 else x,pred_class))pred_class = list(map(lambda x:0 if x < 0 else x,pred_class))act_class = test_yaccuracy = np.sum([pred_class == act_class])*1.0/len(test_X)kappa = cohen_kappa_score(pred_class,act_class,weights='quadratic')return pred_class,accuracy,kappa
從前面的代碼中我們可以看到,計算出每一折的預測平均值,并通過四舍五入預測分數(shù)將其轉(zhuǎn)換為最接近的嚴重性類別。 用于回歸的 Python 腳本位于 GitHub 鏈接中。 名稱為TransferLearning_reg.py
。 可以通過運行以下命令來調(diào)用相同的命令:
python TransferLearning_reg.py --path '/media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/book AI/Diabetic Retinopathy/Extra/assignment2_train_dataset/' --class_folders '["class0","class1","class2","class3","class4"]' --dim 224 --lr 1e-4 --batch_size 32 --epochs 5 --initial_layers_to_freeze 10 --model InceptionV3 --folds 5 --outdir '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/Regression/'
訓練的輸出日志如下:
Model saved to dest: {1: '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/Regression/InceptionV3___1', 2: '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/Regression/InceptionV3___2', 3: '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/Regression/InceptionV3___3', 4: '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/Regression/InceptionV3___4', 5: '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/Regression/InceptionV3___5'}
如我們所見,對應于5
折疊的5
模型已保存在我們指定的Regression
文件夾下。 接下來,我們可以對驗證數(shù)據(jù)集進行推斷,并查看回歸模型的運行情況。 可以如下調(diào)用相同的 Python 腳本:
python TransferLearning_reg.py --path '/media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/book AI/Diabetic Retinopathy/Extra/assignment2_train_dataset/' --class_folders '["class0","class1","class2","class3","class4"]' --dim 224 --lr 1e-4 --batch_size 32 --model InceptionV3 --outdir '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/Regression/' --mode validation --model_save_dest --'/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/Regression/model_dict.pkl' --folds 5
推斷結(jié)果如下:
Models loaded from: {1: '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/Regression/InceptionV3___1', 2: '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/Regression/InceptionV3___2', 3: '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/Regression/InceptionV3___3', 4: '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/Regression/InceptionV3___4', 5: '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/Regression/InceptionV3___5'}-----------------------------------------------------
Kappa score: 0.4662660860310418
accuracy: 0.661350042722871
End of training
-----------------------------------------------------
Processing Time 138.52878069877625 secs
從前面的日志中可以看到,假設我們剛剛使用回歸得分將模型映射到最接近的嚴重性條件,該模型可實現(xiàn)約 66% 的不錯的驗證準確率和0.466
的二次 Kappa 得分。 建議讀者進行實驗,看看是否基于預測的二級模型對進行評分,并且眼睛是左眼還是右眼比將樸素的評分映射到最近的嚴重性類別給出了更好的結(jié)果。
總結(jié)
在本章中,我們介紹了遷移學習的實際方面,以解決醫(yī)療保健領域的現(xiàn)實問題。 希望讀者通過盡可能嘗試定制這些示例來進一步構(gòu)建這些概念。
我們通過分類和基于回歸的神經(jīng)網(wǎng)絡獲得的準確率和 kappa 分數(shù)足以用于生產(chǎn)實現(xiàn)。 在第 3 章,“神經(jīng)機器翻譯”中,我們將致力于實現(xiàn)智能機器翻譯系統(tǒng),這是比本章介紹的主題更為高級的主題。 我期待您的參與。
三、神經(jīng)機器翻譯
機器翻譯簡而言之,是指使用計算機將文本從一種語言翻譯成另一種語言。 它是計算機語言學的一個分支,已經(jīng)發(fā)展了幾年。 目前,在美國,翻譯業(yè)是一個價值 400 億美元的產(chǎn)業(yè),并且在歐洲和亞洲也正在快速發(fā)展。 翻譯存在巨大的社會,政府,經(jīng)濟和商業(yè)需求,并且 Google,Facebook,eBay 等公司在其應用中廣泛使用它。 尤其是 Google 的神經(jīng)翻譯系統(tǒng)是目前最先進的翻譯系統(tǒng)之一,能夠僅用一種模型執(zhí)行多種語言的翻譯。
早期的機器翻譯系統(tǒng)首先將文本中的單詞和短語翻譯成所需目標語言的相關替代詞。 但是,由于以下原因,通過這些簡單的技術(shù)實現(xiàn)的翻譯質(zhì)量受到限制:
- 從源語言到目標語言的詞到詞映射并非始終可用。
- 即使在源語言和目標語言之間確實存在精確的詞對詞映射,這些語言的句法結(jié)構(gòu)通常也不相互對應。 機器翻譯中的此問題通常稱為對齊錯誤。
但是,隨著循環(huán)神經(jīng)網(wǎng)絡(RNN)架構(gòu)的最新進展,機器翻譯不僅提供了更高的翻譯質(zhì)量,而且還提供了更高的翻譯質(zhì)量。這種系統(tǒng)的復雜性遠遠小于傳統(tǒng)系統(tǒng)。
機器翻譯系統(tǒng)大致可分為三類:基于規(guī)則的機器翻譯,統(tǒng)計機器翻譯和神經(jīng)機器翻譯。
在本章中,我們將介紹以下主題:
- 基于規(guī)則的機器翻譯
- 統(tǒng)計機器學習系統(tǒng)
- 神經(jīng)機器翻譯
- 序列到序列的神經(jīng)翻譯
- 神經(jīng)翻譯的損失函數(shù)
技術(shù)要求
您將需要具有 Python 3,TensorFlow 和 Keras 的基礎知識。
可以在 GitHub 上找到本章的代碼文件
觀看以下視頻,查看運行中的代碼。
基于規(guī)則的機器翻譯
基于經(jīng)典規(guī)則的機器翻譯系統(tǒng)嚴重依賴于將文本從源語言轉(zhuǎn)換為目標語言的規(guī)則。 這些規(guī)則通常由語言學家創(chuàng)建,通常在句法,語義和詞匯層面上起作用。 傳統(tǒng)的基于規(guī)則的機器翻譯系統(tǒng)通常分為三個階段:
- 分析階段
- 詞匯翻譯階段
- 生成階段
圖 3.1 是典型的基于規(guī)則的機器翻譯系統(tǒng)的流程圖:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ekRab2Oe-1681653992509)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/87104398-8e42-45a6-bf08-3dbad0d1e745.png)]
圖 3.1:基于規(guī)則的機器翻譯系統(tǒng)的流程圖
分析階段
基于規(guī)則的機器翻譯的第一階段是分析階段,其中分析源語言文本以提取與形態(tài),詞性,命名實體識別以及詞義歧義有關的信息。 形態(tài)信息涉及單詞的結(jié)構(gòu),詞干的派生方式,詞根的檢測等。 詞性標記器使用可能的語音標記來標記文本中的每個單詞,例如名詞,動詞,副詞,形容詞等。 接下來是命名實體識別(NER)任務,該任務嘗試將命名實體分類到預定義的存儲桶中,例如人員名稱,位置,組織名稱,以此類推。 NER 之后是單詞義消歧,它試圖識別句子中如何使用特定單詞。
詞匯翻譯階段
詞匯翻譯階段位于分析階段之后,并分為兩個階段:
- 單詞翻譯:在單詞翻譯中,使用雙語翻譯詞典將在分析階段導出的源根單詞翻譯為相應的目標根單詞。
- 語法翻譯:在語法翻譯階段,將進行語法修飾,包括翻譯后綴等。
生成階段
在生成階段,將對翻譯的文本進行驗證和更正,以便在將最終翻譯的文本作為附件提供之前,就動詞而言,就動詞,性別以及主語和賓語相對于動詞的同意而言,它是正確的。 輸出。 在每個步驟中,機器翻譯系統(tǒng)都使用預定義的詞典。 對于基于規(guī)則的機器翻譯系統(tǒng)的最低限度的實現(xiàn),需要以下詞典:
- 用于源語言形態(tài)分析的詞典
- 雙語詞典,包含源語言單詞到目標語言對應單詞的映射
- 包含用于目標單詞生成的目標語言形態(tài)信息的字典
統(tǒng)計機器學習系統(tǒng)
統(tǒng)計機器翻譯系統(tǒng)通過在給定源文本的情況下最大化其條件概率來選擇目標文本。 例如,假設我們有一個源文本s
,并且我們想要導出目標語言中的最佳等效文本t
。 可以如下得出:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-gTBtiv74-1681653992509)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/d5a5fe04-9f29-433c-a9bf-83303d4e5232.png)]
(1)
中P(t / s)
的公式可使用貝葉斯定理擴展如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-9uSQcbRl-1681653992509)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/f9bcb9c3-e8e5-4d9b-bddc-cf07222a55ee.png)]
對于給定的源句子,P(s)
將是固定的,因此找到最佳目標翻譯結(jié)果如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-inJPj9Mq-1681653992510)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/3e0fe8f4-e700-48f5-95a8-07ce870ed1c2.png)]
您可能想知道為什么直接將P(s / t)P(t)
最大化而不是P(t / s)
會帶來優(yōu)勢。 通常,通過將問題分為兩個部分來避免P(t / s)
下很可能出現(xiàn)的格式錯誤的句子,即P(s / t)
和P(t)
,如上式所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-87qFUKBH-1681653992510)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/f4f8506a-2b5f-4c49-a434-12012a2dcf97.png)]
圖 3.2:統(tǒng)計機器翻譯架構(gòu)
從上圖可以看出,統(tǒng)計機器翻譯問題已分解為三個不同的子問題,如上所述:
- 為目標建立語言模型,使我們能夠估計
P(t)
- 從目標語言到源語言構(gòu)建翻譯模型,這使我們能夠估計
P(s / t)
- 對可能的目標翻譯進行搜索,然后選擇最大化
P(s / t)P(t)
的翻譯
我們將討論這三個主題中的每一個,因為這些函數(shù)是任何機器翻譯問題所固有的。
語言模型
在語言模型中,句子的概率表示為各個單詞或短語的條件概率的乘積。 假設句子t
由單詞t[1], t[2], ..., t[n]
。 根據(jù)概率的鏈式規(guī)則,句子t
的概率可以表示為:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zyAfBNC0-1681653992510)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/5b33f286-e730-48d1-83d1-f491731ad41f.png)]
根據(jù)上述公式構(gòu)建語言模型將需要我們估計幾個順序的條件概率,這在實際中是不可能的。 為了使問題在計算上可行,一個簡單的假設是僅根據(jù)前一個單詞而不是之前的所有單詞來對單詞進行條件處理。 該假設也稱為馬爾可夫假設,該模型稱為二元模型。 根據(jù)二元模型,單詞的條件概率可以表示為:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-vWD7nydz-1681653992510)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/fed81518-1cd1-4beb-a689-4f153de98fea.png)]
為了進一步改善結(jié)果,我們可以使用三元模型,該模型將句子中的特定單詞置于其前面兩個單詞的條件下,如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-1CPQeZkd-1681653992511)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/b00765b5-99f5-4dc3-a9cd-32f3ae13a51f.png)]
對于二元模型,給定訓練語料庫中偶對(t[1], t[2])
的總數(shù),計算當前單詞t[1]
的下一個單詞t[2]
的條件概率,并根據(jù)語料庫中t[1]
出現(xiàn)的次數(shù)歸一化:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-FbTLTLHs-1681653992511)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/4011e558-b4fc-468a-89cc-3ae457494668.png)]
對于三元模型,當前單詞t[3]
在兩個單詞t[1], t[2]
之前的條件概率,可以估計如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zNSherBq-1681653992511)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/a4e37492-f2ae-4999-b8d1-53d9afac5fba.png)]
超出三字母組合模型通常會導致稀疏性。 即使對于雙字母組模型,我們也可能會缺少幾個雙字母組的條件概率,因為它們沒有出現(xiàn)在訓練語料庫中。 但是,那些缺失的二元組可能非常相關,并且估計其條件概率非常重要。 不用說,n
元模型傾向于估計出現(xiàn)在訓練數(shù)據(jù)中的單詞對的高條件概率,而忽略沒有出現(xiàn)的單詞。
語言模型的困惑度
困惑度指標用于評估語言模型的有用性。 假設我們已經(jīng)在訓練語料庫上訓練了一個語言模型,并且使句子或文本上的學習概率模型為P(.)
。 P(.)
的困惑度是根據(jù)與訓練語料庫相同的總體抽取的測試集語料庫進行評估的。 如果我們用M
詞表示測試集語料庫,請說(w[1], w[2], ..., w[M])
,則模型在測試集序列上的困惑性表示如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8E8aAjEF-1681653992511)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/3ae0b20f-358d-46c3-a2df-addd87872199.png)]
如圖所示H
的表達式可衡量每字的不確定性:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-dTevXGjh-1681653992512)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/1628bf07-cbf5-4c9d-a63d-de3baa23d975.png)]
根據(jù)語言模型,我們可以按如下方式分解測試語料庫的概率表達式:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Plv2THm9-1681653992512)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/f6cd48b3-1beb-41ae-aedb-3cc8382266ca.png)]
如果我們將測試集中第i
個詞的概率表示為條件,則以先前的字為條件P(s[i])
,則測試語料庫的概率如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-O1otMiD7-1681653992512)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/9028a0b4-506c-49df-a783-869bb5c75426.png)]
這里P(s[i]) = P(w[i] / w[1], w[2], ..., w[i-1])
。 將(1)
和(4)
結(jié)合起來,困惑可以寫成如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-CmbRLDB9-1681653992512)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/785a9853-f2d3-4c28-a718-aeb9e2fcb6de.png)]
假設我們有一個語言模型P(.)
和一個進行評估的測試集I love Machine Learning
。 根據(jù)語言模型,測試集的概率如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-kYl71z8M-1681653992512)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/82e0b0cf-e3ee-43a0-962e-91bf737abd0a.png)]
如果語言模型的訓練語料也是I love Machine Learning
,則測試集的概率為 1,導致對數(shù)概率為0
,且困惑為1
。 這意味著該模型可以完全確定地生成下一個單詞。
另一方面,如果我們有一個更現(xiàn)實的訓練語料庫,其詞匯量為N = 20,000
,并且訓練數(shù)據(jù)集在測試數(shù)據(jù)集上的困惑度為 100,那么平均而言, 為了預測序列中的下一個單詞,我們將搜索范圍從 20,000 個單詞縮小到 100 個單詞。
讓我們看一下最壞的情況,在這種情況下,我們設法建立一個模型,其中每個單詞都與序列中的先前單詞無關:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ci3hqJ9U-1681653992513)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/a231a609-f58f-4faa-a081-64addf74b643.png)]
對于M
個單詞的測試集,使用(5)
的困惑如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-97PD2zlC-1681653992513)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/ba0cff31-c3c7-49ec-9967-d48fdcdb8ae4.png)]
如果我們像以前一樣有N = 20,000
,那么要預測序列中的任何單詞,就需要考慮詞匯表中的所有N
個單詞,因為它們都是一樣的。 在這種情況下,我們無法減少單詞的平均搜索空間來預測序列中的單詞。
翻譯模型
翻譯模型可以視為機器翻譯模型的核心。 在翻譯模型中,我們需要估計概率P(s / t)
,其中s
是源語言句子,t
是目標語言的句子。 在這里,給出了源句,而目標是我們試圖找出的句子。 因此,該概率可以稱為源句子給定目標句子的可能性。 例如,假設我們正在將源文本從法語翻譯為英語。 因此,在P(s / t)
的情況下,我們的目標語言是法語,我們的源語言是英語,而在實際翻譯的情況下,即P(s / t)P(t)
,我們的源語言是法語,而我們的目標語言是英語。
該翻譯主要包括三個部分:
- 豐富度:并非源語言中的所有單詞在目標語言中都有對應的單詞。 例如,英語句子
Santanu loves math
法語翻譯為Santanu aim les maths
。 如我們所見,英語中的math
一詞已翻譯成法文的兩個單詞,即les maths
。 形式上,豐富度定義為目標語言中源語言單詞生成的單詞數(shù)量上的概率分布,并且可以表示為P(n / w[s])
,其中w[s]
代表源詞。 而不是使用硬編碼的數(shù)字n
,而是使用概率分布,因為相同的單詞可能會根據(jù)上下文生成不同長度的翻譯。 - 失真:對于任何機器翻譯系統(tǒng),源句子和目標句子之間的單詞到單詞的對應關系都很重要。 但是,源語言句子中單詞的位置可能并不總是與目標語言句子中對應單詞的位置完全同步。 失真通過概率函數(shù)
P(p[t], p[s], l)
覆蓋了對齊的概念,其中p[t]
和p[t]
分別代表目標詞和源詞的位置,而l
代表目標句子的長度。 如果源語言是英語,目標語言是法語,則P(p[t] / p[s], l)
表示位置p[s]
的英語單詞對應于位置p[s]
中的法語單詞,其長度為l
。 - 單詞到單詞的翻譯:最后,我們來進行單詞到單詞的翻譯,這通常由給定源語言單詞的目標語言單詞的概率分布表示。 對于給定的源語言單詞
w[s]
,概率可以表示為P(w[t] / w[s])
,其中w[t]
代表目標語言單詞。
對于語言模型,需要在訓練過程中估計生育率,失真率和單詞到單詞的翻譯率。
現(xiàn)在,讓我們回到估計概率P(s / t)
的原始問題。 如果我們用E
表示英語句子,而用F
表示法語句子,則需要計算P(F / E)
的概率。 為了考慮單詞的對齊方式,我們將概率修改為P(F, a / E)
,其中a
表示目標句子在法語中的對齊方式。 這種一致性將有助于我們注入有關畸變和生育能力的信息。
讓我們通過一個示例來計算概率P(F, a / E)
。 讓一個特定的英語句子由五個單詞的句子表示。 e = (e[1], e[2], ..., e[5])
,實際上這是實際法語句子的正確翻譯。 f = (f[1], f[2], ..., f[6])
。 另外,讓單詞的相應對齊方式如下:
e1 -> f6
e2 ->
不對應法語中的任何單詞e3 -> f3, f4
e4 -> f1
e5 -> f2
f5 ->
不對應英語中的任何單詞
由于這是一個概率模型,因此該算法將嘗試使用具有不同對齊方式的不同英語句子,在給定法語句子的情況下,其中具有正確對齊方式的正確英語句子應該具有最高的概率。
讓我們將第一個英語單詞考慮為e[1]
-它與法語單詞f[6]
對齊,并且還會產(chǎn)生一個法語單詞,如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8vID9gvO-1681653992513)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/688b2388-8434-4d98-8d00-66ea374148b9.png)]
現(xiàn)在,讓我們將對齊方式作為兩個成分的組合:失真a[d]
和豐富度f[d]
。 (1)
中的表達式可以重寫如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-q6WekdRR-1681653992513)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/edaf41f6-4172-4397-b791-f714571e57ca.png)]
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Keu1uNRj-1681653992513)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/546bd7e5-37e3-44ba-a006-3378e4664053.png)]
如果我們仔細觀察, P(f[5] / e[1])
就是翻譯概率, P(a[f] / e[1])
是豐富度,而 P(a[d] / e[1], f[5])
是失真概率。 我們需要針對英語句子中與給定法語句子的所有比對中的所有給定英語單詞進行此活動,以計算P(F, a / E)
。 最后,我們需要采用最佳英語句子E_hat
和對齊方式a_hat
,以使P(F, a / E)P(E)
的概率最大化。 如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-vLTDHXyk-1681653992514)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/396ee058-a561-4b7e-a60d-1a568bbb52b6.png)]
這里要注意的一件事是,嘗試不同的對齊方式和不同的可能單詞翻譯以尋求最佳翻譯可能在計算上變得棘手,因此,需要部署巧妙的算法以在最短的時間內(nèi)找到最佳翻譯。
神經(jīng)機器翻譯
神經(jīng)機器翻譯(NMT)使用深度神經(jīng)網(wǎng)絡來執(zhí)行從源語言到目標語言的機器翻譯。 神經(jīng)翻譯機將源語言中的文本作為輸入序列,并將其編碼為隱藏的表示形式,然后將其解碼回以生成目標語言中的翻譯文本序列。 該 NMT 系統(tǒng)的主要優(yōu)勢之一是,整個機器翻譯系統(tǒng)可以從端到端一起進行訓練,這與基于規(guī)則的機器翻譯系統(tǒng)和統(tǒng)計機器翻譯系統(tǒng)不同。 一般而言,在[ 神經(jīng)翻譯機架構(gòu)。
NMT 與其他傳統(tǒng)方法相比的一些優(yōu)點如下:
- 基于損失函數(shù)對 NMT 模型的所有參數(shù)進行端到端訓練,從而降低了模型的復雜性
- 這些 NMT 模型使用的上下文比傳統(tǒng)方法大得多,因此產(chǎn)生了更準確的翻譯
- NMT 模型可以更好地利用單詞和短語的相似性
- RNN 允許生成更好質(zhì)量的文本,因此,相對于已翻譯文本的語法而言,翻譯更加準確
編碼器-解碼器模型
下圖說明了一種神經(jīng)翻譯機的架構(gòu),該結(jié)構(gòu)使用一個 LSTM 作為編碼器,將輸入源語言序列編碼為最終隱藏狀態(tài)h[f]
和最終存儲單元狀態(tài)c[f]
。 最終的隱藏狀態(tài)和單元狀態(tài)[h[f]; c[f]]
將捕獲整個輸入序列的上下文。 因此,[h[f]; c[f]]
成為解碼器網(wǎng)絡可適應的良好候選者。
此隱藏狀態(tài)和單元狀態(tài)信息[h[f]; c[f]]
作為初始隱藏狀態(tài)和單元狀態(tài)被饋送到解碼器網(wǎng)絡,然后解碼器在目標序列上訓練,其中輸入目標序列相對于輸出目標序列滯后一。 根據(jù)解碼器,輸入序列的第一個字是偽字[START]
,而輸出標簽是字c'est
。 解碼器網(wǎng)絡僅被訓練為一種生成語言模型,在任何時候,輸出標簽t
都是相對于輸入的下一個單詞,即y[t] = x[t + 1]
。 唯一的新變化是編碼器的最終隱藏狀態(tài)和單元狀態(tài)(即[h[f]; c[f]]
) 解碼器的隱藏狀態(tài)和單元狀態(tài)為翻譯提供內(nèi)容。
這意味著可以將訓練過程視為為目標語言(由解碼器表示)建立語言模型,該模型以代表源語言的編碼器的隱藏狀態(tài)為條件:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-l5Ahvkif-1681653992514)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/6b5fdbd2-9d77-4305-b64c-3a62dadede35.png)]
圖 3.3:神經(jīng)機器翻譯系統(tǒng)的高級編碼器-解碼器架構(gòu)
如果T
是與源語言文本S
相對應的目標語言文本,那么為了進行訓練,我們只是試圖使P[w](T[s+1] / S, T)
的對數(shù)概率相對于W
最大化,其中T[s+1]
表示平移一個時間步驟的目標語言文本,W
表示編碼器-解碼器架構(gòu)模型參數(shù)。
現(xiàn)在我們已經(jīng)討論了編碼器-解碼器 NMT 的訓練過程,現(xiàn)在我們將研究如何在推理過程中使用訓練后的模型。
將編碼器-解碼器模型用于推理
在 NMT(神經(jīng)翻譯機)上進行推理的架構(gòu)流程與訓練 NMT 略有不同。 以下是使用 NMT 執(zhí)行推理的架構(gòu)流程:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZZm2a8I4-1681653992514)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/c8e700f7-0973-4a7a-b1d8-1f00a2ef91e7.png)]
圖 3.4:基于編碼器/解碼器的神經(jīng)機器翻譯的推理
在推理過程中,源語言輸入序列被饋送到編碼器網(wǎng)絡并產(chǎn)生最終的隱藏狀態(tài)和單元狀態(tài), [h[f]; c[f]]
饋給解碼器的隱藏狀態(tài)和單元狀態(tài)。 解碼器被轉(zhuǎn)換為單個時間步,饋送到解碼器的第一個輸入是偽[START]
字。 因此,基于[h[f]; c[f]]
和初始虛擬字[START]
,解碼器將輸出一個字w
,以及新的隱藏狀態(tài)和單元格狀態(tài)[h[d]; c[d]]
。 這個單詞w
再次以新的隱藏狀態(tài)和單元狀態(tài)饋送到解碼器。 [h[d]; c[d]]
生成下一個單詞。 重復此過程,直到遇到序列結(jié)束字符。
實現(xiàn)序列到序列的神經(jīng)翻譯機
我們將建立一個神經(jīng)機器翻譯系統(tǒng),該系統(tǒng)將學習將英語短句子翻譯成法語。 為此,我們將使用位于這個頁面上的英語到法語文本語料庫(fra-eng/fra.txt
)。
處理輸入數(shù)據(jù)
文本數(shù)據(jù)不能直接輸入任何神經(jīng)網(wǎng)絡,因為神經(jīng)網(wǎng)絡只能理解數(shù)字。 我們將每個單詞視為一個單編碼的向量,其長度等于每個語料庫中出現(xiàn)的單詞的數(shù)量。 如果英語語料庫包含 1,000 個單詞,則一鍵編碼的向量v[e]
的大小為 1,000,即v[e] ∈ R^(1000 x 1)
。
我們將通讀英語和法語語料庫,并確定它們各自中唯一詞的數(shù)量。 我們還將通過索引來表示單詞,對于該單詞的單編碼向量,該單詞對應的索引將設置為 1,而其余索引將設置為 0。 例如,假設在英語語料庫中,我們有四個詞:Global warming is real
。 我們可以如下定義每個單詞的索引:
單詞 | 索引 |
---|---|
global | 0 |
warming | 1 |
is | 2 |
real | 3 |
在這種情況下,我們可以將單詞global
的單熱編碼向量定義為[1,0,0,0]^T
。 類似地,real
的一鍵編碼向量可以表示為[1,0,0,0]^T
。
現(xiàn)在,轉(zhuǎn)到每個句子或記錄的源語言輸入,我們將有一系列單詞表示為一個單編碼的向量序列。 下一個明顯的問題是如何管理序列長度,因為這可能會有所不同。 最普遍接受的方法是使固定序列長度等于語料庫中句子的最大序列長度,或者達到預定的合理長度。 我們將使用目標語句兩次:一次作為解碼器的翻譯輸出序列,一次作為解碼器的輸入序列,唯一的區(qū)別是輸出序列比輸入序列提前一個時間步長。 因此,輸入目標序列中的第一個單詞將是偽單詞[START]
,而輸出目標序列中的最后一個單詞將是偽單詞[END]
,標記句子序列的結(jié)尾。
如果目標法語句子是Je m'appelle Santanu
,則解碼器中的輸入目標和輸出目標序列如下:
[START],[Je],[m’appelle] [Santanu]
[Je],[m’appelle] [Santanu][END]
我們選擇用制表符表示[START]
,用下一行表示[END]
。
我們將數(shù)據(jù)創(chuàng)建活動分為三個部分:
- 讀取源文件(英文)和目標文本(法文)的輸入文件
- 從源語言和目標語言文本構(gòu)建詞匯表
- 將輸入的英語和法語語料庫處理為數(shù)字表示形式,以便可以在神經(jīng)機器翻譯網(wǎng)絡中使用它們
此處說明的read_input_file
函數(shù)可用于閱讀源語言和目標語言文本:
def read_input_file(self,path,num_samples=10e13):input_texts = []target_texts = []input_words = set()target_words = set()with codecs.open(path, 'r', encoding='utf-8') as f:lines = f.read().split('\n')for line in lines[: min(num_samples, len(lines) - 1)]:input_text, target_text = line.split('\t')# \t as the start of sequence target_text = '\t ' + target_text + ' \n'# \n as the end of sequenceinput_texts.append(input_text)target_texts.append(target_text)for word in input_text.split(" "):if word not in input_words:input_words.add(word)for word in target_text.split(" "):if word not in target_words:target_words.add(word)return input_texts,target_texts,input_words,target_words
vocab_generation
函數(shù)可用于為源語言和目標語言構(gòu)建單詞的詞匯集:
def vocab_generation(self,path,num_samples,verbose=True):input_texts,target_texts,input_words,target_words = self.read_input_file(path,num_samples)input_words = sorted(list(input_words))target_words = sorted(list(target_words))self.num_encoder_words = len(input_words)self.num_decoder_words = len(target_words)self.max_encoder_seq_length = max([len(txt.split(" ")) for txt in input_texts])self.max_decoder_seq_length =max([len(txt.split(" ")) for txt in target_texts])if verbose == True:print('Number of samples:', len(input_texts))print('Number of unique input tokens:',self.num_encoder_words)print('Number of unique output tokens:',self.num_decoder_words)print('Max sequence length for inputs:',self.max_encoder_seq_length)print('Max sequence length for outputs:',self.max_decoder_seq_length)self.input_word_index =dict([(word, i) for i, word in enumerate(input_words)])self.target_word_index = dict([(word, i) for i, word in enumerate(target_words)])self.reverse_input_word_dict = dict((i, word) for word, i in self.input_word_index.items())self.reverse_target_word_dict = dict((i, word) for word, i in self.target_word_index.items())
process_input
函數(shù)利用先前函數(shù)中構(gòu)建的輸入和目標文本以及詞匯表,將文本數(shù)據(jù)轉(zhuǎn)換為數(shù)字形式,以供神經(jīng)翻譯機架構(gòu)使用。 process_input
函數(shù)的代碼如下:
def process_input(self,input_texts,target_texts=None,verbose=True):encoder_input_data = np.zeros((len(input_texts), self.max_encoder_seq_length, self.num_encoder_words), dtype='float32')decoder_input_data = np.zeros((len(input_texts), self.max_decoder_seq_length, self.num_decoder_words), dtype='float32')decoder_target_data = np.zeros((len(input_texts), self.max_decoder_seq_length, self.num_decoder_words), dtype='float32')if self.mode == 'train':for i, (input_text, target_text) in enumerate(zip(input_texts,target_texts)):for t, word in enumerate(input_text.split(" ")):try:encoder_input_data[i, t, self.input_word_index[word]] = 1.except:print(f'word {word} encoutered for the 1st time, skipped')for t, word in enumerate(target_text.split(" ")):# decoder_target_data is ahead of decoder_input_databy one timestepdecoder_input_data[i, t, self.target_word_index[word]] = 1.if t > 0:# decoder_target_data will be ahead by one timestep#and will not include the start character.try:decoder_target_data[i, t - 1, self.target_word_index[word]] = 1.except:print(f'word {word} encoutered for the 1st time,skipped')return encoder_input_data,decoder_input_data,decoder_target_data,np.array(input_texts),np.array(target_texts)else:for i, input_text in enumerate(input_texts):for t, word in enumerate(input_text.split(" ")):try:encoder_input_data[i, t, self.input_word_index[word]] = 1.except:print(f'word {word} encoutered for the 1st time, skipped')return encoder_input_data,None,None,np.array(input_texts),None
encoder_input_data
變量將包含輸入源數(shù)據(jù),并且將是記錄數(shù),時間步數(shù)以及每個維度的維數(shù)的三維數(shù)組。 熱編碼向量。 類似地,decoder_input_data
將包含輸入目標數(shù)據(jù),而decoder_target_data
將包含目標標簽。 在執(zhí)行上述函數(shù)后,將生成訓練機器翻譯系統(tǒng)所需的所有相關輸入和輸出。 以下代碼塊包含與使用40000
樣本執(zhí)行vocab_generation
函數(shù)有關的顯示統(tǒng)計信息:
('Number of samples:', 40000)
('Number of unique input tokens:', 8658)
('Number of unique output tokens:', 16297)
('Max sequence length for inputs:', 7)
('Max sequence length for outputs:', 16)
從前面的統(tǒng)計數(shù)據(jù)可以看出,40000
語料庫中輸入的英語單詞的數(shù)量為8658
,文本句子的數(shù)量為8658
,而對應的法語單詞的數(shù)量為16297
。 這表明以下事實:每個英語單詞平均發(fā)出大約兩個法語單詞。 同樣,我們看到英語句子中的最大單詞數(shù)為7
,而法語句子中的最大單詞數(shù)為14
(如果您排除了我們在法語句子中添加的[START]
和[END]
字符) 訓練目的。 這也證實了以下事實:平均而言,每個要翻譯的英語句子將產(chǎn)生雙倍的單詞數(shù)。
讓我們看一下神經(jīng)翻譯機的輸入和目標的形狀:
('Shape of Source Input Tensor:',(40000, 7, 8658))
('Shape of Target Input Tensor:',(40000, 16, 16297))
(Shape of Target Output Tensor:',(40000, 16, 16297))
編碼器數(shù)據(jù)的形狀為(40000, 7, 8658)
,其中第一維用于源語言語句的數(shù)量,第二維用于時間步長的數(shù)量,最終維是單次熱編碼向量的大小, 是8658
,與英語詞匯中的8658
源語言單詞相對應。 類似地,對于目標輸入和輸出張量,我們看到一熱編碼向量的大小為16297
,與法語詞匯中的16297
單詞相對應。 法語句子的時間步長為16
。
定義神經(jīng)機器翻譯的模型
如前所述,編碼器將通過 LSTM 處理源輸入序列,并將源文本編碼為有意義的摘要。 有意義的摘要將存儲在最后的序列步驟中,即隱藏和單元狀態(tài)h[f]
和c[f]
。 這些向量在一起(即[h[f]; c[f]]
)提供了有關源文本的有意義上下文,并且訓練了解碼器來產(chǎn)生具有隱藏和單元狀態(tài)向量的目標序列[h[f]; c[f]]
。
下圖所示“圖 3.5”是英語到法語翻譯的訓練過程的詳細圖。 英文句子It's a beautiful day
通過 LSTM 轉(zhuǎn)換為含義摘要,然后存儲在隱藏和單元格狀態(tài)向量[h[f]; c[f]]
中。 然后使解碼器根據(jù)嵌入在[h[f]; c[f]]
中的信息,以輸入源語句為條件,生成自己的目標序列。 給定源句,使在時間步t
的解碼器預測下一個目標單詞,即在時間步t + 1
的單詞。 這就是為什么目標輸入字和目標輸出字之間有一個時間步長的滯后的原因。 對于第一步,解碼器在目標文本序列中沒有任何先前的單詞,因此可用于預測目標單詞的唯一信息是以[h[f]; c[f]]
,作為初始隱藏和單元狀態(tài)向量提供。 像編碼器一樣,解碼器也使用 LSTM,并且如上所述,輸出目標序列比輸入目標序列提前一個時間步長:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-qvJittQe-1681653992514)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/421b234c-10f6-4cbe-9233-97a33233b2ca.png)]
圖 3.5:訓練時機器翻譯網(wǎng)絡流程的圖示
我們基于“圖 3.5”中所示的架構(gòu),定義了用于訓練函數(shù)model_enc_dec
的編碼器解碼器端到端模型。 在這里,**編碼器(LSTM 1)依次獲取源語言文本單詞,并在編碼器(LSTM 1)的最后序列步驟中捕獲源語言句子或文本的整個上下文。來自編碼器的上下文將作為解碼器(LSTM 2)**的初始狀態(tài)進行饋送,該學習器將根據(jù)當前單詞來預測下一個單詞,因為在訓練過程中我們會得到一個句子 / text 作為目標語言,因此解碼器只需將其輸入移位一個時間步即可形成目標:
def model_enc_dec(self):#Encoder Modelencoder_inp = Input(shape=(None,self.num_encoder_words),name='encoder_inp')encoder = LSTM(self.latent_dim, return_state=True,name='encoder')encoder_out,state_h, state_c = encoder(encoder_inp)encoder_states = [state_h, state_c]#Decoder Modeldecoder_inp = Input(shape=(None,self.num_decoder_words),name='decoder_inp')decoder_lstm = LSTM(self.latent_dim, return_sequences=True, return_state=True,name='decoder_lstm')decoder_out, _, _ = decoder_lstm(decoder_inp, initial_state=encoder_states)decoder_dense = Dense(self.num_decoder_words, activation='softmax',name='decoder_dense')decoder_out = decoder_dense(decoder_out)print(np.shape(decoder_out))#Combined Encoder Decoder Modelmodel = Model([encoder_inp, decoder_inp], decoder_out)#Encoder Model encoder_model = Model(encoder_inp,encoder_states)#Decoder Modeldecoder_inp_h = Input(shape=(self.latent_dim,))decoder_inp_c = Input(shape=(self.latent_dim,))decoder_input = Input(shape=(None,self.num_decoder_words,))decoder_inp_state = [decoder_inp_h,decoder_inp_c]decoder_out,decoder_out_h,decoder_out_c = decoder_lstm(decoder_input,initial_state=decoder_inp_state)decoder_out = decoder_dense(decoder_out)decoder_out_state = [decoder_out_h,decoder_out_c]decoder_model = Model(inputs = [decoder_input] + decoder_inp_state,output=[decoder_out]+ decoder_out_state)plot_model(model,show_shapes=True, to_file=self.outdir + 'encoder_decoder_training_model.png')plot_model(encoder_model,show_shapes=True, to_file=self.outdir + 'encoder_model.png')plot_model(decoder_model,show_shapes=True, to_file=self.outdir + 'decoder_model.png')return model,encoder_model,decoder_model
雖然訓練模型是一個簡單的端到端模型,但推理模型并不是那么簡單,因為我們不知道每個時間步長之前解碼器的輸入。 我們將在“構(gòu)建推理模型”部分中更詳細地討論推理模型。
神經(jīng)翻譯機的損失函數(shù)
神經(jīng)翻譯機的損失函數(shù)是用于預測模型序列中每個目標單詞的平均交叉熵損失。 實際目標詞和預測目標詞可以是我們所采用的法語語料庫中的16,297
個詞中的任何一個。 在時間步t
處的目標標簽將是單熱點編碼的向量y[t] ∈ {0,1}^16297
,法語詞匯表中每個16,297
個單詞的預測輸出將采用概率形式。 如果將預測的輸出概率向量表示為p[t] ∈ (0, 1)^16297
,則特定句子在每個時間步的平均分類損失由以下給出:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-X0NnFCLM-1681653992515)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/be4d5566-e340-4986-8364-96b61806475a.png)]
通過匯總所有序列時間步長上的損失,可以得出整個句子的損失,如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-mM9Uh1WI-1681653992515)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/b7bc5575-d588-40d6-926a-4178a812cd58.png)]
由于我們使用小批量隨機梯度下降進行工作,因此可以通過對小批量中所有句子的平均損失來獲得小批量的平均成本。 如果我們使用大小為m
的微型批量,則每個微型批量的平均損失如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-JDhtMqbl-1681653992515)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/c36a2af9-5786-4cb3-baf8-29dbd0b17918.png)]
最小批量成本用于計算隨機梯度下降的梯度。
訓練模型
我們首先執(zhí)行model_enc_dec
函數(shù)來定義訓練模型以及encoder_model
和decoder_model
進行推理,然后將其與categorical_crossentropy
損失和rmsprop
一起編譯 ]優(yōu)化器。 我們可以嘗試使用其他優(yōu)化器,例如 Adam,具有動量的 SDG 等,但是目前,我們將堅持使用rmsprop
。train
函數(shù)可以定義如下:
# Run trainingdef train(self,encoder_input_data,decoder_input_data,decoder_target_data):print("Training...")model,encoder_model,decoder_model = self.model_enc_dec()model.compile(optimizer='rmsprop', loss='categorical_crossentropy')model.fit([encoder_input_data, decoder_input_data],decoder_target_data,batch_size=self.batch_size,epochs=self.epochs,validation_split=0.2)# Save modelmodel.save(self.outdir + 'eng_2_french_dumm.h5')return model,encoder_model,decoder_model
我們在 80% 的數(shù)據(jù)上訓練模型,并將其余 20% 的數(shù)據(jù)用于驗證。 訓練/測試拆分由以下定義的函數(shù)執(zhí)行:
def train_test_split(self,num_recs,train_frac=0.8):rec_indices = np.arange(num_recs)np.random.shuffle(rec_indices)train_count = int(num_recs*0.8)train_indices = rec_indices[:train_count]test_indices = rec_indices[train_count:]return train_indices,test_indices
建立推理模型
讓我們嘗試回顧推理模型的工作機制,并了解如何使用已經(jīng)訓練的模型的組件來構(gòu)建它。 該模型的編碼器部分應通過以源語言中的文本句子作為輸入來工作,并提供最終的隱藏和單元狀態(tài)向量[h[f]; c[f]]
作為輸出。 我們不能按原樣使用解碼器網(wǎng)絡,因為目標語言輸入字不再可以饋送到解碼器。 相反,我們將解碼器網(wǎng)絡折疊為一個步驟,并將該步驟的輸出作為下一步的輸入。 我們以虛擬字[START]
作為解碼器的第一個輸入字,以及[h[f]; c[f]]
,用作其初始隱藏狀態(tài)和單元格狀態(tài)。 目標輸出字w[1]
以及隱藏和單元狀態(tài)h[1]; c[1]
由解碼器使用[START]
和[h[f]; c[f]]
,因為輸入再次饋送到解碼器以生成下一個字,然后重復該過程,直到解碼器輸出偽字[END]
為止。 下圖說明了推理過程的逐步表示形式,以便于解釋:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-kyQGhcMH-1681653992515)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/32c9e729-5872-4e25-bf42-0f87c5ba9d4d.png)]
圖 3.6:推理過程的逐步說明
從上圖可以看出,解碼器第一步的輸出為C'est
,而隱藏狀態(tài)和單元狀態(tài)為[h'[1]; c'[1]]
。 如虛線所示,將其再次饋送到解碼器,以生成下一個字以及下一組隱藏狀態(tài)和單元狀態(tài)。 由于解碼器輸出偽結(jié)束字符[END]
,因此重復該過程。
為了進行推斷,我們可以按原樣使用網(wǎng)絡的編碼器部分,并進行一些修改以使解碼器折疊以使其包含一個時間步。 概括地說,無論 RNN 是一個時間步長還是幾個時間步長,與 RNN 相關的權(quán)重都不會改變,因為 RNN 的所有時間步長都共享相同的權(quán)重。
為了進行推斷,我們可以看到訓練模型的編碼器部分用作函數(shù)model_enc_dec
中的encoder_model
。 類似地,使用相同的解碼器 LSTM 定義單獨的decoder_model
,該解碼器將輸入作為隱藏狀態(tài),單元狀態(tài)和輸入字,并輸出目標字以及更新的隱藏和單元狀態(tài)。 為了清楚起見,再次重復定義了推斷模型的函數(shù)model_enc_dec
encoder_model
和decoder_model
:
def model_enc_dec(self):#Encoder Modelencoder_inp = Input(shape=(None,self.num_encoder_words),name='encoder_inp')encoder = LSTM(self.latent_dim, return_state=True,name='encoder')encoder_out,state_h, state_c = encoder(encoder_inp)encoder_states = [state_h, state_c]#Decoder Modeldecoder_inp = Input(shape=(None,self.num_decoder_words),name='decoder_inp')decoder_lstm = LSTM(self.latent_dim, return_sequences=True, return_state=True,name='decoder_lstm')decoder_out, _, _ = decoder_lstm(decoder_inp, initial_state=encoder_states)decoder_dense =Dense(self.num_decoder_words, activation='softmax',name='decoder_dense')decoder_out = decoder_dense(decoder_out)print(np.shape(decoder_out))#Combined Encoder Decoder Modelmodel = Model([encoder_inp, decoder_inp], decoder_out)#Encoder Model encoder_model = Model(encoder_inp,encoder_states)#Decoder Modeldecoder_inp_h = Input(shape=(self.latent_dim,))decoder_inp_c = Input(shape=(self.latent_dim,))decoder_input = Input(shape=(None,self.num_decoder_words,))decoder_inp_state = [decoder_inp_h,decoder_inp_c]decoder_out,decoder_out_h,decoder_out_c = decoder_lstm(decoder_input,initial_state=decoder_inp_state)decoder_out = decoder_dense(decoder_out)decoder_out_state = [decoder_out_h,decoder_out_c]decoder_model = Model(inputs = [decoder_input] + decoder_inp_state,output=[decoder_out]+ decoder_out_state)plot_model(model,to_file=self.outdir + 'encoder_decoder_training_model.png')plot_model(encoder_model,to_file=self.outdir + 'encoder_model.png')plot_model(decoder_model,to_file=self.outdir + 'decoder_model.png')return model,encoder_model,decoder_model
解碼器一次將運行一個時間步。 在第一種情況下,它將從編碼器獲取隱藏狀態(tài)和單元狀態(tài),并根據(jù)偽單詞[START]
猜測翻譯的第一個單詞。 第一步中預測的單詞,連同生成的隱藏狀態(tài)和單元狀態(tài)一起,再次饋送到解碼器以預測第二個單詞,然后繼續(xù)進行處理,直到預測出由虛擬單詞[END]
表示的句子結(jié)尾。
現(xiàn)在,我們已經(jīng)定義了將源句子/文本翻譯成目標語言對應物所需的所有函數(shù),我們將它們組合起來以構(gòu)建一個函數(shù),該函數(shù)會生成翻譯后的句子,給定源語言輸入序列或句子:
def decode_sequence(self,input_seq,encoder_model,decoder_model):# Encode the input as state vectors.states_value = encoder_model.predict(input_seq)# Generate empty target sequence of length 1.target_seq = np.zeros((1, 1, self.num_decoder_words))# Populate the first character of target sequence with the start character.target_seq[0, 0, self.target_word_index['\t']] = 1.# Sampling loop for a batch of sequencesstop_condition = Falsedecoded_sentence = ''while not stop_condition:output_word, h, c = decoder_model.predict([target_seq] + states_value)# Sample a tokensampled_word_index = np.argmax(output_word[0, -1, :])sampled_char = self.reverse_target_word_dict[sampled_word_index]decoded_sentence = decoded_sentence + ' ' + sampled_char# Exit condition: either hit max length# or find stop character.if (sampled_char == '\n' orlen(decoded_sentence) > self.max_decoder_seq_length):stop_condition = True# Update the target sequence (of length 1).target_seq = np.zeros((1, 1, self.num_decoder_words))target_seq[0, 0, sampled_word_index] = 1.# Update statesstates_value = [h, c]return decoded_sentence
訓練模型后,我們就對保持數(shù)據(jù)集進行推斷并檢查翻譯質(zhì)量。 inference
函數(shù)可以編碼如下:
def inference(self,model,data,encoder_model,decoder_model,in_text):in_list,out_list = [],[]for seq_index in range(data.shape[0]):input_seq = data[seq_index: seq_index + 1]decoded_sentence = self.decode_sequence(input_seq,encoder_model,decoder_model)print('-')print('Input sentence:', in_text[seq_index])print('Decoded sentence:',decoded_sentence)in_list.append(in_text[seq_index])out_list.append(decoded_sentence)return in_list,out_list
通過調(diào)用 Python 腳本MachineTranslation.py
,可以在保持數(shù)據(jù)集上訓練和驗證機器翻譯模型,如下所示:
python MachineTranslation.py --path '/home/santanu/ML_DS_Catalog/Machine Translation/fra-eng/fra.txt' --epochs 20 --batch_size 32 -latent_dim 128 --num_samples 40000 --outdir '/home/santanu/ML_DS_Catalog/Machine Translation/' --verbose 1 --mode train
保留數(shù)據(jù)集中機器翻譯模型表現(xiàn)出色的幾個英語句子的翻譯結(jié)果如下,以供參考:
('Input sentence:', u'Go.')
('Decoded sentence:', u' Va ! \n')
('Input sentence:', u'Wait!')
('Decoded sentence:', u' Attendez ! \n')
('Input sentence:', u'Call me.')
('Decoded sentence:', u' Appelle-moi ! \n')
('Input sentence:', u'Drop it!')
('Decoded sentence:', u' Laisse tomber ! \n')
('Input sentence:', u'Be nice.')
('Decoded sentence:', u' Soyez gentil ! \n')
('Input sentence:', u'Be fair.')
('Decoded sentence:', u' Soyez juste ! \n')
('Input sentence:', u"I'm OK.")
('Decoded sentence:', u' Je vais bien. \n')
('Input sentence:', u'I try.')
('Decoded sentence:', u' Je vais essayer.')
但是,在某些情況下,機器翻譯的效果不佳,如下所示:
('Input sentence:', u'Attack!')
('Decoded sentence:', u' ma ! \n')('Input sentence:', u'Get up.')
('Decoded sentence:', u' un ! \n')
總之,先前說明的神經(jīng)機器翻譯實現(xiàn)將相對較短的英語句子翻譯為法語的工作相當不錯。 我要強調(diào)的一件事是使用單熱編碼向量來表示每種語言中的輸入單詞。 由于我們使用的是相對較小的 40,000 個單詞的語料庫,因此詞匯量是合理的,因此,我們能夠分別使用大小為 8,658 和 16,297 的英語和法語詞匯量的一鍵編碼向量。 隨著語料庫的增加,單熱點編碼詞向量的大小將進一步增加。 比較兩個單詞時,這種稀疏的高維向量沒有任何相似性概念,因為即使兩個單詞的含義幾乎相同,它們的余弦乘積也將是0
。 在下一節(jié)中,我們將了解如何以較小的維數(shù)進行單詞向量嵌入來解決此問題。
詞向量嵌入
代替單熱編碼向量,可以使用詞向量嵌入來表示維的密集空間中的單詞,該空間比單熱編碼向量低得多。 嵌入單詞w
的單詞向量可以用v[w] ∈ R^m
表示,其中m
是詞向量嵌入的維數(shù)。 如我們所見,雖然單熱編碼向量的每個分量只能占用{0,1}的二進制值,但詞向量嵌入的分量卻可以占用任何實數(shù),因此具有更密集的表示。 相似性和類比的概念也與詞向量嵌入有關。
通常通過諸如連續(xù)詞袋法,skip-gram,GloVe 等技術(shù)來訓練詞向量嵌入。 我們將不對它們的實現(xiàn)進行過多的介紹,但中心思想是以這樣的方式定義詞向量嵌入:將類似的詞緊密放置在m
維歐幾里得空間中:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZbrqKpEx-1681653992516)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/9f7522e1-949b-4004-b21d-4f95010db169.png)]
圖 3.7:GloVe 嵌入的相似性和類比說明
在上一張圖中,我們繪制了男人,女人,國王和女王的 GloVe 詞向量嵌入的二維 TSNE 視圖 。 我們可以看到,男人和女人具有內(nèi)在的相似性,國王和女王的情況也是如此。 此外,我們看到國王和男人之間的向量差異與女王和女人的向量差異幾乎相同,這可能代表王權(quán)的一些概念。 如我們所見,除了表達單詞之間的相似性之外,還可以通過單詞向量嵌入來表示類似男人:國王:女人:女王之類的東西。 在下一節(jié)中,我們將討論使用 RNN 中的嵌入層將輸入單詞表示為單詞向量嵌入,而不是單編碼的向量。
嵌入層
嵌入層將輸入單詞的索引作為輸入,并提供單詞的單詞向量嵌入作為輸出。 嵌入層的大小為R^(dxV)
,其中d
是詞向量嵌入的大小,V
是詞匯的大小。 嵌入層可以根據(jù)問題了解嵌入本身,也可以提供預訓練的嵌入層。 在我們的案例中,我們將讓神經(jīng)機器翻譯找出對于源語言和目標語言而言,嵌入向量應該是什么,以實現(xiàn)良好的翻譯。 結(jié)果,我們定義的每個函數(shù)都應更改以適應嵌入層。
實現(xiàn)基于嵌入的 NMT
我們將需要對現(xiàn)有函數(shù)進行一些更改,以適應嵌入層。 首先,process_input
將處理輸入以在不同的時間步長中具有單詞索引,而不是單熱編碼向量,如下所示:
def process_input(self,input_texts,target_texts=None,verbose=True):encoder_input_data = np.zeros((len(input_texts), self.max_encoder_seq_length),dtype='float32')decoder_input_data = np.zeros((len(input_texts), self.max_decoder_seq_length),dtype='float32')decoder_target_data = np.zeros((len(input_texts), self.max_decoder_seq_length,1),dtype='float32')if self.mode == 'train':for i, (input_text, target_text) in enumerate(zip(input_texts,target_texts)):for t, word in enumerate(input_text.split(" ")):try:encoder_input_data[i, t] = self.input_word_index[word]except:encoder_input_data[i, t] = self.num_encoder_wordsfor t, word in enumerate(target_text.split(" ")):# decoder_target_data is ahead of decoder_input_databy one timesteptry:decoder_input_data[i, t] = self.target_word_index[word]except:decoder_input_data[i, t] = self.num_decoder_words if t > 0:# decoder_target_data will be ahead by one timestep#and will not include the start character.try:decoder_target_data[i, t - 1] = self.target_word_index[word]except:decoder_target_data[i, t - 1] = self.num_decoder_words print(self.num_encoder_words)print(self.num_decoder_words)print(self.embedding_dim)self.english_emb = np.zeros((self.num_encoder_words + 1,self.embedding_dim))self.french_emb = np.zeros((self.num_decoder_words + 1,self.embedding_dim))return encoder_input_data,decoder_input_data,decoder_target_data,np.array(input_texts),
np.array(target_texts)else:for i, input_text in enumerate(input_texts):for t, word in enumerate(input_text.split(" ")):try:encoder_input_data[i, t] = self.input_word_index[word]
與以前的process_input
函數(shù)相比,唯一的變化是,我們不再用單熱編碼向量表示單詞,而是用單詞的索引表示。 另外,您是否注意到我們?yōu)樵~匯表中不存在的單詞添加了額外的單詞索引? 理想情況下,這不是為了訓練數(shù)據(jù)而發(fā)生的,但是在測試過程中,可能會出現(xiàn)一個不在詞匯表中的全新單詞。
以下是來自輸入處理的統(tǒng)計信息:
Number of samples: 40000
Number of unique input tokens: 8658
Number of unique output tokens: 16297
Max sequence length for inputs: 7
Max sequence length for outputs: 16
('Shape of Source Input Tensor:', (40000, 7))
('Shape of Target Input Tensor:', (40000, 16))
('Shape of Target Output Tensor:', (40000, 16, 1))
如我們所見,源和目標輸入張量現(xiàn)在具有7
和16
時間步長,但是沒有一鍵編碼向量的維數(shù)。 每個時間步長都包含單詞的索引。
下一個變化是關于編碼器和解碼器網(wǎng)絡,以在 LSTM 層之前容納嵌入層:
def model_enc_dec(self):#Encoder Modelencoder_inp = Input(shape=(None,),name='encoder_inp')encoder_inp1 = Embedding(self.num_encoder_words + 1,self.embedding_dim,weights=[self.english_emb])(encoder_inp)encoder = LSTM(self.latent_dim, return_state=True,name='encoder')encoder_out,state_h, state_c = encoder(encoder_inp1)encoder_states = [state_h, state_c]#Decoder Modeldecoder_inp = Input(shape=(None,),name='decoder_inp')decoder_inp1 = Embedding(self.num_decoder_words+1,self.embedding_dim,weights= [self.french_emb])(decoder_inp)decoder_lstm = LSTM(self.latent_dim, return_sequences=True, return_state=True,name='decoder_lstm')decoder_out, _, _ = decoder_lstm(decoder_inp1,initial_state=encoder_states)decoder_dense = Dense(self.num_decoder_words+1, activation='softmax',name='decoder_dense')decoder_out = decoder_dense(decoder_out)print(np.shape(decoder_out))#Combined Encoder Decoder Modelmodel = Model([encoder_inp, decoder_inp], decoder_out)#Encoder Model encoder_model = Model(encoder_inp,encoder_states)#Decoder Modeldecoder_inp_h = Input(shape=(self.latent_dim,))decoder_inp_c = Input(shape=(self.latent_dim,))decoder_inp_state = [decoder_inp_h,decoder_inp_c]decoder_out,decoder_out_h,decoder_out_c = decoder_lstm(decoder_inp1,initial_state=decoder_inp_state)decoder_out = decoder_dense(decoder_out)decoder_out_state = [decoder_out_h,decoder_out_c]decoder_model = Model(inputs = [decoder_inp] + decoder_inp_state,output=[decoder_out]+ decoder_out_state)return model,encoder_model,decoder_model
訓練模型需要使用sparse_categorical_crossentropy
進行編譯,因為輸出目標標簽表示為索引,而不是一鍵編碼的單詞向量:
def train(self,encoder_input_data,decoder_input_data,decoder_target_data):print("Training...")model,encoder_model,decoder_model = self.model_enc_dec()model.compile(optimizer='rmsprop', loss='sparse_categorical_crossentropy')model.fit([encoder_input_data, decoder_input_data],decoder_target_data,batch_size=self.batch_size,epochs=self.epochs,validation_split=0.2)# Save modelmodel.save(self.outdir + 'eng_2_french_dumm.h5')return model,encoder_model,decoder_model
接下來,我們需要對與推理相關的函數(shù)進行修改,以適應與嵌入相關的更改。 現(xiàn)在,用于推斷的encoder_model
和decoder_model
分別將嵌入層用于英語和法語詞匯。
最后,我們可以使用decoder_model
和encoder_model
如下創(chuàng)建序列生成器函數(shù):
def decode_sequence(self,input_seq,encoder_model,decoder_model):# Encode the input as state vectors.states_value = encoder_model.predict(input_seq)# Generate empty target sequence of length 1.target_seq = np.zeros((1, 1))# Populate the first character of target sequencewith the start character.target_seq[0, 0] = self.target_word_index['\t']# Sampling loop for a batch of sequencesstop_condition = Falsedecoded_sentence = ''while not stop_condition:output_word, h, c = decoder_model.predict([target_seq] + states_value)# Sample a tokensampled_word_index = np.argmax(output_word[0, -1, :])try:sampled_char = self.reverse_target_word_dict[sampled_word_index]except:sampled_char = '<unknown>'decoded_sentence = decoded_sentence + ' ' + sampled_char# Exit condition: either hit max length# or find stop character.if (sampled_char == '\n' orlen(decoded_sentence) > self.max_decoder_seq_length):stop_condition = True# Update the target sequence (of length 1).target_seq = np.zeros((1, 1))target_seq[0, 0] = sampled_word_index# Update statesstates_value = [h, c]return decoded_sentence
可以通過如下運行腳本來調(diào)用模型的訓練:
python MachineTranslation_word2vec.py --path '/home/santanu/ML_DS_Catalog-/Machine Translation/fra-eng/fra.txt' --epochs 20 --batch_size 32 --latent_dim 128 --num_samples 40000 --outdir '/home/santanu/ML_DS_Catalog-/Machine Translation/' --verbose 1 --mode train --embedding_dim 128
該模型在 GeForce GTX 1070 GPU 上進行了訓練,大約需要 9.434 分鐘才能訓練 32,000 條記錄并進行 8,000 條記錄的推理。 強烈建議用戶使用 GPU,因為 RNN 的計算量很大,可能需要數(shù)小時才能在 CPU 上訓練相同的模型。
我們可以通過運行 python 腳本MachineTranslation.py
來訓練機器翻譯模型并在保持數(shù)據(jù)集上執(zhí)行驗證,如下所示:
python MachineTranslation.py --path '/home/santanu/ML_DS_Catalog/Machine Translation/fra-eng/fra.txt' --epochs 20 --batch_size 32 -latent_dim 128 --num_samples 40000 --outdir '/home/santanu/ML_DS_Catalog/Machine Translation/' --verbose 1 --mode train
從嵌入向量方法獲得的結(jié)果與單熱編碼詞向量的結(jié)果相似。 這里提供了一些來自保持數(shù)據(jù)集推理的翻譯:
Input sentence: Where is my book?
Decoded sentence: Où est mon Tom ?
-
Input sentence: He's a southpaw.
Decoded sentence: Il est en train de
-
Input sentence: He's a very nice boy.
Decoded sentence: C'est un très bon
-
Input sentence: We'll be working.
Decoded sentence: Nous pouvons faire
-
Input sentence: May I have a program?
Decoded sentence: Puis-je une ? -
Input sentence: Can you make it safe?
Decoded sentence: Peux-tu le faire
-
Input sentence: We walked to my room.
Decoded sentence: Nous avons devons
-
Input sentence: Don't stand too close.
Decoded sentence: Ne vous en prie.
-
Input sentence: Where's the dog?
Decoded sentence: Où est le chien ?
-
Input sentence: He's a hopeless case.
Decoded sentence: Il est un fait de
-
Input sentence: Where were we?
Decoded sentence: Où fut ?
總結(jié)
讀者現(xiàn)在應該對幾種機器翻譯方法以及神經(jīng)翻譯機器與傳統(tǒng)機器有何不同有很好的理解。 現(xiàn)在,我們還應該深入了解如何從頭開始構(gòu)建神經(jīng)機器翻譯系統(tǒng),以及如何以有趣的方式擴展該系統(tǒng)。 借助提供的信息和實現(xiàn)演示,建議讀者探索其他并行語料庫數(shù)據(jù)集。
在本章中,我們定義了嵌入層,但未使用預訓練的嵌入(例如 GloVe,FastText 等)來加載它們。 建議讀者使用預訓練的詞向量嵌入為嵌入層加載,并查看是否會產(chǎn)生更好的結(jié)果。 在第 4 章,“使用 GAN 進行時裝行業(yè)中的樣式遷移”中,我們將通過與生成性對抗網(wǎng)絡(這是現(xiàn)代的革命)進行與時裝業(yè)中樣式遷移有關的項目。 人工智能領域。
四、使用 GAN 的時尚行業(yè)樣式遷移
樣式遷移的概念是指將產(chǎn)品樣式渲染為另一種產(chǎn)品的過程。 想象一下,您的一位時尚狂朋友買了一個藍色的手袋,想買一雙類似印花的鞋子。 直到 2016 年,這還是不可能實現(xiàn)的,除非他們與一位時裝設計師成為朋友,他們必須首先設計一款鞋子,然后才能批準生產(chǎn)。 然而,隨著生成對抗網(wǎng)絡的最新進展,這種設計過程可以很容易地進行。
生成對抗網(wǎng)絡是通過在生成器網(wǎng)絡和判別器網(wǎng)絡之間進行零和游戲來學習的網(wǎng)絡。 假設一位時裝設計師想要設計一種特定結(jié)構(gòu)的手袋,并且正在探索不同的印花。 設計人員可以繪制手提包的結(jié)構(gòu)草圖,然后將草圖圖像輸入到生成的對抗網(wǎng)絡中,以得出手提包的幾種可能的最終印刷品。 這種樣式遷移過程可以使客戶自己繪制產(chǎn)品設計和圖案,而無需征集大量設計師的意見,從而對時尚行業(yè)產(chǎn)生巨大影響。 通過推薦具有類似設計和風格的產(chǎn)品來補充客戶已經(jīng)擁有的產(chǎn)品,時裝屋也可以從中受益。
在這個項目中,我們將構(gòu)建一個智能人工智能系統(tǒng),該系統(tǒng)將生成與給定手提袋樣式相似的鞋子,反之亦然。 我們之前討論的原始 GAN 不足以實現(xiàn)這個項目。 我們需要的是 GAN 的定制版本,例如 DiscoGAN 和 CycleGAN。
在本章中,我們將介紹以下主題:
- 我們將討論 DiscoGAN 背后的工作原理和數(shù)學基礎
- 我們將比較和對比 DiscoGAN 與 CycleGAN,后者在架構(gòu)和工作原理上非常相似
- 我們將訓練一個 DiscoGAN,該系統(tǒng)學習從給定的袋子素描中生成袋子的圖像
- 最后,我們將討論與訓練 DiscoGAN 有關的復雜性
技術(shù)要求
讀者應具有 Python 3 和人工智能的基礎知識,才能完成本章中的項目。
本章的代碼文件可以在 GitHub 上找到
觀看以下視頻,查看運行中的代碼
DiscoGAN
DiscoGAN 是一個生成的對抗網(wǎng)絡,它在給定域A
中的圖像的情況下生成域B
中產(chǎn)品的圖像。下圖說明了 DisoGAN 網(wǎng)絡的架構(gòu)圖:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-6iUsFnFh-1681653992516)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/ad2f3c06-0854-4f44-871f-d0916c1f0980.png)]
圖 4.1:DiscoGAN 的架構(gòu)圖
域B
中生成的圖像在樣式和樣式上都類似于域A
中的圖像。 無需在訓練過程中顯式配對來自兩個域的圖像就可以學習這種關系。 鑒于項目的配對是一項耗時的任務,因此這是一項非常強大的功能。 在較高的水平上,它嘗試學習神經(jīng)網(wǎng)絡G[AB]
和G[BA]
形式的兩個生成器函數(shù)。 圖像x[A]
,當通過生成器饋入時G[AB]
,產(chǎn)生圖像x[AB]
,在域B
中看起來很真實。此外,當此圖像x[AB]
通過其他生成器網(wǎng)絡G[BA]
饋送時,它應產(chǎn)生圖像x[ABA]
,理想情況下應與原始圖像x[A]
相同。 關于生成器函數(shù),以下關系應成立:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-dxW7nznh-1681653992516)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/b12bb426-7c2a-4116-be5b-98a133536497.png)]
但是實際上,生成器函數(shù)G[AB]
和G[BA]
不可能彼此相反,因此我們嘗試通過選擇 L1 或 L2 歸一化的損失來盡量減少重建圖像和原始圖像之間的損失。 L1 規(guī)范損失基本上是每個數(shù)據(jù)點的絕對誤差之和,而 L2 規(guī)范損失表示每個數(shù)據(jù)點的平方誤差的和。 我們可以如下表示單個圖像的 L2 范數(shù)損失:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-JwY6c8YV-1681653992516)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/09ada4ef-4d12-45db-a6b8-19064db767e9.png)]
僅使前面的損失最小化是不夠的。 我們必須確保在域B
中創(chuàng)建的圖像x[B]
看起來逼真。例如,如果我們將域A
中的衣服映射到域B
中的鞋子,我們將確保x[B]
類似于鞋子。 如果圖像不夠真實,則在域B
側(cè)的判別器D[B]
將檢測為x[B]
為假。 鞋子,因此也要考慮與此有關的損失。 通常,在訓練過程中,向判別器提供生成的域B
圖像x[AB] = G[AB](X[A])
,我們選擇在這里用y[B]
表示,以便它學習從假圖像中對真實圖像進行分類。 您可能還記得,在 GAN 中,生成器和判別器相互進行零和最小最大值游戲,以便不斷變得更好,直到達到平衡為止。 如果偽造的圖像看起來不夠逼真,則判別器將對其進行懲罰,這意味著生成器必須學習產(chǎn)生更好的圖像x[AB]
,如果輸入圖像x[A]
。 考慮到所有這些因素,我們可以將我們希望最小化的生成器損失公式化為重建損失,以及判別器將x[AB]
識別為假冒的損失。 第二種損失將試圖使生成器在域B
中生成逼真的圖像。將域A
中的圖像x[A]
映射到域B
中的圖像的生成器損失可以表示如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-JQZp1agh-1681653992516)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/f2cdd9c3-d4d4-4c89-a533-e5044ea37592.png)]
L2 范數(shù)下的重建損失可以表示為:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-eh2XhhF5-1681653992517)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/db0f4732-8246-4e5f-ab9f-d680b444e478.png)]
由于我們正在處理圖像,因此可以假設x[A]
是所有像素的扁平向量,以符合 L2 規(guī)范項。 如果我們假設x[A]
是矩陣,則最好將||·||_2^2
稱為 Frobenius 范數(shù)。 但是,這些只是數(shù)學術(shù)語,實質(zhì)上,我們只是將原始圖像和重建圖像之間的像素值差的平方和求和。
讓我們考慮一下生成器在使變換后的圖像x[AB]
追求時要盡量降低成本的做法。 判別器將嘗試將圖像標記為偽圖像,因此生成器G[AB]
應當在這種情況下產(chǎn)生x[AB]
使其成為假圖片的對數(shù)損失的方式盡可能小。 如果域B
中的判別器D[B]
將真實圖像標記為1
,將偽圖像標記為0
,則圖像真實的概率由D[B](.)
,則生成器應使x[AB]
在判別器網(wǎng)絡下極有可能出現(xiàn),從而使D[B](x[B]) = D[B](G[AB](x[A]))
接近1
)。 就對數(shù)損失而言,生成器應使先前概率的負對數(shù)最小化,這基本上使我們得到C[D(AB)]
,如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-jnKt2b3I-1681653992517)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/549bd16c-1bd7-4f76-b4c1-6179369f92c7.png)]
結(jié)合(3)
和(4)
,我們可以獲得將鏡像從域A
映射到域A
的總生成器成本C_G[AB]
域B
,如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-CAW2wBhF-1681653992517)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/581b9be4-661a-41e9-a9f3-597d1116c2c7.png)] [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-K55D7diy-1681653992517)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/7d74f982-9649-4c22-a18e-d20e9b27d51c.png)]
最大的問題是,我們可以在這里停下來嗎? 由于我們有來自兩個域的圖像,因此要獲得更好的映射,我們也可以從域B
拍攝圖像,并通過生成器G[BA]
將它們映射到域A
。 如果我們在域B
中拍攝x[B]
圖像,并通過生成器G[BA]
將其轉(zhuǎn)換為圖像x[BA]
,而域A
上的標識符由D[A]
給出,則與這種轉(zhuǎn)換相關的成本函數(shù)由以下給出:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-IpS8t7Pz-1681653992518)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/e8a983da-5a03-453d-8317-eff82e4164e6.png)] [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8BDMEB1B-1681653992518)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/0812b062-6e92-4e91-b725-747ac4b61f31.png)]
如果我們對兩個域中的全部圖像總數(shù)求和,則生成器損失將由(5)
和(6)
之和給出,如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-wuNVbwrp-1681653992518)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/bd4d5890-a506-4180-b680-4c842cca1286.png)] [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-3tO0ZBq1-1681653992518)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/bcb0c1db-84a7-44ac-864c-4e7545fb6016.png)]
現(xiàn)在,讓我們構(gòu)建成本函數(shù),這些判別器將嘗試最小化以建立零和最小/最大游戲。 每個域中的判別器都會嘗試將真實圖像與偽圖像區(qū)分開,因此判別器G[B]
會嘗試將成本降到最低C_D[B]
,如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-QM7u1fqR-1681653992518)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/c8b74f9b-c8cb-47d4-99e1-00c9b35eef05.png)]
同樣,判別器D[A]
會嘗試將成本降到最低。C_D[A]
如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-OdriIbyu-1681653992519)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/78578465-7cc8-40c5-9662-21aa263baa81.png)]
結(jié)合(8)
和(9)
的總判別器成本由C[D]
給出,如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-RhtQHhIq-1681653992519)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/8907ca7c-ae87-4a4b-80e3-37c4bfb678cc.png)]
如果我們表示G[AB]
的參數(shù),則G[BA]
,D[A]
和D[B]
設為θ[GAB]
,θ[GBA]
,θ[DA]
和θ[DB]
,則網(wǎng)絡的優(yōu)化參數(shù)可以表示為:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-QDcFadtV-1681653992519)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/3d609715-cecb-4823-8ce5-87fda4d8846f.png)]
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-SurlmLi7-1681653992519)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/4937092d-eabc-4bc6-80ec-9e836b4f128e.png)]
對成本函數(shù)執(zhí)行隨機梯度下降(例如 Adam),以得出最優(yōu)解。 請注意,如前所述,生成對抗網(wǎng)絡的解決方案是優(yōu)化成本函數(shù)的一個障礙。
CycleGAN
CycleGAN 從根本上類似于 DiscoGAN,但有一個小的修改。 在 CycleGAN 中,我們可以靈活地確定相對于 GAN 損失或歸因于判別器的損失,為重建損失分配多少權(quán)重。 該參數(shù)有助于根據(jù)眼前的問題按正確比例平衡損失,以幫助網(wǎng)絡在訓練時更快地收斂。 CycleGAN 的其余實現(xiàn)與 DiscoGAN 相同。
學習從草繪的輪廓生成自然手袋
在本章中,我們將使用草繪的輪廓生成手袋,而無需使用 DiscoGAN 進行顯式配對。 我們將草圖圖像表示為屬于域A
,而將自然手袋圖像表示為屬于域B
。將有兩種生成器:一種生成器,用于獲取域A
的圖像并將其映射到在域B
下看起來逼真的圖像,以及另一個與此相反:將域B
中的手袋圖像映射到在域A
下看起來很逼真的圖像。判別器將嘗試從每個域中真實圖像的生成器中識別生成器生成的虛假圖像。 生成器和判別器將相互進行 minimax 零和游戲。
要訓??練該網(wǎng)絡,我們將需要兩套圖像,手袋的草圖或輪廓以及手袋的自然圖像。 可以從以下鏈接下載圖像。
在接下來的幾節(jié)中,我們將完成在 TensorFlow 中定義 DiscoGAN 網(wǎng)絡的過程,然后訓練它使用充當圖像邊緣的手提包草圖來生成逼真的手提包圖像。 我們將從定義生成器網(wǎng)絡的架構(gòu)開始。
預處理圖像
edges2handbags
數(shù)據(jù)集文件夾中的每個圖像在同一圖像中包含bag
的圖片和bag edges
的圖片。 為了訓練網(wǎng)絡,我們需要將它們分離為屬于我們在 DiscoGAN 架構(gòu)中討論過的兩個域A
和 B 的圖像。 通過使用以下代碼(image_split.py
),可以將圖像分為域A
和域B
圖像:
# -*- coding: utf-8 -*-
"""
Created on Fri Apr 13 00:10:12 2018@author: santanu
"""import numpy as np
import os
from scipy.misc import imread
from scipy.misc import imsave
import fire
from elapsedtimer import ElapsedTimer
from pathlib import Path
import shutil
'''
Process the images in Domain A and Domain and resize appropriately
Inputs contain the Domain A and Domain B image in the same image
This program will break them up and store them in their respecective folder'''def process_data(path,_dir_):os.chdir(path)try: os.makedirs('trainA')except:print(f'Folder trainA already present, cleaning up and recreating empty folder trainA')try:os.rmdir('trainA')except:shutil.rmtree('trainA')os.makedirs('trainA')try: os.makedirs('trainB')except:print(f'Folder trainA already present, cleaning up and recreating empty folder trainB')try:os.rmdir('trainB')except:shutil.rmtree('trainB')os.makedirs('trainB')path = Path(path) files = os.listdir(path /_dir_)print('Images to process:', len(files))i = 0for f in files:i+=1 img = imread(path / _dir_ / str(f))w,h,d = img.shapeh_ = int(h/2)img_A = img[:,:h_]img_B = img[:,h_:]imsave(f'{path}/trainA/{str(f)}_A.jpg',img_A)imsave(f'{path}/trainB/{str(f)}_B.jpg',img_A)if ((i % 10000) == 0 & (i >= 10000)):print(f'the number of input images processed : {i}')files_A = os.listdir(path / 'trainA')files_B = os.listdir(path / 'trainB')print(f'No of images written to {path}/trainA is {len(files_A)}')print(f'No of images written to {path}/trainA is {len(files_B)}')with ElapsedTimer('process Domain A and Domain B Images'):fire.Fire(process_data)
image_split.py
代碼可以按以下方式調(diào)用:
python image_split.py --path /media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/edges2handbags/ --_dir_ train
輸出日志如下:
Folder trainA already present, cleaning up and recreating empty folder trainA
Folder trainA already present, cleaning up and recreating empty folder trainB
Images to process: 138569 the number of input images processed : 10000
the number of input images processed : 20000
the number of input images processed : 30000.....
DiscoGAN 的生成器
DiscoGAN 的生成器是前饋卷積神經(jīng)網(wǎng)絡,其中輸入和輸出是圖像。 在網(wǎng)絡的第一部分中,圖像在空間維度上按比例縮小,而輸出特征映射的數(shù)量隨層的進展而增加。 在網(wǎng)絡的第二部分中,圖像沿空間維度按比例放大,而輸出特征映射的數(shù)量則逐層減少。 在最終輸出層中,將生成具有與輸入相同的空間大小的圖像。 如果生成器將圖像x[A]
轉(zhuǎn)換為x[AB]
從域A
到域B
表示為G[AB]
,則我們有x[AB] = G[AB](x[A])
。
此處顯示的是build_generator
函數(shù),我們可以使用它來構(gòu)建 DiscoGAN 網(wǎng)絡的生成器:
def build_generator(self,image,reuse=False,name='generator'):with tf.variable_scope(name):if reuse:tf.get_variable_scope().reuse_variables()else:assert tf.get_variable_scope().reuse is False"""U-Net generator"""def lrelu(x, alpha,name='lrelu'):with tf.variable_scope(name):return tf.nn.relu(x) - alpha * tf.nn.relu(-x)"""Layers used during downsampling"""def common_conv2d(layer_input,filters,f_size=4,stride=2,padding='SAME',norm=True,name='common_conv2d'):with tf.variable_scope(name):if reuse:tf.get_variable_scope().reuse_variables()else:assert tf.get_variable_scope().reuse is Falsed = tf.contrib.layers.conv2d(layer_input,filters,kernel_size=f_size,stride=stride,padding=padding)if norm:d = tf.contrib.layers.batch_norm(d)d = lrelu(d,alpha=0.2)return d"""Layers used during upsampling"""def common_deconv2d(layer_input,filters,f_size=4,stride=2,padding='SAME',dropout_rate=0,name='common_deconv2d'):with tf.variable_scope(name):if reuse:tf.get_variable_scope().reuse_variables()else:assert tf.get_variable_scope().reuse is Falseu = tf.contrib.layers.conv2d_transpose(layer_input,filters,f_size,stride=stride,padding=padding)if dropout_rate:u = tf.contrib.layers.dropout(u,keep_prob=dropout_rate)u = tf.contrib.layers.batch_norm(u)u = tf.nn.relu(u)return u # Downsampling# 64x64 -> 32x32dwn1 = common_conv2d(image,self.gf,stride=2,norm=False,name='dwn1') # 32x32 -> 16x16dwn2 = common_conv2d(dwn1,self.gf*2,stride=2,name='dwn2') # 16x16 -> 8x8dwn3 = common_conv2d(dwn2,self.gf*4,stride=2,name='dwn3') # 8x8 -> 4x4 dwn4 = common_conv2d(dwn3,self.gf*8,stride=2,name='dwn4') # 4x4 -> 1x1 dwn5 = common_conv2d(dwn4,100,stride=1,padding='valid',name='dwn5') # Upsampling# 4x4 -> 4x4up1 = common_deconv2d(dwn5,self.gf*8,stride=1,padding='valid',name='up1') # 4x4 -> 8x8up2 = common_deconv2d(up1,self.gf*4,name='up2') # 8x8 -> 16x16up3 = common_deconv2d(up2,self.gf*2,name='up3') # 16x16 -> 32x32 up4 = common_deconv2d(up3,self.gf,name='up4') out_img = tf.contrib.layers.conv2d_transpose(up4,self.channels,kernel_size=4,stride=2, padding='SAME',activation_fn=tf.nn.tanh) # 32x32 -> 64x64return out_img
在生成器函數(shù)中,我們定義了 LReLU 激活函數(shù),并使用0.2
的泄漏因子。 我們還定義了卷積層生成函數(shù)common_conv2d
(用于對圖像進行下采樣)和common_deconv2d
(用于將經(jīng)降采樣的圖像上采樣至其原始空間大小)。
我們通過使用tf.get_variable_scope().reuse_variables()
使用reuse
選項定義生成器函數(shù)。 當多次調(diào)用同一個生成器函數(shù)時,重用選項可確保我們重用特定生成器使用的相同變量。 當我們刪除重用選項時,我們?yōu)樯善鲃?chuàng)建了一組新的變量。
例如,我們可能使用生成器函數(shù)創(chuàng)建了兩個生成器網(wǎng)絡,因此在第一次創(chuàng)建這些網(wǎng)絡時不會使用reuse
選項。 如果再次引用該生成器函數(shù),則使用reuse
選項。 卷積(下采樣)和解卷積(上采樣)期間的激活函數(shù)是 LReLU,然后進行批量歸一化,以實現(xiàn)穩(wěn)定和快速的收斂。
網(wǎng)絡不同層中的輸出特征映射的數(shù)量可以是self.gf
或其倍數(shù)。 對于我們的 DiscoGAN 網(wǎng)絡,我們選擇了self.gf
作為64
。
生成器中要注意的一件事是輸出層的tanh
激活函數(shù)。 這樣可以確保生成器生成的圖像的像素值在[-1, +1]
的范圍內(nèi)。 這對于輸入圖像具有[-1, +1]
范圍內(nèi)的像素強度非常重要,這可以通過對像素強度進行簡單的逐元素變換來實現(xiàn),如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ltgSG6sX-1681653992520)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/7d1812c6-e792-454e-a693-76421c5d5d91.png)]
同樣,要將圖像轉(zhuǎn)換為可顯示的 0-255 像素強度格式,我們需要應用逆變換,如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-eBevT0Xs-1681653992520)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/118fba60-e8de-4a9c-8ffa-bcd85c456a21.png)]
DiscoGAN 的判別器
DiscoGAN 的判別器將學會在特定域中將真實圖像與假圖像區(qū)分開。 我們將有兩個判別器:一個用于域A
,一個用于域B
。這些判別器也是可以執(zhí)行二分類的卷積網(wǎng)絡。 與傳統(tǒng)的基于分類的卷積網(wǎng)絡不同,判別器沒有任何全連接層。 使用步長為 2 的卷積對輸入圖像進行下采樣,直到最終層(輸出為1 x 1
)為止。同樣,我們使用 LReLU 作為激活函數(shù)并使用批量歸一化以實現(xiàn)穩(wěn)定和快速的收斂。 以下代碼顯示了 TensorFlow 中判別器構(gòu)建函數(shù)的實現(xiàn):
def build_discriminator(self,image,reuse=False,name='discriminator'):with tf.variable_scope(name):if reuse:tf.get_variable_scope().reuse_variables()else:assert tf.get_variable_scope().reuse is Falsedef lrelu(x, alpha,name='lrelu'):with tf.variable_scope(name):if reuse:tf.get_variable_scope().reuse_variables()else:assert tf.get_variable_scope().reuse is Falsereturn tf.nn.relu(x) - alpha * tf.nn.relu(-x)"""Discriminator layer"""def d_layer(layer_input,filters,f_size=4,stride=2,norm=True,name='d_layer'):with tf.variable_scope(name):if reuse:tf.get_variable_scope().reuse_variables()else:assert tf.get_variable_scope().reuse is Falsed = tf.contrib.layers.conv2d(layer_input,filters,kernel_size=f_size,stride=2, padding='SAME')if norm:d = tf.contrib.layers.batch_norm(d)d = lrelu(d,alpha=0.2)return d#64x64 -> 32x32 down1 = d_layer(image,self.df, norm=False,name='down1') #32x32 -> 16x16down2 = d_layer(down1,self.df*2,name='down2') #16x16 -> 8x8down3 = d_layer(down2,self.df*4,name='down3') #8x8 -> 4x4down4 = d_layer(down3,self.df*8,name='down4') #4x4 -> 1x1down5 = tf.contrib.layers.conv2d(down4,1,kernel_size=4,stride=1,padding='valid')return down5
判別器網(wǎng)絡不同層中輸出特征映射的數(shù)量為self.df
或其倍數(shù)。 對于我們的網(wǎng)絡,我們將self.df
設為64
。
建立網(wǎng)絡并定義成本函數(shù)
在本節(jié)中,我們將使用生成器和鑒別函數(shù)來構(gòu)建整個網(wǎng)絡,并定義在訓練過程中要優(yōu)化的成本函數(shù)。 TensorFlow 代碼如下:
def build_network(self):def squared_loss(y_pred,labels):return tf.reduce_mean((y_pred - labels)**2)def abs_loss(y_pred,labels):return tf.reduce_mean(tf.abs(y_pred - labels)) def binary_cross_entropy_loss(logits,labels):return tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(labels=labels,logits=logits))self.images_real = tf.placeholder(tf.float32,[None,self.image_size,self.image_size,self.input_dim + self.output_dim])self.image_real_A = self.images_real[:,:,:,:self.input_dim]self.image_real_B = self.images_real[:,:,:,self.input_dim:self.input_dim + self.output_dim]self.images_fake_B = self.build_generator(self.image_real_A,reuse=False,name='generator_AB')self.images_fake_A = self.build_generator(self.images_fake_B,reuse=False,name='generator_BA')self.images_fake_A_ = self.build_generator(self.image_real_B,reuse=True,name='generator_BA')self.images_fake_B_ = self.build_generator(self.images_fake_A_,reuse=True,name='generator_AB')self.D_B_fake = self.build_discriminator(self.images_fake_B ,reuse=False, name="discriminatorB")self.D_A_fake = self.build_discriminator(self.images_fake_A_,reuse=False, name="discriminatorA") self.D_B_real = self.build_discriminator(self.image_real_B,reuse=True, name="discriminatorB")self.D_A_real = self.build_discriminator(self.image_real_A,reuse=True, name="discriminatorA")self.loss_GABA = self.lambda_l2*squared_loss(self.images_fake_A,self.image_real_A) +binary_cross_entropy_loss(labels=tf.ones_like(self.D_B_fake),logits=self.D_B_fake)self.loss_GBAB = self.lambda_l2*squared_loss(self.images_fake_B_,self.image_real_B) + binary_cross_entropy_loss(labels=tf.ones_like(self.D_A_fake),logits=self.D_A_fake)self.generator_loss = self.loss_GABA + self.loss_GBABself.D_B_loss_real = binary_cross_entropy_loss(tf.ones_like(self.D_B_real),self.D_B_real)self.D_B_loss_fake = binary_cross_entropy_loss(tf.zeros_like(self.D_B_fake),self.D_B_fake)self.D_B_loss = (self.D_B_loss_real + self.D_B_loss_fake) / 2.0self.D_A_loss_real = binary_cross_entropy_loss(tf.ones_like(self.D_A_real),self.D_A_real)self.D_A_loss_fake = binary_cross_entropy_loss(tf.zeros_like(self.D_A_fake),self.D_A_fake)self.D_A_loss = (self.D_A_loss_real + self.D_A_loss_fake) / 2.0self.discriminator_loss = self.D_B_loss + self.D_A_lossself.loss_GABA_sum = tf.summary.scalar("g_loss_a2b", self.loss_GABA)self.loss_GBAB_sum = tf.summary.scalar("g_loss_b2a", self.loss_GBAB)self.g_total_loss_sum = tf.summary.scalar("g_loss", self.generator_loss)self.g_sum = tf.summary.merge([self.loss_GABA_sum,self.loss_GBAB_sum,self.g_total_loss_sum])self.loss_db_sum = tf.summary.scalar("db_loss", self.D_B_loss)self.loss_da_sum = tf.summary.scalar("da_loss", self.D_A_loss)self.loss_d_sum = tf.summary.scalar("d_loss",self.discriminator_loss)self.db_loss_real_sum = tf.summary.scalar("db_loss_real", self.D_B_loss_real)self.db_loss_fake_sum = tf.summary.scalar("db_loss_fake", self.D_B_loss_fake)self.da_loss_real_sum = tf.summary.scalar("da_loss_real", self.D_A_loss_real)self.da_loss_fake_sum = tf.summary.scalar("da_loss_fake", self.D_A_loss_fake)self.d_sum = tf.summary.merge([self.loss_da_sum, self.da_loss_real_sum, self.da_loss_fake_sum,self.loss_db_sum, self.db_loss_real_sum, self.db_loss_fake_sum,self.loss_d_sum])trainable_variables = tf.trainable_variables()self.d_variables = [var for var in trainable_variables if 'discriminator' in var.name]self.g_variables =[var for var in trainable_variables if 'generator' in var.name]print ('Variable printing start :' )for var in self.d_variables: print(var.name)self.test_image_A = tf.placeholder(tf.float32,[None, self.image_size,self.image_size,self.input_dim], name='test_A')self.test_image_B =tf.placeholder(tf.float32,[None, self.image_size,self.image_size,self.output_c_dim], name='test_B')self.saver = tf.train.Saver()
在構(gòu)建網(wǎng)絡中,我們首先定義 L2 范數(shù)誤差和二進制交叉熵誤差的成本函數(shù)。 L2 范數(shù)誤差將用作重建損失,而二元互熵將用作判別器損失。 然后,我們使用生成器函數(shù)為兩個域中的圖像定義占位符,并為每個域中的偽圖像定義 TensorFlow 操作。 我們還通過傳遞特定于域的偽造和真實圖像來定義判別器輸出的操作。 除此之外,我們?yōu)閮蓚€域中的每個域中的重建圖像定義 TensorFlow 操作。
一旦定義了操作,我們就可以使用它們來計算損失函數(shù),同時考慮圖像的重建損失和歸因于判別器的損失。 請注意,我們使用了相同的生成器函數(shù)來定義域A
到B
的生成器,也定義了從B
到A
的生成器。唯一不同的是為這兩個網(wǎng)絡提供了不同的名稱:generator_AB
和 generator_BA
。 由于變量作用域定義為name
,所以這兩個生成器都將具有不同的權(quán)重集,并以提供的名稱為前綴。
下表顯示了我們需要跟蹤的不同損失變量。 就生成器或判別器的參數(shù)而言,所有這些損失都需要最小化:
不同損失的變量 | 說明 |
---|---|
self.D_B_loss_real | 在對域B 中的真實圖像進行分類時,判別器D[B] 的二進制交叉熵損失。(相對于判別器D[B] 的參數(shù),該損失應最小化) |
self.D_B_loss_fake | 在對域B 中的偽造圖像進行分類時,判別器D[B] 的二進制交叉熵損失。(相對于判別器D[B] 的參數(shù),該損失應最小化) |
self.D_A_loss_real | 在對域A 中的真實圖像進行分類時,判別器D[A] 的二進制交叉熵損失。(相對于判別器D[A] 的參數(shù),該損失應最小化) |
self.D_A_loss_fake | 在對域A 中的偽造圖像進行分類時,判別器D[A] 的二進制交叉熵損失。(相對于判別器D[A] 的參數(shù),該損失應最小化) |
self.loss_GABA | 通過兩個生成器G[AB] 和G[BA] 將域A 中的圖像映射到B ,然后再映射回A 的重建損失,加上假圖片G[AB](x[A]) 的二進制交叉熵,由域B 中的判別器標記為真實圖像。(相對于生成器G[AB] 和G[BA] 的參數(shù),該損失應最小化) |
self.loss_GBAB | 通過兩個生成器G[BA] 和G[AB] 將域B 中的圖像映射到A ,然后再映射回B 的重建損失,加上偽圖片G[BA](x[B]) 的二元交叉熵,由域A 中的判別器標記為真實圖像。(相對于生成器G[AB] 和G[BA] 的參數(shù),該損失應最小化) |
前四個損失組成了判別器損失,需要根據(jù)判別器的參數(shù)將其最小化。 ]。 最后兩個損失組成了生成器損失,需要根據(jù)生成器的參數(shù)將其最小化。 。
損失變量通過tf.summary.scaler
與 TensorBoard 綁定,以便可以在訓練過程中監(jiān)控這些損失,以確保以期望的??方式減少損失。 稍后,我們將在訓練進行時在 TensorBoard 中看到這些損失痕跡。
建立訓練流程
在train_network
函數(shù)中,我們首先為生成器和判別器損失函數(shù)定義優(yōu)化器。 我們將 Adam 優(yōu)化器用于生成器和判別器,因為這是隨機梯度下降優(yōu)化器的高級版本,在訓練 GAN 方面非常有效。 亞當使用梯度的衰減平均值(非常類似于穩(wěn)定梯度的動量)和平方梯度的衰減平均值,以提供有關成本函數(shù)曲率的信息。 與tf.summary
定義的不同損失有關的變量將寫入日志文件,因此可以通過 TensorBoard 進行監(jiān)視。 以下是train
函數(shù)的詳細代碼:
def train_network(self):self.learning_rate = tf.placeholder(tf.float32)self.d_optimizer = tf.train.AdamOptimizer(self.learning_rate,beta1=self.beta1,beta2=self.beta2).minimize(self.discriminator_loss,var_list=self.d_variables)self.g_optimizer = tf.train.AdamOptimizer(self.learning_rate,beta1=self.beta1,beta2=self.beta2).minimize(self.generator_loss,var_list=self.g_variables) self.init_op = tf.global_variables_initializer()self.sess = tf.Session()self.sess.run(self.init_op)#self.dataset_dir = '/home/santanu/Downloads/DiscoGAN/edges2handbags/train/'self.writer = tf.summary.FileWriter("./logs", self.sess.graph)count = 1start_time = time.time()for epoch in range(self.epoch):data_A = os.listdir(self.dataset_dir + 'trainA/')data_B = os.listdir(self.dataset_dir + 'trainB/')data_A = [ (self.dataset_dir + 'trainA/' + str(file_name)) for file_name in data_A ] data_B = [ (self.dataset_dir + 'trainB/' + str(file_name)) for file_name in data_B ] np.random.shuffle(data_A)np.random.shuffle(data_B)batch_ids = min(min(len(data_A), len(data_B)), self.train_size) // self.batch_sizelr = self.l_r if epoch < self.epoch_step else self.l_r*(self.epoch-epoch)/(self.epoch-self.epoch_step)for id_ in range(0, batch_ids):batch_files = list(zip(data_A[id_ * self.batch_size:(id_ + 1) * self.batch_size],data_B[id_ * self.batch_size:(id_ + 1) * self.batch_size]))batch_images = [load_train_data(batch_file, self.load_size, self.fine_size) for batch_file in batch_files]batch_images = np.array(batch_images).astype(np.float32)# Update G network and record fake outputsfake_A, fake_B, _, summary_str = self.sess.run([self.images_fake_A_,self.images_fake_B,self.g_optimizer,self.g_sum],feed_dict={self.images_real: batch_images, self.learning_rate:lr})self.writer.add_summary(summary_str, count)[fake_A,fake_B] = self.pool([fake_A, fake_B])# Update D network_, summary_str = self.sess.run([self.d_optimizer,self.d_sum],feed_dict={self.images_real: batch_images,# self.fake_A_sample: fake_A,# self.fake_B_sample: fake_B,self.learning_rate: lr})self.writer.add_summary(summary_str, count)count += 1print(("Epoch: [%2d] [%4d/%4d] time: %4.4f" % (epoch, id_, batch_ids, time.time() - start_time)))if count % self.print_freq == 1:self.sample_model(self.sample_dir, epoch, id_)if count % self.save_freq == 2:self.save_model(self.checkpoint_dir, count)
正如我們在代碼末尾看到的那樣,在訓練期間會不時調(diào)用sample_model
函數(shù),以根據(jù)來自另一個域的輸入圖像來檢查在一個域中生成的圖像的質(zhì)量。 還基于save_freq
定期保存模型。
我們在前面的代碼中引用了sample_model
函數(shù)和save_model
函數(shù),以供參考:
def sample_model(self, sample_dir, epoch, id_):if not os.path.exists(sample_dir):os.makedirs(sample_dir)data_A = os.listdir(self.dataset_dir + 'trainA/')data_B = os.listdir(self.dataset_dir + 'trainB/') data_A = [ (self.dataset_dir + 'trainA/' + str(file_name)) for file_name in data_A ]data_B = [ (self.dataset_dir + 'trainB/' + str(file_name)) for file_name in data_B ]np.random.shuffle(data_A)np.random.shuffle(data_B)batch_files = list(zip(data_A[:self.batch_size], data_B[:self.batch_size]))sample_images = [load_train_data(batch_file, is_testing=True) for batch_file in batch_files]sample_images = np.array(sample_images).astype(np.float32)fake_A, fake_B = self.sess.run([self.images_fake_A_,self.images_fake_B],feed_dict={self.images_real: sample_images})save_images(fake_A, [self.batch_size, 1],'./{}/A_{:02d}_{:04d}.jpg'.format(sample_dir, epoch, id_))save_images(fake_B, [self.batch_size, 1],'./{}/B_{:02d}_{:04d}.jpg'.format(sample_dir, epoch, id_))
在此sample_model
函數(shù)中,從域A
隨機選擇的圖像被拍攝并饋送到生成器G[AB]
,以在域B
中生成圖像。類似地,從域B
隨機選擇的圖像饋送到生成器G[BA]
中,以在域A
中生成圖像。這些輸出圖像由兩個生成器在不同周期生成,并將批量保存在樣本文件夾中,來查看生成器在訓練過程中是否隨著時間的推移而不斷改進,以產(chǎn)生更好的圖像質(zhì)量。
使用 TensorFlow 保存器功能保存模型的save_model
函數(shù)如下所示:
def save_model(self,checkpoint_dir,step):model_name = "discogan.model"model_dir = "%s_%s" % (self.dataset_dir, self.image_size)checkpoint_dir = os.path.join(checkpoint_dir, model_dir)if not os.path.exists(checkpoint_dir):os.makedirs(checkpoint_dir)self.t(self.sess,os.path.join(checkpoint_dir, model_name),global_step=step)
GAN 訓練的重要參數(shù)值
在本節(jié)中,我們將討論用于訓練 DiscoGAN 的不同參數(shù)值。 下表中列出了這些:
| 參數(shù)名稱 | 變量名稱和值集 | 原理 |
| Adam 優(yōu)化器的學習率 | self.l_r = 2e-4
| 我們應該始終訓練學習率較低的 GAN 網(wǎng)絡以獲得更好的穩(wěn)定性,而 DiscoGAN 也不例外。 |
| Adam 優(yōu)化器的衰減率 | self.beta1 = 0.5
self.beta2 = 0.99
| 參數(shù)beta1
定義梯度的衰減平均值,而參數(shù)beta2
定義梯度平方的衰減平均值。 |
| 周期 | self.epoch = 200
| 在此實現(xiàn)中,200
周期足以滿足 DiscoGAN 網(wǎng)絡的收斂要求。 |
| 批量大小 | self.batch_size = 64
| 64
的批量大小非常適合此實現(xiàn)。 但是,由于資源限制,我們可能不得不選擇較小的批量大小。 |
| 學習率線性下降的周期 | epoch_step = 10
| 在epoch_step
指定的周期數(shù)之后,學習率呈線性下降,由以下方案確定:lr = self.l_r if epoch < self.epoch_step else self.l_r*(self.epoch-epoch)/(self.epoch-self.epoch_step)
|
調(diào)用訓練
我們前面說明的所有函數(shù)都是在DiscoGAN()
類內(nèi)創(chuàng)建的,并在__init__
函數(shù)中聲明了重要的參數(shù)值,如以下代碼塊所示。 訓練網(wǎng)絡時僅需要傳遞的兩個參數(shù)是dataset_dir
和需要對其進行訓練的epochs
的數(shù)量
def __init__(self,dataset_dir,epochs=200):# Input shapeself.dataset_dir = dataset_dirself.lambda_l2 = 1.0self.image_size = 64self.input_dim = 3self.output_dim = 3self.batch_size = 64 self.df = 64self.gf = 64self.channels = 3self.output_c_dim = 3self.l_r = 2e-4self.beta1 = 0.5self.beta2 = 0.99self.weight_decay = 0.00001self.epoch = epochsself.train_size = 10000self.epoch_step = 10self.load_size = 64self.fine_size = 64 self.checkpoint_dir = 'checkpoint'self.sample_dir = 'sample'self.print_freq = 5self.save_freq = 10 self.pool = ImagePool()return None
現(xiàn)在我們已經(jīng)定義了訓練模型所需的所有內(nèi)容,我們可以通過process_main
函數(shù)調(diào)用訓練,如下所示:
def process_main(self):self.build_network()self.train_network()
我們之前為訓練所展示的端到端代碼在腳本cycledGAN_edges_to_bags.py
中。 我們可以通過運行 python 腳本cycledGAN_edges_to_bags.py
來訓練模型,如下所示:
python cycledGAN_edges_to_bags.py process_main --dataset_dir /media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/edges2handbags/ epochs 100
腳本cycledGAN_edges_to_bags.py
執(zhí)行的輸出日志如下:
Epoch: [ 0] [ 0/ 156] time: 3.0835
Epoch: [ 0] [ 1/ 156] time: 3.9093
Epoch: [ 0] [ 2/ 156] time: 4.3661
Epoch: [ 0] [ 3/ 156] time: 4.8208
Epoch: [ 0] [ 4/ 156] time: 5.2821
Epoch: [ 0] [ 5/ 156] time: 6.2380
Epoch: [ 0] [ 6/ 156] time: 6.6960
Epoch: [ 0] [ 7/ 156] time: 7.1528
Epoch: [ 0] [ 8/ 156] time: 7.6138
Epoch: [ 0] [ 9/ 156] time: 8.0732
Epoch: [ 0] [ 10/ 156] time: 8.8163
Epoch: [ 0] [ 11/ 156] time: 9.6669
Epoch: [ 0] [ 12/ 156] time: 10.1256
Epoch: [ 0] [ 13/ 156] time: 10.5846
Epoch: [ 0] [ 14/ 156] time: 11.0427
Epoch: [ 0] [ 15/ 156] time: 11.9135
Epoch: [ 0] [ 16/ 156] time: 12.3712
Epoch: [ 0] [ 17/ 156] time: 12.8290
Epoch: [ 0] [ 18/ 156] time: 13.2899
Epoch: [ 0] [ 19/ 156] time: 13.7525
.......
監(jiān)控生成器和判別器損失
可以在 TensorBoard 儀表板中監(jiān)控損失。 TensorBoard 儀表板可以按以下方式調(diào)用:
- 在終端上,運行以下命令:
tensorboard --logdir=./logs
./logs
是特定于該程序的 Tensorboard 日志的存儲目標,應在程序中定義如下:
self.writer = tf.summary.FileWriter("./logs", self.sess.graph)
- 執(zhí)行完步驟 1 中的命令后,導航到 TensorBoard 的
localhost:6006
站點:
以下屏幕快照中展示了在項目中實現(xiàn)的 DiscoGAN 訓練期間在 TensorBoard 中查看的一些生成器和判別器損失的痕跡:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-wMQRiasT-1681653992520)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/8c1362f9-bf37-4c56-ac05-30bb6d00a579.png)]
圖 4.2:Tensorboard Scalars
部分包含不同損失的跡線
以下屏幕截圖顯示了隨著訓練的進行,域A
中判別器的損失成分:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-OgZnUOvk-1681653992520)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/a02c2ba3-678a-4f01-8f16-0addf12d379e.png)]
圖 4.3:域A
中的判別器損失
從前面的屏幕截圖中,我們可以看到不同批量中域A
中判別器的損失。 da_loss
是da_loss_real
和da_loss_fake
損失的總和。 da_loss_real
穩(wěn)步下降,這是因為判別器易于學會識別域A
中的真實圖像,而虛假圖像的損失則穩(wěn)定在 0.69 左右,這是二分類器輸出時您可以期望的logloss
具有1/2
概率的類。 發(fā)生這種情況是因為生成器也在同時學習以使偽圖像看起來逼真,因此使鑒別人員很難輕松地將生成器圖像分類為偽圖像。 域B
上的判別器的損失曲線看起來與域A
的上一幅屏幕快照所示的相似。
現(xiàn)在讓我們看一下生成器的損失曲線,如下所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ObN24vkZ-1681653992520)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/70ea99da-3268-4b40-a8df-225aed87f8b3.png)]
圖 4.4:DiscoGAN 生成器的損失曲線
g_loss_a2b
是從域A
到域B
以及從域B
反向重構(gòu)圖像的組合生成器損失,也是與使變換后的圖像在B
域中看起來逼真相關的二進制交叉熵損失。 g_loss_b2a
是從域B
到域A
以及從域A
重建圖像的組合生成器損失,也是與使變換后的圖像在A
域中看起來逼真相關的二進制交叉熵損失。這兩種損失曲線及其總和隨著批量的進行不斷減少,正如我們從上一個屏幕快照中的 TensorBoard 視覺圖中看到的那樣。
由于訓練生成對抗網(wǎng)絡通常非常棘手,因此監(jiān)視其損失概況的進度以了解訓練是否按預期進行是有意義的。
DiscoGAN 生成的樣本圖像
在本章結(jié)束時,讓我們看一下 DiscoGAN 在兩個域中生成的一些圖像:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZmGrFTWS-1681653992521)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/4feced13-0f3d-4f42-b954-41c050233276.png)]
圖 4.5:根據(jù)草圖生成的手提包圖像
以下屏幕截圖包含手提包草圖的生成圖像(域A
):
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-uyYEEMlF-1681653992521)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/b4acc286-1586-405f-b2a9-52123d6b7c5a.png)]
圖 4.6:根據(jù)手提包圖像生成的草圖
我們可以看到 DiscoGAN 在將任一域中的圖像轉(zhuǎn)換為另一域中的高質(zhì)量逼真的圖像方面做得非常出色。
總結(jié)
現(xiàn)在,我們到了本章的結(jié)尾。 您現(xiàn)在應該精通 DiscoGAN 的技術(shù)知識和實現(xiàn)復雜性。 我們在本章中探討的概念可用于實現(xiàn)各種生成性對抗性網(wǎng)絡,這些網(wǎng)絡具有適合當前問題的細微變化。 DiscoGAN 網(wǎng)絡的端到端實現(xiàn)位于 GitHub 存儲庫中,位于這里。
在第 5 章,“視頻字幕應用”中,我們將研究視頻到文本翻譯應用,它們屬于人工智能領域的專家系統(tǒng)。
五、視頻字幕應用
隨著視頻制作速度成倍增長,視頻已成為一種重要的溝通媒介。 但是,由于缺乏適當?shù)淖帜?#xff0c;視頻仍無法吸引更多的觀眾。
視頻字幕(翻譯視頻以生成有意義的內(nèi)容摘要的藝術(shù))在計算機視覺和機器學習領域是一項具有挑戰(zhàn)性的任務。 傳統(tǒng)的視頻字幕制作方法并沒有成功。 但是,隨著最近借助深度學習在人工智能方面的發(fā)展,視頻字幕最近引起了極大的關注。 卷積神經(jīng)網(wǎng)絡以及循環(huán)神經(jīng)網(wǎng)絡的強大功能使得構(gòu)建端到端企業(yè)級視頻字幕系統(tǒng)成為可能。 卷積神經(jīng)網(wǎng)絡處理視頻中的圖像幀以提取重要特征,這些特征由循環(huán)神經(jīng)網(wǎng)絡依次處理以生成有意義的視頻摘要。 視頻字幕系統(tǒng)的一些重要應用如下:
- 自動監(jiān)控工廠安全措施
- 根據(jù)通過視頻字幕獲得的內(nèi)容對視頻進行聚類
- 銀行,醫(yī)院和其他公共場所的更好的安全系統(tǒng)
- 網(wǎng)站中的視頻搜索,以便提供更好的用戶體驗
通過深度學習構(gòu)建智能視頻字幕系統(tǒng)主要需要兩種類型的數(shù)據(jù):視頻和文本字幕,它們是用于訓練端到端系統(tǒng)的標簽。
作為本章的一部分,我們將討論以下內(nèi)容:
- 討論 CNN 和 LSTM 在視頻字幕中的作用
- 探索序列到序列視頻字幕系統(tǒng)的架構(gòu)
- 利用序列到序列的架構(gòu),構(gòu)建視頻到文本的視頻字幕系統(tǒng)
在下一節(jié)中,我們將介紹如何使用卷積神經(jīng)網(wǎng)絡和循環(huán)神經(jīng)網(wǎng)絡的 LSTM 版本來構(gòu)建端到端視頻字幕系統(tǒng)。
技術(shù)要求
您將需要具備 Python 3,TensorFlow,Keras 和 OpenCV 的基礎知識。
本章的代碼文件可以在 GitHub 上找到
觀看以下視頻,查看運行中的代碼
視頻字幕中的 CNN 和 LSTM
視頻減去音頻后,可以認為是按順序排列的圖像集合。 可以使用針對特定圖像分類問題訓練的卷積神經(jīng)網(wǎng)絡(例如 ImageNet)從這些圖像中提取重要特征。 預訓練網(wǎng)絡的最后一個全連接層的激活可用于從視頻的順序采樣圖像中得出特征。 從視頻順序采樣圖像的頻率速率取決于視頻中內(nèi)容的類型,可以通過訓練進行優(yōu)化。
下圖(“圖 5.1”)說明了用于從視頻中提取特征的預訓練神經(jīng)網(wǎng)絡:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-OeafbBJZ-1681653992521)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/079370cc-0b47-4578-bf5e-3fceaaba687f.png)]
圖 5.1:使用預訓練的神經(jīng)網(wǎng)絡提取視頻圖像特征
從上圖可以看出,從視頻中順序采樣的圖像經(jīng)過預訓練的卷積神經(jīng)網(wǎng)絡,并且輸出最后一個全連接層中的4,096
單元的激活。 如果將t
時的視頻圖像表示為x[t]
,并且最后一個全連接層的輸出表示為f[t] ∈ R^4096
,然后f[t] = f[w](x[t])
。 此處,W
表示直到最后一個全連接層的卷積神經(jīng)網(wǎng)絡的權(quán)重。
這些序列的輸出函數(shù)f[1], f[2], ..., f[t], ..., f[n]
可以作為循環(huán)神經(jīng)網(wǎng)絡的輸入,該神經(jīng)網(wǎng)絡學習根據(jù)輸入特征生成文本標題,如下圖所示(“圖 5.2”):
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-TxKKpQCH-1681653992522)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/c4301074-d0a6-499d-97fb-04b39f20d188.png)]
圖 5.2:處理來自 CNN 的順序輸入特征時的 LSTM
從上圖可以看出,來自預訓練卷積神經(jīng)的生成的特征f[1], f[2], ..., f[t], ..., f[n]
由 LSTM 依次處理,產(chǎn)生文本輸出o[1], o[2], ..., o[t], ..., o[n]
,它們是給定視頻的文本標題。 例如,上圖中的視頻標題可能是“一名戴著黃色頭盔的人正在工作”:
o1, o2, . . . . . ot . . . oN = { "A ","man" "in" "a" "yellow" "helmet" "is" "working"}
既然我們對視頻字幕在深度學習框架中的工作方式有了一個很好的了解,讓我們在下一部分中討論一個更高級的視頻字幕網(wǎng)絡,稱為逐序列視頻字幕。 在本章中,我們將使用相同的網(wǎng)絡架構(gòu)來構(gòu)建視頻字幕系統(tǒng)。
序列到序列的視頻字幕系統(tǒng)
序列到序列的架構(gòu)基于 Subhashini Venugopalan,Marcus Rohrbach,Jeff Donahue,Raymond Mooney,Trevor Darrell 和 Kate Saenko 撰寫的名為《序列到序列-視頻到文本》的論文。 該論文可以在這個頁面中找到。
在下圖(“圖 5.3”)中,說明了基于先前論文的序列到字幕視頻字幕神經(jīng)網(wǎng)絡架構(gòu):
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ogCZ47QG-1681653992522)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/6dac330d-1814-4543-8312-a3b863240d00.png)]
圖 5.3:序列到序列視頻字幕網(wǎng)絡架構(gòu)
序列到序列模型通過像以前一樣通過預訓練的卷積神經(jīng)網(wǎng)絡處理視頻圖像幀,最后一個全連接層的輸出激活被視為要饋送到后續(xù) LSTM 的特征。 如果我們在時間步t
表示預訓練卷積神經(jīng)網(wǎng)絡的最后一個全連接層的輸出激活為f[t] ∈ R^4096
,那么我們將為視頻中的N
個圖像幀使用N
個這樣的特征向量。 這些N
個特征向量f[1], f[2], ..., f[t], ..., f[n]
依次輸入 LSTM,以生成文本標題。
背靠背有兩個 LSTM,LSTM 中的序列數(shù)是來自視頻的圖像幀數(shù)與字幕詞匯表中文本字幕的最大長度之和。 如果在視頻的N
個圖像幀上訓練網(wǎng)絡,并且詞匯表中的最大文本標題長度為M
,則 LSTM 在N + M
時間步長上訓練。 在N
個時間步中,第一個 LSTM 依次處理特征向量f[1], f[2], ..., f[t], ..., f[n]
,并將其生成的隱藏狀態(tài)饋送到第二 LSTM。 在這些N
個時間步中,第二個 LSTM 不需要文本輸出目標。 如果我們將第一個 LSTM 在時間步t
的隱藏狀態(tài)表示為h[t]
,則第二個 LSTM 在前N
個時間步長的輸入為h[t]
。 請注意,從N + 1
時間步長開始,第一個 LSTM 的輸入是零填充的,因此該輸入對t > N
的h[t]
沒有隱藏狀態(tài)的影響。。 請注意,這并不保證t > N
的隱藏狀態(tài)h[t]
總是相同的。 實際上,我們可以選擇在任何時間步長t > N
中將h[t]
作為h[T]
送入第二個 LSTM。
從N + 1
時間步長開始,第二個 LSTM 需要文本輸出目標。 在任何時間步長t > N
的輸入是h[t], w[t-1]
,其中h[t]
是第一個 LSTM 在時間步t
的隱藏狀態(tài),而w[t-1]
是時間步t-1
中的文本標題詞。
在N + 1
時間步長處,饋送到第二 LSTM 的單詞w[N]
是由<bos>
表示的句子的開頭。 一旦生成句子符號<eos>
的結(jié)尾,就訓練網(wǎng)絡停止生成標題詞。 總而言之,兩個 LSTM 的設置方式是,一旦它們處理完所有視頻圖像幀特征 [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Xa0PdQnh-1681653992522)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/8ad769e2-57c1-4581-98e1-7c7f70a12413.png)],它們便開始產(chǎn)生文本標題詞。
處理時間步t > N
的第二個 LSTM 輸入的另一種方法是只喂w[t-1]
而不是h[t], w[t-1]
,并在時間步T
傳遞第一個 LSTM 的隱藏狀態(tài)和單元狀態(tài)h[T], c[T]
,到第二個 LSTM 的初始隱藏和單元狀態(tài)。 這樣的視頻字幕網(wǎng)絡的架構(gòu)可以說明如下(請參見“圖 5.4”):
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-1FT3Homg-1681653992522)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/f2805f4e-f512-4272-8988-4c3b2b73a400.png)]
圖 5.4:序列到序列模型的替代架構(gòu)
預訓練的卷積神經(jīng)網(wǎng)絡通常具有通用架構(gòu),例如VGG16
,VGG19
,ResNet
,并在 ImageNet 上進行了預訓練。 但是,我們可以基于從我們要為其構(gòu)建視頻字幕系統(tǒng)的域中的視頻中提取的圖像來重新訓練這些架構(gòu)。 我們還可以選擇一種全新的 CNN 架構(gòu),并在特定于該域的視頻圖像上對其進行訓練。
到目前為止,我們已經(jīng)介紹了使用本節(jié)中說明的序列到序列架構(gòu)開發(fā)視頻字幕系統(tǒng)的所有技術(shù)先決條件。 請注意,本節(jié)中建議的替代架構(gòu)設計是為了鼓勵讀者嘗試幾種設計,并查看哪種設計最適合給定的問題和數(shù)據(jù)集。
從下一部分開始,我們致力于構(gòu)建智能視頻字幕系統(tǒng)。
視頻字幕系統(tǒng)的數(shù)據(jù)
我們通過在MSVD dataset
上訓練模型來構(gòu)建視頻字幕系統(tǒng),該模型是 Microsoft 的帶預字幕的 YouTube 視頻存儲庫。 可以從以下鏈接下載所需的數(shù)據(jù)??赏ㄟ^以下鏈接獲得視頻的文本標題。
MSVD dataset
中大約有1,938
個視頻。 我們將使用它們來訓練逐序列視頻字幕系統(tǒng)。 還要注意,我們將在“圖 5.3”中所示的序列到序列模型上構(gòu)建模型。 但是,建議讀者嘗試在“圖 5.4”中介紹的架構(gòu)上訓練模型,并了解其表現(xiàn)。
處理視頻圖像來創(chuàng)建 CNN 特征
從指定位置下載數(shù)據(jù)后,下一個任務是處理視頻圖像幀,以從預訓練的卷積神經(jīng)網(wǎng)絡的最后全連接層中提取特征。 我們使用在 ImageNet 上預訓練的VGG16
卷積神經(jīng)網(wǎng)絡。 我們將激活從VGG16
的最后一個全連接層中取出。 由于VGG16
的最后一個全連接層具有4096
單元,因此每個時間步t
的特征向量f[t]
為4096
維向量,f[t] ∈ R^4096
。
在通過VGG16
處理視頻中的圖像之前,需要從視頻中對其進行采樣。 我們從視頻中采樣圖像,使每個視頻具有80
幀。 處理來自VGG16
的80
圖像幀后,每個視頻將具有80
特征向量f[1], f[2], ..., f[80]
。 這些特征將被饋送到 LSTM 以生成文本序列。 我們在 Keras 中使用了預訓練的VGG16
模型。 我們創(chuàng)建一個VideoCaptioningPreProcessing
類,該類首先通過函數(shù)video_to_frames
從每個視頻中提取80
視頻幀作為圖像,然后通過函數(shù)extract_feats_pretrained_cnn
中的預訓練VGG16
卷積神經(jīng)網(wǎng)絡處理這些視頻幀。 。
extract_feats_pretrained_cnn
的輸出是每個視頻幀大小為4096
的 CNN 特征。 由于我們正在處理每個視頻的80
幀,因此每個視頻將具有80
這樣的4096
維向量。
video_to_frames
函數(shù)可以編碼如下:
def video_to_frames(self,video):with open(os.devnull, "w") as ffmpeg_log:if os.path.exists(self.temp_dest):print(" cleanup: " + self.temp_dest + "/")shutil.rmtree(self.temp_dest)os.makedirs(self.temp_dest)video_to_frames_cmd = ["ffmpeg",'-y','-i', video, '-vf', "scale=400:300", '-qscale:v', "2", '{0}/%06d.jpg'.format(self.temp_dest)]subprocess.call(video_to_frames_cmd,stdout=ffmpeg_log, stderr=ffmpeg_log)
從前面的代碼中,我們可以看到在video_to_frames
函數(shù)中,ffmpeg
工具用于將 JPEG 格式的視頻圖像幀轉(zhuǎn)換。 為圖像幀指定為ffmpeg
的大小為300 x 400
。 有關ffmpeg
工具的更多信息,請參考以下鏈接。
在extract_feats_pretrained_cnnfunction
中已建立了從最后一個全連接層中提取特征的預訓練 CNN 模型。 該函數(shù)的代碼如下:
# Extract the features from the pre-trained CNN def extract_feats_pretrained_cnn(self):model = self.model_cnn_load()print('Model loaded')if not os.path.isdir(self.feat_dir):os.mkdir(self.feat_dir)#print("save video feats to %s" % (self.dir_feat))video_list = glob.glob(os.path.join(self.video_dest, '*.avi'))#print video_list for video in tqdm(video_list):video_id = video.split("/")[-1].split(".")[0]print(f'Processing video {video}')#self.dest = 'cnn_feat' + '_' + video_idself.video_to_frames(video)image_list = sorted(glob.glob(os.path.join(self.temp_dest, '*.jpg')))samples = np.round(np.linspace(0, len(image_list) - 1,self.frames_step))image_list = [image_list[int(sample)] for sample in samples]images = np.zeros((len(image_list),self.img_dim,self.img_dim,self.channels))for i in range(len(image_list)):img = self.load_image(image_list[i])images[i] = imgimages = np.array(images)fc_feats = model.predict(images,batch_size=self.batch_cnn)img_feats = np.array(fc_feats)outfile = os.path.join(self.feat_dir, video_id + '.npy')np.save(outfile, img_feats)# cleanupshutil.rmtree(self.temp_dest)
我們首先使用model_cnn_load
函數(shù)加載經(jīng)過預訓練的 CNN 模型,然后針對每個視頻,根據(jù)ffmpeg.
指定的采樣頻率,使用video_to_frames
函數(shù)將多個視頻幀提取為圖像。 圖像通過ffmpeg
創(chuàng)建的視頻中的圖像幀,但是我們使用np.linspace
函數(shù)拍攝了80
等距圖像幀。 使用load_image
函數(shù)將ffmpeg
生成的圖像調(diào)整為224 x 224
的空間大小。 最后,將這些調(diào)整大小后的圖像通過預訓練的 VGG16 卷積神經(jīng)網(wǎng)絡(CNN),并提取輸出層之前的最后一個全連接層的輸出作為特征。 這些提取的特征向量存儲在numpy
數(shù)組中,并在下一階段由 LSTM 網(wǎng)絡進行處理以產(chǎn)生視頻字幕。 本節(jié)中定義的函數(shù)model_cnn_load
函數(shù)定義如下:
def model_cnn_load(self):model = VGG16(weights = "imagenet", include_top=True,input_shape = (self.img_dim,self.img_dim,self.channels))out = model.layers[-2].outputmodel_final = Model(input=model.input,output=out)return model_final
從前面的代碼可以看出,我們正在加載在 ImageNet 上經(jīng)過預訓練的VGG16
卷積神經(jīng)網(wǎng)絡,并提取第二層的輸出(索引為-2
)作為維度為4096
的特征向量 ]。
圖像讀取和調(diào)整大小函數(shù)load_image
定義為在饋入 CNN 之前處理原始ffmpeg
圖像,其定義如下:
def load_image(self,path):img = cv2.imread(path)img = cv2.resize(img,(self.img_dim,self.img_dim))return img
可以通過調(diào)用以下命令來運行預處理腳本:
python VideoCaptioningPreProcessing.py process_main --video_dest '/media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/Video Captioning/data/' --feat_dir '/media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/Video Captioning/features/' --temp_dest '/media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/Video Captioning/temp/' --img_dim 224 --channels 3 --batch_size=128 --frames_step 80
該預處理步驟的輸出是大小為4096
的80
特征向量,被寫為擴展名npy
的 numpy 數(shù)組對象。 每個視頻將在feat_dir
中存儲自己的numpy
數(shù)組對象。 從日志中可以看到,預處理步驟大約運行 28 分鐘:
Processing video /media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/Video Captioning/data/jmoT2we_rqo_0_5.avi
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▋| 1967/1970 [27:57<00:02, 1.09it/s]Processing video /media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/Video Captioning/data/NKtfKR4GNjU_0_20.avi
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▊| 1968/1970 [27:58<00:02, 1.11s/it]Processing video /media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/Video Captioning/data/4cgzdXlJksU_83_90.avi
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▉| 1969/1970 [27:59<00:01, 1.08s/it]Processing video /media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/Video Captioning/data/0IDJG0q9j_k_1_24.avi
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1970/1970 [28:00<00:00, 1.06s/it]
28.045 min: VideoCaptioningPreProcessing
在下一部分中,我們將處理視頻中帶標簽的字幕的預處理。
處理帶標簽的視頻字幕
corpus.csv
文件以文本標題的形式包含視頻的描述(請參見“圖 5.5”)。 以下屏幕快照顯示了數(shù)據(jù)片段。 我們可以刪除一些[VideoID,Start,End]
組合記錄,并將它們視為測試文件,以便稍后進行評估:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-XmInWZk2-1681653992523)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/a78d11a7-39d3-4e85-9bc8-768d81b7ee8c.png)]
圖 5.5:字幕文件格式的快照
VideoID
,Start
和End
列組合以形成以下格式的視頻名稱:VideoID_Start_End.avi
。 基于視頻名稱,來自卷積神經(jīng)網(wǎng)絡VGG16
的特征已存儲為VideoID_Start_End.npy
。 以下代碼塊中所示的函數(shù)是處理視頻的文本標題并創(chuàng)建來自VGG16
的視頻圖像特征的路徑交叉引用:
def get_clean_caption_data(self,text_path,feat_path):text_data = pd.read_csv(text_path, sep=',')text_data = text_data[text_data['Language'] == 'English']text_data['video_path'] =text_data.apply(lambda row: row['VideoID']+'_'+str(int(row['Start']))+'_'+str(int(row['End']))+'.npy', axis=1)text_data['video_path'] = text_data['video_path'].map(lambda x: os.path.join(feat_path, x))text_data = text_data[text_data['video_path'].map(lambda x: os.path.exists(x))]text_data = text_data[text_data['Description'].map(lambda x: isinstance(x, str))]unique_filenames = sorted(text_data['video_path'].unique())data =text_data[text_data['video_path'].map(lambda x: x in unique_filenames)]return data
在已定義的get_data
函數(shù)中,我們從video_corpus.csv
文件中刪除了所有非英語的字幕。 完成后,我們首先通過構(gòu)建視頻名稱(作為VideoID
,Start
和End
函數(shù)的連接)并在特征目錄名稱前添加前綴來形成視頻特征的鏈接。 然后,我們刪除所有未指向features
目錄中任何實際視頻特征向量或具有無效非文本描述的視頻語料庫文件記錄。
數(shù)據(jù)如下圖所示輸出(“圖 5.6”):
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ezjcGNWv-1681653992523)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/c08fc9df-4002-49ca-80f3-f9118e0055b1.png)]
圖 5.6:預處理后的字幕數(shù)據(jù)
建立訓練和測試數(shù)據(jù)集
訓練模型后,我們想評估模型的運行情況。 我們可以根據(jù)測試集中的視頻內(nèi)容驗證為測試數(shù)據(jù)集生成的字幕。 可以使用以下函數(shù)創(chuàng)建訓練測試集數(shù)據(jù)集。 我們可以在訓練期間創(chuàng)建測試數(shù)據(jù)集,并在訓練模型后將其用于評估:
def train_test_split(self,data,test_frac=0.2):indices = np.arange(len(data))np.random.shuffle(indices)train_indices_rec = int((1 - test_frac)*len(data))indices_train = indices[:train_indices_rec]indices_test = indices[train_indices_rec:]data_train, data_test = data.iloc[indices_train],data.iloc[indices_test]data_train.reset_index(inplace=True)data_test.reset_index(inplace=True)return data_train,data_test
通常保留 20% 的數(shù)據(jù)用于評估是一種公平的做法。
建立模型
在本節(jié)中,說明了核心模型構(gòu)建練習。 我們首先為文本標題的詞匯表中的單詞定義一個嵌入層,然后是兩個 LSTM。 權(quán)重self.encode_W
和self.encode_b
用于減少卷積神經(jīng)網(wǎng)絡中特征f[t]
的大小。 對于第二個 LSTM(LSTM 2),在任何時間步長t > N
處的其他輸入之一是前一個單詞w[t-1]
,以及來自第一個 LSTM(LSTM 1)的輸出h[t]
。 將w[t-1]
的詞嵌入送入 LSTM 2,而不是原始的單熱編碼向量。 對于前N
個(self.video_lstm_step)
,LSTM 1 處理來自 CNN 的輸入特征f[t]
,并且輸出的隱藏狀態(tài)h[t]
(輸出 1)被饋送到 LSTM2。在此編碼階段,LSTM 2 不會接收任何單詞w[t-1]
作為輸入。
從N + 1
時間步長,我們進入解碼階段,在此階段,連同來自 LSTM 1 的h[t]
(輸出 1),先前的時間步的詞嵌入向量w[t-1]
被饋送到 LSTM2。在這一階段,沒有到 LSTM 1 的輸入,因為所有特征f[t]
在時間步N
耗盡。 解碼階段的時間步長由self.caption_lstm_step
確定。
現(xiàn)在,如果我們用函數(shù)f[2]
表示 LSTM 2 的活動,則f[2](h[t], w[t-1]) = h[2t]
,其中h[2t]
是 LSTM 2 在時間步t
的隱藏狀態(tài)。 該隱藏狀態(tài)h[2t]
在時間t
時,通過 softmax 函數(shù)轉(zhuǎn)換為輸出單詞的概率分布, 選擇最高概率的單詞作為下一個單詞o_hat[t]
:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-0dRwPAxb-1681653992523)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/09b71b6c-5a27-49ae-965b-98e8810fbdb9.png)] = [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZEzVRtLk-1681653992524)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/9ae080a8-cf15-45f5-ad50-b98244404366.png)]
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-k6QqGDjk-1681653992524)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/aa71833b-2882-4ace-abe6-d6dad3254469.png)]
這些權(quán)重W[ho]
和b
在以下代碼塊中定義為self.word_emb_W
和self.word_emb_b
。 有關更多詳細信息,請參考build_model
函數(shù)。 為了方便解釋,構(gòu)建函數(shù)分為三部分。 構(gòu)建模型有 3 個主要單元
- 定義階段:定義變量,標題詞的嵌入層和序列到序列模型的兩個 LSTM。
- 編碼階段:在這一階段中,我們將視頻幀圖像特征傳遞給 LSTM1 的時間步長,并將每個時間步長的隱藏狀態(tài)傳遞給 LSTM2。此活動一直進行到時間步長
N
其中N
是從每個視頻中采樣的視頻幀圖像的數(shù)量。 - 解碼階段:在解碼階段,LSTM 2 開始生成文本字幕。 關于時間步驟,解碼階段從步驟
N + 1
開始。 從 LSTM 2 的每個時間步生成的單詞將作為輸入與 LSTM 1 的隱藏狀態(tài)一起輸入到下一個狀態(tài)。
模型變量的定義
視頻字幕模型的變量和其他相關定義可以定義如下:
Defining the weights associated with the Networkwith tf.device('/cpu:0'):self.word_emb = tf.Variable(tf.random_uniform([self.n_words, self.dim_hidden],-0.1, 0.1), name='word_emb')self.lstm1 = tf.nn.rnn_cell.BasicLSTMCell(self.dim_hidden, state_is_tuple=False)self.lstm2 = tf.nn.rnn_cell.BasicLSTMCell(self.dim_hidden, state_is_tuple=False)self.encode_W = tf.Variable( tf.random_uniform([self.dim_image,self.dim_hidden],-0.1, 0.1), name='encode_W')self.encode_b = tf.Variable( tf.zeros([self.dim_hidden]), name='encode_b')self.word_emb_W =tf.Variable(tf.random_uniform([self.dim_hidden,self.n_words], -0.1,0.1), name='word_emb_W')self.word_emb_b = tf.Variable(tf.zeros([self.n_words]), name='word_emb_b')# Placeholders video = tf.placeholder(tf.float32, [self.batch_size, self.video_lstm_step, self.dim_image])video_mask = tf.placeholder(tf.float32, [self.batch_size, self.video_lstm_step])caption = tf.placeholder(tf.int32, [self.batch_size, self.caption_lstm_step+1])caption_mask = tf.placeholder(tf.float32, [self.batch_size, self.caption_lstm_step+1])video_flat = tf.reshape(video, [-1, self.dim_image])image_emb = tf.nn.xw_plus_b( video_flat, self.encode_W,self.encode_b )image_emb = tf.reshape(image_emb, [self.batch_size, self.lstm_steps, self.dim_hidden])state1 = tf.zeros([self.batch_size, self.lstm1.state_size])state2 = tf.zeros([self.batch_size, self.lstm2.state_size])padding = tf.zeros([self.batch_size, self.dim_hidden])
所有相關變量以及占位符均由先前的代碼定義。
編碼階段
在編碼階段,我們通過使它們經(jīng)過 LSTM 1 的時間步長,依次處理每個視頻圖像幀特征(來自 CNN 最后一層)。視頻圖像幀的維數(shù)為4096.
,然后再將這些高維視頻幀特征向量饋入 LSTM 1,它們縮小為較小的512.
LSTM 1 在每個時間步長處理視頻幀圖像并將隱藏狀態(tài)傳遞給 LSTM 2,此過程一直持續(xù)到時間步長N
(self.video_lstm_step
)。 編碼器的代碼如下:
probs = []loss = 0.0# Encoding Stage for i in range(0, self.video_lstm_step):if i > 0:tf.get_variable_scope().reuse_variables()with tf.variable_scope("LSTM1"):output1, state1 = self.lstm1(image_emb[:,i,:], state1)with tf.variable_scope("LSTM2"):output2, state2 = self.lstm2(tf.concat([padding, output1],1), state2)
解碼階段
在解碼階段,產(chǎn)生用于視頻字幕的單詞。 沒有更多輸入到 LSTM1。但是 LSTM 1 前滾,并且所產(chǎn)生的隱藏狀態(tài)像以前一樣饋送到 LSTM 2 時間步長。 在每個步驟中,LSTM 2 的另一個輸入是標題中前一個單詞的嵌入向量。 因此,在每個步驟中,LSTM 2 都會生成一個新的字幕詞,該詞以在前一個時間步中預測的單詞為條件,并帶有該時間步的 LSTM 1 的隱藏狀態(tài)。 解碼器的代碼如下:
# Decoding Stage to generate Captions for i in range(0, self.caption_lstm_step):with tf.device("/cpu:0"):current_embed = tf.nn.embedding_lookup(self.word_emb, caption[:, i])tf.get_variable_scope().reuse_variables()with tf.variable_scope("LSTM1"):output1, state1 = self.lstm1(padding, state1)with tf.variable_scope("LSTM2"):output2, state2 = self.lstm2(tf.concat([current_embed, output1],1), state2)
為每個小批量建立損失
在 LSTM 2 的每個時間步長上,優(yōu)化的損失是關于從字幕單詞的整個語料庫中預測正確單詞的分類交叉熵損失。 對于批量中的所有數(shù)據(jù)點,在解碼階段的每個步驟中都會累積相同的內(nèi)容。 與解碼階段的損失累積相關的代碼如下:
labels = tf.expand_dims(caption[:, i+1], 1)indices = tf.expand_dims(tf.range(0, self.batch_size, 1), 1)concated = tf.concat([indices, labels],1)onehot_labels = tf.sparse_to_dense(concated, tf.stack([self.batch_size,self.n_words]), 1.0, 0.0)logit_words = tf.nn.xw_plus_b(output2, self.word_emb_W, self.word_emb_b)# Computing the loss cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=logit_words,labels=onehot_labels)cross_entropy = cross_entropy * caption_mask[:,i]probs.append(logit_words)current_loss = tf.reduce_sum(cross_entropy)/self.batch_sizeloss = loss + current_loss
可以使用任何合理的梯度下降優(yōu)化器(例如 Adam,RMSprop 等)來優(yōu)化損失。 我們將選擇Adam
進行實驗,因為它對大多數(shù)深度學習優(yōu)化都表現(xiàn)良好。 我們可以使用 Adam 優(yōu)化器定義訓練操作,如下所示:
with tf.variable_scope(tf.get_variable_scope(),reuse=tf.AUTO_REUSE):train_op = tf.train.AdamOptimizer(self.learning_rate).minimize(loss)
為字幕創(chuàng)建單詞詞匯
在本節(jié)中,我們?yōu)橐曨l字幕創(chuàng)建單詞詞匯。 我們創(chuàng)建了一些其他單詞,要求如下:
eos => End of Sentence
bos => Beginning of Sentence
pad => When there is no word to feed,required by the LSTM 2 in the initial N time steps
unk => A substitute for a word that is not included in the vocabulary
其中一個單詞是輸入的 LSTM 2 將需要這四個附加符號。 對于N + 1
時間步長,當我們開始生成字幕時,我們將前一個時間步長w[t-1]
的單詞送入。 對于要生成的第一個單詞,沒有有效的上一個時間步長單詞,因此我們輸入了偽單詞<bos>
,它表示句子的開頭。 同樣,當我們到達最后一個時間步時, w[t-1]
是字幕的最后一個字。 我們訓練模型以將最終單詞輸出為<eos>
,它表示句子的結(jié)尾。 當遇到句子結(jié)尾時,LSTM 2 停止發(fā)出任何其他單詞。
為了舉例說明,我們以句子The weather is beautiful
。 以下是從時間步N + 1
開始的 LSTM 2 的輸入和輸出標簽:
時間步長 | 輸入 | 輸出 |
---|---|---|
N + 1 | <bos> , h[N + 1] | The |
N + 2 | The ,h[N + 2] | weather |
N + 3 | weather ,h[N + 3] | is |
N + 4 | is ,h[N + 4] | beautiful |
N + 5 | beautiful ,h[N + 5] | <eos> |
用來詳細說明單詞的create_word_dict
函數(shù)如下所示:
def create_word_dict(self,sentence_iterator, word_count_threshold=5):word_counts = {}sent_cnt = 0for sent in sentence_iterator:sent_cnt += 1for w in sent.lower().split(' '):word_counts[w] = word_counts.get(w, 0) + 1vocab = [w for w in word_counts if word_counts[w] >= word_count_threshold]idx2word = {}idx2word[0] = '<pad>'idx2word[1] = '<bos>'idx2word[2] = '<eos>'idx2word[3] = '<unk>'word2idx = {}word2idx['<pad>'] = 0word2idx['<bos>'] = 1word2idx['<eos>'] = 2word2idx['<unk>'] = 3for idx, w in enumerate(vocab):word2idx[w] = idx+4idx2word[idx+4] = wword_counts['<pad>'] = sent_cntword_counts['<bos>'] = sent_cntword_counts['<eos>'] = sent_cntword_counts['<unk>'] = sent_cntreturn word2idx,idx2word
訓練模型
在本節(jié)中,我們將所有部分放在一起以構(gòu)建用于訓練視頻字幕模型的函數(shù)。
首先,我們結(jié)合訓練和測試數(shù)據(jù)集中的視頻字幕,創(chuàng)建單詞詞匯詞典。 完成此操作后,我們將結(jié)合兩個 LSTM 調(diào)用build_model
函數(shù)來創(chuàng)建視頻字幕網(wǎng)絡。 對于每個帶有特定開始和結(jié)束的視頻,都有多個輸出視頻字幕。 在每個批量中,從開始和結(jié)束的特定視頻的輸出視頻字幕是從多個可用的視頻字幕中隨機選擇的。 調(diào)整 LSTM 2 的輸入文本標題,使其在時間步N + 1
處的起始詞為<bos>
,而輸出文本標題的結(jié)束詞被調(diào)整為最終文本標簽<eos>
。 每個時間步長上的分類交叉熵損失之和被視為特定視頻的總交叉熵損失。 在每個時間步中,我們計算完整單詞詞匯上的分類交叉熵損失,可以表示為:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-gRwjv412-1681653992524)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/21a19e2b-fef3-4156-b1b2-8812e5a6b329.png)]
此處,y^(t) = [y1^(t), y2^(t), ..., y[V]^(t)]
是時間步長t
時實際目標單詞的單熱編碼向量,而p^(t) = [p1^(t), p2^(t), ..., p[V]^(t)]
是模型預測的概率向量。
在訓練期間的每個周期都會記錄損失,以了解損失減少的性質(zhì)。 這里要注意的另一件事是,我們正在使用 TensorFlow 的tf.train.saver
函數(shù)保存經(jīng)過訓練的模型,以便我們可以恢復模型以進行推理。
train
函數(shù)的詳細代碼在此處說明以供參考:
def train(self):data = self.get_data(self.train_text_path,self.train_feat_path)self.train_data,self.test_data = self.train_test_split(data,test_frac=0.2)self.train_data.to_csv(f'{self.path_prj}/train.csv',index=False)self.test_data.to_csv(f'{self.path_prj}/test.csv',index=False)print(f'Processed train file written to {self.path_prj}/train_corpus.csv')print(f'Processed test file written to {self.path_prj}/test_corpus.csv')train_captions = self.train_data['Description'].valuestest_captions = self.test_data['Description'].valuescaptions_list = list(train_captions) captions = np.asarray(captions_list, dtype=np.object)captions = list(map(lambda x: x.replace('.', ''), captions))captions = list(map(lambda x: x.replace(',', ''), captions))captions = list(map(lambda x: x.replace('"', ''), captions))captions = list(map(lambda x: x.replace('\n', ''), captions))captions = list(map(lambda x: x.replace('?', ''), captions))captions = list(map(lambda x: x.replace('!', ''), captions))captions = list(map(lambda x: x.replace('\\', ''), captions))captions = list(map(lambda x: x.replace('/', ''), captions))self.word2idx,self.idx2word = self.create_word_dict(captions, word_count_threshold=0)np.save(self.path_prj/ "word2idx",self.word2idx)np.save(self.path_prj/ "idx2word" ,self.idx2word)self.n_words = len(self.word2idx)tf_loss, tf_video,tf_video_mask,tf_caption,tf_caption_mask, tf_probs,train_op= self.build_model()sess = tf.InteractiveSession()saver = tf.train.Saver(max_to_keep=100, write_version=1)tf.global_variables_initializer().run()loss_out = open('loss.txt', 'w')val_loss = []for epoch in range(0,self.epochs):val_loss_epoch = []index = np.arange(len(self.train_data))self.train_data.reset_index()np.random.shuffle(index)self.train_data = self.train_data.loc[index]current_train_data = self.train_data.groupby(['video_path']).first().reset_index()for start, end in zip(range(0, len(current_train_data),self.batch_size),range(self.batch_size,len(current_train_data),self.batch_size)):start_time = time.time()current_batch = current_train_data[start:end]current_videos = current_batch['video_path'].valuescurrent_feats = np.zeros((self.batch_size, self.video_lstm_step,self.dim_image))current_feats_vals = list(map(lambda vid: np.load(vid),current_videos))current_feats_vals = np.array(current_feats_vals) current_video_masks = np.zeros((self.batch_size,self.video_lstm_step))for ind,feat in enumerate(current_feats_vals):current_feats[ind][:len(current_feats_vals[ind])] = featcurrent_video_masks[ind][:len(current_feats_vals[ind])] = 1current_captions = current_batch['Description'].valuescurrent_captions = list(map(lambda x: '<bos> ' + x, current_captions))current_captions = list(map(lambda x: x.replace('.', ''), current_captions))current_captions = list(map(lambda x: x.replace(',', ''), current_captions))current_captions = list(map(lambda x: x.replace('"', ''), current_captions))current_captions = list(map(lambda x: x.replace('\n', ''), current_captions))current_captions = list(map(lambda x: x.replace('?', ''), current_captions))current_captions = list(map(lambda x: x.replace('!', ''), current_captions))current_captions = list(map(lambda x: x.replace('\\', ''), current_captions))current_captions = list(map(lambda x: x.replace('/', ''), current_captions))for idx, each_cap in enumerate(current_captions):word = each_cap.lower().split(' ')if len(word) < self.caption_lstm_step:current_captions[idx] = current_captions[idx] + ' <eos>'else:new_word = ''for i in range(self.caption_lstm_step-1):new_word = new_word + word[i] + ' 'current_captions[idx] = new_word + '<eos>'current_caption_ind = []for cap in current_captions:current_word_ind = []for word in cap.lower().split(' '):if word in self.word2idx:current_word_ind.append(self.word2idx[word])else:current_word_ind.append(self.word2idx['<unk>'])current_caption_ind.append(current_word_ind)current_caption_matrix = sequence.pad_sequences(current_caption_ind, padding='post', maxlen=self.caption_lstm_step)current_caption_matrix = np.hstack( [current_caption_matrix, np.zeros([len(current_caption_matrix), 1] ) ] ).astype(int)current_caption_masks =np.zeros( (current_caption_matrix.shape[0], current_caption_matrix.shape[1]) )nonzeros = np.array( list(map(lambda x: (x != 0).sum() + 1, current_caption_matrix ) ))for ind, row in enumerate(current_caption_masks):row[:nonzeros[ind]] = 1probs_val = sess.run(tf_probs, feed_dict={tf_video:current_feats,tf_caption: current_caption_matrix})_, loss_val = sess.run([train_op, tf_loss],feed_dict={tf_video: current_feats,tf_video_mask : current_video_masks,tf_caption: current_caption_matrix,tf_caption_mask: current_caption_masks})val_loss_epoch.append(loss_val)print('Batch starting index: ', start, " Epoch: ", epoch, " loss: ", loss_val, ' Elapsed time: ', str((time.time() - start_time)))loss_out.write('epoch ' + str(epoch) + ' loss ' + str(loss_val) + '\n')# draw loss curve every epochval_loss.append(np.mean(val_loss_epoch))plt_save_dir = self.path_prj / "loss_imgs"plt_save_img_name = str(epoch) + '.png'plt.plot(range(len(val_loss)),val_loss, color='g')plt.grid(True)plt.savefig(os.path.join(plt_save_dir, plt_save_img_name))if np.mod(epoch,9) == 0:print ("Epoch ", epoch, " is done. Saving the model ...")saver.save(sess, os.path.join(self.path_prj, 'model'), global_step=epoch)loss_out.close()
從前面的代碼中我們可以看到,我們通過根據(jù)batch_size.
隨機選擇一組視頻來創(chuàng)建每個批量
對于每個視頻,標簽是隨機選擇的,因為同一視頻已被多個標記器標記。 對于每個選定的字幕,我們都會清理字幕文本,并將它們中的單詞轉(zhuǎn)換為單詞索引。 字幕的目標移動了 1 個時間步,因為在每一步中,我們都根據(jù)字幕中的前一個單詞來預測單詞。 針對指定的周期數(shù)訓練模型,并在指定的周期間隔(此處為9
)對模型進行檢查。
訓練結(jié)果
可以使用以下命令訓練模型:
python Video_seq2seq.py process_main --path_prj '/media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/Video Captioning/' --caption_file video_corpus.csv --feat_dir features --cnn_feat_dim 4096 --h_dim 512 --batch_size 32 --lstm_steps 80 --video_steps=80 --out_steps 20 --learning_rate 1e-4--epochs=100
參數(shù) | 值 |
---|---|
Optimizer | Adam |
learning rate | 1e-4 |
Batch size | 32 |
Epochs | 100 |
cnn_feat_dim | 4096 |
lstm_steps | 80 |
out_steps | 20 |
h_dim | 512 |
訓練的輸出日志如下:
Batch starting index: 1728 Epoch: 99 loss: 17.723186 Elapsed time: 0.21822428703308105
Batch starting index: 1760 Epoch: 99 loss: 19.556421 Elapsed time: 0.2106935977935791
Batch starting index: 1792 Epoch: 99 loss: 21.919321 Elapsed time: 0.2206578254699707
Batch starting index: 1824 Epoch: 99 loss: 15.057275 Elapsed time: 0.21275663375854492
Batch starting index: 1856 Epoch: 99 loss: 19.633915 Elapsed time: 0.21492290496826172
Batch starting index: 1888 Epoch: 99 loss: 13.986136 Elapsed time: 0.21542596817016602
Batch starting index: 1920 Epoch: 99 loss: 14.300303 Elapsed time: 0.21855640411376953
Epoch 99 is done. Saving the model ...
24.343 min: Video Captioning
我們可以看到,使用 GeForce Zotac 1070 GPU 在 100 個時間段上訓練模型大約需要 24 分鐘。
每個周期的訓練損失減少表示如下(“圖 5.7”):
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-9v8CZD7D-1681653992524)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/350a0f8a-c04a-409d-835e-4a0c1448b630.png)]
圖 5.7 訓練期間的損失概況
從前面的圖表(“圖 5.7”)可以看出,損失減少在最初的幾個周期中較高,然后在周期80
處逐漸減小。 在下一節(jié)中,我們將說明該模型在為看不見的視頻生成字幕時的表現(xiàn)。
用沒見過的測試視頻推斷
為了進行推理,我們構(gòu)建了一個生成器函數(shù)build_generator
,該函數(shù)復制了build_model
的邏輯以定義所有模型變量,以及所需的 TensorFlow 操作來加載模型并在同一模型上進行推理:
def build_generator(self):with tf.device('/cpu:0'):self.word_emb = tf.Variable(tf.random_uniform([self.n_words, self.dim_hidden],-0.1, 0.1), name='word_emb')self.lstm1 =tf.nn.rnn_cell.BasicLSTMCell(self.dim_hidden, state_is_tuple=False)self.lstm2 = tf.nn.rnn_cell.BasicLSTMCell(self.dim_hidden, state_is_tuple=False)self.encode_W = tf.Variable(tf.random_uniform([self.dim_image,self.dim_hidden], -0.1, 0.1), name='encode_W')self.encode_b = tf.Variable(tf.zeros([self.dim_hidden]), name='encode_b')self.word_emb_W = tf.Variable(tf.random_uniform([self.dim_hidden,self.n_words],-0.1,0.1), name='word_emb_W')self.word_emb_b = tf.Variable(tf.zeros([self.n_words]), name='word_emb_b')video = tf.placeholder(tf.float32, [1, self.video_lstm_step, self.dim_image])video_mask = tf.placeholder(tf.float32, [1, self.video_lstm_step])video_flat = tf.reshape(video, [-1, self.dim_image])image_emb = tf.nn.xw_plus_b(video_flat, self.encode_W, self.encode_b)image_emb = tf.reshape(image_emb, [1, self.video_lstm_step, self.dim_hidden])state1 = tf.zeros([1, self.lstm1.state_size])state2 = tf.zeros([1, self.lstm2.state_size])padding = tf.zeros([1, self.dim_hidden])generated_words = []probs = []embeds = []for i in range(0, self.video_lstm_step):if i > 0:tf.get_variable_scope().reuse_variables()with tf.variable_scope("LSTM1"):output1, state1 = self.lstm1(image_emb[:, i, :], state1)with tf.variable_scope("LSTM2"):output2, state2 = self.lstm2(tf.concat([padding, output1],1), state2)for i in range(0, self.caption_lstm_step):tf.get_variable_scope().reuse_variables()if i == 0:with tf.device('/cpu:0'):current_embed = tf.nn.embedding_lookup(self.word_emb, tf.ones([1], dtype=tf.int64))with tf.variable_scope("LSTM1"):output1, state1 = self.lstm1(padding, state1)with tf.variable_scope("LSTM2"):output2, state2 = self.lstm2(tf.concat([current_embed, output1],1), state2)logit_words = tf.nn.xw_plus_b( output2, self.word_emb_W, self.word_emb_b)max_prob_index = tf.argmax(logit_words, 1)[0]generated_words.append(max_prob_index)probs.append(logit_words)with tf.device("/cpu:0"):current_embed =tf.nn.embedding_lookup(self.word_emb, max_prob_index)current_embed = tf.expand_dims(current_embed, 0)embeds.append(current_embed)return video, video_mask, generated_words, probs, embeds
推理函數(shù)
在推理過程中,我們調(diào)用build_generator
定義模型以及推理所需的其他 TensorFlow 操作,然后使用tf.train.Saver.restoreutility
從訓練后的模型中保存已保存的權(quán)重,以加載定義的模型。 一旦加載了模型并準備對每個測試視頻進行推理,我們就從 CNN 中提取其對應的視頻幀圖像預處理特征并將其傳遞給模型進行推理:
def inference(self):self.test_data = self.get_test_data(self.test_text_path,self.test_feat_path)test_videos = self.test_data['video_path'].unique()self.idx2word = pd.Series(np.load(self.path_prj / "idx2word.npy").tolist())self.n_words = len(self.idx2word)video_tf, video_mask_tf, caption_tf, probs_tf, last_embed_tf = self.build_generator()sess = tf.InteractiveSession()saver = tf.train.Saver()saver.restore(sess,self.model_path)f = open(f'{self.path_prj}/video_captioning_results.txt', 'w')for idx, video_feat_path in enumerate(test_videos):video_feat = np.load(video_feat_path)[None,...]if video_feat.shape[1] == self.frame_step:video_mask = np.ones((video_feat.shape[0], video_feat.shape[1]))else:continuegen_word_idx = sess.run(caption_tf, feed_dict={video_tf:video_feat, video_mask_tf:video_mask})gen_words = self.idx2word[gen_word_idx]punct = np.argmax(np.array(gen_words) == '<eos>') + 1gen_words = gen_words[:punct]gen_sent = ' '.join(gen_words)gen_sent = gen_sent.replace('<bos> ', '')gen_sent = gen_sent.replace(' <eos>', '')print(f'Video path {video_feat_path} : Generated Caption {gen_sent}')print(gen_sent,'\n')f.write(video_feat_path + '\n')f.write(gen_sent + '\n\n')
可以通過調(diào)用以下命令來運行推理:
python Video_seq2seq.py process_main --path_prj '/media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/Video Captioning/' --caption_file '/media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/Video Captioning/test.csv' --feat_dir features --mode inference --model_path '/media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/Video Captioning/model-99'
評估結(jié)果
評估結(jié)果很有希望。 來自測試集0lh_UWF9ZP4_82_87.avi
和8MVo7fje_oE_139_144.avi
的兩個視頻的推斷結(jié)果顯示如下:
在以下屏幕截圖中,我們說明了對視頻video0lh_
UWF9ZP4_82_87.avi
的推斷結(jié)果:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Wlx1VU4l-1681653992525)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/654f2dd8-315e-4313-a3cd-5720be8ac811.png)]
使用經(jīng)過訓練的模型對視頻0lh_UWF9ZP4_82_87.avi
進行推斷
在以下屏幕截圖中,我們說明了對另一個video8MVo7fje_oE_139_144.avi
的推斷結(jié)果:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-HEx5NeQJ-1681653992525)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/4b44212d-a2f4-4aa3-8db7-9f3147a53025.png)]
使用訓練后的模型推斷視頻/8MVo7fje_oE_139_144.avi
從前面的屏幕截圖中,我們可以看到訓練后的模型為提供的測試視頻提供了很好的字幕,表現(xiàn)出色。
該項目的代碼可以在 GitHub 中找到。 VideoCaptioningPreProcessing.py
模塊可用于預處理視頻并創(chuàng)建卷積神經(jīng)網(wǎng)絡特征,而Video_seq2seq.py
模塊可用于訓練端到端視頻字幕系統(tǒng)和運行推斷。
總結(jié)
現(xiàn)在,我們已經(jīng)完成了令人興奮的視頻字幕項目的結(jié)尾。 您應該能夠使用 TensorFlow 和 Keras 構(gòu)建自己的視頻字幕系統(tǒng)。 您還應該能夠使用本章中介紹的技術(shù)知識來開發(fā)其他涉及卷積神經(jīng)網(wǎng)絡和循環(huán)神經(jīng)網(wǎng)絡的高級模型。 下一章將使用受限玻爾茲曼機構(gòu)建智能的推薦系統(tǒng)。 期待您的參與!