Re:從零開始的機器學習 - Machine Learning(二) 邏輯迴歸LR

從我對整個職業生涯的規劃出發,我不只想作一些高質量的應用(軟件工程的角度),還想作一些激動人心的應用,因此我但願能在機器學習的方向走,儘管我在大學粗淺的學了些皮毛,但若是要把機器學習做爲職業發展的話這些還差得遠,因此我開始寫了這個系列的文章。python

我但願經過這個系列能對機器學習領域的知識點作一個總結,因此對於Machine Learning這個部分,個人目標是寫出能讓高中生看得懂git

前言

繼上一章 Re:從零開始的機器學習 - Machine Learning(一) 線性迴歸,我仍是繼續按照斯坦福的路線,這一章來講說邏輯迴歸(Logistic Regression)github

分類(Classification)

和迴歸(Regression)同樣,分類(Classification)問題也是機器學習裏面很大的一塊。算法

分類問題是機器學習很是重要的一個組成部分,它的目標是根據已知樣本的某些特徵,判斷一個新的樣本屬於哪一種已知的樣本類。編程

其實常見的例子不少,判斷一個郵件是不是垃圾郵件之類的,預測一個用戶是否對個人商品感興趣,以及圖像處理裏面對圖像進行的分類。 windows

分類問題有簡單的二分類也有多分類。

正文

這篇博客主要講的是邏輯迴歸(Logistic regression)。機器學習

邏輯迴歸LR(Logistic Regression)

看到名字的時候你可能會有一些奇怪,爲何明明叫邏輯「迴歸」卻用在分類問題上。雖然這個名字彷佛指示着什麼,可是邏輯迴歸其實是分類算法。我猜它之因此這樣命名是由於在它的學習方法上和線性迴歸類似,可是損失(loss)和梯度(gradient)函數的表達不一樣。特別的,邏輯迴歸使用 S型函數(sigmoid)而不是線性迴歸的連續輸出。當咱們深刻到實現中去,咱們會了解到更多。函數

首先咱們先把邏輯迴歸放到一邊,以前也說了邏輯迴歸是用來解決分類問題的,對於分類問題,咱們其實是但願獲得一個分類器(Classifier),當輸入數據以後,這個分類器能給我預測這個數據屬於某一類的機率,也就是說咱們須要的是一個機率post

上一節咱們介紹的線性迴歸,其輸出的是預測值,其假設函數(Hypothesis Function)也就是輸出預測值的函數是這樣的。性能

而邏輯迴歸則是預測屬於某一類的 機率,因此咱們讓其假設函數是下面這個

這個函數的意義實際上是 當輸入爲x時輸出y=1的機率,其實就是二分類問題裏面是某個東西的機率。讀者如今可能會對這個函數有所疑問,好比爲何是這個函數,這個留在後面會討論。

這裏出現了條件機率,實際上就是指事件A在另一個事件B已經發生條件下的發生機率。高中生能夠補這部分也能夠暫時不補

咱們把這個函數g(z)叫作sigmoid函數,很明顯這個函數的值域是0到1的開區間。接下來咱們會詳細介紹一下這個函數。

Sigmoid

Sigmoid函數的函數表達式以下

sigmoid表達式

sigmoid函數圖像

爲何是Sigmoid(選修)

邏輯迴歸選擇Sigmoid的緣由既有內因也有外因。 www.zhihu.com/question/35…

Sigmoid自己具有很是良好的特性

  1. sigmoid 函數連續,單調遞增
  2. sigmiod 函數關於(0,0.5) 中心對稱
  3. 對sigmoid函數求導

損失函數(Loss Function)

上一小節咱們也說過,爲了修正參數Θ咱們須要有個手段來衡量當前參數Θ的優秀程度。損失函數(Loss Function)就是用來衡量假設函數(hypothesis function)的準確性。

對於邏輯迴歸來講,咱們但願的是當預測機率約接近實際狀況(0或1)的時候偏差最小,並且不但願曲線是一條直線,而是對於越接近的地方變化越小,約遠離的地方變化越大的函數。

下面就是邏輯迴歸的損失函數。

邏輯迴歸的損失函數

咱們能夠將函數合併一下,畢竟這種分段函數處理起來不是很舒服,其實就是下圖這樣,也很好理解,畢竟二分類訓練數據y只有0和1兩個值。

這樣咱們就能夠算出在一個訓練集中基於當前參數Θ獲得結果的偏差了。

矢量化編程

其實上一小節的實戰中咱們已經用到了矢量化編程。

# 計算損失,用了矢量化編程而不是for循環
def computeLoss(X, y, theta):  
    inner = np.power(((X * theta.T) - y), 2)
    return np.sum(inner) / (2 * len(X))
複製代碼

