机器学习(六)——逻辑回归

逻辑回归(Logistics Regression),逻辑回归虽然叫回归,但实际上属于分类算法,常用于二分类的任务。当然逻辑回归也可以用于多分类,这就需要加上其它的方法。至于逻辑回归是怎么解决分类问题,实质上是把样本特征和样本发生的概率联系起来。

认识逻辑回归

逻辑回归,通常作为分类算法,只可以解决二分类问题。最终得出的结果是一个概率值。
首先给出逻辑回归的公式:

如何得到 p ^ \widehat{p} p 的函数表达式呢?既然叫逻辑回归,那就说明跟回归还是有关系的,先回顾一下回归问题:

通常线性方程的值域为 (- ∞ \infty , + ∞ \infty ) ,而概率的值域为[0, 1],因此我们在这个基础上做一个变形,完成从 (- ∞ \infty , + ∞ \infty ) 到[0,1]的转换。

这个转换函数就叫做 Sigmoid 函数,函数的表达式:
机器学习(六)——逻辑回归_第1张图片
matplotlib 绘制一下函数图像:

import numpy as np
import matplotlib.pyplot as plt

def sigmoid(t):
    return 1 / (1 + np.exp(-t))

x = np.linspace(-10, 10, 500)
y = sigmoid(x)
plt.plot(x, y)
plt.show()

机器学习(六)——逻辑回归_第2张图片
相当于把一条直线强行掰弯,使其值域在(0, 1)之间。这样就完成从直线回归到逻辑回归的转变。
接下来再了解一下这个转换函数:
机器学习(六)——逻辑回归_第3张图片
现在的问题就是如何对于给定的样本数据集 x y 如何找到最优的 θ


逻辑回归的损失函数

通过前面的分析已经成功地把线性预测转换成了一个概率,那如何定义损失函数呢?
机器学习(六)——逻辑回归_第4张图片
把上面的描述转换为数学公式:

下面是这个函数的图像:
机器学习(六)——逻辑回归_第5张图片
上面的 cost 是一个分段函数,我们将其整合起来:

到此,就可以总结出逻辑回归的损失函数:
机器学习(六)——逻辑回归_第6张图片
这个函数是一个凸函数,他没有公式解,只能使用梯度下降法求解。


损失函数的梯度

机器学习(六)——逻辑回归_第7张图片
对于这种复杂的损失函数,我们首先来看一个sigmoid函数的求导。
机器学习(六)——逻辑回归_第8张图片
接下来看看一下 log(σ(t)) 函数的求导:
机器学习(六)——逻辑回归_第9张图片
进一步求导:
机器学习(六)——逻辑回归_第10张图片
将两项合并(1)+(2):
机器学习(六)——逻辑回归_第11张图片
再代入损失函数:
机器学习(六)——逻辑回归_第12张图片
梯度的向量化过程,其实这个推导过程跟线性回归中的推导很类似。
机器学习(六)——逻辑回归_第13张图片


编码实现逻辑回归

import numpy as np

class LogisticRegression(object):

    def __init__(self):
        "初始化logistic回归模型"
        self.coef_ = None
        self.interception_ = None
        self._theta = None

    def _sigmoid(self, t):
        return 1. / (1. + np.exp(-t))

    def fit(self, x_train, y_train, eta=0.01, n_iters=1e4):
        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, init_theta, eta, n_iters=1e4, epsilon=1e-8):

            theta = init_theta
            i_iter = 0

            while i_iter < n_iters:
                gradient = dj(theta, X_b, y)
                last_theta = theta
                theta = last_theta - eta * gradient

                if (abs(J(theta, X_b, y) - J(last_theta, X_b, y)) < epsilon):
                    break

                i_iter += 1

            return theta

        X_b = np.hstack([np.ones((len(x_train), 1)), x_train])
        init_theta = np.zeros(X_b.shape[1])
        self._theta = gradient_descent(X_b, y_train, init_theta, eta, n_iters=1e4)
        self.interception_ = self._theta[0]
        self.coef_ = self._theta[1:]

        return self

    def predict_prob(self, x_predict):
        assert self.interception_ is not None and self.coef_ is not None, "must fit before predict"
        assert x_predict.shape[1] == len(self.coef_), "the feature number must be equal to x_train"

        X = np.hstack([np.ones((len(x_predict), 1)), x_predict])
        return self._sigmoid(X.dot(self._theta))

    def predict(self, x_predict):
        assert self.interception_ is not None and self.coef_ is not None, "must fit before predict"
        assert x_predict.shape[1] == len(self.coef_), "the feature number must be equal to x_train"

        prob = self.predict_prob(x_predict)
        return np.array(prob >= 0.5, dtype='int')

    def score(self, x_test, y_test):
        y_predict = self.predict(x_test)
        assert y_test.shape[0] == y_predict.shape[0], \
            "the size of y_true must be equal to the size of y_predict"
        return sum(y_test == y_predict) / len(y_test)

    def __repr__(self):
        return "Logistic Regression"

