逻辑回归
为什么需要逻辑回归
- 线性回归是不能解决分类问题的。线性回归实际上做了三个假设
- 1.因变量Yi和自变量Xi之间呈线性相关
- 2.自变量Xi与干扰项相互独立
- 3.没被线性模型捕捉到的随机因素服务正态分布
- 理论上来说,任何数据放在模型里面都会得到相应的参数估计,进而通过模型对数据进行预测。但是这并一定能保证模型效果,有时会得到”错且无用”的模型,因此建模的过程中需要不断提出假设和检验假设
什么是线性回归
-
逻辑回归假设数据服从伯努利分布,通过极大似然函数的方法,运用梯度下降来求解参数,来达到将数据二分类目的。
-
原理是将样本的特征和样本发生的概率联系起来,即,预测的是样本发生的概率是多少。由于概率是一个数,因此被叫作”逻辑回归”
-
逻辑回归是一个非线性模型,但是其背后是以线性回归为理论支撑,其提出一个与线性模型长相类似但不同的新公式: 假设特征X所对应的y值是在质数上裱花,那么久可以将结果y值取对数,作为其线性模型逼近的目标。也就是所谓的”对数线性回归”
-
对数线性回归公式中,可以改写如下形式。实际上是求输入空间X到输出空间y的非线性函数映射。对数函数的作用是将线性回归模型的预测值与真实标记联系起来
-
一般意义上的单调可微的“联系函数”:g(a) = ln(a).其本质就是给原来线性变换加上一个非线性变换(或者说映射),使得模拟的函数有非线性的属性,但本质上调参还是线性的,主体是内部线性的调参。那么对于解决分类问题的逻辑回归来说,我们需要找到一个“联系函数”,将线性回归模型的预测值与真实标记联系起来。将“概率”转换为“分类”的工具是“阶段函数”:
-
对于上述阶梯函数不连续,不能作为“联系函数”g,因此使用对数几率函数来在一定程度上近似阶梯函数,将线性回归模型的预测值转换为分类所对应的概率(如下)
-
如果另y为整理,1-y为负例,所谓的“几率”就是二者的比值y/(1-y) ,几率反映了样本X为整理的相对可能性(如y值分别预测为0,1的概率为0.4,0.6,若阈值设置为0.5,那么y就被预测为1)。对数几率即对几率取对数ln(y/(1-y)),对数几率实际上就是sigmod函数,将线性模型转换为分类。如图,可以看出,sigmod实际上就是用线性回归模型的预测结果取逼近真实值的对数几率,因此逻辑回归也被成为”对数几率回归”
-
之所以用sigmod做假设函数,因为线性回归模型预测值为实数,而样本标记为(0,1),我们需要将二分类任务的真实性标记y与线性回归模型的预测值联系起来,也就是找到了广义线性模型中的联系函数。如果选择单位阶跃函数的话,它是不连续的不可微,而选择sigmod函数,他是连续的,而且能够将z转换为一个接近0或1的值
逻辑回归代码
- 原生实现
import numpy as np \#计算回归函数的输出指标,根据分类问题对输出指标进行修改 from sklearn.metrics import accuracy_score class LogisticRegressionWithSigmod: def __init__(self): """ 初始化类 """ # 斜率 self.coef_ = None self.intercept_ = None self._theta = None def _sigmoid(self, t): """ 定义sigmod方法 1/(1+ e^(-t)) :param t: 线性模型 t :return: sigmod表达式 """ return 1./(1. + np.exp(-t)) def fit(self,X_train, y_train, eta = 0.01, n_iters=1e4): """ fit方法,内部使用梯度下降训练法 来训练 Logitstic Regression 参数:训练集特征集 X_train, 标签集y_train 输出:训练好的模型 :param X_train: :param y_train: :param eta: :param n_iters: :return: """ # 检查训练集数量和标签集数量相等 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): """ 定义逻辑回归的损失函数 :param theta: 参数theta :param X_b: 构造好的矩阵X_b :param y: 标签y :return: 损失表达式 """ # 定义逻辑回归的模型 y_hat y_hat = self._sigmoid(X_b.dot(theta)) try: # 选择损失函数的表达式 return - np.sum(y*np.log(y_hat)+(1-y)*np.log(y-y_hat))/len(y) except: return float('inf') def dj(theta,X_b,y): """ 损失函数的导数计算 :param theta: 参数theta :param X_b: 构造好的矩阵X_b :param y: 标签y :return: 计算好的表达式 """ return X_b.T.dot(self._sigmoid(X_b.dot(theta)) - y) / len(y) def gradient_descent(X_b, y, initial_theta, n_iters=1e4, epsilon=1e-8): 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]) # 梯度下降的结果求出参数heta 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 """ 逻辑回归是根据概率进行分类的,因此先预测概率 参数:输入空间X_predict 输出:结果概率向量 """ 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]) # 将梯度下降得到的参数theta带入逻辑回归的表达式中 return self._sigmoid(X_b.dot(self._theta)) """ 使用X_predict的结果概率向量,将其转换为分类 参数:输入空间X_predict 输出:分类结果 """ 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) # 判断概率是否大于0.5,然后将布尔表达式得到的向量,强转为int类型,即为0-1向量 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()"