逻辑回归——鸢尾花数据集的分类

1.背景介绍

1.1. 逻辑回归 Logistic Regression (对数几率回归 Logit Regression)

  关于名字,有文献将Logistic Regression译为“逻辑回归”, 但中文“逻辑”与logitic 和 logit 的含义相去甚远,因此在《机器学习》中意译为“对数几率回归”,简称“对率回归”。

  线性回归

  在介绍对数几率回归之前先介绍一下线性回归,线性回归的主要思想是通过历史数据拟合出一条直线,因变量与自变量是线性关系,对新的数据用这条直线进行预测。
  线性回归的公式如下:y=w0+w1x1+...+wnxn=wTx+b

  逻辑回归

  

  对数几率回归是一种广义的线性回归分析模型,是一种预测分析。虽然它名字里带回归,但实际上对数几率回归是一种分类学习方法。它不是仅预测出“类别”, 而是可以得到近似概率预测,这对于许多需要利用概率辅助决策的任务很有用。普遍应用于预测一个实例是否属于一个特定类别的概率,比如一封email是垃圾邮件的概率是多少。 因变量可以是二分类的,也可以是多分类的。因为结果是概率的,除了分类外还可以做ranking model。LR的应用场景很多,如点击率预测(CTR)、天气预测、一些电商的购物搭配推荐、一些电商的搜索排序基线等。

  对数几率函数是一种“Sigmoid”函数,呈现S型曲线,它将z值转化为一个接近0或1的 y值。
  对数几率回归公式如下:
y=g(z)=11+e−z, z=wTx+b,

  其中,y=11+e−x 被称作Sigmoid函数。

  Logistic Regression算法是将线性函数的结果映射到了Sigmoid函数中,即y=11+e(wTx+b)。

 

 

  Sigmoid函数

  sigmoid函数输出值范围在(0,1)之间,即代表了数据属于某一类别的概率,0.5是作为判别的临界值。

1.2逻辑回归的优化

1:逻辑回归的损失函数

  

 

 不采用最小二乘法进行优化:因为采用最小二乘法的话损失函数就是非凸了(凸函数的定义是在整个定义域内只有一个极值,极大或者极小,该极值就是全部的最大或者最小)

2:梯度下降法

  逻辑回归的优化方法是梯度下降法。

  从数学的角度来说,函数梯度的方向就是函数增长最快的方向,反之梯度的反方向就是函数减少最快的方向。因此我们想要计算一个函数的最小值,就朝着该函数梯度相反的方向前进。
我们先简单地介绍一下该算法。

     

       很多人可能是听说过随机梯度下降,批次梯度下降,梯度下降法。这三者的区别非常简单,就是看样本数据,随机梯度下降每次计算一个样本的损失,
然后更新参数θ,批次梯度下降是每次根据一批次的样本来更新参数,梯度下降是全部样本。一般来说随机梯度下降速度快(每计算一个样本就可以更新一次参数,参数更新的速度极快)。同样的,随机梯度下降的收敛性会比较差,容易陷入局部最优。反过来每次用来参数更新的样本越多,速度会越慢,但是更能够达到全局最优。

    根据链式求导可得:

      

3:逻辑回归的优化

import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split
X=datasets.load_iris()['data']
Y=datasets.load_iris()['target']
Y[Y>1]=1
X_train,X_test,y_train,y_test=train_test_split(X,Y,test_size=0.4,stratify=Y)

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

def cal_grad(y, t):
    grad = np.sum(t - y) / t.shape[0]
    return grad

def cal_cross_loss(y, t):
    loss=np.sum(-y * np.log(t)- (1 - y) * np.log(1 - t))/t.shape[0]
    return loss

class LR:
    def __init__(self, in_num, lr, iters, train_x, train_y, test_x, test_y):
        self.w = np.random.rand(in_num)
        self.b = np.random.rand(1)
        self.lr = lr
        self.iters = iters
        self.x = train_x
        self.y = train_y
        self.test_x=test_x
        self.test_y=test_y


    def forward(self, x):
        self.a = np.dot(x, self.w) + self.b
        self.g = sigmoid(self.a)
        return self.g

    def backward(self, x, grad):
        w = grad * x
        b = grad
        self.w = self.w - self.lr * w
        self.b = self.b - self.lr * b

    def valid_loss(self):
        pred = sigmoid(np.dot(self.test_x, self.w) + self.b)
        return cal_cross_loss(self.test_y, pred)

    def train_loss(self):
        pred = sigmoid(np.dot(self.x, self.w) + self.b)
        return cal_cross_loss(self.y, pred)

    def train(self):
        for iter in range(self.iters):
            ##这里我采用随机梯度下降的方法

            for i in range(self.x.shape[0]):
                t = self.forward(self.x[i])
                grad = cal_grad(self.y[i], t)
                self.backward(self.x[i], grad)

            train_loss = self.train_loss()
            valid_loss = self.valid_loss()
            if iter%5==0:
                print("当前迭代次数为:", iter, "训练loss:", train_loss, "验证loss:", valid_loss)
