逻辑回归算法实现鸢尾花数据集的二分类

1、介绍

前言
  为了更好的理解本章内容,请参考下面的网址,自主学习一些矩阵求导运算。

https://zhuanlan.zhihu.com/p/158182135

机器学习约定规则:
向量对标量求导,默认使用分子布局(本博客采用了这一条)
矩阵对标量求导,默认使用分子布局
标量对向量求导,默认使用分母布局
向量对向量求导,常使用分母布局

1、概述
  逻辑回归(Logistic Regression)虽然是名词是回归,但是实际上它是一种分类模型。假设输入值函数(单值 x 1 x_1 x1)如下:
z = W T x 1 = w 0 + w 1 x 11 + w 2 x 12 + ⋯ + w p x 1 p W ∈ R p + 1 , x 1 ∈ R p + 1 z=W^Tx_1=w^0+w^1x_{11}+w^2x_{12}+\cdots+w^px_{1p} \\ W \in R^{p+1} , x_1 \in R^{p+1} z=WTx1=w0+w1x11+w2x12++wpx1pWRp+1,x1Rp+1
  逻辑回归模型通过sigmoid函数将输入值映射到[0,1]区间范围。其中,逻辑回归函数形式如下:
p = s ( z ) = 1 1 + e − z p=s(z)=\frac{1}{1+e^{-z}} p=s(z)=1+ez1
  逻辑回归函数图像如下:


  s(z)函数的定义域范围为 ( − ∞ , + ∞ ) (-\infty,+\infty) (,+),sigmoid函数是一个(0,1)上的函数,从而可以将连续数据进行分类,这可能就是“回归”二字的含义吧。此外,我们也可以将s(z)函数看成概率p,假设p=0.8,此时概率值离1很近,可以将数据类别判别为1;假设p=0.2,概率值离0很近,可以将数据类别判别为0。
  因此,假设我们已经训练好了一组权值W ,只要把我们需要预测的数据X代入到方程,输出的y值就是这个标签为0/1的概率,进而判断输入数据是属于哪个类别。下面我们分析使用梯度下降法来分析权值W的求解过程。
2、梯度下降法求解权值W
(1)损失函数的求解过程
  假设二分类问题的输出值是一个包含2个离散值函数,即 y ∈ { 0 , 1 } y \in \{0,1\} y{0,1},令p等于 y = 1 y=1 y=1发生的概率,将每个样本看成一个事件,每个事件发生的概率如下:
