邏輯回歸可以看做是被sigmoid進行歸一化的線性函數。
這裡首先規定,向量x是分類器的輸入數據,向量 就是我們要找到的最佳參數。
注意:多分類的預測函數為:(softmax函數)
損失函數為:
利用極大似然函數進行求解,對應似然函數為:
對數似然函數為:
下面參數更新的求解就是在原有的對數似然函數上乘上了-1/m,由於是負數,所以也由梯度上升轉換到了梯度下降),對數似然函數乘上了-1/m之後,對這個新的式子求偏導。
計算過程:
y=1的概率為:
這裡θ是模型參數,也就是回歸係數,σ是sigmoid函數。實際上這個函數是由下面的對數幾率(也就是x屬於正類的可能性和負類的可能性的比值的對數)變換得到的:
所以說上面的logistic回歸就是一個線性分類模型,它與線性回歸的不同點在於:為了將線性回歸輸出的很大範圍的數,例如從負無窮到正無窮,壓縮到0和1之間。
似然函數:
對數似然函數:
對對數似然函數進行求導,看導數為0的時候可不可以解出來,也就是有沒有解析解
然後我們令該導數為0,你會很失望的發現,它無法解析求解。藉助迭代進行求最優解。
梯度下降(gradient descent)
Gradient descent 又叫 steepest descent,是利用一階的梯度信息找到函數局部最優解的一種方法,也是機器學習裡面最簡單最常用的一種優化方法。它的思想很簡單,和我開篇說的那樣,要找最小值,只需要每一步都往下走(也就是每一步都可以讓代價函數小一點),然後不斷的走,那肯定能走到最小值的地方。
而這個下坡最快的方向,就是梯度的負方向了。
利用梯度下降法求參數
梯度的負方向就是代價函數下降最快的方向,藉助泰勒展開,可以得到(函數可微,可導)
其中,f(x) 和δ為向量,那麼這兩者的內積就等於
當θ=π時,也就是在δ與f(x)的方向相反時,取得最小值, 也就是下降的最快的方向了
這裡也就是: f(x+δ) - f(x) = - ||δ||·||f(x)||。
其中,wj表示第j個特徵的權重,η為學習率,用來控制步長。
對損失函數J(θ)中的θ的第j個權重求偏導。
所以,在使用梯度下降法更新權重時,根據以下公式進行參數更新:
梯度下降演算法的偽代碼如下:
################################################
初始化回歸係數為1
重複下面步驟直到收斂{
計算整個數據集的梯度
使用alpha x gradient來更新回歸係數
}
返回回歸係數值
2.2、隨機梯度下降SGD (stochastic gradient descent)
梯度下降演算法在每次更新回歸係數的時候都需要遍歷整個數據集(計算整個數據集的回歸誤差),該方法對小數據集尚可。但當遇到有數十億樣本和成千上萬的特徵時,就有點力不從心了,它的計算複雜度太高。改進的方法是一次僅用一個樣本點(的回歸誤差)來更新回歸係數。這個方法叫隨機梯度下降演算法。由於可以在新的樣本到來的時候對分類器進行增量的更新(假設我們已經在資料庫A上訓練好一個分類器h了,那新來一個樣本x。對非增量學習演算法來說,我們需要把x和資料庫A混在一起,組成新的資料庫B,再重新訓練新的分類器。但對增量學習演算法,我們只需要用新樣本x來更新已有分類器h的參數即可),所以它屬於在線學習演算法。與在線學習相對應,一次處理整個數據集的叫「批處理」。
隨機梯度下降演算法的偽代碼如下:
對數據集中每個樣本
計算該樣本的梯度
2.3、改進的隨機梯度下降
對隨機梯度下降演算法,我們做兩處改進來避免上述的波動問題:
1)在每次迭代時,調整更新步長alpha的值。隨著迭代的進行,alpha越來越小,這會緩解係數的高頻波動(也就是每次迭代係數改變得太大,跳的跨度太大)。當然了,為了避免alpha隨著迭代不斷減小到接近於0(這時候,係數幾乎沒有調整,那麼迭代也沒有意義了),我們約束alpha一定大於一個稍微大點的常數項。
2)每次迭代,改變樣本的優化順序。也就是隨機選擇樣本來更新回歸係數。這樣做可以減少周期性的波動,因為樣本順序的改變,使得每次迭代不再形成周期性。
改進的隨機梯度下降演算法的偽代碼如下:
對隨機遍歷的數據集中的每個樣本
隨著迭代的逐漸進行,減小alpha的值
3. 優缺點
(1)、優點
1. 形式簡單,模型的可解釋性非常好。從特徵的權重可以看到不同的特徵對最後結果的影響,某個特徵的權重值比較高,那麼這個特徵最後對結果的影響會比較大;
2. 模型效果不錯。在工程上是可以接受的(作為baseline),如果特徵工程做的好,效果不會太差,並且特徵工程可以大家並行開發,大大加快開發的速度;
3. 訓練速度較快。分類的時候,計算量僅僅只和特徵的數目相關。並且邏輯回歸的分散式優化sgd發展比較成熟,訓練的速度可以通過堆機器進一步提高,這樣我們可以在短時間內迭代好幾個版本的模型;
4. 資源佔用小,尤其是內存。因為只需要存儲各個維度的特徵值;
5. 方便輸出結果調整。邏輯回歸可以很方便的得到最後的分類結果,因為輸出的是每個樣本的概率分數,我們可以很容易的對這些概率分數進行cutoff,也就是劃分閾值(大於某個閾值的是一類,小於某個閾值的是一類)。
(2)、缺點
1. 準確率並不是很高。因為形式非常的簡單(非常類似線性模型),很難去擬合數據的真實分布;
2. 很難處理數據不平衡的問題。舉個例子:如果我們對於一個正負樣本非常不平衡的問題比如正負樣本比 10000:1.我們把所有樣本都預測為正也能使損失函數的值比較小。但是作為一個分類器,它對正負樣本的區分能力不會很好;
3. 處理非線性數據較麻煩。邏輯回歸在不引入其他方法的情況下,只能處理線性可分的數據,或者進一步說,處理二分類的問題;
4. 邏輯回歸本身無法篩選特徵。有時候,我們會用gbdt來篩選特徵,然後再上邏輯回歸。
4. 實例
(1)導入數據
import numpy as np import pandas as pd from sklearn import preprocessing import matplotlib.pyplot as plt plt.rc("font",size=14) import seaborn as sns sns.set(stylex="white") sns.set(stylex="whitegrid",color_codes=True) df=pd.read_csv("D:/Users/jl.qiao/Desktop/網上數據文件夾/titanic/titanic(1).csv") print(df.head()) print("數據集包含的數據個數%d"%df.shape[0])
得到:
前5行數據: pclass survived ... cabin embarked0 1 1 ... B5 S1 1 1 ... C22 C26 S2 1 0 ... C22 C26 S3 1 0 ... C22 C26 S4 1 0 ... C22 C26 S數據的行數:數據集包含的數據個數1309
前5行數據:
(2)數據分析
1.查看數據的確實情況
print(df.isnull().sum())
pclass 0survived 0name 0sex 0age 263sibsp 0parch 0ticket 0fare 1cabin 1014embarked 2dtype: int64
pclass 0
可以看出在總數據1309中,age、fare、cabin 、embarked 4個特徵存在缺失情況。
2. 特徵age的數據分布
#年齡數據缺失的百分比 print("age" 缺失的百分比 %.2f%% %((df[age].isnull().sum()/df.shape[0])*100))
"age" 缺失的百分比 20.09%
#通過直方圖看不同年齡的分布情況 ax=df[age].hist(bins=15,color=teal,alpha=0.6) ax.set(xlabel=age) ax.set(ylabel=number) ax.set(title=age_number hist) plt.xlim(-10,85) #限定x軸的取值範圍 plt.show()
#年齡的均值和中位數 print(The mean of Age is %.2f%(df[age].mean(skipna=True))) print(The median of Age is %.2f%(df[age].median(skipna=True))) #skipna=False表示有缺失值不可加總,=True表示可以存在缺失值
The mean of Age is 29.88The median of Age is 28.00
The mean of Age is 29.88
結論:由於「年齡」的偏度不為0,即數據是有偏的, 使用均值替代缺失值不是最佳選擇, 這裡可以選擇使用中位數替代缺失值。
3. 倉位
#缺失的百分比 print("cabin"缺失的百分比 %.2f%%%((df[cabin].isnull().sum()/df.shape[0])*100))
結論:約 77% 的乘客的倉位都是缺失的, 最佳的選擇是不使用這個特徵的值.
4. 登船地點
#缺失百分比 print("embarked"缺失的百分比 %.2f%%%((df[embarked].isnull().sum()/df.shape[0])*100)) print(按照登船地點分組(C=Cherbourg,Q=Queenstown,S=Southampton)) print(df[embarked].value_counts()) #類別計數
"embarked"缺失的百分比 0.15%按照登船地點分組(C=Cherbourg,Q=Queenstown,S=Southampton:S 914C 270Q 123
"embarked"缺失的百分比 0.15%
sns.countplot(x=embarked,data=df,palette=Set2) #條形圖,palette是調色板 plt.show()
乘客登船最多的港口位於:
print(乘客登船地點的眾數為%s%df[embarked].value_counts().idxmax())
乘客登船地點的眾數為:S
結論:由於登船地點數據缺失較少,且大多數人是在南安普頓(Southhampton)登船, 故可以使用「S」替代缺失的數據值。
5. 船票的費用
from scipy import stats print("fare"缺失的百分比 %.2f%%%((df[fare].isnull().sum()/df.shape[0])*100)) print(df[fare].min(),df[fare].max()) #船票費用的最大值和最小值
"fare"缺失的百分比 0.08%0.0 512.3292
"fare"缺失的百分比 0.08%
#分布直方圖 ax=df[fare].hist(bins=15, color=yellow, alpha=0.5) ax.set(xlabel=fare) plt.xlim(-10,600) plt.show()
#船票費用的均值和中位數 print(The mean of fare is %.2f%(df[fare].mean(skipna=True))) print(The median of fare is %.2f%(df[fare].median(skipna=True))) print(The mode of fare is %.2f%(stats.mode(df[fare])[0][0]))
得到:The mean of fare is 33.30The median of fare is 14.45The mode of fare is 8.05
結論:從數據可以看出,船票費用呈長尾分布,且數據缺失個數僅為1個,故可以用眾數進行替代
(3)根據缺失情況調整數據
根據以上分析的情況,對缺失數據進行填充或刪除
data=df.copy() data[age].fillna(df[age].median(skipna=True),inplace=True) data[embarked].fillna(df[embarked].value_counts().idxmax(),inplace=True) data.drop(cabin,axis=1,inplace=True) data[fare].fillna(stats.mode(df[fare])[0][0],inplace=True) print(data.isnull().sum()) print(data.head())
得到:pclass 0survived 0name 0sex 0age 0sibsp 0parch 0ticket 0fare 0embarked 0dtype: int64 pclass survived ... fare embarked0 1 1 ... 211.3375 S1 1 1 ... 151.5500 S2 1 0 ... 151.5500 S3 1 0 ... 151.5500 S4 1 0 ... 151.5500 S
可以看到,經過處理後的數據已經沒有缺失值出現。
(4) 特徵處理
#數據中的兩個特徵 「sibsp」 (一同登船的兄弟姐妹或者配偶數量)與「parch」(一同登船的父母或子女數量)都是代表是否有同伴同行. 為了預防這兩個逼啊了可能的多重共線性, 我們可以將這兩個變數轉為一個變數 「TravelAlone」 (是否獨自一人成行) data[TravelAlone]=np.where((data[sibsp]+data[parch])>0,0,1) data.drop(sibsp,axis=1,inplace=True) data.drop(parch,axis=1,inplace=True) print(data.head())
# 對 "embarked","sex"進行獨熱編碼, 丟棄 name, ticket final=pd.get_dummies(data,columns=[embarked,sex]) final.drop(name,axis=1,inplace=True) final.drop(ticket,axis=1,inplace=True) print(final.head()) print(final.columns.values.tolist()) #獲取dataframe的列名
pclass survived age ... embarked_S sex_female sex_male0 1 1 29.0000 ... 1 1 01 1 1 0.9167 ... 1 0 12 1 0 2.0000 ... 1 1 03 1 0 30.0000 ... 1 0 14 1 0 25.0000 ... 1 1 0[pclass, survived, age, fare, TravelAlone, embarked_C, embarked_Q, embarked_S, sex_female, sex_male]
pclass survived age ... embarked_S sex_female sex_male
(5)各特徵的數據分析
1.年齡
plt.figure(figsize=(15,8)) ax=sns.kdeplot(final[age][final.survived==1],color=darkturquoise,shade=True) sns.kdeplot(final[age][final.survived==0],color=lightcoral,shade=True) plt.legend([Survived,Died]) plt.title(Density Plot of age for surviving population and deceased population) ax.set(xlabel=Age) plt.xlim(-10,85) plt.show()
結論:生還與遇難群體的分布相似, 唯一大的區別是生還群體中有一部分低年齡的乘客. 說明當時的人預先保留了孩子的生還機會.
2.票價
plt.figure(figsize=(15,8)) #核密度估計圖 ax=sns.kdeplot(final[fare][final.survived==1],color=darkturquoise,shade=True) sns.kdeplot(final[fare][final.survived==0],color=lightcoral,shade=True) plt.legend([Survived,Died]) plt.title(Density Plot of fare for surviving population and deceased population) ax.set(xlabel=Fare) plt.xlim(-20,200) plt.show()
結論:生還與遇難群體的票價分布差異比較大, 說明這個特徵對預測乘客是否生還非常重要. 票價和倉位相關, 也許是倉位影響了逃生的效果, 我們接下來看倉位的分析.
3.倉位
sns.barplot(x=pclass,y=survived,data=df,color=darkturquoise) #箱形圖 plt.show()
結論:一等艙的乘客生還幾率最高。
sns.barplot(embarked, survived, data=df, color="teal") plt.show()
結論:法國 Cherbourge 登錄的乘客生還率最高。
5.是否獨立旅行
sns.barplot(TravelAlone, survived, data=final, color="mediumturquoise") plt.show()
結論:女性的生還率比較高。
(6)利用logistic進行預測
from sklearn.linear_model import LinearRegression from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score from sklearn.metrics import recall_score,precision_score,f1_score from sklearn.metrics import roc_auc_score #使用一下特徵進行預測 col=[age,fare,TravelAlone,pclass,embarked_C,embarked_S,sex_male] #創建x和y x=final[col] y=final[survived] #劃分訓練集和測試集 x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.2,random_state=40) #利用logistic回歸進行訓練 logi=LinearRegression() logi.fit(x_train,y_train) #利用訓練模型進行預測 y_pred=logi.predict(x_test) #y_pred為預測概率,需要轉換成類別,一般>0.5為1 y_pred=np.where(y_pred>0.5,1,0) print(準確率為 %2.3f%accuracy_score(y_test,y_pred)) print(auc為 %2.3f%roc_auc_score(y_test,y_pred)) #準確率、召回率和F1score print(recall_score(y_test,y_pred),precision_score(y_test,y_pred),f1_score(y_test,y_pred))
準確率為 0.760auc為 0.743以下分別是召回率、查准率和f1score0.6571428571428571 0.71875 0.6865671641791045
準確率為 0.760
推薦閱讀: