title: 機器學習之邏輯回歸
date: 2017-04-23 22:42:16
categories: IT技術
tags: 機器學習
邏輯回歸介紹
在介紹Logistic Regression之前我們先簡單說一下線性回歸,線性回歸的主要思想就是通過歷史數據擬合出一條直線,用這條直線對新的數據進行預測。線性回歸的公式如下:
$$ 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函數,我們可以看到,Logistic Regression算法是將線性函數的結果映射到了sigmoid函數中。sigmoid函數的圖像如下
# **sigmoid函數**
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函數(一般是sigmoid函數)歸一化后的線性回歸。
邏輯回歸(Logistic Regression)是比較正統的機器學習算法。
機器學習算法的一般步驟
- 對于一個問題,我們用數學語言來描述它,然后建立一個模型(如回歸模型或者分類模型等)來描述這個問題;
- 通過最大似然、最大后驗概率或者最小化分類誤差等等建立模型的代價函數,也就是一個最優化問題。
- 然后我們需要求解這個代價函數,找到最優解。這求解也就分很多種情況了:
- 如果這個優化函數存在解析解。例如我們求最值一般是對代價函數求導,找到導數為0的點,也就是最大值或者最小值的地方了。如果代價函數能簡單求導,并且求導后為0的式子存在解析解,那么我們就可以直接得到最優的參數了。
- 如果式子很難求導或者求導后式子得不到解釋解,這時候我們就需要借助迭代算法來一步一步找到最優解了。
另外需要考慮的情況是,如果代價函數是凸函數,那么就存在全局最優解。但如果是非凸的,那么就會有很多局部最優的解。
邏輯回歸的一般步驟
- 設定擬合函數(hypothesis function):hθ(x),其意義是給定參數θ,根據輸入x,給出輸出hθ(x),當輸出值大于0.5時預測錄取,否則預測被拒。
- 設定代價函數(cost function):J(θ),其意義是累加所有樣本的預測結果hθ(x)與真實結果y之間的差距。
- 通過優化算法(如梯度下降法)來調整參數θ,迭代求解出最優的模型參數,使得代價函數J( θ )的值最小。
比較線性回歸與Logistic回歸,可以看出二者非常相似,但是Logistic回歸的擬合函數(步驟一)和代價函數(步驟二)的定義方法與線性回歸有所不同。
線性回歸輸出的是連續的實數,而Logistic回歸輸出的是[0,1]區間的概率值,給我們提供的就是你的這個樣本屬于正類的可能性是多少,通過概率值來判斷因變量應該是1還是0。
假設我們的樣本是{x, y},y是0或者1,表示正類或者負類,x是我們的m維的樣本特征向量。那么這個樣本x屬于正類,也就是y=1的概率可以通過下面的邏輯函數來表示:
$$ 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}}}$$
這里θ是模型參數,也就是回歸系數,σ是sigmoid函數。
舉個相親的例子,y表示你被對方相中的概率,概率值與多個因素有關,如你人品怎樣、車子是兩個輪的還是四個輪的、長得丑還是美、有千尺豪宅還是三寸茅廬等等,我們把這些因素表示為x1, x2,…, xm。那對方的怎樣考量這些因素呢?最快的方式就是把這些因素的得分都加起來,最后得到的和越大,就表示越喜歡。但每個人心里其實都有一桿稱,每個人考慮的因素不同,如這個女生更看中你的人品,人品的權值是0.6,不看重你有沒有錢,那么有沒有錢的權值是0.001等等。我們將這些對應x1, x2,…, xm的權值叫做回歸系數,表達為θ1, θ2,…, θm,他們的加權和就是你的總得分了,總分帶入sigmoid函數的結果就代表你被接受的概率。
最優化算法
梯度下降(gradient descent)
梯度下降是利用函數的梯度信息找到函數最優解的一種方法,也是機器學習里面最簡單最常用的一種優化方法。它的思想很簡單,要找最小值,只需要每一步都往下走(也就是每一步都可以讓代價函數小一點),然后不斷的走,那肯定能走到最小值的地方。為了更快的到達最小值啊,需要每一步都找下坡最快的地方,這個下坡最快的方向就是梯度的負方向了。而每一步的長度叫作步長也叫學習率,一般用參數α表示。α不宜太大也不能太小,太小的話整個迭代過程太慢,太大的話可能會一步跨過了最值點。梯度算法的迭代公式如下:
$$ \theta := \theta + \nabla h_{\theta}(x)$$
梯度下降算法的偽代碼如下:
初始化回歸系數為1
重復下面步驟直到收斂{
計算整個數據集的梯度
使用alpha x gradient來更新回歸系數
}
返回回歸系數值
隨機梯度下降SGD (stochastic gradient descent)
梯度下降算法在每次更新回歸系數的時候都需要遍歷整個數據集(計算整個數據集的回歸誤差),該方法對小數據集尚可。但當遇到有數十億樣本和成千上萬的特征時,就有點力不從心了,它的計算復雜度太高。改進的方法是一次僅用一個樣本點(的回歸誤差)來更新回歸系數。這個方法叫隨機梯度下降算法。由于可以在新的樣本到來的時候對分類器進行增量的更新(假設我們已經在數據庫A上訓練好一個分類器h了,那新來一個樣本x。對非增量學習算法來說,我們需要把x和數據庫A混在一起,組成新的數據庫B,再重新訓練新的分類器。但對增量學習算法,我們只需要用新樣本x來更新已有分類器h的參數即可),所以它屬于在線學習算法。與在線學習相對應,一次處理整個數據集的叫“批處理”。
初始化回歸系數為1
重復下面步驟直到收斂{
對數據集中每個樣本
計算該樣本的梯度
使用alpha xgradient來更新回歸系數
}
返回回歸系數值
改進的隨機梯度下降
評價一個優化算法的優劣主要是看它是否收斂,也就是說參數是否達到穩定值,是否還會不斷的變化?收斂速度是否快?
上圖展示了隨機梯度下降算法對三個回歸系數的變化過程,對100個二維樣本進行200次迭代,每次迭代都要遍歷這100個樣本來調整系數,所以共有200*100=20000次調整。其中系數X2經過50次迭代就達到了穩定值。但系數X1和X0到100次迭代后穩定。而且可恨的是系數X1和X2還在很調皮的周期波動,迭代次數很大了,還停不下來。產生這個現象的原因是存在一些無法正確分類的樣本點,也就是我們的數據集并非線性可分,但我們的logistic regression是線性分類模型,對非線性可分情況無能為力。然而我們的優化程序并沒能意識到這些不正常的樣本點,還一視同仁的對待,調整系數去減少對這些樣本的分類誤差,從而導致了在每次迭代時引發系數的劇烈改變。對我們來說,我們期待算法能避免來回波動,從而快速穩定和收斂到某個值。
對隨機梯度下降算法,我們做兩處改進來避免上述的波動問題:
在每次迭代時,調整更新步長alpha的值。隨著迭代的進行,alpha越來越小,這會緩解系數的高頻波動(也就是每次迭代系數改變得太大,跳的跨度太大)。當然了,為了避免alpha隨著迭代不斷減小到接近于0(這時候,系數幾乎沒有調整,那么迭代也沒有意義了),我們約束alpha一定大于一個稍微大點的常數項。
每次迭代,改變樣本的優化順序。也就是隨機選擇樣本來更新回歸系數。這樣做可以減少周期性的波動,因為樣本順序的改變,使得每次迭代不再形成周期性。
改進的隨機梯度下降算法的偽代碼如下:
初始化回歸系數為1
重復下面步驟直到收斂{
對隨機遍歷的數據集中的每個樣本
隨著迭代的逐漸進行,減小alpha的值
計算該樣本的梯度
使用alpha x gradient來更新回歸系數
}
返回回歸系數值
比較原始的隨機梯度下降和改進后的梯度下降,可以看到兩點不同:
- 系數不再出現周期性波動。
- 系數可以很快的穩定下來,也就是快速收斂。這里只迭代了20次就收斂了。而上面的隨機梯度下降需要迭代200次才能穩定。
代碼實現
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('訓練結束,花費時間: %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__':
# 加載數據
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)
# 查看結果
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...
訓練結束,花費時間: 0.170251s!
step 3: testing...
step 4: show the result...
The classify accuracy is: 95.000%