Logistic 回归的决策边界

决策边界的重要性/意义
        在使用数据集训练机器学习模型之后,我们通常需要可视化特征空间中数据点的类。散点图上的决策边界就是出于这个目的。而散点图更是包含着属于不同类别的数据点(用颜色或形状表示),决策边界可以通过多种不同的策略绘制:

        单线决策边界:在散点图上绘制决策边界的基本策略是找到一条将数据点分隔成不同类区域的单线。现在,利用训练过的模型找到与机器学习算法相关的参数,进而找到这条直线。然后利用得到的参数和机器学习算法找到直线坐标。如果你不知道ML算法的工作原理,那么你将无法继续进行下去。

        基于轮廓的决策边界:另一种策略是绘制轮廓,这些轮廓是用匹配或紧密匹配的颜色包围数据点的区域——描绘数据点所属的类,以及描绘预测类的轮廓。这是最常用的策略,因为它不使用模型训练后得到的机器学习算法的参数和相关计算。但另一方面,我们并不能很好地用一条直线来分离数据点,也就是说这条直线只能通过训练后得到的参数及其坐标计算得到。


1 决策边界

        在二分类问题中,我们只需要一个线性判别函数 𝑓 ( 𝒙 ; 𝒘 ) = 𝒘 T 𝒙 + 𝑏 𝑓(𝒙; 𝒘) = 𝒘^T𝒙 + 𝑏 f(x;w)=wTx+b。特征空间 R 𝐷 \mathbb{R}^𝐷 RD 中所有满足 𝑓 ( 𝒙 ; 𝒘 ) = 0 𝑓(𝒙; 𝒘) = 0 f(x;w)=0 的点组成一个分割超平面(Hyperplane),称为决策边界(Decision Boundary)或决策平面(Decision Surface)。决策边界将特征空间一分为二,划分成两个区域,每个区域对应一个类别。

        超平面是纯粹的数学概念,不是物理概念,它是平面中的直线、空间中的平面的推广,只有当维度大于3,才称为“超”平面。通常,在1维空间里超平面为数轴上的一个点,在2维空间中的超平面是一条线,在3维空间中的超平面是一个平面。一个超平面可以将它所在的空间分为两半, 它的法向量指向的那一半对应的一面是它的正面, 另一面则是它的反面。

        决策边界主要分为线性决策边界(linear decision boundaries)和非线性决策边界(non-linear decision boundaries)。注意:决策边界是假设函数的属性,由参数决定,而不是由数据集的特征决定。下面主要举一些例子,形象化的来说明线性决策边界和非线性决策边界。

        对于下面的数据分布,图中这条直线可以比较完美地将数据分成两类。如下:

        如果我们遇到下图的情况,我们就不能用一个直线将其进行分类了,而是可以用一个圆将数据进行分类。下面来看一下非线性的决策边界的例子:


2 逻辑回归的线性决策边界

2.1 原理简介

        对于逻辑回归原理的详细介绍,可以阅读:线性分类(三)-- 逻辑回归 LR

        回顾逻辑回归分类的原理:通过训练的方式求出一个 n + 1 n+1 n+1 维权值向量 w \pmb{w} www,每当新来一个样本 x i \pmb{x}_i xxxi 时,与参数 w \pmb{w} www 进行点乘,结果带入sigmoid函数,得到的值为该样本发生(即 y ^ = 1 \hat y =1 y^=1 事件发生)的概率值。如果概率大于0.5,分类为1;否则分类为0。
        对于下面的公式:
p ^ = σ ( z ) = 1 1 + e ( − z ) z = w T ⋅ x i (1) \hat p = \sigma(z) = \frac {1} {1 + e^{(-z)}} \qquad z = \pmb{w}^T \centerdot \pmb{x}_i \tag{1} p^=σ(z)=1+e(z)1z=wwwTxxxi(1)
        当 z > 0 z > 0 z>0 时, 1 < 1 + e ( − z ) < 2 1 < 1 + e^{(-z)} < 2 1<1+e(z)<2,因此 p ^ > 0.5 \hat p > 0.5 p^>0.5; 当 z < 0 z < 0 z<0 时, 2 < 1 + e ( − z ) 2 < 1 + e^{(-z)} 2<1+e(z),因此 p ^ < 0.5 \hat p < 0.5 p^<0.5;

        也就是,其中有一个边界点 w T ⋅ x i = 0 \pmb{w}^T \centerdot \pmb{x}_i=0 wwwTxxxi=0,大于这个边界点,分类为1,小于这个边界点。我们称之为决策边界(decision boundary)。

        决策边界: w T ⋅ x i = 0 \pmb{w}^T \centerdot \pmb{x}_i=0 wwwTxxxi=0 同时也代表一个直线: 假设 X X X 有两个特征 x 1 、 x 2 x_1、x_2 x1x2,那么有 w T ⋅ x i = w 0 + w 1 x 1 + w 2 x 2 = 0 \pmb{w}^T \centerdot \pmb{x}_i= w_0 + w_1x_1 +w_2x_2=0 wwwTxxxi=w0+w1x1+w2x2=0

        这是一个直线,可以将数据集分成两类。可以改写成我们熟悉的形式:
x 2 = − w 0 − w 1 x 1 w 2 (2) x_2 = \frac {-w_0 - w_1x_1} {w_2} \tag{2} x2=w2w0w1x1(2)

2.2 具体实现

        使用鸢尾花数据集,画出决策边界。首先可视化数据集
1. 导入相关的包

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

2. 导入数据集

iris = datasets.load_iris()
X = iris.data
y = iris.target
X = X[y<2, :2]
y = y[y<2]
plt.scatter(X[y==0,0], X[y==0,1], s=10, c='b', marker='o')
plt.scatter(X[y==1,0], X[y==1,1], s=10, c='r', marker='x')
plt.xlabel('sepal_length')
plt.ylabel('sepal_width')
plt.show()

3. 训练模型

X_train, X_test, y_train, y_test = train_test_split(X, y)
model_logistic_regression = LogisticRegression()
model_logistic_regression.fit(X_train, y_train)
y_predict = model_logistic_regression.predict(X_test)
print(accuracy_score(y_test, y_predict))
print(model_logistic_regression.coef_)
print(model_logistic_regression.intercept_)

4. 绘制决策边界

# 生成一条直线,x为4~8范围内的1000点 y为x2函数的调用
coef = np.array(model_logistic_regression.coef_)
x1_plot = np.arange(4, 8, step=0.1)
x2_plot = -(x1_plot * coef[0][0] + model_logistic_regression.intercept_)/coef[0][1]
plt.plot(x1_plot, x2_plot)
plt.scatter(X[y==0,0], X[y==0,1], s=10, c='b', marker='o')
plt.scatter(X[y==1,0], X[y==1,1], s=10, c='r', marker='x')
plt.xlabel('sepal_length')
plt.ylabel('sepal_width')
plt.title('Decision Boundaray')
plt.show()

        可以发现,对于逻辑回归算法来说,其得到的决策边界就是一条直线,所以本身还是线性回归算法中的一种,其分类标准本质就是点落在线的上面还是下面,按此分为两类。

        不过对于多项式来说,就不能简单的求出一条直线的方程了,这样就需要绘制一个不规则的决策边界的方法,在平面中,分布着无数个点,对于每一个点,都使用模型来判断一下其分为哪一类,最后绘制出来的颜色块就是决策边界。

5. 图像绘制函数

        简单叙述就是,将X轴和Y轴分为数个点,相应的都使用每个进行预测,对应分配一些颜色属性

# 绘制决策边界的方法
from matplotlib.colors import ListedColormap
def plot_decision_boundary(model, axis):

    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)
    )
    X_new = np.c_[x0.ravel(),x1.ravel()]
    y_predict = model.predict(X_new)
    zz = y_predict.reshape(x0.shape)
    custom_cmap = ListedColormap(['#EF9A9A', '#FFF59D', '#90CAF9'])

    plt.contourf(x0, x1, zz, linewidth=5, cmap=custom_cmap)
plot_decision_boundary(model_logistic_regression, axis=[4,7.5,1.5,4.5])
plt.scatter(X[y==0,0], X[y==0,1], s=10, c='b', marker='o')
plt.scatter(X[y==1,0], X[y==1,1], s=10, c='r', marker='x')
plt.show()

3 逻辑回归的非线性决策边界

3.1 多项式回归

        其核心思想就是换元法,比如圆的表达式: x 1 2 + x 2 2 − r 2 = 0 x_1^2+x_2^2-r^2=0 x12+x22r2=0,将上式中的 x 1 2 x_1^2 x12 整体看作第一个特征 m 1 m_1 m1,将 x 2 2 x_2^2 x22 整体看作第二个特征 m 2 m_2 m2,那么则可以转换成线性回归的问题:其中 m 1 m_1 m1 前的系数为1, m 1 m_1 m1 前系数也为1,且 r 2 r^2 r2 是系数 θ 0 \theta_0 θ0

        这样就从 m 1 、 m 2 m_1、m_2 m1m2 来说还是线性边界,针对于 x 1 、 x 2 x_1、x_2 x1x2 来说变成了非线性的圆形决策边界。这就从线性回归转换成多项式回归,同理为逻辑回归添加多项式项,就可以对非线性的方式进行比较好的分类,决策边界就是曲线的形状。
