【MXnet】-线性回归-两种方法的对比理解

Mxnet -线性回归

由于,在Mxnet中使用了大量的函数来搭建模型结构,虽然简单,却同时看不出本来面目,不易理解。在本文中,比较《线性回归从零开始》与《线性回归的简介实现gluon》这两种方法,对比理解mxnet搭建模型的流程。并在《附录》中添加了运算过程中出现的函数解释,方便大家理解。

import mxnet

%matplotlib inlinezhi
from IPython import display             # display 中设置 矢量图片
from matplotlib import pyplot as plt    #  画图
from mxnet import autograd, nd          #  导入 梯度、ndarray数据
import random                           #  导入随机包

一、线性回归从零开始

简单得理解线性回归得流程。

1.1 生成数据集

我们构造⼀个简单的⼈⼯训练数据集,它可以使我们能够直观⽐较学到的参数和真实的模型参数的区别。设训练数据集样本数为1000,输⼊个数(特征数)为2。给定随机⽣成的批量样本特征 X ∈ R 1000 × 2 X\in R^{1000×2} XR1000×2,我们使⽤线性回归模型真实权重 w = [ 2 , − 3.4 ] T w = [2, -3.4]^T w=[2,3.4]T和偏差 b = 4.2 b = 4.2 b=4.2,以及⼀个随机噪声项 ϵ ϵ ϵ来⽣成标签
y = X w + b + ϵ y = Xw + b + ϵ y=Xw+b+ϵ
其中噪声项ϵ服从均值为0、标准差为0.01的正态分布。噪声代表了数据集中⽆意义的⼲扰。下⾯,让我们⽣成数据集.

num_inputs = 2             # 输入特征
num_examples = 1000        # 样本个数
true_w = [2, -3.4]         # 真实权重
true_b = 4.2               # 真是偏差
features = nd.random.normal(scale=1, shape=(num_examples, num_inputs))# 随机生成一个 1000*2结构得据
                            # featrues的每⼀⾏是⼀个⻓度为2的向量
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b # 通过真实线性关系构造标签
                            # labels的每⼀⾏是⼀个⻓度为1的向量(标量)。
labels += nd.random.normal(scale=0.01, shape=labels.shape) # 给标签增加一个噪声
features[0], labels[0]  # 简单查看数据
查看线性关系

以第二个特征为例,通过⽣成第⼆个特征features[:, 1]和标签 labels 的散点图,可以更直观地观察两者间的 线性关系。

def use_svg_display():
    # ⽤⽮量图显⽰
    display.set_matplotlib_formats('svg')   # 附录1
    
def set_figsize(figsize=(3.5, 2.5)):
    use_svg_display()
    # 设置图的尺⼨
    plt.rcParams['figure.figsize'] = figsize
    
set_figsize()  # 设置图行尺寸和矢量图格式
# 画散点图,大小s=1
plt.scatter(features[:, 1].asnumpy(), labels.asnumpy(), 1); #加分号只显⽰图 # 附录2

1.2 读取数据集

在训练模型的时候,我们需要遍历数据集并不断读取小批量数据样本。这⾥我们定义⼀个函数: 它每次返回batch_size(批量⼤小)个随机样本的特征和标签。

def data_iter(batch_size, features, labels): 
    num_examples = len(features)         # 特征个数
    indices = list(range(num_examples))  # 特征个数索引列表
    random.shuffle(indices)              # 样本的读取顺序是随机的 # 附录3
    for i in range(0, num_examples, batch_size):
        j = nd.array(indices[i: min(i + batch_size, num_examples)]) 
        # 切片得索引(包含最后没有达到batch_size得一个切片索引)
        yield features.take(j), labels.take(j) # take函数根据索引返回对应元素  # 附录4

batch_size: 批量大小,整形

features: 特征, ndarray,shape=(1000*2)

labels: 标签ndarray,shape=(1000,)

分析:[如果改变成gluon中的函数呢]

  1. batch_size:用来决定切片的大小,和计算切片的索引。—保留参数batch_size
  2. 返回的数据是对应的切片的feature 和 labels --------feature 和labels 数据
  3. 中间进行了shuffle--------shuffle=True
根据批量产生数据并查看

让我们读取第⼀个小批量数据样本并打印。每个批量的特征形状为(10, 2),分别对应批量⼤小和
输⼊个数;标签形状为批量⼤小.

batch_size = 10
for X, y in data_iter(batch_size, features, labels):
    print(X, y)
    break    #因为data_iter是一个迭代函数,如果不加的化,则会返回多个值

1.3 初始化模型参数

我们将权重初始化成均值为0、标准差为0.01的正态随机数,偏差则初始化成0

w = nd.random.normal(scale=0.01, shape=(num_inputs, 1))
b = nd.zeros(shape=(1,))
params=[w,b]

