title: 機器學習之邏輯回歸
date: 2017-04-23 22:42:16
categories: IT技術(shù)
tags: 機器學習
邏輯回歸介紹
在介紹Logistic Regression之前我們先簡單說一下線性回歸,線性回歸的主要思想就是通過歷史數(shù)據(jù)擬合出一條直線,用這條直線對新的數(shù)據(jù)進行預測。線性回歸的公式如下:
$$ z={\theta_{0}}+{\theta_{1}x_{1}}+{\theta_{2}x_{2}+{\theta_{3}x_{3}}...+{\theta_{n}x_{n}}}=\theta^Tx $$
而對于Logistic Regression來說,其思想也是基于線性回歸(Logistic Regression屬于廣義線性回歸模型)。其公式如下:
$$ h_{\theta}(x)=\frac{1}{1+e{-z}}=\frac{1}{1+e{-\theta^Tx}} $$
其中,$ g(z)=\frac{1}{1+e^{-z}} $
被稱作sigmoid函數(shù),我們可以看到,Logistic Regression算法是將線性函數(shù)的結(jié)果映射到了sigmoid函數(shù)中。sigmoid函數(shù)的圖像如下
# **sigmoid函數(shù)**
import matplotlib.pyplot as plt
from math import exp
import numpy as np
def sigmoid(n):
return 1.0/(1+exp(-n))
a1 = [n for n in np.arange(-10, 10, 0.1)]
a2 = [sigmoid(n) for n in a1]
plt.plot(a1, a2)
plt.show()

