JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

從像素到神經元:最小的學習機器

認識人工神經元——這個把像素數字網格轉成第一個決策火花的微小「加權求和再壓縮」單元。

視覺為何需要一台會學習的機器

在先前的單元裡,你已經知道數位影像並不神秘——它就是一個像素的網格,每個像素都是一個數字;而傳統電腦視覺則是用人手寫的規則來打造偵測器:在亮度跳變的地方找邊緣、用顏色門檻把紅色的球框出來、把一個樣板在影像上滑動去比對形狀。這些工具確實有用,你也該把它們留在工具箱裡。但它們都共享一個脆弱的前提:假設「你」這個程式設計師,能事先把物體長什麼樣子完整講清楚。然而一旦光線變暗、貓轉了個頭、背景變得雜亂,或是換了一個拍攝角度,那些精心調好的門檻與樣板就會悄悄地全面崩潰。

有一個很值得記住的比喻。想像你要用電話、而且只能用文字,去教一個陌生人辨認貓:「牠有尖尖的三角形耳朵、有鬍鬚、有毛、四條腿、一條尾巴……」。你永遠講不完。無毛貓沒有毛、摺耳貓沒有尖耳朵、睡著的貓看不到腿。你每加一條規則,就要再補十個例外,而例外還會生出例外。手寫「貓」的規則是一件沒完沒了的苦差事——這正是傳統視覺在真實世界的困難影像上撞到的那道牆。

整個單元都建立在一個不同的賭注上。與其自己寫規則,我們要打造一台內部有大量數字(我們稱之為參數)的機器,然後餵給它許多有標籤的範例——也就是我們已經標好「貓」或「不是貓」的圖片。這台機器會自行調整它的數字,直到它的猜測符合標籤為止。我們不再口述規則,而是改成口述目標;規則則被自動地發現出來。這種機器最微小的版本,就是單一的人工神經元,而一塊一塊地把這一個單元搭起來,正是這份指南的全部任務。

目標:一張影像進去,機器就輸出一個帶有信心度的標籤——把像素對應到「貓」的那條規則是被學出來的,不是被寫出來的。

一張照片進入一個標示「分類器」的方塊,出來時變成一個帶有機率分數的類別名稱。

一張影像其實只是一個數字張量

在神經元能對一張圖片做任何事之前,我們得先回想:對電腦來說,一張圖片到底「是」什麼。灰階影像是一個二維的亮度網格,每一格(每一個像素)裝著一個數字——慣例上從 0(黑)到 255(白),中間的值則是各種灰階。彩色影像不過就是把三個這樣的網格疊在一起:一個給紅、一個給綠、一個給藍。把每個像素上這三個強度混合起來,就能得到螢幕上任何一種顏色。一張影像,除了這些數字陣列之外,再也沒有別的東西了。

放得夠大,一張灰階影像其實就是一張由 0 到 255 的數字所組成的試算表。

一張被放大的灰階影像,顯示出網格中每個像素都是一個數值化的亮度值。

我們給這些數字盒子一個簡潔的名字:張量(tensor)就是「一個有形狀的數字盒子」。形狀告訴你這些數字是怎麼排列的。一個 28×28 的灰階數字圖(著名的 MNIST 格式)是一個形狀為 (28, 28) 的張量:28 列乘 28 行。一張彩色照片則是形狀為 (H, W, 3) 的張量:高乘寬乘三個顏色通道。別被這個花俏的名字嚇到——目前為止,「張量」和「一個有已知形狀的數字網格(或網格的堆疊)」指的完全是同一件事。這個概念在詞彙表裡有自己的條目:張量表示

一張彩色影像是三個疊起來的網格——紅、綠、藍——所以它的張量形狀是 (H, W, 3)。

一張彩色照片被分離成三層網格,分別標示為紅、綠、藍。