之后的模型训练中,需要对这些参数求梯度来迭代参数的值,因此我们需要创建它们的梯度

创建梯度 # 附录5(求导流程)
w.attach_grad()     #w.grad 是附加到这个NDArray的梯度(属性)
b.attach_grad()

1.4 定义模型

下⾯是线性回归的⽮量计算表达式的实现。我们使⽤dot函数做矩阵乘法。

def linreg(X, w, b):  
    return nd.dot(X, w) + b   # nd.dot() 点乘

1.5 定义损失函数

真实值y,预测值y_hat.两个ndarray的形状相同。定义平方损失。

def squared_loss(y_hat, y): # 
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
    # y.reshape(y_hat.shape) 是将y变成y_hat 的形状

1.6定义优化算法

以下的sgd函数实现了上⼀节中介绍的小批量随机梯度下降算法。它通过不断迭代模型参数来优化损失函数。这⾥⾃动求梯度模块计算得来的梯度是⼀个批量样本的梯度和。我们将它除以批量⼤小来得到平均值.

根据梯度的定义:参数=参数-学习步长*总梯度/批量大小

def sgd(params, lr, batch_size): 
    for param in params:
        param[:] = param - lr * param.grad / batch_size

params : 参数列表集合

lr :学习率

batch_size:批量大小

1.7 训练模型

在训练中,我们将多次迭代模型参数。

在每次迭代中,我们根据当前读取的小批量数据样本(特征X和标签y),通过调⽤反向函数backward计算小批量随机梯度,并调⽤优化算法sgd迭代模型参数。

由于我们之前设批量⼤小batch_size为10,每个小批量的损失l的形状为(10, 1)。回忆⼀下“⾃动求梯度” ⼀节。由于变量l并不是⼀个量,运⾏l.backward()将对l中元素求和得到新的变量,再求该变量有关模型参数的梯度。

在⼀个迭代周期(epoch)中,我们将完整遍历⼀遍data_iter函数,并对训练数据集中所有样本都使⽤⼀次(假设样本数能够被批量⼤小整除)。这⾥的迭代周期个数num_epochs和学习率lr都是超参数,分别设3和0.03。在实践中,⼤多超参数都需要通过反复试错来不断调节。虽然迭代周期数设得越⼤模型可能越有效,但是训练时间可能过⻓

lr = 0.03                #学习率
num_epochs = 3           #轮次
net = linreg             #线性回归 # 模型函数
loss = squared_loss      #损失函数
for epoch in range(num_epochs): # 训练模型⼀共需要num_epochs个迭代周期
    # 在每⼀个迭代周期中,会使⽤训练数据集中所有样本⼀次(假设样本数能够被批量⼤⼩整除)。 
    # X和y分别是⼩批量样本的特征和标签
    for X, y in data_iter(batch_size, features, labels):
        with autograd.record():
            l = loss(net(X, w, b), y)   # l是有关⼩批量X和y的损失
        l.backward()                    # ⼩批量的损失对模型参数求梯度
        sgd([w, b], lr, batch_size)     # 使⽤⼩批量随机梯度下降迭代模型参数  # 优化器
    train_l = loss(net(features, w, b), labels)
    print('epoch %d, loss %f' % (epoch + 1, train_l.mean().asnumpy()))

out:

epoch 1, loss 0.054347
epoch 2, loss 0.000246
epoch 3, loss 0.000050

训练完成后,我们可以⽐较学到的参数和⽤来⽣成训练集的真实参数。它们应该很接近.

true_w, w

out:

([2, -3.4],

[[ 1.9992912]
 [-3.3992891]]
<NDArray 2x1 @cpu(0)>)
true_b, b

out:

(4.2,

[4.1990314]
<NDArray 1 @cpu(0)>)

二、线性回归的简洁实现

我们将介绍如何使⽤MXNet提供的Gluon接口更⽅便地实现线性回归的训练。

2.1 生成数据集

我们⽣成与上⼀节中相同的数据集。其中features是训练数据特征, labels是标签
%为了避免麻烦,我重启了一下

from mxnet import ndarray as nd
from mxnet import autograd
from mxnet import gluon

数据集的构造类似不变

num_inputs = 2
num_examples = 1000
true_w = [2, -3.4]
true_b = 4.2

features = nd.random.normal(scale=1, shape=(num_examples, num_inputs))
labels= true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
labels += nd.random.normal(scale=0.01, shape=labels.shape)

2.2 读取数据集

Gluon提供了data包来读取数据。由于data常⽤作变量名,我们将导⼊的data模块⽤添加了Gluon⾸代替。在每⼀次迭代中,我们将随机读取包含10个数据样本的小批量。