矢量化編程是提升算法速度的一種有效方法。爲了提高特定數值運算操做(如矩陣相乘、矩陣相加、矩陣-向量乘法等)的速度,數值計算和並行計算的研究人員已經努力了幾十年。矢量化編程的思想就是儘可能使用這些被高度優化的數值運算操做來實現咱們的學習算法。

換句話說就是儘可能避免使用for循環,畢竟矩陣相乘這種場景很是適合並行計算,在巨量的數據面前性能收益很是明顯。

若是剛剛的損失函數用矢量化編程的思想來表示的話

若是一時不理解的話我先解釋一下,咱們先假設共m個數據,而這個模型中變量有n個。則矩陣h就是(m, n) X (n, 1)也就是(m,1)矩陣,矩陣h的意義就是這m個數據的預測值。

損失函數中y的轉置爲(1, m),相乘後獲得(1, 1)也就是一個值,這兩個矩陣相乘的意義則是對應的預測值取對數乘以對應的實際值,最後加在一塊兒。

(m,n)表示維度爲m行n列的矩陣,若是學過矩陣的乘法應該知道矩陣相乘(m, n) X (n, k)獲得的矩陣是(m, k)

凹函數

邏輯迴歸的梯度降低法(Gradient Descent)

關於梯度降低法我在上一小節裏面已經作了比較具體的描述,若是忘記了能夠回去翻翻。咱們剛剛知道了怎麼評價當前參數Θ的好壞,如今咱們須要作的是使用梯度降低法來調整參數。

依舊是對損失函數求偏導數,別忘記α是學習速率的意思。

矢量化表示爲

損失函數偏導數求解過程(選修)

過擬合問題

對於一個訓練數據集,可視化後以下圖所示。

對於三個不一樣的分類器劃分出的邊界的三種狀況,咱們對其有不一樣的稱呼
第一種,分類很是不許,這種咱們叫 欠擬合(underfitting)

第二種,分類得恰到好處,這種其實沒有特別的稱呼。

第三種,分類太過於契合訓練數據了。這種咱們稱爲過擬合(overfitting)

過擬合所產生的問題也很明顯,它實在太過於契合訓練集了,對於咱們來講,第二個曲線纔是咱們想要的,過擬合的結果太過於契合訓練數據,實用性可想而知的低。

而解決過擬合的方法主要有兩種

減小特徵的數量,這個很好理解,更多的特徵意味着劃分出來的函數曲線能夠越複雜。這個能夠擴展到之後會講的特徵工程(Feature Engineering)

使用正則化項, 保持全部的特徵,可是保證參數θj不會變得巨大。正則化項很是適合在咱們擁有不少稍微有點用的特徵的時候。

正則化項(regularizer)

正則化項其實也叫懲罰項(penalty term),其做用是減緩過擬合問題,其實就是在損失函數後面加一個含有各個Θ的項,這樣作的目的是讓Θ也參與損失函數的計算,這樣因爲咱們須要求的是損失函數的最小值,這個項就會限制Θ的大小。

這個正則化項的目的實際上是一個權衡,咱們即但願參數Θ能在訓練數據集上表現得比較好,又不但願參數Θ訓練出來的值很是大而產生一些奇葩的劃分曲線,就像下圖這樣的。

實踐

在這篇文章中咱們將目標從預測連續值的迴歸問題轉到將結果進行分類的分類問題

數據均可以在個人GitHub庫上下載到。

環境

若是你不想被配環境煩死的話,我真的推薦裝Anaconda,除此以外要說的就是我用的都是Python3.x。

背景

本篇的實戰主要是利用邏輯迴歸來幫助招生辦篩選申請人。假設你想要基於兩次考試的結果預測每一個申請人是否會被錄取。你有以前歷史申請人的歷史數據,包括兩次考試的分數以及最後他們是否被錄取。爲了完此目的,咱們將使用邏輯迴歸創建一個基於考試分數的分類模型(classification model )以估計錄取的可能性。

代碼及註釋

import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

def sigmoid(z):  
    return 1 / (1 + np.exp(-z))

# 讀入訓練數據
# windows用戶路徑可能須要修改下,後期有時間可能會作統一
def loadData(path):
    trainingData = pd.read_csv(path, header=None, names=['Exam 1', 'Exam 2', 'Admitted'])

    trainingData.head()

    trainingData.describe()

    positive = trainingData[trainingData['Admitted'].isin([1])]
    negative = trainingData[trainingData['Admitted'].isin([0])]

    fig, ax = plt.subplots(figsize=(12,8))
    ax.scatter(positive['Exam 1'], positive['Exam 2'], s=50, c='b', marker='o', label='Admitted')
    ax.scatter(negative['Exam 1'], negative['Exam 2'], s=50, c='r', marker='x', label='Not Admitted')
    ax.legend()
    ax.set_xlabel('Exam 1 Score')
    ax.set_ylabel('Exam 2 Score')
    # plt.show()
    return trainingData

