【深度学习/机器学习】Softmax回归

开始之前,需要了解信息论的一些基本概念。

信息量、信息熵、相对熵和交叉熵

信息量:一个事件发生的概率越低,信息量越大。怎么理解呢?

  • “一个事件发生的概率是 1 2 \frac{1}{2} 21”,如果我们要以最小代价把这个信息传递出去,需要用到一位数(0或1)
  • “一个事件发生的概率是 1 4 \frac{1}{4} 41”,如果我们要以最小代价把这个信息传递出去,需要用到两位数(00/01/10/11)
  • “一个事件发生的概率是 1 8 \frac{1}{8} 81”,如果我们要以最小代价把这个信息传递出去,需要用到三位数(000/001/010/011/100/101/110/111)
    ……

从上面的例子中,我们可以得到一个事件的信息可以使用 log ⁡ 2 n \log_{2}{n} log2n n n n是等可能事件的数量)进行表示。一个系统中等可能事件越多( n n n 比较大),一个事件发生的概率就越低,那么信息量自然就大。如果已经得知了一个事件的概率为 p p p,那么信息量又可以表示为:
− log ⁡ 2 p -\log_{2}{p} log2p 信息熵:信息熵是对于系统而言的。一个系统中存在各种各样的事件,如何衡量这个系统的总信息量?其实就是对这个系统中的各个事件(假定数量为m)的信息量求期望。也即“m个事件概率与m个事件信息量相乘,再求和”。
H ( P ) = − ∑ i = 1 m p i log ⁡ 2 p i H(P)=-\sum_{i=1}^{m}p_i\log_{2}{p_i} H(P)=i=1mpilog2pi相对熵(KL散度):现在问题又来了,给定一组事件,事件 i i i在系统P中的概率是 p i p_i pi,在系统Q中的概率是 q i q_i qi,以系统P为基准,如何衡量系统Q相对于系统P的差异?我们可以求出每一个事件在系统Q中相对于系统P的信息量差异,然后在系统P中求期望。这就得到了相对熵。
D K L ( P ∣ ∣ Q ) = ∑ i = 1 m p i ( ( − log ⁡ 2 q i ) − ( − log ⁡ i p i ) ) = ∑ i = 1 m p i ( − log ⁡ 2 q i ) − ∑ i = 1 m p i ( − log ⁡ 2 p i ) D_{KL}(P||Q)=\sum_{i=1}^mp_i((-\log_2{q_i})-(-\log_i{p_i})) \\ \quad\quad\quad\quad\quad\quad\quad=\sum_{i=1}^mp_i(-\log_2{q_i})-\sum_{i=1}^mp_i(-\log_2{p_i}) DKL(P∣∣Q)=i=1mpi((log2qi)(logipi))=i=1mpi(log2qi)i=1mpi(log2pi)化简之后,可以发现, − ∑ i = 1 m p i ( − log ⁡ 2 p i ) -\sum_{i=1}^mp_i(-\log_2{p_i}) i=1mpi(log2pi)这一部分正是系统P的熵,而 ∑ i = 1 m p i ( − log ⁡ 2 q i ) \sum_{i=1}^mp_i(-\log_2{q_i}) i=1mpi(log2qi)这一部分我们称为交叉熵。吉布斯不等式证明,系统Q相对于系统P的交叉熵一定会大于或等于系统P的信息熵。
所以,我们只需要把交叉熵减小,就可以减小系统P和系统Q之间的差异!

Softmax回归

虽然Softmax回归叫做“回归”,但是它其实是一个用于分类的模型。
回归VS分类

  • 回归估计一个连续值
  • 分类判断一个离散类别
    在这里插入图片描述

类别编码

由于分类通常具有多个输出,而且有时候类别并不是数值类型的,可以使用one-hot对其进行编码。比如,有三个类别:猫、狗、鸟,可以编码为:

  • 猫:[1,0,0]
  • 狗:[0,1,0]
  • 鸟:[0,0,1]

Softmax函数