P ( y i ∣ x i ) = { p , y = 1 1 − p , y = 0 P(y_i|x_i)= \begin{cases} p,y=1 \\ 1-p,y=0 \end{cases} P(yixi)={p,y=11py=0
  上面的表达式等价于下面的形式:
P ( y i ∣ x i ) = p y i ( 1 − p ) 1 − y i P(y_i|x_i)=p^{y_i}(1-p)^{1-y_i} P(yixi)=pyi(1p)1yi
  假设采集到服从独立同分布的样本N个数据,其联合概率就是每个样本的概率的乘积,此时似然函数有:
L ( W ) = ∏ i = 1 N p y i ( 1 − p ) 1 − y i L(W)=\prod_{i=1}^N{p^{y_i}(1-p)^{1-y_i}} L(W)=i=1Npyi(1p)1yi
  为了计算简便,对上面的似然函数求对数:
F ( W ) = l n ( L ( W ) ) = ∑ i = 1 N ( y i l n ( p ) + ( 1 − y i ) l n ( 1 − p ) ) F(W)=ln(L(W))=\sum_{i=1}^N({{y_i}ln(p)+({1-y_i})ln(1-p)}) F(W)=ln(L(W))=i=1N(yiln(p)+(1yi)ln(1p))
  上面的表达式求出最大值,对应的似然函数的求解,最大化似然函数对应最小化代价函数,因此可以在前面加上负号,从而将上面的表达式看成我们的损失函数。
(2)对损失函数求梯度
  假设输入值的特征向量有n个,可以对输入值特征向量进行下面的扩展,此时输出值是一个n维的向量,其中向量的每一个元素表示一个输入值的特征向量对应的输出。
[ x 1 T , 1 x 2 T , 1 ⋯ x n T , 1 ] \left[ \begin{array}{c} x_1^T,1 \\ x_2^T ,1\\ \cdots \\ x_n^T,1 \end{array} \right] x1T1x2T1xnT1
  假设逻辑回归函数形式使用下面的形式:
p ′ = s ( z ) ′ = 1 1 + e − X W p ′ ∈ R n p^{'}=s(z)^{'}=\frac{1}{1+e^{-XW}} \\ p^{'} \in R^{n} p=s(z)=1+eXW1pRn
   p ′ p^{'} p进行 w i w_i wi求导:
( p ′ ) ′ = ( 1 1 + e − X W ) ′ = − ( 1 1 + e − X W ) − 2 . ( 1 + e − X W ) ′ = − ( 1 1 + e − X W ) − 2 . e − X W . ( − X W ) ′ = ( 1 1 + e − X W ) − 2 . e − X W . X i = p ′ ( 1 − p ′ ) X i (p^{'})^{'}=(\frac{1}{1+e^{-XW}})^{'} \\ =-(\frac{1}{1+e^{-XW}})^{-2}.(1+e^{-XW})^{'} \\ =-(\frac{1}{1+e^{-XW}})^{-2}.e^{-XW}.(-XW)^{'} \\ =(\frac{1}{1+e^{-XW}})^{-2}.e^{-XW}.X_i \\ =p^{'}(1-p^{'})X_i (p)=(1+eXW1)=(1+eXW1)2.(1+eXW)=(1+eXW1)2.eXW.(XW)=(1+eXW1)2.eXW.Xi=p(1p)Xi
  当我们采用整个矩阵进行求导的时候,我发现上面得出的矩阵结果维度好像不多,但是有一点就是通过下面的代码可以看出 ( − X W ) ′ (-XW)^{'} (XW) w i w_i wi求导确实只剩下 X i X_i Xi,此时向量对于变量进行求导,采用分子布局方式。
[ 1 x 11 x 12 ⋯ x 1 p 1 x 21 x 22 ⋯ x 2 p ⋯ 1 x n 1 x n 2 ⋯ x n p ] ∗ [ w 0 w 1 ⋯ w i ⋯ w p ] = [ y 0 y 1 ⋯ y i ⋯ y n ] \left[ \begin{array}{c} 1 & x_{11} & x_{12} & \cdots & x_{1p} \\ 1 & x_{21} & x_{22} & \cdots & x_{2p}\\ \cdots \\ 1 & x_{n1} & x_{n2} & \cdots & x_{np} \end{array} \right] * \left[ \begin{array}{c} w_0 \\ w_1 \\ \cdots \\ w_i \\ \cdots \\ w_p \end{array} \right] = \left[ \begin{array}{c} y_0 \\ y_1 \\ \cdots \\ y_i \\ \cdots \\ y_n \end{array} \right] 111x11x21xn1x12x22xn2x1px2pxnpw0w1wiwp=y0y1yiyn
  因此,我们不对整个输入值矩阵进行求导,我们只针对单个输入值进行求导,我们采用下面的求导方式,然后观察出规律即可。假设输入值为 x ( i ) x^{(i)} x(i),逻辑回归函数对 w i w_i wi进行求导

( p ) ′ = ( 1 1 + e − W T x ( i ) ) ′ = − ( 1 1 + e − W T x ( i ) ) − 2 . ( 1 + e − W T x ( i ) ) ′ = − ( 1 1 + e − W T x ( i ) ) − 2 . e − W T x ( i ) . ( − W T x ( i ) ) ′ = ( 1 1 + e − W T x ( i ) ) − 2 . e − W T x ( i ) . x i ( i ) = p ( 1 − p ) x i ( i ) (p)^{'}=(\frac{1}{1+e^{-W^Tx^{(i)}}})^{'} \\ =-(\frac{1}{1+e^{-W^Tx^{(i)}}})^{-2}.(1+e^{-W^Tx^{(i)}})^{'} \\ =-(\frac{1}{1+e^{-W^Tx^{(i)}}})^{-2}.e^{-W^Tx^{(i)}}.(-W^Tx^{(i)})^{'} \\ =(\frac{1}{1+e^{-W^Tx^{(i)}}})^{-2}.e^{-W^Tx^{(i)}}.x^{(i)}_i \\ =p(1-p)x^{(i)}_i (p)=(1+eWTx(i)1)=(1+eWTx(i)1)2.(1+eWTx(i))=(1+eWTx(i)1)2.eWTx(i).(WTx(i))=(1+eWTx(i)1)2.eWTx(i).xi(i)=p(1p)xi(i)

  损失函数F(W)对W进行求导:
F ( W ) ′ = ( ∑ i = 1 N ( y i l n ( p ) + ( 1 − y i ) l n ( 1 − p ) ) ) ′ = ∑ i = 1 N ( y i l n ( p ) ′ + ( 1 − y i ) l n ( 1 − p ) ′ ) = ∑ i = 1 N ( y i ( 1 p p ′ ) + ( 1 − y i ) l n ( 1 − p ) ′ ) = ∑ i = 1 N ( y i ( 1 p p ′ ) + ( 1 − y i ) ( 1 1 − p ( 1 − p ) ′ ) ) = ∑ i = 1 N ( y i − p ) x i ( i ) {F(W)}^{'}=(\sum_{i=1}^N({{y_i}ln(p)+({1-y_i})ln(1-p)}))^{'} \\ =\sum_{i=1}^N({{y_i}ln(p)^{'}+({1-y_i})ln(1-p)^{'}}) \\ =\sum_{i=1}^N({{y_i}(\frac{1}{p}p^{'})+({1-y_i})ln(1-p)^{'}}) \\ =\sum_{i=1}^N({{y_i}(\frac{1}{p}p^{'})+({1-y_i}) (\frac{1}{1- p}{(1-p)^{'}})}) \\ =\sum_{i=1}^N(y_i - p)x^{(i)}_i F(W)=(i=1N(yiln(p)+(1yi)ln(1p)))=i=1N(yiln(p)+(1yi)ln(1p))=i=1N(yi(p1p)+(1yi)ln(1p))=i=1N(yi(p1p)+(1yi)(1p1(1p)))=i=1N(yip)xi(i)
  最后采用下面的公式,在代码中更新权重即可:
w i k + 1 = w i k + α ∗ ∂ F ( w ) ∂ w i w^{k+1}_i = w^{k}_i + \alpha*\frac{\partial {F(w)}}{\partial {w_i}} wik+1=wik+αwiF(w)

2、代码

1、算法描述
  本博客采用逻辑回归算法实现鸢尾花数据集的二分类。
2、数据csv准备
(1)数据格式

sepallength	sepalwidth	petallength	petalwidth	class
5.1	3.5	1.4	0.2	Iris-setosa
4.9	3	1.4	0.2	Iris-setosa
4.7	3.2	1.3	0.2	Iris-setosa
4.6	3.1	1.5	0.2	Iris-setosa
5	3.6	1.4	0.2	Iris-setosa
5.4	3.9	1.7	0.4	Iris-setosa
4.6	3.4	1.4	0.3	Iris-setosa
5	3.4	1.5	0.2	Iris-setosa
4.4	2.9	1.4	0.2	Iris-setosa
...

  为了大家能够更好的学习KNN算法,我将这个csv文件放到我博客的资源中,通过下面的链接即可下载:

https://download.csdn.net/download/weixin_43334389/13130463

3、代码

# @Time : 2020/11/22 20:45
# @Description : 使用鸢尾花数据集采用逻辑回归算法实现二分类
import numpy as np
import pandas as pd

data = pd.read_csv(r"dataset/iris.arff.csv", header=0)
data.drop_duplicates(inplace=True)
# 名称映射为数字
data["class"] = data["class"].map({"Iris-setosa": 0, "Iris-versicolor": 1, "Iris-virginica": 2})
# 筛选数据,只选择0和1
data = data[data["class"] != 2]


class LogisticRegression:
    """
    @desc:逻辑回归算法实现
    """

    def __init__(self, learning_rate, times):
        """
        @desc:初始化算法
        @param learning_rate: 学习率
        @param times: 迭代次数
        """
        self.learning_rate = learning_rate
        self.times = times

    def sigmoid(self, z):
        """
        @desc:sigmoid函数实现
        @param z: 自变量: z=w.T*x
        @return: float, 属于[0,1]区间。s>=0.5(z>0)时,类别为1,否则为0
        """
        return 1.0 / (1.0 + np.exp(-z))

    def fit(self, X, y):
        """
        @desc:训练模型
        @param X:训练集特征矩阵,shape[0]为样本数量(即特征矩阵),shape[1]为特征数量
        @param y:标签数组
        """
        X = np.asarray(X)
        print(X.shape)
        print(type(X))
        y = np.asarray(y)
        # 创建权重向量,初始化为0.多出来的1是截距
        self.w_ = np.zeros(1 + X.shape[1])  # shape[0]为样本数量,shape[1]为特征数量
        # 创建损失列表,保存每次迭代后的损失值
        self.loss_ = []
        for i in range(self.times):
            z = np.dot(X, self.w_[1:]) + self.w_[0]
            # 计算概率值(判定为1的概率值)
            p = self.sigmoid(z)
            # 根据逻辑回归的代价函数(目标函数或叫损失函数)计算损失值
            # 通过计算逻辑回归函数的最大似然函数得到下面的公式,np.log()函数以e为底
            # J(W) = - sum(yi * log(s(zi))+(1-yi)log(1-s(zi)))
            cost = - np.sum(y * np.log(p) + (1 - y) * np.log(1 - p))
            self.loss_.append(cost)
            # 调整权重值,上面的损失函数对w求导得到
            # 公式 权重(j行) = 权重(j行) + 学习率 * sum( y - s(z)) * X(j列)
            self.w_[0] += self.learning_rate * np.sum(y - p) * 1
            self.w_[1:] += self.learning_rate * np.dot(X.T, y - p)

    def predict_proba(self, X):
        """
        @desc: 根据参数传递的样本,对样本数据进行预测属于某一个类别的概率
        @param X:测试集特征矩阵
        @return:预测(概率值)
        """
        X = np.asarray(X)
        z = np.dot(X, self.w_[1:]) + self.w_[0]
        p = self.sigmoid(z)
        # 将预测结果转换为二维数组,方便后续拼接
        p = p.reshape(-1, 1)
        # 将两个数组拼接,方向是横向拼接
        test1 = np.concatenate([1 - p, p], axis=1)
        print("--------test1----------")
        # (18, 2)
        print(test1.shape)
        # <class 'numpy.ndarray'>
        print(type(test1))
        # [[0.99066619 0.00933381]
        #  [0.99297624 0.00702376]
        #  [0.97928108 0.02071892]
        print(test1)
        return np.concatenate([1 - p, p], axis=1)  # 横向拼接为为1

    def predict(self, X):
        """
        @desc:根据参数预测样本属于哪个类别
        @param X:测试集特征矩阵
        @return: 返回测试数据属于那个类别标签对应的较大值的索引
        axis=1 表示每一行的最大索引,当下面函数输出为0的时候,表示第一列数据大,
        刚好对应分类为0的标签。
        """
        return np.argmax(self.predict_proba(X), axis=1)


t1 = data[data["class"] == 0]
t2 = data[data["class"] == 1]
t1 = t1.sample(len(t1), random_state=0)
t2 = t2.sample(len(t2), random_state=0)
train_X = pd.concat([t1.iloc[:40, :-1], t2.iloc[:40, :-1]], axis=0)
train_y = pd.concat([t1.iloc[:40, -1], t2.iloc[:40, -1]], axis=0)
test_X = pd.concat([t1.iloc[40:, :-1], t2.iloc[40:, :-1]], axis=0)
test_y = pd.concat([t1.iloc[40:, -1], t2.iloc[40:, -1]], axis=0)

# 鸢尾花的特征列都在同一个数量级,我们可以不用标准化处理
lr = LogisticRegression(learning_rate=0.01, times=20)
lr.fit(train_X, train_y)
# 预测的概率值
# lr.predict_proba(test_X)
# lr.predict(test_X)预测的类别
result = lr.predict(test_X)
# 计算准确性
accuracy = np.sum(result == test_y) / len(test_y)
# 1.0
print(accuracy)

import matplotlib as mpl
import matplotlib.pyplot as plt

mpl.rcParams['font.family'] = 'SimHei'
mpl.rcParams['axes.unicode_minus'] = False

# 绘制预测值
plt.plot(result, 'ro', ms=15, label="预测值")  # ms指定圆圈大小
plt.plot(test_y.values, 'go', label="真实值")  # pandas读取时serise类型,我们需要转为ndarray
plt.title('逻辑回归预测')
plt.xlabel('样本序号')
plt.ylabel('预测值')
plt.legend()
plt.show()

# 绘制目标函数随着迭代次数的损失值图像
plt.plot(range(1, lr.times + 1), lr.loss_, 'go-')
plt.xlabel('迭代次数')
plt.ylabel('损失值loss')
plt.show()

4、结果展示
(1)预测值和真实值之间的拟合

(2)损失函数图像

3、分析

1、argmax函数
  

 def predict(self, X):
 	  #索引值和分类类别相对应
      return np.argmax(self.predict_proba(X), axis=1)
      
# 举例
matrix2_2 = np.array([[1, 2], [3, 4]])
# [1 1] 即第一行的2和第二行的4比较大,作为索引返回。
# axis=1表示返回每1行的最大索引(0/1).
# 参数axis,默认是0.
print(np.argmax(matrix2_2, axis=1))

2、数组拼接函数concatenate

#拼接之后成为一个n*2的数组矩阵,其索引值(0/1)和分类类别相对应
return np.concatenate([1 - p, p], axis=1)  # 横向拼接为1

# 举例 按轴axis连接array组成一个新的array
# 一维情况进行连接 axis=1
# 我们对其进行转换尺寸
a1 = a.reshape(-1, 1)
b1 = b.reshape(-1, 1)
ab1 = np.concatenate((a1, b1), axis=1)
# [[ 1 11]
#  [ 2 22]
#  [ 3 33]]
print(ab1)
(3, 2)
print((ab1).shape)

# 二维情况进行连接 axis=0
c = np.array([[1, 2], [3, 4]])
d = np.array([[5, 6]])
cd = np.concatenate((c, d), axis=0)
# [[1 2]
#  [3 4]
#  [5 6]]
print(cd)

# 二维情况进行连接 axis=0
cd2 = np.concatenate((c, d.T), axis=1)
# [[1 2 5]
#  [3 4 6]]
print(cd2)
  • 6
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值