逻辑回归(Logistic回归又名对数几率回归)原理及python代码实现

1. 公式推导

        为了实现Logistic回归分类器,我们可以在每个特征上都乘以一个回归系数,然后把所有的结果值相加,将这个总和代人Sigmoid函数中,进而得到一个范围在0~1之间的数值。任何大于0.5的数据被分人1类 ,小于0.5即被归人0类 ,所以Logistic回归也可以被看成是一种概率估计。逻辑回归的本质还是线性回归,母体函数是线性回归函数,只不过将结果值代入Sigmoid函数转换为0到1之间的数值用来完成分类。

线性回归方程如下所示:

\hat{y}=\theta ^{T}X_{b}                                                                        (1)

为了将预测结果值转换到0~1之间的值,将\hat{y}代入Sigmoid函数中:

\sigma (\hat{y})= \frac{1}{1+e^{-\hat{y}}}                                                                        (2)

将式(1)代入到式(2)中,得到下式概率公式(3):

\hat{p}= \frac{1}{1+e^{-\theta ^{T}X_{b}}}                                                                       (3)

        构造如下损失函数:

J(\theta ) = \left\{\begin{matrix} -log(\hat{p}) & if & y=1\\ -log(1-\hat{p}) &if& y=0\end{matrix}\right.                                                (4)

        设 \hat{p} 是当 y = 1 时的概率,那么肯定希望概率越大越好,如果偏离的100%就给一定的惩罚,如果概率为0 ,则让其损失函数无限大。

        反之,当 y = 0 时,让其1-\hat{p} 的概率最大化,如果偏离100%就给一定的惩罚,如果概率为1,则让其损失无限大。

        上式分段函数(4)可转换为下式(5):

J(\theta )=-ylog(\hat{p})-(1-y)log(1-\hat{p})                                (5)

        上式便是经典的交叉熵损失函数,又称对数损失函数,此函数可衡量两个分布之间的举例,一般用于分类模型的损失函数。

2. 决策边界

        当概率大于等于0.5时,我们认为y = 1,反之小于0.5时,认为y = 0,即如下式所示:

\hat{y} = \left\{\begin{matrix} 1, &\hat{p}\geqslant 0.5 & \theta ^{T}.X_{b}\geqslant 0\\ 0, &\hat{p}< 0.5& \theta ^{T}.X_{b}< 0\end{matrix}\right.                                        (6)

即决策边界为:

\theta ^{T}.X_{b} = 0                                                                        (7)

        在实际分类问题中,决策边界可能会是一个弯曲的曲线或平面,但以上表达式为线性回归方程,注定最后的决策边界将会是一条直线或一个平面,无法对非线性关系的样本做到精准的分类。

所以为了更好的解决这类非线性的分类问题,我们可以引入多项式回归方程,通过对特征向量增加阶数实现非线性关系的数据集分类,具体实现方法可参考笔者另一篇博文:

多项式回归(非线性回归)的python代码实现_南山十一少的博客-CSDN博客

3. 多分类问题

        逻辑回归本身只能处理二分类问题,但通过巧妙的多次运用逻辑回归,也能完成多分类任务。(sklearn封装的逻辑回归算法可通过设置超参数处理多分类任务,使用方法见本文 5.2)

方法一:OVR法(One vs Rest)

假定N分类问题,首先将其中一个类别和其他类别作为一次二分类,然后从N类别中循环执行N次,分别得到每个样本N个类别的概率,挑出概率最高的类别作为次样本的类别分类。

方法二:OVO法(One vs One)

假定N分类,首先拿出两个类别进行二次分类任务,然后从N类别中循环挑选出两个类别进行分类计算概率,共计算C_{n}^{2}次,得到每个样本N个类别的概率,挑出概率最高的类别作为次样本的类别分类。

4. 自制代码实现

首先编写Sigmoid函数、损失函数、损失函数的偏导,然后根据批量梯度下降的方法不断迭代计算参数\theta,具体编码如下:

注意:

1. 在编写Sigmoid函数时,因为代入的-t = X_b.dot(theta) 比较大,通过指数函数np.exp(-t)后容易导致数据溢出,建议更换为另一个公式代替:return .5 * (1 + np.tanh(.5 * t)),否则会有如下报错:

RuntimeWarning: overflow encountered in exp
  return 1. / (1. + np.exp(-t))

2. 计算损失函数时,由于分母过大,容易导致精度不够,建议增加浮点数的精度,在y_hat后面增加1e-6,否则会有如下报错

RuntimeWarning: divide by zero encountered in log
  return - np.sum(y * np.log(y_hat) + (1 - y) * np.log(1 - y_hat)) / len(y)

RuntimeWarning: invalid value encountered in multiply
  return - np.sum(y * np.log(y_hat) + (1 - y) * np.log(1 - y_hat)) / len(y)

RuntimeWarning: invalid value encountered in scalar subtract
  if abs(J(theta, X_b, y) - J(last_theta, X_b, y)) < epsilon:

# 自编逻辑回归函数
def MyselfLogisticRegression(X_train, y_train, eta=0.01, n_iters=1e4):
    def sigmoid(t):
        # return 1. / (1. + np.exp(-t))
        return .5 * (1 + np.tanh(.5 * t))

    def J(theta, X_b, y):
        y_hat = sigmoid(X_b.dot(theta))
        try:
            return - np.sum(y * np.log(y_hat + 1e-6) + (1 - y) * np.log(1 - y_hat + 1e-6)) / len(y)
        except:
            return float('inf')

    def dJ(theta, X_b, y):
        return X_b.T.dot(sigmoid(X_b.dot(theta)) - y) / len(y)

    def gradient_descent(X_b, y, initial_theta, eta, n_iters=1e4, epsilon=1e-8):

        theta = initial_theta
        cur_iter = 0

        while cur_iter < n_iters:
            gradient = dJ(theta, X_b, y)
            last_theta = theta
            theta = theta - eta * gradient
            if abs(J(theta, X_b, y) - J(last_theta, X_b, y)) < epsilon:
                break

            cur_iter += 1

        return theta

    X_b = np.hstack([np.ones((len(X_train), 1)), X_train])
    # initial_theta = np.zeros(X_b.shape[1])
    initial_theta = np.random.randn(X_b.shape[1])
    theta = gradient_descent(X_b, y_train, initial_theta, eta, n_iters)

    return theta

5 调用sklearn的逻辑回归函数

5.1 模型正则化超参数使用

在sklearn中封装的逻辑函数中,自动封装了正则化,可以通过超参数进行调节,其正则化后的损失函数如下:

C.J(\theta ) + L

如果读者对正则化感兴趣,可以参考笔者另一篇博文:

模型正则化在多项式回归中的运用:Ridge回归(岭回归)、LASSO回归、弹性网络回归的原理及python代码实现_南山十一少的博客-CSDN博客

C:正则化强度权重,大于0的浮点数,不填写默认1.0。C越小,正则化的效力越强,参数会被压缩得越小。

penalty:正则化方式, penalty = l_{1}时,损失函数中的L为系数值绝对值和,penalty = l_{2}时,损失函数的L为系数平方和

solver:优化算法选择参数,参数为liblinear时,使用了坐标轴下降法迭代损失函数。

具体实现代码如下:

from sklearn import datasets
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.model_selection import train_test_split
import numpy as np

if __name__ == "__main__":
    # 构造带随机误差的呈非线性关系的数据集
    np.random.seed(666)
    X = np.random.normal(0, 1, size=(200, 2))
    y = np.array((X[:, 0] ** 2 + X[:, 1]) < 1.5, dtype='int')
    for _ in range(20):
        y[np.random.randint(200)] = 1
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)

    # 预处理数据进行非线性化多项式组合
    poly = PolynomialFeatures(degree=20)
    poly.fit(X_train)
    X_train = poly.transform(X_train)
    X_test = poly.transform(X_test)

    # 调用sklearn逻辑回归函数
    poly_log_reg = LogisticRegression(C=0.2,solver='liblinear',penalty="l1")
    poly_log_reg.fit(X_train, y_train)
    train_score = poly_log_reg.score(X_train, y_train)
    test_score = poly_log_reg.score(X_test, y_test)
    print(
        "poly_log_reg.intercept_ = {}, poly_log_reg.coef_ = {}".format(poly_log_reg.intercept_[0], poly_log_reg.coef_))
    print("PolynomialLogisticRegression train_score = {}, test_score = {}".format(train_score, test_score))

    # 调用自编逻辑回归函数对比
    theta = MyselfLogisticRegression(X_train, y_train)
    poly_log_reg.intercept_[0] = theta[0]
    poly_log_reg.coef_[0] = theta[1:]
    print("poly_log_reg.intercept_ = {}, poly_log_reg.coef_ = {}".format(poly_log_reg.intercept_, poly_log_reg.coef_))
    train_score = poly_log_reg.score(X_train, y_train)
    test_score = poly_log_reg.score(X_test, y_test)
    print("MyselfLogisticRegression train_score = {}, test_score = {}".format(train_score, test_score))

5.2 多分类超参数使用

如果要进行多分类任务,同样sklearn也封装了OVR和OVO的方法,只需通过超参数设置便能使用。

multi_class:多分类模式

ovr:,一对剩余。有K类,则训练K个模型,每个模型把第i类当一类,其余当一类。最后选择预测概率最高的一类作为预测类别。
multinomial:多项模式,OVR模式,两两分类,最后选择预测概率最高的一类作为预测类,值得注意的是,如果选择次模式,solver超参数需要选择newton-cg

代码如下:

if __name__ == "__main__":
    # 鸢尾花为例调用sklearn逻辑回归函数进行多分类试验
    iris = datasets.load_iris()
    X = iris.data
    y = iris.target
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)
    log_reg = LogisticRegression(multi_class='multinomial',solver='newton-cg')
    log_reg.fit(X_train, y_train)
    score = log_reg.score(X_test, y_test)
    y_predict = log_reg.predict(X_test)
    print("multi_class predict y_predict = {}.".format(y_predict))
    print("multi_class LogisticRegression score = {}".format(score))

6 结论

        本文通过讲解逻辑回归(对数几率回归)从回归模型到分类模型的演变过程以及损失函数的推导过程,然后利用梯度下降编写逻辑回归方法,最后通过调用sklearn封装的逻辑回归方法,分别演示了模型正则化和多分类的超参数使用。

你可能感兴趣的:(机器学习,逻辑回归,回归,python)