测试一下:

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

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], color='red')
plt.scatter(x[y==1, 0], x[y==1, 1], color='blue')
plt.show()

x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=666)
log_reg = LogisticRegression()
log_reg.fit(x_train, y_train)
print(log_reg.score(x_test, y_test))
print(log_reg.predict_prob(x_test))
print(y_test)
print(log_reg.coef_)
print(log_reg.interception_)

机器学习(六)——逻辑回归_第14张图片


决策边界

逻辑回归的决策边界

首先回顾一下Logistic Regression进行分类的原理:
机器学习(六)——逻辑回归_第15张图片
接下来再看一下Sigmoid这个函数:
在这里插入图片描述
机器学习(六)——逻辑回归_第16张图片

def x2(x1):     
    return (-log_reg.coef_[0] * x1 - log_reg.interception_) / log_reg.coef_[1]

x1_plot = np.linspace(4, 8, 1000)
x2_plot= x2(x1_plot)
plt.scatter(x[y==0, 0], x[y==0, 1], color='red')
plt.scatter(x[y==1, 0], x[y==1, 1], color='blue')
plt.plot(x1_plot, x2_plot)
plt.show()

机器学习(六)——逻辑回归_第17张图片
第四节预测明明是100%的准确率为什么还会有个红点在决策边界下方呢?因为这里绘图用的是全部数据。而测试时用的是测试集(说明训练集不是100%正确率)。
把绘制决策边界封装为一个函数

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)

    from matplotlib.colors import ListedColormap
    custom_cmap = ListedColormap(['#EF9A9A', '#FFF59D', '#90CAF9'])

    plt.contourf(x0, x1, zz, linewidth=5, cmap=custom_cmap)
plot_decision_boundary(log_reg, axis=[4, 7.5, 1.5, 4.5])
plt.scatter(x[y==0, 0], x[y==0, 1], color='red')
plt.scatter(x[y==1, 0], x[y==1, 1], color='blue')
plt.show()

机器学习(六)——逻辑回归_第18张图片

KNN的决策边界

  • 第一种情况,knn作用在两个类别上:
from sklearn.neighbors import KNeighborsClassifier

knn_clf = KNeighborsClassifier()
knn_clf.fit(x_train, y_train)

knn_clf.score(x_test, y_test)
# 1.0
plot_decision_boundary(knn_clf, axis=[4, 7.5, 1.5, 4.5])
plt.scatter(x[y==0, 0], x[y==0, 1], color='red')
plt.scatter(x[y==1, 0], x[y==1, 1], color='blue')
plt.show()

机器学习(六)——逻辑回归_第19张图片

  • 第二种情况,knn作用在三个类别上:
from sklearn.neighbors import KNeighborsClassifier

knn_clf_all = KNeighborsClassifier()
knn_clf_all.fit(iris.data[:,:2], iris.target)
#KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
#          metric_params=None, n_jobs=None, n_neighbors=5, p=2,
#          weights='uniform')

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()

机器学习(六)——逻辑回归_第20张图片


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

​ 逻辑回归中如果使用直线分类方式就只能针对二分类了,如果像下图中不可能使用一根直线完成分割,但是很显然可以使用圆形或者椭圆形完整这个分类任务。其实在线性回归到多项式回归我们思想就是给训练数据集添加多项式项。同理我们把这个东西用到逻辑回归中。
机器学习(六)——逻辑回归_第21张图片
首先生成需要的数据:

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.show()

机器学习(六)——逻辑回归_第22张图片
对于这样一个样本集首先使用没有多项式特征逻辑回归试一下效果如何?
机器学习(六)——逻辑回归_第23张图片
显然有很多错误的分类,那接下来给逻辑回归添加多项式特征。

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

def PolynomiaLogisticRegression(degree):
    return Pipeline([
        ('poly', PolynomialFeatures(degree=degree)),
        ('std_scale', StandardScaler()),
        ('log_reg', LogisticRegression())
    ])

poly_log_reg = PolynomiaLogisticRegression(degree=2)
poly_log_reg.fit(x, y)
poly_log_reg.score(x, y)
# 0.94999999999999996
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()

