DL基础补全计划(二)---Softmax回归及示例(Pytorch,交叉熵损失)

2024-06-16 06:58

本文主要是介绍DL基础补全计划(二)---Softmax回归及示例(Pytorch,交叉熵损失),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

PS:要转载请注明出处,本人版权所有。

PS: 这个只是基于《我自己》的理解,

如果和你的原则及想法相冲突,请谅解,勿喷。

环境说明
  • Windows 10
  • VSCode
  • Python 3.8.10
  • Pytorch 1.8.1
  • Cuda 10.2

前言


  在《DL基础补全计划(一)—线性回归及示例(Pytorch,平方损失)》(https://blog.csdn.net/u011728480/article/details/118463588 )一文中我们对深度学习中的非常基础的知识进行了简单介绍,按照常见深度学习中的基本流程设计了一个简单的线性模型。同时,基于基本的语法,展示了数据收集,数据小批量随机获取,网络forward, loss设计,基于loss的bp,随机小批量梯度下降,模型训练,模型预测等基本的流程。 记录这篇文章的原因也很简单,为了将自己从学校里面带出来的知识和深度学习中的基础知识关联起来,不要出现大的断层和空洞。

  在上文我们提到,我们已经能够设计一类模型能够求解特定函数的数值,但是在实际应用场景中,我们还有一些问题主要还是关注他们的分类。比如我们有一堆数据,怎么把他们分为N类。这里就要介绍深度学习中一类常见的模型,softmax回归模型。本文的主要目的就是基于FashionMNIST数据集(60000 * 28 * 28 训练集,10000 * 28 * 28 测试集),从基础的语法开始设计一个softmax分类模型,并介绍一些softmax相关的重点,在本文之后,其实我们就可以做一些深度学习的简单分类任务了。





Softmax介绍及实例


  我们可以知道,Softmax这个函数其实就是对N个类别进行打分,输出N个类别的概率,那么它的实际底层工作原理到底是什么呢?

  假如我们定义输出类别为N,输入特征为X, 输出类别分数为Y,参数为W,偏置为b,那么我们可以设计一个函数为: Y = W X + b Y=WX+b Y=WX+b,W.shape是(N, len(X)), X.shape是(len(X), 1), b.shape 是(N, len(X)),Y.shape是(N , 1),通过这样的一个线性运算后,我们就可以将len(X)个输入变换为N个输出,其实这个时候的N个输出就是我们不同类别的分数,理论上来说,我们就可以用这个当做每个类别的分数或者说概率。由于这里的Y是实数范围,有正有负,有大有小,存在数据不稳定性,而且我们需要把输出的类别当做概率使用,这里如果存在负数的话,不满足概率的一些定义。因此我们在经过一个线性变换后,再通过softmax运算,才能够将这些分数转换为相应的概率。

  Y.shape是(N , 1),Softmax定义为: S o f t m a x ( Y i ) = e x p ( Y i ) / ∑ j = 0 N − 1 Y j Softmax(Yi)=exp(Yi)/\sum\limits_{j=0}^{N-1}Yj Softmax(Yi)=exp(Yi)/j=0N1Yj ,因此我们可以通过Softmax得到每个类别的分数。 Y ′ = S o f t m a x ( Y ) Y'=Softmax(Y) Y=Softmax(Y),通过这样的运算后,就把Y归一化到0~1,而且满足概率的一些定义和保持了和Y同样的性质。

  下面我们基于FashionMNIST数据集(此数据集有10个类别,60000个训练集,10000个测试集,图片为单通道28*28),设计一个简单的分类模型。下面是python需要导入的依赖

from numpy.core.numeric import cross
import torch
from torch.utils.data import Dataset
from torchvision import datasets
from torchvision.transforms import ToTensor
import matplotlib.pyplot as plt
from torch.utils.data import DataLoader
import numpy as np


获取并处理FashionMNIST数据集

  通过Pytorch的设计好的api直接获取数据集,并得到解析后的数据

def LoadFashionMNISTByTorchApi():# 60000*28*28training_data = datasets.FashionMNIST(root="data",train=True,download=True,transform=ToTensor())# 10000*28*28test_data = datasets.FashionMNIST(root="data",train=False,download=True,transform=ToTensor())labels_map = {0: "T-Shirt",1: "Trouser",2: "Pullover",3: "Dress",4: "Coat",5: "Sandal",6: "Shirt",7: "Sneaker",8: "Bag",9: "Ankle Boot",}figure = plt.figure(figsize=(8, 8))cols, rows = 3, 3for i in range(1, cols * rows + 1):sample_idx = torch.randint(len(training_data), size=(1,)).item()img, label = training_data[sample_idx]figure.add_subplot(rows, cols, i)plt.title(labels_map[label])plt.axis("off")plt.imshow(img.squeeze(), cmap="gray")plt.show()return training_data, test_data
rep_img

  通过面的代码可以知道,datasets.FashionMNIST()返回的是集合,集合里面存的是每个图的数据以及其标签。这里其实Pytorch帮我们做了解析工作,实际FashionMNIST的二进制存储格式如下,我们也可以自己写代码按照此规则解析数据集,这里就不关注这个问题了。

'''
Image:
[offset] [type]          [value]          [description]
0000     32 bit integer  0x00000803(2051) magic number
0004     32 bit integer  60000            number of images
0008     32 bit integer  28               number of rows
0012     32 bit integer  28               number of columns
0016     unsigned byte   ??               pixel
0017     unsigned byte   ??               pixel
........
xxxx     unsigned byte   ??               pixel
''''''
Label:
[offset] [type]          [value]          [description]
0000     32 bit integer  0x00000801(2049) magic number (MSB first)
0004     32 bit integer  60000            number of items
0008     unsigned byte   ??               label
0009     unsigned byte   ??               label
........
xxxx     unsigned byte   ??               label
The labels values are 0 to 9.
'''

  还记得我们前文的随机小批量怎么实现的吗?首先随机打乱数据集中的每个数据(图片和标签为一个数据)的顺序,然后根据batch_size参数构造一个可迭代的对象返回出来,最后训练的时候我们通过for xx in data_iter 来访问这一批的数据。这里我们也不需要自己来写这个了,直接调用Pytorch的函数来生成这样的一个data_iter,我们应该把更多注意力放到其他地方去。代码如下:

training_data, test_data = LoadFashionMNISTByTorchApi()
batch_size = 200# 返回训练集和测试集的可迭代对象
train_dataloader = DataLoader(training_data, batch_size, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size, shuffle=True)


设计网络

  我们的网络由两个部分构成,一个是从28*28到10的一个映射函数,一个是softmax函数。我们定义一个 Y = W X + b , Y ′ = S o f t m a x ( Y ) Y=WX+b, Y'=Softmax(Y) Y=WX+b,Y=Softmax(Y),因此我们可以看到,我们所需要学习的参数有W和b。

  根据前文介绍,我们可以知道Y’/Y.shape是(10, 1), X.shape是(784, 1), W.shape是(10, 784), b.shape(10, 1)

def softmax(out):# example:# out = (p1, p2, p3)# set Sum=p1+p2+p3# softmax(out) = (p1/Sum, p2/Sum, p3/Sum)exp_out = torch.exp(out)partition = exp_out.sum(dim=1, keepdim=True)return exp_out/partitiondef my_net(w, b, X):# print(X.shape)# print(w.shape)# print(b.shape)liear_out = torch.matmul(X, w) + b# print(liear_out.shape)return softmax(liear_out)


设计Loss函数及优化函数

  在前文的线性回归中,我们使用了平方误差来作为Loss函数,在分类这一问题里面,我们需要引入交叉熵来作为Loss函数。交叉熵作为信息论中的概念,我们简单的通过几个前置知识来引入:

  • 信息论研究的是一个随机事件携带的信息量,基本思想是事件发生概率越大,所携带的信息量越小。因此这里可以引入一个自信息定义: I ( x ) = − log ⁡ 2 ( P ( x ) ) I(x)=-\log_{2}(P(x)) I(x)=log2(P(x))。通过这个定义我们可以得到同样的趋势,一个事件发生的概率越小,携带的信息量越大。

  • 熵(Entropy),自信息是对单个随机事件的信息量大小描述,我们需要定义来描述整个随机分布的信息量大小的描述。假设随机分布是离散的,熵的定义为: H ( X ) = − ∑ i = 0 n − 1 P ( X i ) log ⁡ 2 ( P ( X i ) ) H(X)=-\sum\limits_{i=0}^{n-1}P(Xi)\log_{2}(P(Xi)) H(X)=i=0n1P(Xi)log2(P(Xi))

  • KL差异(Kullback-Leibler (KL) divergence),主要就是用来描述两个分布的差异。因为在有些时候,一个概率分布很复杂,我们可以用一个简单的概率分布来替代,但是我们需要知道这两个分布的差异。定义原概率分布为P(X),近似概率分布为Q(X),假如X是离散随机变量,KL差异定义为: D K L ( P ( X ) ∣ ∣ Q ( X ) ) = ∑ i = 0 n − 1 P ( X i ) log ⁡ 2 ( P ( X i ) / Q ( X i ) ) = ∑ i = 0 n − 1 P ( X i ) [ log ⁡ 2 ( P ( X i ) ) − log ⁡ 2 ( Q ( X i ) ) ] D_{KL}(P(X)||Q(X))=\sum\limits_{i=0}^{n-1}P(Xi)\log_{2}(P(Xi)/Q(Xi))=\sum\limits_{i=0}^{n-1}P(Xi)[\log_{2}(P(Xi)) - \log_{2}(Q(Xi))] DKL(P(X)Q(X))=i=0n1P(Xi)log2(P(Xi)/Q(Xi))=i=0n1P(Xi)[log2(P(Xi))log2(Q(Xi))]

  • 交叉熵(cross-entropy),交叉熵定义为: H ( P , Q ) = − ∑ i = 0 n − 1 P ( X i ) log ⁡ 2 ( Q ( X i ) ) H(P,Q)=-\sum\limits_{i=0}^{n-1}P(Xi)\log_{2}(Q(Xi)) H(P,Q)=i=0n1P(Xi)log2(Q(Xi)),我们可以看到 H ( P , Q ) = H ( P ) + D K L ( P ∣ ∣ Q ) H(P,Q)=H(P)+D_{KL}(P||Q) H(P,Q)=H(P)+DKL(PQ)

  • 在上文中,我们一步一步引出了交叉熵,这个时候,我们来看为什么在深度学习中可以引入交叉熵作为Loss函数,对于特定的一组Feature,我们可以通过标签得到这组feature代表什么,相当于其概率为1,因此在原概率分布上面, P ( X i ) = 1 , H ( X i ) = 0 P(Xi)=1, H(Xi)=0 P(Xi)=1,H(Xi)=0,我们可以看到这个时候交叉熵和KL差异是相等的,那么交叉熵其实就是描述我们训练时得到的概率分布和原分布的差异。因此,在分类问题中我们得到的是当前的每个分类的概率,那么我们分别求每个分类当前概率分布相对于原分布的KL差异,那么我们就知道我们的训练参数和真实参数的差异。我们求交叉熵的最小值,也就代表我们参数越接近真实值。

# 信息论,熵,kl熵(相对),交叉熵
def cross_entropy(y_train, y_label):# l = -y*log(y')# print(y_train.shape)# print(y_label.shape)# print(y_train)# print(y_label)# print(y_train[0][:].sum())# call pickmy_loss = -torch.log(y_train[range(len(y_train)), y_label])# nll_loss = torch.nn.NLLLoss()# th_loss = nll_loss(torch.log(y_train), y_label)# print(my_loss.sum()/len(y_label))# print(th_loss)return my_loss


设计准确率统计函数以及评估准确率函数

  在前一小节,我们已经设计了损失函数,我们在训练的过程中,除了要关注损失函数的值外,还需要关注我们模型的准确率。

  模型的准确率代码如下:

def accuracy(y_train, y_label): #@save"""计算预测正确的数量。"""# y_train = n*num_classif len(y_train.shape) > 1 and y_train.shape[1] > 1:# argmax get the max-element-indexy_train = y_train.argmax(axis=1)# cmp = n*1 , eg: [0 0 0 1 1 1 0 0 0]# print(y_train.dtype)# print(y_label.dtype)cmp = y_train == y_labelreturn float(cmp.sum()/len(y_label))

  从上面的代码可以知道,我们的网络输出是当前feature在每个类别上的概率,因此我们求出网络输出中,概率最大的索引,和真实label进行对比,相等就代表预测成功一个,反之。我们对最终数据求和后除以batch_size,就可以得到在batch_size个特征中,我们的预测正确的个数占比是多少。

  我们还需要在指定的数据集上评估我们的准确率,其代码如下(就是分批调用获得准确率后求平均):

def evaluate_accuracy(net, w, b, data_iter): #@save"""计算在指定数据集上模型的精度。"""test_acc_sum = 0.0times = 1for img, label in data_iter:test_acc_sum += accuracy(net(w, b, img.reshape(-1, w.shape[0])), label)times += 1return test_acc_sum/times


设计预测函数

  预测函数就是在特定数据上面,通过我们训练的网络,求出类别,并与真实label进行对比,其代码如下:

def predict(net, w, b, test_iter, n=6): #@save"""预测标签(定义⻅第3章)。"""for X, y in test_iter:breaklabels_map = {0: "T-Shirt",1: "Trouser",2: "Pullover",3: "Dress",4: "Coat",5: "Sandal",6: "Shirt",7: "Sneaker",8: "Bag",9: "Ankle Boot",}trues = [labels_map[i] for i in y.numpy()]preds = [labels_map[i] for i in net(w, b, X.reshape(-1, w.shape[0])).argmax(axis=1).numpy()]for i in np.arange(n):print(f'pre-idx {i} \n true_label/pred_label: {trues[i]}/{preds[i]}')


训练模型

  训练模型的话,其实就是将上面的代码缝合起来。代码如下:

if __name__ == '__main__':training_data, test_data = LoadFashionMNISTByTorchApi()batch_size = 200train_dataloader = DataLoader(training_data, batch_size, shuffle=True)test_dataloader = DataLoader(test_data, batch_size, shuffle=True)# train_features, train_labels = next(iter(train_dataloader))# print(f"Feature batch shape: {train_features.size()}")# print(f"Labels batch shape: {train_labels.size()}")# img = train_features[1].squeeze()# label = train_labels[1]# plt.imshow(img, cmap="gray")# plt.show()# print(f"Label: {label}")# 28*28num_inputs = 784# num of classnum_outputs = 10# (748, 10)w = torch.from_numpy(np.random.normal(0, 0.01, (num_inputs, num_outputs)))w = w.to(torch.float32)w.requires_grad = Trueprint('w = ', w.shape)# (10, 1)b = torch.from_numpy(np.zeros(num_outputs))b = b.to(torch.float32)b.requires_grad = Trueprint('b = ', b.shape)num_epochs = 10lr = 0.1net = my_netloss = cross_entropy# if torch.cuda.is_available():#     w = w.to('cuda')#     b = b.to('cuda')for epoch in range(num_epochs):times = 1train_acc_sum = 0.0train_loss_sum = 0.0for img, label in train_dataloader:# if torch.cuda.is_available():#     img = img.to('cuda')#     label = label.to('cuda')            # print(img.shape, label.shape)l = loss(net(w, b, img.reshape(-1, w.shape[0])), label)# print(l.shape)# print(l)# clean grad of w,bw.grad = Noneb.grad = None # bpl.backward(torch.ones_like(l))# update paramsgd([w, b], lr, batch_size)train_acc_sum += accuracy(net(w, b, img.reshape(-1, w.shape[0])), label)train_loss_sum += (l.sum()/batch_size)times += 1# breaktest_acc = evaluate_accuracy(net, w, b, test_dataloader)print('epoch = ', epoch)print('train_loss = ', train_loss_sum.detach().numpy()/times)print('train_acc = ', train_acc_sum/times)print('test_acc = ', test_acc)# break# predictpredict(net, w, b, test_dataloader, n = 10)

  从如上的代码可知,首先从数据集中得到小批量数据迭代器,然后随机生成初始化参数,最后在小批量数据上推理,求loss之,bp,更新参数,记录loss和acc,最终训练次数完了后,去预测。

  训练截图如下:

rep_img

  预测截图如下:

rep_img
  从预测的截图来看,预测成功的准确率大于1/10。说明我们的模型的有效的。此图中看起来准确率较高,这是偶然现象,但是真实不应该这样的,因为在测试集上,准确率只有81%左右。



后记


  此外,我们这里仅仅是按照数学定义来做计算,在计算机中,我们现在设计的一些函数可能不合理,比如softmax会产生溢出,我们会用到LogExpSum技巧,把softmax和交叉熵一起计算,通过冥函数和对数函数的一些性质,我们可以化简后抵消一些exp的计算,保证数值的稳定性,我们只需要知道有这么一个事情即可。但是这一切都不需要我们来弄,我们只需要调用别人设计的好的函数即可,比如pythorch中的torch.nn.CrossEntropyLoss()。如果真的有需要,可以根据LogExpSum的定义来直接编写就行,在这里,本文就不关注这个。

  从线性回归到softmax回归,我们算是基本了解清楚了深度学习的一些基本的概念,这为我们去看和改一些比较好的、公开的模型打下了基础。

参考文献

  • https://github.com/d2l-ai/d2l-zh/releases (V1.0.0)
  • https://github.com/d2l-ai/d2l-zh/releases (V2.0.0 alpha1)
  • https://d2l.ai/chapter_appendix-mathematics-for-deep-learning/information-theory.html



打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)
qrc_img

