1. 使用Pyhon+Flux+Julia實現手寫數字識別

        共 7611字,需瀏覽 16分鐘

         ·

        2020-09-15 22:40

        點擊上方小白學視覺”,選擇加"星標"或“置頂

        重磅干貨,第一時間送達


        使用MNIST數據集對0到9之間的數字進行手寫數字識別是神經網絡的一個典型入門教程。
        該技術在現實場景中是很有用的,比如可以把該技術用來掃描銀行轉帳單或支票,其中帳號和需要轉賬的金額可以被識別處理并寫在明確定義的方框中。
        在本教程中,我們將介紹如何使用Julia編程語言和名為Flux的機器學習庫來實現這一技術。

        為什么使用Flux和Julia?

        本教程為什么想使用Flux(https://fluxml.ai/) 和Julia(https://julialang.org/) ,而不是像Torch、PyTorch、Keras或TensorFlow 2.0這樣的知名框架呢?
        一個很好的原因是因為Flux更易于學習,而且它提供更好的性能和擁有有更大的潛力,另外一個原因是,Flux在仍然是一個小庫的情況下實現了很多功能。Flux庫非常小,因為它所做的大部分工作都是由Julia編程語言本身提供的。
        例如,如果你查看Gorgonia ML庫(https://github.com/gorgonia/gorgonia) 中的Go編程語言,你將看到,它明確地展示了其他機器學習庫如何構建一個需要執(zhí)行和區(qū)分的表達式圖。在Flux中,這個圖就是Julia本身。Julia與LISP非常相似,因為Julia代碼可以很容易地表示為數據結構,可以對其進行修改和計算。

        機器學習概論

        如果你是機器學習的新手,你可以跟著本教程來學習,但并不是所有的東西對你來說都是有價值的。你也可以看看我以前關于Medium的一些文章,它們可能會解釋你一些新手的疑惑:
        • 線性代數的核心思想。(https://medium.com/@Jernfrost/the-core-idea-of-linear-algebra-7405863d8c1d
          • 線性代數基本上是關于向量和矩陣的,這是你在機器學習中經常用到的東西。
        • 使用引用。(https://medium.com/@Jernfrost/working-with-and-emulating-references-in-julia-e02c1cae5826
          • 它看起來有點不太好理解,但是如果你想理解像Flux這樣的ML庫,那么理解Julia中的引用是很重要的。
        • Flux的實現。(https://medium.com/@Jernfrost/implementation-of-a-modern-machine-learning-library-3596badf3be
          • 如何實現Flux-ML庫的初學者指南。
        • 機器學習簡介。(https://medium.com/@Jernfrost/machine-learning-for-dummies-in-julia-6cd4d2e71a46) 機器學習概論。

        簡單多層感知機

        我們要編程的人工神經網絡被稱為簡單的多層感知機,這是神經網絡(ANN)的基礎,大多數教科書都會從它開始。
        我先展示整個程序,然后我們再更詳細地講解不同的部分。
        using?Flux,?Flux.Data.MNIST,?Statistics
        using?Flux:?onehotbatch,?onecold,?crossentropy,?throttle
        using?Base.Iterators:?repeated

        #?Load?training?data.?28x28?grayscale?images?of?digits
        imgs?=?MNIST.images()

        #?Reorder?the?layout?of?the?data?for?the?ANN
        imagestrip(image::Matrix{<:Gray})?=?Float32.(reshape(image,?:))
        X?=?hcat(imagestrip.(imgs)...)

        #?Target?output.?What?digit?each?image?represents.
        labels?=?MNIST.labels()
        Y?=?onehotbatch(labels,?0:9)

        #?Defining?the?model?(a?neural?network)
        m?=?Chain(
        ?Dense(28*28,?32,?relu),
        ?Dense(32,?10),
        ?softmax)
        ?
        loss(x,?y)?=?crossentropy(m(x),?y)
        dataset?=?repeated((X,?Y),?200)
        opt?=?ADAM()

        evalcb?=?()?->?@show(loss(X,?Y))

        #?Perform?training?on?data
        Flux.train!(loss,?params(m),?dataset,?opt,?cb?=?throttle(evalcb,?10))

        探索輸入數據

        數據預處理通常是數據科學中最大的工作之一。通常情況下,數據的組織或格式化方式與將其輸入算法所需的方式不同。
        我們首先將MNIST數據集加載為60000個28x28像素的灰度圖像:
        imgs?=?MNIST.images()
        現在,如果你這樣處理數據,你可能不知道輸出的數據是怎么樣子的,但使用Julia研究,我們只需檢查一下:
        julia>?size(imgs)
        (60000,)
        輸出說明了imgs是一個包含60000個元素的一維數組。但這些元素是什么?
        julia>?eltype(imgs)
        Array{Gray{FixedPointNumbers.Normed{UInt8,8}},2}
        你可能看不懂,但我可以簡單地告訴你這是什么:
        julia>?eltype(imgs)?<:?Matrix{T}?where?T?<:?Gray
        true
        這告訴我們imgs中的每個元素都是某種值矩陣,這些值屬于某種類型T,它是Gray類型的子類型。什么是Gray類型?
        我們可以在Julia在線文檔中查找:
        help?>?Gray
        ??Gray?is?a?grayscale?object.?You?can?extract?its?value?with?gray(c).
        如果我們想知道這些灰度值矩陣的維數,則可以:
        julia>?size(imgs[1])
        (28,?28)

        julia>?size(imgs[2])
        (28,?28)
        這告訴我們它們的尺寸為28x28像素。我們可以通過簡單地繪制其中的一些圖來進一步驗證這一點。Julia的Plots庫使你可以繪制函數和圖像。
        julia>?using?Plots
        julia>?plot(imgs[2])
        得出了下面的圖像,顯然看起來像一個數字:

        但是,你可能會發(fā)現了解更多的數據看起來是更有用。我們可以很容易地一起繪制幾個圖像:
        imgplots?=?plot.(imgs[1:9])
        plot(imgplots...)
        現在我們知道了數據是什么樣的了。

        準備輸入數據

        然而,我們不能像這樣將數據輸入到我們的神經網絡(ANN),因為每個神經網絡輸入必須是列向量,而不是矩陣。
        這是因為神經網絡期望一個矩陣作為輸入,矩陣中的每一列都是輸入。ANN所看到的三乘十矩陣對應于十個不同的輸入,其中每個輸入包含三個不同的值或者更具體地說是三個不同的特征,因此,我們將28x28灰度圖像轉換為28x28=784的長像素帶。
        其次,我們的神經網絡并不知道什么是灰度值,它是對浮點數據進行操作的,所以我們必須同時轉換數據的維度和元素類型。
        數組中的列和行數稱為其形狀。很多人提到了張量,雖然它并不完全精確,但它是一個涵蓋了標量、向量、矩陣、立方體或任何等級的數組(基本上是數組的所有維度)的概念。
        在Julia中,我們可以使用reshape函數來改變數組的形狀。下面是一些你如何使用它的例子。
        這將創(chuàng)建一個包含四個元素的列向量A
        julia>?A?=?collect(1:4)
        4-element?Array{Int64,1}:
        ?1
        ?2
        ?3
        ?4
        通過reshape我們把它變成一個二乘二的矩陣B:
        julia>?B?=?reshape(A,?(2,?2))
        2×2?Array{Int64,2}:
        ?1??3
        ?2??4
        矩陣可以再次轉換為列向量:
        julia>?reshape(B,?4)
        4-element?Array{Int64,1}:
        ?1
        ?2
        ?3
        ?4
        找出一個列向量到底有多少個元素是不切實際的,你可以讓Julia只通過寫來計算合適的長度。
        julia>?reshape(B,?:)
        4-element?Array{Int64,1}:
        ?1
        ?2
        ?3
        ?4
        有了這些信息,應該更容易看到imagestrip函數的實際功能了,它將28x28的灰度矩陣轉換為784個32位浮點值的列向量。
        imagestrip(image::Matrix{<:Gray})?=?Float32.(reshape(image,?:))
        .符號用于將函數應用于數組的每個元素,因此Float32.(xs)map(Float32, xs)是相同的。
        接下來,我們將imagestrip函數應用于6萬張灰度圖像中的每一張,生成784x6000個輸入矩陣X。
        X?=?hcat(imagestrip.(imgs)...)
        這是如何運作的?可以想象為imagestrip.(imgs)將圖像轉換為單個輸入值的數組,例如[X?, X?, X?, ..., X?],其中n = 60,000,每個X?都是784個浮點值。
        使用splat運算符...,我們將其轉換為所有這些列向量的水平連接,以產生模型輸入。
        X?=?hcat(X?,?X?,?X?,?...,?X?)
        如果要驗證尺寸,則可以運行size(X)。接下來,我們加載標簽。
        labels?=?MNIST.labels()
        標簽是我們稱之為監(jiān)督學習中觀察的"答案"部分。在我們的任務中,標簽是從0到9的數字。手繪數字的每一個圖像都應歸類為十個不同的數字之一,例如,如果這是一個包含不同花卉品種的花瓣長度和花瓣寬度的虹膜數據集,那么該品種的名稱就是標簽。
        X?代表我們所有的特征向量,用機器學習的術語來說,每個像素的灰度值都是一個特征。
        你可以將標簽與我們繪制的圖像進行比較。
        imgplots?=?plot.(imgs[1:9])
        plot(imgplots...)
        labels[1:9]

        獨熱編碼

        每個圖像一個標簽,則有60000個標簽,然而神經網絡不能直接輸出標簽。例如,如果你正試圖對貓和狗的圖像進行分類,那么一個網絡不能輸出字符串“dog”或“cat”,因為它是使用浮點值的。
        如果標簽是一個不一定有用的數字,例如如果輸出是一系列郵政編碼,那么將3000的郵政編碼視為1500的郵政編碼的兩倍是沒有意義的,同樣,當使用神經網絡從圖像中預測數字時,4的大小是2的兩倍并不重要,數字也可能是字母,因此它們的值不重要。
        我們在機器學習中處理這個問題的方法是使用所謂的獨熱編碼,這意味著,如果我們有標簽A、B和C,并且我們想用獨熱編碼來表示它們,那么A是[1、0、0],B是[0、1、0],C是[0、0、1]。
        這看起來很浪費空間,但在Julia one hot數組內部,它只跟蹤元素的索引,并不保存所有的零。
        下面是一些正在使用的編碼示例:
        julia>?Flux.onehot('B',?['A',?'B',?'C'])
        3-element?Flux.OneHotVector:
        0
        1
        0

        julia>?Flux.onehot("foo",?["foo",?"bar",?"baz"])
        3-element?Flux.OneHotVector:
        1
        0
        0
        但是,我們不會使用onehot函數,因為我們正在創(chuàng)建一批獨熱編碼標簽,我們將把60000張圖片作為一個批次來處理。
        機器學習的批次指的是在我們模型(神經網絡)的權值或參數更新之前必須完成的最小樣本數量。
        Y?=?onehotbatch(labels,?0:9)
        這將創(chuàng)建目標輸出。在理想情況下,模型(X)==Y,但在現實中,即使經過模型的訓練,也會有一些偏差。
        我們已經討論完數據準備,現在讓我們用人工神經網絡來構造我們的模型。

        構造神經網絡模型

        模型是真實世界的簡化表示,就像我們可以建立簡化的物理模型一樣,我們也可以用數學或代碼來創(chuàng)建物理世界的模型,現實中存在許多這樣的數學模型。
        例如,統(tǒng)計模型可以使用統(tǒng)計數據來模擬人們一天中是如何到達商店的。一般來說,人們會以一種遵循特定概率分布的方式到達。
        在我們的例子中,我們試圖用神經網絡來模擬現實世界中的一些東西,當然,這只是對現實世界的一種近似。
        當我們建立一個神經網絡時,我們有很多可以玩的東西。網絡是由多個層連接而成的,每一層通常都有一個激活函數。
        建立一個神經網絡的挑戰(zhàn)是選擇合適的層和激活函數,并決定每層應該有多少個節(jié)點。
        我們的模型非常簡單,定義如下:
        m?=?Chain(
        ??Dense(28^2,?32,?relu),
        ??Dense(32,?10),
        ??softmax)
        這是一個三層的神經網絡。Chain用于將各個層連接在一起。第一層Dense(28^2, 32, relu)有784(28x28)個輸入節(jié)點,對應于每個圖像中的像素數。
        它使用校正線性單元(ReLU)函數作為激活函數。在經典的神經網絡文獻中,通常會介紹sigmoidtanh。relu等激活函數,這些激活函數在大多數情況下都工作得很好,包括圖像的分類。
        下一層是我們的隱藏層,它接受32個輸入,因為前一層有32個輸出,隱藏節(jié)點的數量沒有明確的對錯選擇。
        但輸出的數量根據不同任務是不一樣的,因為我們希望每個數字有一個輸出,這也就是“獨熱編碼”發(fā)揮作用的地方。

        Softmax函數

        最后一層,是softmax函數,它以前一層的輸出的矩陣作為輸入,并沿著每一列進行歸一化。
        標準化將60000列中的每一列轉換為概率分布。那到底是什么意思?
        概率是0到1之間的值,0表示事件永遠不會發(fā)生,1是肯定會發(fā)生。
        與min-max歸一化一樣,softmax將所有輸入歸一化為0到1之間的值,但是與min max不同的是它會確保所有值的和為一。這需要一些例子來說明。
        假設我創(chuàng)建了10個從1到10的隨機值,我們可以放任意范圍和任意數量的值。
        julia>?ys?=?rand(1:10,?10)
        10-element?Array{Int64,1}:
        ??9
        ??6
        ?10
        ??5
        ?10
        ??2
        ??6
        ??6
        ??7
        ??9
        現在讓我們使用不同的歸一化函數歸一化這個數組,我們將使用來自LinearAlgebra模塊的normalize,因為它與Julia捆綁在一起。
        但首先使用softmax
        julia>?softmax(ys)
        10-element?Array{Float64,1}:
        ?0.12919082661651196???
        ?0.006432032517257137??
        ?0.3511770763952676????
        ?0.002366212528045101??
        ?0.3511770763952676????
        ?0.00011780678490667763
        ?0.006432032517257137??
        ?0.006432032517257137??
        ?0.017484077111717768??
        ?0.12919082661651196
        如你所見,所有值都在0到1之間?,F在看一下如果我們把它們加起來會發(fā)生什么:
        julia>?sum(softmax(ys))
        0.9999999999999999
        它們基本上變成了1?,F在將其與normalize的功能進行對比:
        julia>?using?LinearAlgebra

        julia>?normalize(ys)
        10-element?Array{Float64,1}:
        0.38446094597254243
        0.25630729731502827
        0.4271788288583805?
        0.21358941442919024
        0.4271788288583805?
        0.0854357657716761?
        0.25630729731502827
        0.25630729731502827
        0.2990251802008663?
        0.38446094597254243

        julia>?sum(normalize(ys))
        2.9902518020086633

        julia>?norm(normalize(ys))
        1.0

        julia>?norm(softmax(ys))
        0.52959100847191
        如果對用normalize歸一化的值求和,它們只會得到一些隨機值,然而如果我們把結果反饋給norm,我們得到的結果正好是1.0。
        不同之處在于,normalize將向量中的值進行了歸一化,以便它們可以表示單位向量,即長度正好為一的向量。norm給出向量的大小。
        相比之下,softmax不會將這些值視為向量,而是將其視為概率分布,每個元素表示輸入圖像為該數字的概率。
        假設我們有A,B和C的圖像作為輸入,如果你從softmax得到一個輸出值是[0.1,0.7,0.2],那么輸入圖像有10%的可能性是A的圖形,有70%的可能性是B的圖形,最后有20%的可能性是C的圖形。
        這就是為什么我們希望softmax作為最后一層的原因。用神經網絡不能絕對確定輸入圖像是什么,但是我們可以給出一個概率分布,它表示更有可能是哪個數字。

        定義損失函數

        當訓練我們的神經網絡(模型)給出準確的預測時,我們需要定義人工神經網絡(ANN)的評估指標。
        為此,我們使用所謂的損失函數。損失函數有很多名字,20年前當我被教授神經網絡時,我們曾稱之為誤差函數,也有人稱之為成本函數。
        然而,歸根結底,這是一種表達我們的預測與現實相比有多正確的方式。
        loss(x,?y)?=?crossentropy(m(x),?y)
        訓練神經網絡實際上是最小化這個函數的輸出,所以這是一個優(yōu)化問題。訓練是一個反復調整模型中參數(權重)的過程,直到損失函數的輸出變低,或者換句話說,直到我們的預測誤差變低。
        均方誤差函數(MSE)是計算預測錯誤程度的經典方法,這就意味著取差的平方,然而,MSE更適合于線性回歸(將一條或多條直線擬合到某些觀測值)。
        在這種情況下,我們改用交叉熵函數。當你的最后一層是softmax,進行分類而不是線性回歸時,這是我比較推薦的選擇。

        指定Epoch

        在機器學習術語中,Epoch是訓練算法進行一次完整的迭代,換句話說:一個Epoch處理一個批次并更新權重
        因此,如果我們使用10個Epoch來進行訓練,那么模型的參數/權重將更新/調整10次。
        為了得到200個Epoch,我們使用repeat重復我們的批處理200次。它實際上不會重復我們的數據200次,它只是用迭代器創(chuàng)建了這樣的錯覺。
        dataset?=?repeated((X,?Y),?200)
        在數據集中,我們得到的數組如下:
        dataset?=?[(X1,?Y1),?(X2,?Y2),?...,?(X200,?Y200)]

        優(yōu)化器

        最常見和最著名的訓練神經網絡策略是梯度下降算法,這是由Julia中的Descent類型提供的。
        然而,在我們的例子中,當我們處理大量帶有相當數量噪聲的數據時,建議改用ADAM優(yōu)化器,這就是所謂的隨機優(yōu)化。
        opt?=?ADAM()

        進行訓練

        我們終于可以進行訓練了,但我們希望在訓練進行的過程中得到一些反饋。我們定義了一個回調函數,在每次迭代(epoch)時,它將輸出loss函數的值,從而顯示錯誤。我們希望每次迭代時都能看到這個錯誤。
        evalcb?=?()?->?@show(loss(X,?Y))
        觀察錯誤發(fā)展的一個有用的地方是,你可以看到是否有振蕩。人工神經網絡過快地朝著最低值過渡,會導致它朝相反的方向移動,如果速度太快,則會向相反的方向超調,振蕩會變得更加劇烈,直到誤差變?yōu)闊o窮大。
        這是一個切換優(yōu)化算法或降低學習率的提示。
        不管怎樣,這就是你訓練的方式。注意,回調是可選的:
        Flux.train!(loss,?params(m),?dataset,?opt,?cb?=?throttle(evalcb,?10))

        評價模型預測精度

        經過訓練后,我們可以測試模型在預測方面的表現。
        我們定義了這樣一個函數:
        accuracy(x,?y)?=?mean(onecold((m(x)))?.==?onecold(y))
        然后我們用輸入數據和標簽作為輸入參數來調用它:
        @show?accuracy(X,?Y)
        至于什么是onecold?在某種程度上,它與onehot實現的效果是相反的。
        我們的輸出m(X)都是概率分布,而我們的目標Y都是獨熱向量。
        它們不能直接比較,所以我們需要使用onecold來做一個轉換。給定概率分布,它選擇最可能的候選:
        julia>?onecold([0.1,?0.7,?0.2])
        2

        julia>?onecold([0.9,?0.05,?0.05])
        1
        因此,使用onecold(m(X))我們可以得到預測的標簽,這可以與實際的標簽onecold(y)進行比較。

        用測試數據驗證模型

        到目前為止,我們只根據我們使用的訓練數據來驗證了我們的模型,然而,如果該模型不適用于新的數據,它將是完全無用的。
        因此,在訓練網絡時,我們通常將數據分為訓練數據和測試數據。測試數據不是訓練的一部分,只有在訓練完成后才能進行測試。
        tX?=?hcat(float.(reshape.(MNIST.images(:test),?:))...)
        tY?=?onehotbatch(MNIST.labels(:test),?0:9)

        @show?accuracy(tX,?tY)

        最后

        我希望這能幫助你理解建立神經網絡的過程。
        太多的教程傾向于跳過向初學者解釋的內容,從而所有的新概念都會很快變得令人困惑。我希望這為初學者在進一步探索機器學習之前提供了一個起點,特別是基于Julia的機器學習,因為我認為Julia有著光明的未來。
        參考鏈接:https://medium.com/better-programming/handwriting-recognition-using-an-artificial-neural-network-78060d2a7963

        ☆ END ☆
        瀏覽 32
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 5858成人免费A片 | 天天看人体| 夜噜噜久久国产欧美日韩精品 | 大香蕉美女频道在线观看 | 双乳被穿环调教成奴 |