# 計算損失,用了矢量化編程而不是for循環,公式在博客中有詳細描述和證實。
def computeLoss(X, y, theta):  
    theta = np.copy(theta)
    X = np.copy(X)
    y = np.copy(y)
    m = X.shape[0]
    h = sigmoid(np.matmul(X, theta.T))
    first = np.matmul(-(y.T), np.log(h))
    second = np.matmul((1 - y).T, np.log(1 - h))
    return np.sum(first - second) / m

# 梯度降低部分
def gradientDescent(X, y, theta, alpha, iters):  
    m = X.shape[0] # 數據項數m
    temp = np.matrix(np.zeros(theta.shape))
    # parameters = 1
    cost = np.zeros(iters)

    for i in range(iters):
        error = sigmoid(np.matmul(X, theta.T)) - y


        theta = theta - ((alpha/m) * np.matmul(X.T, error)).T
        cost[i] = computeLoss(X, y, theta)

    return theta, cost

def predict(theta, X):  
    probability = sigmoid(np.matmul(X, theta.T))
    return [1 if x >= 0.5 else 0 for x in probability]

trainingData = loadData(os.getcwd() + '/../../data/ex2data1.txt')

# 插入常數項
trainingData.insert(0, 'Ones', 1)

cols = trainingData.shape[1]  
X = trainingData.iloc[:,0:cols-1]
y = trainingData.iloc[:,cols-1:cols]

# 初始化X、Y以及theta矩陣
X = np.matrix(X.values)
y = np.matrix(y.values)
theta = np.matrix(np.zeros(3))

# 計算訓練前的損失值
computeLoss(X, y, theta)

# 使用梯度降低獲得模型參數
alpha = 0.001
iters = 20000
theta_fin, loss = gradientDescent(X, y, theta, alpha, iters)


# 計算訓練後的參數的損失值 (不優化)
computeLoss(X, y, theta_fin)  # 


# 損失隨着迭代次數的變化 (不優化)
# fig, ax = plt.subplots(figsize=(12,8)) 
# ax.plot(np.arange(iters), loss, 'r') 
# ax.set_xlabel('Iterations') 
# ax.set_ylabel('Cost') 
# ax.set_title('Error vs. Training Epoch') 
# plt.show()

# 不理解爲何不優化的會這麼低,是學習速率沒動態變化麼?
predictions = predict(theta_fin, X)  
correct = [1 if ((a == 1 and b == 1) or (a == 0 and b == 0)) else 0 for (a, b) in zip(predictions, y)]  
accuracy = (sum(map(int, correct)) % len(correct))  
print('accuracy 1 = {0}%'.format(accuracy)) # 60%


# 使用scipy的optimize來作優化
import scipy.optimize as opt
# 換了下參數位置讓其符合fmin_tnc
def gradient(theta, X, y):
    theta = np.matrix(theta)
    X = np.matrix(X)
    y = np.matrix(y)
    
    parameters = int(theta.ravel().shape[1])
    grad = np.zeros(parameters)
    
    error = sigmoid(X * theta.T) - y
    
    for i in range(parameters):
        term = np.multiply(error, X[:,i])
        grad[i] = np.sum(term) / len(X)
    
    return grad
# 換了下參數位置讓其符合fmin_tnc
def computeLoss2(theta, X, y):  
    theta = np.copy(theta)
    X = np.copy(X)
    y = np.copy(y)
    m = X.shape[0]
    h = sigmoid(np.matmul(X, theta.T))
    first = np.matmul(-(y.T), np.log(h))
    second = np.matmul((1 - y).T, np.log(1 - h))
    return np.sum(first - second) / m
result = opt.fmin_tnc(func=computeLoss2, x0=theta, fprime=gradient, args=(X, y))

predictions = predict(np.matrix(result[0]), X)  
correct = [1 if ((a == 1 and b == 1) or (a == 0 and b == 0)) else 0 for (a, b) in zip(predictions, y)]  
accuracy = (sum(map(int, correct)) % len(correct))  
print('accuracy 2 = {0}%'.format(accuracy)) # 89%
複製代碼

解釋其實註釋裏面都比較清楚,就不贅述了。

結果

數據可視化

訓練集中正確率

疑問

代碼中使用了兩個方法,一個是和上一章同樣的手動梯度降低更新theta值,另外一個是使用scipy的優化方法fmin_tnc。最後的準確率差異很大不知道爲何。

本文章來源於 - 梁王(lwio、lwyj123)

主要參考斯坦福ML課程 johnwittenauer

相關文章
相關標籤/搜索