【DL筆記3】一步步親手用python實(shí)現(xiàn)Logistic Regression

前面的【DL筆記1】Logistic回歸:最基礎(chǔ)的神經(jīng)網(wǎng)絡(luò)【DL筆記2】神經(jīng)網(wǎng)絡(luò)編程原則&Logistic Regression的算法解析講解了Logistic regression的基本原理,并且我提到過(guò)這個(gè)玩意兒在我看來(lái)是學(xué)習(xí)神經(jīng)網(wǎng)絡(luò)和深度學(xué)習(xí)的基礎(chǔ),學(xué)到后面就發(fā)現(xiàn),其實(shí)只要這個(gè)東西弄清楚了,后面的就很好明白。
另外,雖然說(shuō)現(xiàn)在有很多很多的機(jī)器學(xué)習(xí)包和深度學(xué)習(xí)框架,像sklearn、TensorFlow、Keras等等,讓我們實(shí)現(xiàn)一個(gè)神經(jīng)網(wǎng)絡(luò)十分容易,但是如果你不了解原理,即使給你一個(gè)框架,里面的大量的函數(shù)和方法你依然不知道如何下手,不知道什么時(shí)候該使用什么,而這些框架里面經(jīng)常提到的“前向傳播”、“反向傳播”、“計(jì)算圖”、各種梯度下降、mini-batch、各種initialization方法等等你也難以理解,更別提如何針對(duì)你的實(shí)際場(chǎng)景在對(duì)癥下藥了。
因此,我的深度學(xué)習(xí)系列筆記,主要是講解神經(jīng)網(wǎng)絡(luò)的思路、算法、原理,然后前期主要使用python和numpy來(lái)實(shí)現(xiàn),只有到我們把神經(jīng)網(wǎng)絡(luò)基本講完,才會(huì)開(kāi)始使用諸如TensorFlow這樣的框架來(lái)實(shí)現(xiàn)。當(dāng)然,這也是我正在聽(tīng)的吳恩達(dá)的深度學(xué)習(xí)系列課程的特點(diǎn),不急不躁,耐心地用最樸素的方法來(lái)實(shí)踐所有的原理,這樣才能融會(huì)貫通,玩轉(zhuǎn)各種框架。

這次的前言有點(diǎn)啰嗦了。。。主要是怕有的讀者說(shuō)“明明可以用機(jī)器學(xué)習(xí)包幾行代碼搞定,為啥偏要用純python費(fèi)勁去實(shí)現(xiàn)”。
好了,進(jìn)入正題:

用python實(shí)現(xiàn)Logistic Regression

一、算法搭建步驟

(一)數(shù)據(jù)預(yù)處理

  • 搞清楚數(shù)據(jù)的形狀、維度
  • 將數(shù)據(jù)(例如圖片)轉(zhuǎn)化成向量(image to vector)方便處理
  • 將數(shù)據(jù)標(biāo)準(zhǔn)化(standardize),這樣更好訓(xùn)練

(二)構(gòu)造各種輔助函數(shù)

  • 激活函數(shù)(此處我們使用sigmoid函數(shù))--activation function
  • 參數(shù)初始化函數(shù)(用來(lái)初始化W和b)--initialization
  • 傳播函數(shù)(這里是用來(lái)求損失cost并對(duì)W、b求導(dǎo),即dW、db)--propagate
  • 優(yōu)化函數(shù)(迭代更新W和b,來(lái)最小化cost)--optimize
  • 預(yù)測(cè)函數(shù)(根據(jù)學(xué)習(xí)到的W和b來(lái)進(jìn)行預(yù)測(cè))--predict

(三)綜合上面的輔助函數(shù),結(jié)合成一個(gè)模型

  • 可以直接輸入訓(xùn)練集、預(yù)測(cè)集、超參數(shù),然后給出模型參數(shù)和準(zhǔn)確率

上面這么多輔助函數(shù)可能看的讓人有點(diǎn)懵逼,因此我花了半小時(shí)在PowerPoint里面畫(huà)了這個(gè)圖(ヾ????),以便更清楚地說(shuō)明它們之間的關(guān)系:


Logistic regression各函數(shù)結(jié)構(gòu)圖

構(gòu)造輔助函數(shù)(helper function)是為了讓我們的結(jié)構(gòu)更清晰,更容易調(diào)試和修改。下面我們按照上面的步驟一個(gè)一個(gè)來(lái)。

二、開(kāi)始編程吧

下面我們采用“展示代碼和注釋+重點(diǎn)地方詳解”的方式來(lái)一步步實(shí)現(xiàn):

(一)數(shù)據(jù)導(dǎo)入和預(yù)處理

