1. 程式人生 > >深度學習:邏輯回歸

深度學習:邏輯回歸

圖片 name 拓展 變量 測試 隨機 最終 分享圖片 ase

深度學習(Deep Learning)是機器學習(Machine Learning)的一大分支,它試圖使用包含復雜結構或由多重非線性變換構成的多個處理層對數據進行高層抽象的算法。

邏輯回歸(Logistic Regression,也譯作“對數幾率回歸”)是離散選擇法模型之一,屬於多重變量分析範疇,是社會學、生物統計學、臨床、數量心理學、計量經濟學、市場營銷等統計實證分析的常用方法。

符號約定

邏輯回歸一般用於二分類(Binary Classification)問題中,給定一些輸入,輸出結果是離散值。例如用邏輯回歸實現一個貓分類器,輸入一張圖片xx,預測圖片是否為貓,輸出該圖片中存在貓的概率結果yy。
技術分享圖片

圖片是一類非結構化的數據,在計算機中,一張圖片以RGB編碼方式存儲時,是將它以紅、綠、藍為三基色,以每個像素點上三種顏色的十六進制顏色碼進行編碼,形成一個包含三個信道(Channel),各信道大小和圖片的大小相同的矩陣。例如圖中的貓圖大小為64×6464×64,那麽表示為矩陣後的每個信道大小即為64×6464×64。

模式識別(Pattern Recognition)以及機器學習中,處理的各種類型的數據都需要用一些特征向量來表示。為了將上例中的圖片表示為一個特征向量xx,將每三個信道中三種顏色的值進行拆分重塑,最終形成的特征向量xx的維數為nx=64×64×3=12288nx=64×64×3=12288:
技術分享圖片


實現一個貓分類器,需要準備大量的貓圖及少量的非貓圖,取其中大部分組成該分類器的訓練樣本,少部分組成測試樣本。將這些樣本都以上述的方式表示為特征向量的形式,則一個樣本由一對(x,y)進行表示,其中x為nxnx維的特征向量,y是該特征向量的標簽(Label),根據該特征向量表示的是貓或非貓,值為0或1。m個訓練樣本對將被表示為:

{(x(1),y(1)),(x(2),y(2)),...,(x(m),y(m))}{(x(1),y(1)),(x(2),y(2)),...,(x(m),y(m))}

將訓練集的中的特征向量x(1)x(1)、x(2)x(2)…以及它們的標簽y(1)y(1)、y(2)y(2)…分別堆疊起來,組成兩個矩陣XX、YY:

X=[x(1),x(2),...,x(m)]X=[x(1),x(2),...,x(m)]

Y=[y(1),y(2),...,y(m)]Y=[y(1),y(2),...,y(m)]


則XX會是個大小為nx×mnx×m的矩陣,YY是個大小為1×m1×m的矩陣。

邏輯回歸

邏輯回歸是一種用於解決監督學習(Supervised Learning)問題的學習算法。進行邏輯回歸的目的,是使訓練數據的標簽值與預測出來的值之間的誤差最小化。

貓分類器要實現的,是給定以一個nxnx維特征向量xx表示,標簽為yy的一張圖片,估計這張圖片為貓圖的概率y^y^,即:

y^=p(y=1|x),0≤y^≤1y^=p(y=1|x),0≤y^≤1

有大量貓圖的數據XX時,希望能用一個函數,來表示出y^y^。所以考慮使用線性擬合的方法,從大量數據中尋找規律,從而實現這個貓分類器。規定一個nxnx維向量ww和一個值bb作為參數,可得到線性回歸的表達式:

y^=wTX+by^=wTX+b

由於y^y^為概率值,取值範圍為[0,1][0,1],簡單地進行線性擬合,得出的y^y^可能非常大,還可能為負值。這時,便需要一個邏輯回歸單元來對其值域進行約束。以sigmoid函數為邏輯回歸單元,而sigmoid函數的表達式為:

σ(z)=11+e?zσ(z)=11+e?z

其函數圖像為:
技術分享圖片

由函數圖像可知,sigmoid函數有以下幾個很好的性質:
* 當zz趨近於正無窮大時,σ(z)=1σ(z)=1;
* 當zz趨近於負無窮大時,σ(z)=0σ(z)=0;
* 當z=0z=0時,σ(z)=0.5σ(z)=0.5。

所以可以用sigmoid函數來約束y^y^的值域,此時:

y^=σ(wTX+b)=11+e?(wTX+b)y^=σ(wTX+b)=11+e?(wTX+b)

成本函數