机器学习(六)——逻辑回归_第24张图片
接下来把degree调大,试一下:

poly_log_reg2 = PolynomiaLogisticRegression(degree=20)
poly_log_reg2.fit(x, y)

plot_decision_boundary(poly_log_reg2, 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越来模型越复杂,也就越容易发生过拟合。接下来我们就来解决这个过拟合问题。这里使用模型正则化。出现这样的边界形状是因为发生了过拟合现象,degree越来模型越复杂,也就越容易发生过拟合。接下来我们就来解决这个过拟合问题。这里使用模型正则化。


逻辑回归中使用正则化

在实际的应用过程中,很少有问题直接用直线就能完成分类或者回归任务,因此正则化必不可少。之前学过模型泛化的时候提到的L1正则、L2正则化的方式:

但是在sklearn中对逻辑回归中的正则化:

  • 先使用直线逻辑回归
    接下来看看sklearn中的逻辑回归是如何加入正则化的。还是先生成样本数据:
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] < 1.5, dtype='int')
# 添加一些噪音。
for i in range(20):
    y[np.random.randint(200)] = 1

plt.scatter(x[y==0, 0], x[y==0, 1])
plt.scatter(x[y==1, 0], x[y==1, 1])
plt.show()

机器学习(六)——逻辑回归_第25张图片

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression

x_train, x_test, y_train, y_test = train_test_split(x, y)
log_reg = LogisticRegression()
log_reg.fit(x, y)
# LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
#          intercept_scaling=1, max_iter=100, multi_class='warn',
#         n_jobs=None, penalty='l2', random_state=None, solver='warn',
#          tol=0.0001, verbose=0, warm_start=False)

通过输出结果我们可以发现默认C=1.0,这个C就是最开始提到的逻辑回归中正则中的C,penalty='l2’说明sklearn默认使用L2正则来进行模型正则化。