# 導(dǎo)入數(shù)據(jù),“_orig”代表這里是原始數(shù)據(jù),我們還要進(jìn)一步處理才能使用:
train_set_x_orig, train_set_y, test_set_x_orig, test_set_y, classes = load_dataset()
#由數(shù)據(jù)集獲取一些基本參數(shù),如訓(xùn)練樣本數(shù)m,圖片大?。?m_train = train_set_x_orig.shape[0]  #訓(xùn)練集大小209
m_test = test_set_x_orig.shape[0]    #測(cè)試集大小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
#對(duì)數(shù)據(jù)進(jìn)行標(biāo)準(zhǔn)化:
train_set_x = train_set_x_flatten/255.
test_set_x = test_set_x_flatten/255.

上面的代碼有幾點(diǎn)要說(shuō)明:

  1. 數(shù)據(jù)導(dǎo)入是直接用吳恩達(dá)網(wǎng)課中的數(shù)據(jù)集,他提供了一個(gè)接口load_dataset()可以直接導(dǎo)入數(shù)據(jù),如果需要數(shù)據(jù)的話可以在文章下方留言獲取。這里主要是展示方法,完全可以用自己的數(shù)據(jù)集來(lái)操作。
    數(shù)據(jù)集是一些圖片,我們要訓(xùn)練一個(gè)識(shí)別貓的分類(lèi)器。
    train_set_x_orig,也就是我們的原始數(shù)據(jù)形狀(209, 64, 64, 3),第一維代表m,即樣本數(shù)量,第二維第三維分別是圖片的長(zhǎng)和寬,第四維代表圖片的RGB三個(gè)通道。
  2. numpy包有重要的關(guān)于矩陣“形狀”的方法:.shape.reshape()
    .shape可以獲取一個(gè)矩陣的形狀,于是我們可以通過(guò)[i]來(lái)知道每一維的大?。?br> .reshape()用來(lái)重構(gòu)矩陣的形狀,直接在里面填寫(xiě)維度即可,還有一些特殊用法,比如此處的用法:
    當(dāng)我們要把一個(gè)向量X(m,a,b,c)這個(gè)四維向量扁平化成X_flatten(m,a* b* c)的二維向量,可以寫(xiě)X_flatten=X.reshape(X.shape[0],-1)即可,其中“-1”代表把剩余維度壓扁的模式。而代碼中還有一個(gè).T,代表轉(zhuǎn)置,因?yàn)槲覀兿M延?xùn)練樣本壓縮成(64* 64 *3,m)的形式。
  3. 為什么需要標(biāo)準(zhǔn)化?
    在說(shuō)明為什么要標(biāo)準(zhǔn)化前,我們不妨說(shuō)說(shuō)一般的標(biāo)準(zhǔn)化是怎么做的:先求出數(shù)據(jù)的均值和方差,然后對(duì)每一個(gè)樣本數(shù)據(jù),先減去均值,然后除以方差,也就是(x-μ)/σ2,說(shuō)白了就是轉(zhuǎn)化成標(biāo)準(zhǔn)正態(tài)分布!這樣,每個(gè)特征都轉(zhuǎn)化成了同樣的分布,不管原來(lái)的范圍是什么,現(xiàn)在都基本限定在同樣的范圍內(nèi)了。
    這樣做的好處是什么呢?且看下面兩個(gè)等高線圖:
    未標(biāo)準(zhǔn)化

    標(biāo)準(zhǔn)化之后

    上面兩個(gè)圖展示了數(shù)據(jù)在未標(biāo)準(zhǔn)化和標(biāo)準(zhǔn)化之后的情形。原數(shù)據(jù)的不同特征的范圍可能會(huì)有很大差別,比如一批數(shù)據(jù)中“年齡”的范圍就比較小,可能20歲 ~ 60歲之間,但是另一個(gè)特征“年收入”可能波動(dòng)范圍就很大,也許0.5萬(wàn) ~ 1000萬(wàn),這種情況下回導(dǎo)致我們的等高線圖變得十分“扁平”,在梯度下降的時(shí)候會(huì)很容易走彎路,因此梯度下降會(huì)比較慢,精度也不高。但是經(jīng)過(guò)標(biāo)準(zhǔn)化(也稱(chēng)歸一化)之后,等高線就變規(guī)矩了,就很容易梯度下降了
    另外,對(duì)于圖片數(shù)據(jù)的話,進(jìn)行標(biāo)準(zhǔn)化很簡(jiǎn)單,因?yàn)镽GB三個(gè)通道的范圍都是255,我們對(duì)圖片的處理就是直接除以255即可。

至此,數(shù)據(jù)預(yù)處理就完成了,我們進(jìn)入下一步:

