Logistic 回归,又名逻辑回归,它从线性回归发展而来,是一种广义的线性回归模型;该模型预测输出的是样本类别的条件概率分布,因而可以取概率值最大的类别作为分类结果,实质上是一个分类模型。
算法原理
sigmoid函数
首先设想一个应用场景下的分类问题:某电商网站有着大量的商品,当用户看到这些商品时,有两个选择,一是点击,二是不点击。如果点击的类型是购买,则用户的这一行为将给商家带来直接的收益。由于电商网站使用的模型要预测用户是否点击这个行为,取值只有两个。目前学过的分类算法只有 KNN,但由于 KNN 严重依赖训练数据,一旦出现近邻的噪声样本,就会对最终的预测结果产生严重的影响。另一方面,KNN 用在电商这个大数据量的场景下显然是不合适的。一是计算量大、效率低:每预测一个样本,都要和所有已知的训练样本计算距离,样本的特征空间大多是成千上万的,这样就造成了计算的困难。二是电商场景下的样本类别不平衡问题会导致正样本的召回率偏低,毕竟用户的不点击行为是占多数的,而 KNN 是使用多数表决的分类规则。
回想人类自身的判断与决策过程:当有50%以上的把握时,我们就可以判定事物的属性或者决定是否要做某一件事情,这个50%以上的把握可以理解为事件发生的概率值大于0.5 。如果将线性回归的输出值 z z z映射为[0,1]之间的概率值,那么就可以通过概率值来进行分类决策了。具体到前面商品点击的例子中,可以通过预测用户点击商品的概率值,来判断该用户是否会点击购买当前的商品。
现在的问题是找到一个可以将线性回归的预测值 z z z 映射到 [0,1] 之间任意取值的函数。恰好数学中就有sigmoid函数:

这个函数实际上是Logistic 分布的分布函数,它的形状为一条S形曲线,因而人们习惯把它叫做Sigmoid函数。观察图像可知,该函数关于点(0,0.5)中心对称,且靠近对称中心时的增长速度较快。sigmoid函数公式为:
σ
(
z
)
=
1
1
+
e
−
z
\sigma(z)=\frac{1}{1+e^{-z}}
σ(z)=1+e−z1
其对
z
z
z的导数为:
σ
′
(
z
)
=
σ
(
z
)
[
1
−
σ
(
z
)
]
\sigma'(z)=\sigma(z)[1-\sigma(z)]
σ′(z)=σ(z)[1−σ(z)]
对 sigmoid 函数求导得到的是随机变量
z
z
z 的概率密度函数:

图中函数曲线与
x
x
x 轴围成的面积为1,表示随机变量
z
z
z 取所有可能值的概率之和为1。物体质量的密度衡量了物体质量在空间中不同位置的分布情况;概率密度则衡量了概率值1在随机变量
z
z
z 的不同取值区间内的分布情况。
逻辑回归模型
将线性回归的预测输出代入 Sigmoid 函数就得到了逻辑回归的预测函数:
f
(
x
i
)
=
σ
(
w
⋅
x
i
)
,
i
∈
{
1
,
2
,
.
.
.
,
m
}
f(x_{i})=\sigma(w\cdot x_{i}),i\in \left \{ 1,2,...,m\right\}
f(xi)=σ(w⋅xi),i∈{1,2,...,m}
该函数预测输出的是样本
i
i
i 属于正例的概率值。具体地,可以理解成用户发生点击行为的概率,如果概率大于 0.5,则判断用户会点击购买当前的商品,否则不点击。
概率决策的阈值一般取 0.5,也可以根据实际情况进行调整。某医院拥有患者大量的病理特征数据,如果使用逻辑回归来预测病人得癌症的概率,进而确定病人是否患了癌症。在判断病人是否患癌症这个场景中,要从患者的角度考虑误诊风险。将阈值设置为 0.5 以上很可能导致原本患癌症的病人错过最佳的治疗时机,造成不可挽回的生命损失。正确的做法是在 0.5 的基础上调低阈值。
"回归"二字的由来
在解释这个问题之前,先了解几个概念:一个是随机事件发生的几率,另一个是对数几率。随机事件发生的几率指的是该事件发生的概率与不发生概率的比值,用来衡量该事件发生的相对可能性大小。事件
i
i
i 发生的几率:
f
(
x
i
)
1
−
f
(
x
i
)
=
e
w
⋅
x
i
,
i
∈
{
1
,
2
,
.
.
.
,
m
}
\frac{f(x_{i})}{1-f(x_{i})}=e^{w \cdot x_{i}},i\in \left \{ 1,2,...,m\right\}
1−f(xi)f(xi)=ew⋅xi,i∈{1,2,...,m}
对数几率,顾名思义就是上面的结果取对数:
I
n
(
f
(
x
i
)
1
−
f
(
x
i
)
)
=
I
n
(
e
w
⋅
x
i
)
=
w
⋅
x
i
,
i
∈
{
1
,
2
,
.
.
.
,
m
}
In(\frac{f(x_{i})}{1-f(x_{i})})=In(e^{w \cdot x_{i}})=w \cdot x_{i},i\in \left \{ 1,2,...,m\right\}
In(1−f(xi)f(xi))=In(ew⋅xi)=w⋅xi,i∈{1,2,...,m}
由此发现,对数几率正是线性回归模型对样本
i
i
i 的预测输出值,换句话说,logistic 回归是使用线性回归来预测事件发生的对数几率,所以 logistic 回归又叫对数几率回归。
前面已经推导出随机事件发生与不发生的概率表达式。进一步地,可以将二分类的逻辑回归模型表示成如下形式的条件概率分布:
P
(
y
i
=
1
∣
x
i
)
=
σ
(
w
⋅
x
i
)
,
i
∈
{
1
,
2
,
.
.
.
,
m
}
P(y_{i}=1|x_{i})=\sigma(w\cdot x_{i}),i\in \left \{ 1,2,...,m\right\}
P(yi=1∣xi)=σ(w⋅xi),i∈{1,2,...,m}
P
(
y
i
=
0
∣
x
i
)
=
1
−
σ
(
w
⋅
x
i
)
,
i
∈
{
1
,
2
,
.
.
.
,
m
}
P(y_{i}=0|x_{i})=1-\sigma(w\cdot x_{i}),i\in \left \{ 1,2,...,m\right\}
P(yi=0∣xi)=1−σ(w⋅xi),i∈{1,2,...,m}
等式左边是条件概率的表示方法,简单来说就是:给定样本特征
x
i
x_{i}
xi 的条件下,样本标记
y
i
y_{i}
yi 取值为 1 或 0 的概率。由此可知,二项逻辑回归是服从伯努利分布(又称两点分布、零一分布)的概率模型。逻辑回归不是直接预测样本的类别,而是对分类的可能性进行建模,这样可以得到类别概率的近似值,更有利于分类决策。
机器学习模型可以分为概率模型和非概率模型,前面的线性回归和K近邻都属于非概率模型,用预测函数 y = f ( x ) y=f(x) y=f(x)来表示;而概率模型一般用条件概率分布 P ( y ∣ x ) P(y|x) P(y∣x) 来表示,逻辑回归既可看作是概率模型,又可看作是非概率模型。
在后面的学习中,朴素贝叶斯和决策树属于概率模型;K均值和感知机与支持向量机属于非概率模型。在监督学习中,概率模型是生成模型,非概率模型是判别模型。
极大似然估计
逻辑回归的模型参数是通过极大化对数似然函数来估计得到的。似然函数(likelihood function)是一种关于概率模型参数的函数,逻辑回归的似然函数为:
∏
i
=
1
m
P
(
y
i
∣
x
i
)
\prod_{i=1}^{m}P(y_{i}|x_{i})
i=1∏mP(yi∣xi)
其中
m
m
m 是样本个数,
P
(
y
i
∣
x
i
)
P(y_{i}|x_{i})
P(yi∣xi) 是在给定样本特征
x
i
x_{i}
xi 的条件下, 模型将样本
i
i
i 预测为真实标记值
y
i
y_{i}
yi 的概率,将每个样本预测正确的概率连乘起来就得到了似然函数。由于
x
i
x_{i}
xi 和
y
i
y_{i}
yi 是已知的,故似然函数的表达式是关于模型参数的函数。
二项逻辑回归的似然函数可以表示成如下样本概率连乘的形式:
∏
i
=
1
m
[
P
(
y
i
=
1
∣
x
i
)
]
y
i
[
P
(
y
i
=
0
∣
x
i
)
]
1
−
y
i
\prod_{i=1}^{m}[P(y_{i}=1|x_{i})]^{y_{i}}[P(y_{i}=0|x_{i})]^{1-y_{i}}
i=1∏m[P(yi=1∣xi)]yi[P(yi=0∣xi)]1−yi
二项逻辑回归的似然函数也可以写成样本预测函数连乘的形式:
∏
i
=
1
m
[
σ
(
w
⋅
x
i
)
]
y
i
[
1
−
σ
(
w
⋅
x
i
)
]
1
−
y
i
\prod_{i=1}^{m}[\sigma(w\cdot x_{i})]^{y_{i}}[1-\sigma(w\cdot x_{i})]^{1-y_{i}}
i=1∏m[σ(w⋅xi)]yi[1−σ(w⋅xi)]1−yi
由于每个样本的概率值位于 [0,1] 之间,连乘之后将会变成一个很小的数,可能会引起浮点数下溢。当样本数
m
m
m 很大时,似然函数的值会趋向于 0,非常不利于之后的计算。所以,将原似然函数取对数,使其从积的形式转变为和的形式,得到的对数似然函数
L
(
w
)
L(w)
L(w) 如下:
L
(
w
)
=
∑
i
=
1
m
[
y
i
l
o
g
(
σ
(
w
⋅
x
i
)
)
+
(
1
−
y
i
)
l
o
g
(
1
−
σ
(
w
⋅
x
i
)
)
]
L(w)=\sum_{i=1}^{m}[y_{i}log(\sigma(w\cdot x_{i}))+(1-y_{i})log(1-\sigma(w\cdot x_{i}))]
L(w)=i=1∑m[yilog(σ(w⋅xi))+(1−yi)log(1−σ(w⋅xi))]
由对数似然函数
L
(
w
)
L(w)
L(w) 的表达式可知,模型将训练样本预测正确的概率越大,对数似然函数的值就越大。所以只要极大化
L
(
w
)
L(w)
L(w),对应的
w
w
w 值就是要求解的模型参数了。
分类模型的损失函数一般使用如下形式的对数损失(又称交叉熵损失):
L
(
y
i
,
P
(
y
i
∣
x
i
)
)
=
−
l
o
g
P
(
y
i
∣
x
i
)
,
i
∈
{
1
,
2
,
.
.
.
,
m
}
L(y_{i},P(y_{i}|x_{i}))=-logP(y_{i}|x_{i}),i\in \left \{ 1,2,...,m\right\}
L(yi,P(yi∣xi))=−logP(yi∣xi),i∈{1,2,...,m}
由公式可知,分类模型在样本
i
i
i 上的预测损失为:模型将样本
i
i
i 预测为真实分类
y
i
y_{i}
yi 的概率取负对数。所以,对数损失函数值越小,模型在该样本上的表现就越好。进一步地,分类模型在整个训练集上的对数损失可以表示成如下形式:
−
∑
i
=
1
m
l
o
g
P
(
y
i
∣
x
i
)
-\sum_{i=1}^{m}logP(y_{i}|x_{i})
−i=1∑mlogP(yi∣xi)
二分类模型在整个训练集上的对数损失如下:
−
∑
i
=
1
m
[
y
i
l
o
g
(
σ
(
w
⋅
x
i
)
)
+
(
1
−
y
i
)
l
o
g
(
1
−
σ
(
w
⋅
x
i
)
)
]
-\sum_{i=1}^{m}[y_{i}log(\sigma(w\cdot x_{i}))+(1-y_{i})log(1-\sigma(w\cdot x_{i}))]
−i=1∑m[yilog(σ(w⋅xi))+(1−yi)log(1−σ(w⋅xi))]
二项逻辑回归的对数似然函数
L
(
w
)
L(w)
L(w) 和它在训练集上的对数损失互为相反数。也就是说,极大化对数似然函数等价于极小化训练集上的对数损失。极小化对数损失使用梯度下降法,而极大化似然函数要使用梯度上升法:沿梯度方向更新模型参数。
梯度上升
根据对数似然函数:
L
(
w
)
=
∑
i
=
1
m
[
y
i
l
o
g
(
σ
(
w
⋅
x
i
)
)
+
(
1
−
y
i
)
l
o
g
(
1
−
σ
(
w
⋅
x
i
)
)
]
L(w)=\sum_{i=1}^{m}[y_{i}log(\sigma(w\cdot x_{i}))+(1-y_{i})log(1-\sigma(w\cdot x_{i}))]
L(w)=i=1∑m[yilog(σ(w⋅xi))+(1−yi)log(1−σ(w⋅xi))]
然后根据链式法则(复合函数求导法则)求解对数似然函数的梯度:
L
′
(
w
)
=
∑
i
=
1
m
[
y
i
−
σ
(
w
⋅
x
i
)
]
x
i
L'(w)=\sum_{i=1}^{m}[y_{i}-\sigma(w\cdot x_{i})]x_{i}
L′(w)=i=1∑m[yi−σ(w⋅xi)]xi
最后沿梯度方向更新模型参数向量:
w
=
w
+
α
L
′
(
w
)
w=w+\alpha L'(w)
w=w+αL′(w)
numpy实现逻辑回归
算法改写矩阵形式
使用 numpy 实现逻辑回归算法,然后利用该算法在乳腺癌数据集上进行癌症诊断。
首先回顾算法原理,将其改写至矩阵形式,输入数据
x
x
x形状为
(
m
,
n
)
(m,n)
(m,n),
w
w
w的形状为
(
n
,
1
)
(n,1)
(n,1),则预测为正例的概率为:
f
(
x
)
=
σ
(
x
w
)
f(x)=\sigma(xw)
f(x)=σ(xw)
将损失函数
J
(
w
)
J(w)
J(w)写成矩阵形式:
J
(
w
)
=
−
1
m
[
y
T
l
o
g
(
f
(
x
)
)
+
(
1
−
y
)
T
l
o
g
(
1
−
f
(
x
)
)
]
J(w)=-\frac{1}{m}[y^{T}log(f(x))+(1-y)^{T}log(1-f(x))]
J(w)=−m1[yTlog(f(x))+(1−y)Tlog(1−f(x))]
其中,类别标签
y
y
y形状为
(
m
,
1
)
(m,1)
(m,1)
将参数
w
w
w形状转换为
(
1
,
n
)
(1,n)
(1,n),如果是极大化似然函数,参数更新需要沿着似然函数的梯度方向,前面计算过似然函数对于参数的梯度为:
L
′
(
w
)
=
∑
i
=
1
m
[
y
i
−
σ
(
w
⋅
x
i
)
]
x
i
L'(w)=\sum_{i=1}^{m}[y_{i}-\sigma(w\cdot x_{i})]x_{i}
L′(w)=i=1∑m[yi−σ(w⋅xi)]xi
因此矩阵表达为:
w
=
w
+
α
L
′
(
w
)
=
w
+
α
[
y
−
f
(
x
)
]
T
x
w=w+\alpha L'(w)=w+\alpha[y-f(x)]^{T}x
w=w+αL′(w)=w+α[y−f(x)]Tx
数据加载
数据加载和预处理:
from sklearn.datasets import load_breast_cancer
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
# 获取乳腺癌数据集
cancer = load_breast_cancer()
# 获取数据集特征
X = cancer.data
# 获取数据集标记
y = cancer.target
# 特征归一化到 [0,1] 范围内:提升模型收敛速度
X = MinMaxScaler().fit_transform(X)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.2, random_state=2020)
模型实现
补充一个numpy的操作:
- np.column_stack(tuple)
将1维数组作为一列拼接到2维数组得到一个新的2维数组,或是拼接到1维数组成为2维数组:
a = np.array((1,2,3))
b = np.array((2,3,4))
np.column_stack((a,b))
"""
array([[1, 2],
[2, 3],
[3, 4]])
"""
通过以上分析,模型实现为:
import numpy as np
import matplotlib.pyplot as plt
class LogisticRegression:
'''逻辑回归算法实现'''
def __init__(self, alpha=0.1, epoch=5000, fit_bias=True, threshold=0.5):
'''
alpha: 学习率,控制参数更新的幅度
epoch: 在整个训练集上训练迭代(参数更新)的次数
fit_bias: 是否训练偏置项参数
threshold:判定为正类的概率阈值
'''
self.alpha = alpha
self.epoch = epoch
# cost_record 记录每一次迭代后的经验风险
self.cost_record = []
self.fit_bias = fit_bias
self.threshold = threshold
# 概率预测函数
def predict_proba(self, X_test):
'''
X_test: m x n 的 numpy 二维数组
'''
# 模型有偏置项参数时:为每个测试样本增加特征 x_0 = 1
if self.fit_bias:
x_0 = np.ones(X_test.shape[0])
X_test = np.column_stack((x_0, X_test))
# 根据预测公式返回结果
z = np.dot(X_test, self.w)
return 1 / (1 + np.exp(-z))
# 类别预测函数
def predict(self, X_test):
'''
X_test: m x n 的 numpy 二维数组
'''
probs = self.predict_proba(X_test)
results = map(lambda x: int(x > self.threshold), probs)
return np.array(list(results))
# 模型训练:使用梯度下降法更新参数
def fit(self, X_train, y_train):
'''
X_train: m x n 的 numpy 二维数组
y_train:有 m 个元素的 numpy 一维数组
'''
# 训练偏置项参数时:为每个训练样本增加特征 x_0 = 1
if self.fit_bias:
x_0 = np.ones(X_train.shape[0])
X_train = np.column_stack((x_0, X_train))
# 训练样本数量
m = X_train.shape[0]
# 样本特征维数
n = X_train.shape[1]
# 初始模型参数
self.w = np.ones(n)
# 模型参数迭代
for i in range(self.epoch):
# 计算训练样本预测值
z = np.dot(X_train, self.w)
y_pred = 1 / (1 + np.exp(-z))
# 计算训练集经验风险
cost = -(np.dot(y_train, np.log(y_pred)) + np.dot(np.ones(m) - y_train, np.log(np.ones(m) - y_pred))) / m
# 记录训练集经验风险
self.cost_record.append(cost)
# 参数更新:梯度上升法
self.w += self.alpha / m * np.dot(y_train - y_pred, X_train)
# 保存模型
self.save_model()
# 显示经验风险的收敛趋势图
def polt_cost(self):
plt.plot(np.arange(self.epoch), self.cost_record)
plt.xlabel("epoch")
plt.ylabel("cost")
plt.show()
# 保存模型参数
def save_model(self):
np.savetxt("model.txt", self.w)
# 加载模型参数
def load_model(self):
self.w = np.loadtxt('model.txt')
模型的训练和预测:
# 实例化一个对象
model = LogisticRegression()
# 在训练集上训练
model.fit(X_train,y_train)
# 在测试集上预测
y_pred = model.predict(X_test)
查看模型收敛趋势图:
model.polt_cost()

实现对比
与sklearn实现的逻辑回归对比:
from sklearn import linear_model
# 实例化一个对象
model_1 = LogisticRegression(epoch=60000)
model_2 = linear_model.LogisticRegression()
# 在训练集上训练
model_1.fit(X_train,y_train)
model_2.fit(X_train,y_train)
# 在测试集上预测
y_pred_1 = model_1.predict(X_test)
y_pred_2 = model_2.predict(X_test)
利用 sklearn.metrics 模块中的一些评估函数来对两个模型进行评估:
import pandas as pd
from sklearn.metrics import accuracy_score
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
from sklearn.metrics import f1_score
from sklearn.metrics import roc_auc_score
metrics = dict()
acc_1 = accuracy_score(y_test,y_pred_1)
acc_2 = accuracy_score(y_test,y_pred_2)
metrics['准确率'] = [acc_1,acc_2]
pre_1 = precision_score(y_test,y_pred_1)
pre_2 = precision_score(y_test,y_pred_2)
metrics['精确率'] = [pre_1,pre_2]
rec_1 = recall_score(y_test,y_pred_1)
rec_2 = recall_score(y_test,y_pred_2)
metrics['召回率'] = [rec_1,rec_2]
f1_1 = f1_score(y_test,y_pred_1)
f1_2 = f1_score(y_test,y_pred_2)
metrics['F1值'] = [f1_1,f1_2]
auc_1 = roc_auc_score(y_test, model_1.predict_proba(X_test))
# model_2.predict_proba(X_test)的形状为(m,2),第0列是分类为负例的概率,第1列是分类为正例的概率
auc_2 = roc_auc_score(y_test, model_2.predict_proba(X_test)[:,1])
metrics['AUC'] = [auc_1,auc_2]
df = pd.DataFrame(metrics,index=['model_1','model_2'])
结果df为:

4万+

被折叠的 条评论
为什么被折叠?