model=LR(4,0.01,100,X_train,y_train,X_test,y_test)
model.train()
手写随机梯度下降

2.数据集介绍

  Iris也称鸢尾花卉数据集,是常用的分类实验数据集,由R.A. Fisher于1936年收集整理的。其中包含3种植物种类,分别是山鸢尾(setosa)变色鸢尾(versicolor)和维吉尼亚鸢尾(virginica),每类50个样本,共150个样本。

  该数据集包含4个特征变量,1个类别变量。iris每个样本都包含了4个特征:花萼长度,花萼宽度,花瓣长度,花瓣宽度,以及1个类别变量(label)。我们需要建立一个分类器,分类器可以通过这4个特征来预测鸢尾花卉种类是属于山鸢尾,变色鸢尾还是维吉尼亚鸢尾。其中有一个类别是线性可分的,其余两个类别线性不可分,这在最后的分类结果绘制图中可观察到。

                                                                           

 

            

 

  特征对两两之间的相关性散点图:
  如图所示,特征散点图成对角分布,4个特征两两组合(任意两个特征作为x轴,y轴),不同品种的花用不同颜色标注:setosa(橙色),versicolor(绿色),virginica(粉色)。共有12种组合,其实只有6种,因为另外6种与之对称。

                           

 

 

   对角线部分: 核密度估计图(Kernel Density Estimation),就是用来看某 一个 变量分布情况,横轴对应着该变量的值,纵轴对应着该变量的密度(可以理解为出现频次)。

   非对角线部分:两个 变量之间分布的关联散点图。将任意两个变量进行配对,以其中一个为横坐标,另一个为纵坐标,将所有的数据点绘制在图上,用来衡量两个变量的关联度(Correlation)。

3.案例

 1 import pandas as pd
 2 import numpy as np
 3 import matplotlib.pyplot as plt
 4 import plotly.plotly as py
 5 import plotly.graph_objs as go
 6 from plotly.offline import init_notebook_mode, iplot
 7 init_notebook_mode(connected = True)
 8 from sklearn.model_selection import train_test_split
 9 from sklearn.preprocessing import StandardScaler