(二)構(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是一個(gè)列向量,傳入維度dim,返回shape為(dim,1)的W,b就是一個(gè)數(shù)。
這里用到的方法是np.zeros(shape).

3.propagate函數(shù):
這里再次解釋一下這個(gè)propagate,它包含了forward-propagate和backward-propagate,即正向傳播和反向傳播。正向傳播求的是cost,反向傳播是從cost的表達(dá)式倒推W和b的偏導(dǎo)數(shù),當(dāng)然我們會(huì)先求出Z的偏導(dǎo)數(shù)。這兩個(gè)方向的傳播也是神經(jīng)網(wǎng)絡(luò)的精髓。
具體倒數(shù)怎么求,這里就不推導(dǎo)了,就是很簡(jiǎn)單的求導(dǎo)嘛,公式請(qǐng)參見(jiàn)上一篇文章:【DL筆記2】神經(jīng)網(wǎng)絡(luò)編程原則&Logistic Regression的算法解析
那么我就直接上代碼了:

def propagate(w, b, X, Y):
    """
    傳參:
    w -- 權(quán)重, shape: (num_px * num_px * 3, 1)
    b -- 偏置項(xiàng), 一個(gè)標(biāo)量
    X -- 數(shù)據(jù)集,shape: (num_px * num_px * 3, m),m為樣本數(shù)
    Y -- 真實(shí)標(biāo)簽,shape: (1,m)

    返回值:
    cost, dw ,db,后兩者放在一個(gè)字典grads里
    """
    #獲取樣本數(shù)m:
    m = X.shape[1]
    
    # 前向傳播 :
    A = sigmoid(np.dot(w.T,X)+b)    #調(diào)用前面寫(xiě)的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

這里需要額外說(shuō)明的就是,numpy中矩陣的點(diǎn)乘,也就是內(nèi)積運(yùn)算,是用np.dot(A,B),它要求前一個(gè)矩陣的列數(shù)等于后一個(gè)矩陣的行數(shù)。但矩陣也可以進(jìn)行元素相乘(element product),就是兩個(gè)相同形狀的矩陣對(duì)于元素相乘得到一個(gè)新的相同形狀的矩陣,可以直接用A * B,或者用np.multiply(A,B)。
上面的代碼中,既有點(diǎn)乘,也有元素相乘,我們?cè)趯?xiě)的時(shí)候,先搞清楚形狀,再確定用什么乘法。
上面還有各種numpy的數(shù)學(xué)函數(shù),對(duì)矩陣求log就用np.log(),對(duì)矩陣元素求和就用np.sum(),賊方便。

4.optimize函數(shù):
有了上面這些函數(shù)的加持,optimize函數(shù)就很好寫(xiě)了,就是在迭代中調(diào)用各個(gè)我們剛剛寫(xiě)的函數(shù)就是:

def optimize(w, b, X, Y, num_iterations, learning_rate, print_cost = False):
    #定義一個(gè)costs數(shù)組,存放每若干次迭代后的cost,從而可以畫(huà)圖看看cost的變化趨勢(shì):
    costs = []
    #進(jìn)行迭代:
    for i in range(num_iterations):
        # 用propagate計(jì)算出每次迭代后的cost和梯度:
        grads, cost = propagate(w,b,X,Y)
        dw = grads["dw"]
        db = grads["db"]
        
        # 用上面得到的梯度來(lái)更新參數(shù):
        w = w - learning_rate*dw
        b = b - learning_rate*db
        
        # 每100次迭代,保存一個(gè)cost看看:
        if i % 100 == 0:
            costs.append(cost)
        
        # 這個(gè)可以不在意,我們可以每100次把cost打印出來(lái)看看,從而隨時(shí)掌握模型的進(jìn)展:
        if print_cost and i % 100 == 0:
            print ("Cost after iteration %i: %f" %(i, cost))
    #迭代完畢,將最終的各個(gè)參數(shù)放進(jìn)字典,并返回:
    params = {"w": w,
              "b": b}
    grads = {"dw": dw,
             "db": db}
    return params, grads, costs

這個(gè)函數(shù)就沒(méi)什么好解釋的了。

5.predict函數(shù):
預(yù)測(cè)就很簡(jiǎn)單了,我們已經(jīng)學(xué)到了參數(shù)W和b,那么讓我們的數(shù)據(jù)經(jīng)過(guò)配備這些參數(shù)的模型就可得到預(yù)測(cè)值。注意,X->Z->激活得到A,此時(shí)還并不是預(yù)測(cè)值,由sigmoid函數(shù)我們知道,A的范圍是01,但是我們的標(biāo)簽值是0和1,因此,我們可以設(shè)立規(guī)則:0.51的A對(duì)于預(yù)測(cè)值1,小于0.5的對(duì)應(yīng)預(yù)測(cè)值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了:



