凌云时刻 · 技术

导读:决策边界顾名思义就是需要分类的数据中,区分不同类别的边界,举个不恰当的例子,就像省的地界一样,你处在北京还是处在河北,全看你站在区分北京和河北的那条线的哪边。这节我们来看看使用逻辑回归算法如何绘制鸢尾花前两个分类的决策边界。

作者 | 计缘

来源 | 凌云时刻(微信号:linuxpk)

决策边界

 线性决策边界

再来回顾一下逻辑回归,我们需要找到一‍‍组   值,让这‍‍组   和训‍‍‍‍练数据相乘,然后代入Sigmoid函数,求出某个类别的概率,并且假设,当概率大于等于0.5时,分类为1,当概率小于0.5时,分类为0:

   

   

在Sigmoid函数那节解释过,‍‍当   时‍‍,   ‍‍。当‍‍   时,‍‍   ‍‍,因为‍‍   ‍‍,所以:‍‍

   

那么‍‍当   时‍‍,理论‍‍上   ‍‍就是0.5,分类既可以为0,也可以为1。只不过我们在这里将   是,分类假设为1。由此可见‍‍   就‍‍是逻辑回归中的决策边界,并且是线性决策边界。

下面来解释一下为何说是线性决策边界。我们以前两个分类的鸢尾花为例,将‍‍   ‍‍展开得:

   

‍‍‍‍‍‍‍‍‍‍   就是截距,   和‍‍   ‍‍是‍‍‍‍‍‍‍‍‍‍‍‍系数,这个公式绘制出来的是一条直线,这条直线就是能将鸢尾花数据的前两个分类区分开的直线,既线性决策边界。为了能方便的将这条直线绘制出来,我们对上面的公式做一下转换:

   

下面我们在Jupyter Notebook中绘制出来看看:

  

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets

# 还是使用鸢尾花的前两个类型的前两个特征
iris = datasets.load_iris()

X = iris.data
y = iris.target

X = X[y<2, :2]
y = y[y<2]

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)

from myML.LogisticRegression import LogisticRegression
log_reg = LogisticRegression()
log_reg.fit(X_train, y_train)
log_reg.score(X_test, y_test)
# 结果
1.0

# 系数,既theta1和theta2
log_reg.coef_
# 结果
array([ 3.01749692, -5.03046934])

# 截距
log_reg.intercept_
# 结果
-0.68273836989931069

上面的代码中可以‍‍看到,   ,‍‍   和‍‍   ‍‍都‍‍已经知道了。接下来要做的就是给定一组‍‍   ‍‍然后通过上面的公式求出   ,最后绘‍‍制出线性决策边界直线:

 

# 定义求X2的函数
def X2(X1):
	return (-log_reg.intercept_ - log_reg.coef_[0] * X1) / log_reg.coef_[1]

# 构建X1
X1 = np.linspace(4, 8, 1000)
X2 = X2(X1)

plt.scatter(X[y==0, 0], X[y==0, 1], color='r')
plt.scatter(X[y==1, 0], X[y==1, 1], color='b')
plt.plot(X1, X2)
plt.show()

 不规则决策边界

目前我们实现的逻辑回归是使用线性回归来实现的,同样可以通过添加多项式项使决策边界不再是直线。同样,还有像KNN算法在多分类问题中决策边界必然都不是直线,而是不规则的决策边界,所以自然也无法通过一个线性方程来绘制。那么这一小节来看看如何绘制不规则决策边界。

从上面的图中可以看出,红蓝点的区分界限并不是一条直线,而是一个不规则的形状,这就是不规则决策边界。那么绘制不规则决策边界的方法其实也很简单,就是将特征平面上的每一个点都用我们训练出的模型判断它属于哪一类,然后将判断出的分类颜色绘制出来,就得到了上图所示的效果,那么不规则决策边界自然也就出来了,这个原理类似绘制地形图的等高线,在同一等高范围内的点就是同一类。

等高线指的是地形图上高程相等的各点所连成的闭合曲线。

既然运用了等高线的原理,那么我们的绘制方法思路就很明了了:

 

