1. python 原生实现
这里的原生实现异常粗糙(没有正则项,随机梯度上升),就是上一篇 原理篇 的代码实现,数据集直接来自sklearn iris(3分类问题),另外,手工提出了0,1两类,仅做了两类iris的分类。
对于 (h(X) = w_0 + w_1 x_1 + w_2 x_2 + … + W_m x_m) = (W^T X)
其中 (W =[w_0,w_1,…w_m] , X = [1,x_1,x_2,…,X_m]) 我习惯于把截距(w_0) 也合到向量中去了。
import numpy as np
from sklearn.datasets import load_iris
"""
加载数据
"""
def load_dataSet():
X, Y = load_iris(return_X_y=True)
X_data, Y_data = X[:100,:], Y[:100] ## 只要 0,1 两类,做两分类
Z = np.concatenate((X_data, Y_data.reshape(X_data.shape[0], 1)), axis=1)
np.random.shuffle(Z)
#分割数据为训练集和测试集
X_train, Y_train = Z[:75,0:-1], Z[:75,-1:]
X_test, Y_test = Z[75:, :-1], Z[75:, -1:]
return X_train,Y_train,X_test,Y_test
"""
模型训练
"""
def sigmoid(W,X):
return 1.0 / (np.exp( - np.dot(W.T,X)) + 1)
def LRModel(W,X,Y,epoch,alpha):
for i in range(0,epoch):
Yhat = sigmoid(W, X)
J = np.dot(Y, np.log(Yhat.T + 0.001)) + np.dot((1 - Y), np.log(1 - Yhat.T +0.001)) ## 这里我手工加了一个0.001,防止处理log0的情况
W = W + (alpha * (np.dot((Y - Yhat),X.T)).T)
return W
"""
模型预测,计算各个指标
"""
def predict(X_test,Y_test,Wf):
Ypre = sigmoid(Wf, X_test)
print(Ypre)
Ypre = np.where(Ypre > 0.5, 1., 0.)
print(Ypre)
print(Y_test)
acc = Ypre - Y_test
print(acc)
if __name__ == "__main__":
# 初始化W
W0 = np.random.randn(5,1) # 列向量
print(W0)
X_train,Y_train,X_test,Y_test = load_dataSet()
X_train = np.concatenate((np.ones((X_train.shape[0], 1)), X_train),axis=1)
X_test = np.concatenate((np.ones((X_test.shape[0], 1)), X_test), axis=1)
Wf = LRModel(W0,X_train.T,Y_train.T,1000,0.05)
print("最终学习的参数为:")
print(Wf)
predict(X_test.T,Y_test.T,Wf)
结果
[[-0.32794377]
[-0.16341301]
[-0.46871745]
[-0.58777365]
[ 0.72274566]]
最终学习的参数为:
[[-0.92044644]
[-0.86818604]
[-3.75098037]
[ 5.22856039]
[ 3.17813294]]
[[9.99919845e-01 9.99939370e-01 9.99912569e-01 9.99983816e-01
9.97146748e-01 6.61803146e-05 3.19672831e-06 9.99976409e-01
9.99999553e-01 9.99837974e-01 9.99827795e-01 4.69817201e-04
1.65549104e-05 9.99878991e-01 9.99982895e-01 1.66078700e-04
9.88524297e-01 1.12949239e-05 9.99600934e-01 6.22679950e-05
9.99989180e-01 1.44838053e-04 4.05583288e-05 3.13544364e-04
9.99987482e-01]]
[[1. 1. 1. 1. 1. 0. 0. 1. 1. 1. 1. 0. 0. 1. 1. 0. 1. 0. 1. 0. 1. 0. 0. 0.
1.]]
[[1. 1. 1. 1. 1. 0. 0. 1. 1. 1. 1. 0. 0. 1. 1. 0. 1. 0. 1. 0. 1. 0. 0. 0.
1.]]
[[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0.]] ## acc,可以看到在测试集上完全预测正确
2. scikit-learn代码实现
scikit-learn中LR的损失函数函数和我们想象的不一样,一方面,他加入了正则项,另一方面,scikit-learn中(y_i)取 1 或者 -1,故他构造的损失函数形式与我们常规的构造不同,(其实,不管是机器学习,还是深度学习中,在得出目标函数后,我们都要想办法把(y_i)加入目标函数中,得到了损失函数,所以可以灵活构造哈)
[hat{y_i} = P(y_i| x_i;W) = frac{1}{e^{-x_i^T W + c} + 1} (1)
]
其中(hat{y_i})为预测值,W为列向量,c为标量
[(hat{y_i} = 1) = P(y_i = 1| x_i;W) = frac{1}{e^{y_i(-x_i^TW + c)} + 1} = frac{1}{e^{-x_i^T W + c} + 1} (2)
]
[(hat{y_i} = -1) = P(y_i = -1| x_i;W) = 1 – P(y_i = 1| x_i;W) = 1 – frac{1}{e^{-x_i^T W + c} + 1} = frac{1}{e^{x_i^TW + c} + 1} = frac{1}{e^{y_i(-x_i^T W + c)} + 1} (3)
]
综上(0)(1)(2),sklearn LR的损失函数可定义为:
[J(W,c) = mathop{argmax}_{W,c} prod_i^m frac{1}{e^{y_i(-x_i^T W + c)} + 1}
]
即为等价于:
[J(W,c) = mathop{argmax}_{W,c}( – sum_{i}^{m} log(e^{y_i(-x_i^T W + c)} + 1) )
]
即为:
[J(W,c) = mathop{argmin}_{W,c} sum_{i}^{m} log(exp(-y_i(x_i^T W + c)) + 1)
]
引入(L_2)正则化:
[J(W,c) = mathop{argmin}_{W,c} sum_{i}^{m} log(exp(-y_i(x_i^T W + c)) + 1) + frac{1}{2} lambda W^T W (4)
]
其中 (lambda)是超参且必须为正浮点数,需要手工调参
(4)式求出来的参数W,c的值和下式无异:
[J(W,c) = mathop{argmin}_{W,c} lambda sum_{i}^{m} log(exp(-y_i(x_i^T W + c)) + 1) + frac{1}{2} W^T W (5)
]
对于无异的解释:
另外除了L2正则化外,还有L1,Elastic-Net 正则化
L1正则化:
[min_{W,c} |w|_1 + C sum_{i=1}^n log(exp(- y_i (X_i^T w + c)) + 1)
]
Elastic-Net正则化:
[min_{w, c} frac{1 – ho}{2}w^T w + ho |w|_1 + C sum_{i=1}^n log(exp(- y_i (X_i^T w + c)) + 1)
]
简单来讲,L1正则化偏向于起到特征选择作用,L2正则化偏向于起到防止过拟合的作用,Elastic-Net综合了L1,L2两种正则化的作用
另外说一句,由于水平有限,鄙人仅关注于sklearn的API的使用,各个参数的含义,不管追究sklearn中算法的具体实现,原因参考如下:https://www.zhihu.com/question/37217348/answer/71147129 [Glenn Qian的回答]
sklearn API
sklearn.linear_model.LogisticRegression 参数介绍
class sklearn.linear_model.LogisticRegression(penalty='l2', *, dual=False, tol=0.0001, C=1.0, fit_intercept=True, intercept_scaling=1,
class_weight=None, random_state=None, solver='lbfgs', max_iter=100, multi_class='auto',
verbose=0, warm_start=False, n_jobs=None, l1_ratio=None)
penalty{‘l1’, ‘l2’, ‘elasticnet’, ‘none’}, default=’l2’
penalty表示选择的正则化,可选L1,L2,elasticnet, none,默认是l2
dual: bool, default=False
当使用solver=’liblinear’,l2正则化时,才有dual = true
tol: float, default=1e-4
停止训练的条件,当迭代前后的函数差值小于等于tol时就停止
C: float, default=1.0
正则化强度的倒数,必须为正浮点数。越小的值表示越强的正则化
fit_intercept: bool, default=True
指定是否应将常量(也称为偏差或截距)添加到决策函数
intercept_scaling: float, default=1
仅在使用solver = ‘liblinear’ 且fit_intercept = True时有用。
*intercept_scaling: float, default=1
class_weight: dict or ‘balanced’, default=None
以字典或者’balanced’形式给出模型的参数。如果没有给出,所有类的权重都应该是1。“balanced” 模式使用y的值自动调整权值与输入数据中的类频率成反比,如n_samples / (n_classes * np.bincount(y))。
注意,如果指定了sample_weight,那么这些权重将与sample_weight相乘(通过fit方法传递)。
0.17版本新增:class_weight= ‘balanced’
random_state: int, RandomState instance, default=None
在solver= ‘sag’,’saga’或 ‘liblinear’ 才需要有random_state设置。
solver: {‘newton-cg’, ‘lbfgs’, ‘liblinear’, ‘sag’, ‘saga’}, default=’lbfgs’
求解器solver可以使用 ‘newton-cg’,’lbfgs’,’liblinear’,’sag’,’sage’算法来更新参数,其中sage 表示 Stochastic Average Gradient descent。选择solver的一般原则如下:
对于小型数据集,选择’liblinear’,大型数据集,可选择‘sag’,’saga’;
‘newton-cg’, ‘lbfgs’, ‘sag’ and ‘saga’ 应用在L2正则化或没有正则化的地方;
‘liblinear’ 和‘saga’ 也可以应用在L1正则化出现的地方;
‘saga’支持‘elasticnet’正则化;
‘liblinear’不支持penalty=’none’;
对于多分类问题,只有“newton-cg”, “sag”, “saga”和“lbfgs”直接处理多分类问题。而”liblinear”多分类问题转化成多个二分类问题(one-versus-rest,又称ovr)
max_iter: int, default=100
使求解器solver收敛所需的最大迭代次数
multi_class: {‘auto’, ‘ovr’, ‘multinomial’}, default=’auto’
multi_class: {‘auto’, ‘ovr’, ‘multinomial’}, default=’auto’
如果选择的选项是’ovr’,那么将多分类转化成多个二分类问题。对于“multinomial”损失最小化是多项式损失适合整个概率分布,即使当数据是二进制。当求解器= ‘ liblinear ‘时,’多项’不可用。auto选择’ ovr ‘如果数据是二进制的,或者如果solver= ‘ liblinear ‘,否则选择’多项’。
verbose: int, default=0
记录训练的日志,int类型。默认为0。就是不输出训练过程,1的时候偶尔输出结果
warm_start: bool, default=False
热启动参数,bool类型。默认为False。如果为True,则下一次训练是以重新上一次的调用作为初始化
n_jobs : int, default: 1
用cpu的几个核来跑程序,-1表示使用所有处理器。
l1_ratio: float, default=None
当 0 <= l1_ratio <= 1 且 penalty = ‘Elasticnet’时, 使用的才是’elasticnet’正则化。设置l1_ratio=0 相当于使用penalty=’l2’,而设置l1_ratio=1 相当于使用penalty=’l1’。
参考文献
[1] https://www.cnblogs.com/wjq-Law/p/9779657.html (参数的一些解释)
[2] https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html