邏輯回歸就是一個被logistic函數(shù)(一般是sigmoid函數(shù))歸一化后的線性回歸。
邏輯回歸(Logistic Regression)是比較正統(tǒng)的機器學習算法。
機器學習算法的一般步驟
- 對于一個問題,我們用數(shù)學語言來描述它,然后建立一個模型(如回歸模型或者分類模型等)來描述這個問題;
- 通過最大似然、最大后驗概率或者最小化分類誤差等等建立模型的代價函數(shù),也就是一個最優(yōu)化問題。
- 然后我們需要求解這個代價函數(shù),找到最優(yōu)解。這求解也就分很多種情況了:
- 如果這個優(yōu)化函數(shù)存在解析解。例如我們求最值一般是對代價函數(shù)求導,找到導數(shù)為0的點,也就是最大值或者最小值的地方了。如果代價函數(shù)能簡單求導,并且求導后為0的式子存在解析解,那么我們就可以直接得到最優(yōu)的參數(shù)了。
- 如果式子很難求導或者求導后式子得不到解釋解,這時候我們就需要借助迭代算法來一步一步找到最優(yōu)解了。
另外需要考慮的情況是,如果代價函數(shù)是凸函數(shù),那么就存在全局最優(yōu)解。但如果是非凸的,那么就會有很多局部最優(yōu)的解。
邏輯回歸的一般步驟
- 設(shè)定擬合函數(shù)(hypothesis function):hθ(x),其意義是給定參數(shù)θ,根據(jù)輸入x,給出輸出hθ(x),當輸出值大于0.5時預測錄取,否則預測被拒。
- 設(shè)定代價函數(shù)(cost function):J(θ),其意義是累加所有樣本的預測結(jié)果hθ(x)與真實結(jié)果y之間的差距。
- 通過優(yōu)化算法(如梯度下降法)來調(diào)整參數(shù)θ,迭代求解出最優(yōu)的模型參數(shù),使得代價函數(shù)J( θ )的值最小。
比較線性回歸與Logistic回歸,可以看出二者非常相似,但是Logistic回歸的擬合函數(shù)(步驟一)和代價函數(shù)(步驟二)的定義方法與線性回歸有所不同。
線性回歸輸出的是連續(xù)的實數(shù),而Logistic回歸輸出的是[0,1]區(qū)間的概率值,給我們提供的就是你的這個樣本屬于正類的可能性是多少,通過概率值來判斷因變量應該是1還是0。
假設(shè)我們的樣本是{x, y},y是0或者1,表示正類或者負類,x是我們的m維的樣本特征向量。那么這個樣本x屬于正類,也就是y=1的概率可以通過下面的邏輯函數(shù)來表示:
$$ P(y=1|x;\theta) = σ(\theta^Tx) = \frac{1}{1+e{-\thetaTx}} $$
$$ P(y=0|x;\theta) = 1- σ(\theta^Tx) = 1 - \frac{1}{1+e{-\thetaTx}} $$
$$ \ln{\frac{P(y=1|x;\theta)} {P(y=0|x;\theta)}} = \frac{1- \frac{1}{1+e{-\thetaTx}}} {\frac{1}{1+e{-\thetaTx}}} = \theta^Tx = {\theta_{0}}+{\theta_{1}x_{1}}+{\theta_{2}x_{2}+{\theta_{3}x_{3}}...+{\theta_{n}x_{n}}}$$
這里θ是模型參數(shù),也就是回歸系數(shù),σ是sigmoid函數(shù)。
舉個相親的例子,y表示你被對方相中的概率,概率值與多個因素有關(guān),如你人品怎樣、車子是兩個輪的還是四個輪的、長得丑還是美、有千尺豪宅還是三寸茅廬等等,我們把這些因素表示為x1, x2,…, xm。那對方的怎樣考量這些因素呢?最快的方式就是把這些因素的得分都加起來,最后得到的和越大,就表示越喜歡。但每個人心里其實都有一桿稱,每個人考慮的因素不同,如這個女生更看中你的人品,人品的權(quán)值是0.6,不看重你有沒有錢,那么有沒有錢的權(quán)值是0.001等等。我們將這些對應x1, x2,…, xm的權(quán)值叫做回歸系數(shù),表達為θ1, θ2,…, θm,他們的加權(quán)和就是你的總得分了,總分帶入sigmoid函數(shù)的結(jié)果就代表你被接受的概率。
最優(yōu)化算法
梯度下降(gradient descent)
梯度下降是利用函數(shù)的梯度信息找到函數(shù)最優(yōu)解的一種方法,也是機器學習里面最簡單最常用的一種優(yōu)化方法。它的思想很簡單,要找最小值,只需要每一步都往下走(也就是每一步都可以讓代價函數(shù)小一點),然后不斷的走,那肯定能走到最小值的地方。為了更快的到達最小值啊,需要每一步都找下坡最快的地方,這個下坡最快的方向就是梯度的負方向了。而每一步的長度叫作步長也叫學習率,一般用參數(shù)α表示。α不宜太大也不能太小,太小的話整個迭代過程太慢,太大的話可能會一步跨過了最值點。梯度算法的迭代公式如下:
$$ \theta := \theta + \nabla h_{\theta}(x)$$
梯度下降算法的偽代碼如下:
初始化回歸系數(shù)為1
重復下面步驟直到收斂{
計算整個數(shù)據(jù)集的梯度
使用alpha x gradient來更新回歸系數(shù)
}
返回回歸系數(shù)值
隨機梯度下降SGD (stochastic gradient descent)
梯度下降算法在每次更新回歸系數(shù)的時候都需要遍歷整個數(shù)據(jù)集(計算整個數(shù)據(jù)集的回歸誤差),該方法對小數(shù)據(jù)集尚可。但當遇到有數(shù)十億樣本和成千上萬的特征時,就有點力不從心了,它的計算復雜度太高。改進的方法是一次僅用一個樣本點(的回歸誤差)來更新回歸系數(shù)。這個方法叫隨機梯度下降算法。由于可以在新的樣本到來的時候?qū)Ψ诸惼鬟M行增量的更新(假設(shè)我們已經(jīng)在數(shù)據(jù)庫A上訓練好一個分類器h了,那新來一個樣本x。對非增量學習算法來說,我們需要把x和數(shù)據(jù)庫A混在一起,組成新的數(shù)據(jù)庫B,再重新訓練新的分類器。但對增量學習算法,我們只需要用新樣本x來更新已有分類器h的參數(shù)即可),所以它屬于在線學習算法。與在線學習相對應,一次處理整個數(shù)據(jù)集的叫“批處理”。
初始化回歸系數(shù)為1
重復下面步驟直到收斂{
對數(shù)據(jù)集中每個樣本
計算該樣本的梯度
使用alpha xgradient來更新回歸系數(shù)
}
返回回歸系數(shù)值
改進的隨機梯度下降
評價一個優(yōu)化算法的優(yōu)劣主要是看它是否收斂,也就是說參數(shù)是否達到穩(wěn)定值,是否還會不斷的變化?收斂速度是否快?

