機器學習:邏輯迴歸

SXWisON發表於2024-12-02

簡介

在前兩篇文章中,我們詳細探討了如何利用取樣資料來估計迴歸曲線。接下來,在本節中,我們將深入討論如何處理分類問題。

章節安排

  1. 背景介紹
  2. 數學方法
  3. 程式實現

背景介紹

線性可分


線性可分是指在多維空間\(\mathbb{R}^D\)中,對於任意兩個類別的資料,總是存在一個超平面,可以將這兩個類別的資料點完全分開。

在二分類問題中,如果資料集是線性可分的,那麼可以找到一個超平面,使得螢幕的一側的所有點屬於一個類別,而另一側的所有點都屬於另一個類別。

設資料集\(D=\{\text{X},\text{y}\}\),其中\(\text{X}\)為輸入特徵向量,\(\text{y}\)為類別標籤。

如果存在一個超平面\(L:z=Xw+b\),使得:

\[\begin{align*} \forall y_i=1,\text{x}_iw+b>0\\ \forall y_i=0,\text{x}_iw+b<0 \end{align*} \]

則稱該資料集\(\text{X}\)是線性可分的;其中\(w\)為權重向量,\(b\)為偏置項,\(\text{x}_i\)為第\(i\)組資料,是矩陣\(X\)的第\(i\)行.

在一些情形下,並不是嚴格線性可分的,也就是說不存在一個超平面能夠將所有不同類別的點完全分隔開來。這種情況下,我們可能會考慮使用“寬鬆的線性可分”(Soft Margin)的概念。
在寬鬆線性可分中,定義鬆弛變數\(\xi\),原條件改為

\[\begin{align*} \forall y_i&=1,\text{x}_iw+b>-\xi\\ \forall y_i&=0,\text{x}_iw+b<\xi \end{align*} \]

注意到,對於任何一個超平面\(L\),總是存在一個足夠大的鬆弛變數\(\xi\)使得該超平面滿足寬鬆線性可分條件。
因此,一般認為,對於某一個超平面\(L_i\),使得其滿足寬鬆線性條件的最小常數\(\xi_i\)越小,則說明該直線劃分效果越好。

初識啟用函式


超平面\(L\)是一個從\(\mathbb{R}^D\)\(\mathbb{R}\)的對映(函式)。其值域為\((-\infty,\infty)\)。然而,在實際應用中,通常希望輸出的範圍現在\([0,1]\)之間,以便於解釋和處理。為了實現這一目標,通常會引入啟用函式

Sigmoid函式是一個經典的啟用函式,因其連續性和較低的計算複雜度而在機器學習中得到了廣泛的應用。Sigmoid 函式的定義如下:

\[\sigma(z)=\frac{1}{1+e^{-z}} \]

