多分类逻辑回归判断三分类鸢尾花

多分类逻辑回归判断三分类鸢尾花

小记

2022.4.20
参考张觉非的书的 ch04章,

训练目标

有一个鸢尾花数据集,里面有150个鸢尾花样本,每个样本有4个特征,有3个类别,判断该样本属于哪个类别

思路

  1. 这是一个四特征,三分类的分类问题,尝试构造一个三分类逻辑回归模型来解决。 Wx+b是线性部分,线性部分经SoftMax函数得到三维概率向量p,从而得到预测值, 线性部分与分类经CrossEntropyWithSoftMax 得到交叉熵。

    mark
  2. logit函数,也就是线性部分为 mark

​ 对logit向量施加SoftMax函数得到多分类概率分布函数为mark

​ **SoftMax函数的好处:**使得输出向量的所有分量之和是1,且当输入向量的某个分量极小时 候,对应分量近似为0;当输入向量的某个分量极大时,对应分量为近似为1。

  1. 交叉熵(cross entropy) 可以用来衡量两个分布的相似程度mark

​ 交叉熵越小,则分布p与分布l就越相似。

分布p是由SoftMax得到的, 但是在程序中,不是先调用SoftMax函数再调用交叉熵函数,而是把SoftMax函数和交叉熵合并实现为了一个结点类,这是因为这样会使得反向传播计算雅可比矩阵时候更容易。
∂ C r o s s E n t r o p y ( p , l ) ∂ a s = p s − l s \frac{\partial CrossEntropy(p,l) }{\partial a_s}=p_s-l_s asCrossEntropy(p,l)=psls

  1. 分布l,即代表 标签的分布,用独热编码即 One-Hot 编码表示,其特点是任意时候,只有一位有效。 样本属于第几类否则向量的第几分量就取1,其余分量取0。

代码更新

  1. 上篇优化器 的代码上改。
  2. 增加 了激活函数模块 增加 SoftMax 类,损失函数模块增加CrossEntropyWithSoftMax类

代码框架

mark

代码

main.py
# -*- coding: utf-8 -*-
"""
Created on Sat Feb  8 20:31:30 2020

@author: chaos
"""

import sys
import numpy as np
import pandas as pd
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
import node,ope,optimizer,loss
from node import Variable
from graph import  default_graph
from icecream import ic

# 读取鸢尾花数据集,去掉第一列Id
data = pd.read_csv("Iris.csv").drop("Id", axis=1)
# 随机打乱样本顺序
data = data.sample(len(data), replace=False)

# 将字符串形式的类别标签转换成整数0,1,2
le = LabelEncoder()
number_label = le.fit_transform(data["Species"])

# 将整数形式的标签转换成One-Hot编码
oh = OneHotEncoder(sparse=False)
one_hot_label = oh.fit_transform(number_label.reshape(-1, 1))

# 特征列
features = data[['SepalLengthCm',
                 'SepalWidthCm',
                 'PetalLengthCm',
                 'PetalWidthCm']].values


# 构造计算图:输入向量,是一个4x1矩阵,不需要初始化,不参与训练
x = Variable(shape=(4, 1), trainable=False)
W = Variable(shape=(3, 4),  trainable=True)
b = Variable(shape=(3, 1), trainable=True)

W.set_value(np.mat(np.random.normal(0, 0.001, (3, 4))))
b.set_value(np.mat(np.random.normal(0, 0.001, (3, 1))))

one_hot =Variable(shape=(3, 1), trainable=False)


# 线性部分
linear = ope.Add(ope.MatMul(W, x), b)

# 模型输出
predict = ope.SoftMax(linear)

# 交叉熵损失
loss = loss.CrossEntropyWithSoftMax(linear, one_hot)

# 学习率
learning_rate = 0.02

# 构造Adam优化器
optimizer = optimizer.Adam(default_graph, loss, learning_rate)

# 批大小为16
batch_size = 16

# 训练执行200个epoch
for epoch in range(100):

    # 批计数器清零
    batch_count = 0

    # 遍历训练集中的样本
    for i in range(len(features)):

        # 将特征赋给x节点,将标签赋给one_hot节点
        x.set_value(np.mat(features[i, :]).T)
        one_hot.set_value(np.mat(one_hot_label[i, :]).T)

        # 调用优化器的one_step方法,执行一次前向传播和反向传播
        optimizer.forward_backward()
        batch_count += 1

        # 若批计数器大于等于批大小,则执行一次梯度下降更新,并清零计数器
        if batch_count >= batch_size:
            optimizer.update()
            batch_count = 0

    # 每个epoch结束后评估模型的正确率
    pred = []

    # 遍历训练集,计算当前模型对每个样本的预测值
    for i in range(len(features)):
        feature = np.mat(features[i, :]).T
        x.set_value(feature)

        # 在模型的predict节点上执行前向传播        # 批计数器加1
        predict.forward()
        pred.append(predict.value.A.ravel())  # 模型的预测结果:3个概率值

    # 取最大概率对应的类别为预测类别
    pred = np.array(pred).argmax(axis=1)

    # 判断预测结果与样本标签相同的数量与训练集总数量之比,即模型预测的正确率
    accuracy = (number_label == pred).astype(np.int).sum() / len(data)

    # 打印当前epoch数和模型在训练集上的正确率
    print("epoch: {:d}, accuracy: {:.3f}".format(epoch + 1, accuracy))
SoftMax(Operator):
class SoftMax(Operator):
    """
    SoftMax函数
    """

    @staticmethod
    def softmax(a):
        a[a > 1e2] = 1e2  # 防止指数过大
        ep = np.power(np.e, a)
        return ep / np.sum(ep)

    def compute(self):
        self.value = SoftMax.softmax(self.parents[0].value)

    def get_jacobi(self, parent):
        """
        我们不实现SoftMax节点的get_jacobi函数, 因为它只用在输出预测值时。
        训练时使用CrossEntropyWithSoftMax节点
        """
        raise NotImplementedError("Don't use SoftMax's get_jacobi")
CrossEntropyWithSoftMax(LossFunction):
class CrossEntropyWithSoftMax(LossFunction):
    """
    对第一个父节点施加SoftMax之后,再以第二个父节点为标签One-Hot向量计算交叉熵
    """

    def compute(self):
        prob = ope.SoftMax.softmax(self.parents[0].value)
        self.value = np.mat(
            -np.sum(np.multiply(self.parents[1].value, np.log(prob + 1e-10))))

    def get_jacobi(self, parent):
        # 这里存在重复计算,但为了代码清晰简洁,舍弃进一步优化
        prob = ope.SoftMax.softmax(self.parents[0].value)
        if parent is self.parents[0]:
            return (prob - self.parents[1].value).T
        else:
            return (-np.log(prob)).T
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

是Mally呀!

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

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

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

打赏作者

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

抵扣说明:

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

余额充值