不過神經元想要的輸入是一個攤平的清單——一個一維向量——而不是二維網格。所以我們要做攤平(flatten):我們一格一格、一列一列地把網格讀出來,把這些值排成一長條。看看最小卻仍有意思的例子:一個 2×2 的小灰階區塊,上排是 a、b,下排是 c、d。由左到右、由上到下地讀,就得到向量 x = [a, b, c, d]。就這樣——攤平不過就是「把網格展開成一條線」。

\begin{bmatrix} a & b \\ c & d \end{bmatrix} \;\longrightarrow\; \mathbf{x} = [\,a,\; b,\; c,\; d\,]

把一個 2×2 區塊攤平成長度為 4 的向量。

請仔細讀這個對應,因為後面所有東西都建立在它之上。左邊是原本的 2×2 網格;右邊是攤平後的向量 x。每一個元素 x_i 都恰好是某一個像素的強度:x_1 = a、x_2 = b、x_3 = c、x_4 = d。向量的長度 n 就是像素的總數:對一張 H×W 的灰階影像,n = H × W;彩色的話再乘上通道數,n = H × W × 3。因此一張 28×28 的數字圖會變成長度 28 × 28 = 784 的向量。要牢牢記住的一句話是:「第 i 個像素變成第 i 個輸入 x_i。」 從本單元的這一刻起,每當我們說「一張影像」,真正的意思就是「這條我們可以餵給神經元的長數字向量」。

人工神經元:加權求和再加偏置

現在主角登場了。人工神經元是本單元裡每一個網路所由以構成的原子——搞懂這一個單元,你就理解了九成的機制。請在腦海裡保留這幅畫面:一個神經元是一個小小的投票委員會。每個輸入像素都會投一票,但票票並不等值——每個像素都帶著一個「投票權重」,說明這個像素有多重要,以及它是支持這個決定(正權重)還是反對它(負權重)。神經元把所有加權後的票數加總成單一分數,再加上一個額外的常數——一個基準傾向——稱為偏置(bias)。

我們用最小的情況——只有兩個輸入——把它具體化。假設攤平後的區塊給了我們 x = [x_1, x_2] = [0.8, 0.6](兩個像素強度,為了整齊縮放到 0–1 區間)。再假設神經元的權重是 w = [w_1, w_2] = [0.5, -0.3],偏置是 b = 0.1。第一個像素受信任(權重 +0.5,算是支持);第二個則往反方向推(權重 -0.3,算是反對)。我們動手算這個加權總和:0.5 × 0.8 = 0.40,接著 -0.3 × 0.6 = -0.18,最後加上偏置 0.1。合計:0.40 − 0.18 + 0.10 = 0.32。這單一的數字 0.32,就是神經元對這個輸入給出的分數。

z \;=\; \mathbf{w}\cdot\mathbf{x} + b \;=\; \sum_{i=1}^{n} w_i\,x_i \;+\; b

神經元的加權總和(激活前分數)。

這就是你剛才做的同一套算術,只是寫成一般式。逐個符號來看:x_i 是第 i 個輸入——攤平影像中的某一個像素值。w_i 是那個像素的權重,它的大小說明這個像素有多重要、它的正負號說明它往哪個方向推。那個大大的 Σ(「i 從 1 加到 n」)正是「把加權後的票數加總」這一步:它代表 w_1 x_1 + w_2 x_2 + … + w_n x_n。點乘 w·x 只是同一個總和的簡寫。b 是偏置,一個無論輸入是什麼都會加上去的常數傾向——它把整個分數往上或往下平移,就像在秤上偷偷壓一根手指。而 z 是結果:激活前的分數,也就是在我們塑形之前,神經元最原始的意見。把我們的數字代進去——n = 2、w = [0.5, -0.3]、x = [0.8, 0.6]、b = 0.1——得到 z = (0.5)(0.8) + (-0.3)(0.6) + 0.1 = 0.32,和剛才一模一樣。這條公式不過就是你能在紙上做的乘法加法,沒什麼好怕的。

import numpy as np

# A single artificial neuron: weighted sum + bias, then an activation.
def neuron(x, w, b, activation):
    z = np.dot(w, x) + b      # pre-activation score: tally weighted votes, add lean
    return activation(z)      # final output a = g(z)  (next section)

