「修煉開始」一文帶你入門深度學習

2020-12-23 AI科技大本營

來源 | Jack Cui

責編 | Carol

封圖 | CSDN下載自視覺中國

前言

圖解 AI 算法系列教程,不僅僅是涉及深度學習基礎知識,還會有強化學習、遷移學習等,再往小了講就比如拆解目標檢測算法,對抗神經網絡(GAN)等等。

難度會逐漸增加,今天咱先熱熱身,來點輕鬆的,當作這個系列的開篇。

深度學習

深度學習(Deep Learning)是近年來發展十分迅速的研究領域,並且在人 工智能的很多子領域都取得了巨大的成功。從根源來講,深度學習是機器學習的一個分支。

深度學習就是從有限樣例中通過算法總結出一般性的規律,並可以應用到新的未知數據上。

比如,我們可以從一些歷史病例的集合中總結出症狀疾病之間的規律。這樣,當有新的病人時,我們可以利用總結出來的規律來判斷這個病人得了什麼疾病。

深度學習主要由上圖所示的幾個部分組成,想學一個深度學習算法的原理,就看它是什麼樣的網絡結構,Loss 是怎麼計算的,預處理和後處理都是怎麼做的。

權重初始化和學習率調整策略、優化算法、深度學習框架就那麼多,並且也不是所有都要掌握,比如深度學習框架,Pytorch 玩的溜,就能應付大多數場景。

先有個整體的認知,然後再按照這個思維導圖,逐個知識點學習,最後整合到一起,你會發現,你也可以自己實現各種功能的算法了

深度學習的主要目的是從數據中自動學習到有效的特徵表示,它是怎麼工作的?那得從神經元說起。

隨著神經科學、認知科學的發展,我們逐漸知道人類的智能行為都和大腦活動有關。

人腦神經系統[1]是一個非常複雜的組織,包含近 860 億個神經元,這 860 億的神經元構成了超級龐大的神經網絡

我們知道,一個人的智力不完全由遺傳決定,大部分來自於生活經驗。也就是說人腦神經網絡是一個具有學習能力的系統。

不同神經元之間的突觸有強有弱,其強度是可以通過學習(訓練)來不斷改變的,具有一定的可塑性,不同的連接又形成了不同的記憶印痕。

而深度學習的神經網絡,就是受人腦神經網絡啟發,設計的一種計算模型,它從結構、實現機理和功能上模擬人腦神經網絡。

比如下圖就是一個最簡單的前饋神經網絡,第 0 層稱為輸入層,最後一層稱為輸出層,其他中間層稱為隱藏層

那神經網絡如何工作的?網絡層次結構、損失函數、優化算法、權重初始化、學習率調整都是如何運作的?

反向傳播給你答案。前方,高能預警

反向傳播

要想弄懂深度學習原理,必須搞定反向傳播[2]和鏈式求導法則。

先說思維導圖裡的網絡層級結構,一個神經網絡,可複雜可簡單,為了方便推導,假設,你有這樣一個網絡層:

第一層是輸入層,包含兩個神經元 i1, i2 和截距項 b1(偏置);

第二層是隱含層,包含兩個神經元 h1, h2 和截距項 b2 ;

第三層是輸出層o1 和 o2 ,每條線上標的 wi 是層與層之間連接的權重,激活函數我們默認為 sigmoid 函數。

在訓練這個網絡之前,需要初始化這些 wi 權重,這就是權重初始化,這裡就有不少的初始化方法,我們選擇最簡單的,隨機初始化

隨機初始化的結果,如下圖所示:

其中,輸入數據: i1=0.05, i2=0.10;

輸出數據(期望的輸出) : o1=0.01, o2=0.99;

初始權重: w1=0.15, w2=0.20, w3=0.25, w4=0.30, w5=0.40, w6=0.45, w7=0.50, w8=0.55。

目標:給出輸入數據 i1, i2(0.05 和 0.10),使輸出儘可能與原始輸出o1, o2(0.01 和 0.99)接近。

神經網絡的工作流程分為兩步:前向傳播反向傳播

1、前向傳播

前向傳播是將輸入數據根據權重,計算到輸出層。

1)輸入層->隱藏層

計算神經元 h1 的輸入加權和:

神經元後面,要跟個激活層,從而引入非線性因素,這就像人的神經元一樣,讓細胞處於興奮抑制的狀態。

數學模擬的形式就是通過激活函數,大於閾值就激活,反之抑制。

常用的激活函如思維導圖所示,這裡以非常簡單的 sigmoid 激活函數為例,它的函數形式如下:

數學公式:

使用 sigmoid 激活函數,繼續計算,神經元 h1 的輸出 o_h1:

同理,可計算出神經元 h2 的輸出 o_h2:

2)隱藏層->輸出層

計算輸出層神經元 o1 和 o2 的值:

這樣前向傳播的過程就結束了,根據輸入值和權重,我們得到輸出值為[0.75136079, 0.772928465],與實際值(目標)[0.01, 0.99]相差還很遠,現在我們對誤差進行反向傳播,更新權值,重新計算輸出。

2、反向傳播

前向傳播之後,發現輸出結果與期望相差甚遠,這時候就要更新權重了。

所謂深度學習的訓練(煉丹),學的就是這些權重,我們期望的是調整這些權重,讓輸出結果符合我們的期望。

而更新權重的方式,依靠的就是反向傳播。

1)計算總誤差

一次前向傳播過後,輸出值(預測值)與目標值(標籤值)有差距,那得衡量一下有多大差距。

衡量的方法,就是用思維導圖中的損失函數。

損失函數也有很多,咱們還是選擇一個最簡單的,均方誤差(MSE loss)。

均方誤差的函數公式:

根據公式,直接計算預測值與標籤值的總誤差:

有兩個輸出,所以分別計算 o1 和 o2 的誤差,總誤差為兩者之和:

2)隱含層->輸出層的權值更新

以權重參數 w5 為例,如果我們想知道 w5 對整體誤差產生了多少影響,可以用整體誤差對 w5 求偏導求出。

這是鏈式法則,它是微積分中複合函數的求導法則,就是這個:

根據鏈式法則易得:

下面的圖可以更直觀的看清楚誤差是怎樣反向傳播的:

現在我們來分別計算每個式子的值:

計算

計算:

這一步實際上就是對sigmoid函數求導,比較簡單,可以自己推導一下。

計算:

最後三者相乘:

這樣我們就計算出整體誤差E(total)對 w5 的偏導值。

回過頭來再看看上面的公式,我們發現:

為了表達方便,用來表示輸出層的誤差:

因此,整體誤差E(total)對w5的偏導公式可以寫成:

如果輸出層誤差計為負的話,也可以寫成:

最後我們來更新 w5 的值:

這個更新權重的策略,就是思維導圖中的優化算法, 是學習率,我們這裡取0.5。

如果學習率要根據迭代的次數調整,那就用到了思維導圖中的學習率調整

同理,可更新w6,w7,w8:

3)隱含層->隱含層的權值更新

方法其實與上面說的差不多,但是有個地方需要變一下,在上文計算總誤差對 w5 的偏導時,是從out(o1)->net(o1)->w5,但是在隱含層之間的權值更新時,是out(h1)->net(h1)->w1,而 out(h1) 會接受 E(o1) 和 E(o2) 兩個地方傳來的誤差,所以這個地方兩個都要計算。

計算:

先計算:

同理,計算出:

兩者相加得到總值:

再計算:

再計算:

最後,三者相乘:

為了簡化公式,用 sigma(h1) 表示隱含層單元 h1 的誤差:

最後,更新 w1 的權值:

同理,額可更新w2,w3,w4的權值:

這樣誤差反向傳播法就完成了,最後我們再把更新的權值重新計算,不停地迭代。

在這個例子中第一次迭代之後,總誤差E(total)由0.298371109下降至0.291027924。

迭代10000次後,總誤差為0.000035085,輸出為[0.015912196,0.984065734](原輸入為[0.01,0.99]),證明效果還是不錯的。

這就是整個神經網絡的工作原理,如果你跟著思路,順利看到這裡。那麼恭喜你,深度學習的學習算是通過了一關。

Python 實現

整個過程,可以用 Python 代碼實現。

#coding:utf-8

import random

import math

#

# 參數解釋:

# "pd_" :偏導的前綴

# "d_" :導數的前綴

# "w_ho" :隱含層到輸出層的權重係數索引