生成批量数据的代码被统一成两个函数。

  • 函数gluon.data.ArrayDataset(features,labels)将数据的特征和标签组合。
  • 函数gluon.data.Dataloader(dataset,batch_size, shuffle=True)读取批量数据
batch_size = 10
# 将训练数据的特征和标签组合
dataset = gluon.data.ArrayDataset(features, labels)
# 随机读取⼩批量
data_iter = gluon.data.DataLoader(dataset, batch_size, shuffle=True)

这⾥data_iter的使⽤与上⼀节中的⼀样。让我们读取并打印第⼀个小批量数据样本

for data, label in data_iter:
    print(data,label)
    break

2.3 定义模型

Gluon提供了⼤量预定义的层,这使我们只需关注使⽤哪些层来构造模型。下⾯将介绍如何使⽤Gluon更简洁地定义线性回归。
⾸先,导⼊nn模块。实际上,“nn”是neural networks(神经⽹络)的缩写。顾名思义,该模块定义了⼤量神经⽹络的层。我们先定义⼀个模型变量net,它是一个Sequential(顺序的?)实例。在Gluon中,Sequential实例可以看作是⼀个串联各个层的容器造模型时,我们在该容器中依次添加层。当给定输⼊数据时,容器中的每⼀层将依次计算并将输出作为下⼀层的输⼊

构建模型最简单的方法就是利用Sequential来把所有层串起来。首先我们定义一个空的模型

from mxnet.gluon import nn
net = nn.Sequential()

然后我们加入一个Dense层,它唯一必须要定义的参数就是输出节点的个数,在线性模型里面是1.因为后面是输出的节点的个数,我们知道,y=wx+b,有1个y,所以,输出为1.

net.add(nn.Dense(1))  # net.add() 在nn.Sequential中添加# 附录6 # nn.Dense() 密集层,全连接层 # 附录7
net

out:

Sequential(
 (0): Dense(None -> 1, linear)
)

⼀个单层神经⽹络,线性回归输出层中的神经元和输⼊层中各个输⼊完全连接。因此,线性回归的输出层⼜叫全连接层。在Gluon中,全连接层是⼀个Dense实例。我们定义该层输出个数为1

值得⼀提的是,在Gluon中我们⽆须指定每⼀层输⼊的形状,例如线性回归的输⼊个数。当模型得到数据时,例如后⾯执⾏net(X)时,模型将⾃动推断出每⼀层的输⼊个数。我们将在之后“深度学习计算”⼀章详细介绍这种机制。 Gluon的这⼀设计为模型开发带来便利。

2.4 初始化模型参数

在使⽤net前,我们需要初始化模型参数,如线性回归模型中的权重和偏差。我们从MXNet导⼊init模块该模块提供了模型参数初始化的各种⽅法。这⾥的init是initializer的缩写形式。我们通过init.Normal(sigma=0.01)指定权重参数每个元素将在初始化时随机采样于均值为0、标准差为0.01的正态分布。偏差参数默认会初始化为零。

from mxnet import init
net.initialize(init.Normal(sigma=0.01))
#net.initialize() 默认方式的初始化

2.5 定义损失函数

在Gluon中, loss模块定义了各种损失函数。直接 使⽤它提供的平⽅损失作为模型的损失函数。

from mxnet.gluon import loss as gloss
loss = gloss.L2Loss() # 平⽅损失⼜称L2范数损失

2.6 定义优化算法

同样,我们也⽆须实现小批量随机梯度下降。在导⼊Gluon后,我们创建⼀个Trainer实例,并指定学习率为0.03的小批量随机梯度下降(sgd)为优化算法。该优化算法将⽤来迭代net实例所有通过add函数嵌套的层所包含的全部参数。这些参数可以通过collect_params函数获取。

trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.03})
#Trainer(params, optimizer, optimizer_params=None, kvstore='device', compression_params=None, update_on_kvstore=None)
#第一个是参数,第二个是优化器,后面的字典是优化器的参数

2.7 训练模型

在使⽤Gluon训练模型时,我们通过调⽤Trainer实例的step函数来迭代模型参数

上⼀节中我们提到,由于变量l是⻓度为batch_size的⼀维NDArray,执⾏l.backward()等价于执⾏l.sum().backward()。按照小批量随机梯度下降的定义,我们在step函数中指明批量⼤小,从而对批量中样本梯度求平均.

num_epochs = 3
for epoch in range(1, num_epochs + 1):
    for X, y in data_iter:         # 读取数据
        with autograd.record():    # 对参数添加梯度属性,开辟空间
            l = loss(net(X), y)    #  计算损失
        l.backward() #向后          #  对小批量求梯度(是一个sum的状态)
        trainer.step(batch_size)# trainer 是一个优化算法,利用step函数中的批量大小,求平均梯度
    # 每一轮迭代得到一个模型,计算该模型下 预测值net(features) 和 真实值labels 的损失
    l = loss(net(features), labels)
    print('epoch %d, loss: %f' % (epoch, l.mean().asnumpy()))