相当于为样本多添加了几个特征,这些特征是原先样本的多项式项(像是 X 2 X^2 X2 就是对 X X X 进行了平方),增加了这些特征以后就可以使用线性回归的思路,来更好的拟合原来的数据,本质上就是,求出了原来的特征而言的非线性的曲线,即为了更好地拟合数据进行了升维

        需要注意的是,思路方面,对数据集的操作里,PCA是对数据集进行降维处理,而多项式回归是让数据集进行升维,在升维以后可以更好地拟合高维的数据,这两种要看情况使用。

3.2 代码实现

        加载相关包,然后设置一个随机数,种子为666,生成 X X X y y y X X X 为两百个样本,每个样本为两个特征,是第一个特征的平方和第二个特征的平方相加小于1.5,小于1.5为1,大于为0,然后绘制图像。

1. 导入相关包

import numpy as np
import matplotlib.pyplot as plt
np.random.seed(666)

X = np.random.normal(0, 1, size=(200, 2))
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.xlabel('First Feature')
plt.ylabel('Second Feature')
plt.show()

2. 特征映射

def feature_mapping(x1, x2, degree, as_ndarray=False):
    """
    特征映射
    """
    data = {"f{}{}".format(i - p, p): np.power(x1, i - p) * np.power(x2, p)
                for i in np.arange(degree + 1)
                for p in np.arange(i + 1)
            } 
    if as_ndarray:
        return np.array(pd.DataFrame(data))
    else:
        return pd.DataFrame(data)

3. Sigmoid函数

def sigmoid(z):
    """
    Sigmoid Function
    """
    return(1/(1+np.exp(-z)))

4. 损失函数
        Logistic Regression正则化代价函数为:
J ( θ ) = 1 m ∑ i = 1 m [ − y i log ⁡ ( h θ ( x i ) ) − ( 1 − y i ) log ⁡ ( 1 − h θ ( x i ) ) ] + λ 2 m ∑ j = 1 n θ j 2 (3) J\left( \theta \right)=\frac{1}{m}\sum\limits_{i=1}^{m}{[-{{y}_{i}}\log \left( {{h}_{\theta }}\left( {{x}_{i}} \right) \right)-\left( 1-{{y}_{i}} \right)\log \left( 1-{{h}_{\theta }}\left( {{x}_{i}} \right) \right)]}+\frac{\lambda }{2m}\sum\limits_{j=1}^{n}{\theta _{j}^{2}}\tag{3} J(θ)=m1i=1m[yilog(hθ(xi))(1yi)log(1hθ(xi))]+2mλj=1nθj2(3)

def regularized_cost(theta, X, y, learning_rate=0.1):
    """
    加入正则化
    """
    theta_j1_to_n = theta[1:]
    regularized_term = (1/(2*len(X))) * np.power(theta_j1_to_n, 2).sum()
    total_cost = np.mean(-y*np.log(sigmoid(X @ theta)) - (1-y)*np.log(1-sigmoid(X @ theta))) + regularized_term
    return total_cost

5. 梯度下降
        正则化梯度为:
∂ J ( θ ) ∂ θ j = ( 1 m ∑ i = 1 m ( h θ ( x i ) − y i ) ) + λ m θ j  for j ≥ 1 (4) \frac{\partial J\left( \theta \right)}{\partial {{\theta }_{j}}}=\left( \frac{1}{m}\sum\limits_{i=1}^{m}{\left( {{h}_{\theta }}\left( {{x}_{i}} \right)-{{y}_{i}} \right)} \right)+\frac{\lambda }{m}{{\theta }_{j}}\text{ }\text{for j}\ge \text{1} \tag{4} θjJ(θ)=(m1i=1m(hθ(xi)yi))+mλθj for j1(4)

def regularized_gradient(theta, X, y, lam=1):
    '''
    加入正则化
    '''
    theta_j1_to_n = theta[1:]
    regularized_theta = (lam / len(X)) * theta_j1_to_n
    regularized_term = np.concatenate([np.array([0]), regularized_theta])
    total_gradient = (1 / len(X)) * X.T @ (sigmoid(X @ theta) - y) + regularized_term

    return total_gradient

6. 预测

def predict(x, theta):
    prob = sigmoid(x @ theta)
    return (prob >= 0.5).astype(int)

7. 拟合参数

