PyTorch - 03 - softmax手写实现FashionMNIST数据集的十分类器

softmax分类器是使用非常广泛的一种分类器,为了熟悉相关的操作,首先自己手写实现softmax分类模型,对FashionMNIST的数据做出预测

FashionMNIST数据集的使用可以参考我的上一篇博客

import matplotlib.pyplot as plt
import torch
import torchvision.transforms as transforms
from torchvision.datasets import FashionMNIST
from torch.utils.data import DataLoader

数据加载

基本的流程是:

读取数据集 ----> 数据处理 --------> 批处理

batch_size = 256

root_dir = "./torchvision/data"
train_set = FashionMNIST(root=root_dir, train=True, transform=transforms.ToTensor(), download=False)
test_set = FashionMNIST(root=root_dir, train=False, transform=transforms.ToTensor(), download=False)

train_iter = DataLoader(train_set, batch_size, shuffle=True, num_workers=0)
test_iter = DataLoader(test_set, batch_size, shuffle=False, num_workers=0)
print(len(train_iter))  # batch_size : 256
print(len(test_iter))   # batch_size : 256
235
40

初始化参数

当我们得到了批处理过后的数据之后, 可以根据输入数据的空间信息来得到相关参数的空间信息

假设输入为 batch_size * d, batch_size记作n, 把输入数据的空间信息记为 $ R^{n*d} $, 表示输入的数据是 \(n*d\)的二维矩阵

那么他是怎么对应我们的输入数据的呢?

输出第一个数据的空间信息如下:

for X, y in train_iter:
    print("X size:", X.size(), end='\n')
    print("y size:", y.size(), end='\n')
    break
X size: torch.Size([256, 1, 28, 28])
y size: torch.Size([256])

可以看到,输入的 X 是 \(R^{256*1*28*28}\)大小的四维张量,我们的输入是 \(R^{n*d}\)

因此对输入数据的操作是, 将\(1*28*28\)形状转换为 \(28*28*1 = 784\)的一维张量

其中1代表图片的颜色通道数,FashionMNIST都是灰度图,所以是1

这样将输入数据映射到 \(R^{256 * 784}\)的空间上, PyTorch就可以将输入的图片信息转换为矩阵进而进行计算了

num_inputs, num_outputs = 28*28*1, 10   # 输入的数是 28*28*1 = 784, 代表特征数量; 输出数量是10,对应十个类别

W = torch.normal(0, 0.01, [num_inputs, num_outputs], requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)
print(W.size(),b.size())
torch.Size([784, 10]) torch.Size([10])

我们看到 权值 W 的空间是 \(R^{784 * 10}\), 每一行对应的是10个类别的权值

偏置项b的空间是\(R^{10}\), 对应的是十个类别的偏置项

这样可以得到一组公式:

\[O^{n*q} = X^{n*d}W^{d*q} + b^{1*q},\hat{Y}^{n*q} = softmax(O^{n*q}) \]

X代表输入数据,有d个特征,W代表每个特征对应每个类别的权值, b是偏置项

O是计算结果,用softmax对其归一化,这样得到结果\(\hat{Y}^{n*q}\),其中每一行中最大数值的索引就是模型预测的类别标签。

注意:\((A^{n*q}+b^{1*q}=B^{n*q})\)计算中,b的行数是不够的,这个加法遵循 广播机制

softmax定义

softmax不仅归一化我们的计算结果,而且得到的结果符合概率分布

若有序列 \({x}_1, {x}_2, ..., {x}_n \in R\) ,那么

\[softmax({x}_i)=\frac{e^{{x}_i}}{\sum\limits_{i=1}^{n}(e^{x}_i)} \]

这是一位博主,介绍了dim参数的使用场景,包括多维张量。

pytorch 的 sum 和 softmax 方法 dim 参数的使用

def softmax(x):
    # dim=1表示对矩阵的行操作Keepdim=True将计算计算保留行和列两个维度
    return x.exp() / torch.sum(x.exp(), dim=1, keepdim=True)

模型定义

def net(X):
    return softmax(torch.mm(X.view(-1, num_inputs), W) + b )

测试下softmax

test_hat = torch.tensor([[0.2, 0.5, 0.3], [0.6, 0.1, 0.3]])
softmax_test_hat = softmax(test_hat)
print("sx_test:\n", softmax_test_hat)
print("softmax(sx_test):\n", torch.sum(softmax_test_hat, dim=1, keepdim=True))
sx_test:
 tensor([[0.2894, 0.3907, 0.3199],
        [0.4260, 0.2584, 0.3156]])
softmax(sx_test):
 tensor([[1.],
        [1.]])

交叉熵损失定义

交叉熵的系统性的介绍可以参考这位博主的介绍。交叉熵的详细介绍

交叉熵定义如下:\(H(p,q)=\sum\limits_{i}{p(x_i)log(\frac{1}{q(x_i)})}\), p(x), q(x)是离散随机变量X的两个概率分布

在分类问题中, 只有 \(i={label}\)\(p(x_i)\)才不等于0, 并且i=label时概率为1, 因此 可以简化为 \(H(p,q)=-log(q(x_{label}))\)

分类的交叉熵损失函数定义如下

def cross_entropy(y_hat, y):
    return -torch.log(y_hat.gather(1, y.view(-1, 1)))

sx_test.gather中第一个参数dim指定对行还是列操作

第二个参数index指定要获得的每行(列)的索引,可以是列表,但是不能超过行(列)数

计算如下:

print(softmax_test_hat)
y = torch.tensor([0, 0])
print("\ncross_entropy(test_hat, y): \n", cross_entropy(test_hat, y))

tensor([[0.2894, 0.3907, 0.3199],
        [0.4260, 0.2584, 0.3156]])

cross_entropy(test_hat, y): 
 tensor([[1.6094],
        [0.5108]])

训练模型和测试测试集数据

# optimizer
def SGD(params, learning_rate, size):
    for param in params:
        param.data -= learning_rate*param.grad/size

# 测试集数据评估
def evaluate_accuracy(data_iter, net):
    acc_sum, n = 0.0, 0
    for X, y in data_iter:
        acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
        n += y.shape[0]
    return acc_sum / n

def train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, params=None, lr=None, optimizer=None):
    for epoch in range(num_epochs):
        train_loss_sum, train_acc_sum, n = 0.0, 0.0, 0
        for X, y in train_iter:
            y_hat = net(X)
            cost = loss(y_hat, y).sum()

            if optimizer is not None:
                optimizer.zero_grad()
            elif params is not None and params[0].grad is not None:
                for param in params:
                    param.grad.data.zero_()

            cost.backward()

            if optimizer is None:
                SGD(params, lr, batch_size)
            else:
                optimizer.step()

            train_loss_sum += cost.item()
            train_acc_sum += (torch.argmax(y_hat, dim=1) == y).float().sum().item()
            n += y.shape[0]
        test_acc = evaluate_accuracy(test_iter, net)
        print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f' % (epoch + 1, train_loss_sum / n, train_acc_sum / n, test_acc))

num_epochs, lr = 5, 0.1
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, batch_size, [W, b], lr)
epoch 1, loss 0.7859, train acc 0.750, test acc 0.795
epoch 2, loss 0.5718, train acc 0.812, test acc 0.809
epoch 3, loss 0.5268, train acc 0.825, test acc 0.821
epoch 4, loss 0.5015, train acc 0.832, test acc 0.825
epoch 5, loss 0.4855, train acc 0.837, test acc 0.828
posted @ 2020-11-21 19:52  赝·goodfellow  阅读(632)  评论(0编辑  收藏  举报