对于一个具有m个分量的向量 o ⃗ \vec{o} o ,对其使用Softmax函数:
y ^ i = e o i ∑ j = 1 m e o j \hat{y}_i=\frac{e^{o_i}}{\sum_{j=1}^{m}e^{o_j}} y^i=j=1meojeoi结果会把这个向量的每一个分量进行归一化(映射到0和1之间),可以把每一个分量都视为概率。
注意:由于这个公式是使用指数进行计算,有时候如果数值太大的情况,会出现指数爆炸的情况,计算机是无法表示这么大的数字的,会出现nan。因此,需要首先对输入的数据集进行预处理。

训练softmax模型的思路

用于训练和验证的数据集是一个非常经典的数据集——FashionMNIST。这个数据集中的训练集有60000张图片,测试集有10000张图片。图片分为10个类别。

  • 首先,加载数据集。每张图片的大小为28*28个像素,因此图片的特征有784个。可以直接使用pytorch将图片按照规定格式进行加载。
  • 定义softmax的网络:输入有784个特征向量,输出有10个类别。权重矩阵要对784个特征向量进行10次线性组合,因此权重矩阵有10列,每列中有784个元素,形状是784*10。此外,因为有10个输出,所以偏差向量的元素具有10个。
  • 假如输入网络的矩阵形状为100 * 784(100个样本,每个样本有784个特征),那么输出矩阵的形状为100 * 10(100个样本在10个类别上通过权重矩阵计算出的数值)。对于这个输出矩阵的每一行,都需要使用softmax公式将数值变为概率。这样就得到了一个100 * 10的概率矩阵(每一行有10个概率,分别对应每个类别,这些概率的和都是1)。
  • 得到经过softmax化的输出矩阵(100*10)之后,根据真实标签one-hot编码(100 * 10的矩阵),计算输出矩阵与真实标签矩阵的交叉熵,得到的结果会是一个100行1列的列向量(每一个分量都是每一个样本与它对应的真实标签之间的交叉熵)。
  • 交叉熵每个分量都进行求和对权重矩阵和偏差向量求导(求和之后和没有求和的结果是一样的),得到权重矩阵以及偏差向量的梯度。
  • 根据梯度对权重矩阵以及偏差向量进行梯度下降(迭代优化)。
  • 重复上面的过程,不断更新权重和偏差,进行一定学习周期之后,损失函数收敛,就可以得到最终的参数。

使用pytorch从零实现softmax模型

import torch
from torch.utils import data
from torchvision import datasets
from torchvision import transforms


def get_train_test_loader(image_size=28, train_batch_size=10, test_batch_size=10, num_workers=0, is_download=True):
    """
    获得训练数据生成器和验证数据生成器,这个数据集总共有10个类别(即10个标签)
    :param image_size: 图片的大小,取28
    :param train_batch_size: 数据生成器的批量大小
    :param num_workers: 数据生成器每次读取时调用的线程数量
    :param is_download: 是否要下载数据集(如果还未下载设置为True)
    :return: 训练数据生成器和验证数据生成器
    """
    data_transform = transforms.Compose([
        transforms.Resize(image_size), #设置图片大小
        transforms.ToTensor()           #转化为tensor张量
    ])

    train_data = datasets.FashionMNIST(root='../data', train=True, download=is_download, transform=data_transform)
    test_data = datasets.FashionMNIST(root='../data', train=False, download=is_download, transform=data_transform)
    train_loader = data.DataLoader(train_data, batch_size=train_batch_size, shuffle=True, num_workers=num_workers,
                                   drop_last=True)
    test_loader = data.DataLoader(test_data, batch_size=test_batch_size, shuffle=False, num_workers=num_workers,
                                  drop_last=True)
    return train_loader, test_loader