data = feature_mapping(X[:,0], X[:,1], degree=6)
theta = np.zeros(data.shape[1])
        
res = opt.minimize(fun=regularized_cost, x0=theta, args=(data, y), method='Newton-CG', jac=regularized_gradient)

8. 决策边界

def draw_boundary(power, lam):
#     """
#     power: polynomial power for mapped feature
#     lam: lambda constant
#     """
    density = 1000
    threshhold = 2 * 10**-3

    final_theta = feature_mapped_logistic_regression(power, lam)
    x1, x2 = find_decision_boundary(density, power, final_theta, threshhold)
    plt.scatter(X[y==0,0], X[y==0,1])
    plt.scatter(X[y==1,0], X[y==1,1])
    plt.xlabel('First Feature')
    plt.ylabel('Second Feature')
    
    plt.scatter(x1, x2, c='y', s=10, marker='x')
    plt.title('Decision boundary')
    plt.show()

def feature_mapped_logistic_regression(power, lam):
#     """for drawing purpose only.. not a well generealize logistic regression
#     power: int
#         raise x1, x2 to polynomial power
#     lam: int
#         lambda constant for regularization term
#     """

    data = feature_mapping(X[:,0], X[:,1], power, as_ndarray=True)
    theta = np.zeros(data.shape[1])

    res = opt.minimize(fun=regularized_cost,
                       x0=theta,
                       args=(data, y, lam),
                       method='TNC',
                       jac=regularized_gradient)
    final_theta = res.x

    return final_theta

def find_decision_boundary(density, power, theta, threshhold):
	"""
	寻找决策边界函数
	"""
    t1 = np.linspace(-4, 4, density)
    t2 = np.linspace(-4, 4, density)

    cordinates = [(x, y) for x in t1 for y in t2]
    x_cord, y_cord = zip(*cordinates)
    mapped_cord = feature_mapping(x_cord, y_cord, power)  # this is a dataframe

    inner_product = np.array(mapped_cord) @ theta

    decision = mapped_cord[np.abs(inner_product) < threshhold]

    return decision.f10, decision.f01


draw_boundary(power=6, lam=100)#lambda=1
3.3 管道

        看了一些博客,发现可以通过pipeline(管道)实现。首先引用pipeline这个类,进行实例化,对pipeline进行构造,其中的列表就是对应的每一个步骤的对应的类,第一步进行多项式的过程,第二步进行数据归一化,第三步就是进行线性回归,这就创建了一个管道。然后引用PolynomialFeatures类,使用方法同样的,先进行实例化,传入参数degree,其表示为原来的数据集添加的最高的幂,这里设置为2,这就初始化好了,最后fit一下 X X X。详细过程参考:PolynomialFeatures的运算逻辑

from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

# 为逻辑回归添加多项式项的管道
def PolynomialLogisticRegression(degree):
    return Pipeline([
        ('poly', PolynomialFeatures(degree=degree)),
        ('std_scaler', StandardScaler()),
        ('log_reg', LogisticRegression())
    ])

# 使用管道得到对象
poly_log_reg = PolynomialLogisticRegression(degree=2)
poly_log_reg.fit(X, y)

print(poly_log_reg.score(X, y))
"""
输出:0.96
"""
plot_decision_boundary(poly_log_reg, axis=[-4, 4, -4, 4])
plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
plt.show()

        当degree较大时,会发生过拟合现象,此时可以引入一个新参数C,和惩罚项penalty。计算方式为: C ⋅ J ( θ ) + L 2 C\cdot J(\theta) + L_2 CJ(θ)+L2,如果C越大,在优化损失函数的时候就可以更好更快的将前项减到最小,这种就是在sklearn中的使用的方式。

def PolynomialLogisticRegression(degree,C=0.1,penalty='l2'):
    return Pipeline([
        ('poly',PolynomialFeatures(degree=degree)),
        ('std_scaler',StandardScaler()),
        ('log_reg',LogisticRegression(C=C,penalty=penalty))
    ])


补充:

在现实生活中和一些高级项目中,都会涉及到许多特征。那么,如何在二维散点图中绘制决策边界呢?

面对这种情况,通常有多种解决办法:

  1. 利用随机森林分类器等给特征进行重要性评分,得到2个最重要的特征,然后在散点图上绘制决策边界。

  2. 主成分分析(PCA)或线性判别分析(LDA)等降维技术可用于将N个特征嵌入到2个特征中,从而将N个特征的信息解释或减少为2个特征(n_components = 2)。然后再基于这两个特征在散点图上绘制决策边界。


参考

  • 12
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长路漫漫2021

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值