def plot_decision_boundary(model, axis):
	# meshgrid函数用两个坐标轴上的点在平面上画格,返回坐标矩阵
	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),
	)
	# ravel()方法将高维数组降为一维数组,c_[]将两个数组以列的形式拼接起来,形成矩阵
	X_grid_matrix = np.c_[X0.ravel(), X1.ravel()]

	# 通过训练好的逻辑回归模型,预测平面上这些点的分类
	y_predict = model.predict(X_grid_matrix)
	y_predict_matrix = y_predict.reshape(X0.shape)

	# 设置色彩表
	from matplotlib.colors import ListedColormap
	my_colormap = ListedColormap(['#0000CD', '#40E0D0', '#FFFF00'])

	# 绘制等高线,并且填充等高区域的颜色
	plt.contourf(X0, X1, y_predict_matrix, linewidth=5, cmap=my_colormap)

我对这个方法中的几个函数做一下解释:

  • np.meshgrid()这个函数的作用是用给定坐标轴上的点在平面上画格,返回组成网格点的坐标矩阵。

  • ravel()方法将高维数组降为一维数组。

  • c_[]将两个数组以列的形式拼接起来,形成矩阵。

我用一幅图对上面的方法做以形象的说明:

假设传给np.meshgrid()方法的两个坐标轴上共计六个点,然后返回由这六个点组成的网格的坐标矩阵,既网格相交点的坐标矩阵:

 

x0, x1 = np.meshgrid(
		np.linspace(0, 3, 3).reshape(-1, 1),
		np.linspace(0, 3, 3).reshape(-1, 1),
	)

x0
# 结果
array([[ 0. ,  1.5,  3. ],
	   [ 0. ,  1.5,  3. ],
	   [ 0. ,  1.5,  3. ]])

x1
# 结果
array([[ 0. ,  0. ,  0. ],
	   [ 1.5,  1.5,  1.5],
	   [ 3. ,  3. ,  3. ]])

因为返回的结果将这九个点的坐标分开了,所以通过np.c_[X0.ravel(), X1.ravel()]将这九个点的坐标合起来。

 

np.c_[x0.ravel(), x1.ravel()]
# 结果
array([[ 0. ,  0. ],
	   [ 1.5,  0. ],
	   [ 3. ,  0. ],
	   [ 0. ,  1.5],
	   [ 1.5,  1.5],
	   [ 3. ,  1.5],
	   [ 0. ,  3. ],
	   [ 1.5,  3. ],
	   [ 3. ,  3. ]])

然后通过训练好的逻辑回归模型对这九个点预测它们的分类,将预测出的分类作为等高区间。最后通过ListedColormap定义我们自己的色彩表,再使用Matplotlib的contourf函数将等高区域绘制出来,也就是将分类用颜色区分出来。contourf函数的前两个参数是确定点的坐标矩阵,第三个参数是高度,第四个参数是等高线的粗细度,第五个参数是色彩表。

下面我们来使用一下plot_decision_boundary方法:

 

plot_decision_boundary(log_reg, axis=[4, 7.5, 1.5, 4.5])
plt.scatter(X[y==0, 0], X[y==0, 1], color='r')
plt.scatter(X[y==1, 0], X[y==1, 1], color='b')
plt.show()



 kNN的决策边界

因为kNN算法在解决二分类问题时是无法像逻辑回归算法那样推导出线性决策边界的公式的,所以我们使用绘制不规则决策边界的方式来看一下kNN算法的决策边界:

 

from sklearn.neighbors import KNeighborsClassifier
knn_clf = KNeighborsClassifier()
knn_clf.fit(X_train, y_train)

plot_decision_boundary(knn_clf, axis=[4, 7.5, 1.5, 4.5])
plt.scatter(X[y==0, 0], X[y==0, 1], color='r')
plt.scatter(X[y==1, 0], X[y==1, 1], color='b')
plt.show()

下面再来看看当kNN在解决多分类问题时的决策边界是怎样的:

 

knn_clf_all = KNeighborsClassifier()
# 鸢尾花还是取前两个特征,但是使用全部的三个分类
knn_clf_all.fit(iris.data[:, :2], iris.target)

plot_decision_boundary(knn_clf_all, axis=[4, 8, 1.5, 4.5])
plt.scatter(iris.data[iris.target==0,0], iris.data[iris.target==0,1])
plt.scatter(iris.data[iris.target==1,0], iris.data[iris.target==1,1])
plt.scatter(iris.data[iris.target==2,0], iris.data[iris.target==2,1])
plt.show()