def sigmoid(z):
    return 1.0 / (1.0 + np.exp(-z))

# Two pixel inputs, e.g. flattened from a tiny 1x2 patch, scaled to 0..1
x = np.array([0.8, 0.6])      # the x_i: pixel intensities
w = np.array([0.5, -0.3])     # the w_i: each pixel's learned weight (size + sign)
b = 0.1                       # the bias: a constant lean

z = np.dot(w, x) + b          # 0.5*0.8 + (-0.3)*0.6 + 0.1 = 0.32
print(z)                      # -> 0.32   (the raw score)
print(sigmoid(z))             # -> ~0.579 (squashed, previewed next section)
整個神經元就是一次點積再加一個偏置。執行它就會得到 z = 0.32,與手算結果相符。
神經元:每個輸入 x_i 各自乘上權重 w_i、加總、再加偏置 b,產生分數 z。

一張圖示:數個輸入透過帶權重的邊連到單一加總節點,該節點再加上偏置並輸出 z。

激活函數:加上「壓縮」這一步

我們的分數 z 是個不錯的起點,但它本身有個問題:它是純粹線性的。把每個輸入加倍,(先不管偏置)z 也跟著加倍;這種關係是一條平直、成比例、毫無轉折的東西。真實的決定很少是這樣的。一個決定通常帶有「啟動」或「飽和」的味道——在某個水準以下什麼都不發生,超過之後答案就被打開;或者答案被擠進一個合理的範圍,例如「介於 0 與 1 之間的機率」。為了賦予神經元這種能彎折的能力,我們讓 z 通過一個激活函數:一個固定的、非線性的塑形器,緊接在加權求和之後施加上去。

a = g(z)

激活函數 g 把原始分數 z 轉成神經元的輸出 a。

把它讀成:拿激活前的分數 z(我們算出的 0.32),把它餵進一個函數 g,再把結果稱為 a——也就是神經元的最終輸出。字母 g 是一個佔位符,代表我們所挑選的任何一種塑形器;a 才是真正離開神經元、被往下傳遞的東西(傳到下一層,或是當作答案輸出)。激活函數是神經元「我該不該激發、又該多強烈地激發?」的那一步:z 是證據的原始計票,而 a 則是經過考量、塑形後的裁決。有兩個特定的 g 選擇將會貫穿你整個單元,我們現在就來認識它們。

\sigma(z) = \dfrac{1}{1 + e^{-z}}

Sigmoid 激活:把任意 z 壓進開區間 (0, 1)。

Sigmoid 激活函數寫作 σ(z),它把任何實數壓進 0 與 1 之間的範圍,使得它的輸出讀起來像一個柔性的機率,或是一個可調光的旋鈕(不只是開/關,而是中間任何程度)。關鍵的部件是 e^{-z},其中 e ≈ 2.718 是一個固定常數。當 z 非常負時,−z 是個很大的正數,所以 e^{-z} 暴漲得很大;分母 1 + e^{-z} 變得巨大,整個分數被推向 0。當 z 是很大的正數時,−z 是個很大的負數,所以 e^{-z} 縮向 0;分母趨近於 1,σ(z) 便往 1 攀升。正中央在 z = 0 時,e^0 = 1,所以 σ(0) = 1/(1+1) = 0.5。把我們的 z = 0.32 代入:e^{-0.32} ≈ 0.726,所以 σ(0.32) = 1/(1 + 0.726) ≈ 1/1.726 ≈ 0.579——讀作「約有 58% 的信心」。像 z = −2 這種強烈為負的輸入,則給出 σ(−2) ≈ 1/(1 + 7.39) ≈ 0.12,也就是「相當有把握:不是」。

\mathrm{ReLU}(z) = \max(0,\, z)

整流線性單元:讓正數通過、把負數夾到零。

