逻辑回归:一个二分类器
一个用于二分类任务的逻辑回归类。
> 从 mlxtend.classifier 导入 LogisticRegression
概述
与 感知器
和 Adaline
相关,逻辑回归模型是一个用于二分类的线性模型。然而,与 Adaline 中最小化平方误差和(SSE)这样的线性成本函数不同,我们最小化的是一个 sigmoid 函数,即 logit 函数:
$$\phi(z) = \frac{1}{1 + e^{-z}},$$
其中 $z$ 定义为净输入
$$z = w_0x_0 + w_1x_1 + ... + w_mx_m = \sum_{j=0}^{m} w_j x_j= \mathbf{w}^T\mathbf{x}.$$
净输入又基于 logit 函数
$$logit(p(y=1 \mid \mathbf{x})) = z.$$
这里,$p(y=1 \mid \mathbf{x})$ 是给定特征 $\mathbf{x}$ 一个特定样本属于类 1 的条件概率。logit 函数接收在 [0, 1] 范围内的输入,并将其转换为整个实数范围的值。相反,logistic 函数接受整个实数范围的输入值,并将其转换为 [0, 1] 范围内的值。换句话说,logistic 函数是 logit 函数的反函数,它使我们能够预测某个特定样本属于类 1(或类 0)的条件概率。
在模型拟合后,条件概率 $p(y=1 \mid \mathbf{x})$ 通过阈值函数 $g(\cdot)$ 转换为二进制类别标签:
$$y = g({z}) = \begin{cases} 1 & \text{如果 $\phi(z) \ge 0.5$}\ 0 & \text{否则.} \end{cases} $$
或者等效地:
$$y = g({z}) = \begin{cases} 1 & \text{如果 z $\ge$ 0}\ 0 & \text{否则.} \end{cases} $$
目标函数 -- 对数似然函数
为了参数化逻辑回归模型,我们最大化似然函数 $L(\cdot)$(或最小化逻辑成本函数)。
我们将似然函数写为
$$L(\mathbf{w}) = P(\mathbf{y} \mid \mathbf{x};\mathbf{w}) = \prod_{i=1}^{n} P\big(y^{(i)} \mid x^{(i)}; \mathbf{w}\big) = \prod^{n}_{i=1}\bigg(\phi\big(z^{(i)}\big)\bigg)^{y^{(i)}} \bigg(1-\phi\big(z^{(i)}\big)\bigg)^{1-y^{(i)}},$$
在训练样本彼此独立的假设下。
在实践中,最大化这个方程的(自然)对数更为容易,这被称为对数似然函数:
$$l(\mathbf{w}) = \log L(\mathbf{w}) = \sum^{n}_{i=1} y^{(i)} \log \bigg(\phi\big(z^{(i)}\big)\bigg) + \big( 1 - y^{(i)}\big) \log \big(1-\phi\big(z^{(i)}\big)\big)$$
采取对数的一个好处是可以避免在非常小的似然值下发生数值下溢(以及浮点数学的挑战)。另一个好处是我们可以更容易地获得导数,使用加法技巧将因子的乘积重写为求和项,然后可以利用诸如梯度上升法等优化算法对其进行最大化。
目标函数 -- 逻辑成本函数
一个最大化对数似然的替代方案是定义一个需要最小化的代价函数 $J(\cdot)$;我们可以将对数似然重写为:
$$J(\mathbf{w}) = \sum_{i=1}^{m} - y^{(i)} \log \bigg( \phi\big(z^{(i)}\big) \bigg) - \big(1 - y^{(i)}\big) \log\bigg(1-\phi\big(z^{(i)}\big)\bigg)$$
$$ J\big(\phi(z), y; \mathbf{w}\big) =\begin{cases} -\log\big(\phi(z) \big) & \text{如果 $y = 1$}\ -\log\big(1- \phi(z) \big) & \text{如果 $y = 0$} \end{cases} $$
正如我们在上面的图中所看到的,我们对错误预测施加越来越大的惩罚成本。
梯度下降(GD)和随机梯度下降(SGD)优化
梯度上升与对数似然函数
通过基于梯度的优化来学习逻辑回归模型的权重系数,我们计算对数似然函数关于第 j 个权重的偏导数,具体如下:
$$\frac{\partial}{\partial w_j} l(\mathbf{w}) = \bigg(y \frac{1}{\phi(z)} - (1-y) \frac{1}{1-\phi{(z)}} \bigg) \frac{\partial}{\partial w_j}\phi(z)$$
作为一个中间步骤,我们计算sigmoid函数的偏导数,这将在后面派上用场:
\begin{align}
&\frac{\partial}{\partial z} \phi(z) = \frac{\partial}{{\partial z}} \frac{1}{1+e^{-z}} \\\\
&= \frac{1}{(1 + e^{-z})^{2}} e^{-z}\\\\
&= \frac{1}{1+e^{-z}} \bigg(1 - \frac{1}{1+e^{-z}} \bigg)\\\\
&= \phi(z)\big(1-\phi(z)\big)
\end{align}
现在,我们将 $$\frac{\partial}{\partial z} \phi(z) = \phi(z) \big(1 - \phi(z)\big)$$ 重新代入对数似然偏导数方程中,并得到下面所示的方程:
\begin{align} & \bigg(y \frac{1}{\phi{(z)}} - (1 - y) \frac{1}{1 - \phi(z)} \bigg) \frac{\partial}{\partial w_j} \phi(z) \\ &= \bigg(y \frac{1}{\phi{(z)}} - (1 - y) \frac{1}{1 - \phi(z)} \bigg) \phi(z) \big(1 - \phi(z)\big) \frac{\partial}{\partial w_j}z\\ &= \big(y(1-\phi(z)\big) - (1 - y) \phi(z)\big)x_j\\ &=\big(y - \phi(z)\big)x_j \end{align}
现在,为了找到模型的权重,我们沿着梯度的正方向采取一个与之成比例的步骤,以最大化对数似然。此外,我们将一个系数,即学习率 $\eta$ 添加到权重更新中:
\begin{align} & w_j := w_j + \eta \frac{\partial}{\partial w_j} l(\mathbf{w})\\ & w_j := w_j + \eta \sum^{n}_{i=1} \big( y^{(i)} - \phi\big(z^{(i)}\big)\big)x_j^{(i)} \end{align}
注意,与随机梯度上升/下降相比,梯度(和权重更新)是从训练集中的所有样本中计算得出的。有关梯度下降和随机梯度下降之间差异的更多信息,请参见相关文章 梯度下降与随机梯度下降。
前一个方程显示了单个权重 $j$ 的权重更新。在基于梯度的优化中,所有权重系数同时更新;权重更新可以更简洁地写为
$$\mathbf{w} := \mathbf{w} + \Delta\mathbf{w},$$ 其中
$$\Delta{\mathbf{w}} = \eta \nabla l(\mathbf{w})$$
梯度下降法与逻辑代价函数
在前一节中,我们推导了对数似然函数的梯度,可以通过梯度上升进行优化。同样,我们可以获得逻辑代价函数 $J(\cdot)$ 的代价值梯度,并通过梯度下降来最小化它,以便学习逻辑回归模型。
单个权重的更新规则:
\begin{align} & \Delta{w_j} = -\eta \frac{\partial J}{\partial w_j} \ & = - \eta \sum_{i=1}^{n}\big(y^{(i)} - \phi\big(z^{(i)}\big) x^{(i)} \big) \end{align}
同时的权重更新:
$$\mathbf{w} := \mathbf{w} + \Delta\mathbf{w}$$
其中
$$\Delta{\mathbf{w}} = - \eta \nabla J(\mathbf{w}).$$
洗牌
随机洗牌实现如下:
- 对一个或多个轮次
- 随机洗牌训练集中的样本
- 对于训练样本 i
- 计算梯度并执行权重更新
- 对于训练样本 i
- 随机洗牌训练集中的样本
正则化
作为解决过拟合的一种方法,我们可以通过正则化项向逻辑回归模型添加额外的偏差。通过L2正则化项,我们通过惩罚大的权重系数来减少模型的复杂性:
$$L2: \frac{\lambda}{2}\lVert \mathbf{w} \lVert_2 = \frac{\lambda}{2} \sum_{j=1}^{m} w_j^2$$
$$L2: \frac{\lambda}{2}\lVert \mathbf{w} \lVert_2 = \frac{\lambda}{2} \sum_{j=1}^{m} w_j^2$$
为了应用正则化,我们只需要将正则化项添加到我们为逻辑回归定义的成本函数中,以缩小权重:
$$J(\mathbf{w}) = \sum_{i=1}^{m} \Bigg[ - y^{(i)} \log \bigg( \phi\big(z^{(i)}\big) \bigg) - \big(1 - y^{(i)}\big) \log\bigg(1-\phi\big(z^{(i)}\big)\bigg) \Bigg] + \frac{\lambda}{2} \sum_{j=1}^{m} w_j^2$$
单个权重的更新规则:
\begin{align} & \Delta{w_j} = -\eta \bigg( \frac{\partial J}{\partial w_j} + \lambda w_j\bigg)\ & = - \eta \sum_{i=1}^{n}\big(y^{(i)} - \phi\big(z^{(i)}\big) x^{(i)} \big) - \eta \lambda w_j \end{align}
同时的权重更新:
$$\mathbf{w} := \mathbf{w} + \Delta\mathbf{w}$$
其中
$$\Delta{\mathbf{w}} = - \eta \big( \nabla J(\mathbf{w}) + \lambda \mathbf{w}\big).$$
有关正则化的更多信息,请参见 广义线性模型的正则化。
参考文献
- Bishop, Christopher M. 模式识别与机器学习。施普林格,2006年。第203-213页
示例 1 - 梯度下降
from mlxtend.data import iris_data
from mlxtend.plotting import plot_decision_regions
from mlxtend.classifier import LogisticRegression
import matplotlib.pyplot as plt
# 加载数据
X, y = iris_data()
X = X[:, [0, 3]] # 萼片长度和花瓣宽度
X = X[0:100] # 0类和1类
y = y[0:100] # 0类和1类
# 标准化
X[:,0] = (X[:,0] - X[:,0].mean()) / X[:,0].std()
X[:,1] = (X[:,1] - X[:,1].mean()) / X[:,1].std()
lr = LogisticRegression(eta=0.1,
l2_lambda=0.0,
epochs=100,
minibatches=1, # 对于梯度下降
random_seed=1,
print_progress=3)
lr.fit(X, y)
plot_decision_regions(X, y, clf=lr)
plt.title('Logistic Regression - Gradient Descent')
plt.show()
plt.plot(range(len(lr.cost_)), lr.cost_)
plt.xlabel('Iterations')
plt.ylabel('Cost')
plt.show()
Iteration: 100/100 | Cost 0.32 | Elapsed: 0:00:00 | ETA: 0:00:00
预测类别标签
y_pred = lr.predict(X)
print('Last 3 Class Labels: %s' % y_pred[-3:])
Last 3 Class Labels: [1 1 1]
预测类别概率
y_pred = lr.predict_proba(X)
print('Last 3 Class Labels: %s' % y_pred[-3:])
Last 3 Class Labels: [ 0.99997968 0.99339873 0.99992707]
示例 2 - 随机梯度下降
from mlxtend.data import iris_data
from mlxtend.plotting import plot_decision_regions
from mlxtend.classifier import LogisticRegression
import matplotlib.pyplot as plt
# 加载数据
X, y = iris_data()
X = X[:, [0, 3]] # 萼片长度和花瓣宽度
X = X[0:100] # 0类和1类
y = y[0:100] # 0类和1类
# 标准化
X[:,0] = (X[:,0] - X[:,0].mean()) / X[:,0].std()
X[:,1] = (X[:,1] - X[:,1].mean()) / X[:,1].std()
lr = LogisticRegression(eta=0.5,
epochs=30,
l2_lambda=0.0,
minibatches=len(y), # 对于随机梯度下降学习
random_seed=1,
print_progress=3)
lr.fit(X, y)
plot_decision_regions(X, y, clf=lr)
plt.title('Logistic Regression - Stochastic Gradient Descent')
plt.show()
plt.plot(range(len(lr.cost_)), lr.cost_)
plt.xlabel('Iterations')
plt.ylabel('Cost')
plt.show()
Iteration: 30/30 | Cost 0.27 | Elapsed: 0:00:00 | ETA: 0:00:00
示例 3 - 使用小批量的随机梯度下降
在这里,我们将minibatches
设置为5,这将导致小批量学习,批量大小为20个样本(因为100个鸢尾花样本除以5个小批量等于20)。
from mlxtend.data import iris_data
from mlxtend.plotting import plot_decision_regions
from mlxtend.classifier import LogisticRegression
import matplotlib.pyplot as plt
# 加载数据
X, y = iris_data()
X = X[:, [0, 3]] # 萼片长度和花瓣宽度
X = X[0:100] # 0类和1类
y = y[0:100] # 0类和1类
# 标准化
X[:,0] = (X[:,0] - X[:,0].mean()) / X[:,0].std()
X[:,1] = (X[:,1] - X[:,1].mean()) / X[:,1].std()
lr = LogisticRegression(eta=0.5,
epochs=30,
l2_lambda=0.0,
minibatches=5, # 100/5 = 20 -> 小批次大小
random_seed=1,
print_progress=3)
lr.fit(X, y)
plot_decision_regions(X, y, clf=lr)
plt.title('Logistic Regression - Stochastic Gradient Descent')
plt.show()
plt.plot(range(len(lr.cost_)), lr.cost_)
plt.xlabel('Iterations')
plt.ylabel('Cost')
plt.show()
Iteration: 30/30 | Cost 0.25 | Elapsed: 0:00:00 | ETA: 0:00:00
API
LogisticRegression(eta=0.01, epochs=50, l2_lambda=0.0, minibatches=1, random_seed=None, print_progress=0)
Logistic regression classifier.
Note that this implementation of Logistic Regression expects binary class labels in {0, 1}.
Parameters
-
eta
: float (default: 0.01)Learning rate (between 0.0 and 1.0)
-
epochs
: int (default: 50)Passes over the training dataset. Prior to each epoch, the dataset is shuffled if
minibatches > 1
to prevent cycles in stochastic gradient descent. -
l2_lambda
: floatRegularization parameter for L2 regularization. No regularization if l2_lambda=0.0.
-
minibatches
: int (default: 1)The number of minibatches for gradient-based optimization. If 1: Gradient Descent learning If len(y): Stochastic Gradient Descent (SGD) online learning If 1 < minibatches < len(y): SGD Minibatch learning
-
random_seed
: int (default: None)Set random state for shuffling and initializing the weights.
-
print_progress
: int (default: 0)Prints progress in fitting to stderr. 0: No output 1: Epochs elapsed and cost 2: 1 plus time elapsed 3: 2 plus estimated time until completion
Attributes
-
w_
: 2d-array, shape={n_features, 1}Model weights after fitting.
-
b_
: 1d-array, shape={1,}Bias unit after fitting.
-
cost_
: listList of floats with cross_entropy cost (sgd or gd) for every epoch.
Examples
For usage examples, please see https://rasbt.github.io/mlxtend/user_guide/classifier/LogisticRegression/
Methods
fit(X, y, init_params=True)
Learn model from training data.
Parameters
-
X
: {array-like, sparse matrix}, shape = [n_samples, n_features]Training vectors, where n_samples is the number of samples and n_features is the number of features.
-
y
: array-like, shape = [n_samples]Target values.
-
init_params
: bool (default: True)Re-initializes model parameters prior to fitting. Set False to continue training with weights from a previous model fitting.
Returns
self
: object
predict(X)
Predict targets from X.
Parameters
-
X
: {array-like, sparse matrix}, shape = [n_samples, n_features]Training vectors, where n_samples is the number of samples and n_features is the number of features.
Returns
-
target_values
: array-like, shape = [n_samples]Predicted target values.
predict_proba(X)
Predict class probabilities of X from the net input.
Parameters
-
X
: {array-like, sparse matrix}, shape = [n_samples, n_features]Training vectors, where n_samples is the number of samples and n_features is the number of features.
Returns
Class 1 probability
: float
score(X, y)
Compute the prediction accuracy
Parameters
-
X
: {array-like, sparse matrix}, shape = [n_samples, n_features]Training vectors, where n_samples is the number of samples and n_features is the number of features.
-
y
: array-like, shape = [n_samples]Target values (true class labels).
Returns
-
acc
: floatThe prediction accuracy as a float between 0.0 and 1.0 (perfect score).