def softmax(input, w, b):
    """
    对于这个网络而言,输入是一个batch_size*784的矩阵(行数是批量大小,共有784个特征),输出要有10列
    那么,权重矩阵应该有10次线性组合即10列,10列中的每一列都需要对784个特征进行线性组合,所以有784行,故权重矩阵为784*10
    偏差目前是一个列向量
    :param input: 网络的输入
    :param w: 权重矩阵
    :param b: 偏差向量
    :return: 输出值是一个batch_size*10的矩阵(每一行加起来都为1)
    """
    o = torch.mm(input, w) + b
    # 利用softmax公式直接求出
    return torch.exp(o) / torch.sum(torch.exp(o), dim=1).reshape(-1, 1)


def cross_entropy_loss(y, y_hat):
    """
    交叉熵损失函数
    :param y: 真实值,是一个256行向量,类别0~9
    :param y_hat: 模型计算出的预测值,是一个256*10的矩阵
    :return: 交叉损失函数值
    """
    # 求每一个样本(每一行)的预测值相对于真实值的交叉熵
    # 这里的求法是非常独特的,巧妙运用了python的语法,y_hat[range(len(y_hat)),y]是指遍历range(len(y_hat))行,每一行取第y个元素
    # 看不懂的话,建议再看看交叉熵的公式,深刻理解这个python语法,然后把y(写成one-hot,本质上这个向量也需要是one-hot的
    # 形状才能求交叉熵)和y_hat的形状写出来。就很好理解了
    return -torch.log(y_hat[range(len(y_hat)), y])


def sgb(w, b, lr, batch_size):
    """参数梯度下降"""
    with torch.no_grad():
        w -= lr * (w.grad / batch_size)
        b -= lr * (b.grad / batch_size)
        w.grad.zero_()
        b.grad.zero_()


def accuracy(y_hat, y):
    """模型训练完成后,判断预测结果的准确率"""
    if y_hat.shape[0] < 2 and y_hat.shape[1] < 2:
        raise ValueError("dimesion error")
    # 得到预测的y_hat每一行中最大概率所在的索引(索引即类别)
    y_hat = y_hat.argmax(axis=1)
    # 判断预测类别是否与实际类别相等
    judge = y_hat.type(y.dtype) == y
    # 现在cmp是一个bool类型的向量,转成0和1,统计1的数量
    return float(judge.type(y.dtype).sum()) / len(y)


# 学习率
lr = 0.03
# 批量大小
batch_size = 100
test_batch_size = 10000
# 初始化权重和偏差,权重现在是一个矩阵,每一列都是线性组合的系数。总共有(28*28=784)个特征。此外,标签有10个
w = torch.normal(0, 0.01, (784, 10), requires_grad=True)
b = torch.zeros(10, requires_grad=True)
# 模型
net = softmax
# 损失函数
loss = cross_entropy_loss
# 梯度下降
trainer = sgb

# 获取数据生成器以及数据
train_loader, test_loader = get_train_test_loader(train_batch_size=batch_size, test_batch_size=test_batch_size,
                                                  is_download=False)
train_loader_test, _ = get_train_test_loader(train_batch_size=60000, is_download=False)

# 学习代数
num_epoch = 20

# 开始训练
for i in range(num_epoch):
    for x, y in train_loader:
        # 模型得到预测值
        y_hat = net(x.reshape(batch_size, 784), w, b)
        # 损失函数
        l = loss(y, y_hat).sum()
        print(f"\r{batch_size}个批量样本损失为{l}", end="", flush=True)
        # 求偏导
        l.backward()
        # 梯度下降
        sgb(w, b, lr, batch_size=batch_size)
    with torch.no_grad():
        for x, y in train_loader_test:
            y_hat = net(x.reshape(len(x), 784), w, b)
            l = loss(y, y_hat).sum()
            print(f"\n第{i}代,所有训练样本损失为{l}")
            print(f"训练集预测正确率:{accuracy(net(x.reshape(len(x), 784), w, b), y)}")
        for x, y in test_loader:
            print(f"验证集预测正确率:{accuracy(net(x.reshape(len(x), 784), w, b), y)}")
        print("=" * 25)

直接调用pytorch的库进行简洁实现

