機器學習之邏輯回歸


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()
output_1_0.png

邏輯回歸就是一個被logistic函數(shù)(一般是sigmoid函數(shù))歸一化后的線性回歸。

邏輯回歸(Logistic Regression)是比較正統(tǒng)的機器學習算法。

機器學習算法的一般步驟

  1. 對于一個問題,我們用數(shù)學語言來描述它,然后建立一個模型(如回歸模型或者分類模型等)來描述這個問題;
  2. 通過最大似然、最大后驗概率或者最小化分類誤差等等建立模型的代價函數(shù),也就是一個最優(yōu)化問題。
  3. 然后我們需要求解這個代價函數(shù),找到最優(yōu)解。這求解也就分很多種情況了:
  • 如果這個優(yōu)化函數(shù)存在解析解。例如我們求最值一般是對代價函數(shù)求導,找到導數(shù)為0的點,也就是最大值或者最小值的地方了。如果代價函數(shù)能簡單求導,并且求導后為0的式子存在解析解,那么我們就可以直接得到最優(yōu)的參數(shù)了。
  • 如果式子很難求導或者求導后式子得不到解釋解,這時候我們就需要借助迭代算法來一步一步找到最優(yōu)解了。
    另外需要考慮的情況是,如果代價函數(shù)是凸函數(shù),那么就存在全局最優(yōu)解。但如果是非凸的,那么就會有很多局部最優(yōu)的解。

邏輯回歸的一般步驟

  1. 設(shè)定擬合函數(shù)(hypothesis function):hθ(x),其意義是給定參數(shù)θ,根據(jù)輸入x,給出輸出hθ(x),當輸出值大于0.5時預測錄取,否則預測被拒。
  2. 設(shè)定代價函數(shù)(cost function):J(θ),其意義是累加所有樣本的預測結(jié)果hθ(x)與真實結(jié)果y之間的差距。
  3. 通過優(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)定值,是否還會不斷的變化?收斂速度是否快?

波動1.png

上圖展示了隨機梯度下降算法對三個回歸系數(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)定和收斂到某個值。

對隨機梯度下降算法,我們做兩處改進來避免上述的波動問題:

  1. 在每次迭代時,調(diào)整更新步長alpha的值。隨著迭代的進行,alpha越來越小,這會緩解系數(shù)的高頻波動(也就是每次迭代系數(shù)改變得太大,跳的跨度太大)。當然了,為了避免alpha隨著迭代不斷減小到接近于0(這時候,系數(shù)幾乎沒有調(diào)整,那么迭代也沒有意義了),我們約束alpha一定大于一個稍微大點的常數(shù)項。

  2. 每次迭代,改變樣本的優(yōu)化順序。也就是隨機選擇樣本來更新回歸系數(shù)。這樣做可以減少周期性的波動,因為樣本順序的改變,使得每次迭代不再形成周期性。

改進的隨機梯度下降算法的偽代碼如下:

初始化回歸系數(shù)為1
重復下面步驟直到收斂{
   對隨機遍歷的數(shù)據(jù)集中的每個樣本
      隨著迭代的逐漸進行,減小alpha的值
      計算該樣本的梯度
      使用alpha x gradient來更新回歸系數(shù)
}
返回回歸系數(shù)值

波動2.png

比較原始的隨機梯度下降和改進后的梯度下降,可以看到兩點不同:

  1. 系數(shù)不再出現(xiàn)周期性波動。
  2. 系數(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%
output_7_1.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容