文章目录
逻辑回归原理
什么是逻辑回归
注意,本文里的y_pred指的是y预测值
逻辑回归是用来做分类算法的,大家都熟悉线性回归,一般形式是y_pred=aX+b,y的取值范围是[-∞, +∞],有这么多取值,怎么进行分类呢?不用担心,伟大的数学家已经为我们找到了一个方法。
也就是把Y的结果带入一个非线性变换的Sigmoid函数中,即可得到(0,1)之间取值范围的数S,S可以把它看成是一个概率值,如果我们设置概率阈值为0.5,那么S大于0.5可以看成是正样本,小于0.5看成是负样本,就可以进行分类了。
再简短说下:
- 为啥叫逻辑回归:创造者名叫逻辑斯谛(Logistic )
- 线性回归的输出是[-∞, +∞],通过Sigmoid函数转换成(0,1)之间的值
- 分类模型
- 输出目标:(0,1)之间的值
- 模型:直观上看就是线性模型加入sigmoid函数:y_pred = sigmoid(W0 + W1*X1+…+WnXn)
Sigmoid函数
公式:
g
(
z
)
=
1
1
+
e
−
z
g\left( z \right) =\frac{1}{1+e^{-z}}
g(z)=1+e−z1
其中 z 是 wx + b,(其实就是线性模型中的预测值y,只不过这里习惯写成 z ),扩展开来 z = w0+w1x1…+wnxn
图像:
性质:
- 单调性。必须单调性,保持和原线性模型输出的结果单调性的一致性。因为Sigmoid函数只是把结果压缩到(0,1)而已,并不改变结果值的走向
- 两边梯度趋于饱和,即导数趋于0(作为激活函数在神经网络中的弊端)
- 1/2处的导数最大
- 设f(x)=sigmoid(x),f(x)导数为f(x)(1-f(x))
- 不以原点为中心(作为激活函数在神经网络中的弊端)
逻辑回归的损失函数
任何一个模型都应该有个损失函数,就是描述预测值与实际值的差值的函数。
先上损失函数(这里假设样本服从伯努利分布(0-1分布)):
l
o
s
s
=
−
(
Σ
y
i
log
y
^
i
+
(
1
−
y
i
)
log
(
1
−
y
^
i
)
)
其中
y
^
=
s
i
g
m
o
i
d
(
W
T
X
)
loss=-\left( \varSigma y_i\log \hat{y}_i+\left( 1-y_i \right) \log \left( 1-\hat{y}_i \right) \right) \\ \text{其中}\hat{y}=sigmoid\left( W^TX \right)
loss=−(Σyilogy^i+(1−yi)log(1−y^i))其中y^=sigmoid(WTX)
损失函数变换过程:从极大似然估计理解逻辑回归的损失函数
逻辑回归的损失函数别名1:负对数似然损失
假设样本服从伯努利分布(0-1分布):
P
(
Y
∣
X
)
=
{
y
^
,
y
=
1
1
−
y
^
,
y
=
0
}
合并成一个形式:
y
^
y
(
1
−
y
^
)
1
−
y
那么所有样本:
Π
i
y
^
i
y
i
(
1
−
y
^
i
)
1
−
y
i
目标函数:
max
Π
i
y
^
i
y
i
(
1
−
y
^
i
)
1
−
y
i
取
log
并加上负号变成取最小值(方便优化):
l
o
s
s
=
−
(
Σ
y
i
log
y
^
i
+
(
1
−
y
i
)
log
(
1
−
y
^
i
)
)
求解参数
w
∗
,使得
l
o
s
s
最小化:
w
∗
=
a
r
g
min
−
(
Σ
y
i
log
y
^
i
+
(
1
−
y
i
)
log
(
1
−
y
^
i
)
)
解释下相关符号:
y
表示实际值;
y
^
表示预测值;
w
∗
和
a
r
g
min
表示当参数
w
为
w
∗
时使得
l
o
s
s
最小,这只是一种符号化形式
P\left( Y|X \right) =\left. \left\{ \begin{array}{c} \hat{y}, y=1\\ 1-\hat{y}, y=0\\ \end{array} \right. \right\} \\ \text{合并成一个形式:}\hat{y}^y\left( 1-\hat{y} \right) ^{1-y} \\ \text{那么所有样本:}\underset{i}{\varPi}{\hat{y}_i}^{y_i}\left( 1-\hat{y}_i \right) ^{1-y_i} \\ \text{目标函数:}\max \underset{i}{\varPi}{\hat{y}_i}^{y_i}\left( 1-\hat{y}_i \right) ^{1-y_i} \\ \text{取}\log\text{并加上负号变成取最小值(方便优化):}loss=-\left( \varSigma y_i\log \hat{y}_i+\left( 1-y_i \right) \log \left( 1-\hat{y}_i \right) \right) \\ \text{求解参数}w^*\text{,使得}loss\text{最小化:}w^*=arg\min -\left( \varSigma y_i\log \hat{y}_i+\left( 1-y_i \right) \log \left( 1-\hat{y}_i \right) \right) \\ \text{解释下相关符号:}y\text{表示实际值;}\hat{y}\text{表示预测值;} \\ w^*\text{和}arg\min\text{表示当参数}w\text{为}w^*\text{时使得}loss\text{最小,这只是一种符号化形式}
P(Y∣X)={y^,y=11−y^,y=0}合并成一个形式:y^y(1−y^)1−y那么所有样本:iΠy^iyi(1−y^i)1−yi目标函数:maxiΠy^iyi(1−y^i)1−yi取log并加上负号变成取最小值(方便优化):loss=−(Σyilogy^i+(1−yi)log(1−y^i))求解参数w∗,使得loss最小化:w∗=argmin−(Σyilogy^i+(1−yi)log(1−y^i))解释下相关符号:y表示实际值;y^表示预测值;w∗和argmin表示当参数w为w∗时使得loss最小,这只是一种符号化形式
小结一下
- 1)样本服从什么分布:伯努利(0-1)
- 2)损失函数的由来:伯努利分布的极大似然估计
- 这里怎么理解呢:回到P(Y|X),这表示的是在X情况下Y的概率,在0-1分布的式子里,当然希望预测值越大越好,放到整个样本来描述就是,当x1,x2…xn情况下预测值的情况就是联合概率P(Y|x1,x2,x3…xn),就是所有概率值连乘。并且,我们仍然是希望预测值越大越好,即希望概率值连乘的结果越大越好,上述式子就能看明白了吧! 这就是极大似然函数,也是我们的目标函数。
- 为啥加log:log是单调性的,能够和原式子保持一致的结果单调性,不会影响式子求解。连乘法加log变成加法,方便求解
- 为啥加负号:一般求极小值更容易有优化方法,方便求解
损失函数变换过程:从交叉熵的角度理解逻辑回归的损失函数
逻辑回归的损失函数别名2:交叉熵损失
参考网上文章叙述
先引入一个概念:KL散度:衡量两个概率分布的差异
逻辑回归模型最后的计算结果(通过sigmoid或softmax函数)是各个分类的概率(可以看做是各个分类的概率分布)。那么假设真实的概率分布是,估计得到的概率分布是, 这两个概率分布的距离如何去衡量?
在信息论中,「相对熵」,也就是KL散度,可以衡量两个概率分布的差异性。具体公式为:
D
K
L
(
p
∣
∣
q
)
=
Σ
x
p
(
x
)
log
p
(
x
)
q
(
x
)
=
Σ
x
p
(
x
)
(
log
p
(
x
)
−
log
q
(
x
)
)
=
−
Σ
x
p
(
x
)
log
q
(
x
)
−
(
−
Σ
x
p
(
x
)
log
p
(
x
)
)
=
H
(
p
,
q
)
−
H
(
p
)
其中
H
(
p
,
q
)
=
−
Σ
x
p
(
x
)
log
q
(
x
)
就是交叉熵,
H
(
p
)
是真实概率分布的信息熵
所以:
K
L
散度
=
交叉熵
−
真实概率分布的信息熵
\\ D_{KL}\left( p||q \right) =\underset{x}{\varSigma}p\left( x \right) \log \frac{p\left( x \right)}{q\left( x \right)}=\underset{x}{\varSigma}p\left( x \right) \left( \log p\left( x \right) -\log q\left( x \right) \right) \\ =-\underset{x}{\varSigma}p\left( x \right) \log q\left( x \right) -\left( -\underset{x}{\varSigma}p\left( x \right) \log p\left( x \right) \right) \\ =H\left( p,q \right) -H\left( p \right) \\ \text{其中}H\left( p,q \right) =-\underset{x}{\varSigma}p\left( x \right) \log q\left( x \right) \text{就是交叉熵,}H\left( p \right) \text{是真实概率分布的信息熵} \\ \text{所以:}KL\text{散度}=\text{交叉熵}-\text{真实概率分布的信息熵}
DKL(p∣∣q)=xΣp(x)logq(x)p(x)=xΣp(x)(logp(x)−logq(x))=−xΣp(x)logq(x)−(−xΣp(x)logp(x))=H(p,q)−H(p)其中H(p,q)=−xΣp(x)logq(x)就是交叉熵,H(p)是真实概率分布的信息熵所以:KL散度=交叉熵−真实概率分布的信息熵
因为交叉熵越大,KL散度越大,也可以用交叉熵来衡量两个概率分布之间的距离,所以逻辑回归使用交叉熵作为逻辑回归的损失函数。
逻辑回归损失函数求解
一个模型在一个特定样本分布上只有一个损失函数
为啥逻辑回归要用梯度下降?因为参数项太多了,无法直接求导得出最优解
先明确几个概念:
- 1)梯度下降属于优化算法来求解逻辑回归的损失函数,牛顿法也是属于优化算法,都是迭代算法
- 2)LR中,梯度下降求解是参数W
- 3)梯度下降中的“梯度”针对的是损失函数loss
这里就是使用梯度下降来求解逻辑回归的损失函数的参数
梯度的定义:
g
r
a
d
i
e
n
t
(
w
)
=
∂
l
o
s
s
(
w
)
∂
w
w
t
+
1
=
w
t
−
α
.
g
r
a
d
i
e
n
t
(
w
)
符号解释:
w
t
和
w
t
+
1
表示进行梯度下降的前后两个值(点);
α
表示学习率或者叫步长;
g
r
a
d
i
e
n
t
(
w
)
在公式中就是梯度,也是学习方向或者梯度方向,就是你要往哪个方向进行学习更新参数
w
;
在式子里含义就是
w
t
进行
α
.
g
r
a
d
i
e
n
t
(
w
)
梯度下降值后的新位置
w
t
+
1
gradient\left( w \right) =\frac{\partial loss\left( w \right)}{\partial w} \\ w_{t+1}=w_t-\alpha .gradient\left( w \right) \\ \text{符号解释:}w_t\text{和}w_{t+1}\text{表示进行梯度下降的前后两个值(点);} \\ \alpha \text{表示学习率或者叫步长;} \\ gradient\left( w \right) \text{在公式中就是梯度,也是学习方向或者梯度方向,就是你要往哪个方向进行学习更新参数}w; \\ \text{在式子里含义就是}w_t\text{进行}\alpha .gradient\left( w \right) \text{梯度下降值后的新位置}w_{t+1}
gradient(w)=∂w∂loss(w)wt+1=wt−α.gradient(w)符号解释:wt和wt+1表示进行梯度下降的前后两个值(点);α表示学习率或者叫步长;gradient(w)在公式中就是梯度,也是学习方向或者梯度方向,就是你要往哪个方向进行学习更新参数w;在式子里含义就是wt进行α.gradient(w)梯度下降值后的新位置wt+1
图示梯度下降样例:如图,当我要往山底走时(求最小值);我的方向四面八方,参考的东西(特征)很多,比如树的稀疏程度、草的茂盛程度、动物出没情况等等;你不清楚啥情况的时候,不知道往哪个方向,那么梯度公式就告诉你当前这一步你尝试往gradient(w)这个方向走a步(阿尔法符号)看看(梯度–>方向),每走完一步又继续确认方向再往前试探的走,直到走到谷底(求到最小值)。
LR中梯度法的推导:
推导流程来源于网络,但是意思能表示清楚
最后,带入梯度下降公式:这也就是LR的w参数关系式:
w
t
+
1
=
w
t
−
α
(
Σ
i
(
f
(
w
,
x
)
−
y
i
)
x
)
w_{t+1}=w_t-\alpha \left( \underset{i}{\varSigma}\left( f\left( w,x \right) -y_i \right) x \right)
wt+1=wt−α(iΣ(f(w,x)−yi)x)
解释下相关符号: J ( w , x ) 等价与 l o s s ( w ) ,就是损失函数的另一种写法; y _ p r e d i 等价于我上述损失函数的 y ^ ,表示的是预测值 y ; y i 就是实际值 y ; s i g m o i d ( w T x ) = s i g m o i d ( w 0 + w 1 x 1 + . . . w n x n ) , 其中 w T x 是向量的表示方式 ; 推导式子里有些有下角标有些没有 虽不严谨,但不影响推导理解,没有角标的都表示的是向量形式 \text{解释下相关符号:}J\left( w,x \right) \text{等价与}loss\left( w \right) \text{,就是损失函数的另一种写法;} \\ y\_pred_i\text{等价于我上述损失函数的}\hat{y}\text{,表示的是预测值}y; \\ y_i\text{就是实际值}y; \\ sigmoid\left( w^Tx \right) =sigmoid\left( w_0+w_1x_1+...w_nx_n \right) ,\text{其中}w^Tx\text{是向量的表示方式}; \\ \text{推导式子里有些有下角标有些没有} \\ \text{虽不严谨,但不影响推导理解,没有角标的都表示的是向量形式} 解释下相关符号:J(w,x)等价与loss(w),就是损失函数的另一种写法;y_predi等价于我上述损失函数的y^,表示的是预测值y;yi就是实际值y;sigmoid(wTx)=sigmoid(w0+w1x1+...wnxn),其中wTx是向量的表示方式;推导式子里有些有下角标有些没有虽不严谨,但不影响推导理解,没有角标的都表示的是向量形式
逻辑斯特回归为什么要对特征进行离散化
- 引入非线性:逻辑回归属于广义线性模型,表达能力受限;单变量离散化为N个后,每个变量有单独的权重,相当于为模型引入了非线性,能够提升模型表达能力,加大拟合; 离散特征的增加和减少都很容易,易于模型的快速迭代;
- 速度快:稀疏向量内积乘法运算速度快,计算结果方便存储,容易扩展;
- 鲁棒性:离散化后的特征对异常数据有很强的鲁棒性:比如一个特征是年龄>30是1,否则0。如果特征没有离散化,一个异常数据“年龄300岁”会给模型造成很大的干扰;
- 方便交叉与特征组合:离散化后可以进行特征交叉,由M+N个变量变为M*N个变量,进一步引入非线性,提升表达能力;
- 稳定性:特征离散化后,模型会更稳定,比如如果对用户年龄离散化,20-30作为一个区间,不会因为一个用户年龄长了一岁就变成一个完全不同的人。当然处于区间相邻处的样本会刚好相反,所以怎么划分区间是门学问;
- 简化模型:特征离散化以后,起到了简化了逻辑回归模型的作用,降低了模型过拟合的风险。
逻辑回归应用
优缺点(特点)
- 轻量级训练快:因为是线性的套了个sigmoid,故计算很快。
- 易存储易扩展:稀疏向量内积乘法运算速度快,计算结果方便存储,容易扩展
- 鲁棒性:离散化后的特征对异常数据有很强的鲁棒性:比如一个特征是年龄>30是1,否则0。如果特征没有离散化,一个异常数据“年龄300岁”会给模型造成很大的干扰;
- LR的可解释性强:参数代表每个特征对输出的影响,可解释性强
- 因为结果是概率,可以做ranking model
- 解决过拟合的方法很多,如L1、L2正则化
- 具有参数记忆从而进行特征选择的用途
- 缺点:因为它本质上是一个线性的分类器,所以处理不好特征之间相关的情况,即更喜欢特征独立的样本数据
- 缺点:特征空间很大时,性能不好。
- 缺点:容易欠拟合,精度不高
一般应用场景
- 模型的baseline
- CTR预估/推荐系统的learning to rank/各种分类场景。
- 某搜索引擎厂的广告CTR预估基线版是LR。
- 某电商搜索排序/广告CTR预估基线版是LR。
- 某电商的购物搭配推荐用了大量LR。
- 某现在一天广告赚1000w+的新闻app排序基线是LR
对于过拟合和欠拟合等优化方案
- 梯度下降实际上就是一种优化方案,同类的还有牛顿法,这里不展开了
- 对于过拟合和欠拟合通常使用正则化方式处理,对于正则化与逻辑回归的优化过程,后面单独开一篇详细描述
基于逻辑回归的分类示例
本示例代码取用sklearn的鸢尾花数据集,并取用标签结果为0,1的数据集来测试分类
手动实现逻辑回归
from math import exp
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
# data
def create_data():
# 加载鸢尾花数据集(sklearn中的数据集)
iris = load_iris()
# 通过feature_names构造dataFrame
df = pd.DataFrame(iris.data, columns=iris.feature_names)
# 把iris的结果放到dataFrame的label属性中
df['label'] = iris.target
# 声明dataFrame的新列项
df.columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'label']
# 切取dataFrame前100行,index为0,1,-1的列
data = np.array(df.iloc[:100, [0,1,-1]])
# data[:,:2]切取所有行和前两列 ;data[:,-1]切取所有行和index=-1的那列
return data[:,:2], data[:,-1]
class LogisticReressionClassifier:
def __init__(self, max_iter=200, learning_rate=0.01):
self.max_iter = max_iter
self.learning_rate = learning_rate
# 定义sigmoid函数
def sigmoid(self, x):
return 1 / (1 + exp(-x))
def data_matrix(self, X):
data_mat = []
for d in X:
# 把d所有值放到数组里并append到data_mat里。这里为啥第一项是1.0?因为我们的公式w0+w1x1+....wnxn,公式中没有x0,实际上第一项x0等同于1.0
data_mat.append([1.0, *d])
return data_mat
def fit(self, X, y):
# 获得数据矩阵,也就是我们公式中的x集合
data_mat = self.data_matrix(X) # m*n
# 初始化参数w,即公式中的w0,w1,w2。为啥这里初始化全为0?因为训练过程中会逐渐根据损失函数调整参数w
self.weights = np.zeros((len(data_mat[0]), 1), dtype=np.float32)
# 迭代的根据损失函数优化参数w的取值
for iter_ in range(self.max_iter):
for i in range(len(X)):
# 计算逻辑回归的结果
result = self.sigmoid(np.dot(data_mat[i], self.weights))
# 计算损失值
error = y[i] - result
# 根据损失函数更新参数w,其中np.transpose表示矩阵转置
self.weights += self.learning_rate * error * np.transpose([data_mat[i]])
print('LogisticRegression Model(learning_rate={},max_iter={})'.format(
self.learning_rate, self.max_iter))
# 模型得分
def score(self, X_test, y_test):
right = 0
X_test = self.data_matrix(X_test)
# zip是方便for取到每个x,y
for x, y in zip(X_test, y_test):
# 根据训练出来的w参数对测试数据集进行校验真实结果
result = np.dot(x, self.weights)
# 由于数据集只取了前100行,故y的值只有0,1,用来分类
if (result > 0 and y == 1) or (result < 0 and y == 0):
right += 1
return right / len(X_test)
# ====================================开始训练============
# 调用函数获得样本集
X, y = create_data()
# 划分数据集为训练、测试数据集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
lr_clf = LogisticReressionClassifier()
# 训练
lr_clf.fit(X_train, y_train)
lr_clf.score(X_test, y_test)
# score结果打印:0.9666666666666667
画图展示
# 画图展示参数划分结果
x_ponits = np.arange(4, 8)
y_ = -(lr_clf.weights[1]*x_ponits + lr_clf.weights[0])/lr_clf.weights[2]
plt.plot(x_ponits, y_)
#lr_clf.show_graph()
plt.scatter(X[:50,0],X[:50,1], label='0')
plt.scatter(X[50:,0],X[50:,1], label='1')
plt.legend()
使用sklearn逻辑回归模型
# 使用SKlearn里的模型
from sklearn.linear_model import LogisticRegression
# 设置迭代次数为200得到模型
clf = LogisticRegression(max_iter=200)
# 训练(沿用手动实现中的划分数据集)
clf.fit(X_train, y_train)
# 查看得分(沿用手动实现中的划分数据集)
clf.score(X_test, y_test)
# 打印参数w;coef_和intercept_都是模型参数,即为w;intercept_为w0,coef_为w1到wn
print(clf.coef_, clf.intercept_)
score为:1.0
打印的参数w为:[[ 2.71974676 -2.60054221]] [-6.86475951]
画图展示:
# 图像化展示分类效果
x_ponits = np.arange(4, 8)
y_ = -(clf.coef_[0][0]*x_ponits + clf.intercept_)/clf.coef_[0][1]
plt.plot(x_ponits, y_)
plt.plot(X[:50, 0], X[:50, 1], 'bo', color='blue', label='0')
plt.plot(X[50:, 0], X[50:, 1], 'bo', color='orange', label='1')
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.legend()
以上,若有表述错误的地方,希望指出,谢谢!