import torch
from torch.utils import data
from torchvision import datasets
from torchvision import transforms
from torch import nn


def get_train_test_loader(image_size=28, train_batch_size=10, test_batch_size=10, num_workers=0, is_download=True):
    """
    获得训练数据生成器和验证数据生成器,这个数据集总共有10个类别(即10个标签)
    :param image_size: 图片的大小,取28
    :param train_batch_size: 数据生成器的批量大小
    :param num_workers: 数据生成器每次读取时调用的线程数量
    :param is_download: 是否要下载数据集(如果还未下载设置为True)
    :return: 训练数据生成器和验证数据生成器
    """
    data_transform = transforms.Compose([
        # 设置图片大小
        transforms.Resize(image_size),
        # 转化为tensor张量
        transforms.ToTensor()
    ])

    train_data = datasets.FashionMNIST(root='../data', train=True, download=is_download, transform=data_transform)
    test_data = datasets.FashionMNIST(root='../data', train=False, download=is_download, transform=data_transform)
    train_loader = data.DataLoader(train_data, batch_size=train_batch_size, shuffle=True, num_workers=num_workers,
                                   drop_last=True)
    test_loader = data.DataLoader(test_data, batch_size=test_batch_size, shuffle=False, num_workers=num_workers,
                                  drop_last=True)
    return train_loader, test_loader

def accuracy(y_hat, y):
    """模型训练完成后,判断预测结果的准确率"""
    if y_hat.shape[0] < 2 and y_hat.shape[1] < 2:
        raise ValueError("dimesion error")
    # 得到预测的y_hat每一行中最大概率所在的索引(索引即类别)
    y_hat = y_hat.argmax(axis=1)
    # 判断预测类别是否与实际类别相等
    judge = y_hat.type(y.dtype) == y
    # 现在cmp是一个bool类型的向量,转成0和1,统计1的数量
    return float(judge.type(y.dtype).sum()) / len(y)

def init_weights(m):
    """将网络中每一个线性层的所有权重都利用标准差为0.01的正态分布进行初始化,b没有初始化,所以初始为0"""
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, std=0.01)


# 学习率
lr = 0.03
# 批量大小
batch_size = 100
test_batch_size = 10000
# 模型(nn.Flatten()是第一层,叫做展平层,会将输入的x的形状batch_size*28*28的矩阵自动reshape成batch_size*784,
# 这样就不用每次都手动对x进行reshape了,第二层是线性层,输入是784个特征,输出是10个向量)
net = nn.Sequential(nn.Flatten(),nn.Linear(784,10))
# apply会将net中的每一层都作为参数进入init_weights进行初始化,当发现是线性层,会对线性层的w自动初始化
net.apply(init_weights)
# 损失函数是交叉熵函数,参数是y_hat和y,注意,会对传入的y_hat先进行一次softmax处理
loss = nn.CrossEntropyLoss()
# 梯度下降
trainer = torch.optim.SGD(net.parameters(), lr=lr)

# 获取数据生成器以及数据
train_loader, test_loader = get_train_test_loader(train_batch_size=batch_size, test_batch_size=test_batch_size,
                                                  is_download=False)
train_loader_test, _ = get_train_test_loader(train_batch_size=60000, is_download=False)

# 学习代数
num_epoch = 20

for i in range(num_epoch):
    for x, y in train_loader:
        # 模型得到预测值
        y_hat = net(x)
        # 损失函数
        l = loss(y_hat,y)
        print(f"\r{batch_size}个批量样本损失为{l}", end="", flush=True)
        trainer.zero_grad()
        # 求偏导
        l.backward()
        # 梯度下降
        trainer.step()
    with torch.no_grad():
        for x, y in train_loader_test:
            y_hat = net(x)
            l = loss(y_hat,y)
            print(f"\n第{i}代,所有训练样本损失为{l}")
            print(f"验证集预测正确率:{accuracy(y_hat,y)}")
        for x, y in test_loader:
            print(f"测试集预测正确率:{accuracy(net(x), y)}")
        print("=" * 25)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值