邏輯迴歸--理論與python實例

一、邏輯迴歸介紹

邏輯迴歸又稱logistic迴歸分析,是一種廣義的線性迴歸分析模型,常用於數據挖掘,疾病自動診斷,經濟預測等領域。以胃癌病情分析爲例,選擇兩組人羣,一組是胃癌組,一組是非胃癌組,兩組人羣必定具有不同的體徵與生活方式等。因此因變量就爲是否胃癌,值爲“是”或“否”,自變量就可以包括很多了,如年齡、性別、飲食習慣、幽門螺桿菌感染等。自變量既可以是連續的,也可以是分類的。然後通過logistic迴歸分析,可以得到自變量的權重,從而可以大致瞭解到底哪些因素是胃癌的危險因素。同時根據該權值可以根據危險因素預測一個人患癌症的可能性。(摘自百度百科)

Logistic迴歸常用於預測離散型目標變量,經典的二分類算法,(也可做多分類算法,這篇先講邏輯迴歸二分類算法。)它的優點:穩定,應用廣泛;解釋性強,易於理解。缺點:容易欠擬合,分類精度可能不高。如果拿到一個分類任務,我們可以先用邏輯迴歸做個基礎模型,做好後,再用其他算法優化。另外邏輯迴歸的決策邊界是可以非線性的。 

二、Sigmoid函數

爲了實現Logistic迴歸分類器,我們可以將每個特徵乘上一個迴歸係數再相加得到一個線性迴歸預測值(z=w_0x_0+w_1x_1+...+w_nx_n),而這個預測值是(- \infty,+\infty)的實數,那麼如何將這個預測值映射出我們想要的分類結果呢?這就連想到Sigmoid函數,公式:\sigma (z)=\frac{1}{1+e^-^z}

由上圖兩個都是sigmoid函數,其自變量z取值爲任意實數,值域都在[0,1]之間。這不就是我們想要的嗎?我們在線性迴歸中得到的預測值映射到sigmoid函數,得到由值到概率的轉換,得到的概率大於0.5的歸爲一類,小於0.5的歸爲一類。所以,Logistic迴歸也可看成是一種概率估計。下圖座標尺度足夠大,可以看到在x=0處,Sigmoid函數看起來很想階躍函數。(海維塞德階躍函數:函數在跳躍點上從0瞬間跳躍到1)

三、最佳迴歸係數公式推導

可見邏輯迴歸就是由線性迴歸公式代入到Sigmoid函數中,將預測值轉變成概率。我們回顧一下線性迴歸公式:(https://blog.csdn.net/weixin_41712808/article/details/85168389

①式:z=w_0x_0+w_1x_1+...+w_nx_n=\theta ^Tx

②式:\sigma (z)=\frac{1}{1+e^-^z}

將①式代入②式,得③:h_\theta (x)=g(\theta ^Tx)=\frac{1}{1+e^{-\theta ^{T}x}}

對於二分類任務,目標變量是隻有0和1,如判斷是否患胃癌,1代表“是”,0代表“否”。我們設目標變量爲y,當y=1時,概率值公式等於h_\theta (x),那麼y=0時,概率值公式等於1-h_\theta (x),爲了公式方便,我們可以整合如下:

\left.\begin{matrix} P(y=1|x;\theta ) =h_\theta (x) & \\ P(y=0|x;\theta ) =1-h_\theta (x) & \end{matrix}\right\} P(y|x;\theta)=(h_\theta(x))^y(1-h_\theta(x))^{1-y}  —— ④式

雖然邏輯迴歸結合了sigmoid函數,但和迴歸分析目的一樣都是求出迴歸係數。如何求迴歸係數,我們首先想到的是似然函數和對數似然。(似然表示的是在給定樣本X=x_0時,參數\theta是多少。舉個例子:有個籃球隊拿到一個球,如果給小a投球,投中概率30%,給小b投球,投中概率80%。那麼選誰投中籃球概率更高呢?答案很簡單,設x是投中或未投中,小a,小b就是參數,參數等於多少時,x="投中"概率最大。)

似然函數:L(\theta )=\prod_{i=1}^{m}P(y_i|x_i;\theta)=\prod_{i=1}^{m}(h_\theta(x_i) )^{y_i}(1-h_\theta(x_i))^{1-y_i}

對數似然:l(\theta)=logL(\theta)=\sum_{i=1}^{m}(y_i*logh_\theta(x_i)+(1-y_i)*log(1-h_\theta(x_i)))   —— ⑤式

似然函數的累乘\prod是比較難計算的,但如果我們在前面加一個log對數,不就可以把累乘變成累加了嗎。

似然函數求得是最大值,我們可以用最優化算法——梯度上升法,當然也可以用梯度下降。使用梯度下降前,我們需要進行一個轉換:J(\theta )=-\frac{1}{m}l(\theta )   ——⑥式  (\frac{1}{m}:樣本個數m)  

以下是⑥式對\theta求導過程:

J(\theta )=-\frac{1}{m}l(\theta )=-\frac{1}{m}\sum_{i=1}^{m}(y_i*logh_\theta(x_i)+(1-y_i)*log(1-h_\theta(x_i)))

\frac{\delta }{\delta_{\theta _j} }J(\theta )=-\frac{1}{m}\sum_{i=1}^{m}\begin{bmatrix} y_i\frac{1}{h_\theta (x_i)}\frac{\delta}{\delta_{\theta _j}}h_\theta (x_i)-(1-y_i)\frac{1}{1-h_\theta (x_i)}\frac{\delta }{\delta_{\theta_j} }h_\theta(x_i) \end{bmatrix}

……此處省略n行化簡……

最終化簡得:{\color{Red} \frac{\delta }{\delta_{\theta _j}}J(\theta )=\frac{1}{m}\sum_{i=1}^{m}(h_\theta (x_i)-y_i)x_{i}^{j}}   (m:樣本數,i:第i個樣本,j:第j個參數,\theta:參數,\frac{\delta }{\delta _{\theta }}:對\theta求導,y:因變量,x_{i}^{j}:第i個樣本得第j個特徵得自變量)在做模型得時候,我們會有多個自變量,有多少個自變量就要求多少個\theta得導。

梯度下降是要對\theta不斷得迭代,從而找到最優的\theta

更新公式:{\color{Red} \theta _j=\theta _j-\alpha \frac{1}{m}\sum_{i=1}^{m}(h_\theta (x_i)-y_i)x_{i}^{j}}   (右邊的\theta _j是上一次迭代的\theta _j\alpha:步長,學習率,控制學習速度,最好0.01,\frac{1}{m}\sum_{i=1}^{m} (h_\theta (x_i)-y_i)x_{i}^{j}):方向)

拓展:上面適合二分類的,那如果遇到多分類則要用softmax函數,其實就是對我們求出的結果值進行歸化。這涉及到神經網絡,如果後續有機會可以詳細寫一下。

四、邏輯迴歸python應用

案例背景:假設你是一個大學系的管理員,你想根據兩次考試的結果來決定每個申請人的錄取機會。你有以前的申請人的歷史數據,你可以用它作爲邏輯迴歸的訓練集。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

import os

#導入數據
path=r'data\LogiReg_data.txt'
pdData=pd.read_csv(path,header=None,names=['Exam 1', 'Exam 2', 'Admitted'])
pdData.insert(0,'ones',1) #在最前列增加一列內容爲1的變量
print('pdData.shape:',pdData.shape)
pdData.head()

#作圖
positive=pdData[pdData['Admitted']==1]
negative=pdData[pdData['Admitted']==0]

fit,ax=plt.subplots(figsize=(10,5))
ax.scatter(positive['Exam 1'],positive['Exam 2'], s=30, c='b', marker='o', label='Admitted')
ax.scatter(negative['Exam 1'], negative['Exam 2'], s=30, c='r', marker='x', label='Not Admitted')
ax.legend()
ax.set_xlabel('Exam 1 Score')
ax.set_ylabel('Exam 2 Score')

接下來應該要數據預處理,但我們這裏主要講邏輯迴歸,那我們直接進入建模步驟。要完成的模塊(函數):

  • sigmoid:映射到概率的函數  \sigma (z)=\frac{1}{1+e^-^z}
  • model:返回預測結果值  (\theta _0 , \theta _1 , \theta _2)*\begin{pmatrix} 1\\ x_1\\ x_2 \end{pmatrix}=\theta _0+\theta _1x_1+\theta _2x_2
  • cost:損失函數,根據參數計算損失-\frac{1}{m}\sum_{i=1}^{m}(y_i*logh_\theta(x_i)+(1-y_i)*log(1-h_\theta(x_i)))損失函數就是上面提到的用梯度下降法轉換後的對數似然。詳細介紹該函數帖子:https://blog.csdn.net/shenxiaoming77/article/details/51614601
  • gradient:計算每個參數的梯度方向,就是梯度求導公式:\frac{\delta}{\delta_{\theta_i}}=-\frac{1}{m}\sum_{i=1}^{m}(y_i-h_\theta (x_i))x_{i}^{j}
  • stopCriterion:設定停止策率
  • descent:進行參數更新
  • accuracy:計算精度
#準備數據
orig_data=pdData.as_matrix()# 將數據的panda表示形式轉換爲一個數組,以便進一步計算
#假設theta都爲0
theta=np.zeros([1,3])

#建模塊
def sigmoid(z):
    return (1 / (1 + np.exp(-z)))
def model(X, theta):
    return (sigmoid(np.dot(X, theta.T)))
def cost(X, y, theta):
    left = np.multiply(-y, np.log(model(X, theta)))
    right = np.multiply(1 - y, np.log(1 - model(X, theta)))
    return (np.sum(left - right) / (len(X)))

#計算梯度即對每個theta求偏導,確定梯度下降的方向
def gradient(X, y, theta):
    grad = np.zeros(theta.shape) #定梯度
    error = (model(X, theta)- y).ravel()
    for j in range(len(theta.ravel())): #for each parmeter
        term = np.multiply(error, X[:,j])
        grad[0, j] = np.sum(term) / len(X)
    return (grad)