PS: 请尊重原创,不喜勿喷。

PS: 要转载请注明出处,本人版权所有。

PS: 有问题请留言,看到后我会第一时间回复。

这篇关于DL基础补全计划(二)---Softmax回归及示例(Pytorch,交叉熵损失)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1065758

相关文章

RedHat运维-Linux文本操作基础-AWK进阶

你不用整理,跟着敲一遍,有个印象,然后把它保存到本地,以后要用再去看,如果有了新东西,你自个再添加。这是我参考牛客上的shell编程专项题,只不过换成了问答的方式而已。不用背,就算是我自己亲自敲,我现在好多也记不住。 1. 输出nowcoder.txt文件第5行的内容 2. 输出nowcoder.txt文件第6行的内容 3. 输出nowcoder.txt文件第7行的内容 4. 输出nowcode

Vim使用基础篇

本文内容大部分来自 vimtutor,自带的教程的总结。在终端输入vimtutor 即可进入教程。 先总结一下,然后再分别介绍正常模式,插入模式,和可视模式三种模式下的命令。 目录 看完以后的汇总 1.正常模式(Normal模式) 1.移动光标 2.删除 3.【:】输入符 4.撤销 5.替换 6.重复命令【. ; ,】 7.复制粘贴 8.缩进 2.插入模式 INSERT