為了訓練邏輯回歸模型中的參數w和b,使得輸出值y^y^與真實值y盡可能一致,即盡可能準確地判斷一張圖是否為貓,需要定義一個成本函數(Cost Function)作為衡量的標準。

用損失函數(Loss Function)來衡量單個樣本預測值(y^(i)y^(i))與真實值(y(i)y(i))之間的差異,換句話說,損失函數用來計算單個訓練樣本的預測值與真實值之間的誤差。平方誤差(Square Loss)是一種常用的損失函數:

L(y^,y)=12(y^?y)2L(y^,y)=12(y^?y)2

但在邏輯回歸中一般不使用這個損失函數,因為在訓練參數過程中,使用這個損失函數將得到一個非凸函數,最終將存在很多局部最優解,這種情況下使用梯度下降(Gradient Descent)將無法獲得最優解。對邏輯回歸模型,希望滿足條件概率:

p(y|x)={y^, 1?y^,(y=1)(y=0)p(y|x)={y^,(y=1) 1?y^,(y=0)

將上下兩個式子合二為一,可寫成:

p(y|x)=y^y(1?y^)(1?y)p(y|x)=y^y(1?y^)(1?y)

對兩邊取對數,進一步化簡為:

log p(y|x)=ylog y^+(1?y)log (1?y^)log p(y|x)=ylog y^+(1?y)log (1?y^)

p(y|x)p(y|x)的值需要最大化,而損失函數需要最小化,所以在原來的式子中填上負號,就可以將它作為一個損失函數。在上式右邊添加一個負號,就推導出了應用很廣的交叉熵(Cross Entropy)損失函數,表達式為:

L(y^,y)=?(ylog y^+(1?y)log (1?y^))L(y^,y)=?(ylog y^+(1?y)log (1?y^))

交叉熵損失函數有如下性質:
* 當y(i)=1y(i)=1時,L(y^(i),y(i))=?log y^(i)L(y^(i),y(i))=?log y^(i)
* 當y(i)=0y(i)=0時,L(y^(i),y(i))=?log (1?y^(i))L(y^(i),y(i))=?log (1?y^(i))

對m個訓練樣本整體的成本函數,可以使用數理統計中的極大似然估計法(Maximum Likelihood Estimation)推導出來的。

假設所有訓練樣本獨立同分布,則聯合概率為所有樣本概率的乘積:

P=∏i=1mp(y(i)|x(i))P=∏i=1mp(y(i)|x(i))

對上式進行極大似然估計,還是兩邊取對數得:

log P=∑i=1mlog p(y(i)|x(i))=?∑i=1mL(y^(i),y(i))log P=∑i=1mlog p(y(i)|x(i))=?∑i=1mL(y^(i),y(i))

對上式再取平均值,得成本函數為:

J(w,b)=1m∑i=1mL(y^(i),y(i))J(w,b)=1m∑i=1mL(y^(i),y(i))

梯度下降

想找到使成本函數的值最小的參數w和b的值,一般采用梯度下降法。

標量場中某一點上的梯度指向標量場增長最快的方向,梯度的長度是這個最大的變化率。
技術分享圖片

在空間坐標中以w,b為軸畫出損失函數J(w,b)的三維圖像,可知這個函數為一個凸函數。為了找到合適的參數,先將w和b賦一個初始值,正如圖中的小紅點。

在邏輯回歸中,幾乎任何初始化方法都有效,通常將參數初始化為零。隨機初始化也起作用,但通常不會在邏輯回歸中這樣做,因為這個成本函數是凸的,無論初始化的值是多少,總會到達同一個點或大致相同的點。梯度下降就是從起始點開始,試圖在最陡峭的下降方向下坡,以便盡可能快地下坡到達最低點,這個下坡的方向便是此點的梯度值。
技術分享圖片
在二維圖像中來看,順著導數的方向,下降速度最快,用數學公式表達即是:

w:=w?α?J(w,b)?ww:=w?α?J(w,b)?w

b:=b?α?J(w,b)?bb:=b?α?J(w,b)?b

其中的“:=”意思為賦值,αα為學習率,通常為一個小於1的數,用來控制梯度下降過程中每一次移動的規格,相當於邁的步子大小。αα的不宜太小也不宜過大:太小會使叠代次數增加,容易陷入局部最優解;太大容易錯過最優解。

Python實現

使用Python編寫一個邏輯回歸分類器來識別貓,以此來了解如何使用神經網絡的思維方式來進行這項任務,識別過程如圖:
技術分享圖片

實現過程

其中用到的Python包有:
* numpy是使用Python進行科學計算的基礎包。
* matplotlib是Python中著名的繪圖庫。
* h5py在Python提供讀取HDF5二進制數據格式文件的接口,本次的訓練及測試圖片集是以HDF5儲存的。
* PIL(Python Image Library)為Python提供圖像處理功能。
* scipy基於NumPy來做高等數學、信號處理、優化、統計和許多其它科學任務的拓展庫。
幾個Python包的安裝及基本使用方法詳見官網。

1.導入要用到的所有包

#導入用到的包
import numpy as np
import matplotlib.pyplot as plt
import h5py
import scipy
from PIL import Image
from scipy import ndimage

2.導入數據

#導入數據
def load_dataset():
    train_dataset = h5py.File("train_cat.h5","r") #讀取訓練數據,共209張圖片
    test_dataset = h5py.File("test_cat.h5", "r") #讀取測試數據,共50張圖片

    train_set_x_orig = np.array(train_dataset["train_set_x"][:]) #原始訓練集(209*64*64*3)
    train_set_y_orig = np.array(train_dataset["train_set_y"][:]) #原始訓練集的標簽集(y=0非貓,y=1是貓)(209*1)

    test_set_x_orig = np.array(test_dataset["test_set_x"][:]) #原始測試集(50*64*64*3
    test_set_y_orig = np.array(test_dataset["test_set_y"][:]) #原始測試集的標簽集(y=0非貓,y=1是貓)(50*1)

    train_set_y_orig = train_set_y_orig.reshape((1,train_set_y_orig.shape[0])) #原始訓練集的標簽集設為(1*209)
    test_set_y_orig = test_set_y_orig.reshape((1,test_set_y_orig.shape[0])) #原始測試集的標簽集設為(1*50)

    classes = np.array(test_dataset["list_classes"][:])
    return train_set_x_orig, train_set_y_orig, test_set_x_orig, test_set_y_orig, classes

需要說明的是,本次的訓練及測試圖片集是以HDF5格式儲存的,train_cat.h5、test_cat.h5文件打開後結構如下:

技術分享圖片

另外,也可以調用以下方法來查看訓練集或測試集中的圖片:

#顯示圖片
def image_show(index,dataset):
    index = index
    if dataset == "train":
        plt.imshow(train_set_x_orig[index])
        print ("y = " + str(train_set_y[:, index]) + ", 它是一張" + classes[np.squeeze(train_set_y[:, index])].decode("utf-8") +  "‘ 圖片。")
    elif dataset == "test":
        plt.imshow(test_set_x_orig[index])
        print ("y = " + str(test_set_y[:, index]) + ", 它是一張" + classes[np.squeeze(test_set_y[:, index])].decode("utf-8") +  "‘ 圖片。")

3.sigmoid函數

#sigmoid函數
def sigmoid(z):
    s = 1/(1+np.exp(-z))
    return s

4.初始化參數w,b

#初始化參數w,b
def initialize_with_zeros(dim):
    w = np.zeros((dim,1)) #w為一個dim*1矩陣
    b = 0    
    return w, b

5.計算Y_hat,成本函數J以及dw,db

#計算Y_hat,成本函數J以及dw,db
def propagate(w, b, X, Y):
    m = X.shape[1] #樣本個數
    Y_hat = sigmoid(np.dot(w.T,X)+b)                                     
    cost = -(np.sum(np.dot(Y,np.log(Y_hat).T)+np.dot((1-Y),np.log(1-Y_hat).T)))/m #成本函數

    dw = (np.dot(X,(Y_hat-Y).T))/m
    db = (np.sum(Y_hat-Y))/m

    cost = np.squeeze(cost) #壓縮維度    
    grads = {"dw": dw,
             "db": db} #梯度

    return grads, cost

6.梯度下降找出最優解

#梯度下降找出最優解
def optimize(w, b, X, Y, num_iterations, learning_rate, print_cost = False):#num_iterations-梯度下降次數 learning_rate-學習率,即參數ɑ
    costs = [] #記錄成本值

    for i in range(num_iterations): #循環進行梯度下降
        grads, cost = propagate(w,b,X,Y)
        dw = grads["dw"]
        db = grads["db"]

        w = w - learning_rate*dw
        b = b - learning_rate*db

        if i % 100 == 0: #每100次記錄一次成本值
            costs.append(cost)

        if print_cost and i % 100 == 0: #打印成本值
            print ("循環%i次後的成本值: %f" %(i, cost))

    params = {"w": w,
              "b": b} #最終參數值

    grads = {"dw": dw,
             "db": db}#最終梯度值

    return params, grads, costs

7.得出預測結果

#預測出結果
def predict(w, b, X):
    m = X.shape[1] #樣本個數
    Y_prediction = np.zeros((1,m)) #初始化預測輸出
    w = w.reshape(X.shape[0], 1) #轉置參數向量w

    Y_hat = sigmoid(np.dot(w.T,X)+b) #最終得到的參數代入方程

    for i in range(Y_hat.shape[1]):
        if Y_hat[:,i]>0.5:
            Y_prediction[:,i] = 1
        else:
            Y_prediction[:,i] = 0

    return Y_prediction

8.建立整個預測模型

#建立整個預測模型
def model(X_train, Y_train, X_test, Y_test, num_iterations = 2000, learning_rate = 0.5, print_cost = False): #num_iterations-梯度下降次數 learning_rate-學習率,即參數ɑ
    w, b = initialize_with_zeros(X_train.shape[0]) #初始化參數w,b

    parameters, grads, costs = optimize(w, b, X_train, Y_train, num_iterations, learning_rate, print_cost) #梯度下降找到最優參數

    w = parameters["w"]
    b = parameters["b"]

    Y_prediction_train = predict(w, b, X_train) #訓練集的預測結果
    Y_prediction_test = predict(w, b, X_test) #測試集的預測結果

    train_accuracy = 100 - np.mean(np.abs(Y_prediction_train - Y_train)) * 100 #訓練集識別準確度
    test_accuracy = 100 - np.mean(np.abs(Y_prediction_test - Y_test)) * 100 #測試集識別準確度

    print("訓練集識別準確度: {} %".format(train_accuracy))
    print("測試集識別準確度: {} %".format(test_accuracy))

    d = {"costs": costs,
         "Y_prediction_test": Y_prediction_test,
         "Y_prediction_train" : Y_prediction_train,
         "w" : w,
         "b" : b,
         "learning_rate" : learning_rate,
         "num_iterations": num_iterations}

    return d

9.初始化樣本,輸入模型,得出結果

#初始化數據
train_set_x_orig, train_set_y, test_set_x_orig, test_set_y, classes = load_dataset()

m_train = train_set_x_orig.shape[0] #訓練集中樣本個數
m_test = test_set_x_orig.shape[0] #測試集總樣本個數
num_px = test_set_x_orig.shape[1] #圖片的像素大小

train_set_x_flatten = train_set_x_orig.reshape(train_set_x_orig.shape[0],-1).T #原始訓練集的設為(12288*209)
test_set_x_flatten = test_set_x_orig.reshape(test_set_x_orig.shape[0],-1).T #原始測試集設為(12288*50)

train_set_x = train_set_x_flatten/255. #將訓練集矩陣標準化
test_set_x = test_set_x_flatten/255. #將測試集矩陣標準化

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

結果分析

運行程序最終得到的結果為:
技術分享圖片
訓練集識別準確率接近100%,測試集的準確率有70%。由於訓練使用的小數據集,而且邏輯回歸是線性分類器,所以這個結果對於這個簡單的模型實際上還是不錯。

使用mathplotlib畫出學習曲線:

# 畫出學習曲線
costs = np.squeeze(d[‘costs‘])
plt.plot(costs)
plt.ylabel(‘cost‘)
plt.xlabel(‘iterations (per hundreds)‘)
plt.title("Learning rate =" + str(d["learning_rate"]))
plt.show()

技術分享圖片

學習率不同時的學習曲線:

learning_rates = [0.01, 0.001, 0.0001]
models = {}
for i in learning_rates:
    print ("學習率: " + str(i))
    models[str(i)] = model(train_set_x, train_set_y, test_set_x, test_set_y, num_iterations = 1500, learning_rate = i, print_cost = False)
    print (‘\n‘ + "-------------------------------------------------------" + ‘\n‘)

for i in learning_rates:
    plt.plot(np.squeeze(models[str(i)]["costs"]), label= str(models[str(i)]["learning_rate"]))

plt.ylabel(‘cost‘)
plt.xlabel(‘iterations‘)

legend = plt.legend(loc=‘upper center‘, shadow=True)
frame = legend.get_frame()
frame.set_facecolor(‘0.90‘)
plt.show()

技術分享圖片
說明不同的學習率會帶來不同的成本,從而產生不同的預測結果。

參考資料

    1. 吳恩達-神經網絡與深度學習-網易雲課堂
    2. Andrew Ng-Neural Networks and Deep Learning-Coursera
    3. deeplearning.ai
    4. 課程代碼與資料-GitHub

深度學習:邏輯回歸