STOP_ITER = 0  #根據迭代次數更新theta
STOP_COST = 1  #上一次迭代目標函數和這次迭代的目標函數差異很小,損失值變化很小時停止迭代
STOP_GRAD = 2  #根據梯度,梯度變化很小時停止迭代
def stopCriterion(type, value, threshold):
    #設定三種不同的停止策略
    if type == STOP_ITER:        return (value > threshold)
    elif type == STOP_COST:      return (abs(value[-1]-value[-2]) < threshold)
    elif type == STOP_GRAD:      return (np.linalg.norm(value) < threshold)

#洗牌
def shuffleData(data):
    np.random.shuffle(data)
    cols = data.shape[1]
    X = data[:, 0:cols-1]
    y = data[:, cols-1:]
    return (X, y)

#batchSize:1:隨機梯度下降樣本數,stopType:停止策率;thresh:策率對應的域值,alpha:學習率,步長。
def descent(data, theta, batchSize, stopType, thresh, alpha):
    #數據初始化
    init_time = time.time()
    i = 0 # 迭代次數
    k = 0 # batch
    X, y = shuffleData(data)  #洗牌
    grad = np.zeros(theta.shape) # 計算的梯度
    costs = [cost(X, y, theta)] # 損失值

    while True:
        grad = gradient(X[k:k+batchSize], y[k:k+batchSize], theta)
        k += batchSize #取batch數量個數據
        if k >= n: 
            k = 0 
            X, y = shuffleData(data) #重新洗牌
        theta = theta - alpha*grad # 參數更新
        costs.append(cost(X, y, theta)) # 計算新的損失
        i += 1 

        if stopType == STOP_ITER:       value = i
        elif stopType == STOP_COST:     value = costs
        elif stopType == STOP_GRAD:     value = grad
        if stopCriterion(stopType, value, thresh): break
    
    return (theta, i-1, costs, grad, time.time() - init_time)

關於bathSize從總樣本集中選多少個訓練集:https://blog.csdn.net/wangmengmeng99/article/details/82460437

#作圖
def runExpe(data, theta, batchSize, stopType, thresh, alpha):
    #import pdb; pdb.set_trace();
    theta, iter, costs, grad, dur = descent(data, theta, batchSize, stopType, thresh, alpha)
    name = "Original" if (data[:,1]>2).sum() > 1 else "Scaled"
    name += " data - learning rate: {} - ".format(alpha)
    if batchSize==n: strDescType = "Gradient"
    elif batchSize==1:  strDescType = "Stochastic"
    else: strDescType = "Mini-batch ({})".format(batchSize)
    name += strDescType + " descent - Stop: "
    if stopType == STOP_ITER: strStop = "{} iterations".format(thresh)
    elif stopType == STOP_COST: strStop = "costs change < {}".format(thresh)
    else: strStop = "gradient norm < {}".format(thresh)
    name += strStop
    print ("***{}\nTheta: {} - Iter: {} - Last cost: {:03.2f} - Duration: {:03.2f}s".format(
        name, theta, iter, costs[-1], dur))
    fig, ax = plt.subplots(figsize=(12,4))
    ax.plot(np.arange(len(costs)), costs, 'r')
    ax.set_xlabel('Iterations')
    ax.set_ylabel('Cost')
    ax.set_title(name.upper() + ' - Error vs. Iteration')
    return (theta)

#設用STOP_ITER方式迭代,次數爲5000次,步長爲0.000001進行迭代
n=100
runExpe(orig_data, theta, n, STOP_ITER, thresh=5000, alpha=0.000001)

#用STOP_COST梯度下降,差異值在0.000001以下,步長爲0.001進行迭代
runExpe(orig_data, theta, n, STOP_COST, thresh=0.000001, alpha=0.001)

#設定閾值 0.05,差不多需要40 000次迭代
runExpe(orig_data, theta, n, STOP_GRAD, thresh=0.05, alpha=0.001)

#設定閾值,即預測估計大於0.5時認爲能被錄取,小於0.5時認爲不能被錄取
def predict(X, theta):
    return ([1 if x >= 0.5 else 0 for x in model(X, theta)])

#精度,預測值(是否被錄取)估計準確概率
scaled_X = scaled_data[:, :3]
y = scaled_data[:, 3]
predictions = predict(scaled_X, theta)
correct = [1 if ((a == 1 and b == 1) or (a == 0 and b == 0)) else 0 for (a, b) in zip(predictions, y)]
accuracy = (sum(map(int, correct)) % len(correct))
print ('accuracy = {0}%'.format(accuracy))

拓展:如果模型圖(損失值)是呈下圖所示:

有點爆炸,每次迭代不穩定,損失值忽上忽下,模型不會收斂,當發現這樣問題,我們可先從數據層面進行優化,再從模型層面優化。數據層面比如有無數據預處理,模型層面會不會是學習率太大,我們可以調小學習率,增加迭代次數。


end……

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章