零基础STM32单片机编程入门(一)初识STM32单片机

文章目录 一.概要二.单片机型号命名规则三.STM32F103系统架构四.STM32F103C8T6单片机启动流程五.STM32F103C8T6单片机主要外设资源六.编程过程中芯片数据手册的作用1.单片机外设资源情况2.STM32单片机内部框图3.STM32单片机管脚图4.STM32单片机每个管脚可配功能5.单片机功耗数据6.FALSH编程时间,擦写次数7.I/O高低电平电压表格8.外设接口

ps基础入门

1.基础      1.1新建文件      1.2创建指定形状      1.4移动工具          1.41移动画布中的任意元素          1.42移动画布          1.43修改画布大小          1.44修改图像大小      1.5框选工具      1.6矩形工具      1.7图层          1.71图层颜色修改          1

基于CTPN(tensorflow)+CRNN(pytorch)+CTC的不定长文本检测和识别

转发来源:https://swift.ctolib.com/ooooverflow-chinese-ocr.html chinese-ocr 基于CTPN(tensorflow)+CRNN(pytorch)+CTC的不定长文本检测和识别 环境部署 sh setup.sh 使用环境: python 3.6 + tensorflow 1.10 +pytorch 0.4.1 注:CPU环境

[FPGA][基础模块]跨时钟域传播脉冲信号