主要特點

  1. 連續性和可導性:
    Sigmoid 函式及其導數都是連續的,這使得它非常適合用於基於梯度下降的最佳化演算法。

  2. 輸出範圍:
    Sigmoid 函式的輸出範圍是 ((0, 1)),這使其在二分類問題中特別有用。它可以將線性組合的輸出轉換為一個機率值,從而更容易解釋模型的預測結果。

  3. 計算複雜度:
    Sigmoid 函式的計算相對簡單,不涉及複雜的數學運算,這有助於提高模型的訓練速度。同時,其導函式可以方便的從原函式計算,即:\(\sigma'(z) = \sigma(z) \cdot (1 - \sigma(z))\)

邏輯迴歸


邏輯迴歸(Logistic Regression)是一種廣泛應用於分類問題的統計模型和機器學習演算法。儘管名稱中包含“迴歸”,但它實際上主要用於解決分類問題,特別是二分類問題。

工作原理


  1. 線性組合
    首先,邏輯迴歸模型對輸入特徵進行線性組合,也稱對輸入進行評估:

    \[z = \mathbf{w}^T \mathbf{x} + b \]

  2. Sigmoid變換
    然後,將評估的結果\(z\)透過Sigmoid函式進行變換:

    \[\hat{y} = \sigma(z) = \frac{1}{1 + e^{-(\mathbf{w}^T \mathbf{x} + b)}} \]

    Sigmoid函式的輸出 \(\hat{y}\) 可以解釋為樣本屬於正類的機率。

  3. 決策邊界
    通常,選擇一個閾值(例如\(0.5\))來決定分類結果:

    \[y = \begin{cases} 1 & \text{如果 } \hat{y} \geq 0.5 \\ 0 & \text{如果 } \hat{y} < 0.5 \end{cases} \]

損失函式


邏輯迴歸的損失函式通常採用對數損失(Log Loss)或稱交叉熵損失(Cross-Entropy Loss):

\[L(\mathbf{w}, b) = -\frac{1}{N} \sum_{i=1}^{N} \left[ y_i \log(\hat{y}_i) + (1 - y_i) \log(1 - \hat{y}_i) \right] \]

其中,\(N\)是樣本數量,\(y_i\)是真實標籤,\(\hat{y}_i\)是預測的機率值。

最佳化


本文將介紹如何採用梯度下降法最佳化邏輯迴歸模型。
在梯度下降法中,核心的部分是計算損失\(\text{LOSS}\)關於引數\(\text{w}\)\(b\)的梯度,其反應了引數更新的方向和步長。

通常採用鏈式法則計算梯度,以引數\(\text{w}\)為例,有:

\[\nabla_{\text{w}}\text{LOSS}=\frac{\partial \text{LOSS}}{\partial \text{w}}= \frac{\partial \text{LOSS}}{\partial \hat{\text{y}}} \frac{\partial \hat{\text{y}}}{\partial \text{z}} \frac{\partial \text{z}}{\partial \text{w}} \]

梯度下降法中,採用梯度的反方向作為更新方向,其公式為:

\[w:=w-\lambda\cdot\nabla_{\text{w}}\text{LOSS} \]

其中,\(\lambda\)為學習率。

程式實現


在上一篇文章《機器學習:線性迴歸(下)》中已經講述了超平面\(L\)的實現方法;因此,本文中將討論諸如啟用函式對數損失等上一章為設計的部分的程式實現。

啟用函式


下述函式用於計算輸入矩陣或向量的每個元素的Sigmoid函式值。

MatrixXd Sigmoid::cal(const MatrixXd& input) {
    return input.unaryExpr([](double x) { return 1.0 / (1.0 + exp(-x)); });
}

這段程式碼是一個簡短的函式實現,程式碼解釋如下:

  1. input.unaryExpr
    unaryExpr 是Eigen庫中的一個函式,用於對矩陣或向量的每個元素應用一個給定的單變數函式。在這裡,input 是一個Eigen矩陣或向量。

  2. Lambda函式
    [](double x) { return 1.0 / (1.0 + exp(-x)); } 是一個Lambda函式,它定義了一個匿名函式,接受一個 double 型別的引數 x,並返回 1.0 / (1.0 + exp(-x))。這個函式實現了Sigmoid函式的計算。

MatrixXd Sigmoid::grad(const MatrixXd& input) {
    Matrix temp = input.unaryExpr([](double x) { return 1.0 / (1.0 + exp(-x)); });
    return temp.cwiseProduct((1 - temp.array()).matrix());
}

這段程式碼實現了啟用函式的梯度的計算,類似與Sigmoid::cal(),先計算啟用函式\(\sigma(z)\)的值,再採用逐個元素相乘cwiseProduct計算(即Hadamard乘積)

\[A \circ B = \begin{bmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{bmatrix} \circ \begin{bmatrix} b_{11} & b_{12} \\ b_{21} & b_{22} \end{bmatrix} = \begin{bmatrix} a_{11} \cdot b_{11} & a_{12} \cdot b_{12} \\ a_{21} \cdot b_{21} & a_{22} \cdot b_{22} \end{bmatrix} \]

對數損失

下述函式分別採用Eigen的矩陣計算方法,實現了對數損失及對數損失的梯度的計算

double LogisticLoss::computeLoss(const MatrixXd& predicted, const MatrixXd& actual) {
    MatrixXd log_predicted = predicted.unaryExpr([](double p) { return log(p); });
    MatrixXd log_1_minus_predicted = predicted.unaryExpr([](double p) { return log(1 - p); });

    MatrixXd term1 = actual.cwiseProduct(log_predicted);
    // MatrixXd term2 = (1 - actual).cwiseProduct(log_1_minus_predicted);
    MatrixXd term2 = (1 - actual.array()).matrix().cwiseProduct(log_1_minus_predicted);

    double loss = -(term1 + term2).mean();

    return loss;
}

MatrixXd LogisticLoss::computeGradient(const MatrixXd& predicted, const MatrixXd& actual) {
    MatrixXd temp1 = predicted - actual;
    MatrixXd temp2 = predicted.cwiseProduct((1 - predicted.array()).matrix());

    return (temp1).cwiseQuotient(temp2);
}

為了便於讀者理解、學習,下面給出了LogisticLoss::computeLoss()函式的標量方法實現(採用矩陣索引):

double computeLoss(const MatrixXd& predicted, const MatrixXd& actual) {
    int n = predicted.rows();
    double loss = 0.0;
    for (int i = 0; i < n; ++i) {
        double p = predicted(i, 0);
        double y = actual(i, 0);
        loss += -(y * log(p) + (1 - y) * log(1 - p));
    }
    return loss / n;
}

相關文章