深度学习框架
回顾
首先,我们来回顾一下前两节我们做了什么。
在第一节,我们建立了一个线性数据集,将其打包进行批量计算;通过加权求和的方式,建立了一个线性网络;使用平方误差作为损失函数;通过梯度下降的方式进行反向传播,使线性网络收敛。
在第二节,我们使用Mnist数据集,将其打包进行批量计算;使用了Softmax非线性网络;使用交叉熵作为损失函数;通过梯度下降的方式进行反向传播,使非线性网络收敛。
可以注意到,在两种方式中,我们的操作都有共同的成分,比如打开数据集、设置网络格式、选择误差函数、选择网络训练方法等。不可忽视的是,在前两节中,我们都采用手动求导的方法,进行梯度下降的操作,需要用户熟练掌握微积分。
但实际上,哪怕不是数学家或者信息学家,也能很好地使用深度学习,因为这些东西,深度学习框架都会自动帮我们处理,很多时候,我们需要做的,仅仅只是数据标注,以及超参数的调整而已,哪怕我们对模型的细节一无所知,也可以将其作为一个工具进行使用,这也是调参侠的由来。
下面,我们依照第二节,来了解一下MindSpore是如何为我们实现模型的训练的。
选择超参数
在这里,我们选择批处理量为256, 学习率为0.0001,训练轮次为10轮。
batch_size, lr, num_epochs = 256, 0.0001, 10
加载数据集
在上一节中,我们使用
mindvision.classification.dataset
中的
Mnist
可以获取Mnist的数据集。同时,它也可以直接作为MindSpore框架的数据集,使用
dataset_train = Mnist(path='/shareData/mindspore-dataset/Mnist', split="train",
batch_size=batch_size, repeat_num=1, shuffle=True,
download=False, resize=28).run()
dataset_test = Mnist(path='/shareData/mindspore-dataset/Mnist', split="test",
batch_size=batch_size, repeat_num=1, shuffle=True,
download=False, resize=28).run()
即可将其作为MindSpore的标准数据集输入。
但如果我们要使用我们自己定义的方式加载数据,则需要进行一定的处理。
比如说我们要使用第二节自定义的
load_mnist
函数,首先要定义一个迭代器来输出数据与标签的值
def synthetic_data(features, labels):
for i in range(len(labels)):
yield features[i]/255, labels[i]
要自定义数据集,我们需要引用
mindspore.dataset
import mindspore.dataset as ds
下面的代码,第二行,我们使用了上述的迭代器;第三行,利用
mindspore.dataset.GeneratorDataset
来建立MindSpore可接受的数据集;第四行与第五行,我们对其进行格式转换;第七行,我们以一个batchsize的大小将其打包。
def data_iter(features, labels, batch_size):
data_gen = synthetic_data(features, labels)
data = ds.GeneratorDataset(tuple(data_gen), ['image', 'label'], shuffle=True)
data = data.map(operations=[TypeCast(ms.dtype.float32)])
data = data.map(operations=[TypeCast(ms.dtype.int32)],
input_columns='label')
data = data.batch(batch_size)
return data
使用下面的代码,即可将我们自建的数据集转换为MindSpore的格式。
features_t, labels_t = load_mnist('/shareData/mindspore-dataset/Mnist/train')
features_v, labels_v = load_mnist('/shareData/mindspore-dataset/Mnist/test', split='t10k')
dataset_train = data_iter(features_t, labels_t, batch_size)
dataset_valid = data_iter(features_v, labels_v, batch_size)
设置网络
在MindSpore中,我们的线性网络是
nn.Dense
(其实是全连接层),其计算方式为
y
=
a
c
t
i
v
a
t
i
o
n
(
W
X
T
+
B
)
y = activation(WX^T+B)
y
=
a
c
t
i
v
a
t
i
o
n
(
W
X
T
+
B
)
其中,activation可以是线性的,可以是非线性的,默认为线性的激活函数。
在官方的
文档
中,可以看到函数的说明:
mindspore.nn.Dense(in_channels, out_channels, weight_init='normal', bias_init='zeros', has_bias=True, activation=None)
其中,in_channels为输入的通道数,out_channels为输出的通道数,weight_init权重为初始化的方式(默认为高斯分布),bias_init为偏置的初始化方式(默认为0),has_bias为是否设置偏置,actitvation是上述提到的激活函数。
如果我们使用官方的数据集,可以使用下面的方式建立神经网络。
net = nn.SequentialCell(
nn.Flatten(),
nn.Dense(28*28, 10),
)
使用我们自定义的数据集,只需要下面的方式即可。
net = nn.Dense(28*28, 10)
设置损失函数
设置Softmax输出层的交叉熵损失函数非常简单,只需要一句话:
loss = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')
设置优化器
现在,我们不需要手动求导来获得梯度了。我们可以直接使用SGD优化器,可以自动进行优化。
opti = nn.SGD(net.trainable_params(), learning_rate=lr)
回调函数
回调函数是在训练过程中会被调用的函数,在之后的章节中会详细说明(下面的函数在util.callback文件中)。
def get_accuracy(network, datasets):
metric = nn.Accuracy('classification')
for x, y in datasets:
y_hat = network(x)
metric.update(y_hat, y)
return metric.eval()
def get_loss(network, loss_f, dataset):
loss = 0
for x, y in dataset:
y_hat = network(x)
loss += loss_f(y_hat, y)
return loss
class AccuracyMonitor(ms.Callback):
def __init__(self, valid_data):
self.valid_data = valid_data
def on_train_epoch_end(self, run_context):
"""Called after each epoch finished."""
callback_params = run_context.original_args()
cur_epoch_num = callback_params.cur_epoch_num
epoch_num = callback_params.epoch_num
network = callback_params.network
train_data = callback_params.train_dataset
train_accu = get_accuracy(network, train_data)
valid_accu = get_accuracy(network, self.valid_data)
loss = get_loss(network, callback_params.loss_fn, train_data)
loss /= callback_params.batch_num
print(f'epoch:[{cur_epoch_num}/{epoch_num}] Loss:{loss} Train Accuracy:{train_accu} Valid Accuracy:{valid_accu}')
训练模型
在MindSpore提供模型训练的高阶API,使用以下的短短两行代码即可进行训练。
model = ms.Model(net, loss_fn=loss, optimizer=opti, metrics={'acc'})
model.train(num_epochs, dataset_train, callbacks=[AccuracyMonitor(dataset_valid)])
输出结果
epoch:[1/10] Loss:2.2774138 Train Accuracy:0.14748333333333333 Valid Accuracy:0.155
epoch:[2/10] Loss:2.252627 Train Accuracy:0.19975 Valid Accuracy:0.2083
epoch:[3/10] Loss:2.2284462 Train Accuracy:0.2749666666666667 Valid Accuracy:0.2841
epoch:[4/10] Loss:2.2046874 Train Accuracy:0.3722666666666667 Valid Accuracy:0.3815
epoch:[5/10] Loss:2.1814284 Train Accuracy:0.46326666666666666 Valid Accuracy:0.4711
epoch:[6/10] Loss:2.1586423 Train Accuracy:0.5326166666666666 Valid Accuracy:0.5395
epoch:[7/10] Loss:2.136246 Train Accuracy:0.58175 Valid Accuracy:0.588
epoch:[8/10] Loss:2.11424 Train Accuracy:0.6151666666666666 Valid Accuracy:0.6216
epoch:[9/10] Loss:2.0927134 Train Accuracy:0.6402666666666667 Valid Accuracy:0.6477
epoch:[10/10] Loss:2.071551 Train Accuracy:0.6594166666666667 Valid Accuracy:0.669