菜鸟一枚,有错误欢迎指正,但不要喷。
在线性回归中,要求解的模型是
实际上线性回归模型在稍加修改后也可以求解其他问题,例如考虑模型
两边同时取对数后有
在形式上仍然是线性回归,但实际上已经是输入空间到输出空间的非线性映射。更一般的,考虑单调可微函数,令
这样得到的模型称为广义线性模型,其中函数称为“联系函数”。
接下来就可以利用广义线性模型来解决分类问题,也就是对数几率回归。
在二分类任务中,输出标记,而线性回归模型产生的预测值是实值,于是需要将实值转换为0/1值,最理想的函数是单位越阶函数(亦称Heaviside函数)
即若预测值大于0就判为正例,小于0则判为反例,为0则任意判别。而单位越阶函数不连续,因此不能用作,因此便有了替代函数
这是一个Sigmoid函数,即形似S型的函数。
Sigmoid函数能够将z值转换为0和1之间的y值,其输出值在z=0附近变化很陡,能够很好的近似单位越阶函数。
将Sigmoid函数作为代入式(4)得到
若则预测为正例,反之预测为反例。上式可以变化为
若将看作样本作为正例的可能性,则是其反例的可能性。两者的比值
称为“几率",反应了样本作为正例的相对可能性,取对数则得到"对数几率"
这就是对数几率回归名称的由来。对数几率回归在得到分类结果的同时还能得到近似概率预测。
接下来介绍如何确定和。如果将式(8)中的看作类后验概率,式(8)可重写为
显然有
于是可以通过极大似然估计来估计,,给定数据集,对率回归模型最大化对数似然
为方便书写,令,,则可简写为,再令,,接下来对似然函数进行化简,可以写作:
取对数
取指数
代入式(14)得
于是最大化的目标为
等价于最小化
这是关于的高阶可导凸函数,使用梯度下降法即可求解,仿照scikit-learn的实现模式,代码如下:
from sklearn.model_selection import train_test_split
from sklearn import datasets
from sklearn import preprocessing
import numpy as np
# 对数几率回归
class LogitRegression:
def __init__(self):
self._beta = None
self.coef_ = None
self.interception_ = None
def _sigmoid(self, x):
return 1. / (1. + np.exp(-x))
def fit(self, X, y, eta=0.01, n_iters=1e4, eps=1e-8):
assert(X.shape[0] == y.shape[0])
def L(beta, X_b, y):
try:
vec = beta.dot(X_b.T)
return np.sum(-vec * y + np.log(1 + np.exp(vec))) / len(X_b)
except Exception:
return float('inf')
def dL(beta, X_b, y):
try:
vec = beta.dot(X_b.T)
coef = np.exp(vec) / (1 + np.exp(vec))
return (-y.dot(X_b) + X_b.T.dot(coef)) / len(X_b)
except Exception:
return float('inf')
def gradient_decent(X_b, y, initial_beta, eta, n_iters, eps):
i_iters = 0
beta = initial_beta
while i_iters < n_iters:
gradient = dL(beta, X_b, y)
last_beta = beta
beta = beta - eta * gradient
if (abs(L(beta, X_b, y) - L(last_beta, X_b, y)) < eps):
break
i_iters += 1
return beta
X_b = np.hstack([np.ones((len(X), 1)), X])
initial_beta = np.zeros(X_b.shape[1])
self._beta = gradient_decent(X_b, y, initial_beta, eta, n_iters, eps)
self.coef_ = self._beta[1:]
self.interception_ = self._beta[0]
return self
def fit_sgd(self, X, y, n_iters=5, t0=5, t1=50):
assert X.shape[0] == y.shape[0]
assert n_iters >= 1
def dL_sgd(beta, X_b_i, y_i):
coef = np.exp(beta.dot(X_b_i)) / (1 + np.exp(beta.dot(X_b_i)))
return (-y_i + coef) * X_b_i
def gradient_decent_sgd(X_b, y, initial_beta, n_iters, t0, t1):
def learning_rate(t):
return t0 / (t1 + t)
beta = initial_beta
m = len(X_b)
for i in range(n_iters):
indexes = np.random.permutation(m)
X_b_new = X_b[indexes]
y_new = y[indexes]
for j in range(m):
gradient = dL_sgd(beta, X_b_new[j], y_new[j])
beta = beta - learning_rate(i * m + j) * gradient
return beta
X_b = np.hstack([np.ones((len(X), 1)), X])
initial_beta = np.zeros(X_b.shape[1])
self._beta = gradient_decent_sgd(X_b, y, initial_beta, n_iters, t0, t1)
self.coef_ = self._beta[1:]
self.interception_ = self._beta[0]
return self
def predict_probability(self, X_predict):
X_b = np.hstack([np.ones((len(X_predict), 1)), X_predict])
return self._sigmoid(X_b.dot(self._beta))
def predict(self, X_predict):
assert self.coef_ is not None and self.interception_ is not None
assert X_predict.shape[1] == len(self.coef_)
probability = self.predict_probability(X_predict)
return np.array(probability >= 0.5, dtype='int')
def score(self, X_test, y_test):
y_predict = self.predict(X_test)
return np.sum(y_predict == y_test) / len(y_test)
def __repr__(self):
return "LogitRegression()"
if __name__ == "__main__":
breast_cancer = datasets.load_breast_cancer()
y = breast_cancer.target
X = breast_cancer.data
train_X, test_X, train_y, test_y = train_test_split(X, y, test_size=0.3, random_state=123)
scaler = preprocessing.StandardScaler()
train_X = scaler.fit(train_X).transform(train_X)
test_X = scaler.transform(test_X)
logit = LogitRegression()
logit.fit(train_X, train_y)
print(logit.score(test_X, test_y))
运行结果如下所示:
扩展:多分类对数几率回归
的取值集合是,多项对数几率回归的模型是
多项对数几率回归的输入,其取值为
此时的似然函数是
对数似然是
其中用到了条件,求偏导得
之后可以使用梯度下降法求解。