# "w_ih" :輸入層到隱含層的權重係數的索引

class NeuralNetwork:

LEARNING_RATE = 0.5

def __init__(self, num_inputs, num_hidden, num_outputs, hidden_layer_weights = None, hidden_layer_bias = None, output_layer_weights = None, output_layer_bias = None):

self.num_inputs = num_inputs

self.hidden_layer = NeuronLayer(num_hidden, hidden_layer_bias)

self.output_layer = NeuronLayer(num_outputs, output_layer_bias)

self.init_weights_from_inputs_to_hidden_layer_neurons(hidden_layer_weights)

self.init_weights_from_hidden_layer_neurons_to_output_layer_neurons(output_layer_weights)

def init_weights_from_inputs_to_hidden_layer_neurons(self, hidden_layer_weights):

weight_num = 0

for h in range(len(self.hidden_layer.neurons)):

for i in range(self.num_inputs):

if not hidden_layer_weights:

self.hidden_layer.neurons[h].weights.append(random.random)

else:

self.hidden_layer.neurons[h].weights.append(hidden_layer_weights[weight_num])

weight_num += 1

def init_weights_from_hidden_layer_neurons_to_output_layer_neurons(self, output_layer_weights):

weight_num = 0

for o in range(len(self.output_layer.neurons)):

for h in range(len(self.hidden_layer.neurons)):

if not output_layer_weights:

self.output_layer.neurons[o].weights.append(random.random)

else:

self.output_layer.neurons[o].weights.append(output_layer_weights[weight_num])

weight_num += 1

def inspect(self):

print('------')

print('* Inputs: {}'.format(self.num_inputs))

print('------')

print('Hidden Layer')

self.hidden_layer.inspect

print('------')

print('* Output Layer')

self.output_layer.inspect

print('------')

def feed_forward(self, inputs):

hidden_layer_outputs = self.hidden_layer.feed_forward(inputs)

return self.output_layer.feed_forward(hidden_layer_outputs)

def train(self, training_inputs, training_outputs):

self.feed_forward(training_inputs)

# 1. 輸出神經元的值

pd_errors_wrt_output_neuron_total_net_input = [0] * len(self.output_layer.neurons)

for o in range(len(self.output_layer.neurons)):

# E/z

pd_errors_wrt_output_neuron_total_net_input[o] = self.output_layer.neurons[o].calculate_pd_error_wrt_total_net_input(training_outputs[o])

# 2. 隱含層神經元的值

pd_errors_wrt_hidden_neuron_total_net_input = [0] * len(self.hidden_layer.neurons)

for h in range(len(self.hidden_layer.neurons)):

# dE/dy = Σ E/z * z/y = Σ E/z * w

d_error_wrt_hidden_neuron_output = 0

for o in range(len(self.output_layer.neurons)):

d_error_wrt_hidden_neuron_output += pd_errors_wrt_output_neuron_total_net_input[o] * self.output_layer.neurons[o].weights[h]

# E/z = dE/dy * z/

pd_errors_wrt_hidden_neuron_total_net_input[h] = d_error_wrt_hidden_neuron_output * self.hidden_layer.neurons[h].calculate_pd_total_net_input_wrt_input

# 3. 更新輸出層權重係數

for o in range(len(self.output_layer.neurons)):

for w_ho in range(len(self.output_layer.neurons[o].weights)):

# E/w = E/z * z/w

pd_error_wrt_weight = pd_errors_wrt_output_neuron_total_net_input[o] * self.output_layer.neurons[o].calculate_pd_total_net_input_wrt_weight(w_ho)

# Δw = α * E/w

self.output_layer.neurons[o].weights[w_ho] -= self.LEARNING_RATE * pd_error_wrt_weight

# 4. 更新隱含層的權重係數

for h in range(len(self.hidden_layer.neurons)):

for w_ih in range(len(self.hidden_layer.neurons[h].weights)):

# E/w = E/z * z/w

pd_error_wrt_weight = pd_errors_wrt_hidden_neuron_total_net_input[h] * self.hidden_layer.neurons[h].calculate_pd_total_net_input_wrt_weight(w_ih)

# Δw = α * E/w

self.hidden_layer.neurons[h].weights[w_ih] -= self.LEARNING_RATE * pd_error_wrt_weight

