逻辑回归(Logistic Regression)
逻辑回归,本质是一种线性模型 y = w x + b y=wx+b y=wx+b。
不过逻辑回归和线性模型不同的是,为了实现二分类任务,加了 s i g m o d sigmod sigmod 函数。
s i g m o i d sigmoid sigmoid 函数,使得预测的结果从样本成绩变成了样本成绩的概率。
如果这个概率 p > 0.5 p > 0.5 p>0.5,把这个数据分类为 y = 1 y=1 y=1(正样本)。
如果这个概率 p < 0.5 p < 0.5 p<0.5,就把这个数据分类为 y = 0 y=0 y=0(负样本)。
这个损失函数的意义是模型预测出来的值 p p p 要和样本的标签值 y y y 尽量一致,即正样本的预测值要尽量为正,负样本要为负,否则会产生一个损失值。
按照 y 、 p y、p y、p 之间的关系,我们反过来设计:
用数学语言表达:
函数图像:

当
p
p
p 接近 1 时,损失函数接近正无穷,会分为正样本,但实际值却是负样本。
所以,我们会给一个正无穷的惩罚,直到 p p p 的减小,惩罚值会越来越低,直到当 p = 0 p=0 p=0 时,会分为负样本,实际值也是负样本,就没有惩罚。
不过目前这个损失函数是分类函数形式,我们需要合并为一个函数:
这是对单个训练样本(一张图)的损失函数,但我们输入都神经网络的通常是一个训练集(大量的图片)。
我们需要累加单个训练样本的结果,再求平均值:
为什么使用这个损失函数呢?
这个损失函数(逻辑回归)的函数图像是一个向下凸的图形。

