逻辑回归与决策边界
what?
逻辑回归其实是一个分类算法而不是回归算法。通常是利用已知的自变量来预测一个离散型因变量的值(像二进制值0/1,是/否,真/假)。简单来说,它就是通过拟合一个逻辑函数(logit fuction)来预测一个事件发生的概率。所以它预测的是一个概率值,自然,它的输出值应该在0到1之间。
Logistic回归简单分析:
优点:计算代价不高,易于理解和实现
缺点:容易欠拟合,分类精度可能不高
适用数据类型:数值型和标称型数据
基本原理
按照我自己的理解,可以简单的描述为这样的过程:
找一个合适的预测函数,一般表示为h函数,该函数就是我们需要找的分类函数,它用来预测输入数据的判断结果。这个过程时非常关键的,需要对数据有一定的了解或分析,知道或者猜测预测函数的“大概”形式,比如是线性函数还是非线性函数。
借助sigmoid函数构造出的预测函数形式一般为:
$$
h_{\theta }(x) = g(\theta ^{_{T}}x) = \frac{1}{1+e^{-\theta ^{T}x}}
$$
其中sigmoid函数为:构造一个Cost函数(损失函数),该函数表示预测的输出(h)与训练数据类别(y)之间的偏差,可以是二者之间的差(h-y)或者是其他的形式。cost函数为:
$$
Cost(h_{\theta }x,y) = \begin{cases} -log(h_{\theta }(x))& \text{ if } y=1 \ -log(1-h_{\theta }(x))& \text{ if } y=0 \end{cases}
$$
综合考虑所有训练数据的“损失”,将Cost求和或者求平均,记为J(θ)函数,表示所有训练数据预测值与实际类别的偏差。J(θ函数一般为:
$$
J(\theta ) = -\frac{1}{m}[\sum_{i=1}^{m}(y_{i}logh_{\theta }(x_{i})+(1-y_{i})log(1-h_{\theta }(x_{i})))]
$$显然,J(θ)函数的值越小表示预测函数越准确(即h函数越准确),所以这一步需要做的是找到J(θ)函数的最小值。找函数的最小值有不同的方法,Logistic Regression实现时用的是梯度下降法(Gradient Descent)。
关于详细的公式推导就不介绍了伪代码
1
2
3
4
5
6
7
8初始化线性函数参数为1
构造sigmoid函数
重复循环I次
计算数据集梯度
更新线性函数参数
确定最终的sigmoid函数
输入训练(测试)数据集
运用最终sigmoid函数求解分类代码实现
逻辑回归的python代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84import numpy as np
from sklearn.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.0 / (1.0 + np.exp(-t))
def fit(self, X_train, y_train, eta=0.01, n_iters=1e4):
"""根据训练数据集X_train, y_train, 使用梯度下降法训练Logictic 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(X_b)
'''梯度下降法'''
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()
上述结果为本次测试数据,可以看出来该数据集其实是三维的数据,因为逻辑回归只能解决二分类的问题,因此取数据集中的前两维数据,作为两种类别,因此使用该数据集可以用来判断逻辑回归测试结果的好坏
测试代码:
1 | from playML.model_selection import train_test_split |
输出的测试结果:
1 | 对于上述简单的测试数据,输出的array中的数据表示将某个数据分类成某一类别的概率,越接近于0就越趋近于分类成0这个类别,同理越趋近与1,就越趋近于分类成1,最后的分类的测试值输出为1. |
得到的结果如下:
上述的决策边界是一条直线,所以不严格的说还是属于线性分类,当分类数据不线性的时候就需要不规则的决策边界。
举个例子使用KNN算法来对上述数据进行分类:
1 | from sklearn.neighbors import KNeighborsClassifier |
结果如下:
可以看出通过使用KNN方法对上述数据的决策边界就是不规则的
因为KNN是支持多类别数据的分类的,然后我们的数据集也是3中类别的,所以测试下KNN在三分类中的分类的效果。
1 | knn_clf_all = KNeighborsClassifier() |
得到的结果如下:
可以看出分类的结果是非常的不规则的,其实也就是应该是发生了过拟合的问题。
对于KNeighborsClassifier()这个函数其实其中有一个参数是可以调节的,就是n_neighbors这个参数,可以直接运行 knn_clf_all = KNeighborsClassifier()查看其中的参数,n_neighbors这个参数的含义其实就是分类的复杂程度,越小的话越复杂,就容易出现过拟合的问题。这里调节下这个参数看一下效果,设置knn_clf_all = KNeighborsClassifier(n_neighbors = 50),其余代码相同
得到的结果如下:
明显能够看出来决策边界规则了许多,但是相应的分类效果弱了一些,所以调参,调参。
上面的数据集是可以线性分类的,当数据的类别线性不可分的时候,逻辑回归的方式怎么去处理呢?举个例子:
当数据集是这样的,显然线性不可分,决策边界是不规则类似于圆。其实这个时候就需要类似于使用多项式回归的方式来处理。给逻辑回归中添加多项式。
1 | from sklearn.pipeline import Pipeline |
得到如下的结果:
可以看出添加了多项式的逻辑回归可以解决非线性可分的问题。
逻辑回归中使用正则化处理过拟合的问题
因为数据线性不可分的时候,需要在逻辑回归中引入多项式,这也使得分类变得复杂,容桂产生过拟合的问题,解决方法有两个,一个是调节degree参数,另一种就是正则化。通用的正则化的方式就是在J(θ)函数中加一个正则项,使用J(θ)+aL2作为新的损失函数。a用来调节J(θ)和L2各自所占比重。这里C·J(θ)+L1作为所示函数,其实C也是用来平衡J(θ)和L1,原理是一样的。L1和L2是正则化中的一个重要的参数。
1 | from sklearn.linear_model import LogisticRegression |
说实话这里选取的数据集的代表性不太够,没有太突出正则化的优点,注重点在方法的实现上,但是还是能看出有一点区别的,决策边界更加清楚了。