背景说明
本人java程序员,在接触这个课程之前,在B站学习过李沐的深度学习课程视频,对于深度学习的概念有简单的认识,但是对于其原理并不了解,也不知道如何解决实际问题。
学习目的
我参与这个课程是因为工作中遇到了自然语言分类方面的需求,希望能在这次课程中找到解决思路及方法。
学习内容
快速入门
快速入门使用MNIST_Data数据集,在MindSpore训练,实现一个简单的图片分类问题。
环境说明
使用昇思平台的Jupyter云上开发环境,已默认安装好mindspore(2.3.0rc1)、numpy、pandas等依赖。
import mindspore
import time
from mindspore import nn
from mindspore.dataset import vision, transforms
from mindspore.dataset import MnistDataset
处理数据集
# Download data from open datasets
from download import download
url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/" \
"notebook/datasets/MNIST_Data.zip"
path = download(url, "./", kind="zip", replace=True)
#数据下载完成后,获得数据集对象
train_dataset = MnistDataset('MNIST_Data/train')
test_dataset = MnistDataset('MNIST_Data/test')
#MindSpore的dataset使用数据处理流水线(Data Processing Pipeline),需指定map、batch、shuffle等操作。这里我们使用map对图像数据及标签进行变换处理,然后将处理好的数据集打包为大小为64的batch。
def datapipe(dataset, batch_size):
image_transforms = [
vision.Rescale(1.0 / 255.0, 0),
vision.Normalize(mean=(0.1307,), std=(0.3081,)),
vision.HWC2CHW()
]
label_transform = transforms.TypeCast(mindspore.int32)
dataset = dataset.map(image_transforms, 'image')
dataset = dataset.map(label_transform, 'label')
dataset = dataset.batch(batch_size)
return dataset
# Map vision transforms and batch dataset
train_dataset = datapipe(train_dataset, 64)
test_dataset = datapipe(test_dataset, 64)
#可使用create_tuple_iterator 或create_dict_iterator对数据集进行迭代访问,查看数据和标签的shape和datatype。
for image, label in test_dataset.create_tuple_iterator():
print(f"Shape of image [N, C, H, W]: {image.shape} {image.dtype}")
print(f"Shape of label: {label.shape} {label.dtype}")
break
for data in test_dataset.create_dict_iterator():
print(f"Shape of image [N, C, H, W]: {data['image'].shape} {data['image'].dtype}")
print(f"Shape of label: {data['label'].shape} {data['label'].dtype}")
break
数据处理环节时不太明白的地方,我的理解是image_transforms 方法对图像进行缩放(Rescale),特征缩放归一化(Normalize),转换图像格式(HWC2CHW)
其中
Rescale:用于调整图像大小,参数:rescale:缩放因子,shift:平移因子
Normalize:特征缩放,也叫归一化 参数为 mean:图像每个通道的均值,std:图像每个通道的标准差,还有一个默认参数是is_hwc=True表示 输入图像的格式。True为(height, width, channel),这里没有传,在下一步设置
HWC2CHW:转换图像格式 将图像格式的(height, width, channel) 转为 (channel, height, width)
网络构建
# Define model
class Network(nn.Cell):
def __init__(self):
super().__init__()
self.flatten = nn.Flatten()
self.dense_relu_sequential = nn.SequentialCell(
nn.Dense(28*28, 512),
nn.ReLU(),
nn.Dense(512, 512),
nn.ReLU(),
nn.Dense(512, 10)
)
def construct(self, x):
x = self.flatten(x)
logits = self.dense_relu_sequential(x)
return logits
model = Network()
print(model)
自定义网络需要继承nn.Cell
类,并重写__init__
方法和construct
方法。__init__
包含所有网络层的定义,construct
中包含数据(Tensor)的变换过程。
self.flatten = nn.Flatten()
将张量维度范围展平为张量,这里就是将图像的 H,W维度铺平,保留C维度。
nn.SequentialCell()
用于按照顺序组合神经网络层,以构建神经网络模型
nn.Dense(28*28, 512)
全连接层,类似PyTorch 中的线性层(也称为全连接层Linear),用于构建神经网络中的线性变换。线性层将输入张量与权重矩阵相乘,然后添加偏置(偏移量)来产生输出。这是深度学习中最常见的一种层类型之一,通常用于将输入数据映射到输出数据。参数为
in_features:输入特征的数量。这是输入张量的最后一个维度大小。
out_features:输出特征的数量。这是线性层产生的输出张量的最后一个维度大小。
bias:一个布尔值,指示是否应该包括偏置项(偏移量)。如果设置为 True,线性层将添加一个与输出特征数量相等的偏置向量;如果设置为 False,不会添加偏置。
这里是将通过nn.Flatten()转换的图片(3,28*28)转换为 (3,512)的张量
nn.ReLU
激活函数层,用于引入非线性性质到神经网络中,表达式为
f(x) = max(0, x)
它的作用是将输入值 x 大于等于 0 的部分保持不变,小于 0 的部分置为零。这种操作引入了非线性性质,有助于神经网络学习复杂的非线性关系。
剩余层的演示
模型训练
在模型训练中,一个完整的训练过程(step)需要实现以下三步:
- 正向计算:模型预测结果(logits),并与正确标签(label)求预测损失(loss)。
- 反向传播:利用自动微分机制,自动求模型参数(parameters)对于loss的梯度(gradients)。
- 参数优化:将梯度更新到参数上。
MindSpore使用函数式自动微分机制,因此针对上述步骤需要实现:
- 定义正向计算函数。
- 使用value_and_grad通过函数变换获得梯度计算函数。
- 定义训练函数,使用set_train设置为训练模式,执行正向计算、反向传播和参数优化。
# Instantiate loss function and optimizer
loss_fn = nn.CrossEntropyLoss()
optimizer = nn.SGD(model.trainable_params(), 1e-2)
# 1. Define forward function
def forward_fn(data, label):
logits = model(data)
loss = loss_fn(logits, label)
return loss, logits
# 2. Get gradient function
grad_fn = mindspore.value_and_grad(forward_fn, None, optimizer.parameters, has_aux=True)
# 3. Define function of one-step training
def train_step(data, label):
(loss, _), grads = grad_fn(data, label)
optimizer(grads)
return loss
def train(model, dataset):
size = dataset.get_dataset_size()
model.set_train()
for batch, (data, label) in enumerate(dataset.create_tuple_iterator()):
loss = train_step(data, label)
if batch % 100 == 0:
loss, current = loss.asnumpy(), batch
print(f"loss: {loss:>7f} [{current:>3d}/{size:>3d}]")
损失函数nn.CrossEntropyLoss
用于多分类问题的一种损失函数,特别适用于输出层是softmax激活函数后的分类任务。它结合了softmax函数和交叉熵损失(Cross-Entropy Loss)的操作,简化了模型训练过程中的计算步骤和代码实现。
基本概念:
交叉熵损失(Cross-Entropy Loss)源于信息论中的熵概念,用于衡量两个概率分布之间的差异。在机器学习和深度学习中,它用来量化模型预测的概率分布与真实标签分布之间的差距。
softmax函数:在多分类问题中,softmax函数将模型的线性输出(logits)转换为一个概率分布,确保所有类别的概率和为1。softmax函数的输出可以用作模型预测的概率分布。
nn.CrossEntropyLoss的工作方式:
PyTorch中的nn.CrossEntropyLoss接收两个输入:
input:模型的原始输出(logits),通常是未经过softmax激活的张量。
target:真实的一维标签张量,包含了每个样本所属类别的索引,通常采用LongTensor类型。
内部处理流程:
对于每个样本,首先计算其对应的softmax概率分布。
然后,根据真实标签计算交叉熵损失。损失是对每个样本的损失值进行平均得到的,如果没有特殊指定,损失默认会在批次(batch)层面求平均。
损失函数计算公式:
对于单个样本,交叉熵损失是 -∑(yi * log(pi)),其中 yi 是实际标签的one-hot编码(在实际情况中,由于标签是索引形式,nn.CrossEntropyLoss内部会处理one-hot编码),pi 是模型预测的该类别概率。
对于整个批次,损失则是各样本损失的平均。
nn.SGD
随机梯度下降是一种简单但非常有效的方法,多用于支持向量机,逻辑回归(LR)等凸损失函数下的线性分类器的学习。并且SGD已成功应用于文本分类和自然语言处理中经常遇到的大规模和稀疏机器学习问题。
SGD既可以用于分类计算,也可以用于回归计算。
SGD算法是从样本中随机抽出一组,训练后按梯度更新一次,然后再抽取一组,再更新一次,在样本量及其大的情况下,可能不用训练完所有的样本就可以获得一个损失值在可接受范围之内的模型了。
mindspore.value_and_grad
生成求导函数,用于计算给定函数的正向计算结果和梯度。
mindspore.value_and_grad(fn, grad_position=0,
weights=None, has_aux=False, return_ids=False)
函数求导包含以下三种场景:
-
对输入求导,此时 grad_position 非None,而 weights 是None;
-
对网络变量求导,此时 grad_position 是None,而 weights 非None;
-
同时对输入和网络变量求导,此时 grad_position 和 weights 都非None。
参数:
-
fn (Union[Cell, Function]) - 待求导的函数或网络。
-
grad_position (Union[NoneType, int, tuple[int]]) - 指定求导输入位置的索引。若为int类型,表示对单个输入求导;若为tuple类型,表示对tuple内索引的位置求导,其中索引从0开始;若是None,表示不对输入求导,这种场景下, weights 非None。默认值:
0
。 -
weights (Union[ParameterTuple, Parameter, list[Parameter]]) - 训练网络中需要返回梯度的网络变量。一般可通过 weights = net.trainable_params() 获取。默认值:
None
。 -
has_aux (bool) - 是否返回辅助参数的标志。若为
True
, fn 输出数量必须超过一个,其中只有 fn 第一个输出参与求导,其他输出值将直接返回。默认值:False
。 -
return_ids (bool) - 是否返回由返回的梯度和指定求导输入位置的索引或网络变量组成的tuple。若为
True
,其输出中所有的梯度值将被替换为:由该梯度和其输入的位置索引,或者用于计算该梯度的网络变量组成的tuple。默认值:False
。
返回:
Function,用于计算给定函数的梯度的求导函数。例如 out1, out2 = fn(*args) ,梯度函数将返回 ((out1, out2), gradient) 形式的结果, 若 has_aux 为True,那么 out2 不参与求导。 若return_ids为 True
,梯度函数返回的 gradient 将被替代为由返回的梯度和指定求导输入位置的索引或网络变量组成的tuple。
#定义测试函数,用来评估模型的性能。
def test(model, dataset, loss_fn):
num_batches = dataset.get_dataset_size()
model.set_train(False)
total, test_loss, correct = 0, 0, 0
for data, label in dataset.create_tuple_iterator():
pred = model(data)
total += len(data)
test_loss += loss_fn(pred, label).asnumpy()
correct += (pred.argmax(1) == label).asnumpy().sum()
test_loss /= num_batches
correct /= total
print(f"Test: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")
pred = model(data): 预测的值,长度为10的张量
correct += (pred.argmax(1) == label).asnumpy().sum() :获取预测的最大值是否等于标签值,并相加。
打卡记录