逻辑回归
LogisticRegression基本概念
- 逻辑回归:解决的分类问题,是基于线性回归的衍生算法。
- 将样本的特征和样本发生的概率联系在一起,概率是一个数。
- 在之前的线性回归中,通过公式 y ^ = f ( x ) \hat{y}=f(x) y^=f(x)求出的 y ^ \hat{y} y^ 就是需要的值。例如预测成绩得到的就是成绩,预测价格得到的就是价格。
- 逻辑回归中,将
y
^
\hat{y}
y^再通过转换公式
p
^
=
p
(
y
^
)
\hat{p} = p(\hat{y})
p^=p(y^) 求出概率
p
^
\hat{p}
p^,根据概率再进行分类。
因此,逻辑回归其实可以看成回归算法也可看成分类算法,作为分类算法只可以解决二分类问题,当然可以通过改进解决多分类问题。
LogisticRegression数学模型
- 对于线性回归公式, y ^ = θ t ⋅ x b \hat{y}= \theta^t\cdot x_b y^=θt⋅xb 求得的可以是任意的值。
- 逻辑回归将线性回归方式求得的值,进行一个转换,变成[0,1]区间的概率。
- 这种转换通常采用Sigmoid函数:
σ
(
t
)
=
1
1
+
e
−
t
σ(t)= \frac{1}{1+e^{-t}}
σ(t)=1+e−t1 , 取值范围为[0,1]
- 将
y
^
=
θ
t
⋅
x
b
\hat{y}= \theta^t\cdot x_b
y^=θt⋅xb 带入sigmiod转换函数中,即得:
- 通常我们定义输出概率 p ^ > 0.5 \hat p>0.5 p^>0.5 ,预测结果 y ^ \hat y y^ 归为1类, 输出概率 p ^ < 0.5 \hat p<0.5 p^<0.5 ,预测结果 y ^ \hat y y^ 归为0类 。也就是 y ^ = θ t ⋅ x b \hat{y}= \theta^t\cdot x_b y^=θt⋅xb 大于0时分为1类,小于0时分为0类。因此 y ^ = θ t ⋅ x b = 0 \hat{y}= \theta^t\cdot x_b = 0 y^=θt⋅xb=0 就成为了分类的边界,称为决策边界。当然并非 θ t ⋅ x b = 0 \theta^t\cdot x_b = 0 θt⋅xb=0 才是决策边界,可定义任意threshold,即 θ t ⋅ x b = t h r e s h o l d \theta^t\cdot x_b = threshold θt⋅xb=threshold 皆可称为决策边界。(tips:决策边界的定义目的用于预测阶段分类别,训练阶段与决策边界的定义无关)
LogisticRegression损失函数
- 对于逻辑回归训练过程,损失函数定义应满足,真值为1类时,输出概率 p ^ \hat p p^ 若越大损失cost应该就要越小,若越小损失cost就要越大。同理当真值为0类时,输出概率 p ^ \hat p p^ 若越小对应的损失cost应该就要越小,若越大损失cost就要越大。
- 损失函数的定义:
1.MSE均方误差仍然有效: J ( θ ) = 1 m ∑ i = 1 m ( p i ^ − y i ) 2 J(\theta) = \frac{1}{m}\sum_{i=1}^m(\hat{p_{i}}-y_{i})^2 J(θ)=m1∑i=1m(pi^−yi)2 , p i ^ \hat{p_{i}} pi^即概率值[0,1], y i y_{i} yi即真值0或1。
2.二元交叉熵损失函数(binary_crossentropy): python中log()默认以e为底。
当y = 1 时,损失函数: − l o g ( p ^ ) -log(\hat p) −log(p^)
当y = 0 时,损失函数: − l o g ( 1 − p ^ ) -log(1-\hat p) −log(1−p^)
对应的坐标图:x 轴即 p ^ \hat p p^ 取值,范围 [0,1]。
从坐标图可以看出:
当样本y = 1时,预测的x(输出概率p)越大,cost就越小。
当样本y = 0时,预测的x(输出概率p)越小,cost就越小。
完全符合logistic损失函数的定义。
利用真值 y y y 将二者结合得到真正的损失函数: 当y=1时只会用到 − l o g ( p ^ ) -log(\hat p) −log(p^),当y=0时只会用到 − l o g ( 1 − p ^ ) -log(1-\hat p) −log(1−p^)
LogisticRegression梯度下降
- 对
l
o
g
(
σ
(
t
)
)
log(σ(t))
log(σ(t)) 求导 :
- 对损失函数前半部分进行求偏导:
- 对
l
o
g
(
1
−
σ
(
t
)
)
log(1-σ(t))
log(1−σ(t)) 求导 :
- 对损失函数后半部分进行求偏导:
- 前后结合化简:公式几乎和线性回归一模一样,不同处线性回归为
X
b
i
θ
X_{b}^i\theta
Xbiθ,逻辑回归为
σ
(
X
b
i
θ
)
σ(X_{b}^i\theta)
σ(Xbiθ)。
import numpy as np
from sklearn.metrics import accuracy_score
class LogisticRegression:
def __int__(self):
'''初始化线性回归模型'''
self.coef_ = None
self.interception_ = None
self._theta = None
def _sigmoid(self,t):
return 1 / (1 + np.exp(-t))
def fit_gd(self,x_train,y_train,eta = 0.001,n_iters = 1e4):
assert x_train.shape[0] == y_train.shape[0],"size of x must equal y"
# 损失函数
def J(theta,X_b,y):
p = self._sigmoid(X_b.dot(theta))
try:
return -np.sum(y * np.log(p) + (1-y) * np.log(1-p)) / len(y)
except:
return float('inf')
# 梯度计算
def dJ(theta,X_b,y):
return X_b.T.dot(self.sigmoid(X_b.dot(theta))-y)/len(X_b)
# 更新参数
def gradient_descent(init_theta,X_b,y,eta,n_iters,epsilon=1e-8):
theta = init_theta
count = 0
while count<n_iters:
last_theta = theta
gradient = dJ(theta,X_b,y)
theta = theta - eta * gradient
if(abs(J(theta,X_b,y)-J(last_theta,X_b,y)) < epsilon) :
break
count += 1
return theta
X_b = np.hstack([np.ones((len(x_train),1)),x_train])
init_theta = np.zeros(X_b.shape[1])
self._theta = gradient_descent(init_theta,X_b,y_train,eta,n_iters)
self.coef_ = self._theta[1:]
self.interception_ = self._theta[0]
return self
# 返回预测概率
def predict_proba(self,x_test):
assert self.interception_ is not None and self.coef_ is not None,"must fit before predict"
assert x_test.shape[1] == len(self.coef_),"the feature number of x_test must be equal to x_ train"
X_b = np.hstack([np.ones((len(x_test),1)),x_test])
return self._sigmoid(X_b.dot(self._theta))
# 返回按概率计算的分类结果
def predict(self,x_test):
assert self.interceptio self.predict_proba(x)n_ is not None and self.coef_ is not None,"must fit before predict"
assert x_test.shape[1] == len(self.coef_),"the feature number of x_test must be equal to x_ train"
proba = self.predict_proba(x_test)
return np.array(proba >= 0.5,dtype="int")
# 评估准确率
def score(self,x_test,y_test):
y_predict = self.predict(x_test)
return accuracy_score(y_test,y_predict)
def __repr__(self):
return "Logistic Regression"
Scikit-Learn中的LogisticRegression
主要参数:
参数名 | 含义 |
---|---|
penalty | 选择正则化方式: ‘l1’, ‘l2’(默认), ‘elasticnet’ or ‘none’ |
C | 正则化影响强度,越小正则效果越强,默认:1.0(必须为大于0的float) |
dual | 是否转化为对偶问题求解,默认=False。仅在solver为liblinear,penalty为l2时有效。当n_samples> n_features时,首选dual = False。 |
tol | 停止训练迭代的误差指标,默认:1e-4 |
fit_intercept | 是否存在截距,默认存在 |
solver | 优化方法:‘newton-cg’, ‘lbfgs’, ‘liblinear’(默认), ‘sag’, ‘saga’ 。‘liblinear’,坐标轴下降法,’lbfgs‘,’newton-cg’皆利用牛顿法(loss二阶导数),‘sag’(随机梯度下降)。小数据集建议使用‘liblinear’,大数据集使用‘sag’或’saga‘。1.saga:支持‘l1’, ‘l2’, ‘elasticnet’ or ‘none’。 2.liblinear:支持‘l1’, ‘l2’。 3.'newton-cg’, ‘lbfgs’, ‘sag’:支持 ‘l2’,‘none’ |
multi_class | 分类策略:‘ovr’(默认), ‘multinomial’(ovo), ‘auto’。sklearn的逻辑回归是默认支持多分类任务,默认使用‘ovr’且可搭配所有solver。若使用’multinomial‘策略,solver只能选择:‘newton-cg’, ‘lbfgs’, ‘sag’, ‘saga’。 |
n_jobs | 使用运算核心数,默认:none(1), -1代表使用全部 |
verbose | 训练资讯显示(仅于solver为liblinear,lbfgs有效),默认:0,数字越大越详细 |
max_iter | 训练最大迭代次数,默认:100。 |
class_weight | 各类别的权重:字典({0:0.9,1:0.1}),‘balanced’,默认None。如果class_weight选择balanced,会根据训练样本量来计算权重,某类样本量越多,权重越低,反之亦然。主要用于高度失衡的样本集。 |
主要参数:
参数名 | 含义 |
---|---|
Cs | 正则化影响强度,float或int,默认:10。如果Cs为int,则以1e-4和1e4之间的对数标度选择Cs值网格,再生成10个C值。 |
cv | 交叉验证折叠数k,默认:None |
refit | bool,默认:True。若为True,则从所有交叉验证中获取对应于最佳得分的coefs和C,并且使用这些参数进行最终训练。否则,取最佳交叉验证k折的平均值。 |
solver | 优化方法:默认变成lbfgs,其它与LogisticRegression一致。 |
- 上面主要列出了LogisticRegression没有或不一致的参数。
- LogisticRegressionCV利用Cs设置多个C值,再通过cv交叉验证获取最佳的C值,并得到最佳参数模型。
- 其实就是多实现了交叉验证和仅对C参数的网格搜索。
逻辑回归本质由线性回归发展而来,对于处理非线性的数据,同样也是使用sklearn.preprocessing. PolynomialFeatures 进行特征构造实现。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
np.random.seed(666)
X = np.random.normal(0, 1, size=(200, 2))
y = np.array((X[:,0]**2+X[:,1]**2)<1.5, dtype='int')
# 多项式逻辑回归
def PolynomialLogisticRegression(degree,C,penalty'):
return Pipeline([
('poly', PolynomialFeatures(degree=degree)),
('std_scaler', StandardScaler()),
('log_reg', LogisticRegression(C=C,penalty=penalty))
])
# 画决策边界(仅画两个特征,即二维)
def plot_decision_boundary(model, axis):
x0, x1 = np.meshgrid(
np.linspace(axis[0], axis[1], int((axis[1]-axis[0])*100)).reshape(-1, 1),
np.linspace(axis[2], axis[3], int((axis[3]-axis[2])*100)).reshape(-1, 1),)
X_new = np.c_[x0.ravel(), x1.ravel()]
y_predict = model.predict(X_new)
zz = y_predict.reshape(x0.shape)
custom_cmap = ListedColormap(['#EF9A9A','#FFF59D','#90CAF9'])
plt.contourf(x0, x1, zz, linewidth=5, cmap=custom_cmap)
poly_log_reg = PolynomialLogisticRegression(degree=2, C=0.1, penalty='l1')
poly_log_reg.fit(X, y)
plot_decision_boundary(poly_log_reg, axis=[-4, 4, -4, 4])
plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
plt.show()
- 使用OvR(One vs Rest)和 OvO(One vs One,是MvM(many-vs-many)的特例)两种策略,这两种策略可以使所有的二分类算法适用于多分类,不仅仅是LogisticRegression。
- OvR:
假设有4个类别,每次取1个类别和剩下的3个类别,就可以变成4个二分类问题,再利用LogisticRegression模型。当预测一个新样本,分别经过4种模型,得到4个概率值,取最高概率值的模型分类作为结果。
3.OvO(MvM,many-vs-many的特例):
假设4个类别,两两组合形成二分类,组合情况有: C m n = m ! n ! ( m − n ) ! C_{m}^n= \frac{m!}{n!(m-n)!} Cmn=n!(m−n)!m! 。当预测一个新样本,每种组合情况都会有一个分类结果,进行投票,多数决。比OvR 更耗时,但是更准确。
4.LogisticRegression中默认可使用多分类,可通过修改参数调整多分类使用策略:
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
#使用鸢尾花的所有数据,一共有3种分类
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)
# 默认的OvR策略
log_reg = LogisticRegression()
log_reg.fit(X_train, y_train)
log_reg.score(X_test, y_test)
>>> 0.94736842105263153
# 使用其它策略(mvm(ovo)等)
log_reg2 = LogisticRegression(multi_class="multinomial", solver="newton-cg")
log_reg2.fit(X_train, y_train)
log_reg2.score(X_test, y_test)
>>> 1.0
Scikit-learn中的OvR和OvO类
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.multiclass import OneVsRestClassifier
from sklearn.multiclass import OneVsOneClassifier
#使用鸢尾花的所有数据,一共有3种分类
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()
# OvR
ovr = OneVsRestClassifier(log_reg)
ovr.fit(X_train, y_train)
ovr.score(X_test, y_test)
>>> 0.94736842105263153
# OvO
ovo = OneVsOneClassifier(log_reg)
ovo.fit(X_train, y_train)
ovo.score(X_test, y_test)
>>> 1.0