前面的【DL筆記1】Logistic回歸:最基礎的神經(jīng)網(wǎng)絡和【DL筆記2】神經(jīng)網(wǎng)絡編程原則&Logistic Regression的算法解析講解了Logistic regression的基本原理淮韭,并且我提到過這個玩意兒在我看來是學習神經(jīng)網(wǎng)絡和深度學習的基礎弯汰,學到后面就發(fā)現(xiàn),其實只要這個東西弄清楚了迷殿,后面的就很好明白瑞躺。
另外树叽,雖然說現(xiàn)在有很多很多的機器學習包和深度學習框架镐捧,像sklearn支救、TensorFlow摘符、Keras等等贤斜,讓我們實現(xiàn)一個神經(jīng)網(wǎng)絡十分容易,但是如果你不了解原理逛裤,即使給你一個框架瘩绒,里面的大量的函數(shù)和方法你依然不知道如何下手,不知道什么時候該使用什么带族,而這些框架里面經(jīng)常提到的“前向傳播”锁荔、“反向傳播”、“計算圖”蝙砌、各種梯度下降阳堕、mini-batch、各種initialization方法等等你也難以理解择克,更別提如何針對你的實際場景在對癥下藥了恬总。
因此,我的深度學習系列筆記肚邢,主要是講解神經(jīng)網(wǎng)絡的思路壹堰、算法拭卿、原理,然后前期主要使用python和numpy來實現(xiàn)贱纠,只有到我們把神經(jīng)網(wǎng)絡基本講完峻厚,才會開始使用諸如TensorFlow這樣的框架來實現(xiàn)。當然谆焊,這也是我正在聽的吳恩達的深度學習系列課程的特點惠桃,不急不躁,耐心地用最樸素的方法來實踐所有的原理懊渡,這樣才能融會貫通刽射,玩轉(zhuǎn)各種框架。
這次的前言有點啰嗦了剃执。誓禁。。主要是怕有的讀者說“明明可以用機器學習包幾行代碼搞定肾档,為啥偏要用純python費勁去實現(xiàn)”摹恰。
好了怒见,進入正題:
用python實現(xiàn)Logistic Regression
一俗慈、算法搭建步驟
(一)數(shù)據(jù)預處理
- 搞清楚數(shù)據(jù)的形狀、維度
- 將數(shù)據(jù)(例如圖片)轉(zhuǎn)化成向量(image to vector)方便處理
- 將數(shù)據(jù)標準化(standardize)遣耍,這樣更好訓練
(二)構(gòu)造各種輔助函數(shù)
- 激活函數(shù)(此處我們使用sigmoid函數(shù))--activation function
- 參數(shù)初始化函數(shù)(用來初始化W和b)--initialization
- 傳播函數(shù)(這里是用來求損失cost并對W闺阱、b求導,即dW舵变、db)--propagate
- 優(yōu)化函數(shù)(迭代更新W和b酣溃,來最小化cost)--optimize
- 預測函數(shù)(根據(jù)學習到的W和b來進行預測)--predict
(三)綜合上面的輔助函數(shù),結(jié)合成一個模型
- 可以直接輸入訓練集纪隙、預測集赊豌、超參數(shù),然后給出模型參數(shù)和準確率
上面這么多輔助函數(shù)可能看的讓人有點懵逼绵咱,因此我花了半小時在PowerPoint里面畫了這個圖(ヾ????)碘饼,以便更清楚地說明它們之間的關系:
構(gòu)造輔助函數(shù)(helper function)是為了讓我們的結(jié)構(gòu)更清晰,更容易調(diào)試和修改悲伶。下面我們按照上面的步驟一個一個來艾恼。
二、開始編程吧
下面我們采用“展示代碼和注釋+重點地方詳解”的方式來一步步實現(xiàn):
(一)數(shù)據(jù)導入和預處理
# 導入數(shù)據(jù)拢切,“_orig”代表這里是原始數(shù)據(jù)蒂萎,我們還要進一步處理才能使用:
train_set_x_orig, train_set_y, test_set_x_orig, test_set_y, classes = load_dataset()
#由數(shù)據(jù)集獲取一些基本參數(shù),如訓練樣本數(shù)m淮椰,圖片大形宕取:
m_train = train_set_x_orig.shape[0] #訓練集大小209
m_test = test_set_x_orig.shape[0] #測試集大小209
num_px = train_set_x_orig.shape[1] #圖片寬度64纳寂,大小是64×64
#將圖片數(shù)據(jù)向量化(扁平化):
train_set_x_flatten = train_set_x_orig.reshape(train_set_x_orig.shape[0],-1).T
test_set_x_flatten = test_set_x_orig.reshape(test_set_x_orig.shape[0],-1).T
#對數(shù)據(jù)進行標準化:
train_set_x = train_set_x_flatten/255.
test_set_x = test_set_x_flatten/255.
上面的代碼有幾點要說明:
- 數(shù)據(jù)導入是直接用吳恩達網(wǎng)課中的數(shù)據(jù)集,他提供了一個接口load_dataset()可以直接導入數(shù)據(jù)泻拦,如果需要數(shù)據(jù)的話可以在文章下方留言獲取毙芜。這里主要是展示方法,完全可以用自己的數(shù)據(jù)集來操作争拐。
數(shù)據(jù)集是一些圖片腋粥,我們要訓練一個識別貓的分類器。
train_set_x_orig架曹,也就是我們的原始數(shù)據(jù)的形狀是(209, 64, 64, 3)隘冲,第一維代表m,即樣本數(shù)量绑雄,第二維第三維分別是圖片的長和寬展辞,第四維代表圖片的RGB三個通道。 - numpy包有重要的關于矩陣“形狀”的方法:.shape和.reshape()
.shape可以獲取一個矩陣的形狀万牺,于是我們可以通過[i]來知道每一維的大新拚洹;
.reshape()用來重構(gòu)矩陣的形狀脚粟,直接在里面填寫維度即可覆旱,還有一些特殊用法,比如此處的用法:
當我們要把一個向量X(m,a,b,c)這個四維向量扁平化成X_flatten(m,a* b* c)的二維向量核无,可以寫X_flatten=X.reshape(X.shape[0],-1)即可扣唱,其中“-1”代表把剩余維度壓扁的模式。而代碼中還有一個.T,代表轉(zhuǎn)置团南,因為我們希望把訓練樣本壓縮成(64* 64 *3画舌,m)的形式。 -
為什么需要標準化已慢?
在說明為什么要標準化前,我們不妨說說一般的標準化是怎么做的:先求出數(shù)據(jù)的均值和方差霹购,然后對每一個樣本數(shù)據(jù)佑惠,先減去均值,然后除以方差齐疙,也就是(x-μ)/σ2,說白了就是轉(zhuǎn)化成標準正態(tài)分布膜楷!這樣,每個特征都轉(zhuǎn)化成了同樣的分布贞奋,不管原來的范圍是什么赌厅,現(xiàn)在都基本限定在同樣的范圍內(nèi)了。
這樣做的好處是什么呢轿塔?且看下面兩個等高線圖:
上面兩個圖展示了數(shù)據(jù)在未標準化和標準化之后的情形特愿。原數(shù)據(jù)的不同特征的范圍可能會有很大差別仲墨,比如一批數(shù)據(jù)中“年齡”的范圍就比較小,可能20歲 ~ 60歲之間揍障,但是另一個特征“年收入”可能波動范圍就很大目养,也許0.5萬 ~ 1000萬,這種情況下回導致我們的等高線圖變得十分“扁平”毒嫡,在梯度下降的時候會很容易走彎路癌蚁,因此梯度下降會比較慢刽严,精度也不高昌抠。但是經(jīng)過標準化(也稱歸一化)之后瞬捕,等高線就變規(guī)矩了吁讨,就很容易梯度下降了甘晤。
另外陷谱,對于圖片數(shù)據(jù)的話褪子,進行標準化很簡單订晌,因為RGB三個通道的范圍都是255菲嘴,我們對圖片的處理就是直接除以255即可饿自。
至此,數(shù)據(jù)預處理就完成了龄坪,我們進入下一步:
(二)構(gòu)建輔助函數(shù)們
1. 激活函數(shù)/sigmoid函數(shù):
def sigmoid(z):
a = 1/(1+np.exp(-z))
return a
就這么easy昭雌,sigmoid的公式就是1/(1+e-x),這里用np.exp()就可以輕松構(gòu)建健田。
2. 參數(shù)初始化函數(shù)(給參數(shù)都初始化為0):
def initialize_with_zeros(dim):
w = np.zeros((dim,1))
b = 0
return w,b
W是一個列向量烛卧,傳入維度dim,返回shape為(dim,1)的W妓局,b就是一個數(shù)总放。
這里用到的方法是np.zeros(shape).
3.propagate函數(shù):
這里再次解釋一下這個propagate,它包含了forward-propagate和backward-propagate好爬,即正向傳播和反向傳播局雄。正向傳播求的是cost,反向傳播是從cost的表達式倒推W和b的偏導數(shù)存炮,當然我們會先求出Z的偏導數(shù)炬搭。這兩個方向的傳播也是神經(jīng)網(wǎng)絡的精髓。
具體倒數(shù)怎么求穆桂,這里就不推導了宫盔,就是很簡單的求導嘛,公式請參見上一篇文章:【DL筆記2】神經(jīng)網(wǎng)絡編程原則&Logistic Regression的算法解析
那么我就直接上代碼了:
def propagate(w, b, X, Y):
"""
傳參:
w -- 權重, shape: (num_px * num_px * 3, 1)
b -- 偏置項, 一個標量
X -- 數(shù)據(jù)集享完,shape: (num_px * num_px * 3, m),m為樣本數(shù)
Y -- 真實標簽灼芭,shape: (1,m)
返回值:
cost, dw 般又,db彼绷,后兩者放在一個字典grads里
"""
#獲取樣本數(shù)m:
m = X.shape[1]
# 前向傳播 :
A = sigmoid(np.dot(w.T,X)+b) #調(diào)用前面寫的sigmoid函數(shù)
cost = -(np.sum(Y*np.log(A)+(1-Y)*np.log(1-A)))/m
# 反向傳播:
dZ = A-Y
dw = (np.dot(X,dZ.T))/m
db = (np.sum(dZ))/m
#返回值:
grads = {"dw": dw,
"db": db}
return grads, cost
這里需要額外說明的就是巍佑,numpy中矩陣的點乘,也就是內(nèi)積運算苛预,是用np.dot(A,B)句狼,它要求前一個矩陣的列數(shù)等于后一個矩陣的行數(shù)。但矩陣也可以進行元素相乘(element product)热某,就是兩個相同形狀的矩陣對于元素相乘得到一個新的相同形狀的矩陣腻菇,可以直接用A * B,或者用np.multiply(A,B)昔馋。
上面的代碼中筹吐,既有點乘,也有元素相乘秘遏,我們在寫的時候丘薛,先搞清楚形狀,再確定用什么乘法邦危。
上面還有各種numpy的數(shù)學函數(shù)洋侨,對矩陣求log就用np.log(),對矩陣元素求和就用np.sum()倦蚪,賊方便希坚。
4.optimize函數(shù):
有了上面這些函數(shù)的加持,optimize函數(shù)就很好寫了陵且,就是在迭代中調(diào)用各個我們剛剛寫的函數(shù)就是:
def optimize(w, b, X, Y, num_iterations, learning_rate, print_cost = False):
#定義一個costs數(shù)組裁僧,存放每若干次迭代后的cost,從而可以畫圖看看cost的變化趨勢:
costs = []
#進行迭代:
for i in range(num_iterations):
# 用propagate計算出每次迭代后的cost和梯度:
grads, cost = propagate(w,b,X,Y)
dw = grads["dw"]
db = grads["db"]
# 用上面得到的梯度來更新參數(shù):
w = w - learning_rate*dw
b = b - learning_rate*db
# 每100次迭代慕购,保存一個cost看看:
if i % 100 == 0:
costs.append(cost)
# 這個可以不在意聊疲,我們可以每100次把cost打印出來看看,從而隨時掌握模型的進展:
if print_cost and i % 100 == 0:
print ("Cost after iteration %i: %f" %(i, cost))
#迭代完畢沪悲,將最終的各個參數(shù)放進字典获洲,并返回:
params = {"w": w,
"b": b}
grads = {"dw": dw,
"db": db}
return params, grads, costs
這個函數(shù)就沒什么好解釋的了。
5.predict函數(shù):
預測就很簡單了殿如,我們已經(jīng)學到了參數(shù)W和b昌妹,那么讓我們的數(shù)據(jù)經(jīng)過配備這些參數(shù)的模型就可得到預測值。注意握截,X->Z->激活得到A,此時還并不是預測值烂叔,由sigmoid函數(shù)我們知道谨胞,A的范圍是01,但是我們的標簽值是0和1蒜鸡,因此胯努,我們可以設立規(guī)則:0.51的A對于預測值1,小于0.5的對應預測值0:
def predict(w,b,X):
m = X.shape[1]
Y_prediction = np.zeros((1,m))
A = sigmoid(np.dot(w.T,X)+b)
for i in range(m):
if A[0,i]>0.5:
Y_prediction[0,i] = 1
else:
Y_prediction[0,i] = 0
return Y_prediction
恭喜牢裳,如果你有耐心看到這里了。叶沛。蒲讯。那。灰署。判帮。我真的忍不住送你一朵fa了:
畢竟我自己都不相信會有幾個人真的去看這么枯燥的過程。但是我相信溉箕,每一份耐心和付出都有回報吧晦墙,學習這事兒,急不來肴茄。
至此晌畅,我們已經(jīng)構(gòu)建好了所有的輔助函數(shù)。接下來就是結(jié)合在一起寡痰,然后用我們的數(shù)據(jù)去訓練抗楔、預測了!
(三)結(jié)合起來拦坠,搭建模型连躏!
def logistic_model(X_train,Y_train,X_test,Y_test,learning_rate=0.1,num_iterations=2000,print_cost=False):
#獲特征維度,初始化參數(shù):
dim = X_train.shape[0]
W,b = initialize_with_zeros(dim)
#梯度下降贪婉,迭代求出模型參數(shù):
params,grads,costs = optimize(W,b,X_train,Y_train,num_iterations,learning_rate,print_cost)
W = params['w']
b = params['b']
#用學得的參數(shù)進行預測:
prediction_train = predict(W,b,X_test)
prediction_test = predict(W,b,X_train)
#計算準確率反粥,分別在訓練集和測試集上:
accuracy_train = 1 - np.mean(np.abs(prediction_train - Y_train))
accuracy_test = 1 - np.mean(np.abs(prediction_test - Y_test))
print("Accuracy on train set:",accuracy_train )
print("Accuracy on test set:",accuracy_test )
#為了便于分析和檢查,我們把得到的所有參數(shù)疲迂、超參數(shù)都存進一個字典返回出來:
d = {"costs": costs,
"Y_prediction_test": prediction_test ,
"Y_prediction_train" : prediction_train ,
"w" : w,
"b" : b,
"learning_rate" : learning_rate,
"num_iterations": num_iterations,
"train_acy":train_acy,
"test_acy":test_acy
}
return d
就是這么easy才顿,只要我們一步步把前面的輔助函數(shù)搭建好,這里就可以很輕松很清晰地構(gòu)造模型尤蒿。
唯一值得一提的是這個準確率怎么計算的問題郑气,我們的predict函數(shù)得到的是一個列向量(1,m)腰池,這個跟我們的標簽Y是一樣的形狀尾组。我們首先可以讓兩者相減:
prediction_test - Y_test,
如果對應位置相同示弓,則變成0讳侨,不同的話要么是1要么是-1,于是再取絕對值:
np.abs(prediction_test - Y_test)奏属,
就相當于得到了“哪些位置預測錯了”的一個向量跨跨,于是我們再求一個均值:
np.mean(np.abs(prediction_test - Y_test)),
就是“錯誤率”了,然后用1來減去它勇婴,就是正確率了忱嘹!
大功告成!試試效果:
d = model(train_set_x, train_set_y, test_set_x, test_set_y, num_iterations = 2000, learning_rate = 0.005, print_cost = True)
運行模型就很簡單了耕渴,把我們的數(shù)據(jù)集穿進去拘悦,設置我們想要的超參數(shù),主要是學習率(learning rate)橱脸、迭代數(shù)(num_iterations)础米,然后把print_cost設為True,這樣可以在模型訓練過程中打印cost的變化趨勢慰技。
運行椭盏,查看結(jié)果:
Cost after iteration 0: 0.693147
Cost after iteration 100: 0.584508
Cost after iteration 200: 0.466949
Cost after iteration 300: 0.376007
Cost after iteration 400: 0.331463
Cost after iteration 500: 0.303273
Cost after iteration 600: 0.279880
Cost after iteration 700: 0.260042
Cost after iteration 800: 0.242941
Cost after iteration 900: 0.228004
Cost after iteration 1000: 0.214820
Cost after iteration 1100: 0.203078
Cost after iteration 1200: 0.192544
Cost after iteration 1300: 0.183033
Cost after iteration 1400: 0.174399
Cost after iteration 1500: 0.166521
Cost after iteration 1600: 0.159305
Cost after iteration 1700: 0.152667
Cost after iteration 1800: 0.146542
Cost after iteration 1900: 0.140872
---------------------
train accuracy: 99.04306220095694 %
test accuracy: 70.0 %
可以看到,隨著訓練的進行吻商,cost在不斷地降低掏颊,這說明的參數(shù)在變得越來越好。
最終艾帐,在訓練集上的準確率達到了99%以上乌叶,測試集準確率為70%。
哈哈柒爸,很明顯准浴,我們的模型過擬合了,測試集的準確率還有待提高捎稚。但是這個不重要乐横!重要的是我們親手再沒有用任何框架的情況下用python把Logistic regression給實現(xiàn)了一遍,每一個細節(jié)都明明白白今野!?(?>?<?)?
況且葡公,這才僅僅是一個Logistic regression,相當于1層的只有一個神經(jīng)元的神經(jīng)網(wǎng)絡条霜,能對圖片分類達到70%的準確率催什,我們已經(jīng)很棒了!
其實,神經(jīng)網(wǎng)絡無非就是在Logistic regression的基礎上宰睡,多了幾個隱層蒲凶,每層多了一些神經(jīng)元,卷積神經(jīng)網(wǎng)絡無非就是再多了幾個特殊的filter拆内,多了一些有特定功能的層旋圆,但是核心都是跟Logistic Regression一樣的:
前向傳播求損失,
反向傳播求倒數(shù)麸恍;
不斷迭代和更新灵巧,
調(diào)參預測準確度。
喲嗬!才發(fā)現(xiàn)自己還有寫詩的天賦孩等。
本文就到此結(jié)束,終于結(jié)束了采够,出去吃串串了~
我的其他深度學習文章:
【DL筆記1】Logistic回歸:最基礎的神經(jīng)網(wǎng)絡
【DL筆記2】神經(jīng)網(wǎng)絡編程原則&Logistic Regression的算法解析
【DL筆記】神經(jīng)網(wǎng)絡參數(shù)初始化的學問
【DL筆記】神經(jīng)網(wǎng)絡中的優(yōu)化算法
歡迎關注我的專題:
DeepLearning.ai學習筆記