def calculate_total_error(self, training_sets):

total_error = 0

for t in range(len(training_sets)):

training_inputs, training_outputs = training_sets[t]

self.feed_forward(training_inputs)

for o in range(len(training_outputs)):

total_error += self.output_layer.neurons[o].calculate_error(training_outputs[o])

return total_error

class NeuronLayer:

def __init__(self, num_neurons, bias):

# 同一層的神經元共享一個截距項b

self.bias = bias if bias else random.random

self.neurons =

for i in range(num_neurons):

self.neurons.append(Neuron(self.bias))

def inspect(self):

print('Neurons:', len(self.neurons))

for n in range(len(self.neurons)):

print(' Neuron', n)

for w in range(len(self.neurons[n].weights)):

print(' Weight:', self.neurons[n].weights[w])

print(' Bias:', self.bias)

def feed_forward(self, inputs):

outputs =

for neuron in self.neurons:

outputs.append(neuron.calculate_output(inputs))

return outputs

def get_outputs(self):

outputs =

for neuron in self.neurons:

outputs.append(neuron.output)

return outputs

class Neuron:

def __init__(self, bias):

self.bias = bias

self.weights =

def calculate_output(self, inputs):

self.inputs = inputs

self.output = self.squash(self.calculate_total_net_input)

return self.output

def calculate_total_net_input(self):

total = 0

for i in range(len(self.inputs)):

total += self.inputs[i] * self.weights[i]

return total + self.bias

# 激活函數sigmoid

def squash(self, total_net_input):

return 1 / (1 + math.exp(-total_net_input))

def calculate_pd_error_wrt_total_net_input(self, target_output):

return self.calculate_pd_error_wrt_output(target_output) * self.calculate_pd_total_net_input_wrt_input;

# 每一個神經元的誤差是由平方差公式計算的

def calculate_error(self, target_output):

return 0.5 * (target_output - self.output) ** 2

def calculate_pd_error_wrt_output(self, target_output):

return -(target_output - self.output)

def calculate_pd_total_net_input_wrt_input(self):

return self.output * (1 - self.output)

def calculate_pd_total_net_input_wrt_weight(self, index):

return self.inputs[index]

# 文中的例子:

nn = NeuralNetwork(2, 2, 2, hidden_layer_weights=[0.15, 0.2, 0.25, 0.3], hidden_layer_bias=0.35, output_layer_weights=[0.4, 0.45, 0.5, 0.55], output_layer_bias=0.6)

for i in range(10000):

nn.train([0.05, 0.1], [0.01, 0.09])

print(i, round(nn.calculate_total_error([[[0.05, 0.1], [0.01, 0.09]]]), 9))

#另外一個例子,可以把上面的例子注釋掉再運行一下:

# training_sets = [

# [[0, 0], [0]],

# [[0, 1], [1]],

# [[1, 0], [1]],

# [[1, 1], [0]]

# ]

# nn = NeuralNetwork(len(training_sets[0][0]), 5, len(training_sets[0][1]))

# for i in range(10000):

# training_inputs, training_outputs = random.choice(training_sets)

# nn.train(training_inputs, training_outputs)

# print(i, nn.calculate_total_error(training_sets))

其他

預處理和後處理就相對簡單很多,預處理就是一些常規的圖像變換操作,數據增強方法等。

後處理每個任務都略有不同,比如目標檢測的非極大值抑制等,這些內容可以放在以後再講。

至於深度學習框架的學習,那就是另外一大塊內容了,深度學習框架是一種為了深度學習開發而生的工具,庫和預訓練模型等資源的總和。

我們可以用 Python 實現簡單的神經網絡,但是複雜的神經網絡,還得靠框架,框架的使用可以大幅度降低我們的開發成本。

至於學哪種框架,看個人喜好,Pytorch 和 Tensorflow 都行。

學習資料推薦

學完本文,只能算是深度學習入門,還有非常多的內容需要深入學習。

推薦一些資料,方便感興趣的讀者繼續研究。

視頻:

吳恩達的深度學習公開課[3]:https://mooc.study.163.com/university/deeplearning_ai書籍:

《神經網絡與深度學習》《PyTorch深度學習實戰》開源項目:

Pytorch教程 1:https://github.com/yunjey/pytorch-tutorialPytorch教程 2:https://github.com/pytorch/tutorials學習的積累是個漫長而又孤獨的過程,厚積才能薄發,有不懂的知識就多看多想,要相信最後勝利的,是堅持下去的那個人。

參考資料

[1]

推薦深度學習書籍: 《神經網絡與深度學習》

[2]

反向傳播: https://www.cnblogs.com/charlotte77/p/5629865.html

[3]

吳恩達的深度學習公開課: https://mooc.study.163.com/university/deeplearning_ai

相關焦點

  • 我的深度學習入門路線
    因此,可以毫不誇張地說,我們有理由相信《西部世界》中的人工智慧有望成為現實,說不定哪一天,你的同事就是一個超級人工智慧機器人!深度學習,以深而複雜的神經網絡模型為基礎,更能挖掘海量數據深層蘊藏的數據規律,具備強大的學習能力。可以毫不誇張地說,深度學習徹底改變了傳統機器學習算法的解決問題方式。下面,我用一張圖來做比較。
  • Java工程師入門深度學習(三):輕鬆上手Deep Java Library_科技...
    DJL(Deep Java Library )是亞馬遜在2019年宣布推出的開源Java深度學習開發包,它是在現有深度學習框架基礎上使用原生Java概念構建的開發庫。它為開發者提供了深度學習的最新創新和使用前沿硬體的能力,例如GPU、MKL等。簡單的API抽象並簡化了開發深度學習模型所涉及的複雜性,使得DJL更易於學習和應用。
  • 萬字長文帶你看盡深度學習中的各種卷積網絡(下篇)
    正如論文中所指出的:「隨著分類問題的難度增加,解決該問題還需要更多的關鍵部分... 深度網絡中學習過濾器具有分布的特徵值,並且將分離直接用於過濾器會導致明顯的信息丟失。」 為了減輕這類問題,論文限制了感受野的關係從而讓模型可以根據訓練學習 1D 分離的過濾器。
  • 天行者未來學院「校慶活動」開始啦,一大波免費福利來襲!
    「天行者未來學院」的名字也取自瑪雅13月亮歷的「紅天行者」,從今天開始的13天時間裡,是學院的校慶時間,學院特別為大家準備了滿滿的「校慶福利」!學院一直提倡「學習-反思-創造」的理念,學習絕不僅僅是學到一個新知識就足夠了,而是能夠通過這個新知識,對照自己現階段的生活和工作狀態,做深度反思,甚至發揮自己的無限想像力,創造出屬於自己的作品!
  • 「深度好文」找回攝影的初衷
    已經民國100年了,臺灣的攝影文化也該提升了,免得被譏為:別人都上太空了,我們還在殺豬公。是的,業餘攝影應該可以再輕鬆一點、優雅一點。對大多數的人而言,相機只是「拍照」的工具,只想為生活留下紀錄,或滿足工作、學習上的需求。
  • 竹上學社·尺八「入門課」
    通過尺八「入門課」的學習,可以了解尺八的歷史與文化背景,掌握尺八吹奏禮儀與基礎心法,基於心法學習並熟練掌握尺八的乙音吹奏技法,可以流暢演奏《瑪麗的小綿羊》、《月》等同等入門級作品。在「竹上學社」尺八教室裡,老師們會用僅全心對每個知識點進行深層次的講解,並且,針對每一個人的特點,單獨給出最適合的建議。
  • 「一點科普」古典樂到底聽什麼才能入門呢?
    但他們時常會問一個問題,古典樂到底先從什麼曲目開始聽,才能入門呢?針對這個問題,很抱歉,沒有標準答案。我們只能憑藉自身經驗,給到一些建議,希望能夠幫助想入門的各位,少走些彎路,儘快入門!對於這樣的困惑,我們建議你從標題音樂開始聽(有關標題音樂的詳解,請見我們之前的文章《「一點科普」想聽懂古典,先從看懂標題開始吧》。
  • 天生一對,硬核微分方程與深度學習的「聯姻」之路
    微分方程真的能結合深度神經網絡?真的能用來理解深度神經網絡、推導神經網絡架構、構建深度生成模型?我們將從鄂維南、董彬和陳天琦等研究者的工作中,窺探微分方程與深度學習聯袂前行的路徑。近日,北京智源人工智慧研究院開展了第一次論壇,其以「人工智慧的數理基礎」這一重大研究方向為主題,從數學、統計和計算等角度討論了智能系統應該怎樣融合數學系統。
  • 「入門課程」Kris 編舞 Jumpshot 1M(韓國)
    Kirs老師這次的入門編舞感覺非常不錯喲,動作比起以前稍微多了一些,不過難度還是沒有很大,適合剛開始學舞的朋友們!
  • 斯坦福CS224n追劇計劃【大結局】:NLP和深度學習的未來
    簡介Stanford CS224n追劇計劃是由夕小瑤的賣萌屋發起的開源開放NLP入門項目
  • 「聖誕裝飾DIY」帶你學習如何製作雪花掛飾!
    「繩編DIY」帶你學習如何製作雪花掛飾! DIY生活館 圖片展示
  • 「230 條款」也許一開始就不該有
    電影管不了,網絡時代來了網絡還是要管一管。總之,總有人希望管一管。1995 年,美國網際網路也剛剛起步。人嘛,到哪兒都一樣,SEX 作為一個既原始又神秘的力量存在,一直是經久不衰的古老行業。所以網絡上流傳這這方面的信息一點都不奇怪,群眾喜聞樂見,但一本正經西裝革履的國會議員們可是見不得群眾這麼「低俗」。
  • 日語入門學習請踏踏實實走好這六步
    日語作為一門語言,和世界上任何一門語言都是一樣的,都只是存在這個世上的,人類的一種交流工具。對於沒有接觸過日語的小白來說,如何日語入門就顯得非常困難了!為什麼說日語入門學習難,主要在於日語本身的特點:民族性、地域性及約定俗成性!其實,日語入門學習踏踏實實走好這六步就夠了給大家介紹一下。
  • 「繩編DIY」帶你學習如何製作漂亮的壁掛相框!
    「繩編DIY」帶你學習如何製作漂亮的壁掛相框! DIY生活館 圖片展示
  • 「繩編DIY」帶你學習如何製作漂亮的雪花圖案!
    「繩編DIY」帶你學習如何製作漂亮的雪花圖案! DIY生活館 圖片展示
  • 「繩編DIY」帶你學習如何製作漂亮的友誼手鍊!
    「繩編DIY」帶你學習如何製作漂亮的友誼手鍊! DIY生活館 圖片展示
  • 乾貨分享&創意盛宴WAVESUMMIT+2020深度學習開發者峰會等你來!
    由深度學習技術及應用國家工程實驗室與百度聯合主辦的WAVE SUMMIT+2020深度學習開發者峰會將於12月20日在北京舉行。目前峰會官網已正式上線,可以通過官網報名參會或觀看直播。WAVE SUMMIT是中國深度學習開發者每年兩次的技術盛會,WAVE SUMMIT+ 2020是今年的第二場。
  • 一文看懂明網、深網、暗網的區別及暗網的危害 | 白話區塊鏈入門140
    「白話區塊鏈入門」系列讓零基礎的小夥伴也能輕鬆入門,掃描文末二維碼,獲取全部零基礎文集。
  • 遇見五月天,便是你「脫胎換骨」的開始!
    有 一 種 脫 胎 換 骨是認識了五 月 天 。新歌出爐你的好友雞湯信上線了!「學著跟不如意和平相處,人生需要經過一番修煉,修煉過後就是脫胎換骨!」「旅途之後是旅途 一生幾十億萬步  活著就是要不斷脫胎換骨末路一樣是出路  不把客棧當歸宿  腳印全都是身外物」漫長的人生中,你我都必定會遇到一個又一個難關但黑暗過後,就會更加珍惜眼前所擁有的一切
  • 每周一封newsletter+終身學習社群,36氪「氪星學院」幫你提升閱讀...
    「氪星學院」是36氪第一個全網推出的以用戶為核心的「學習型」產品。經過兩個月的內測與公測,我們選擇在今天向所有用戶推薦,它的誕生意味著36氪不僅能拿出出色的報導,也決心要為用戶提供同樣出色的體驗。36氪「氪星學院」的成立,目的在於提升用戶閱讀效率和成長速度。 每一個網際網路用戶,每天會有18個小時被來自不同平臺的各種資訊信息「狂轟亂炸」。