验证

下⾯我们分别⽐较学到的模型参数和真实的模型参数。我们从net获得需要的层,并访问其权重(weight)和偏差(bias)。学到的参数和真实的参数很接近。

dense = net[0]
true_w, dense.weight.data()

out:

([2, -3.4],

[[ 1.9994873 -3.3995135]]
<NDArray 1x2 @cpu(0)>)
true_b, dense.bias.data()

out:

(4.2,

[4.1993074]
<NDArray 1 @cpu(0)>)

附录

1. display.set_matplotlib_formats('svg')

  • 功能描述:选择内联后端的图形格式。(可选)通过JPEG的质量。
# 例如,这将启用PNG和JPEG输出,且JPEG质量为90%
set_matplotlib_formats('png', 'jpeg', quality=90)
  • format(格式)种类:'png','retina','jpeg','svg','pdf'

2.plt.scatter()

  • 功能描述: 化散点图

scatter(x, y, s=None, c=None, marker=None, cmap=None, norm=None, vmin=None, vmax=None, alpha=None, linewidths=None, verts=None, edgecolors=None, *, plotnonfinite=False, data=None, **kwargs)

3.random.shuffle

  • 功能描述:shuffle() 方法将序列的所有元素随机排序
import random

random.shuffle (lst )
  • 参数:可以是一个列表
  • 返回值:没有返回值

4.nd.take()

  • 功能描述:沿给定轴从输入数组中获取元素.

    ​ 此函数使用提供的索引沿特定轴对输入数组进行切片

take(a=None, indices=None, axis=_Null, mode=_Null, out=None, name=None, **kwargs)

5.求导数的流程

  1. 需要先调⽤attach_grad函数来申请存储梯度所需要的内存

  2. 为了减少计算和内存开销,默认条件下MXNet不会记录⽤于求梯度 的计算。我们需要调⽤record函数来要求MXNet记录与求梯度有关的计算。

  3. 接下来我们可以通过调⽤backward函数⾃动求梯度。需要注意的是,如果y不是⼀个标量, MXNet将默认先对y中元素求和得到新的变量,再求该变量有关x的梯度。

  4. MXNet的 运 ⾏ 模 式 包 括 训 练 模 式 和 预 测 模 式。 我 们 可 以 通 过autograd.is_training()来判断运⾏模式。

6.net.add()

  • 功能描述:创建并返回一个新对象。

7.nn.Dense

  • 功能描述:密集层
    • output = activation(dot(input, weight) + bias)其中, activation 是逐元素激活函数
    • 作为‘activation’参数传递时,'weight’时权重矩阵由该层创建,
    • ’bias’是偏差向量由该层创建,(仅在“ use_bias”为“ True”时适用)
class Dense(mxnet.gluon.block.HybridBlock)

Dense(units, activation=None, use_bias=True, flatten=True, dtype='float32', weight_initializer=None, bias_initializer='zeros', in_units=0, **kwargs)
  • 参数
  • units: 输出空间的维数(该层的神经元节点数)

  • acivation:激活函数;如果您未指定任何内容,则不会应用任何激活。(即“线性”激活:“ a(x)= x”)。

  • use_bias:boolean型,是否使用偏置项

  • flatten:输入张量是否应该展平。如果为true,则除第一数据轴外的所有数据都会合拢在一起。如果为false,则输入数据的除最后一个轴外的所有轴均保持不变,并进行转换应用于最后一个轴。

  • dtype:输出嵌入的数据类型。

  • weight_initializer:’kernel’权重矩阵初始化

  • bias_initializer:偏差向量的初始化器

  • in_units:输入数据的大小。

  • 方法解析顺序
1.从mxnet.gluon.block.HybridBlock继承的方法

cast(self, dtype) \ export(self, path, epoch=0, remove_amp_cast=True) \ forward(self, x, *args) \ hybridize(self, active=True, **kwargs) \ infer_shape(self, *args) \ infer_type(self, *args) \ register_child(self, block, name=None) \ register_op_hook(self, callback, monitor_all=False) \ \

2. 从 mxnet.gluon.block.Block继承的方法

apply(self, fn) \ collect_params(self, select=None) \ initialize(self, init=<mxnet.initializer.Uniform object at 0x0000026C81339108>, ctx=None, verbose=False, force_reinit=False) \ load_parameters(self, filename, ctx=None, allow_missing=False, ignore_extra=False, cast_dtype=False, dtype_source='current') \ register_forward_hook(self, hook) \ register_forward_pre_hook(self, hook) \ save_parameters(self, filename, deduplicate=False) \ save_params(self, filename) \ summary(self, *inputs) \ \

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值