机器学习正则化
这段代码实现了一个深度学习实验,目的是使用不同的正则化技术(包括dropout、批量归一化、L2正则化以及早期停止策略)来训练神经网络模型,以拟合一个带有噪声的余弦波形数据集。代码使用MindSpore框架进行编写,下面是对代码的详细解析:
数据预处理
- 首先,生成了一组余弦波形的数据作为训练、测试和验证集,并在训练数据上加入了高斯噪声。
- 数据被归一化处理,并且通过
sample_idx
函数随机打乱并分割成训练集、测试集和验证集。
网络模型定义 (CosineNet
)
- 定义了一个名为
CosineNet
的神经网络模型,该模型包含多个隐藏层,每层由卷积层(代替全连接层以兼容后续的批量归一化)、可选的批量归一化层、可选的dropout层和激活函数组成。 - 输出层使用一个全连接层(
Dense
)来产生预测值。 - 注意,尽管处理的是1D数据,但为了兼容MindSpore中批量归一化和dropout等操作,数据被重塑为四维张量(N, C, H, W),其中H和W均为1。
训练配置
- 定义了超参数,包括学习率、迭代次数、批量大小、隐藏层神经元数等。
- 定义了一个构建函数
build_fn
,用于根据传入的参数(是否使用批量归一化、dropout以及L2正则化)创建不同的神经网络模型及其对应的损失函数和优化器。
训练过程
- 使用
build_fn
函数创建了五个不同配置的模型实例,分别对应无正则化、dropout、批量归一化、L2正则化和早期停止。 - 在主循环中,对于每次迭代:
- 随机选择一个小批量数据进行训练。
- 对于所有模型,执行训练步骤,除了在早期停止策略下,当满足条件时会停止训练。
- 每隔20次迭代,计算所有模型在测试集上的损失,并绘制模型预测曲线与实际数据对比,以直观展示各种正则化技术的效果。
- 对于采用早期停止策略的模型,监控其在验证集上的表现,若连续
MAX_COUNT
轮损失没有降低,则停止训练。
结果可视化
- 利用matplotlib库实时绘制模型的学习曲线和预测结果,直观比较不同正则化策略下的模型性能,包括过拟合模型、L2正则化模型、采用dropout和批量归一化的模型,以及应用了早期停止策略的模型。
综上所述,这段代码是一个完整的实验流程,旨在通过实践比较不同正则化技术对模型泛化能力的影响,特别关注于如何减少过拟合现象,提升模型在未见过数据上的表现。
WEIGHT_DECAY = 1e-4
这一行代码定义了一个超参数,称为权重衰减(Weight Decay)系数,它通常与L2正则化相关联。L2正则化是一种防止模型过拟合的技术,通过在损失函数中加入模型参数的平方和,从而鼓励模型学习到较小的权重值,避免权重过度增长,提升模型的泛化能力。
数学上,L2正则化项可以表示为所有模型参数 ( w ) 的平方和与一个正则化强度因子(在这里即 WEIGHT_DECAY
)的乘积,添加到原始损失函数中。因此,如果原始损失函数是 ( L ),那么加上L2正则化后的损失函数变为 ( L + \frac{1}{2} \lambda \sum{w_i^2} ),其中 ( \lambda ) 是正则化强度(本例中的 1e-4
)。
这里的值 1e-4
(即 (10^{-4}) 或者0.0001)是一个相对常见的初始设置,用于很多深度学习任务中的L2惩罚系数。选择这个数值需要根据具体问题和数据集调整,过大的 WEIGHT_DECAY
可能会导致模型欠拟合,而过小可能无法有效缓解过拟合。实践中,该值通常通过交叉验证等方法来确定最优值。
import random
import numpy as np
import matplotlib.pyplot as plt
import mindspore as ms
from mindspore import nn
from mindspore import context
from mindspore.common.initializer import Normal
from IPython.display import clear_output
%matplotlib inline
set execution mode and device platform.
context.set_context(mode=context.GRAPH_MODE, device_target='Ascend')
```
设置超参和全局变量:
```python
N_SAMPLES = 40 # number of samples
BATCH_SIZE = 40
NOISE_RATE = 0.2
INPUT_DIM = 1
HIDDEN_DIM = 100 # number of units in one hidden layer
OUTPUT_DIM = 1
N_LAYERS = 6 # number of hidden layers
ITERATION = 1500
LEARNING_RATE = 0.003
DROPOUT_RATE = 0.7
WEIGHT_DECAY = 1e-4 # coefficient of L2 penalty
MAX_COUNT = 20 # max count that loss does not decrease. Used for early stop.
ACTIVATION = nn.LeakyReLU # activation function
#固定结果
def fix_seed(seed=1):
reproducible
random.seed(seed)
np.random.seed(seed)
小批量样本索引
def sample_idx(m, n):
A = np.random.permutation(m)
idx = A[:n]
return idx
这个函数 sample_idx
的目的是从一个范围为 0
到 m-1
的整数序列中,随机抽取 n
个不重复的索引值。它主要用于实现随机小批量(Mini-Batch)采样,这是机器学习和深度学习中常用的一种训练策略。下面详细解释并举例说明:
函数解释
-
函数签名 :
def sample_idx(m, n):
m
: 表示总数,即你想要从多少个元素中进行抽样。在深度学习的上下文中,这通常指的是你的数据集中样本的总数。n
: 表示每个批次(或小批量)中想要抽取的样本数。
-
函数内部:
A = np.random.permutation(m)
: 这行代码使用NumPy的permutation
函数生成一个从0到m-1
的随机排列数组。这意味着原有序列中的每个数字都会被打乱顺序,但每个数字仍然只出现一次,保证了抽取的随机性和公平性。idx = A[:n]
: 从打乱后的数组A
中截取前n
个元素作为抽样结果。这样就得到了一个随机选择且不重复的索引列表,可以用来索引原始数据集中的小批量样本。
举例说明
假设你有一个包含100个样本的数据集,你想从中每次抽取10个样本进行训练,可以这样调用函数:
python
m = 100 # 数据集样本总数
n = 10 # 每个批次的样本数
indices = sample_idx(m, n)
调用这个函数后,indices
可能返回如下结果(每次调用结果随机):
python
[34, 56, 78, 12, 45, 67, 89, 23, 5, 99]
这意味着在本次迭代中,模型将会使用索引为34、56、78、12、45、67、89、23、5、99的10个样本进行训练。下一次调用该函数时,由于使用了随机排列,返回的索引列表将会不同,从而实现了数据在不同批次间随机且均匀的采样,有助于模型训练的稳定性和泛化能力。
fix_seed(5)
data_x = np.linspace(-1, 1, num=int(N_SAMPLES2.5))[:, np.newaxis]
data_y = np.cos(np.pi data_x)
p = np.random.permutation(len(data_x))
train_x, train_y = data_x[p[0:N_SAMPLES]], data_y[p[0:N_SAMPLES]]
test_x, test_y = data_x[p[N_SAMPLES:N_SAMPLES2]], data_y[p[N_SAMPLES:N_SAMPLES 2]]
validate_x, validate_y = data_x[p[N_SAMPLES2:]], data_y[p[N_SAMPLES2:]]
noise = np.random.normal(0, NOISE_RATE, train_y.shape)
train_y += noise
这段代码涉及到了几个关键步骤来准备数据,用于机器学习或统计分析中,特别是与监督学习相关的任务。让我们分步解析每一部分的含义,并通过一个简单的示例来说明整个过程。
代码解析
-
Fix Seed :
fix_seed(5)
这行代码意在设置随机数生成器的种子,确保每次运行程序时都能得到相同的结果。这对于实验的可复现性非常重要。不过,在Python中,正确的函数调用应该是np.random.seed(5)
,而非fix_seed(5)
。 -
Data Generation:
pythondata_x = np.linspace(-1, 1, num=int(N_SAMPLES*2.5))[:, np.newaxis]
这行代码创建了一个一维数组
data_x
,它包含了从-1
到1
范围内的等间隔数值,总数量为N_SAMPLES * 2.5
(其中N_SAMPLES
是一个预先定义的变量,代表基本样本数量)。使用[:, np.newaxis]
将其转换为列向量,方便后续操作。例如,如果N_SAMPLES=10
,则会生成 25 个点。 -
Target Function Application:
pythondata_y = np.cos(np.pi*data_x)
这里,对
data_x
中的每个值应用了函数y = cos(πx)
,生成对应的data_y
数组。这意味着我们正在创建一个基于余弦波形的数据集,常用于演示回归任务中的数据生成过程。 -
Permutation:
pythonp = np.random.permutation(len(data_x))
这行代码生成了一个与
data_x
长度相同的随机排列序列p
。它通过打乱索引来重新排序数据,不改变数据本身的值,而是改变其顺序。这一步骤经常用于数据集的随机化处理,特别是在交叉验证或者创建随机批次数据时。
示例说明
假设 N_SAMPLES = 10
,那么:
-
Step 1 : 通过
np.linspace(-1, 1, num=int(N_SAMPLES*2.5))
,我们会得到一个包含25个点的数组,这些点均匀分布在-1到1之间。 -
Step 2: 应用余弦函数后,每个x值对应一个y值,形成了一组基于余弦波形的数据点。
-
Step 3 : 执行随机排列后,比如
p = [17, 4, 24, 10, 2, 19, 15, 7, 12, 22, 1, 20, 14, 21, 3, 16, 9, 6, 11, 13, 18, 5, 8, 23, 0]
,意味着原始数据点的顺序被随机重排,但并不改变数据点的实际值,仅改变它们在数据集中的位置顺序。
综上,这段代码首先生成了一个基于余弦函数的数据集,然后对数据点的顺序进行了随机化处理,这样的数据集可用于多种目的,如评估模型对不同分布数据的泛化能力、训练集和验证集的划分等。
class CosineNet(nn.Cell):
def init (self, batchnorm, dropout):
super(CosineNet, self).init ()
layers = []
if batchnorm:
layers.append(nn.BatchNorm2d(INPUT_DIM))
# initialize hidden layers
for l_n in range(N_LAYERS):
in_channels = HIDDEN_DIM if l_n > 0 else INPUT_DIM
# Use 1x1Conv instead of Dense, which coordinate better with BatchNorm2d opetator;
conv = nn.Conv2d(in_channels, HIDDEN_DIM, kernel_size=1, pad_mode='valid', has_bias=True, weight_init=Normal(0.01))
layers.append(conv)
if batchnorm:
layers.append(nn.BatchNorm2d(HIDDEN_DIM))
if dropout:
layers.append(nn.Dropout(DROPOUT_RATE))
layers.append(ACTIVATION())
self.layers = nn.SequentialCell(layers)
# initialize output layers
self.flatten = nn.Flatten() # convert 4-dim tensor (N,C,H,W) to 2-dim tensor(N,C*H*W)
self.fc = nn.Dense(HIDDEN_DIM, OUTPUT_DIM, weight_init=Normal(0.1), bias_init='zeros')
def construct(self, x):
# construct hidden layers
x = self.layers(x)
# construct output layers
x = self.flatten(x)
x = self.fc(x)
return x
这段代码定义了一个名为 CosineNet
的神经网络类,该类继承自 nn.Cell
,这是MindSpore框架中的基本单元用于构建神经网络模型。这个网络设计用于处理二维图像数据,并采用了基于余弦相似度的激活函数(尽管实际激活函数未明确指定,但从命名习惯ACTIVATION()
推测可能是某种非线性变换,如ReLU等),同时整合了批量归一化(BatchNorm)、dropout策略以及卷积层来提高模型的泛化能力和训练稳定性。下面是该网络结构的详细解析:
初始化方法 (__init__
)
-
批量归一化与Dropout设置:
batchnorm
参数决定是否在每层之后添加批量归一化层,这有助于加速训练过程并减少过拟合。dropout
参数控制是否应用dropout层以防止过拟合,随机"丢弃"一部分神经元输出以增强模型的泛化能力。
-
隐藏层初始化:
- 网络包含
N_LAYERS
个隐藏层,每一层使用1x1
卷积核的卷积层代替全连接层(Dense层),这是因为对于每个通道的操作类似于全连接层,但更便于与后续的批量归一化层配合。 - 每层包含的组件顺序为:卷积层 -> (可选的批量归一化)-> (可选的dropout)-> 激活函数。
- 卷积层的权重采用正态分布初始化,偏置如果有则是默认初始化或特定值初始化。
- 网络包含
-
输出层初始化:
- 使用
Flatten
层将卷积层输出的四维张量转换为二维,以便输入到全连接层。 - 全连接层(
Dense
)用于最后的分类或回归任务,其权重初始化为正态分布,偏置初始化为零。
- 使用
构建函数 (construct
)
-
隐藏层计算 :输入
x
首先经过由多个卷积层、可能的批量归一化层和dropout层组成的序列处理。 -
输出层计算 :处理后的特征图通过
Flatten
层展平,然后送入全连接层(fc
)进行最终的输出预测。
总结
CosineNet
是一个结构相对简单的卷积神经网络模型,通过堆叠具有相同隐藏维度的 1x1
卷积层,结合批量归一化和dropout技术,旨在处理二维图像数据并进行分类或回归预测。模型的设计考虑到了效率和泛化性的平衡,利用卷积操作处理空间特征,而通过灵活的配置选项(如是否启用批量归一化和dropout)适应不同任务需求。
def build_fn(batchnorm, dropout, l2):
initilize the net, Loss, optimizer
net = CosineNet(batchnorm=batchnorm, dropout=dropout)
loss = nn.loss.MSELoss()
opt = nn.optim.Adam(net.trainable_params(), learning_rate=LEARNING_RATE, weight_decay=WEIGHT_DECAY if l2 else 0.0)
Build a net with loss and optimizer.
with_loss = nn.WithLossCell(net, loss)
train_step = nn.TrainOneStepCell(with_loss, opt).set_train()
return train_step, with_loss, net
在提供的 build_fn
函数中,l2
参数体现在模型优化器的权重衰减(weight decay)设置上。具体来说,当调用 nn.optim.Adam
创建优化器时,参数 weight_decay=WEIGHT_DECAY if l2 else 0.0
决定了是否在优化过程中应用L2正则化。
- 如果
l2=True
,则weight_decay
参数被设置为WEIGHT_DECAY
的值(在这个案例中是1e-4
),意味着在优化过程中会加入L2正则化项,即对网络权重参数施加一个与权重平方成正比的惩罚项,以此来约束权重大小,避免模型过拟合。 - 如果
l2=False
,则weight_decay
参数被设置为0.0
,表示不应用L2正则化。
结合 CosineNet
网络来看,l2
参数通过这种方式间接地影响网络训练过程:当启用时,它促使网络学习到更平滑、更稀疏的权重分布,有助于提升模型的泛化能力;而不启用时,则不对网络权重施加额外的约束,模型可能会学习到更为复杂的特征表达,但也可能更容易过拟合数据。因此,l2
参数的选择是控制模型复杂度和泛化能力的一个重要策略。
Build 5 different training jobs.
fc_train, fc_loss, fc_predict = build_fn(batchnorm=False, dropout=False, l2=False) # 默认任务
dropout_train, dropout_loss, dropout_predict = build_fn(batchnorm=False, dropout=True, l2=False) # 实验dropout功能
bn_train, bn_loss, bn_predict = build_fn(batchnorm=True, dropout=False, l2=False) # 实验batchnorm功能
l2_train, l2_loss, l2_predict = build_fn(batchnorm=False, dropout=False, l2=True) # 实验l2 regularization功能
early_stop_train, early_stop_loss, early_stop_predict = build_fn(batchnorm=False, dropout=False, l2=False) # 实验Early Stop功能
Used for batchnorm, dropout and other operators to determine whether the net is in the train state or not.
nets_train = [fc_train, dropout_train, bn_train, l2_train, early_stop_train]
nets_loss = [fc_loss, dropout_loss, bn_loss, l2_loss, early_stop_loss]
nets_predict = [fc_predict, dropout_predict, bn_predict, l2_predict, early_stop_predict]
这段代码通过调用 build_fn
函数五次,创建了五个具有不同配置的训练任务,每个任务在正则化策略、批量归一化和dropout方面有所不同。下面是这些任务的区别和各自特点的解析:
-
默认任务 (
fc_train, fc_loss, fc_predict
):- 配置 : 不使用批量归一化 (
batchnorm=False
),不使用dropout (dropout=False
),也不使用L2正则化 (l2=False
)。 - 特点: 这是最基础的配置,没有额外的正则化手段,仅使用Adam优化器进行训练,适用于作为基线模型比较其他策略的效果。
- 配置 : 不使用批量归一化 (
-
实验 Dropout 功能 (
dropout_train, dropout_loss, dropout_predict
):- 配置 : 使用dropout (
dropout=True
),其他配置不变。 - 特点: 在网络的每一层后面添加了dropout层,以随机"关闭"一部分神经元,减少模型对训练数据的依赖,有助于防止过拟合。这是一种常用的正则化技术。
- 配置 : 使用dropout (
-
实验 BatchNorm 功能 (
bn_train, bn_loss, bn_predict
):- 配置 : 使用批量归一化 (
batchnorm=True
),其他配置不变。 - 特点: 在每个隐藏层后添加了批量归一化层,用于标准化网络输入,加速训练过程,提供一定的正则化效果,减少训练时间并提高模型的泛化能力。
- 配置 : 使用批量归一化 (
-
实验 L2 Regularization 功能 (
l2_train, l2_loss, l2_predict
):- 配置 : 使用L2正则化 (
l2=True
),其他配置不变。 - 特点 : 通过在优化器中设置权重衰减 (
weight_decay=WEIGHT_DECAY
) 来实现L2正则化,促使模型学习到更平滑的权重分布,减少模型复杂度,防止过拟合。
- 配置 : 使用L2正则化 (
-
实验 Early Stop 功能 (
early_stop_train, early_stop_loss, early_stop_predict
):- 配置 : 此处的
early_stop_train
本身不直接体现早停策略,因为早停策略的逻辑在训练循环外部实现(通过count
和min_val_loss
变量),而非通过修改模型或优化器的配置。这里的配置与默认任务相同,但计划在外部循环中根据验证集的表现决定何时停止训练。 - 特点 : 虽然配置上没有直接体现早停,但根据外部循环中的逻辑,此任务会在训练过程中监控验证集的损失,当验证损失连续若干轮没有改善时(由
MAX_COUNT
控制),则停止训练,以避免过拟合。
- 配置 : 此处的
总的来说,这五个任务分别探索了不同的正则化策略和技术对模型训练效果的影响,从不同的角度提高了模型的泛化能力和训练效率。
def set_train(nets, mode=True):
for net in nets:
net.set_train(mode=mode)
```python
Convert the tensor shape from (N, C) to (N, C, H, W).
data_xt, data_yt = ms.Tensor(data_x.reshape(data_x.shape + (1, 1)), ms.float32), ms.Tensor(data_y, ms.float32)
test_xt, test_yt = ms.Tensor(test_x.reshape(test_x.shape + (1, 1)), ms.float32), ms.Tensor(test_y, ms.float32)
validate_xt, validate_yt = ms.Tensor(validate_x.reshape(validate_x.shape + (1, 1)), ms.float32), ms.Tensor(validate_y, ms.float32)
```
这两段代码分别实现了两个功能:一个是设置网络训练模式的通用函数,另一个是将数据从二维转换为四维张量,以适配模型的输入要求。
1. set_train
函数
python
def set_train(nets, mode=True):
for net in nets:
net.set_train(mode=mode)
- 功能 :此函数遍历给定的网络列表
nets
,并使用set_train
方法设置每个网络的训练模式。mode=True
表示设置网络为训练模式,而mode=False
则设置为推理(评估)模式。在训练模式下,某些层(如Dropout、BatchNorm)的行为会有所不同,以支持学习过程中的特性如权重更新、随机失活等。而在推理模式下,这些层会表现得更加确定性,以提供一致的输出结果。
2. 数据转换
python
data_xt, data_yt = ms.Tensor(data_x.reshape(data_x.shape + (1, 1)), ms.float32), ms.Tensor(data_y, ms.float32)
test_xt, test_yt = ms.Tensor(test_x.reshape(test_x.shape + (1, 1)), ms.float32), ms.Tensor(test_y, ms.float32)
validate_xt, validate_yt = ms.Tensor(validate_x.reshape(validate_x.shape + (1, 1)), ms.float32), ms.Tensor(validate_y, ms.float32)
-
目的 :这段代码将原始的数据(
data_x
,test_x
,validate_x
)从二维(通常是形状为(样本数, 特征数)
)转换为四维张量,形状变为(样本数, 通道数, 高度, 宽度)
,这里特指(N, C, H, W)
。对于一维的数据(如本例中的余弦波形数据),高度H
和宽度W
都被设置为1
,以满足某些模型(如CosineNet
)的输入要求,该模型可能设计为处理四维输入,即使实际上是在处理一维特征。 -
具体操作:
- 对于每个数据集(训练集
data_x
, 测试集test_x
, 验证集validate_x
),通过.reshape(data_x.shape + (1, 1))
将数据形状从(样本数, 特征数)
转换为(样本数, 特征数, 1, 1)
,增加额外的维度以匹配模型输入的期望格式。 - 然后,将转换后的数据以及原始的目标变量(如
data_y
,test_y
,validate_y
)通过ms.Tensor
转换为MindSpore的Tensor对象,并指定数据类型为ms.float32
,这是深度学习模型训练中常用的浮点数精度。
- 对于每个数据集(训练集
这些步骤是深度学习模型训练前数据预处理的一部分,确保数据格式与模型要求相符,并正确设置了模型的训练或推理模式。
启动5个训练任务,并通过plot观察各模型拟合效果。因为同时启动5个任务,每个任务又有训练、计算loss、预测几个子任务,刚开始模型编译的时间较久。
```python
Define parameters of Early Stop.
If it is True, stop the training process.
early_stop = False
Used to record min validation loss during training. Should be initialized with Big enough value.
min_val_loss = 1
In the training iteration process, how many consecutive times are the validation loss greater than min_val_loss.
count = 0
for it in range(ITERATION):
In each iteration randomly selects a batch sample from the training set.
mb_idx = sample_idx(N_SAMPLES, BATCH_SIZE)
x_batch, y_batch = train_x[mb_idx, :], train_y[mb_idx, :]
x_batch, y_batch = ms.Tensor(x_batch.reshape(x_batch.shape + (1, 1)), ms.float32), ms.Tensor(y_batch, ms.float32)
# Set the nets to training state.
set_train(nets_train, True)
fc_train(x_batch, y_batch)
dropout_train(x_batch, y_batch)
bn_train(x_batch, y_batch)
l2_train(x_batch, y_batch)
# Skip the training for Early Stop model when early_step==True.
if not early_stop:
early_stop_train(x_batch, y_batch)
if it % 20 == 0:
# Set the nets to none training state.
set_train(nets_loss+nets_predict, False)
# Compute the test loss of all models.
loss_fc = fc_loss(test_xt, test_yt)
loss_dropout = dropout_loss(test_xt, test_yt)
loss_bn = bn_loss(test_xt, test_yt)
loss_l2 = l2_loss(test_xt, test_yt)
loss_early_stop = early_stop_loss(test_xt, test_yt)
print("loss_fc", loss_fc)
print("loss_dropout", loss_dropout)
print("loss_bn", loss_bn)
print("loss_l2", loss_l2)
print("loss_early_stop", loss_early_stop)
# Compute the predict values on all samples.
all_fc = fc_predict(data_xt)
all_dropout = dropout_predict(data_xt)
all_bn = bn_predict(data_xt)
all_l2 = l2_predict(data_xt)
all_early_stop = early_stop_predict(data_xt)
# For the Early Stop model, when the validation loss is greater than min_val_loss MAX_COUNT consecutive times,
# stop the training process.
if not early_stop:
val_loss = early_stop_loss(validate_xt, validate_yt)
if val_loss > min_val_loss:
count += 1
else:
min_val_loss = val_loss
count = 0
if count == MAX_COUNT:
early_stop = True
print('='*10, 'early stopped', '='*10)
# Draw the figure.
plt.figure(1, figsize=(15,10))
plt.cla()
plt.scatter(train_x, train_y, c='magenta', s=50, alpha=0.3, label='train samples')
plt.scatter(test_x, test_y, c='cyan', s=50, alpha=0.3, label='test samples')
plt.plot(data_x, all_fc.asnumpy(), 'r', label='overfitting')
plt.plot(data_x, all_l2.asnumpy(), 'y', label='L2 regularization')
plt.plot(data_x, all_early_stop.asnumpy(), 'k', label='early stopping')
plt.plot(data_x, all_dropout.asnumpy(), 'b', label='dropout({})'.format(DROPOUT_RATE))
plt.plot(data_x, all_bn.asnumpy(), 'g', label='batch normalization')
plt.text(-0.1, -1.2, 'overfitting loss=%.4f' % loss_fc.asnumpy(), fontdict={'size': 20, 'color': 'red'})
plt.text(-0.1, -1.5, 'L2 regularization loss=%.4f' % loss_l2.asnumpy(), fontdict={'size': 20, 'color': 'y'})
plt.text(-0.1, -1.8, 'early stopping loss=%.4f' % loss_early_stop.asnumpy(), fontdict={'size': 20, 'color': 'black'})
plt.text(-0.1, -2.1, 'dropout loss=%.4f' % loss_dropout.asnumpy(), fontdict={'size': 20, 'color': 'blue'})
plt.text(-0.1, -2.4, 'batch normalization loss=%.4f' % loss_bn.asnumpy(), fontdict={'size': 20, 'color': 'green'})
plt.legend(loc='upper left');
plt.ylim((-2.5, 2.5));
clear_output(wait=True)
plt.show()
plt.savefig('a.png', format='png')
这段代码段展示了一个深度学习训练流程的实现,涉及多个模型的同时训练和评估,以及早停(Early Stopping)策略的应用。以下是代码段的详细解析:
早期停止(Early Stopping)策略定义
early_stop
: 布尔值,初始设为False
,用于控制是否停止训练。min_val_loss
: 初始化为一个足够大的值,用于记录验证集中最小的损失值。count
: 计数器,记录连续多少轮验证损失没有下降。
主循环
-
迭代训练 :代码通过一个主循环(
for it in range(ITERATION)
)来执行多个训练迭代。在每次迭代中:- 随机抽取一批训练样本(使用
sample_idx
函数)。 - 数据预处理,将样本重塑为四维张量,并转换为
ms.float32
类型的MindSpore Tensor。 - 设置所有训练模型到训练状态,然后分别对每个模型进行训练。
- 如果当前未触发早停(即
early_stop == False
),则对早停模型也进行训练。
- 随机抽取一批训练样本(使用
-
损失计算与预测:每20次迭代,执行以下操作:
- 将所有模型设为非训练状态,用于计算损失和预测。
- 分别计算所有模型在测试集上的损失。
- 对整个数据集进行预测,得到预测值。
- 对于早停模型,检查验证集上的损失。如果连续
MAX_COUNT
次验证损失没有降低,则设置early_stop = True
,停止训练。
-
绘图:每次计算完损失和预测后,使用matplotlib库绘制图像,展示:
- 训练样本和测试样本的实际分布。
- 各模型的预测曲线,包括过拟合模型、L2正则化模型、早停模型、dropout模型、批归一化模型的预测结果。
- 图中还标注了每个模型的损失值。
- 最后,保存图像到文件,并实时显示训练进度。
关键技术应用
- 早停(Early Stopping):是一种防止模型过拟合的技术,当验证集上的性能不再提升时停止训练,以保留泛化能力较好的模型版本。
- 模型对比:通过同时训练和评估不同策略下的模型(如L2正则化、dropout、批归一化、早停),直观地比较它们在解决过拟合问题上的效果。
- 可视化监控:利用图表动态展示模型的拟合情况和性能指标,有助于快速理解模型训练进展和效果。
综上所述,这段代码实现了一个综合性的深度学习实验流程,涉及模型训练、评估、早停策略应用及可视化监控,对于理解和实践模型优化技巧非常有帮助。
这段代码片段展示了机器学习训练过程中的一个循环迭代部分,主要关注了以下几个关键点:早停(Early Stopping)策略的应用、模型训练、数据处理、以及训练状态的管理。下面是详细的步骤解析:
早停(Early Stopping)策略初始化
early_stop = False
: 表示训练开始时,没有激活早停机制。min_val_loss = 1
: 初始化验证损失的最小值为一个较大的数值,以便后续可以被任何实际的验证损失值所更新。count = 0
: 记录连续验证损失没有改善的次数,初始化为0。
训练循环
- 迭代次数 (
for it in range(ITERATION)
): 控制整个训练过程的迭代轮数。
数据处理
mb_idx = sample_idx(N_SAMPLES, BATCH_SIZE)
: 从总样本数N_SAMPLES
中随机选择一个批次索引,用于批量训练,其中BATCH_SIZE
是每个批次的样本数量。- 准备训练数据 : 通过索引
mb_idx
从训练数据train_x
和train_y
中提取出当前批次的数据,并转换为MindSpore(假设的框架,基于代码中的ms.Tensor
)的Tensor格式,且对x_batch
进行了形状调整,添加了额外的维度,可能用于适应特定模型的输入要求。
模型训练
- 设置训练状态 : 使用
set_train(nets_train, True)
将网络设置为训练模式,这通常会影响像dropout、BN层等的行为。 - 模型训练 : 分别对全连接网络 (
fc_train
)、dropout网络 (dropout_train
)、批归一化网络 (bn_train
)、L2正则化的网络 (l2_train
) 进行训练。这里假设每个函数负责对应模型的一个训练步。 - 早停逻辑 : 如果
early_stop
为False
,则同样会对早停模型 (early_stop_train
) 进行训练。这意味着一旦满足早停条件并设置了early_stop = True
,早停模型将停止训练。
早停机制细节
虽然代码中没有直接展示如何更新 early_stop
的逻辑,但通常在每次验证或者一定周期内,会检查验证集上的损失(val_loss
)与当前最小验证损失 (min_val_loss
) 的比较。如果连续 count
轮验证损失没有降低,则认为模型已经过拟合,不再有显著改进,此时会设置 early_stop = True
来终止训练。
总结
该代码片段展示了在一个包含多种正则化技术(如L2正则化、dropout、批归一化)和早停策略的机器学习项目中,如何组织训练循环的基本框架。它涉及到了数据处理、模型训练状态管理、以及早停机制的初步设置,但具体实现细节(如 sample_idx
, set_train
, 各个训练函数的具体实现,以及早停条件的确切检查逻辑)需要依据实际使用的编程框架和项目上下文来完成。
333333333333333333333333
#####################################优化器部分###########################
这段代码实现了一个对比不同梯度下降变种在优化Beale函数上的性能的示例。Beale函数是一个经典的测试函数,常用于评估优化算法的性能,它的全局最小值位于(3, 0.5)。以下是代码的逐段解析:
1. 目标函数定义
beale(x1, x2)
:定义了Beale函数,这是一个非凸函数,具有多个局部极小值但只有一个全局最小值。dbeale_dx(x1, x2)
:计算Beale函数关于x1和x2的偏导数,用于梯度计算。
2. 函数图形绘制
- 使用
numpy
创建网格点并计算这些点处的目标函数值,用于绘制等高线图。通过matplotlib
展示了Beale函数的二维等高线图,标记了真正的最小点(3, 0.5)。
3. 梯度下降变种实现
无优化器的梯度下降 (gd_no
)
- 这个函数实现了一个基本的梯度下降方法,其中
conf_para
允许用户自定义迭代次数和学习率。但是,当前实现中实际上并未利用梯度信息更新x_traj
,因此不会收敛到最小值,是一个错误的实现。
随机梯度下降 (gd_sgd
)
- 虽然命名暗示为随机梯度下降(SGD),但实际上该函数执行的是标准的梯度下降,因为它直接基于当前点的梯度进行更新,并没有体现SGD特有的"随机"选择样本进行梯度估计的特点。
动量法 (gd_momentum
)
- 实现了带有动量的梯度下降,通过累加之前梯度的方向以加速收敛过程,特别是在有曲率变化的函数上表现更佳。
Adagrad (gd_adagrad
)
- 实现了Adagrad算法,这是一种适应性学习率的方法,它根据历史梯度的平方来调整每个参数的学习率,有助于处理稀疏梯度或特征尺度变化大的情况。
4. 结果输出与分析
- 分别应用以上四个方法(注意,
gd_no
实际上未正确实现梯度下降逻辑)从同一初始点开始优化,并打印出每种方法最终找到的极值点位置,以及通过gd_plot
函数可视化优化路径。
注意事项
gd_no
函数中的迭代更新逻辑存在问题,未正确应用梯度信息更新x_traj
。- 名称
gd_sgd
可能误导,实际上它执行的是标准梯度下降而非随机梯度下降。 - 每种优化方法的性能可以通过观察最终找到的极小点位置及收敛速度(通过图表)来评估。Adagrad和动量法通常在复杂优化问题上表现更好,但具体效果依赖于超参数的选择和问题特性。
改进建议
- 修复
gd_no
中的逻辑错误,使其能正确执行梯度下降。 - 更正
gd_sgd
的命名或实现,若意在演示随机梯度下降,则应反映对数据样本随机选择的机制。 - 在比较不同方法时,加入对学习率、动量等超参数的更精细调整,以及可能的并行运行和性能度量,以获得更全面的比较结果。
Beale函数是一个常用的测试函数,在多变量优化问题中特别常见,用于检验优化算法的性能。其数学表达式为:
[f(x_1, x_2) = (1.5 - x_1 + x_1x_2)^2 + (2.25 - x_1 + x_1x_2^2)^2 + (2.625 - x_1 + x_1x_2^3)^2]
这个函数有两个变量(x_1)和(x_2),并且其全局最小值位于点((3, 0.5)),在这个点上的函数值为0。尽管如此描述,实际上Beale函数并非严格意义上具有多个局部极小值,而是在除了全局最小值之外的区域函数值普遍较大,这使得它看起来像是包含多个"伪局部极小值"。准确地说,它在全局最小值周围的形态使得许多局部搜索算法可能会遇到困难,如果初始化位置不佳或步长选择不当时,算法可能需要较长时间才能收敛到最优解。
让我们通过几个关键点来理解Beale函数的行为:
-
全局最小值点: 当(x_1 = 3)且(x_2 = 0.5)时,(f(x_1, x_2) = 0)。这是函数的唯一全局最小值点。
-
函数行为: Beale函数在远离全局最小值的区域快速增加。这种增加是非线性的,并且由于包含了(x_2)的多次幂项,导致函数表面在不同方向上的曲率变化复杂。
-
局部极小值的误解: 传统意义上,一个函数的局部极小值是指在该点的邻域内所有点的函数值都不小于该点的函数值。严格来说,Beale函数并没有除了全局最小值之外的真正局部最小值,即没有其他点满足在其邻域内所有点的函数值都大于等于该点的值。不过,由于其复杂的地形,它可能在视觉上或在某些算法的探索过程中呈现出类似局部极小值的行为,尤其是在算法初始化位置不佳时。
-
优化挑战: 对于优化算法而言,Beale函数的挑战在于其非凸性和多个拐点,这可能导致梯度下降等简单方法在某些初始位置难以直接找到全局最小值,需要细致的参数调整或采用更高级的优化策略,如那些考虑动量、适应性学习率或全局优化策略的算法。
综上所述,虽然直接说Beale函数有多个局部极小值不够精确,但它确实提供了一个复杂的优化场景,考验着优化算法的效率和鲁棒性。
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as plt_cl
------------------定义目标函数beale、目标函数的偏导函数dbeale_dx,并画出目标函数---------------------
定义函数beale
def beale(x1, x2):
return (1.5 - x1 + x1 * x2) ** 2 + (2.25 - x1 + x1 * x2 ** 2) ** 2 + (2.625 - x1 + x1 * x2 ** 3) ** 2
定义函数beale的偏导
def dbeale_dx(x1, x2):
dfdx1 = 2 * (1.5 - x1 + x1 * x2) * (x2 - 1) + 2 * (2.25 - x1 + x1 * x2 ** 2) * (x2 ** 2 - 1) + 2 * (
2.625 - x1 + x1 * x2 ** 3) * (x2 ** 3 - 1)
dfdx2 = 2 * (1.5 - x1 + x1 * x2) * x1 + 2 * (2.25 - x1 + x1 * x2 ** 2) * (2 * x1 * x2) + 2 * (
2.625 - x1 + x1 * x2 ** 3) * (3 * x1 * x2 ** 2)
return dfdx1, dfdx2
step_x1, step_x2 = 0.2, 0.2
X1, X2 = np.meshgrid(np.arange(-5, 5 + step_x1, step_x1),
np.arange(-5, 5 + step_x2, step_x2))
Y = beale(X1, X2)
在这段代码中,您正在使用NumPy库来定义和可视化Beale函数,一个二维的非凸函数。下面是各个变量和操作的解析:
-
X1
和X2
: 这两个变量代表了用于绘制Beale函数图像的网格坐标。您使用np.meshgrid
函数生成了两个坐标矩阵,分别对应于x1
和x2
的值。每个矩阵的元素对应空间中一个点的坐标,其中:X1
是关于x1
的网格,其范围从-5到5(含端点),步长为0.2。X2
是关于x2
的网格,其范围同样是从-5到5(含端点),步长也为0.2。
这两个网格共同定义了评估beale
函数的二维平面,每个点(X1[i,j], X2[i,j])
都会计算对应的函数值。
-
Y
: 这个变量存储了在每个(X1, X2)
网格点上beale
函数的值。也就是说,Y[i,j]
等于当x1=X1[i,j]
和x2=X2[i,j]
时beale(x1, x2)
的结果。通过计算整个网格上的函数值,您可以得到一个二维数组,这个数组可以直接用来绘制Beale函数的三维曲面图或等高线图,以直观地展示函数的地形特征。
综上所述,这段代码首先定义了Beale函数及其对两个变量的偏导数,然后利用NumPy创建了两个坐标网格(X1
, X2
)覆盖了指定范围内的点,并计算了这些点上的函数值(Y
),为后续的可视化做准备。这样做的目的是为了通过图形化方式展示Beale函数的性质,包括它的全局最小值点以及函数值随x1
和x2
变化的复杂模式。
了解您的需求后,我将直接展示如何利用上述代码片段中的X1
, X2
, 和计算出的Y
来生成具体数值示例。不过,请注意,直接在这里运行代码并给出具体数值输出并不现实,因为我实际上不能执行代码。但我可以帮助您理解如何手动选取几个点来展示这些变量的含义。
假设我们基于给定的步长(0.2)手动选取几个坐标点来近似说明计算过程:
网格点选取示例
- 对于
X1
, 假设选取i=2, j=3
,则X1[i,j] = -5 + 2*0.2 = -4.6
(因为是从-5开始,每次增加0.2)。 - 对应地,对于
X2
, 假设选取同样的位置i=2, j=3
,则X2[i,j] = -5 + 3*0.2 = -4.4
。
计算Y值示例
接下来,我们将使用beale
函数计算对应的Y
值。使用上面选取的坐标(-4.6, -4.4):
python
Y_example = beale(-4.6, -4.4)
由于我不能直接运行代码计算具体的Y_example
值,但如果您在自己的Python环境中执行这一行,您会得到一个具体的数值,这个数值就是当x1 = -4.6
和x2 = -4.4
时,beale
函数的输出值。
总结
- X1, X2 的理解:它们代表了平面上一系列的坐标点,用于全面探索函数在该区域的行为。
- Y 的理解 :它是在每个
(X1[i,j], X2[i,j])
坐标点上,Beale函数的输出值,形成了一个与输入网格相对应的值矩阵。
希望这个示例能帮助您更好地理解X1
, X2
, 和Y
是如何生成和关联的。如果您想要看到实际的图形展示,通常会使用matplotlib这类库来绘制这些数据,例如绘制等高线图或者三维曲面图来可视化Beale函数的地形。
print("目标结果 (x_1, x_2) = (3, 0.5)")
定义画图函数
def gd_plot(x_traj):
plt.rcParams['figure.figsize'] = [6, 6]
plt.contour(X1, X2, Y, levels=np.logspace(0, 6, 30),
norm=plt_cl.LogNorm(), cmap=plt.cm.jet)
plt.title('2D Contour Plot of Beale function(Momentum)')
plt.xlabel(' x 1 x_1 x1')
plt.ylabel(' x 2 x_2 x2')
plt.axis('equal')
plt.plot(3, 0.5, 'k*', markersize=10)
if x_traj is not None:
x_traj = np.array(x_traj)
plt.plot(x_traj[:, 0], x_traj[:, 1], 'k-')
plt.show()
gd_plot(None)
------------------------------------------------------------无优化器-------------------------------------------
def gd_no(df_dx, x0, conf_para=None):
if conf_para is None:
conf_para = {}
conf_para.setdefault('n_iter', 1000) # 迭代次数
conf_para.setdefault('learning_rate', 0.001) # 设置学习率
x_traj = []
x_traj.append(x0)
v = np.zeros_like(x0)
for iter in range(1, conf_para['n_iter'] + 1):
x_traj.append(x_traj[-1])
return x_traj
x0 = np.array([1.0, 1.5])
conf_para_no = {'n_iter': 2000, 'learning_rate': 0.005}
x_traj_no = gd_no(dbeale_dx, x0, conf_para_no)
print("无优化器求得极值点 (x_1, x_2) = (%s, %s)" % (x_traj_no[-1][0], x_traj_no[-1][1]))
gd_plot(x_traj_no)
这段代码定义了一个名为gd_no
的函数,它似乎是打算实现一个梯度下降算法的框架,但目前的实现并没有实际执行梯度更新步骤,只是简单地将初始点x0
复制了n_iter
次。让我们先理解现有代码的功能,然后我会提供一个修正版的例子来展示一个完整的梯度下降计算过程。
现有代码解释
df_dx
: 是一个函数,预期接收一个点(如二维的(x1, x2)
)并返回该点处的目标函数的梯度(导数向量)。x0
: 是初始点,即优化开始的位置。conf_para
: 是一个配置参数字典,默认设置了迭代次数为1000次,学习率为0.001。x_traj
: 用于存储每一步迭代后的点的位置,初始时包含x0
。v
: 似乎是为动量(momentum)准备的变量,但在当前代码中未被使用。for
循环:迭代n_iter
次,每次循环只是简单地将x_traj
的最后一项再添加一次到列表中,实际上没有进行任何位置更新。
修正并举例说明计算过程
为了演示一个实际的梯度下降过程,我们需要在循环中加入梯度更新的逻辑。这里假设我们有一个目标函数f(x) = x^2
,其梯度为df/dx = 2x
。我们将修正代码并计算一个示例过程:
python
import numpy as np
def gd_corrected(df_dx, x0, conf_para=None):
if conf_para is None:
conf_para = {}
conf_para.setdefault('n_iter', 1000) # 迭代次数
conf_para.setdefault('learning_rate', 0.001) # 设置学习率
x_traj = [x0] # 初始化轨迹列表,包含初始点
for iter in range(conf_para['n_iter']):
grad = df_dx(x_traj[-1]) # 计算当前位置的梯度
x_traj_new = x_traj[-1] - conf_para['learning_rate'] * grad # 更新位置
x_traj.append(x_traj_new) # 将新位置添加到轨迹列表中
return x_traj
# 定义目标函数的梯度
def gradient(x):
return 2 * x
# 设置初始点
x0 = 10
# 运行修正后的梯度下降
trajectory = gd_corrected(gradient, x0)
# 打印前几个迭代点的值
print("迭代过程中的点:", trajectory[:10])
例子说明
在这个修正后的例子中,我们定义了一个简单的梯度下降过程来最小化函数f(x) = x^2
。初始点设为x0 = 10
。每一步迭代中,我们计算当前位置的梯度(即2*x
),然后根据学习率更新位置。经过多次迭代后,x_traj
会记录下每一步的位置,从而可以看到x
值如何逐渐减小,趋近于函数的最小值点(在这个例子中是x=0
)。通过打印前几个迭代点,我们可以直观地观察到优化过程的动态。
------------------------------------------------------------SGD-------------------------------------------
def gd_sgd(df_dx, x0, conf_para=None):
if conf_para is None:
conf_para = {}
conf_para.setdefault('n_iter', 1000) # 迭代次数
conf_para.setdefault('learning_rate', 0.001) # 设置学习率
x_traj = []
x_traj.append(x0)
v = np.zeros_like(x0)
for iter in range(1, conf_para['n_iter'] + 1):
dfdx = np.array(df_dx(x_traj[-1][0], x_traj[-1][1]))
v = - conf_para['learning_rate'] * dfdx
x_traj.append(x_traj[-1] + v)
return x_traj
x0 = np.array([1.0, 1.5])
conf_para_sgd = {'n_iter': 2000, 'learning_rate': 0.005}
x_traj_sgd = gd_sgd(dbeale_dx, x0, conf_para_sgd)
print("SGD求得极值点 (x_1, x_2) = (%s, %s)" % (x_traj_sgd[-1][0], x_traj_sgd[-1][1]))
gd_plot(x_traj_sgd)
这段代码实现了一个使用随机梯度下降(Stochastic Gradient Descent, SGD)的简化版本来寻找Beale函数的极小值点,但实际上,代码中的实现并没有体现"随机"的特性,而是对整个函数进行了梯度下降,我们可以将其理解为标准梯度下降的示例。下面我将逐步解析这段代码的计算过程,并指出一些潜在的误解。
函数定义与参数
首先,定义了一个名为gd_sgd
的函数,意在实现SGD算法,用于最小化给定的目标函数(通过梯度df_dx
计算)。但是,需要注意的是,真正的SGD通常在每次迭代时只使用一个或一小部分样本来更新模型参数,以减少计算负担并增加泛化能力。然而,这段代码在每次迭代时计算整个函数的梯度,实际上执行的是批量梯度下降(BGD)而不是SGD。
参数初始化
-
conf_para
是一个字典,用于存放算法的配置参数,比如迭代次数n_iter
和学习率learning_rate
。在这个例子中,迭代次数被设定为2000,学习率设为0.005。 -
x0
是初始点,设为(1.0, 1.5)
,即开始搜索的起始位置。
计算过程
-
初始化 :函数开始时,创建了一个空列表
x_traj
来记录每一步的迭代点,并将初始点x0
加入列表。 -
迭代 :接下来进入一个循环,迭代次数由
conf_para['n_iter']
确定,这里是2000次。 -
计算梯度 :在每次迭代中,通过调用
df_dx
函数(这里实际上是dbeale_dx
,计算Beale函数关于x1
和x2
的梯度)计算当前位置的梯度,并乘以负的学习率得到更新向量v
。这里的更新规则遵循标准梯度下降,即朝着梯度的反方向更新。 -
更新位置 :利用
v
更新当前位置,然后将新的位置添加到x_traj
列表中。 -
输出结果 :循环结束后,打印出SGD找到的极值点坐标,并调用
gd_plot
函数绘制优化路径。
注意事项
-
SGD与BGD混淆 :尽管函数名为
gd_sgd
,其实现更像是批量梯度下降(BGD),因为它在每次迭代时都考虑了整个数据集(或函数)的信息来更新参数,而非只使用单个或少量样本。 -
梯度计算:在实际应用中,SGD会针对每个样本或小批量样本独立计算梯度并更新参数,这里则是直接在整个数据集上计算梯度,不符合SGD的核心理念。
-
结果可视化 :通过
gd_plot
函数展示优化过程中的路径,这有助于直观理解算法如何逐步接近最优解。
综上,这段代码提供了一个理解梯度下降算法(特别是其批量形式)如何应用于函数最小化问题的基础案例,但要注意它并没有真正实现SGD的核心概念。
随机梯度下降(Stochastic Gradient Descent, SGD)的更新公式通常表示为:
[ \theta_{t+1} = \theta_t - \alpha \nabla_{\theta} J(\theta; x_i, y_i) ]
其中:
- ( \theta_t ) 表示在时间步 ( t ) 时的模型参数。
- ( \theta_{t+1} ) 表示在时间步 ( t+1 ) 时更新后的模型参数。
- ( \alpha ) 是学习率,决定了参数更新的步长。
- ( \nabla_{\theta} J(\theta; x_i, y_i) ) 是损失函数 ( J ) 关于参数 ( \theta ) 的梯度,计算时使用的是当前迭代到的样本 ( (x_i, y_i) )。
这意味着在每次迭代中,SGD只使用一个训练样本 ( (x_i, y_i) ) 来估计梯度并据此更新参数,这与批量梯度下降(在每次更新时使用所有样本)不同。这样的好处是可以减少计算量,使得算法更加高效,尤其是在处理大规模数据集时。不过,这也可能使得收敛过程更加波动,因为每一次更新都是基于单个样本的信息。为了改善这一点,实践中常常会采用动量(Momentum)、Adaptive Learning Rate方法(如Adagrad, RMSprop, Adam等)来增强SGD的性能。
-------------------------------------------------------Momentum---------------------------------
def gd_momentum(df_dx, x0, conf_para=None):
if conf_para is None:
conf_para = {}
conf_para.setdefault('n_iter', 1000) # 迭代次数
conf_para.setdefault('learning_rate', 0.001) # 设置学习率
conf_para.setdefault('momentum', 0.9) # 设置动量参数
x_traj = []
x_traj.append(x0)
v = np.zeros_like(x0)
for iter in range(1, conf_para['n_iter'] + 1):
dfdx = np.array(df_dx(x_traj[-1][0], x_traj[-1][1]))
v = conf_para['momentum'] * v - conf_para['learning_rate'] * dfdx
不同上面的v = - conf_para['learning_rate'] * dfdx
x_traj.append(x_traj[-1] + v)
return x_traj
x0 = np.array([1.0, 1.5])
conf_para_momentum = {'n_iter': 500, 'learning_rate': 0.005}
x_traj_momentum = gd_momentum(dbeale_dx, x0, conf_para_momentum)
print("Momentum求得极值点 (x_1, x_2) = (%s, %s)" % (x_traj_momentum[-1][0], x_traj_momentum[-1][1]))
gd_plot(x_traj_momentum)
- 带动量的梯度下降(Momentum GD)
代码理解: 第二段代码正确地展示了带有动量的梯度下降方法,其中除了梯度方向外,还加入了动量项,以加速收敛并减少振荡。
特点:
引入了动量项 v = conf_para['momentum'] * v - conf_para['learning_rate'] * dfdx,其中v代表动量向量。
动量项使得更新步骤不仅依赖于当前梯度,还依赖于之前的更新方向和幅度,有助于更快地穿越平坦区域和逃离局部最小值。
在连续梯度方向相同的情况下,累积动量可以加快搜索速度;而当梯度方向改变时,动量项能平滑这种变换,减少振荡现象。
----------------------------------------------------adagrad-----------------------------
def gd_adagrad(df_dx, x0, conf_para=None):
if conf_para is None:
conf_para = {}
conf_para.setdefault('n_iter', 1000) # 迭代次数
conf_para.setdefault('learning_rate', 0.001) # 学习率
conf_para.setdefault('epsilon', 1e-7)
x_traj = []
x_traj.append(x0)
r = np.zeros_like(x0)
for iter in range(1, conf_para['n_iter'] + 1):
dfdx = np.array(df_dx(x_traj[-1][0], x_traj[-1][1]))
r += dfdx ** 2
x_traj.append(x_traj[-1] - conf_para['learning_rate'] / (np.sqrt® + conf_para['epsilon']) * dfdx)
return x_traj
x0 = np.array([1.0, 1.5])
conf_para_adag = {'n_iter': 500, 'learning_rate': 2}
x_traj_adag = gd_adagrad(dbeale_dx, x0, conf_para_adag)
print("Adagrad求得极值点 (x_1, x_2) = (%s, %s)" % (x_traj_adag[-1][0], x_traj_adag[-1][1]))
gd_plot(x_traj_adag)
这段代码实现的是Adagrad(Adaptive Gradient Algorithm)优化算法,它是一种自适应学习率的方法,主要用于解决学习率选择难题,尤其是在参数更新频率差异较大的情况下。Adagrad通过为每个参数维护一个单独的学习率来实现这一点,这个学习率会根据迄今为止观察到的梯度的平方和进行缩放。下面是详细的计算过程和Adagrad与其他方法的不同之处:
计算过程
-
初始化 : 首先,设置初始参数
x0
,配置参数conf_para
(包括迭代次数、初始学习率和防止除零异常的微小值epsilon
),并初始化参数轨迹列表x_traj
和梯度平方累积矩阵r
(每个参数对应一个累积值,初始为0)。 -
迭代更新:
- 对于每一次迭代:
- 计算当前位置的梯度
dfdx
。 - 累积梯度的平方到
r
中,即r += dfdx ** 2
。这样做是为了跟踪每个参数历史上梯度大小的平方。 - 更新参数值,计算公式为:
x_traj.append(x_traj[-1] - conf_para['learning_rate'] / (np.sqrt(r) + conf_para['epsilon']) * dfdx)
。这里,学习率是自适应的,对于历史上梯度变化大的参数,其学习率会自动减小,反之亦然。
- 计算当前位置的梯度
- 对于每一次迭代:
-
输出与绘图 : 迭代完成后,打印出Adagrad算法找到的极值点坐标,并调用
gd_plot
函数绘制优化路径。
不同之处
-
与标准梯度下降(GD)和带动量的梯度下降(Momentum GD)相比:Adagrad的主要区别在于其自适应地调整每个参数的学习率。在标准GD和Momentum GD中,所有参数共用一个全局的学习率,这可能导致某些参数更新过快或过慢。Adagrad解决了这个问题,对于频繁更新的参数(其梯度平方累积大),它会自动降低学习率,而对于不常更新的参数(其梯度平方累积小),则允许较大的更新步长。
-
与RMSprop和Adam等其他自适应学习率方法相比:Adagrad的一个潜在问题是其累积梯度平方会导致学习率随着时间逐渐降低,有时甚至趋于零,这被称为学习率衰减问题。RMSprop通过引入一个衰减因子解决这个问题,而Adam算法则进一步结合了动量项和偏差校正,提供了更为稳定的自适应学习率策略。
综上,Adagrad通过为每个参数动态调整学习率,有效应对了复杂优化问题中参数更新频率不一的挑战,但需注意其长期使用可能导致学习率过低的问题。
import csv
import os
import time
import numpy as np
from easydict import EasyDict as edict
from matplotlib import pyplot as plt
import mindspore
from mindspore import nn
from mindspore import context
from mindspore import dataset
from mindspore.train.callback import TimeMonitor, LossMonitor
from mindspore import Tensor
from mindspore.train import Model
from mindspore.train.callback import ModelCheckpoint, CheckpointConfig
context.set_context(mode=context.GRAPH_MODE, device_target="Ascend")
解析执行本脚本时的传参
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--data_url', required=True, default=None, help='Location of data.')
parser.add_argument('--train_url', required=True, default=None, help='Location of training outputs.')
args, unknown = parser.parse_known_args()
import moxing as mox
mox.file.copy_parallel(src_url=os.path.join(args.data_url, 'iris.data'), dst_url='iris.data') # 将OBS桶中数据拷贝到容器中
cfg = edict({
'data_size': 150,
'train_size': 120, # 训练集大小
'test_size': 30, # 测试集大小
'feature_number': 4, # 输入特征数
'num_class': 3, # 分类类别
'batch_size': 30,
'data_dir': 'iris.data', # 执行容器中数据集所在路径
'save_checkpoint_steps': 5, # 多少步保存一次模型
'keep_checkpoint_max': 1, # 最多保存多少个模型
'out_dir_no_opt': './model_iris/no_opt', # 保存模型路径,无优化器模型
'out_dir_sgd': './model_iris/sgd', # 保存模型路径,SGD优化器模型
'out_dir_momentum': './model_iris/momentum', # 保存模型路径,momentum模型
'out_dir_adam': './model_iris/adam', # 保存模型路径,adam优化器模型
'output_prefix': "checkpoint_fashion_forward" # 保存模型文件名
})
读取数据并预处理:
with open(cfg.data_dir) as csv_file:
data = list(csv.reader(csv_file, delimiter=','))
共150条数据,将数据集的4个属性作为自变量X。将数据集的3个类别映射为{0, 1,2},作为因变量Y。
label_map = {'Iris-setosa': 0, 'Iris-versicolor': 1, 'Iris-virginica': 2}
X = np.array([[float(x) for x in s[:-1]] for s in data[:cfg.data_size]], np.float32)
Y = np.array([label_map[s[-1]] for s in data[:150]], np.int32)
将数据集分为训练集120条,测试集30条。
train_idx = np.random.choice(cfg.data_size, cfg.train_size, replace=False)
test_idx = np.array(list(set(range(cfg.data_size)) - set(train_idx)))
X_train, Y_train = X[train_idx], Y[train_idx]
X_test, Y_test = X[test_idx], Y[test_idx]
print('训练数据x尺寸:', X_train.shape)
print('训练数据y尺寸:', Y_train.shape)
print('测试数据x尺寸:', X_test.shape)
print('测试数据y尺寸:', Y_test.shape)
使用MindSpore GeneratorDataset接口将numpy.ndarray类型的数据转换为Dataset。
def gen_data(X_train, Y_train, epoch_size):
XY_train = list(zip(X_train, Y_train))
ds_train = dataset.GeneratorDataset(XY_train, ['x', 'y'])
ds_train = ds_train.shuffle(buffer_size=cfg.train_size).batch(cfg.batch_size, drop_remainder=True)
XY_test = list(zip(X_test, Y_test))
ds_test = dataset.GeneratorDataset(XY_test, ['x', 'y'])
ds_test = ds_test.shuffle(buffer_size=cfg.test_size).batch(cfg.test_size, drop_remainder=True)
return ds_train, ds_test
训练
def train(network, net_opt, ds_train, prefix, directory, print_times):
net_loss = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction="mean")
model = Model(network, loss_fn=net_loss, optimizer=net_opt, metrics={"acc"})
loss_cb = LossMonitor(per_print_times=print_times)
config_ck = CheckpointConfig(save_checkpoint_steps=cfg.save_checkpoint_steps,
keep_checkpoint_max=cfg.keep_checkpoint_max)
ckpoint_cb = ModelCheckpoint(prefix=prefix, directory=directory, config=config_ck)
print("============== Starting Training ==============")
model.train(epoch_size, ds_train, callbacks=[ckpoint_cb, loss_cb], dataset_sink_mode=False)
return model
评估预测
def eval_predict(model, ds_test):
使用测试集评估模型,打印总体准确率
metric = model.eval(ds_test, dataset_sink_mode=False)
print(metric)
预测
test_ = ds_test.create_dict_iterator().get_next()
test = Tensor(test_['x'], mindspore.float32)
predictions = model.predict(test)
predictions = predictions.asnumpy()
for i in range(10):
p_np = predictions[i, :]
p_list = p_np.tolist()
print('第' + str(i) + '个sample预测结果:', p_list.index(max(p_list)), ' 真实结果:', test_['y'][i])
--------------------------------------------------无优化器-----------------------------------
epoch_size = 20
print('------------------无优化器--------------------------')
数据
ds_train, ds_test = gen_data(X_train, Y_train, epoch_size)
定义网络并训练
network = nn.Dense(cfg.feature_number, cfg.num_class)
model = train(network, None, ds_train, "checkpoint_no_opt", cfg.out_dir_no_opt, 4)
评估预测
eval_predict(model, ds_test)
---------------------------------------------------SGD-------------------------------------
epoch_size = 200
lr = 0.01
print('-------------------SGD优化器-----------------------')
数据
ds_train, ds_test = gen_data(X_train, Y_train, epoch_size)
定义网络并训练、测试、预测
network = nn.Dense(cfg.feature_number, cfg.num_class)
net_opt = nn.SGD(network.trainable_params(), lr)
model = train(network, net_opt, ds_train, "checkpoint_sgd", cfg.out_dir_sgd, 40)
评估预测
eval_predict(model, ds_test)
----------------------------------------------------Momentum-------------------------------
epoch_size = 20
lr = 0.01
print('-------------------Momentum优化器-----------------------')
数据
ds_train, ds_test = gen_data(X_train, Y_train, epoch_size)
定义网络并训练
network = nn.Dense(cfg.feature_number, cfg.num_class)
net_opt = nn.Momentum(network.trainable_params(), lr, 0.9)
model = train(network, net_opt, ds_train, "checkpoint_momentum", cfg.out_dir_momentum, 4)
评估预测
eval_predict(model, ds_test)
----------------------------------------------------Adam-----------------------------------
epoch_size = 15
lr = 0.1
print('------------------Adam优化器--------------------------')
数据
ds_train, ds_test = gen_data(X_train, Y_train, epoch_size)
定义网络并训练
network = nn.Dense(cfg.feature_number, cfg.num_class)
net_opt = nn.Adam(network.trainable_params(), learning_rate=lr)
model = train(network, net_opt, ds_train, "checkpoint_adam", cfg.out_dir_adam, 4)
评估预测
eval_predict(model, ds_test)
-------------------------------------------------------------------------------------------
mox.file.copy_parallel(src_url='model_iris', dst_url=args.train_url) # 将容器输出拷贝到OBS桶中
此代码段展示了一个使用MindSpore框架的机器学习示例,用于训练和评估一个简单的神经网络模型,该模型基于Iris数据集分类鸢尾花种类。程序流程涉及数据预处理、模型定义、训练、评估以及不同优化器的对比。下面是对主要步骤的解析:
1. 环境设置与数据准备
- 使用
mindspore
库,并设置运行模式为GRAPH_MODE,目标设备为Ascend(华为的昇腾AI处理器)。 - 通过
argparse
解析命令行参数,以确定数据和训练输出的存储位置。 - 利用
moxing
库将数据从OBS(对象存储服务)下载到本地容器中,供训练使用。 - 定义配置字典
cfg
,包含数据集大小、训练集大小、测试集大小、特征数量、分类类别数等参数。
2. 数据读取与预处理
- 读取CSV文件
iris.data
中的数据,并根据标签映射将其分类标签转化为数字。 - 将数据集随机划分为训练集和测试集,并打印其维度。
3. 数据集创建
- 定义
gen_data
函数,使用MindSpore的GeneratorDataset
接口将numpy数组转换为可迭代的Dataset对象,用于训练和测试。
4. 模型训练与评估
- 无优化器部分:直接训练一个简单的全连接层网络,没有指定优化器,主要用于展示对比(实际上无优化器模型无法训练,因为需要梯度更新)。
- SGD优化器:使用随机梯度下降(SGD)优化器,设定学习率为0.01,进行训练和评估。
- Momentum优化器:采用带有动量项的SGD,动量系数设为0.9,进行训练和评估。
- Adam优化器:应用Adam优化器,学习率设为0.1,进行训练和评估。
5. 模型保存与输出
- 使用
ModelCheckpoint
回调函数,在每完成一定步数后保存模型检查点。 - 每个优化器训练后,使用
eval_predict
函数对模型进行评估,打印准确率,并对前10个测试样本进行预测。
6. 输出模型
- 训练结束后,使用
mox.file.copy_parallel
将训练得到的模型文件夹复制回OBS桶中,便于后续访问和部署。
总结
此代码实现了利用MindSpore框架在Iris数据集上的分类任务,比较了不同优化器(包括SGD、Momentum、Adam)对模型训练效果的影响。通过命令行参数,数据和模型文件可以灵活地在本地与云存储之间传输,适合分布式或云端训练场景。值得注意的是,无优化器的设置仅作为理论上的对比参照,实际运行时应避免,因为模型参数无法更新。