前段時間由於店鋪不能開門,我花了一些空餘時間看了不少機器學習相關的資料,我發現目前的機器學習入門大多要不門檻比較高,要不過於着重使用而忽視基礎原理,因此我決定開一個新的系列針對程序員講講機器學習。這個系列會從機器學習的基礎原理開始一直講到如何應用,看懂這個系列須要必定的編程知識(主要會使用 python 語言),但不須要過多的數學知識,而且對於涉及到的數學知識會做出簡單的介紹。由於我水平有限(不是專業的機器學習工程師),這個系列不會講的很是深刻,看完可能也就只能作一個調參狗,各路大佬以爲哪些部分講錯的能夠在評論中指出。python
若是你沒有學過 python,但學過其餘語言 (例如 Java 或 C#),推薦你看 Learn Python in Y Minutes
,大約半天時間就能掌握基礎語法(快的可能只需一個小時😂)。git
在講解具體的例子與模型以前,咱們先來了解一下什麼是機器學習。在業務中咱們有不少須要解決的問題,例如用戶提交訂單時如何根據商品列表計算訂單金額,用戶搜索商品時如何根據商品關鍵字得出商品搜索結果,用戶查看商品一覽時如何根據用戶已買商品計算商品推薦列表,這些問題均可以分爲 輸入
,操做
,輸出
,以下圖所示:程序員
其中操做部分咱們一般會直接編寫程序代碼實現,程序代碼會查詢數據庫,使用某種算法處理數據等,這些工做可能很枯燥,一些程序員受不了了就會自稱碼農,由於日復一日編寫這些邏輯就像種田同樣艱苦和缺少新意。你有沒有想過若是有一套系統,能夠只給出一些輸入和輸出的例子就能自動實現操做中的邏輯?若是有這麼一套系統,在處理不少問題的時候就能夠不須要考慮使用什麼邏輯從輸入轉換到輸出,咱們只需提供一些例子這套系統就能夠自動幫咱們實現。github
好消息是這樣的系統是存在的,咱們給出一些輸入與輸出的例子,讓機器自動摸索出它們之間的規律而且創建一套轉換它們的邏輯,就是所謂的機器學習。目前機器學習能夠作到從圖片識別出物體類別,從圖片識別出文字,從文本識別出大概含義,也能夠作到上圖中的從已買商品列表計算出推薦商品列表,這些操做都不須要編寫具體邏輯,只須要準備必定的例子讓機器本身學習便可,若是成功摸索出規律,機器在遇到例子中沒有的輸入時也能夠正確的計算出輸出結果,以下圖所示:算法
惋惜的是機器學習不是萬能的,咱們不能期望機器能夠學習到全部規律從而實現全部操做,機器學習的界限主要有:數據庫
到這裏咱們應該對機器學習是什麼有了一個大概的印象,如何根據輸入與輸出摸索出規律就是機器學習最主要的命題,接下來咱們會更詳細分析機器學習的流程與步驟。須要注意的是,不是全部場景均可以明確的給出輸入與輸出的例子,能夠明確給出例子的學習稱爲有監督學習 (supervised learning),而只給出輸入不給出輸出例子的學習稱爲無監督學習 (unsupervised learning),無監督學習一般用於實現數據分類,雖然不給出輸出可是會按必定的規律控制學習的過程,由於無監督學習應用範圍不廣,這個系列講的基本上都是有監督學習。編程
咱們先來了解一下機器學習的流程:數組
而實現機器學習須要如下的步驟:網絡
在開始機器學習以前咱們須要先收集輸入與輸出的例子,收集到的例子又稱數據集 (Dataset),收集工做通常是個苦力活,例如學習從圖片判斷物體類別須要收集一堆圖片並手動對它們進行分類,學習從圖片識別文字須要收集一堆圖片並手動記錄圖片對應的文本,這樣的工做一般稱爲打標籤 (Labeling),標籤 (Label) 就至關於這個數據對應的輸出結果。有些時候咱們也能夠偷懶,例如實現驗證碼識別的時候咱們能夠反過來根據文本生成圖片,而後把圖片看成輸入文本看成輸出,再例如實現商品推薦的時候咱們能夠把用戶購買過的商品分割成兩部分,一部分做爲已購買商品 (輸入),另外一部分做爲推薦商品 (輸出)。注意輸入與輸出能夠有多個,例如視頻網站能夠根據用戶的年齡,性別,所在地 (3 個輸入) 來判斷用戶喜歡看的視頻類型 (1 個輸出),再例如自動駕駛系統能夠根據視頻輸入,雷達輸入與地圖路線 (3 個輸入) 計算汽車速度與方向盤角度 (2 個輸出),後面會介紹如何處理多個輸入與輸出,包括數量可變的輸入。app
若是你只是想試試手而不是解決實際的業務問題,能夠直接用別人收集好的數據集,如下是包含了各類公開數據集連接的 Github 倉庫:
https://github.com/awesomedata/awesome-public-datasets
用於讓機器學習與實現操做的就是模型 (Model),模型能夠分爲兩部分,第一部分是計算方法,這部分須要咱們來決定而且不會在學習過程當中改變;第二部分是參數,這部分會隨着學習不斷調整,最終實現咱們想要的操做。模型的計算方法須要根據業務(輸入與輸出的類型)來決定,例如分類可使用多層線性模型,圖像識別可使用 CNN 模型,趨勢預測可使用 RNN 模型,文本翻譯可使用 Transformer 模型,對象識別可使用 R-CNN 模型等 (這些模型會在後續的章節詳細介紹),一般咱們能夠直接用別人設計好的模型再加上一些細微調整(只會作這種工做的也叫調參狗😊,咱們的第一個小目標),而一些複雜的業務須要本身設計模型,這是真正難的地方。你可能會想是否有一種模型能夠適用於全部類型的業務,遺憾的是目前並無,若是有那就是真正的人工智能了。
由於篇幅限制,現實使用的模型會在後面的文章中介紹,請參考本文末尾的預告。
在機器學習中,模型只會接受和返回數值 (一般使用多維數組,即矩陣),因此咱們還須要決定輸入輸出與數值之間如何轉換,例如輸入是圖片時,咱們能夠把每一個像素的紅綠藍值與圖片大小一塊兒組成一個三維數組(紅綠藍 * 圖片寬度 * 圖片高度),再例如輸入是數據庫中的商品時,咱們能夠先根據總商品大小建立一個一維數組,而後用數組 1, 0, 0, ...
表明第一個商品,數組 0, 1, 0, ...
表明第二個商品,數組 0, 0, 1, ...
表明第三個商品,把數值轉換到輸出也同樣,將對應關係反過來就好了。注意轉換方式也是一個比較重要的部分,使用正確的轉換方式可讓機器學習事半功倍,而使用錯誤的轉換方式可能致使學習緩慢或學習失敗。
爲了提高學習速度,咱們一般會一次性的給模型傳入多組輸入並讓模型返回多組輸出,傳入的多組輸入也叫批次 (Batch),例如準備了 10000 組輸入與輸出,每次給模型傳入 50 組,那麼批次大小就是 50,須要分 2000 個批次傳入。分批次會讓輸入與輸出的數組維度加一,例如一次性傳 50 張寬 30 x 高 20 的圖片時,須要把這些圖片轉換爲一個 50 x 3 x 30 x 20 的四維數組,再例如傳 50 個商品時,須要把這些商品轉換爲一個 50 x 商品數量的二維數組。你可能會有疑問爲何不能一次性把全部輸入傳給模型,若是輸入輸出數量過大(有的數據集會有上百萬組數據),那麼計算機不會有足夠的內存處理它們;另外一個緣由是分批次傳入能夠防止過擬合 (Overfitting),但本篇不會詳細介紹這點。慣例上,咱們一般會選擇 32 ~ 100 爲批次大小。
此外,爲了提高學習效果咱們還能夠選擇把數值正規化 (Normalization),例如一個輸入數值的取值範圍在 0 ~ 10 的時候,咱們能夠把數值所有除以 10,用 0 表明最小的值,用 1 表明最大的值,這個手法能夠改善模型的學習速度與提高最終的效果。由於理解須要必定的數學知識,本篇不會詳細介紹爲何。
接下來咱們就能夠開始學習了,首先咱們會給模型的參數 (非固定部分) 隨機賦值,而後給模型傳入預先準備好的輸入,而後模型返回預測的輸出,第一次由於參數是隨機的,返回的預測輸出與正確輸出可能會差很遠,例如傳一張狗的圖片給模型,模型可能會告訴你這是豬。接下來你須要糾正模型,把預測輸出的數值與正確輸出的數值經過某種方法獲得它們的相差值 (也叫損失 - Loss),而後根據損失來調整模型的參數 (修改參數使得損失接近 0),讓下一次模型的預測輸出的數值更接近正確輸出的數值。若是把事先準備的全部輸入 (批次) 都傳給了模型,而且根據模型的預測輸出與正確輸出調整了模型的參數,那麼就能夠說通過了一輪訓練 (1 Epoch),一般咱們須要通過好幾輪訓練才能達到理想的效果。
評價模型是否達到理想的效果一般會使用正確率 (Accuracy, 不少文章會縮寫成 Acc),例如傳入 100 個輸入給模型,模型返回的 100 個預測輸出中有 99 個與正確輸出是一致的,那麼正確率就是 99 %。若是模型足夠強大,咱們可讓模型針對參與訓練的輸入達到 100 % 的正確率,但這並不能說明模型訓練成功,咱們還須要使用沒有參與訓練的輸入與輸出來評價模型是否成功摸索出規律。若是模型能力不足,或者用了與業務不匹配的模型,那麼模型會給出很低的正確率,而且通過再多訓練都不會改善,這個時候咱們就須要換一個模型了。模型經過訓練達到很高的正確率又稱收斂 (Converge),咱們首先須要肯定模型能收斂,再肯定模型是否能成功摸索出規律。
若是模型針對參與訓練的輸入達到了很高的正確率,那麼就有兩種狀況,第一種狀況是模型成功的摸索出規律了,第二種狀況是模型只是把全部參與訓練的輸入與輸出記住。第二種狀況很是糟糕,就像咱們把試卷的全部問題和答案記住了,可是沒有理解爲何,遇到另外一張沒看過的試卷時就會得出很低的分數,這樣的狀況又稱過擬合 (Overfitting)。
爲了判斷是否發生過擬合,咱們一般會把事先準備好的輸入與輸出數據集打亂並分爲三個集合,分別是訓練集 (Training Set),驗證集 (Validating Set) 與測試集 (Testing Set),舉例來講咱們能夠把 70 % 的數據劃給訓練集,15 % 的數據劃給驗證集,剩餘 15 % 的數據劃給測試集。訓練集中的輸入與輸出用於傳給模型而且調整模型的參數;驗證集中的輸入與輸出不會參與訓練,用於在通過每一輪訓練後判斷模型在遇到未知的輸入時能夠得出的正確率,若是模型針對訓練集能夠得出 99 % 的正確率,但針對驗證集只能得出 50 % 的正確率,那麼就能夠判斷髮生了過擬合;測試集用於在最終訓練完成後判斷模型是否過分偏向於訓練集與驗證集中的數據,若是針對測試集均可以得出比較高的正確率,那麼就能夠說這個模型訓練成功了。
由於實際的業務場景中收集到的輸入與輸出會夾雜一些不徹底正確的數據,若是不停的去訓練模型,模型爲了迎合這些不徹底正確的數據會去破壞已經摸索出的規律,致使最終必定發生過擬合。爲了防止這種狀況咱們可使用提前中止 (Early Stopping) 的手法,在每一輪訓練後都計算模型針對訓練集與驗證集的正確率,而後在驗證集正確率最高的時候中止訓練,例如:
咱們能夠看出應該在第三輪訓練後中止訓練,在實際操做中咱們會記錄每一輪訓練的正確率與驗證集正確率最高時模型的狀態,若是驗證集正確率通過必定訓練次數都沒有超過以前的最高值,那麼就使用以前記錄的模型狀態做爲結果並中止訓練。在中止訓練後,咱們須要判斷驗證集正確率的最高值是否達到咱們滿意的水平,若是沒有達到則表明模型不適合或者沒有能力應付當前的業務,咱們須要修改模型並從新開始訓練。
若是驗證集正確率的最高值達到咱們滿意的水平,那麼就能夠作最後一步了,即用模型判斷測試集的正確率,由於測試集徹底沒有參與過以前的步驟,若是測試集的正確率也達到滿意的水平,那麼就能夠說這個模型訓練成功了。但若是測試集的正確率沒有達到滿意的水平,則表明模型對訓練集與驗證集有偏向,由於咱們在驗證集正確率不滿意的時候會修改模型,修改後的模型會更偏向於驗證集的數據,但這個偏向可能會不適合驗證集之外的數據。訓練集,驗證集與測試集的意義能夠總結以下:
一個常見的人爲錯誤是劃分這三個集合的時候沒有對數據進行打亂,例若有貓狗豬的圖片各 1000 張,若是劃分集合的時候這些圖片是排序好的,那麼訓練集會只有貓和狗的圖片,測試集會只有豬的圖片,這樣就很難確保訓練出來的模型能夠正確識別豬了。
從劃分數據集到訓練成功的流程能夠總結以下:
注1: 讓模型成功摸索出規律 (針對未知輸入得出正確輸出) 的工做通常稱爲泛化 (Generalization)。
注2: 防止過擬合還有另一些手法,會在接下來的文章中介紹。
對初學者來講一個很常見的問題是,機器學習,深度學習與人工智能有什麼區別?若是機器學習的模型很是複雜(通過多層次的計算),那麼就能夠說是深度學習,若是模型的效果很是好,在某個領域達到或者超過人類的水平,那就能夠說是人工智能。但實際上它們都是 PPT 詞彙,給👼投資人👶看的時候寫人工智能比寫機器學習要搶眼多了,就算不知足人工智能的水平不少公司都會宣傳爲人工智能。這個系列是給在 IT 食物鏈最底層的程序員看的,因此仍是謙虛點叫機器學習吧。
爲了更好的理解前述的步驟,我準備了一個最簡單的例子:
假設有如下的輸入與輸出,怎樣才能自動找出從輸入轉換到輸出的方法呢?
你極可能一眼就已經看出了它們的規律,別急,讓咱們使用機器學習來解決這個問題。
咱們能夠先假設輸入乘以某個值再加上某個值等於輸出,而後:
用數學公式能夠表達以下:
這個公式就是模型中的計算方法部分,而 weight 和 bias 則是這個模型的參數,咱們把部分輸入與輸出代入 x 和 y:
接下來要作的就是找出能夠知足這些等式的 weight 和 bias。
咱們首先隨便給 weight 和 bias 分配值,例如給 weight 分配 1,給 bias 分配 0,而後試試計算結果:
這個計算結果 2 就是預測輸出,而預測輸出和正確輸出之間的差距就是損失。
若是用 predicted (縮寫 p) 表明預測輸出,用 loss (縮寫 l) 表明損失,能夠得出如下公式:
若是 loss 等於 0,那麼預測輸出 predicted 就會等於正確輸出 y,咱們的目標是儘可能的讓 loss 接近 0。
想一想若是 weight 增長 1 時 loss 會增長多少,而 bias 增長 1 時 loss 會增長多少:
能夠看出 weight 和 bias 與 loss 是正相關的,而且 weight 和 bias 對 loss 的貢獻是 x 比 1,在前面的例子中,loss 等於 predicted - y 等於 2 - 5 等於 -3,咱們須要增長 weight 和 bias 的值來讓 loss 更接近 0。增長 weight 和 bias 時的比例應該與貢獻比例一致,試着給 weight 加上 x,bias 加上 1,調整之後 weight 等於 3,bias 等於 1,計算結果以下:
這下 loss 等於 7 - 5 等於 2 了,咱們須要減小 weight 和 bias 來讓 loss 更接近 0,若是和以前同樣 weight 減去 x,bias 減去 1,那麼 weight 和 bias 就會變回以前的值,無論調整多少次都沒法減小 loss,噢😢。解決這個問題能夠控制每次 weight 和 bias 的修改量,例如每次只修改 0.01 倍 (這個倍數又稱學習比率 - Learning Rate - 簡稱 LR),總結規則以下:
若是 loss 小於 0:
若是 loss 大於 0:
模擬一下修改的過程:
第一輪: x = 2, y = 5, weight = 1, bias = 0 predicted = 2 * 1 + 0 = 2 loss = 2 - 5 = -3 weight += 2 * 0.1 bias += 0.1 第二輪: x = 2, y = 5, weight = 1.02, bias = 0.01 predicted = 2 * 1.02 + 0.01 = 2.05 loss = 2.05 - 5 = -2.95 weight += 2 * 0.1 bias += 0.1 第三輪: x = 2, y = 5, weight = 1.04, bias = 0.02 predicted = 2 * 1.04 + 0.02 = 2.1 loss = 2.1 - 5 = -2.9 weight += 2 * 0.1 bias += 0.1
能夠看到 loss 愈來愈接近 0,繼續修改下去 weight 會等於 2.2,bias 會等於 0.6,知足 x 等於 2,y 等於 5 的狀況,但知足不了數據集中的其餘數據。咱們能夠編寫一個程序遍歷數據集中的數據來進行一樣的修改,來看看能不能找到知足數據集中全部數據的 weight 和 bias:
# 定義參數 weight = 1 bias = 0 # 定義學習比率 learning_rate = 0.01 # 準備訓練集,驗證集和測試集 traning_set = [(2, 5), (5, 11), (6, 13), (7, 15), (8, 17)] validating_set = [(12, 25), (1, 3)] testing_set = [(9, 19), (13, 27)] for epoch in range(1, 10000): print(f"epoch: {epoch}") # 根據訓練集訓練並修改參數 for x, y in traning_set: # 計算預測值 predicted = x * weight + bias # 計算損失 loss = predicted - y # 打印除錯信息 print(f"traning x: {x}, y: {y}, predicted: {predicted}, loss: {loss}, weight: {weight}, bias: {bias}") # 判斷須要如何修改 weight 和 bias 才能減小 loss if loss < 0: # 須要增長 weight 和 bias 來讓 loss 更大 weight += x * learning_rate bias += 1 * learning_rate else: # 須要減小 weight 和 bias 來讓 loss 更小 weight -= x * learning_rate bias -= 1 * learning_rate # 檢查驗證集 validating_accuracy = 0 for x, y in validating_set: predicted = x * weight + bias validating_accuracy += 1 - abs(y - predicted) / y print(f"validating x: {x}, y: {y}, predicted: {predicted}") validating_accuracy /= len(validating_set) # 若是驗證集正確率大於 99 %,則中止訓練 print(f"validating accuracy: {validating_accuracy}") if validating_accuracy > 0.99: break # 檢查測試集 testing_accuracy = 0 for x, y in testing_set: predicted = x * weight + bias testing_accuracy += 1 - abs(y - predicted) / y print(f"testing x: {x}, y: {y}, predicted: {predicted}") testing_accuracy /= len(testing_set) print(f"testing accuracy: {testing_accuracy}")
輸出結果以下:
epoch: 1 traning x: 2, y: 5, predicted: 2, loss: -3, weight: 1, bias: 0 traning x: 5, y: 11, predicted: 5.109999999999999, loss: -5.890000000000001, weight: 1.02, bias: 0.01 traning x: 6, y: 13, predicted: 6.4399999999999995, loss: -6.5600000000000005, weight: 1.07, bias: 0.02 traning x: 7, y: 15, predicted: 7.940000000000001, loss: -7.059999999999999, weight: 1.1300000000000001, bias: 0.03 traning x: 8, y: 17, predicted: 9.64, loss: -7.359999999999999, weight: 1.2000000000000002, bias: 0.04 validating x: 12, y: 25, predicted: 15.410000000000004 validating x: 1, y: 3, predicted: 1.3300000000000003 validating accuracy: 0.5298666666666668 epoch: 2 traning x: 2, y: 5, predicted: 2.6100000000000003, loss: -2.3899999999999997, weight: 1.2800000000000002, bias: 0.05 traning x: 5, y: 11, predicted: 6.560000000000001, loss: -4.439999999999999, weight: 1.3000000000000003, bias: 0.060000000000000005 traning x: 6, y: 13, predicted: 8.170000000000002, loss: -4.829999999999998, weight: 1.3500000000000003, bias: 0.07 traning x: 7, y: 15, predicted: 9.950000000000003, loss: -5.049999999999997, weight: 1.4100000000000004, bias: 0.08 traning x: 8, y: 17, predicted: 11.930000000000003, loss: -5.069999999999997, weight: 1.4800000000000004, bias: 0.09 validating x: 12, y: 25, predicted: 18.820000000000007 validating x: 1, y: 3, predicted: 1.6600000000000006 validating accuracy: 0.6530666666666669 epoch: 3 traning x: 2, y: 5, predicted: 3.220000000000001, loss: -1.779999999999999, weight: 1.5600000000000005, bias: 0.09999999999999999 traning x: 5, y: 11, predicted: 8.010000000000002, loss: -2.9899999999999984, weight: 1.5800000000000005, bias: 0.10999999999999999 traning x: 6, y: 13, predicted: 9.900000000000002, loss: -3.099999999999998, weight: 1.6300000000000006, bias: 0.11999999999999998 traning x: 7, y: 15, predicted: 11.960000000000004, loss: -3.0399999999999956, weight: 1.6900000000000006, bias: 0.12999999999999998 traning x: 8, y: 17, predicted: 14.220000000000006, loss: -2.779999999999994, weight: 1.7600000000000007, bias: 0.13999999999999999 validating x: 12, y: 25, predicted: 22.230000000000008 validating x: 1, y: 3, predicted: 1.9900000000000007 validating accuracy: 0.7762666666666669 省略途中的輸出 epoch: 90 traning x: 2, y: 5, predicted: 4.949999999999935, loss: -0.05000000000006466, weight: 1.9799999999999676, bias: 0.9900000000000007 traning x: 5, y: 11, predicted: 10.999999999999838, loss: -1.616484723854228e-13, weight: 1.9999999999999676, bias: 1.0000000000000007 traning x: 6, y: 13, predicted: 13.309999999999807, loss: 0.3099999999998069, weight: 2.0499999999999674, bias: 1.0100000000000007 traning x: 7, y: 15, predicted: 14.929999999999772, loss: -0.07000000000022766, weight: 1.9899999999999674, bias: 1.0000000000000007 traning x: 8, y: 17, predicted: 17.48999999999974, loss: 0.4899999999997391, weight: 2.059999999999967, bias: 1.0100000000000007 validating x: 12, y: 25, predicted: 24.759999999999607 validating x: 1, y: 3, predicted: 2.9799999999999676 validating accuracy: 0.9918666666666534 testing x: 9, y: 19, predicted: 18.819999999999705 testing x: 13, y: 27, predicted: 26.739999999999572 testing accuracy: 0.9904483430799063
最終 weight 等於 2.05,bias 等於 1.01,它針對沒有訓練過的檢查集和測試集能夠達到 99 % 的正確率 (預測輸出 99 % 接近正確輸出),若是 99 % 的正確率能夠接受,那麼就能夠說此次訓練成功了。
若是你想看 weight 和 bias 的變化,能夠記錄它們的值而且使用 matplotlib 來顯示圖表。
安裝 matplotlib 的命令:
pip3 install matplotlib
修改後的代碼:
# 定義參數 weight = 1 bias = 0 # 定義學習比率 learning_rate = 0.01 # 準備訓練集,驗證集和測試集 traning_set = [(2, 5), (5, 11), (6, 13), (7, 15), (8, 17)] validating_set = [(12, 25), (1, 3)] testing_set = [(9, 19), (13, 27)] # 記錄 weight 與 bias 的歷史值 weight_history = [weight] bias_history = [bias] for epoch in range(1, 10000): print(f"epoch: {epoch}") # 根據訓練集訓練並修改參數 for x, y in traning_set: # 計算預測值 predicted = x * weight + bias # 計算損失 loss = predicted - y # 打印除錯信息 print(f"traning x: {x}, y: {y}, predicted: {predicted}, loss: {loss}, weight: {weight}, bias: {bias}") # 判斷須要如何修改 weight 和 bias 才能減小 loss if loss < 0: # 須要增長 weight 和 bias 來讓 loss 更大 weight += x * learning_rate bias += 1 * learning_rate else: # 須要減小 weight 和 bias 來讓 loss 更小 weight -= x * learning_rate bias -= 1 * learning_rate weight_history.append(weight) bias_history.append(bias) # 檢查驗證集 validating_accuracy = 0 for x, y in validating_set: predicted = x * weight + bias validating_accuracy += 1 - abs(y - predicted) / y print(f"validating x: {x}, y: {y}, predicted: {predicted}") validating_accuracy /= len(validating_set) # 若是驗證集正確率大於 99 %,則中止訓練 print(f"validating accuracy: {validating_accuracy}") if validating_accuracy > 0.99: break # 檢查測試集 testing_accuracy = 0 for x, y in testing_set: predicted = x * weight + bias testing_accuracy += 1 - abs(y - predicted) / y print(f"testing x: {x}, y: {y}, predicted: {predicted}") testing_accuracy /= len(testing_set) print(f"testing accuracy: {testing_accuracy}") # 顯示 weight 與 bias 的變化 from matplotlib import pyplot pyplot.plot(weight_history, label="weight") pyplot.plot(bias_history, label="bias") pyplot.legend() pyplot.show()
輸出的圖表,能夠看到 weight 接近 2 之後一直上下浮動,而 bias 逐漸接近 1:
等等,你是否是以爲這個例子很蠢?這個例子的確很蠢,若是咱們用其餘方法 (例如聯立方程式) 能夠立刻計算出 weight 應該等於 2,bias 應該等於 1,這時預測輸出 100 % 等於正確輸出。但這個例子表明了機器學習最基礎的原理 - 計算各個參數對損失的貢獻比例而後修改參數讓損失接近 0,若是模型的計算方法很是複雜,將沒有方法馬上計算出可讓損失等於 0 的參數值,只能慢慢的調整參數去試。
好了,那爲何上面的例子不能調整 weight 到 2,bias 到 1 呢?主要有兩個緣由,第一是學習比率爲 0.01,若是出現 loss 很接近但小於 0,weight 和 bias 增長之後 loss 大於 0,而後減小 weight 和 bias 又讓 loss 變回原來的值,那麼接下來不管學習多少次 loss 都不會等於 0,而是在小於 0 的某個值和大於 0 的某個值之間搖擺;第二是咱們在正確率達到 99 % 的時候就中斷了訓練。你能夠試試減小學習比率和增長中斷訓練須要的正確率,試試 weight 和 bias 會不會更接近 2 和 1。
此外在這個例子中,由於全部數據都是完美的,沒有雜質在裏面,而且模型很是的簡單,因此不會出現過擬合 (Overfitting) 問題,也不須要使用提前中止 (Early Stopping) 的手法來防止過擬合。
不少機器學習的文章喜歡用拋物線和一個球來形容機器學習訓練的過程:
把球看做參數,拋物線看做 loss 的值,若是球在左半部分 loss 小於 0,若是球在右半部分 loss 大於 0,若是球落在最低點那麼 loss 等於 0,機器學習的過程就是調整這個球的位置。球所在的位置的傾斜 (Gradient) 決定了球的移動方向和每次的移動距離(移動速度),球在左半邊的時候會向右移,球在右半邊的時候會向左移,而越傾斜每次的移動距離就越長,若是每次的移動距離很長,球可能會一直左右搖擺而沒法落在最低點,這個時候咱們就須要使用學習比率 (Learning Rate) 來控制每次移動的距離,讓每次移動的距離等於 傾斜 * 學習比率
。
在前述的例子中,參數 weight 的傾斜是 x,而參數 bias 的傾斜是 1,這實際上就是它們的導函數 (Derivative Function):
若是你還記得高中學過的微積分,那麼馬上就能看明白,但我問過但不少程序員都說已經忘光了還給數學老師了😓,因此我在這裏再簡單解釋一下微分的概念,還記得的就當複習叭。
所謂微分就是求某個函數的導函數,而導函數就是求某一個點上值的變化與結果的變化的關聯 (傾斜)。之前面的例子爲例,weight 若是增長 1,那麼 loss 就會增長 x,weight 若是增長 2,那麼 loss 就會增長 2x,因此 weight 的導函數能夠用 x 來表示;而 bias 若是增長 1,那麼 loss 就會增長 1,bias 若是增長 2,那麼 loss 就會增長 2,因此 bias 的導函數能夠用 1 來表示。
求導函數的通用公式以下:
求 weight 和 bias 的導函數 (weight 和 bias 的變化與 loss 的變化的關聯) 的過程以下:
你可能會有疑問爲何要求 h 無限接近於 0,這是由於導函數求的是某個點上變化的關聯,而這個關聯可能會根據點的位置而不一樣,在上述例子中 weight 和 bias 無論在哪裏,它們和 loss 的關聯都是相同的,不會依賴於 weight 和 bias 的值。咱們能夠看一個根據位置不一樣關聯發生變化的例子,例如 x 的平方:
當 x 等於 3 時,x 的平方等於 9 當 x 等於 5 時,x 當平方等於 25 求 x 的變化與 x 的平方的變化的關聯 當 x 等於 3 + 1 時,x 的平方等於 16,與原值相差 7 當 x 等於 5 + 1 時,x 當平方等於 36,與原值相差 11 能夠看到當 x 增長 1 時,x 的平方增長多少不是固定的,會依賴於 x 的值
求 x 的平方的導函數的過程以下:
咱們能夠粗略檢查一下這個導函數是否正確 (如下的代碼運行在 python 的 REPL 中):
>>> ((3 + 1) ** 2 - 3 ** 2) / 1 7.0 >>> ((3 + 0.1) ** 2 - 3 ** 2) / 0.1 6.100000000000012 >>> ((3 + 0.01) ** 2 - 3 ** 2) / 0.01 6.009999999999849 >>> ((3 + 0.001) ** 2 - 3 ** 2) / 0.001 6.000999999999479 >>> ((5 + 1) ** 2 - 5 ** 2) / 1 11.0 >>> ((5 + 0.1) ** 2 - 5 ** 2) / 0.1 10.09999999999998 >>> ((5 + 0.01) ** 2 - 5 ** 2) / 0.01 10.009999999999764 >>> ((5 + 0.001) ** 2 - 5 ** 2) / 0.001 10.001000000002591
能夠看到變化的值越接近 0,變化值與結果的關聯越接近 2x。
如今咱們瞭解微分了,那積分是什麼呢?積分分爲不定積分和定積分,不定積分就是反過來從導函數求原始函數,定積分就是從導函數和參數的變化範圍求結果的變化範圍:
好了,複習就到此爲止,咱們來總結一下機器學習是怎麼利用微分來調整參數的:
(上面的公式有多個參數,嚴格上來講應該計算偏導函數,但實際上機器學習只會考慮各個參數對損失的貢獻比例和調整的方向,因此除非你想用數學證實訓練能夠收斂,不須要去計算偏導函數)
咱們再來回頭看看前面的例子,會發現調整參數的時候,調整量只會依賴輸入與學習比率,不會依賴損失的大小,若是咱們想在損失比較大的時候調整多一點,損失比較小的時候調整少一點,應該怎麼辦呢?
咱們能夠改變損失的計算方法,把預測輸出和正確輸出相差的值的平方做爲損失,這裏我引入一個新的臨時變量 diff (縮寫 d) 來表示預測輸出和正確輸出相差的值:
這個時候應該如何計算 weight 和 bias 的導函數呢?
咱們可使用連鎖律 (Chain Rule),簡單的來講就是若是 x 的變化影響了 y 的變化,y 的變化影響了 z 的變化,那麼 x 的變化 與 z 的變化之間的關係能夠用前面兩個變化的關係組合計算出來 (注意下圖中的公式用的是 Lagrange's notation,只是記法不同):
使用連鎖律計算 weight 和 bias 的導函數的過程以下 (若是你有興趣和時間能夠試試不用連鎖律計算,看看結果是否同樣):
能夠看到修改 loss 的計算方式後,weight 和 bias 對 loss 的貢獻比例是 2 * diff * x
比 2 * diff
,會依賴於預測輸出與正確輸出相差的值,如今咱們修改一下上面例子的代碼,看看是否仍然能夠訓練成功:
# 定義參數 weight = 1 bias = 0 # 定義學習比率 learning_rate = 0.01 # 準備訓練集,驗證集和測試集 traning_set = [(2, 5), (5, 11), (6, 13), (7, 15), (8, 17)] validating_set = [(12, 25), (1, 3)] testing_set = [(9, 19), (13, 27)] # 記錄 weight 與 bias 的歷史值 weight_history = [weight] bias_history = [bias] for epoch in range(1, 10000): print(f"epoch: {epoch}") # 根據訓練集訓練並修改參數 for x, y in traning_set: # 計算預測值 predicted = x * weight + bias # 計算損失 diff = predicted - y loss = diff ** 2 # 打印除錯信息 print(f"traning x: {x}, y: {y}, predicted: {predicted}, loss: {loss}, weight: {weight}, bias: {bias}") # 計算導函數值 derivative_weight = 2 * diff * x derivative_bias = 2 * diff # 修改 weight 和 bias 以減小 loss # diff 爲正時表明預測輸出 > 正確輸出,會減小 weight 和 bias # diff 爲負時表明預測輸出 < 正確輸出,會增長 weight 和 bias weight -= derivative_weight * learning_rate bias -= derivative_bias * learning_rate # 記錄 weight 和 bias 的歷史值 weight_history.append(weight) bias_history.append(bias) # 檢查驗證集 validating_accuracy = 0 for x, y in validating_set: predicted = x * weight + bias validating_accuracy += 1 - abs(y - predicted) / y print(f"validating x: {x}, y: {y}, predicted: {predicted}") validating_accuracy /= len(validating_set) # 若是驗證集正確率大於 99 %,則中止訓練 print(f"validating accuracy: {validating_accuracy}") if validating_accuracy > 0.99: break # 檢查測試集 testing_accuracy = 0 for x, y in testing_set: predicted = x * weight + bias testing_accuracy += 1 - abs(y - predicted) / y print(f"testing x: {x}, y: {y}, predicted: {predicted}") testing_accuracy /= len(testing_set) print(f"testing accuracy: {testing_accuracy}") # 顯示 weight 與 bias 的變化 from matplotlib import pyplot pyplot.plot(weight_history, label="weight") pyplot.plot(bias_history, label="bias") pyplot.legend() pyplot.show()
輸出以下:
epoch: 1 traning x: 2, y: 5, predicted: 2, loss: 9, weight: 1, bias: 0 traning x: 5, y: 11, predicted: 5.66, loss: 28.5156, weight: 1.12, bias: 0.06 traning x: 6, y: 13, predicted: 10.090800000000002, loss: 8.463444639999992, weight: 1.6540000000000001, bias: 0.1668 traning x: 7, y: 15, predicted: 14.246711999999999, loss: 0.567442810944002, weight: 2.003104, bias: 0.22498399999999996 traning x: 8, y: 17, predicted: 17.108564320000003, loss: 0.011786211577063013, weight: 2.10856432, bias: 0.24004976 validating x: 12, y: 25, predicted: 25.332206819199993 validating x: 1, y: 3, predicted: 2.3290725023999994 validating accuracy: 0.8815346140160001 epoch: 2 traning x: 2, y: 5, predicted: 4.420266531199999, loss: 0.3360908948468813, weight: 2.0911940287999995, bias: 0.23787847359999995 traning x: 5, y: 11, predicted: 10.821389980735997, loss: 0.03190153898148744, weight: 2.1143833675519996, bias: 0.24947314297599996 traning x: 6, y: 13, predicted: 13.046511560231679, loss: 0.0021633252351850635, weight: 2.1322443694784, bias: 0.25304534336128004 traning x: 7, y: 15, predicted: 15.138755987910837, loss: 0.019253224181112433, weight: 2.1266629822505987, bias: 0.25211511215664645 traning x: 8, y: 17, predicted: 17.10723714394308, loss: 0.011499805041069082, weight: 2.1072371439430815, bias: 0.2493399923984297 validating x: 12, y: 25, predicted: 25.32814566046583 validating x: 1, y: 3, predicted: 2.3372744504317566 validating accuracy: 0.8829828285293095 epoch: 3 traning x: 2, y: 5, predicted: 4.427353651343945, loss: 0.327923840629112, weight: 2.0900792009121885, bias: 0.24719524951956806 traning x: 5, y: 11, predicted: 10.823573450784844, loss: 0.03112632726796794, weight: 2.112985054858431, bias: 0.2586481764926892 traning x: 6, y: 13, predicted: 13.045942966156671, loss: 0.0021107561392730407, weight: 2.1306277097799464, bias: 0.2621767074769923 traning x: 7, y: 15, predicted: 15.13705972504188, loss: 0.01878536822855566, weight: 2.125114553841146, bias: 0.2612578481538589 traning x: 8, y: 17, predicted: 17.105926192335282, loss: 0.011220358222651178, weight: 2.1059261923352826, bias: 0.2585166536530213 validating x: 12, y: 25, predicted: 25.324134148545966 validating x: 1, y: 3, predicted: 2.3453761313679533 validating accuracy: 0.8844133389237396 省略途中的輸出 epoch: 202 traning x: 2, y: 5, predicted: 4.950471765167672, loss: 0.002453046045606255, weight: 2.0077909582882314, bias: 0.9348898485912089 traning x: 5, y: 11, predicted: 10.984740851695477, loss: 0.00023284160697942092, weight: 2.0097720876815246, bias: 0.9358804132878555 traning x: 6, y: 13, predicted: 13.003973611325808, loss: 1.578958696858945e-05, weight: 2.011298002511977, bias: 0.936185596253946 traning x: 7, y: 15, predicted: 15.011854308097591, loss: 0.00014052462047262272, weight: 2.01082116915288, bias: 0.9361061240274299 traning x: 8, y: 17, predicted: 17.009161566019216, loss: 8.393429192445584e-05, weight: 2.0091615660192175, bias: 0.935869037865478 validating x: 12, y: 25, predicted: 25.02803439201881 validating x: 1, y: 3, predicted: 2.9433815220012365 validating accuracy: 0.9900028991598299 testing x: 9, y: 19, predicted: 19.00494724565038 testing x: 13, y: 27, predicted: 27.03573010747495 testing accuracy: 0.9992081406680464
weight 與 bias 的變化以下:
你可能會發現訓練速度比前面的例子慢不少,這是由於這個例子實在太簡單了,因此沒法顯示出讓參數調整量依賴損失的優點,在複雜的場景下它可讓訓練速度更快而且讓預測輸出更接近正確輸出。此外,還有另一些計算損失的方法,例如 Cross Entropy 等,它們將在後面的文章中提到。
最後補充一個知識點,經過輸入計算預測輸出的過程在機器學習中稱做 Forward,而經過損失調整參數的過程則稱做 Backward,若是參數通過多層計算,那麼能夠把調整多層參數的過程稱爲反向傳播 (Backpropagation),多層計算的模型將在後面的文章中提到。
看到這裏你可能會有點失望,由於這篇太基礎了,也沒有涉及到實用的例子😓。但這個系列的閱讀目標是程序員,主要是那些人到中年,每天只作增刪查改,而且開始掉頭髮的程序員們,他們不少都抱怨其餘高級的教程看不懂。因此這個系列會把容易理解放在首位,看完可能只能作個調參狗,但如主席所說的,無論黑狗白狗,能解決問題的就是好狗😂。但願這個系列可讓你踏入機器學習的大門,而且能夠利用機器學習解決業務上的問題。
接下來的文章內容預計以下: