栏目分类:
子分类:
返回
终身学习网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
终身学习网 > IT > 软件开发 > 后端开发 > Python

AI基础:逻辑回归与梯度下降和基于逻辑回归的分类实践

Python 更新时间:发布时间: 百科书网 趣学号

文章目录
  • 逻辑回归原理
    • 什么是逻辑回归
    • Sigmoid函数
    • 逻辑回归的损失函数
      • 损失函数变换过程:从极大似然估计理解逻辑回归的损失函数
      • 损失函数变换过程:从交叉熵的角度理解逻辑回归的损失函数
      • 逻辑回归损失函数求解
    • 逻辑斯特回归为什么要对特征进行离散化
  • 逻辑回归应用
    • 优缺点(特点)
    • 一般应用场景
    • 对于过拟合和欠拟合等优化方案
  • 基于逻辑回归的分类示例
    • 手动实现逻辑回归
    • 使用sklearn逻辑回归模型

逻辑回归原理 什么是逻辑回归

注意,本文里的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 gleft( 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_ilog hat{y}_i+left( 1-y_i right) log left( 1-hat{y}_i right) right) \ text{其中}hat{y}=sigmoidleft( W^TX right) loss=−(Σyi​logy^​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 最小,这只是一种符号化形式 Pleft( Y|X right) =left. left{ begin{array}{c} hat{y}, y=1\ 1-hat{y}, y=0\ end{array} right. right} \ text{合并成一个形式:}hat{y}^yleft( 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{取}logtext{并加上负号变成取最小值(方便优化):}loss=-left( varSigma y_ilog hat{y}_i+left( 1-y_i right) log left( 1-hat{y}_i right) right) \ text{求解参数}w^*text{,使得}losstext{最小化:}w^*=argmin -left( varSigma y_ilog hat{y}_i+left( 1-y_i right) log left( 1-hat{y}_i right) right) \ text{解释下相关符号:}ytext{表示实际值;}hat{y}text{表示预测值;} \ w^*text{和}argmintext{表示当参数}wtext{为}w^*text{时使得}losstext{最小,这只是一种符号化形式} P(Y∣X)={y^​,y=11−y^​,y=0​}合并成一个形式:y^​y(1−y^​)1−y那么所有样本:iΠ​y^​i​yi​(1−y^​i​)1−yi​目标函数:maxiΠ​y^​i​yi​(1−y^​i​)1−yi​取log并加上负号变成取最小值(方便优化):loss=−(Σyi​logy^​i​+(1−yi​)log(1−y^​i​))求解参数w∗,使得loss最小化:w∗=argmin−(Σyi​logy^​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}pleft( x right) log frac{pleft( x right)}{qleft( x right)}=underset{x}{varSigma}pleft( x right) left( log pleft( x right) -log qleft( x right) right) \ =-underset{x}{varSigma}pleft( x right) log qleft( x right) -left( -underset{x}{varSigma}pleft( x right) log pleft( x right) right) \ =Hleft( p,q right) -Hleft( p right) \ text{其中}Hleft( p,q right) =-underset{x}{varSigma}pleft( x right) log qleft( x right) text{就是交叉熵,}Hleft( p right) text{是真实概率分布的信息熵} \ text{所以:}KLtext{散度}=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 gradientleft( w right) =frac{partial lossleft( w right)}{partial w} \ w_{t+1}=w_t-alpha .gradientleft( w right) \ text{符号解释:}w_ttext{和}w_{t+1}text{表示进行梯度下降的前后两个值(点);} \ alpha text{表示学习率或者叫步长;} \ gradientleft( w right) text{在公式中就是梯度,也是学习方向或者梯度方向,就是你要往哪个方向进行学习更新参数}w; \ text{在式子里含义就是}w_ttext{进行}alpha .gradientleft( 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( fleft( 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{解释下相关符号:}Jleft( w,x right) text{等价与}lossleft( w right) text{,就是损失函数的另一种写法;} \ y_pred_itext{等价于我上述损失函数的}hat{y}text{,表示的是预测值}y; \ y_itext{就是实际值}y; \ sigmoidleft( w^Tx right) =sigmoidleft( w_0+w_1x_1+...w_nx_n right) ,text{其中}w^Txtext{是向量的表示方式}; \ text{推导式子里有些有下角标有些没有} \ text{虽不严谨,但不影响推导理解,没有角标的都表示的是向量形式} 解释下相关符号:J(w,x)等价与loss(w),就是损失函数的另一种写法;y_predi​等价于我上述损失函数的y^​,表示的是预测值y;yi​就是实际值y;sigmoid(wTx)=sigmoid(w0​+w1​x1​+...wn​xn​),其中wTx是向量的表示方式;推导式子里有些有下角标有些没有虽不严谨,但不影响推导理解,没有角标的都表示的是向量形式

逻辑斯特回归为什么要对特征进行离散化
  1. 引入非线性:逻辑回归属于广义线性模型,表达能力受限;单变量离散化为N个后,每个变量有单独的权重,相当于为模型引入了非线性,能够提升模型表达能力,加大拟合; 离散特征的增加和减少都很容易,易于模型的快速迭代;
  2. 速度快:稀疏向量内积乘法运算速度快,计算结果方便存储,容易扩展;
  3. 鲁棒性:离散化后的特征对异常数据有很强的鲁棒性:比如一个特征是年龄>30是1,否则0。如果特征没有离散化,一个异常数据“年龄300岁”会给模型造成很大的干扰;
  4. 方便交叉与特征组合:离散化后可以进行特征交叉,由M+N个变量变为M*N个变量,进一步引入非线性,提升表达能力;
  5. 稳定性:特征离散化后,模型会更稳定,比如如果对用户年龄离散化,20-30作为一个区间,不会因为一个用户年龄长了一岁就变成一个完全不同的人。当然处于区间相邻处的样本会刚好相反,所以怎么划分区间是门学问;
  6. 简化模型:特征离散化以后,起到了简化了逻辑回归模型的作用,降低了模型过拟合的风险。
逻辑回归应用 优缺点(特点)
  • 轻量级训练快:因为是线性的套了个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()

以上,若有表述错误的地方,希望指出,谢谢!

转载请注明:文章转载自 www.051e.com
本文地址:http://www.051e.com/it/272744.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 ©2023-2025 051e.com

ICP备案号:京ICP备12030808号