機器學習之邏輯迴歸

邏輯迴歸(Logistic Regression)是一種用於解決二分類(0 or 1)問題的機器學習方法,能夠用於估計某種事物的可能性。好比某用戶購買某商品的可能性,某病人患有某種疾病的可能性,以及某廣告被用戶點擊的可能性等。python

本文的一個大體目錄以下:算法

  1. 選取預測函數。
  2. 計算損失函數。
  3. 使用梯度降低計算損失函數最小值。
  4. 向量化。
  5. 基於梯度降低實現邏輯迴歸算法。
  6. 多項式特徵。
  7. 多分類。
  8. 正則化。

預測函數

邏輯迴歸雖然帶有迴歸二字,但實際上它是一種分類算法,可用於二分類問題(固然也可用於多分類問題下面章節會作介紹)。預測函數選擇的是 Sigmoid 函數,函數表達式以下:dom

g(\mathrm{z})=\frac{1}{1+e^{-z}}

可使用 Python 代碼來繪製一下:機器學習

import numpy as np
import matplotlib.pyplot as plt

def sigmoid(t):
    return 1. / (1. + np.exp(-t))
  
x = np.linspace(-10, 10, 500)

plt.plot(x, sigmoid(x))
plt.show()
複製代碼

對於線性邊界的狀況,其表達形式爲:函數

\theta_{0}+\theta_{1} x_{1}+\ldots+\theta_{n} x_{n}=\sum_{i=0}^{n} \theta_{i} x_{i}=\theta^{T} x

定義預測函數爲:學習

h_{\theta}(x)=g\left(\theta^{T} x\right)=\frac{1}{1+e^{-\theta^{T} x}}

h_{\theta}(x) 表示爲結果取 1 的機率,所以:優化

\begin{array}{l}{P(y=1 | x ; \theta)=h_{\theta}(x)} \\ {P(y=0 | x ; \theta)=1-h_{\theta}(x)}\end{array}

構造損失函數

損失函數的定義以下:spa