因为学习,就是找到一组 w 和 b,使这个损失函数最小。
也就是在这个函数图像的底部找到一组 w 和 b。
这个损失函数没有局部最优解,只存在唯一的全局最优解。
我们要做的,是求出成本函数的梯度,而后运用梯度下降法求出损失最小的一组 w 和 b。
损失函数 L o s s ( p , y ) Loss(p, y) Loss(p,y),是指对单个样本的做的损失。
成本函数 J ( θ ) J(\theta) J(θ),是数据集上总的成本和损失。
求损失函数的梯度:
Δ
J
=
{
∂
J
(
θ
)
∂
θ
0
∂
J
(
θ
)
∂
θ
1
⋅
⋅
⋅
∂
J
(
θ
)
∂
θ
n
}
\Delta J=
比较难处理的就是 σ ( t ) \sigma(t) σ(t):
对 σ ( t ) \sigma(t) σ(t) 求导:
对 l o g σ ( t ) log~\sigma(t) log σ(t) 求导:
成本函数 J J J 分成了俩段,先对前段求导:
对后段求导:
其中: − 1 1 − σ ( t ) = 1 1 + e − t 1 + e − t − 1 1 + e − t = 1 + e − t e − t -\frac{1}{1-\sigma(t)}=\frac{1}{\frac{1+e^{-t}}{1+e^{-t}}-\frac{1}{1+e^{-t}}}=\frac{1+e^{-t}}{e^{-t}} −1−σ(t)1=1+e−t1+e−t−1+e−t11=e−t1+e−t
代入: l o g ( 1 − σ ( t ) ) ′ = 1 + e − t e − t ⋅ ( 1 + e − t ) − 2 ⋅ e − t = − ( 1 + e − t ) − 1 = − σ ( t ) log(1-\sigma(t))'=\frac{1+e^{-t}}{e^{-t}}·(1+e^{-t})^{-2}·e^{-t}=-(1+e^{-t})^{-1}=-\sigma(t) log(1−σ(t))′=e−t1+e−t⋅(1+e−t)−2⋅e−t=−(1+e−t)−1=−σ(t)
对成本函数 J ( θ ) J(\theta) J(θ) 求导,得先把俩段导数相加:
相加结果:
对某个 θ j \theta_{j} θj 求导,消去负号,结合相加结果,得出:
其中的 σ ( X b i θ ) \sigma(X^{i}_{b}\theta) σ(Xbiθ) 就是预测值 p o r y ^ p~or~\hat{y} p or y^。
要求 Δ J \Delta J ΔJ 的梯度:
编程实现:
import numpy as np
from .metrics import accuracy_score
class LogisticRegression:
def __init__(self):
"""初始化Logistic Regression模型"""
self.coef_ = None
self.intercept_ = None
self._theta = None
def _sigmoid(self, t):
return 1. / (1. + np.exp(-t))
def fit(self, X_train, y_train, eta=0.01, n_iters=1e4):
"""根据训练数据集X_train, y_train, 使用批量-梯度下降法训练Logistic Regression模型"""
assert X_train.shape[0] == y_train.shape[0], \
"the size of X_train must be equal to the size of y_train"
def J(theta, X_b, y):
y_hat = self._sigmoid(X_b.dot(theta))
try:
return - np.sum(y*np.log(y_hat) + (1-y)*np.log(1-y_hat)) / 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(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])
self._theta = gradient_descent(X_b, y_train, initial_theta, eta, n_iters)
self.intercept_ = self._theta[0]
self.coef_ = self._theta[1:]
return self
def predict_proba(self, X_predict):
"""给定待预测数据集X_predict,返回表示X_predict的结果概率向量"""
assert self.intercept_ is not None and self.coef_ is not None, \
"must fit before predict!"
assert X_predict.shape[1] == len(self.coef_), \
"the feature number of X_predict must be equal to X_train"
X_b = np.hstack([np.ones((len(X_predict), 1)), X_predict])
return self._sigmoid(X_b.dot(self._theta))
def predict(self, X_predict):
"""给定待预测数据集X_predict,返回表示X_predict的结果向量"""
assert self.intercept_ is not None and self.coef_ is not None, \
"must fit before predict!"
assert X_predict.shape[1] == len(self.coef_), \
"the feature number of X_predict must be equal to X_train"
proba = self.predict_proba(X_predict)
return np.array(proba >= 0.5, dtype='int')
def score(self, X_test, y_test):
"""根据测试数据集 X_test 和 y_test 确定当前模型的准确度"""
y_predict = self.predict(X_test)
return accuracy_score(y_test, y_predict)
def __repr__(self):
return "LogisticRegression()"
sklearn 调包:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris # 导入鸢尾花数据集
from sklearn.model_selection import train_test_split # 导入数据划分函数
from sklearn.linear_model import LogisticRegression # 导入逻辑回归
# 导入评价指标
from sklearn.metrics import accuracy_score
iris = load_iris()
iris_X = iris.data[:100, ] # x有4个属性,共有100个样本,鸢尾花的label原本是3类,这里为了展示二分类,我只取了鸢尾花的前100个数据,也就是label只有0和1
iris_y = iris.target[:100, ] # y的取值有2个,分别是0,1
model = LogisticRegression() # 选逻辑回归作为分类器
model.fit(X_train, y_train) # 训练模型
y_test_pred = model.predict(X=X_test) # 预测测试集的label
print(y_test_pred) # 模型预测的测试集label
print(y_test) # 测试集实际label
accuracy_score(y_test, y_test_pred) # 查看模型预测的准确率
逻辑回归只能解决二分类的问题,现在我们可以通过 OvR、OvO 实现多分类。
如下图解决四分类问题:

n
n
n 个类别就进行
n
n
n 次分类,选择分类得分最高的。
from sklearn.multiclass import OneVsRestClassifier
ovr = OneVsRestClassifier(log_reg)
ovr.fit(X_train, y_train)
ovr.score(X_test, y_test)
如下图解决四分类问题:

n
n
n 个类别就进行
C
(
n
,
2
)
C(n,~2)
C(n, 2) 次分类,选择赢数最高的分类。
from sklearn.multiclass import OneVsOneClassifier
ovo = OneVsOneClassifier(log_reg)
ovo.fit(X_train, y_train)
ovo.score(X_test, y_test)