畢竟我自己都不相信會(huì)有幾個(gè)人真的去看這么枯燥的過(guò)程。但是我相信,每一份耐心和付出都有回報(bào)吧,學(xué)習(xí)這事兒,急不來(lái)。

至此,我們已經(jīng)構(gòu)建好了所有的輔助函數(shù)。接下來(lái)就是結(jié)合在一起,然后用我們的數(shù)據(jù)去訓(xùn)練、預(yù)測(cè)了!

(三)結(jié)合起來(lái),搭建模型!

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']

    #用學(xué)得的參數(shù)進(jìn)行預(yù)測(cè):
    prediction_train = predict(W,b,X_test)
    prediction_test = predict(W,b,X_train)

    #計(jì)算準(zhǔn)確率,分別在訓(xùn)練集和測(cè)試集上:
    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ù)都存進(jìn)一個(gè)字典返回出來(lái):
    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)造模型。
唯一值得一提的是這個(gè)準(zhǔn)確率怎么計(jì)算的問(wèn)題,我們的predict函數(shù)得到的是一個(gè)列向量(1,m),這個(gè)跟我們的標(biāo)簽Y是一樣的形狀。我們首先可以讓兩者相減
prediction_test - Y_test
如果對(duì)應(yīng)位置相同,則變成0,不同的話要么是1要么是-1,于是再取絕對(duì)值
np.abs(prediction_test - Y_test),
就相當(dāng)于得到了“哪些位置預(yù)測(cè)錯(cuò)了”的一個(gè)向量,于是我們?cè)偾笠粋€(gè)均值
np.mean(np.abs(prediction_test - Y_test)),
就是“錯(cuò)誤率”了,然后用1來(lái)減去它,就是正確率了!

大功告成!試試效果:

d = model(train_set_x, train_set_y, test_set_x, test_set_y, num_iterations = 2000, learning_rate = 0.005, print_cost = True)

運(yùn)行模型就很簡(jiǎn)單了,把我們的數(shù)據(jù)集穿進(jìn)去,設(shè)置我們想要的超參數(shù),主要是學(xué)習(xí)率(learning rate)、迭代數(shù)(num_iterations),然后把print_cost設(shè)為T(mén)rue,這樣可以在模型訓(xùn)練過(guò)程中打印cost的變化趨勢(shì)。

運(yùn)行,查看結(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 %

可以看到,隨著訓(xùn)練的進(jìn)行,cost在不斷地降低,這說(shuō)明的參數(shù)在變得越來(lái)越好。
最終,在訓(xùn)練集上的準(zhǔn)確率達(dá)到了99%以上,測(cè)試集準(zhǔn)確率為70%。
哈哈,很明顯,我們的模型過(guò)擬合了,測(cè)試集的準(zhǔn)確率還有待提高。但是這個(gè)不重要!重要的是我們親手再?zèng)]有用任何框架的情況下用python把Logistic regression給實(shí)現(xiàn)了一遍,每一個(gè)細(xì)節(jié)都明明白白!?(?>?<?)?
況且,這才僅僅是一個(gè)Logistic regression,相當(dāng)于1層的只有一個(gè)神經(jīng)元的神經(jīng)網(wǎng)絡(luò),能對(duì)圖片分類(lèi)達(dá)到70%的準(zhǔn)確率,我們已經(jīng)很棒了!


其實(shí),神經(jīng)網(wǎng)絡(luò)無(wú)非就是在Logistic regression的基礎(chǔ)上,多了幾個(gè)隱層,每層多了一些神經(jīng)元,卷積神經(jīng)網(wǎng)絡(luò)無(wú)非就是再多了幾個(gè)特殊的filter,多了一些有特定功能的層,但是核心都是跟Logistic Regression一樣的:

前向傳播求損失,
反向傳播求倒數(shù);
不斷迭代和更新,
調(diào)參預(yù)測(cè)準(zhǔn)確度。

喲嗬!才發(fā)現(xiàn)自己還有寫(xiě)詩(shī)的天賦。



本文就到此結(jié)束,終于結(jié)束了,出去吃串串了~
我的其他深度學(xué)習(xí)文章
【DL筆記1】Logistic回歸:最基礎(chǔ)的神經(jīng)網(wǎng)絡(luò)
【DL筆記2】神經(jīng)網(wǎng)絡(luò)編程原則&Logistic Regression的算法解析
【DL筆記】神經(jīng)網(wǎng)絡(luò)參數(shù)初始化的學(xué)問(wèn)
【DL筆記】神經(jīng)網(wǎng)絡(luò)中的優(yōu)化算法
歡迎關(guān)注我的專(zhuān)題
DeepLearning.ai學(xué)習(xí)筆記

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容