\operatorname{cost}\left(h_{\theta}(x), y\right)=\left\{\begin{array}{cc}{-\log \left(h_{\theta}(x)\right)} & {\text { if } y=1} \\ {-\log \left(1-h_{\theta}(x)\right)} & {\text { if } y=0}\end{array}\right.

作一下轉換:code

\begin{aligned} J(\theta) &=\frac{1}{m} \sum_{i=1}^{m} \operatorname{cost}\left(h_{\theta}\left(x^{(i)}\right), y^{(i)}\right) \\ &=-\frac{1}{m}\left[\sum_{i=1}^{m} y^{(i)} \log h_{\theta}\left(x^{(i)}\right)+\left(1-y^{(i)}\right) \log \left(1-h_{\theta}\left(x^{(i)}\right)\right)\right] \end{aligned}

梯度降低求解損失函數

求損失函數的最小值可使用梯度降低法,其中 \alpha 爲學習步長:orm

\theta_{j} :=\theta_{j}-\alpha \frac{\partial}{\partial \theta_{j}} J(\theta), \quad(j=0 \ldots n)

下面是求偏導以後的一個結果:

\frac{\partial}{\partial \theta_{j}} J(\theta)=\frac{1}{m} \sum_{i=1}^{m}\left(h_{\theta}\left(\mathrm{x}^{(\mathrm{i})}\right)-y^{(i)}\right) x_{j}^{(\mathrm{i})}

具體的一個求導過程以下:

因此:

\theta_{j} :=\theta_{j}-\alpha \frac{1}{m} \sum_{i=1}^{m}\left(h_{\theta}\left(\mathrm{x}^{(\mathrm{i})}\right)-y^{(i)}\right) x_{j}^{(\mathrm{i})}, \quad(j=0 \ldots n)

由於 1/m 是一個常數,\alpha 也爲一個常量,因此最終的一個表達式爲:

\theta_{j}=\theta_{j}-\alpha \sum_{i=1}^{m}\left(h_{\theta}\left(\mathrm{x}^{(6)}\right)-y^{(i)}\right) x_{j}^{(6)}, \quad(j=0 \ldots n)

向量化

向量化後最終的一個結果爲:

\theta :=\theta-\alpha \cdot\left(\frac{1}{m}\right) \cdot x^{T} \cdot(g(x \cdot \theta)-y)

具體推導一下-todo

Python 實現邏輯迴歸

下面是用 Python 代碼實現的一個邏輯迴歸算法,對於優化損失函數使用的是梯度降低的方法。

def __init__(self):
    self.coef_ = None
    self.intercept_ = None
    self._theta = None

def _sigmoid(self, t):
    return 1. / (1. + np.exp(-t))

def fit(self, X_train, y_train, eta=0.01, n_iters=1e4):
    X_b = np.hstack([np.ones((len(X_train), 1)), X_train])
    initial_theta = np.zeros(X_b.shape[1])
    self._theta = gradient_descent(X_b, y_train, initial_theta, eta, n_iters)

    self.intercept_ = self._theta[0]
    self.coef_ = self._theta[1:]
    return self
複製代碼

梯度降低的實現代碼以下:

def J(theta, X_b, y):
    y_hat = self._sigmoid(X_b.dot(theta))
    try:
        return - np.sum(y*np.log(y_hat) + (1-y)*np.log(1-y_hat)) / len(y)
    except:
        return float('inf')

def dJ(theta, X_b, y):
    return X_b.T.dot(self._sigmoid(X_b.dot(theta)) - y) / len(y)

def gradient_descent(X_b, y, initial_theta, eta, n_iters=1e4, epsilon=1e-8):
    theta = initial_theta
    cur_iter = 0

    while cur_iter < n_iters:
        gradient = dJ(theta, X_b, y)
        last_theta = theta
        theta = theta - eta * gradient
        if (abs(J(theta, X_b, y) - J(last_theta, X_b, y)) < epsilon):
            break

        cur_iter += 1

    return theta
複製代碼

預測方法和得分方法:

def predict(self, X_predict):
    assert self.intercept_ is not None and self.coef_ is not None, \
        "must fit before predict!"
    assert X_predict.shape[1] == len(self.coef_), \
        "the feature number of X_predict must be equal to X_train"

    X_b = np.hstack([np.ones((len(X_predict), 1)), X_predict])
    proba = self._sigmoid(X_b.dot(self._theta))
    return np.array(proba >= 0.5, dtype='int')

def score(self, X_test, y_test):
    y_predict = self.predict(X_test)
    assert len(y_true) == len(y_predict), \
               "the size of y_true must be equal to the size of y_predict"

    return np.sum(y_true == y_predict) / len(y_true)
複製代碼

多項式特徵

上述咱們的假設,都是將決策邊界看做是一條直線。不少時候樣本點的分佈是非線性的。咱們能夠引入多項式項,進而改變樣本的分佈狀態。

首先咱們模擬一個非線性分佈的數據集:

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(666)
X = np.random.normal(0, 1, size=(200, 2))
y = np.array(X[:,0]**2 + X[:,1]**2 < 1.5, dtype='int')

plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
plt.show()
複製代碼

對於這樣一個數據集,只能添加多項式來解決,代碼以下:

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler

def PolynomialLogisticRegression(degree):
    return Pipeline([
        # 給樣本特徵添加多形式項;
        ('poly', PolynomialFeatures(degree=degree)),
        # 數據歸一化處理;
        ('std_scaler', StandardScaler()),
        ('log_reg', LogisticRegression())
    ])

poly_log_reg = PolynomialLogisticRegression(degree=2)
poly_log_reg.fit(X, y)

plot_decision_boundary(poly_log_reg, axis=[-4, 4, -4, 4])
plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
plt.show()
複製代碼

最終數據的決策邊界以下:

def plot_decision_boundary(model, axis):
    x0, x1 = np.meshgrid(
        np.linspace(axis[0], axis[1], int((axis[1]-axis[0])*100)).reshape(-1,1),
        np.linspace(axis[2], axis[3], int((axis[3]-axis[2])*100)).reshape(-1,1)
    )
    X_new = np.c_[x0.ravel(), x1.ravel()]
    
    y_predict = model.predict(X_new)
    zz = y_predict.reshape(x0.shape)
    
    from matplotlib.colors import ListedColormap
    custom_cmap = ListedColormap(['#EF9A9A','#FFF59D','#90CAF9'])
    
    plt.contourf(x0, x1, zz, linewidth=5, cmap=custom_cmap)
複製代碼

多分類問題

上面咱們提到邏輯迴歸只能解決二分類問題,處理多分類問題須要額外轉換一下。一般有兩種途徑:

  1. OVR(One vs Rest),一對剩餘的意思。
  2. OVO(One vs One),一對一的意思。

這兩種方法不單單能夠針對邏輯迴歸算法,對於全部二分類機器學習算法均可以使用此方法進行改造,將二分類問題轉換成多分類問題。

OVR

ovr

如上圖所示,對 n 種類型的樣本進行分類時,分別取某一類樣本做爲一類,將剩下 n-1 類樣本看做是另一類,這樣就能夠轉換成 n 個二分類問題。最終能夠獲得 n 個算法模型(如上圖所示,最終會有 4 個算法模型),將待預測的樣本分別傳入這 n 個模型中,所得機率最高的那個模型對應的樣本類型爲預測結果。

在 sklearn 中對於邏輯迴歸的多分類問題,默認採用的就是 ovo 的方式。同時 sklearn 也提供了一種通用的調用方式:

from sklearn.multiclass import OneVsRestClassifier
from sklearn.linear_model import LogisticRegression

log_reg = LogisticRegression()
ovr = OneVsRestClassifier(log_reg)
ovr.fit(X_train, y_train)
ovr.score(X_test, y_test)
複製代碼

OVO

ovo

n 類樣本中,每次挑選出兩類樣本,最終造成 \mathrm{C}_{\mathrm{n}}^{2} 種二分類狀況,也就是 \mathrm{C}_{\mathrm{n}}^{2} 個算法模型,有 \mathrm{C}_{\mathrm{n}}^{2} 個預測結果,這些結果中種類最多的樣本類型,就是最終的預測結果。

在 sklearn 邏輯迴歸的實現中,對於多分類默認使用的是 ovr,若是使用 ovo 的話須要指定一下 multi_class,同時 solver 參數也須要改一下(p.s: sklearn 中對於損失函數最有化不是採用上述咱們描述的梯度降低):

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression

iris = datasets.load_iris()

X = iris.data
y = iris.target

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)

log_reg2 = LogisticRegression(multi_class="multinomial", solver="newton-cg")
log_reg2.fit(X_train, y_train)
log_reg2.score(X_test, y_test)
複製代碼
from sklearn.multiclass import OneVsRestClassifier

ovr = OneVsRestClassifier(log_reg)
ovr.fit(X_train, y_train)
ovr.score(X_test, y_test)

from sklearn.multiclass import OneVsOneClassifier

ovo = OneVsOneClassifier(log_reg)
ovo.fit(X_train, y_train)
ovo.score(X_test, y_test)
複製代碼

正則化

一般正則化的表達形式以下:

J(\theta)+\alpha L_{1}

能夠轉換一下,改變超參數位置。若是超參數 C 越大,原損失函數 J(\theta) 的地位相對較重要。若是超參數很是小,正則項的地位相對較重要。若是想讓正則項不重要,須要增大參數 C。sklearn 中通常都是採用的這樣的表達方式。

C \cdot J(\theta)+L_{1}

sklearn 中的邏輯迴歸算法自動封裝了模型的正則化的功能,只須要調整 C 和 penalty(正則項選擇 L_{1} 或者 L_{2}

相關文章
相關標籤/搜索