在上一篇文章中,笔者介绍了什么是Softmax回归及其原理。因此在接下来的这篇文章中,我们就来开始动手实现一下Softmax回归,并且最后要完成利用Softmax模型对Fashion MINIST进行分类的任务。在开始实现Softmax之前,我们先来了解一下Fashion MINIST这一数据集。
1 数据集
1.1 FashionMNIST
数据集FashionMNIST虽然名字里面有'MNIST'这个词,但是其与手写体识别一点关系也没有,仅仅只是因为FashionMNIST数据集在数据集规模、类别数量和图片大小上与MINIST手写体数据集一致。
如图1所示便为Fashion MNIST数据集的部分可视化结果,其包含有训练集6万张和测试集1万张,每张图片的大小为[28,28]
。在Pytorch中,我们可以通过如下代码对其进行载入:
def load_dataset():
mnist_train = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST', train=True, download=True,transform=transforms.ToTensor())
mnist_test = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST', train=False, download=True,transform=transforms.ToTensor())
return mnist_train, mnist_test
其中参数root
表示指定数据集的保存目录;train
表示返回训练集还是测试集;download
表示数据集不存在时是否需要下载;transform
表示指定一种转换方法,而指定transforms.ToTensor()
就是将尺寸为(H x W x C)且数据位于[0,255]的PIL图片或者数据类型为np.unit8
的numpy数组转换为尺寸为(C x H x W)且数据类型为torch.float32
,范围在的Tensor
。
同时,我们还可以通过代码image= mnist_train[0][0]
和label=mnist_train[0][1]
来分别访问一张图片和其对应的标签值。
1.2 构造数据集
在模型实际训练过程中,数据读取经常是训练的性能瓶颈。同时,为了能够更好的训练模型我们通常会对数据进行打乱,以及分批(batch)的将数据输入的模型中。在Pytorch中,我们可以通过DataLoader
这个类来方便的完成上述功能。
start = time.time()
train_iter = torch.utils.data.DataLoader(mnist_test, batch_size=1024, shuffle=True, num_workers=2)
for x_test, y_test in train_iter:
print(x_test.shape)
print('%.2f sec' % (time.time() - start))
#结果
torch.Size([1024, 1, 28, 28])
torch.Size([1024, 1, 28, 28])
torch.Size([1024, 1, 28, 28])
torch.Size([1024, 1, 28, 28])
torch.Size([1024, 1, 28, 28])
torch.Size([1024, 1, 28, 28])
torch.Size([1024, 1, 28, 28])
torch.Size([1024, 1, 28, 28])
torch.Size([1024, 1, 28, 28])
torch.Size([784, 1, 28, 28])
2.60 sec
其中batsh_size
表示指定每次返回batsh_size
个样本;shuffle=True
表示对数据集进行打乱;num_workers=2
表示用两个进程来读取数据。
但需要注意的是,这里的数据集mnist_test
是Pytorch内置的,那如果是我们自己读入的数据集该怎么使用DataLoader
呢?我们只需要首先将自己的数据集转换成tensor
,然后再通过TensorDataset
这个类来构造一个数据集即可。
def make_dataset():
x = torch.linspace(0, 100, 100, dtype=torch.float32).reshape(-1, 2)
y = torch.randn(50 )
dataset = torch.utils.data.TensorDataset(x, y)
return dataset
此时返回的dataset数据集也就同样能够通过DataLoader
进行读取。
2 Softmax多分类
在实现这个分类模型之前,我们先来介绍一下几个需要用到的函数。
2.1 softmax计算实现
在上一篇文章中我们介绍了softmax的计算公式,其实现可以通过如下代码来完成:
def softmax(x):
s = torch.exp(x)
return s / torch.sum(s, dim=1, keepdim=True)# 此处触发了广播机制
a = torch.tensor([[1,2,3.],[2,3,1.]])
print(softmax(a))
#结果:
tensor([[0.0900, 0.2447, 0.6652],
[0.2447, 0.6652, 0.0900]])
其中torch.exp()
为计算每个元素的指数次方;sum(s, dim=1, keepdim=True)
表示计算得到每一行的和;最后是按位除操作。需要注意的是传入的x
必须是浮点类型的,不然会报错。
2.2 交叉熵计算实现
假设我们现在有两个样本,其预测得到的概率分布为[0.1,0.3,0.6]
和[0.5,0.4,0.1]
。同时,正确的标签分布为[0,0,1]
和[0,1,0]
,则对应的交叉熵为。但是,我们在用代码实现的时候完全不用这么麻烦,只需要通过正确的标签找到预测概率分布中对应的值,再取对数即可。
例如[0,0,1]
和[0,1,0]
这两个真实分布对应的标签就应该是2和1(从0开始),因此我们只需要分别取[0.1,0.3,0.6]
和[0.5,0.4,0.1]
中第2个元素0.6和第1个原始0.4,再取对数就能实现交叉熵的计算。
上述过程通过如下代码便可完成:
def crossEntropy(logits,y):
c = -torch.log(logits.gather(1,y.reshape(-1,1)))
return torch.sum(c)# 注意这里返回的是和
logits = torch.tensor([[0.1, 0.3, 0.6], [0.5, 0.4, 0.1]])
y = torch.LongTensor([2, 1])
c = crossEntropy(logits,y)
print(c)
#结果
tensor(1.4271)
其中.gather()
就是根据指定维度和索引,选择对应位置上的元素。同时,需要注意的是logits
的每一行为一个样本的概率分布,因此我们需要在行上进行索引操作,故gather()
的第一个参数应该是1
,这一点一定要注意。
2.3 准确率计算实现
在前面介绍softmax时说到,对于每个样本的预测类别,我们会选择对应概率值最大的类别作为输出结果。因此,在计算预测的准确率时,我们首先需要通过torch.argmax()
这个函数来返回预测得到的标签。
y_true = torch.tensor([[2,1]])
logits = torch.tensor([[0.1,0.3,0.6],[0.5,0.4,0.1]])
y_pred = logits.argmax(1)
print(y_pred)
#结果
tensor([2, 0])
最后,我们将预测得到的标签同正确标签进行对比即可求得准确率。
def accuracy(y_true,logits):
acc = (logits.argmax(1) == y_true).float().mean()
return acc.item()
print(accuracy(y_true,logits))
#结果
0.5
2.4 评估模型
一般我们训练得到一个模型后都需要对其在测试集上进行评估,也就是在测试集上计算其总的准确率。因此,我们首先需要计算得到所有预测对的样本(而不仅仅只是一个batch),然后再除以总的样本数即可。
def evaluate(data_iter, forward, input_nodes, w, b):
acc_sum, n = 0.0, 0
for x, y in data_iter:
logits = forward(x, input_nodes, w, b)
acc_sum += (logits.argmax(1) == y).float().sum().item()
n += len(y)
return acc_sum / n
2.5 分类模型实现
w = torch.tensor(np.random.normal(0, 0.5, [input_nodes, output_nodes]),
dtype=torch.float32, requires_grad=True)
b = torch.tensor(np.random.randn(output_nodes), dtype=torch.float32, requires_grad=True)
for epoch in range(epochs):
for i, (x, y) in enumerate(train_iter):
logits = forward(x, input_nodes, w, b)
l = crossEntropy(y, logits)
l.backward()
gradientDescent([w, b], lr)
acc = accuracy(y, logits)
if i % 50 == 0:
print("Epoches[{}/{}]---batch[{}/{}]---acc{:.4}---loss {:.4}".format(
epoches, epoch, len(mnist_train) // batch_size, i, acc,l))
acc = evaluate(test_iter, forward, input_nodes, w, b)
print("Epoches[{}/{}]--acc on test{:.4}".format(epochs, epoch, acc))
# 结果:
Epochs[8000/20]--acc on test0.8323
Epochs[8000/21]---batch[468/0]---acc0.8516---loss 47.13
Epochs[8000/21]---batch[468/50]---acc0.8203---loss 67.22
Epochs[8000/21]---batch[468/100]---acc0.9219---loss 38.74
Epochs[8000/21]---batch[468/150]---acc0.8516---loss 57.39
Epochs[8000/21]---batch[468/200]---acc0.8281---loss 74.76
Epochs[8000/21]---batch[468/250]---acc0.8672---loss 55.32
Epochs[8000/21]---batch[468/300]---acc0.8281---loss 60.19
可以看到,大约20轮迭代后,softmax模型在测试集上的准确率就达到了0.83左右。
3 总结
在这篇文章中,笔者首先介绍了FashionMNIST数据集。然后接着介绍了如何使用Pytorch中的DataLoader
来构造训练数据迭代器。最后,介绍了如何通过Pytorch来一步步的实现Softmax分类模型,包括如何实现softmax操作、如何快捷的计算交叉熵、如何计算模型的准确率等等。本次内容就到此结束,感谢您的阅读!
本次内容就到此结束,感谢您的阅读!如果你觉得上述内容对你有所帮助,欢迎关注并传播本公众号!若有任何疑问与建议,请添加笔者微信'nulls8'进行交流。青山不改,绿水长流,我们月来客栈见!
引用
[1]动手深度学习
[2]示例代码:https://github.com/moon-hotel/DeepLearningWithMe
推荐阅读
[1]想明白多分类必须得谈逻辑回归
[2]Pytorch之Linear与MSELoss
[3]Pytorch之拟合正弦函数你会吗?
[4]你告诉我什么是深度学习
[5]《跟我一起深度学习》终于来了