LR演算法作為一種比較經典的分類演算法,在實際應用和麵試中經常受到青睞,雖然在理論方面不是特別複雜,但LR所牽涉的知識點還是比較多的,同時與概率生成模型、神經網路都有著一定的聯絡,本節就針對這一演算法及其所涉及的知識進行詳細的回顧。
LogisticRegression
0.前言
LR是一種經典的成熟演算法,在理論方面比較簡單,很多資料也有詳細的解釋和推導,但回過頭再看LR演算法會有很多全新的認識,本節就從LR的引入到原理推導以及其與神經網路的有何聯絡串聯起來,可以加深對這方面知識的理解。本節首先從概率生成模型引入LR,然後基於二分類推導LR的演算法過程,再介紹LR在多分類中的應用,最後介紹LR與神經網路的聯絡,並通過一些例項對LR進行實現。
1.LR簡介
LR迴歸是將樣本進行線性疊加之後,再通過sigmoid函式,來對樣本進行分類的過程,sigmoid函式如下:
其圖形如下:
可以看到當z>0時,通過sigmoid函式後,σ(z)>0.5,z<0時,σ(z)<0.5;
那麼LR的具體做法就是:
(1)找到一組引數w、b,求得樣本的線性疊加z=wx+b;
(2)然後將z通過sigmoid函式,如果>0.5,則屬於類別1,否則屬於類別2;
然後其訓練過程就是尋找引數w、b的過程,後面再說如何進行訓練。
2.從概率模型到LogisticRegression
前面有一節有關概率模型專題,詳見https://www.cnblogs.com/501731wyb/p/15149456.html,通過假設樣本分佈,利用最大似然估計來估計樣本分佈引數,從而求解給定一個樣本屬於C1和C2的概率,來進行分類的過程,記給定一個未知樣本X,屬於C1的概率為P(C1|X),那麼,根據貝葉斯公式:
對上面的公式變形,上下同除以分子,得到:
則有:
這裡注意z的“分子”和“分母”與原式中正好相反,因為有“-”號。
通過上面LR的簡介或者熟悉LR的形式,就可以看出這裡的所得到的概率形式與LR及其相似,不同的是,概率形式中的z與原LR的形式wx+b不太一樣。
僅僅上面的變形就說二者有關係稍微有點膚淺,那麼究竟一樣不一樣呢?我們繼續往下看:
根據概率模型那一章節,假設樣本C1、C2均服從高斯分佈,均值分別為μ1、μ2,方差分別為Σ1和Σ2,根據高斯分佈的概率密度函式,那麼帶入上面z中的P(X|C1)和P(X|C2),則有:
將上面的式子進行化簡併展開:
在概率模型中訓練過程中,我們假設兩個類別樣本分佈的方差Σ1和Σ2共用一個Σ,那麼上面的式子可以進一步化簡:
最終得到的z就是上面的式子,由於引數μ1、μ2、Σ是根據樣本估計出來的引數,可以看做已知的,那麼最終得到的z的形式如下:
那麼合併掉常數項,我們可以看到,z就變成了wx+b的形式,也就是說:
到這裡我們就可以清楚地理解了概率生成模型與LR之間的聯絡,但雖然形式一樣,但還是有區別的,到後面我們再進一步討論。
3.LogisticRegression演算法推導
根據上面的理論,我們希望通過訓練資料,找到一組引數w、b,然後能夠給定一個未知資料x,通過計算z=wx+b,在通過sigmoid函式,來對x進行正確預測。這裡我們為了便於表示,先假設一下σ(z)為函式的形式,即:
到這裡完成了機器學習的三步走中的第一步:找出模型的形式a set of model。
那麼在學習之前,我們需要找出一個衡量模型好壞的標準,即損失函式,到了第二步goodness of function:
我們希望所有的樣本都能被正確分類,即所有樣本累積概率最大,類比於概率生成模型,即:
使上面這個式子最大,注意,這裡x3為(1-f(x)),是因為上面我們所求的σ(z)=fw,b(x)是樣本x屬於Class1的概率,假設是二分類的情況,那麼x屬於Class2的概率為1-f(x)。
然後我們通過求加使得上式最大的一組w、b的引數就結束了,也就是:
那麼這個式子具體怎麼求呢?同樣用到梯度下降,但是上面直接求導不是很容易,要先對上面的式子進行一下轉化,令:
然後兩邊同取對數,並且由於梯度下降是求最小值,那麼兩邊同取負號:
那麼問題就變成了:
然而上面的式子還是有點不太直觀,根據二分類的特性,label值在屬於Class1時取1,在屬於Class2時取0,那麼:
於是,L'中的每一項我們都可以寫成一個通用的形式,成為了:
這就是LR的損失函式,也稱之為交叉熵CrossEntropy,不過上面是0~1分佈,形式上稍微有些不同,可以看成有兩個分佈,分別為:
那麼根據交叉熵的公式:
就得到了Lr的交叉損失函式。
接下來就是利用梯度下降對損失函式進行求解的過程了,也就是第三步:find the best function。
這個損失函式稍微有點複雜,涉及到鏈式求導法則,我們一塊一塊來求導數:
然後對兩塊複雜的地方進行求導:
然後再帶入上面的式子中進行整理:
帶上前面的加和,並帶上負號,規範一下:
對b的求導也是一樣的,不過在後面沒有乘以x罷了,那麼引數更新就是:
到這裡是不是發現一個問題,仔細觀察w的引數更新過程,對比Linear Regression的引數更新過程,可以看到引數的更新方程是一模一樣的。
那為什麼在LR中不能直接用均方誤差MSE作為損失函式呢?
這裡我們假設使用均方誤差函式作為損失函式,那麼損失函式為:
對w進行求導:
如果對於一個樣本,該樣本屬於類別2,那麼y=0,如果更新到一組引數w、b,計算得到fw,b(x)=1,那麼此時預測該樣本屬於類別1,但帶入梯度中發現此時梯度已經為0,引數將不再更新。
就像是下面這張圖:
均方誤差損失函式對於分類問題在距離Loss最小的地方也會很平坦,此時很可能陷入鞍點,無法繼續更新,導致最終誤差很大。
4.生成模型與判別模型
上面在從概率生成模型引入LR的過程中說到,雖然兩者形式上是一樣的,但其實還是有較大差別的。
根據二者的推導過程我們可以看到,在概率生成模型中,我們通過求解出樣本的分佈引數作為訓練過程,即概率生成模型首先估計樣本是個什麼樣的分佈,然後再對這個分佈的引數進行估計,換句話說,假設 我們確定了樣本所屬的分佈,那麼這個分佈裡的資料都可以是訓練資料,所多出來的資料並不影響最終的分佈,相當於生成模型自己“腦補”生成出來了一些額外的資料,這種方式稱之為生成模型。
而LR則是通過梯度下降的方式,直接求解出引數,這種方式稱之為判別模型,其始終按照真實資料進行訓練。
下面通過一個小例子來說明,假設有一組訓練資料如下:
資料有兩個維度,根據這樣一組資料,對測試資料進行預測:
那麼利用生成模型和判別模型分別預測的結果是什麼呢?
首先看生成模型,利用樸素貝葉斯進行計算:
可以看出,利用生成模型計算得到的X屬於Class2,而用判別模型因為X與唯一的屬於Class1的訓練樣本一模一樣,因此預測結果就是Class1。
從這裡我們可以看出,生成模型在訓練時會腦補出一些資料,可能出現一些訓練集從來沒出現的資料,比如(1,1)。
但通常由於判別模型解釋性強,我們更傾向於相信判別模型,而生成模型是以概率假設為基礎的,通常需要更少的訓練資料,並且所得到的結果更具有魯棒性。
5.多分類問題
上面所說的LogisticRegression是針對二分類問題的,對於多分類後面作為單獨的一節進行論述,這裡主要說與LR迴歸類似的多分類迴歸Softmax迴歸。
Softmax迴歸類似於LogisticRegression,將資料通過w、b線性疊加後,結合softmax函式,即可以實現將其表達為每種類別的概率的形式,softmax函式如下:
那麼上面softmax迴歸的過程描述如下:
假設樣本有3個類別C1、C2、C3,那麼:
然後計算得到的z1、z2、z3通過softmax函式,計算各個類別的概率,過程如下:
那麼softmax的訓練過程就可以類比LogisticRegression,過程如下:
這裡的交叉熵比LR的形式上更簡單,因為可以將類別y表示成三維:Class1:[1,0,0]、Class2:[0,1,0],Class3:[0,0,1]。具體過程就不再推導了。
6.LogisticRegression與神經網路聯絡
文章開頭提到,LR迴歸與神經網路有一定的聯絡,具體有著什麼樣的關係呢?
LR對於一組二維的資料,我們通過w、b的線性加權後,再通過sigmoid的函式將其進行分類:
那今天如果有下圖這樣一組資料,我們是否能夠找到一個線性模型,將這兩類資料分開呢?
如果單純直接對資料進行分類,可能線性模型將不能將其分開,這時,我們可能需要找一些方法,將資料進行轉換和重構,然後再進行分類。
那如果我將上面的二維資料進行轉換,兩個維度分別表示該點距離(0,0)的距離和距離(1,1)的距離,那麼左下角(0,0)的點可以轉化為:
x1=0;(0,0)距離(0,0)的距離:
x2=√2;(0,0)距離(1,1)的距離;
那麼點(0,0)就轉換成為了(0,√2),同理(1,1)轉換成為了(√2,0),(0,1)轉換成為了(1,1),(1,0)轉換成了(1,1),轉換後的資料如圖:
這樣轉換後就能通過一條直線將資料分開了,具體的轉換方法有很多,比如SVM中的核函式、或者專業領域知識。
上面的過程就是特徵轉換後再經分類器對樣本分類,假設上面這一組資料經過線性的加權,然後再經過sigmoid函式,對原始資料進行轉換,然後再作為LR的輸入在進行分類,其過程如圖所示:
舉個例子,比如w1=-2,w2=2,b=-1,四個點的都是二維的,每個維度轉換後的如圖所示:
然後再將x1',x2'作為輸入,利用LogisticRegression進行分類,如圖所示:
瞭解神經網路的應該到這裡就可以看出來,上面的結構就是神經網路的結構,前半部分是特徵提取部分,後半部分是分類的過程。如圖所示:
那麼裡邊涉及到的引數w1、w2、b以及LR中的引數w3、w4、b2在神經網路中通過訓練資料,可以一起被學習。
到這裡LR的理論部分已經介紹完畢了,主要將其與概率生成模型和神經網路聯絡起來,下面主要對Lr涉及的主函式進行實現一下,然後利用資料集,採用Sklearn自帶的LR方法進行實現。
7.LogisticRegression實現
資料來源於李宏毅老師的homework,資料下載地址:https://pan.baidu.com/s/1r07WmyRceBrXvEEKQ_3a-Q,提取碼:t7pp。
資料描述的是不同屬性人群的收入情況,feature是人物屬性,label為收入情況,首先要對資料集進行轉換,將非數值型資料都轉換為數值型,然後資料分為訓練集和測試集:
import numpy as npfrom sklearn.preprocessing import LabelEncoder import pandas as pd import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression data = pd.read_csv('./train.csv', encoding='utf-8') data = data[data['native_country'] != ' ?'] none_scalar = ['workclass', 'education', 'marital_status', 'occupation', 'relationship', 'race', 'sex', 'native_country'] label_encoder = LabelEncoder() income = label_encoder.fit_transform(np.array(list(data['income']))) data['income'] = income onehot_encoded_dic = {} for key in none_scalar: temp_data = np.array(list(data[key])) label_encoder = LabelEncoder() integer_encoded = label_encoder.fit_transform(temp_data) onehot_encoded_dic[key] = integer_encoded feature_list = data.columns.to_list() data_set = [] for i in range(len(data)): temp_vector = [] for j in range(len(feature_list)): if feature_list[j] in onehot_encoded_dic.keys(): temp_vector.extend(onehot_encoded_dic[feature_list[j]][i]) else: temp_vector.append(data.iloc[i, j]) data_set.append(temp_vector) data_set = np.array(data_set) np.random.shuffle(data_set) trainX, testX, trainY, testY = train_test_split(data_set[:, :-1], data_set[:, -1], test_size=0.3)
然後先定義兩個輔助函式,一個sigmoid函式,另一個是訓練過程中用於將資料打散,便於利用batch_size梯度下降演算法取資料:
def _shuffle(trainx, trainy): randomlist = np.arange(np.shape(trainx)[0]) np.random.shuffle(randomlist) return trainx[randomlist], trainy[randomlist] def sigmoid(z): res = 1/(1 + np.exp(-z)) return np.clip(res, 1e-8, (1-(1e-8)))
接下來就是訓練的主函式,因為lr演算法比較簡單,所以同訓練部分寫在一起:
def train(trainx, trainy): # 這裡將b也放進了w中,便於求解,只需要求解w的梯度即可 trainx = np.concatenate((np.ones((np.shape(trainx)[0], 1)), trainx), axis=1) epoch = 300 batch_size = 32 lr = 0.001 w = np.zeros((np.shape(trainx)[1])) step_num = int(np.floor(len(trainx)/batch_size)) cost_list = [] for i in range(1, epoch): train_x, train_y = _shuffle(trainx, trainy) total_loss = 0.0 for j in range(1, step_num): x = train_x[j*batch_size:(j+1)*batch_size, :] y = train_y[j*batch_size:(j+1)*batch_size] y_ = sigmoid(np.dot(x, w)) loss = np.squeeze(y_) - y cross_entropy = -1 * (np.dot(y, np.log(y_)) + np.dot((1-y), np.log(1-y_)))/len(x) grad = np.dot(x.T, loss)/len(x) w = w - lr * grad total_loss += cross_entropy cost_list.append(total_loss) print("epoch:", i) return w, cost_list
最後,寫一個計算錯誤率的函式,用於比較模型預測結果與原資料的差距有多大:
def evaluation2(X, Y, w): X = np.concatenate((np.ones((np.shape(X)[0], 1)), X), axis=1) y = np.dot(X, w) y = np.array([1 if example > 0.5 else 0 for example in list(y)]) error_count = 0 for i in range(len(y)): if y[i] != Y[i]: error_count += 1 error_rate = error_count/len(y) return error_rate
然後,就可以直接進行訓練了:
w, cost_list = train(trainX, trainY) error_rate2 = evaluation2(trainX, trainY, w) print('訓練集上的錯誤率為:', error_rate2) print('測試集上的錯誤率為:', evaluation2(testX, testY, w)) 訓練集上的錯誤率為: 0.217566118656183 測試集上的錯誤率為: 0.2140921409214092
然後是用sklearn中LogisticRegression方法對上面的過程進行實現,其實很簡單,這裡就建立一個預設引數模型,稍後說明LogisticRegression中各個引數:
model = LogisticRegression() model.fit(trainX, trainY) print('訓練集上的錯誤率為:', model.score(trainX, trainY)) print('訓練集上的錯誤率為:', model.score(testX, testY)) 訓練集上的錯誤率為: 0.7915475339528234 訓練集上的錯誤率為: 0.7883051907442151
下面就說一下LogisticRegression中的引數:LR的引數官方文件https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html
“max_iter”:最大迭代次數;
“penalty”:懲罰項,即正則項,預設為L2正則,可選有L1正則,一般來說L2正則化就夠了,但如果還是會過擬合,可選擇L1正則,或者樣本特徵維度較大,希望歸零一些不重要的特徵,也可以選擇L1正則;
“solver”:優化方法,預設為‘lgfgs’,即一種擬牛頓法,利用損失函式二階導數矩陣即海森矩陣來迭代優化損失函式;此外還有:
‘liblinear’,座標軸梯度下降方法來優化損失函式;資料量較小時可以選擇這種方法;
‘newton-cg’:也是一種牛頓法中的一種;
‘sag’:隨機平均梯度下降,是‘sgd’的一種隨機梯度下降演算法的加速版本,sag其實每次計算時,利用了兩個梯度的值,一個是前一次迭代的梯度值,另一個是新的梯度值。當然這兩個梯度值都只是隨機 選取一個樣本來計算。當資料量很大時可以選擇這種方法;
這裡要說明的是,penalty選擇了l1後,在solver中就只能用liblinear這個優化方法了,因為L1正則化沒有連續導數。
solver的官網文件如下:
總結而言,liblinear支援L1和L2,只支援OvR做多分類,“lbfgs”, “sag” “newton-cg”只支援L2,支援OvR和MvM做多分類。
multi-class:該引數是分類的方式,有兩個值可以選擇:‘ovr’和‘multinomial’,預設為‘ovr’,
‘ovr’即一對多的分類,後面會對多分類做一個總結說到這種方法,具體做法是,對於第K類的分類決策,我們把所有第K類的樣本作為正例,除了第K類樣本以外的所有樣本都作為負例,然後在上面 做二元邏輯迴歸,得到第K類的分類模型。
‘mvm’:如果模型有T類,我們每次在所有的T類樣本里面選擇兩類樣本出來,不妨記為T1類和T2類,把所有的輸出為T1和T2的樣本放在一起,把T1作為正例,T2作為負例,進行二元邏輯迴歸,得到 模型引數。我們一共需要T(T-1)/2次分類。
class-weight:樣本的類別權重,在分類模型中,經常會遇到兩種問題,第一種是誤分類的代價很大,就比如合法使用者和非法使用者,寧願將合法使用者錯分到非法使用者,然後再通過人工甄別,好過將非法使用者誤分到 合法使用者類別中,這時可以適當提高非法使用者的權重,class_weight={0:0.9, 1: 0.1}.
另一種是樣本嚴重失衡,比如病例檢測中,患病人數遠小於不患病人數,假設有10000人檢查,患病人數只有0.5%,若不考慮權重,那麼準確率也有99.5%,但這樣的模型顯然沒有意義,這時可以 選擇balanced,讓類庫自動提升患病樣本的權重。
sample_weight:對於樣本的不均衡,一種方法是上面class_weight設定balanced,另一種就是在fit的時候通過設定每個樣本的權重來調整樣本權重,在scikit-learn做邏輯迴歸時,如果上面兩種方法都用到了,那 麼樣本的真正權重是class_weight*sample_weight。
dual: 對偶方法還是原始方法,預設為False,對偶方法只在求解線性多核的L2正則懲罰中使用,當樣本數量>樣本特徵的時候,dual通常設定為False。
LogisticRegression中的主要引數就是這些,還有一些其他的引數可以看官方文件中說明。
對上面的資料集,調整一下使用L1懲罰和solver改為liblinear,正確率略有提升最終結果為:
訓練集上的錯誤率為: 0.8259471050750536
測試集上的錯誤率為: 0.8217636022514071
另外,最近看到一個問題:“比較一下LR模型和GBDT的差別,什麼情況下GBDT不如LR模型?”在這裡順便做個總結:
GBDT與LR的區別:
①LR是線性模型,而GBDT是多個弱分類器疊加一起的非線性模型;因此有時為了增強模型的非線性表達能力,通常使用LR模型時,需要做大量的特徵工程的工作;
②LR是單模型,而GBDT是整合模型,對於低噪聲的資料,GBDT往往比LR的效果更好;
③LR採用梯度下降的等方式進行訓練,往往需要對特徵進行歸一化處理,而GBDT不需要做特徵的歸一化。
GBDT不如LR的地方:
①LR的解釋能力更強,當對模型需要解釋時,GBDT往往更加“黑盒”,因為不可能去解釋每一棵樹,而LR的特徵權重能夠更直觀的看出每個特徵的貢獻大小,因此LR更具有說服力和營銷策略;
②LR的大規模並行訓練已經非常成熟,模型迭代和訓練速度很快,而GBDT是一種序列的方式,在資料量較大時訓練速度較慢;
③對於高維稀疏矩陣,GBDT往往很容易過擬合,因為這些無用資訊,在進行決策樹訓練時,會導致樹變得很深,因此會容易過擬合,而LR在訓練時可以加入正則化,從而降低特徵的權重(L2正則),或者刪除不重要的特徵(L1正則),從而防止過擬合。
有關邏輯迴歸的內容到這裡就結束了,練習的時候這裡說了LR與神經網路的關係,後面就初步回顧一下神經網路有關內容。