機器學習之邏輯回歸


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

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

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

機器學習算法的一般步驟

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

邏輯回歸的一般步驟

  1. 設定擬合函數(hypothesis function):hθ(x),其意義是給定參數θ,根據輸入x,給出輸出hθ(x),當輸出值大于0.5時預測錄取,否則預測被拒。
  2. 設定代價函數(cost function):J(θ),其意義是累加所有樣本的預測結果hθ(x)與真實結果y之間的差距。
  3. 通過優化算法(如梯度下降法)來調整參數θ,迭代求解出最優的模型參數,使得代價函數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來更新回歸系數
}
返回回歸系數值

改進的隨機梯度下降

評價一個優化算法的優劣主要是看它是否收斂,也就是說參數是否達到穩定值,是否還會不斷的變化?收斂速度是否快?

波動1.png

上圖展示了隨機梯度下降算法對三個回歸系數的變化過程,對100個二維樣本進行200次迭代,每次迭代都要遍歷這100個樣本來調整系數,所以共有200*100=20000次調整。其中系數X2經過50次迭代就達到了穩定值。但系數X1和X0到100次迭代后穩定。而且可恨的是系數X1和X2還在很調皮的周期波動,迭代次數很大了,還停不下來。產生這個現象的原因是存在一些無法正確分類的樣本點,也就是我們的數據集并非線性可分,但我們的logistic regression是線性分類模型,對非線性可分情況無能為力。然而我們的優化程序并沒能意識到這些不正常的樣本點,還一視同仁的對待,調整系數去減少對這些樣本的分類誤差,從而導致了在每次迭代時引發系數的劇烈改變。對我們來說,我們期待算法能避免來回波動,從而快速穩定和收斂到某個值。

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

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

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

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

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

波動2.png

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

  1. 系數不再出現周期性波動。
  2. 系數可以很快的穩定下來,也就是快速收斂。這里只迭代了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%
output_7_1.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內容