上圖展示了隨機梯度下降算法對三個回歸系數(shù)的變化過程,對100個二維樣本進行200次迭代,每次迭代都要遍歷這100個樣本來調(diào)整系數(shù),所以共有200*100=20000次調(diào)整。其中系數(shù)X2經(jīng)過50次迭代就達到了穩(wěn)定值。但系數(shù)X1和X0到100次迭代后穩(wěn)定。而且可恨的是系數(shù)X1和X2還在很調(diào)皮的周期波動,迭代次數(shù)很大了,還停不下來。產(chǎn)生這個現(xiàn)象的原因是存在一些無法正確分類的樣本點,也就是我們的數(shù)據(jù)集并非線性可分,但我們的logistic regression是線性分類模型,對非線性可分情況無能為力。然而我們的優(yōu)化程序并沒能意識到這些不正常的樣本點,還一視同仁的對待,調(diào)整系數(shù)去減少對這些樣本的分類誤差,從而導致了在每次迭代時引發(fā)系數(shù)的劇烈改變。對我們來說,我們期待算法能避免來回波動,從而快速穩(wěn)定和收斂到某個值。
對隨機梯度下降算法,我們做兩處改進來避免上述的波動問題:
在每次迭代時,調(diào)整更新步長alpha的值。隨著迭代的進行,alpha越來越小,這會緩解系數(shù)的高頻波動(也就是每次迭代系數(shù)改變得太大,跳的跨度太大)。當然了,為了避免alpha隨著迭代不斷減小到接近于0(這時候,系數(shù)幾乎沒有調(diào)整,那么迭代也沒有意義了),我們約束alpha一定大于一個稍微大點的常數(shù)項。
每次迭代,改變樣本的優(yōu)化順序。也就是隨機選擇樣本來更新回歸系數(shù)。這樣做可以減少周期性的波動,因為樣本順序的改變,使得每次迭代不再形成周期性。
改進的隨機梯度下降算法的偽代碼如下:
初始化回歸系數(shù)為1
重復下面步驟直到收斂{
對隨機遍歷的數(shù)據(jù)集中的每個樣本
隨著迭代的逐漸進行,減小alpha的值
計算該樣本的梯度
使用alpha x gradient來更新回歸系數(shù)
}
返回回歸系數(shù)值