10 from sklearn.decomposition import PCA
11 from sklearn.pipeline import Pipeline
12 from sklearn.linear_model import LogisticRegression
13 from sklearn import metrics
14 
15 iris_path = r'D:\EdogDownload\iris.csv'
16 data = pd.read_csv(iris_path)
17 
18 # Step1:
19 # 映射函数iris_type: 将string的label映射至数字label
20 # s: 品种的名字
21 def iris_type(s):
22     class_label = {'setosa':0, 'versicolor':1, 'virginica':2}
23     return class_label[s]
24 
25 # Step 2: 将第4列内容映射至iris_type函数定义的内容,查看效果
26 new_iris = pd.io.parsers.read_csv(iris_path, converters = {4:iris_type})
27 new_iris.head()
28 
29 # Step 3: 将new_iris解析至numpy array
30 data = np.array(new_iris)  # 或者直接new_iris.values,结果是一样的
31 data[:10,:]        # 查看前10行的数据
32 
33 # Step 4:将原始数据集划分成训练集与测试集
34 
35 # 用np.split按列(axis=1)进行分割
36 # (4,):分割位置,前4列作为x的数据,第4列之后都是y的数据
37 x,y = np.split(data, (4,), axis = 1)  
38 # X = x[:,0:2] # 取前两列特征
39 # 用train_test_split将数据按照7:3的比例分割训练集与测试集,
40 # 随机种子设为1(每次得到一样的随机数),设为0或不设(每次随机数都不同)
41 x_train, x_test, y_train,y_test = train_test_split(x,y,test_size = 0.3,random_state = 0)
42 
43 #模型的搭建与训练
44 
45 #利用sklearn提供的管道机制Pipeline来实现对全部步骤的流式化封装
46 #第一个环节:可以先进行 数据标准化 StandardScaler()
47 #中间环节:可以加上 PCA降维处理 取2个重要特征
48 #最终环节:逻辑回归分类器
49 pipe_LR = Pipeline([
50                     ('sc', StandardScaler()),
51                     ('pca', PCA(n_components = 2)),
52                     ('clf_lr', LogisticRegression(random_state=1))
53                     ])
54 # 开始训练
55 pipe_LR.fit(x_train, y_train.ravel())
56 
57 #分类器评估
58 print("训练集准确率: %0.2f" %pipe_LR.score(x_train, y_train))
59 print("测试集准确率: %0.2f" %pipe_LR.score(x_test, y_test))
60 y_hat = pipe_LR.predict(x_test)
61 accuracy = metrics.accuracy_score(y_test, y_hat)
62 print("逻辑回归分类器的准确率:%0.2f" % accuracy)
63 
64 #精确度(Precision)、召回率(Recall)、F1 Score
65 target_names = ['setosa', 'versicolor', 'virginica']
66 print(metrics.classification_report(y_test, y_hat, target_names = target_names))

  交叉验证常用于防止模型过于复杂而造成过拟合,同时也称为循环估计。基本思想是将原始数据分成K组(一般是平均分组),每个子集数据分别做一次验证集或测试集,其余的K-1个子集作为训练集。这样就会得到K个模型,取这K个模型的分类准确率的平均数作为分类器的性能指标更具说服力。

  比如说在这里我们使用的是5折交叉验证(5-fold cross validation),即数据集被分成了5份,轮流将其中4份作为训练数据集,剩余1份作为测试集,进行试验。每次试验都会得出相应的正确率,将5次试验得出的相应正确率的平均值作为分类器的准确率的估计。同样的,K也可以取10,20等。

iris_data = x
iris_target = y
from sklearn.model_selection import cross_val_score
scores = cross_val_score(pipe_LR, iris_data, iris_target.ravel(), cv = 5,scoring='f1_macro') # ravel() 将y shape转变成(n_samples,)
print("5折交叉验证:\n逻辑回归分类器的准确率:%.2f 误差范围:(+/- %.2f)"%(scores.mean(), scores.std()*2))

 4.思考

  1: 逻辑回归的正则化

  首先我们介绍一下正则化,对这个方面比较的可以跳过。一般我们模型就是训练就是为了最小化经验风险,正则化就是在这个基础上加上约束(也可以说是引入先验知识),这种约束可以引导优化误差函数的时候倾向于选择向满足约束的梯度下降的方向。

  注:这里我们可以补充一下经验风险,期望风险和结构化风险。
  经验风险就是训练集中的平均损失,期望风险就是(X,y)联合分布的期望损失,当样本数量N趋向于无穷大时,经验风险也趋向于期望风向。机器学习做的就是通过经验风险取估计期望风险。
结构化风险是防止防止过拟合在经验风险的基础上加上正则项。

       L1、L2正则化理论基础:当我们假设参数w服从于正态分布的时候,根据贝叶斯模型可以推导出L2正则化,当我们假设参数w服从于拉普拉斯分布的时候,根据贝叶斯模型可以推导出L1正则化

  2: 为什么逻辑回归中经常会将特征离散化。

  这个是工业界中常见的操作,一般我们不会将连续的值作为特征输入到逻辑回归的模型之中,而是将其离散成0,1变量。这样的好处有:

  1:稀疏变量的内积乘法速度快,计算结果方便存储,并且容易扩展;

  2:离散化后的特征对异常数据有很强的鲁棒性:比如一个特征是年龄>30是1,否则0。如果特征没有离散化,一个异常数据“年龄300岁”会给模型造成很大的干扰。

  3:逻辑回归属于广义线性模型,表达能力受限;单变量离散化为N个后,每个变量有单独的权重,相当于为模型引入了非线性,能够提升模型表达能力,加大拟合;

  4:离散化后可以进行特征交叉,由M+N个变量变为M*N个变量,进一步引入非线性,提升表达能力;

  5:特征离散化后,模型会更稳定,比如如果对用户年龄离散化,20-30作为一个区间,不会因为一个用户年龄长了一岁就变成一个完全不同的人。当然处于区间相邻处的样本会刚好相反,所以怎么划分区间是门学问。

posted @ 2022-03-14 20:49  hungry_J  阅读(147)  评论(0)    收藏  举报