机器学习之LR算法理论和实战(实战篇)
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}\)为预测值,W为列向量,c为标量
综上(0)(1)(2),sklearn LR的损失函数可定义为:
即为等价于:
即为:
引入\(L_2\)正则化:
其中 \(\lambda\)是超参且必须为正浮点数,需要手工调参
(4)式求出来的参数W,c的值和下式无异:
对于无异的解释:
另外除了L2正则化外,还有L1,Elastic-Net 正则化
- L1正则化:
- Elastic-Net正则化:
简单来讲,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#sklearn.linear_model.LogisticRegression