比較原始的隨機梯度下降和改進后的梯度下降,可以看到兩點不同:
- 系數(shù)不再出現(xiàn)周期性波動。
- 系數(shù)可以很快的穩(wěn)定下來,也就是快速收斂。這里只迭代了20次就收斂了。而上面的隨機梯度下降需要迭代200次才能穩(wěn)定。
代碼實現(xiàn)
import numpy as np
import matplotlib.pyplot as plt
import time
def load_data():
train_x = []
train_y = []
with open('testSet.txt') as f:
for line in f.readlines():
line = line.strip().split()
train_x.append([1.0, float(line[0]), float(line[1])])
train_y.append(float(line[2]))
return np.mat(train_x), np.mat(train_y).transpose()
def sigmoid(inX):
return 1.0 / (1 + np.exp(-inX))
# train a logistic regression model using some optional optimize algorithm
# input: train_x is a mat datatype, each row stands for one sample
# train_y is mat datatype too, each row is the corresponding label
# opts is optimize option include step and maximum number of iterations
def train(train_x, train_y, opts):
start_time = time.time()
num_samples, num_features = np.shape(train_x)
alpha = opts['alpha']
max_iter = opts['max_iter']
weights = np.ones((num_features, 1))
for k in range(max_iter):
if opts['optimize_type'] == 'grad_descent': # gradient descent algorilthm
output = sigmoid(train_x * weights)
error = train_y - output
weights = weights + alpha * train_x.transpose() * error
elif opts['optimize_type'] == 'stoc_grad_descent': # stochastic gradient descent
for i in range(num_samples):
output = sigmoid(train_x[i, :] * weights)
error = train_y[i, 0] - output
weights = weights + alpha * train_x[i, :].transpose() * error
elif opts['optimize_type'] == 'smooth_stoc_grad_descent': # smooth stochastic gradient descent
# randomly select samples to optimize for reducing cycle fluctuations
data_index = list(range(num_samples))
for i in range(num_samples):
alpha = 4.0 / (1.0 + k + i) + 0.01
rand_index = int(np.random.uniform(0, len(data_index)))
output = sigmoid(train_x[rand_index, :] * weights)
error = train_y[rand_index, 0] - output
weights = weights + alpha * train_x[rand_index, :].transpose() * error
del(data_index[rand_index]) # during one interation, delete the optimized sample
else:
raise NameError('Not support optimize method type!')
print('訓練結(jié)束,花費時間: %fs!' % (time.time() - start_time))
return weights
# test your trained Logistic Regression model given test set
def test(weights, test_x, test_y):
numSamples, numFeatures = np.shape(test_x)
matchCount = 0
for i in range(numSamples):
predict = sigmoid(test_x[i, :] * weights)[0, 0] > 0.5
if predict == bool(test_y[i, 0]):
matchCount += 1
accuracy = float(matchCount) / numSamples
return accuracy
# show your trained logistic regression model only available with 2-D data
def show(weights, train_x, train_y):
# notice: train_x and train_y is mat datatype
num_samples, num_features = np.shape(train_x)
if num_features != 3:
print("Sorry! I can not draw because the dimension of your data is not 2!")
return 1
# 畫出所有的點
for i in range(num_samples):
if int(train_y[i, 0]) == 0:
plt.plot(train_x[i, 1], train_x[i, 2], 'or')
elif int(train_y[i, 0]) == 1:
plt.plot(train_x[i, 1], train_x[i, 2], 'ob')
# 畫出分割線
min_x = min(train_x[:, 1])[0, 0]
max_x = max(train_x[:, 1])[0, 0]
weights = weights.getA() # convert mat to array
y_min_x = float(-weights[0] - weights[1] * min_x) / weights[2]
y_max_x = float(-weights[0] - weights[1] * max_x) / weights[2]
plt.plot([min_x, max_x], [y_min_x, y_max_x], '-g')
plt.xlabel('X1')
plt.ylabel('X2')
plt.show()
if __name__ == '__main__':
# 加載數(shù)據(jù)
print("step 1: load data...")
train_x, train_y = load_data()
test_x = train_x
test_y = train_y
# 訓練
print("step 2: training...")
opts = {'alpha': 0.01, 'max_iter': 20, 'optimize_type': 'smooth_stoc_grad_descent'}
optimalWeights = train(train_x, train_y, opts)
# 測試
print("step 3: testing...")
accuracy = test(optimalWeights, test_x, test_y)
# 查看結(jié)果
print("step 4: show the result...")
print('The classify accuracy is: %.3f%%' % (accuracy * 100))
show(optimalWeights, train_x, train_y)
step 1: load data...
step 2: training...
訓練結(jié)束,花費時間: 0.170251s!
step 3: testing...
step 4: show the result...
The classify accuracy is: 95.000%