整流線性單元,也就是 ReLU,更為簡單,且是現代視覺的主力。max(0, z) 的意思就是「輸出 0 與 z 之中較大的那個」。如果 z 是正的,max(0, z) = z——它原封不動地直接通過。如果 z 是負的,max(0, z) = 0——它被夾到零。把它想成一個單向閥:正向證據自由地流過,負向證據則被完全關閉。對我們的 z = 0.32,ReLU(0.32) = 0.32(正的,所以不動)。對 z = −2,ReLU(−2) = 0(負的,所以被消音)。注意它與 sigmoid 的對比:sigmoid 溫和地把所有東西擠進 (0,1),而 ReLU 則讓正數完全敞開、只把負數抹掉。無論哪一種,得到的結果都是神經元的最終輸出 a。

Sigmoid 是一條平滑的 S 形曲線,往 0 與 1 攤平;ReLU 對負數平貼於零,對正數則是一條直線斜坡。

兩條繪製出的曲線:一條介於 0 與 1 之間的 S 形 sigmoid,以及一條對負輸入為零、之後線性上升的 ReLU。

把單一神經元當分類器——以及它的硬限制

把這三個部件組合起來——把影像攤平成 x、算出 z = w·x + b、再用 sigmoid 壓縮——你就已經有了一個能運作的分類器。一個帶 sigmoid 輸出的人工神經元讀入任何輸入,並以介於 0 與 1 之間的信心度作答:輸出高於 0.5 的我們稱為「類別 1」(比方說「貓」),低於 0.5 的稱為「類別 0」(「不是貓」),而那個確切的數值告訴你它有多確定。是激活函數讓這份信心度的解讀成為可能。這確實是一個完整的——雖然很微小——機器學習模型。

為了看見這個分類器在做什麼,把世界縮小到兩個像素特徵 x_1 和 x_2,這樣每張影像就只是二維平面上的一個點。沿用 w = [0.5, -0.3]、b = 0.1,神經元在分數 z 為正的地方激發「比較像貓」,在 z 為負的地方激發「比較不像貓」。兩者之間的邊界——也就是神經元恰好完全猶豫不決的那一整組點——就在 z = 0 之處。在一側,每個點都被推向類別 1;在另一側,被推向類別 0。問題是:那道邊界是什麼形狀?

\mathbf{w}\cdot\mathbf{x} + b = 0

決策邊界:神經元分數恰好為零之處。

這條方程式釘住了邊界。w·x + b > 0(也就是 z > 0)的點落在類別 1 那一側;w·x + b < 0(z < 0)的點落在類別 0 那一側;而分界線本身恰恰就在它等於 0 之處。用我們的數字來看,它寫成 0.5·x_1 − 0.3·x_2 + 0.1 = 0。把 x_2 解出來就得到 x_2 = (0.5·x_1 + 0.1) / 0.3——一條直線的方程式。而這絕非偶然:因為 w·x + b 對 x 是線性的(每個 x_i 都只出現一次、乘上一個常數,沒有任何平方項、也沒有彼此相乘),所以這條邊界在二維永遠是一條平直的線、在三維是一個平面、在一般情況下是一個平直的超平面。單一神經元只能用一刀筆直的切口去劃分輸入空間。

單一神經元用一條直線把平面一分為二——每一側的點都被推向某一個類別。

二維散布的點被一條筆直的決策線分隔開,一側標示為類別 1、另一側為類別 0。

而這裡有一句點睛之語,讓本單元接下來的一切變得無可避免。當兩個類別恰好落在某條線的兩側時,一刀筆直的切口很棒——但許多真實的樣態根本不是這樣。經典的例子是 XOR:想像類別 1 坐在左上和右下兩角,而類別 0 坐在右上和左下——兩群相互交錯、排得像棋盤格一樣。沒有任何一條直線能同時把兩個類別 1 的角放在一側、又把兩個類別 0 的角放在另一側;你試試看,總會至少錯分掉其中一群。這就是「線性不可分」的意思,也是單一神經元那道硬天花板。解方正是第二份指南的主題:把許多神經元堆疊成層,它們合起來的邊界就能彎折、轉角、繞著任何直線都分不開的群落包起來。一塊積木已經向我們展示了它的極限;接下來,我們就來砌牆。