从上面的三分类不规则决策边界图中可以看到,在绿色区域里还有些黄色区域,这表示我们的kNN模型有过拟合的现象,也就是k值过小导致的。在第三篇笔记中讲kNN算法时讲过,k值越小,kNN的模型就越复杂。所以我们手动将k值调整为50,再看一下决策边界的情况:

 

knn_clf_all = KNeighborsClassifier(n_neighbors=50)
knn_clf_all.fit(iris.data[:,:2], iris.target)

plot_decision_boundary(knn_clf_all, axis=[4, 8, 1.5, 4.5])
plt.scatter(iris.data[iris.target==0,0], iris.data[iris.target==0,1])
plt.scatter(iris.data[iris.target==1,0], iris.data[iris.target==1,1])
plt.scatter(iris.data[iris.target==2,0], iris.data[iris.target==2,1])
plt.show()

现在可以看到分类区域界限是相对比较规整清晰了。

逻辑回归中使用多项式特征

在讲逻辑回归中使用多项式特征前,先来举个例子看一下:

 

import numpy as np
import matplotlib.pyplot as plt

# 构建随机的均值为0,标准差为1的矩阵X
X = np.random.normal(0, 1, size=(200, 2))
# 构造一个生成y的函数,让其值判断是大于1.5还是小于1.5,既将y值分类
y = np.array(X[:, 0]**2 + X[:, 1]**2 < 1.5, dtype='int')

# 绘样本数据
plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
plt.show()

从上图可以看到,我们构建的样本数据,明显无法用一条直线将两个不同颜色的点区分开,我们使用上一节的方法来验证一下:

 

# 导入我们实现的逻辑回归方法训练模型
from myML.LogisticRegression import LogisticRegression
log_reg = LogisticRegression()
log_reg.fit(X, y)
log_reg.score(X, y)
# 结果
0.42499999999999999

可以看到训练出的模型预测分数非常低。再来看看决策边界:

 

plot_decision_boundary(log_reg, axis=[-4, 3, -3, 3])
plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
plt.show()

从图中可以看到,绘制出的线性决策边界是完全没办法区分样本数据中的两种类型的。

并且我们也清楚的知道,样本数据的决策边界应该是下图所示:

那么我们如何能得到一个圆形的决策边界呢?大家回忆一下,在几何中我们学过圆的标准方程应该是:

   

‍‍   和‍‍   ‍‍是圆心坐标,‍‍   ‍‍是半径。那如果我们将上图的圆看作是一个圆心‍‍在(0,0)的‍‍圆,那么这个圆形的决策边界公式应该就是:

   

和逻辑回归的线性决策边界公式做一下对比:

   

是不是发现相当于给线性决策边界的特征增加幂次,再回想之前笔记中讲过的多项式回归,此时大家应该心中就了然了。那就是如果要让逻辑回归处理不规则决策边界分类问题,那么就运用多项式回归的原理,下面我们实现来看看:

 

# 用到了前面笔记中讲过的Pipeline
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler

def PolynomialLogisticRegression(degree):
	return Pipeline([
		("poly", PolynomialFeatures(degree=degree)),
		("std_scalar", StandardScaler()),
		("log_reg", LogisticRegression())
	])

ploy_log_reg = PolynomialLogisticRegression(degree=2)
ploy_log_reg.fit(X, y)
ploy_log_reg.score(X, y)
# 结果
0.97999999999999998

可以看到使用多项式回归原理后,我们训练出的新的模型对样本数据的预测评分达到了98%。再来绘制一下决策边界看看:

 

plot_decision_boundary(ploy_log_reg, axis=[-4, 3, -3, 3])
plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
plt.show()

现在圆形的决策边界就被绘制出来了,并且将样本数据的类型区分的很准确。

  

 

END

往期精彩文章回顾

机器学习笔记(二十):逻辑回归(2)

机器学习笔记(十九):逻辑回归

机器学习笔记(十八):模型正则化

机器学习笔记(十七):交叉验证

机器学习笔记(十六):多项式回归、拟合程度、模型泛化

机器学习笔记(十五):人脸识别

机器学习笔记(十四):主成分分析法(PCA)(2)

机器学习笔记(十三):主成分分析法(PCA)

机器学习笔记(十二):随机梯度下降

机器学习笔记(十一):优化梯度公式

长按扫描二维码关注凌云时刻

每日收获前沿技术与科技洞见

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