clk_a 周期为10ns clk_b 周期为34ns 代码: module pulse(input clk_a,input clk_b,input signal_a,output reg signal_b);reg [4:0] signal_a_widen_maker = 0;reg signal_a_widen;always @(posedge clk_a)if(signal_a)

00 - React 基础

1. React 基础 安装react指令 可参考: 官网官网使用教程 如: npx create-react-app 项目名如:npx create-react-app react-redux-pro JSX JSX 是一种 JavaScript 的语法扩展,类似于 XML 或 HTML,允许我们在 JavaScript 代码中编写 HTML。 const element =

如何设置windows计划任务

如何设置windows计划任务 前言:在工作过程中写了一个python脚本,用于调用jira接口查询bug单数量,想要在本地定时任务执行,每天发送到钉钉群提醒,写下操作步骤用于记录。 1. 准备 Python 脚本 确保你的 Python 脚本已经保存到一个文件,比如 jira_reminder.py。 2. 创建批处理文件 为了方便任务计划程序运行 Python 脚本,创建一个批处理文

AI赋能天气:微软研究院发布首个大规模大气基础模型Aurora

编者按:气候变化日益加剧,高温、洪水、干旱,频率和强度不断增加的全球极端天气给整个人类社会都带来了难以估计的影响。这给现有的天气预测模型提出了更高的要求——这些模型要更准确地预测极端天气变化,为政府、企业和公众提供更可靠的信息,以便做出及时的准备和响应。为了应对这一挑战,微软研究院开发了首个大规模大气基础模型 Aurora,其超高的预测准确率、效率及计算速度,实现了目前最先进天气预测系统性能的显著

【软考】信息系统项目管理师(高项)备考笔记——信息系统项目管理基础

信息系统项目管理基础 日常笔记 项目的特点:临时性(一次性)、独特的产品、服务或成果、逐步完善、资源约束、目的性。 临时性是指每一个项目都有确定的开始和结束日期独特性,创造独特的可交付成果,如产品、服务或成果逐步完善意味着分步、连续的积累。例如,在项目早期,项目范围的说明是粗略的,随着项目团队对目标和可交付成果的理解更完整和深入时,项目的范围也就更具体和详细。 战略管理包括以下三个过程