log_reg.score(x_train, y_train)
# 0.7933333333333333
log_reg.score(x_test, y_test)
# 0.7933333333333333
plot_decision_boundary(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()

机器学习(六)——逻辑回归_第26张图片

  • 使用多项式Logistic Regression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler

def PolynomiaLogisticRegression(degree):
    return Pipeline([
        ('poly', PolynomialFeatures(degree=degree)),
        ('std_scale', StandardScaler()),
        ('log_reg', LogisticRegression())
    ])

poly_log_reg = PolynomiaLogisticRegression(degree=2)
poly_log_reg.fit(x_train, y_train)
poly_log_reg.score(x_train, y_train)
# 0.9133333333333333
poly_log_reg.score(x_test, y_test)
# 0.94
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()

机器学习(六)——逻辑回归_第27张图片

poly_log_reg2 = PolynomiaLogisticRegression(degree=20)
poly_log_reg2.fit(x_train, y_train)

poly_log_reg2.score(x_train, y_train)
# 0.94
poly_log_reg2.score(x_test, y_test)
# 0.92
plot_decision_boundary(poly_log_reg2, 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()

机器学习(六)——逻辑回归_第28张图片

  • 调整参数C
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler

# 传入一个新的参数C
def PolynomiaLogisticRegression(degree, C):
    return Pipeline([
        ('poly', PolynomialFeatures(degree=degree)),
        ('std_scale', StandardScaler()),
        ('log_reg', LogisticRegression(C=C))
    ])

poly_log_reg3 = PolynomiaLogisticRegression(degree=20, C=0.1)
poly_log_reg3.fit(x, y)

poly_log_reg3.score(x_train, y_train)
# 0.8533333333333334
poly_log_reg3.score(x_test, y_test)
# 0.92
plot_decision_boundary(poly_log_reg3, 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()

机器学习(六)——逻辑回归_第29张图片


逻辑回归解决多分类问题

在开始之初,说逻辑回归只可以解决二分类问题, 其实可以稍加改造使其能够解决多分类问题。当然这个改造方式并不是只针对逻辑回归这一种算法,这是一种通用的近乎于可以改造所有的二分类。

OvR

(One vs Rest)一对剩余,有些说法也叫(One vs All,OVA)。比如下图中的这个四分类问题,显然如果使用逻辑回归一条直线分出4类是不现实的,但是如果我们取出其中任意一种,将剩下的作为另一种,这种就是一个2分类问题,同理将每一个类别分别做一次这样的2分类,如果有n个类别就进行n次分类,选择分类得分最高的。就类似于下图这种,进行C(n,1)中分类。从而完成多分类。
机器学习(六)——逻辑回归_第30张图片

OvO

(One vs One)一对一,就是在多个类别中,先挑出2个来进行2分类,然后逐个进行,也就是C(n,2)中情况进行2分类,选择赢数最高的分类。比如一个手写数字的识别任务来说就要进行C(10,2)=45次分类,才能完成任务。很显然它相比OvR(n级别)来说,OvO这是一个n2级别的,要比耗时更多,但是它分类的结果更加准确。这是因为每次都是在用两个真实的类别再进行2分类,而没有混淆其他的类别信息。
机器学习(六)——逻辑回归_第31张图片

Logistic Regression的OvR和OvO的编程实现

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

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)

    from matplotlib.colors import ListedColormap
    custom_cmap = ListedColormap(['#EF9A9A', '#FFF59D', '#90CAF9'])

    plt.contourf(x0, x1, zz, linewidth=5, cmap=custom_cmap)

iris = datasets.load_iris()
x = iris.data[:, :2]
y = iris.target
x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=666)

log_reg = LogisticRegression()
log_reg.fit(x_train, y_train)
# LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
#          intercept_scaling=1, max_iter=100, multi_class='warn',
#          n_jobs=None, penalty='l2', random_state=None, solver='warn',
#          tol=0.0001, verbose=0, warm_start=False)

从输出结果来看,这个multi_class=‘warn’,这个multi_class就是多分类的意思,从官方文档来看默认是OvR,solver这个参数是因为在sklearn中并不是简单地使用梯度下降,因此我们需要给不同的方法传入不同的解决办法。

  • OvR
log_reg = LogisticRegression(multi_class='ovr',solver='liblinear')
log_reg.fit(x_train, y_train)
log_reg.score(x_test, y_test)
# 0.6578947368421053
plot_decision_boundary(log_reg, axis=[4, 8.5, 1.5, 4.5])
plt.scatter(x[y==0, 0], x[y==0, 1])
plt.scatter(x[y==1, 0], x[y==1, 1])
plt.scatter(x[y==2, 0], x[y==2, 1])
plt.show()

机器学习(六)——逻辑回归_第32张图片

  • OvO
log_reg2 = LogisticRegression(multi_class='multinomial', solver='newton-cg')
log_reg2.fit(x_train, y_train)
# 0.7894736842105263
plot_decision_boundary(log_reg2, axis=[4, 8.5, 1.5, 4.5])
plt.scatter(x[y==0, 0], x[y==0, 1])
plt.scatter(x[y==1, 0], x[y==1, 1])
plt.scatter(x[y==2, 0], x[y==2, 1])
plt.show()

机器学习(六)——逻辑回归_第33张图片通过上面这两个示例发现准确率并不高,这是因为鸢尾花数据集共有4个特征,而只用了前两个,为了方便可视化,下面就是使用所有数据。

iris = datasets.load_iris()
x = iris.data[:, :]
y = iris.target

x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=666)

# OvR
log_reg3 = LogisticRegression(multi_class='ovr', solver='liblinear')
log_reg3.fit(x_train, y_train)
log_reg3.score(x_test, y_test)
# 0.9473684210526315

# OvO
log_reg4 = LogisticRegression(multi_class='multinomial', solver='newton-cg')
log_reg4.fit(x_train, y_train)
log_reg4.score(x_test, y_test)
# 1.0

sklearn中的OvR和OvO

from sklearn.linear_model import LogisticRegression

log_reg = LogisticRegression()

from sklearn.multiclass import OneVsRestClassifier

ovr = OneVsRestClassifier(log_reg)
ovr.fit(x_train, y_train)
# OneVsRestClassifier(estimator=LogisticRegression(C=1.0, class_weight=None, 
# dual=False, fit_intercept=True,
#          intercept_scaling=1, max_iter=100, multi_class='ovr',
#          n_jobs=None, penalty='l2', random_state=None, solver='liblinear',
#          tol=0.0001, verbose=0, warm_start=False),
#          n_jobs=None)
ovr.score(x_test, y_test)
# 0.9473684210526315

from sklearn.multiclass import OneVsOneClassifier

ovo = OneVsOneClassifier(log_reg)
ovo.fit(x_train, y_train)
# OneVsOneClassifier(estimator=LogisticRegression(C=1.0, class_weight=None, 
# dual=False, fit_intercept=True,
#          intercept_scaling=1, max_iter=100, multi_class='multinomial',
#          n_jobs=None, penalty='l2', random_state=None, solver='newton-cg',
#          tol=0.0001, verbose=0, warm_start=False),
#          n_jobs=None)

你可能感兴趣的:(机器学习,机器学习,逻辑回归,算法)