ESL 4: Linear Methods for Classification

ESL 4: Linear Methods for Classification

4.2 Linear Regression of an Indicator Matrix

对于分类问题,我们可以把每个分类用 indicator variable 来编码。

例如,如果有 K 个类型,我们可以定义:

\textbf{y} = [y_1, ..., y_K]^T

当样本是第 i 类时,y_i = 1,其它值为 0。

当我们有 N 个样本时,可以写成一个 N \times K 矩阵(indicator response matrix):

\textbf{Y} = [\textbf{y}_1, ..., \textbf{y}_N]^T

这个矩阵每一行仅有一个 1,其余值为 0。这个 1 代表该行样本的类型。

这实际上就是 one-hot 编码,这种编码相对于直接使用整数 1, ..., K 来表示的有优势在于 类型间的距离都是一样的

利用第三章的 Linear Regression,我们可以得出系数矩阵 \hat{\textbf{B}}

\hat{\textbf{B}} = \textbf{X}(\textbf{X}^T \textbf{X})^{-1} \textbf{X}^T \textbf{Y}

\hat{\textbf{B}} 是一个 (p+1) \times K 的矩阵。\textbf{X}N \times (p+1) 矩阵。

得到估计值:

\hat{\textbf{Y}} = \textbf{X}(\textbf{X}^T \textbf{X})^{-1} \textbf{X}^T \textbf{Y}

该方法的步骤是:

  1. 对类型进行 one-hot 编码得到 \textbf{Y}
  2. 将问题视作一个多变量的线性回归问题,对 one-hot 编码后的每个 bit 进行拟合得到 \hat{\textbf{B}}
  3. 在判断类别时,选择 \textbf{X}\hat{\textbf{B}} 每行的最大值所表示的类型

使用 Linear Regression 解决分类问题的最大问题在于,当类别数 K \leq 3 时,可能会出现某个类被掩盖(mask)的情况。其本质原因是 Linear Regression 的“刚性”特质,即它的分界面是不够灵活的。

举个简单的例子,对于以下三个 1 维正态分布:

  • class1: x \sim N(1, 1)
  • class2: x \sim N(5, 1)
  • class3: x \sim N(9, 1)

分布如下:

masking1.png

如果我们用 Linear Regression 拟合,那我们可以得到 3 组 \beta_0, \beta_1,分别对应第 i 组,可以用来计算 y_i 的值:

y_i = \beta_0 + \beta_1 x

masking2.png

可以看到,y_1 (对应class2)从来不是最大值。也就是说我们的分类结果中只有 class1 和 class3 了,class2 被 mask 了。可以通过结果验证:

pd.DataFrame(X*B).T.idxmax().value_counts()
# 2    1505
# 0    1495
# dtype: int64

代码:

import numpy as np
import pandas as pd

def generate_nd_sample(name, mu_array, sigma, N):
    xx = {}
    for i in range(1, len(mu_array) + 1):
        xi = np.random.normal(mu_array[i-1], sigma, N)
        xx[f"x{i}"] = xi
    xx["name"] = name
    return pd.DataFrame(xx).astype({"name":"category"})

s1 = generate_nd_sample("class1", [1], 1, 1000)
s2 = generate_nd_sample("class2", [5], 1, 1000)
s3 = generate_nd_sample("class3", [9], 1, 1000)

s = pd.concat([s1, s2, s3]).reset_index(drop=True)

# Linear Regression

tmp = s.copy()
tmp.insert(0, "ones", np.ones(s.shape[0]))
X = np.matrix(tmp.drop("name", axis=1).to_numpy().T).T

Y = np.matrix(pd.get_dummies(s.name))

def LR_beta(X, Y):
    # class count
    K = Y.shape[1]
    return (X.T * X).I * X.T * Y

B = LR_beta(X, Y)

# Plot

from bokeh.io import output_notebook
output_notebook()
from bokeh.palettes import viridis
from bokeh.plotting import figure, show
import itertools

def create_histogram_figure(sample_data):
    # sample data must be like:
    # | x | category |
    assert sample_data.shape[1] == 2, "input must be of shape (N, 2)"

    x = sample_data.iloc[:, 0]
    categories = sample_data.iloc[:, 1].unique()

    fig = figure(x_axis_label=x.name, y_axis_label="counts")

    color_gen = itertools.cycle(viridis(len(categories)))
    for (category, color) in zip(categories, color_gen):
        data = sample_data[sample_data.iloc[:, 1] == category]
        counts, bins = np.histogram(data.iloc[:, 0], bins='auto')
        fig.quad(top=counts, bottom=0, left=bins[:-1], right=bins[1:],
                   alpha=0.5, color=color, legend_label=str(category))

    return fig

def create_line_figure(lines, x_start, x_end):
    fig = figure(x_axis_label="x", y_axis_label="y")
    color_gen = itertools.cycle(viridis(lines.shape[0]))
    for i in range(lines.shape[0]):
        b0, b1 = lines[i,0], lines[i, 1]
        fig.line(x=[x_start, x_end], y = [b0 + b1*x_start, b0 + b1*x_end],
                 line_width=2, alpha=0.5, color=next(color_gen),
                 legend_label=f"y{i} = {b0:.6f} + {b1:.6f}x")
    return fig

hist_fig = create_histogram_figure(s)
line_fig = create_line_figure(B.T, s.x1.min(), s.x1.max())
from bokeh import layouts
show(layouts.column(hist_fig, line_fig))

4.3 Linear discriminant analysis

为了获得最优的分类结果,我们需要知道后验概率(X = x 时属于第 k 类的概率):

\text{Pr}(G=k | X=x) = \frac{f_k(x) \pi_k}{\sum_{i=1}^K f_i(x) \pi_i}

因为本质上,我们是在找到一个 k 使得后验概率最大,即:

\begin{align} \hat{k} &= \mathop{\arg \max}_{k} \text{Pr}(G=k | X=x) \\ &= \mathop{\arg \max}_{k} f_k(x) \pi_k \\ &= \mathop{\arg \max}_{k} [\ln(f_k(x)) + \ln(\pi_k)] \end{align}

这被称为 判别函数(discriminant function),其中:

  • f_i(x) 是第 i 类样本取 x 的概率
  • \pi_i 是属于第 i 类的先验概率

这里的难点在于确定 f_i(x),显然 \pi_i 的估计是可以通过样本数据直接得到的。

线性判别分析(Linear Discriminant Analysis, LDA)假设变量 X 服从多维高斯分布(X 包含多维):

f_k(x) = \frac{1}{(2 \pi)^{p/2} |\mathbf{\Sigma}_k|^{1/2}} e^{-\frac{1}{2}(x - \mu_k)^T \mathbf{\Sigma}_k^{-1} (x - \mu_k)}

带入最优分类的式子, 逐步去掉与 k 无关的部分:

\begin{align} \hat{k} &= \mathop{\arg \max}_{k} [\ln(f_k(x)) + \ln(\pi_k)] \\ &= \mathop{\arg \max}_{k} [- \ln((2 \pi)^{p/2} |\mathbf{ \Sigma }_k|^{1/2}) - \frac{1}{2}(x - \mu_k)^T \mathbf{ \Sigma }_k^{-1} (x - \mu_k) + \ln(\pi_k)] \\ &= \mathop{\arg \max}_{k} [- \frac{1}{2} \ln |\mathbf{ \Sigma }_k| - \frac{1}{2}(x - \mu_k)^T \mathbf{\Sigma}_k^{-1} (x - \mu_k) + \ln(\pi_k)] \\ \end{align}

此时,判别函数为:

\delta_k(x) = - \frac{1}{2} \ln |\mathbf{ \Sigma }_k| - \frac{1}{2}(x - \mu_k)^T \mathbf{\Sigma}_k^{-1} (x - \mu_k) + \ln(\pi_k)

x 的二次函数。因此称为二次判别分析(Quadratic Discriminant Analysis, QDA)。

我们再 假设每个类中变量 X 分布的方差是相等的,则 \mathbf{\Sigma} 也与 k 无关了,可以进步一化简判别函数为:

\delta_k(x) = x^T\mathbf{\Sigma}^{-1}\mu_k - \frac{1}{2}\mu_k^T\mathbf{\Sigma}^{-1} \mu_k + \ln(\pi_k)

我们可以看出,化简后判别函数对于 x线性 的。这说明两个类的分界面(即判别函数相等时)也是线性的。因此叫做线性判别分析(Linear Discriminant Analysis, LDA)。

实际中,我们可以通过样本估计高斯分布的参数:

  • \hat{\pi}_k = N_k / N,即第 k 类的样本数占总样本数的比例
  • \hat{\mu}_k = \sum_{g_i = k} x_i / N_k,即第 k 类样本 X 的平均值
  • \hat{\mathbf{\Sigma}} = \sum_{k=1}^K \sum_{g_i = k} (x_i - \hat{\mu}_k)(x_i - \hat{\mu}_k)^T / (N - K),对协方差矩阵的无偏估计,证明在 ESL3 中

有了判别函数的表达式 \delta_k(x),我们只需要依次带入 k = 1, ..., K, 当得到的 \delta_k(x) 最大时的 k 即为最佳分类。

4.3.2 Computation of LDA

协方差矩阵 \mathbf{\Sigma} 是一个对称矩阵,可以进行特征值分解:

\mathbf{\Sigma} = \mathbf{Q}\mathbf{\Lambda}\mathbf{Q}^T

其中:\mathbf{Q} 是单位正交矩阵,\mathbf{\Lambda} 是对角阵。带入判别函数有:

\begin{align} \delta_k(x) &= x^T\mathbf{\Sigma}^{-1}\mu_k - \frac{1}{2}\mu_k^T\mathbf{\Sigma}^{-1} \mu_k + \ln(\pi_k) \\ &= x^T\mathbf{Q}^{T}\mathbf{\Lambda}^{-1}\mathbf{Q}\mu_k - \frac{1}{2}\mu_k^T\mathbf{Q}^{T}\mathbf{\Lambda}^{-1}\mathbf{Q}\mu_k + \ln(\pi_k) \end{align}

令:

x^{*} = \mathbf{\Lambda}^{-\frac{1}{2}}\mathbf{Q}x

\mu^{*} = \mathbf{\Lambda}^{-\frac{1}{2}}\mathbf{Q} \mu

有:

\delta_k(x^{*}) = x^{* T}\mu_k^{*} - \frac{1}{2}\mu_k^{* T} \mu_k^{*} + \ln(\pi_k)

当我们判断某个样本 x_1 属于 m 和 n 中的哪一个类时,可以比较其判别函数,我们判断它是 m 类如果满足:

\delta_m(x_1^*) > \delta_n(x_1^*)

带入表达式有:

x^{*T} (\mu^*_m - \mu^*_n) > \frac{1}{2} (\mu^*_m + \mu^*_n)^T(\mu^*_m - \mu^*_n) - \ln(\pi_m/\pi_n)

这样看起来就非常直观了。 LDA 是将样本投影在两个类中心的连线上,并且比较它更靠近哪一边,以此决定它属于哪个类。当然,这个过程还考虑了两个类的先验概率(\ln(\pi_m/\pi_n) 项)。

4.3.3 Reduced-Rank Linear Discriminant Analysis

LDA 也是一种降维的手段。假设我们有 p 维特征,K 个类别。根据 4.3.2 中介绍的计算方式,我们一共有 K 个类中心点。他们一定在一个最高 K-1 维的空间里。

例:红酒分类

例如,对于 2 个类的分类问题,无论特征是多少维,我们只有 2 个类中心点。他们必定在一条直线(1维)上。同理,对于 3 个类的分类问题,我们只有 3 个类中心点。他们必定在一个平面(2维)内,如果特征维度大于等于 2。

因此,经过 LDA,原始数据总能被投影到一个超平面上,其维度为(对应 sklearn LDA 方法中的 n_components 参数):

L = \min(p, K-1)

这说明,p \gg K 时,使用 LDA 可以将一个 p 维的输入降维到 K-1

我们以 sklearn 中的 wine 数据集为例。它具有 13 维特征,3 个类别。我们使用 LDA 可以将这些数据投影到一个 2 维的平面上。

lda.png

代码:

import pandas as pd
import numpy as np
from sklearn import datasets

wine = datasets.load_wine()
X = pd.DataFrame(wine.data, columns = wine.feature_names)
y = wine.target

# LDA projection
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
model = LinearDiscriminantAnalysis(n_components=2).fit(X, y)
model.transform(X)

# plot
data_to_plot = pd.DataFrame(
    np.insert(model.transform(X), 2, y, axis=1),
    columns=["x1", "x2", "class"])
show(create_scatter_figure("LDA projection for wine data", data_to_plot))

Find the optimal subspace

在实际中,如果 K 也很大,那还需要进一步降低维度。假设我们目标是降低到 L 维(L \ll K-1),即寻找超平面 H_{K-1} 的最优子空间 H_L

Fisher 将这个问题提炼为:

找到线性组合 Z = a^T X 使得类间的方差相对于类内方差最大

X 的类内(Within)方差为:

\textbf{W} = \sum_{k=1}^K \sum_{g_i=k} (x_i - \mu_k)(x_i - \mu_k)^T

X 的类间(Between)方差为:

\textbf{B} = \sum_{k=1}^K (\mu - \mu_k)(\mu - \mu_k)^T

根据向量的统计特性,有 Z 的类内方差为 a^T \textbf{W} a,类间方差为 a^T \textbf{B} a

于是,Fisher 实际上是在解决这个优化问题:

\mathop{\arg \max}_a \frac{a^T \textbf{B} a}{a^T \textbf{W} a}

由于我们总可以通过调节求得的 a 的系数使得 a^T \textbf{W} a = 1,我们可以将其改写为:

\begin{align} \mathop{\arg \max}_a & ~ a^T \textbf{B} a \\ \text{s.t.} & ~ a^T \textbf{W} a = 1 \end{align}

假设 \mathbf{W} 可逆,令 u = \mathbf{W}^{\frac{1}{2}} a,由于 \textbf{W} 是对称矩阵,有:

\begin{align} \mathop{\arg \max}_u & ~ u^T \mathbf{W}^{-\frac{1}{2}}\textbf{B}\mathbf{W}^{-\frac{1}{2}} u \\ \text{s.t.} & ~ u^Tu = 1 \end{align}

\mathbf{W}^{-\frac{1}{2}}\textbf{B}\mathbf{W}^{-\frac{1}{2}} 也是对称矩阵,必定存在特征值分解:

\mathbf{W}^{-\frac{1}{2}}\textbf{B}\mathbf{W}^{-\frac{1}{2}} = \mathbf{Q} \mathbf{\Lambda} \mathbf{Q}^T

因此化简为:

\begin{align} \mathop{\arg \max}_u & ~ u^T \mathbf{Q} \mathbf{\Lambda} \mathbf{Q}^T u \\ \text{s.t.} & ~ u^Tu = 1 \end{align}

再令 v = \mathbf{Q}^T u,由于 \mathbf{Q} 是单位正交矩阵,v^T v = 1 依然成立:

\begin{align} \mathop{\arg \max}_v & ~ v^T \mathbf{\Lambda} v \\ \text{s.t.} & ~ v^Tv = 1 \end{align}

\mathbf{\Lambda} 是对角矩阵,所以 该优化问题本质是求最大特征值。假设 \lambda_i 最大,显然在 v_i = 1 时取得最大值 \lambda_i^2

由于 \mathbf{\Lambda}\mathbf{W}^{-\frac{1}{2}}\textbf{B}\mathbf{W}^{-\frac{1}{2}} 的特征值,为了简化求解,我们利用定理:

\mathbf{AB}\mathbf{BA} 具有同样的特征值,如果 x\mathbf{AB} 的某个特征向量,则对应的 \mathbf{BA} 的特征向量是 y = \mathbf{B}x

可以得到 \mathbf{\Lambda} 也是 \mathbf{W}^{-1}\textbf{B} 的特征值。

假设 \mathbf{W}^{-1}\textbf{B} 的最大特征值为 \lambda_i,对应的特征向量为 \xi,则所求的线性变换为:

a = \mathbf{W}^{-\frac{1}{2}} \xi

这样就找到了 H_L 的 1 个维度,同理,我们选取 top L 个维度,即得到了 H_L

定理证明

假设 \lambda\mathbf{AB} 的任意特征值。

\mathbf{AB}x = \lambda x

y = \mathbf{B}x,则有:

\mathbf{A}y = \lambda x

同时左乘 \mathbf{B}

\mathbf{BA}y = \lambda \mathbf{B}x = \lambda y

例:手写数字分类

手写数字分类是通过 8x8 的手写数字图片判断是 0-9 中的哪个数字。显然,这是一个具有 p = 8 \times 8 = 64 维特征,K = 10 个类别的分类任务。我们可以用 sklearn 库的 LDA 得到下面的结果(降至 2 维 plot):

sklearn_lda.png
import pandas as pd
import numpy as np
import scipy
from sklearn import datasets

digits = datasets.load_digits()
X = pd.DataFrame(digits.data, columns = digits.feature_names)
y = digits.target

trainX = X[:len(X)//2]
trainY = y[:len(y)//2]

from sklearn.discriminant_analysis import LinearDiscriminantAnalysis

model = LinearDiscriminantAnalysis(n_components=2, solver="eigen", shrinkage=0.01).fit(trainX, trainY)

# reduce rank to 2 dimension data
reduceRankX = model.transform(trainX)

显然,直接调包隐藏了太多细节,为了加深理解,我们根据公式推导自己手动实现一个:

def shrink(cov, shrinkage):
    dimensions = cov.shape[0]
    return (1-shrinkage) * cov  + shrinkage * np.trace(cov) / dimensions * np.identity(dimensions)

def my_lda(X, y, n_components, shrinkage):
    T = X.cov()
    W = X.groupby(y).apply(lambda g: g.cov() * (len(g)-1)
                          ).groupby(level=1).sum() / X.shape[0]

    shrunkW = shrink(W, shrinkage)
    invShrunkW = np.linalg.inv(shrunkW)
    shrunkB = shrink(T, shrinkage) - shrunkW

    # eigen values is ascending
    eigenvalues, eigenvectors = np.linalg.eigh(invShrunkW.dot(shrunkB))

    # eigen vectors with greatest eigen values
    xi = eigenvectors[:,::-1][:,:n_components]


    # L = np.linalg.cholesky(invShrunkW)
    # return L.dot(xi)

    # W^{-1/2}, not Cholesky
    return scipy.linalg.sqrtm(invShrunkW).dot(xi)

其中,shrink 函数是为了处理当 \mathbf{W}奇异矩阵 (不可逆)的情形。我们可以使用 shrunk covariance 来使其变为可逆矩阵再处理。这在输入 X 是稀疏矩阵时很常见,例如手写数字分类。

\mathbf{\Sigma}_{\text{shrunk}} = (1 - \alpha) \hat{\mathbf{\Sigma}} + \alpha \frac{\mathop{tr} \hat{\mathbf{\Sigma}}}{p} \mathbf{I}

其中 \alpha 是 shrinkage,p 是特征维度。

另外还需要注意区分矩阵平方根 sqrtm 与 Cholesky Decomposition。

矩阵平方根指 \mathbf{B} \mathbf{B} = \mathbf{B}^T \mathbf{B} = \mathbf{A},要求 \mathbf{B} 是对称矩阵。

Cholesky Decomposition 指 \mathbf{L}^T \mathbf{L} = \mathbf{A},要求 \mathbf{L} 是三角矩阵。

使用自己实现的 LDA 得到与 sklearn 实现类似的结果:

my_lda.png

可以看出,在降到 2 维后,LDA 还是能够清楚区分出数字 0, 1, 2, 3, 4, 6,但是存在一些数字的类别重叠在一起的情况。此时可以 增加维度 L 解决。

下面是第3、4 维:

extra_dimension_lda.png

可以看出,它较好的补足了 1、2 维无法正确分类的数字。对于数字 5、7 有了清楚的分类。

4.4 Logistic regression

逻辑回归希望用输入 x 的线性组合建模属于 k 类的后验概率,并且要求所有概率之和为 1。

以最后一个类(K 类)的后验概率 \text{Pr} (G = K | X = x) 为比较基准,有:

\ln \frac{\text{Pr} (G = k | X = x)}{\text{Pr} (G = K | X = x)} = \beta_{k0} + \beta_{k1}^T x

可以写为:

\text{Pr} (G = k | X = x) = \begin{cases} \dfrac{e^{\beta_{k0} + \beta_{k1}^Tx}}{1 + \sum_{l=1}^{K-1} e^{\beta_{l0} + \beta_{l1}^Tx}}, & k = 1, 2, ..., K-1 \\ \dfrac{1}{1 + \sum_{l=1}^{K-1} e^{\beta_{l0} + \beta_{l1}^Tx}}, & k = K \end{cases}

4.4.1 Fitting Logistic Regression Models

对于第 k 类的某个样本 i,我们希望找到一组模型参数 \theta,使模型认为该样本属于 k 的概率(即 正确分类概率)尽量大。我们将这个概率计作:

\text{Pr} (G = k | X = x) = p_{k} (x; \theta)

则该优化问题是:

\begin{align} \hat{\theta} = \mathop{\arg \max}_{\theta} ~ p_{k} (x; \theta) \end{align}

对于所有样本,我们目标是令他们 被正确分类的概率和最大

\ell (\theta) = \sum_{i=1}^N \ln p_{g_i} (x_i; \theta)

为了简化这个问题,我们只考虑而 K=2二分类 问题情况。此时样本要么属于 1 类要么属于 2 类。

为了简单表示:

p_{k} (x; \theta) = \begin{cases} p_1 (x; \theta), & k = 1 \\ 1 - p_1(x; \theta), & k = 2 \end{cases}

我们可以假设样本观测值 y_i = 1 代表属于第 1 类,y_i = 0 代表 不属于 第 1 类(那么肯定属于第 2 类)。则

p_{k} (x_i; \theta) = y_i p_1 (x_i; \theta) + (1-y_i)(1-p_1(x_i; \theta))

则有:

\begin{align} \ell (\theta) &= \sum_{i=1}^N \ln p_{g_i} (x_i; \theta) \\ &= \sum_{i=1}^N {y_i \ln p_1 (x_i; \theta) + (1-y_i) \ln (1-p_1(x_i; \theta))} \\ &= \sum_{i=1}^N {\ln(1 - p_1(x_i; \theta)) + y_i \ln \frac{ p_1 (x_i; \theta)}{1 - p_1 (x_i; \theta)} } \\ &= \sum_{i=1}^N {\ln(1 - p_1(x_i; \theta)) + y_i \mathbf{\beta}^Tx_i } \\ &= \sum_{i=1}^N {y_i \mathbf{\beta}^Tx_i - \ln(1 + e^{\mathbf{\beta}^Tx_i}) } \end{align}

其中 \mathbf{\beta} = [ \beta_{10}, \beta_{11}, ... \beta_{1p} ],对应 x = [1, x_1, x_2, ... x_p]

求解这个优化问题可以令其对 \beta 的导数为 0:

\frac{\partial \ell(\beta)}{\partial \beta} = \sum_{i=1}^N x_i(y_i - p_1(x_i; \beta)) = 0,

为了求解 \dfrac{\partial \ell(\beta)}{\partial \beta} = 0,我们可以用凸优化中的 Newton-Raphson 法。即选取一个任意初始 x_n(不是初始解,因为要求的就是解),并取其切线与 x 轴交点 x_{n+1}

\beta^{\text{new}} = \beta - (\frac{\partial^2 \ell(\beta)}{\partial \beta \partial \beta^T})^{-1} \frac{\partial \ell(\beta)}{\partial \beta}

其中 (p+1) \times (p+1) 维 Hessian 矩阵:

\begin{align} \frac{\partial^2 \ell(\beta)}{\partial \beta \partial \beta^T} &= - \sum_{i=1}^N x_i \frac{\partial p_1(x_i; \beta)}{\partial \beta^T} \\ &= - \sum_{i=1}^N x_i \dfrac{\partial \dfrac{e^{\beta^T x_i}}{1 + e^{\beta^T x_i}} }{\partial \beta^T} \\ &= - \sum_{i=1}^N x_i \frac{x_i^T e^{\beta^T x_i}}{(1 + e^{\beta^T x_i})^2} \\ &= -\sum_{i=1}^N x_i x_i^T p_1(x_i; \beta) (1 - p_1(x_i; \beta)) \end{align}

我们将他们写成矩阵形式:

\frac{\partial \ell(\beta)}{\partial \beta} = \mathbf{X}^T(\mathbf{y} - \mathbf{p})

\frac{\partial^2 \ell(\beta)}{\partial \beta \partial \beta^T} = \mathbf{X}^T \mathbf{W} \mathbf{X}

其中 \mathbf{X}N \times (p+1) 矩阵,\mathbf{y} - \mathbf{p}N 维列向量,\mathbf{W}N \times N 对角矩阵,第 i 个元素是 p_1(x_i; \beta) (1 - p_1(x_i; \beta))

带入 Newton-Raphson 公式:

\begin{align} \beta^{\text{new}} &= \beta - (\frac{\partial^2 \ell(\beta)}{\partial \beta \partial \beta^T})^{-1} \frac{\partial \ell(\beta)}{\partial \beta} \\ &= \beta + (\mathbf{X}^T \mathbf{W} \mathbf{X})^{-1} \mathbf{X}^T(\mathbf{y} - \mathbf{p}) \\ &= (\mathbf{X}^T \mathbf{W} \mathbf{X})^{-1}\mathbf{X}^T \mathbf{W}(\mathbf{X}\beta + \mathbf{W}^{-1}(\mathbf{y} - \mathbf{p})) \\ &= (\mathbf{X}^T \mathbf{W} \mathbf{X})^{-1}\mathbf{X}^T \mathbf{W}\mathbf{z} \end{align}

这就是 迭代更新 的计算公式。由于 \mathbf{p}\mathbf{W} 都随 \beta 变化,因此我们需要在每轮迭代重新计算他们的值。

上式中,我们定义了:

\mathbf{z} = \mathbf{X}\beta + \mathbf{W}^{-1}(\mathbf{y} - \mathbf{p})

回想 最小二乘法 的公式:

\hat{\beta} = (\mathbf{X}^T \mathbf{X})^{-1} \mathbf{X}^T \mathbf{y}

可以发现两者非常相似,区别在于逻辑回归中多了一个权重对角矩阵 \mathbf{W}。因此这个算法也称为 iteratively reweighted least squares(重新加权迭代最小二乘法)。

接下来我们还需要一个 \beta初始值。一般情况下可以选 \beta = 0。该算法 不保证收敛

例:乳腺癌诊断

该案例通过 30 维特征来判断乳腺癌是恶性还是良性,是一个典型的 2 分类问题。

import pandas as pd
import numpy as np
from sklearn import datasets, model_selection

def load_sklearn_data(sk_data):
    X = pd.DataFrame(sk_data.data, columns = sk_data.feature_names)
    y = pd.Series(sk_data.target, name="target").apply(
        lambda index: sk_data.target_names[index]).astype("category")
    return X, y

X, y = load_sklearn_data(datasets.load_breast_cancer())
X_train, X_test, y_train, y_test = model_selection.train_test_split(
    X, y, test_size=0.2)

from sklearn.metrics import accuracy_score
from sklearn.linear_model import LogisticRegression

model = LogisticRegression(solver="newton-cg").fit(X_train, y_train)
accuracy_score(model.predict(X_test), y_test)

结果是 0.9385964912280702。我们再用自己实现的 LR 进行判断:

def sigmoid(x, beta):
    tmp = np.exp(x.dot(beta))
    return tmp / (1 + tmp)

class MyLogisticRegression:
    def __init__(self, max_iter, tolerance = 0.01):
        self.max_iter_ = max_iter
        self.tolerance_ = tolerance
        self.beta = None

    def fit(self, train_X, train_y):
        N, p = train_X.shape
        X = train_X.to_numpy()
        X = np.concatenate((np.atleast_2d(np.ones(N)).T,
                            train_X.to_numpy()), axis=1)
        self.beta = np.zeros(p+1)
        beta = self.beta
        for i in range(self.max_iter_):
            prob = np.apply_along_axis(sigmoid, 1, X, beta)
            W = np.diag(prob * (1 - prob))
            z = X.dot(beta) + np.linalg.inv(W).dot(train_y.cat.codes - prob)
            new_beta = np.linalg.inv(X.T @ W @ X) @ X.T @ W.dot(z)
            beta = new_beta

        self.beta = beta


    def predict(self, test_X):
        X = test_X.copy()
        X.insert(0, "x_0", np.ones(test_X.shape[0]))
        prob = X.apply(lambda row: sigmoid(row, self.beta), axis=1)
        return prob.apply(lambda x: 1 if x >= 0.5 else 0)

根据公式写代码,注意 \beta 的迭代更新即可。最大的难度是终止条件,因为迭代数次之后会遇到 Hessian 矩阵 \mathbf{X}^T \mathbf{W} \mathbf{X} 成了奇异矩阵的问题。

运行的结果表明分类正确率可以达到 0.9473684210526315

TODO: how to find a stopping criteria?

4.4.5 Logistic Regression or LDA?

在 4.3 中我们介绍了 LDA,回忆其判断某个样本属于 k 还是 l 的函数(当该式大于 0 时属于 k 类):

\begin{align} \ln \frac{\text{Pr}(G=k | X=x)}{\text{Pr}(G=l | X=x)} &= \ln \frac{f_k(x) \pi_k}{ f_l(x) \pi_l} \\ &= \ln \frac{\pi_k}{\pi_l} - \frac{1}{2}(\mu_k + \mu_l) \mathbf{\Sigma}^{-1} (\mu_k - \mu_l) + x^T \mathbf{\Sigma}^{-1} (\mu_k - \mu_l) \\ &= \alpha_{k0} + \alpha_{k1}^T x \end{align}

可以看出其形式与 logistic regression 一致:

\ln \frac{\text{Pr}(G=k | X=x)}{\text{Pr}(G=l | X=x)} = \beta_{k0} + \beta_{k1}^T x

那么他们俩是不是一样的方法呢?并不是。他们的主要区别是:

  • 假设不同。LDA 假设了 x 服从正态分布,且各个类的协方差矩阵相同。LR 没有限定 x 的分布。由于 LDA 假设了正态分布,它对于极端的 outlier 鲁棒性差(会影响分布函数)。

  • 优化目标不同。LDA 最大化 full log-likelihood,需要考虑 x 的边缘分布函数。LR 最大化 conditional likelihood,不需要考虑 x 的边缘分布函数。

比较难理解的是第二点。

首先,根据 Bayes 公式,在已知 B 发生时,A 的条件概率为:

P(A|B) = \frac{P( A \cap B ) }{P(B)}

因此对于分类问题,条件概率(conditional likelihood)在已知 X = x 时,样本类别 G = k 类的概率。它与 全概率(full likelihood) 之间存在关系:

\begin{align} \text{Pr}(G=k | X=x) &= \frac{\text{Pr}(X, G=k)}{\text{Pr}(X)} \\ &= \frac{\text{Pr}(X, G=k)}{\sum_{l=1}^K \text{Pr}(X, G=l)} \\ \end{align}

LDA 的目标是找到一个类别 k 使得后验概率最大,即:

\begin{align} \hat{k} &= \mathop{\arg \max}_{k} \text{Pr}(G=k | X=x) \\ &= \mathop{\arg \max}_{k} \frac{\text{Pr}(X, G=k)}{\sum_{l=1}^K \text{Pr}(X, G=l)} \end{align}

LDA 选择 对全概率建模,需要知道 x 的在每个类的密度函数 f_i(x),LDA 假设其为正态分布,且协方差相等。全概率为:

\text{Pr}(X, G=k) = f_k(x) \pi_k = \phi(x; \mu_k, \mathbf{\Sigma}) \pi_k

所以说它考虑的是 full log-likelihood。

对于 LR,它直接 对条件概率建模。即假设存在一组 \beta 能够使条件概率的 log ratio 和 x 有如下线性关系:

\ln \frac{\text{Pr}(G=k | X=x)}{\text{Pr}(G=l | X=x)} = \beta_{k0} + \beta_{k1}^T x

并且,这一组 \beta 使 所有样本正确分类的概率和 最大:

\begin{align} \hat{\theta} &= \mathop{\arg \max}_{\theta} ~ \ell (\theta) \\ &= \mathop{\arg \max}_{\theta} ~ \sum_{i=1}^N \ln p_{g_i} (x_i; \theta) \\ &= \sum_{i=1}^N y_i \mathbf{\beta}^Tx_i - \ln(1 + e^{\mathbf{\beta}^Tx_i}) \end{align}

其中不包含任何与边缘密度函数 \text{Pr}(X) 相关的部分,也没有假设 x 的分布。因此说它只考虑 conditional likelihood。

Reference

  1. masking in linear regression for multiple classes
  2. Linear discriminant analysis, explained
  3. shrunk covariance
  4. between-class scatter matrix calculation
  5. 逻辑回归
  6. Implementing logistic regression from scratch in Python
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349