为了实现Logistic回归分类器,我们可以在每个特征上都乘以一个回归系数,然后把所有的结果值相加,将这个总和代人Sigmoid函数中,进而得到一个范围在0~1之间的数值。任何大于0.5的数据被分人1类 ,小于0.5即被归人0类 ,所以Logistic回归也可以被看成是一种概率估计。逻辑回归的本质还是线性回归,母体函数是线性回归函数,只不过将结果值代入Sigmoid函数转换为0到1之间的数值用来完成分类。
线性回归方程如下所示:
(1)
为了将预测结果值转换到0~1之间的值,将代入Sigmoid函数中:
(2)
将式(1)代入到式(2)中,得到下式概率公式(3):
(3)
构造如下损失函数:
设 是当 时的概率,那么肯定希望概率越大越好,如果偏离的100%就给一定的惩罚,如果概率为0 ,则让其损失函数无限大。
反之,当 时,让其 的概率最大化,如果偏离100%就给一定的惩罚,如果概率为1,则让其损失无限大。
上式分段函数(4)可转换为下式(5):
(5)
上式便是经典的交叉熵损失函数,又称对数损失函数,此函数可衡量两个分布之间的举例,一般用于分类模型的损失函数。
当概率大于等于0.5时,我们认为,反之小于0.5时,认为,即如下式所示:
即决策边界为:
(7)
在实际分类问题中,决策边界可能会是一个弯曲的曲线或平面,但以上表达式为线性回归方程,注定最后的决策边界将会是一条直线或一个平面,无法对非线性关系的样本做到精准的分类。
所以为了更好的解决这类非线性的分类问题,我们可以引入多项式回归方程,通过对特征向量增加阶数实现非线性关系的数据集分类,具体实现方法可参考笔者另一篇博文:
多项式回归(非线性回归)的python代码实现_南山十一少的博客-CSDN博客
逻辑回归本身只能处理二分类问题,但通过巧妙的多次运用逻辑回归,也能完成多分类任务。(sklearn封装的逻辑回归算法可通过设置超参数处理多分类任务,使用方法见本文 5.2)
方法一:OVR法(One vs Rest)
假定N分类问题,首先将其中一个类别和其他类别作为一次二分类,然后从N类别中循环执行N次,分别得到每个样本N个类别的概率,挑出概率最高的类别作为次样本的类别分类。
方法二:OVO法(One vs One)
假定N分类,首先拿出两个类别进行二次分类任务,然后从N类别中循环挑选出两个类别进行分类计算概率,共计算次,得到每个样本N个类别的概率,挑出概率最高的类别作为次样本的类别分类。
首先编写Sigmoid函数、损失函数、损失函数的偏导,然后根据批量梯度下降的方法不断迭代计算参数,具体编码如下:
注意:
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
在sklearn中封装的逻辑函数中,自动封装了正则化,可以通过超参数进行调节,其正则化后的损失函数如下:
如果读者对正则化感兴趣,可以参考笔者另一篇博文:
模型正则化在多项式回归中的运用:Ridge回归(岭回归)、LASSO回归、弹性网络回归的原理及python代码实现_南山十一少的博客-CSDN博客
C:正则化强度权重,大于0的浮点数,不填写默认1.0。C越小,正则化的效力越强,参数会被压缩得越小。
penalty:正则化方式, 时,损失函数中的为系数值绝对值和,时,损失函数的为系数平方和
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))
如果要进行多分类任务,同样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))
本文通过讲解逻辑回归(对数几率回归)从回归模型到分类模型的演变过程以及损失函数的推导过程,然后利用梯度下降编写逻辑回归方法,最后通过调用sklearn封装的逻辑回归方法,分别演示了模型正则化和多分类的超参数使用。