一、使用NDArray来处理数据
首先从 MXNet 导入ndarray
模块。nd
是ndarray
的缩写形式。
from mxnet import ndarray as nd
然后创建3行4列的2d数组
nd.zeros((3,4))
创建随机数组,元素服从均值0,方差1的正态分布。
y=nd.random_normal(0,1,shape=(3,4))
数组的形状
y.shape
数组的大小
y.size
操作符
加法:x + y
乘法:x * y
指数运算:nd.exp(y)
转秩矩阵然后计算矩阵乘法:nd.dot(x,y.T)
广播
当二元操作符左右两边ndarray形状不一样时,系统会尝试将其复制到一个共同的形状。例如a的第0维是3,b的第0维是1,那么a+b时将b沿着第0维复制3遍:
a = nd.arrange(3).reshape((3,1))
b = nd.arrange(2).reshape((1,2))
print('a:',a)
print('b:',b)
print('a+b',a+b)
与Numpy的转换
ndarray可以很方便同numpy进行转换
import numpy as np
X = np.ones((2,3))
Y = nd.array(X) #numpy->mxnet
Z = y.asnumpy(Y) #mxnet->bumpy
替换操作
y = x + y,会将y从现在指向的实例转到新建的实例上去:
x = nd.ones((3,4))
y = nd.ones((3,4))
before = id(y)
y = y + x
id(y) == before
也可以把结果通过[:]写到一个之前开好的数组里:
z = nd.zeros_like(x)
before = id(z)
z[:] = x + y
id(z) == before
这里为x + y创建了临时空间,然后复制到z,更简便的做法是使用操作符的全名版本中的out参数:
nd.elemwise_add(x, y,out=z)
id(z) == before
总结
- NDArray 是 MXNet 中存储和变换数据的主要工具。
- 我们可以轻松地对 NDArray 创建、运算、指定索引,并与 NumPy 之间相互变换。
- ndarray模块提供一系列多维数组操作函数。所有函数列表可以参见NDArrayAPI文档。
使用autograd来自动求导
MXnet提供autograd包来自动化求导过程
import mxnet.adarray as nd
import mxnet.autograd as ag
为变量赋上梯度
对函数f = 2 * (x**2)求关于x的导数;
创建变量x,并赋初值。
x = nd.array([[1, 2],[3, 4]])
当进行求导时,需要一个空间来存放x的导数,这个可以通过attach_grad()来要求系统申请对应的空间。
x.attch_grad()
下面定义f;默认条件下,MXNet不会自动记录和构建用于求导的计算图,我们需要使用autograd里的record()函数来现实要求MXNet记录我们需要求导的程序。
with at.record():
y = x * 2
z = y * x
使用z.backward()来进行求导;如果z不是一个标量,那么z.backward()等价于nd.sum(z).backward()
z.backward()
验证
x.grad == 4 * x
对控制流求导
可以对python的控制流进行求导。
def f(a):
b = a * 2
while nd.norm(b).asscalar() > 0:
b = b * 2
if nd.sum(b).asscalar()>0:
c = b
else:
c = 100 * b
return c
依旧可以用record记录和backward求导。
a = nd.random_normal(shape=3)
a.attach_grad()
with ag.record():
c = f(a)
c.backward()
a.grad == c/a
小结
- MXNet 提供autograd包来自动化求导过程。
- MXNet 的autograd包可以对一般的命令式程序进行求导。
线性回归
创建数据集
使用如下方法来生成数据
y[i] = 2*X[i][0] - 3.4 * X[i][1] + 4.2 + noise
噪音服从均值0和方差0.1的正态分布。
from mxnet import ndarray as nd
from mxnet import autograd
num_inputs = 2
num_examples = 1000
true_w = [2, -3.4]
true_b = 4.2
X = nd.random_normal(shape=(num_examples, num_inputs))
y = true_w[0] * X[:,0] + true_w[1] * X[:,1] + true_b
y += .01 * nd.random_normal(shape=y.shape)
print(X[0], y[0])
数据读取
当我们开始训练神经网络的时候,我们需要不断读取数据块。这里我们定义一个函数它每次返回batch_size个随机的样本和对应的目标。我们通过python的yield来构造一个迭代器。
import random
batch_size = 10
def data_iter():
#产生一个随机索引
idx = list(range(num_examples))
random.shuffle(idx)
for i in range(0, num_examples, baych_size):
j = nd.array(idx[i:min(i+batch_size,num_examples)])
yield nd.take(X, j), nd.take(y, j)
下面代码读取第一个随机数据块
for data, label in data_iter():
print(data, label)
break
初始化模型参数
下面我们随机初始化模型参数
w = nd.random_normal(shape=(num_inputs, 1))
b = nd.zeros((1,))
params = [w, b]
之后训练时我们需要对这些参数求导来更新它们的值,因此我们需要创建它们的梯度。
for param in params:
param.attach_grad()
定义模型
线性模型就是将输入和模型做乘法再加上偏移:
def net(X):
return nd.dot(X, w) + b
损失函数
我们使用常见的平方误差来衡量预测目标和真实目标之间的差距
def square_loss(yhat, y):
#注意这里我们把y变形成yhat的形状来避免自动广播
return (yhat - y.reshape(yhat.shape)) ** 2
优化
这里通过随机梯度下降来求解:
def SGD(params, lr):
for param in params:
param[:] = param - lr * param.grad
训练
训练通常需要迭代数据数次,一次迭代里,我们每次随机读取固定数个数据点,计算梯度并更新模型参数。
epochs = 5
learning_rate = .001
for e in range(epochs):
total_loss = 0
for data, label in data_iter():
with autograd.record():
output = net(data)
loss = square_loss(output, label)
loss.backward()
SGD(params, learning_rate)
total_loss += nd.sum(loss).asscalar()
print(“Epoch %d, average loss: %f” % (e, total_loss/num_examples))
训练完成后可以比较学到的参数和真实参数:
true_w, w
true_b, b
小结
可以看出,仅使用 NDArray 和autograd就可以很容易地实现一个模型。
使用Gluon的线性回归
创建数据集
生成同样的数据集
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
X = nd.random_normal(shape=(num_examples, num_inputs))
y = true_w[0] * X[:,0] + true_w[1] * X[:,1] + true_b
y += .01 * nd.random_normal(shape=y.shape)
print(X[0], y[0])
数据读取
这里使用data模块来读取数据。
batch_size = 10
dataset = gluon.data.ArrayDataset(X,y)
data_iter = gluon.data.DataLoader(dataset, batch_size, shuffle=True)
读取和前面一致:
for data, label in data_iter:
print(data, label)
break
定义模型
gluon提供大量的提前定制好的层,使得我们只需要主要关注使用哪些层来构建模型。例如线性模型就是使用Dense层。
构建模型最简单的办法是利用Sequential来所有层串起来。首先我们定义一个空的模型:
net = gluon.nn.Sequential()
然后加入一个Dense层,唯一要定义的参数就是输出节点的个数,在线性模型里面是1.
net.add(gluon.nn.Dense(1))
(注意这里没有定义这个层的输入节点是多少,这个在之后真正给数据的时候系统会自动赋值。之后会详细解释这个特性)
初始化模型参数
使用默认初始化方法
net.initialize()
损失函数
gluon提供了平方误差函数:
square_loss = gluon.loss.L2Loss()
优化
创建一个Trainer的实例,并且将模型参数传递给它就行
trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.1})
训练
不再调用SGD而是trainer.step来更新模型
epochs = 5
batch_size = 0
for e in range(epochs):
total_loss = 0
for data, label in data_iter():
with autograd.record():
output = net(data)
loss = square_loss(output, label)
loss.backward()
trainer.step(batch_size)
total_loss += nd.sum(loss).asscalar()
print("Epoch %d, average loss: %f"% (e, total_loss/num_examples))
先从net拿到需要的层,然后访问其权重和偏置。
dense = net[0]
true_w, dense.weight.data()
true_b, dense.bias.data()
小结
使用 Gluon 可以更简洁地实现模型。
在 Gluon 中,data模块提供了有关数据处理的工具,nn模块定义了大量神经网络的层,loss模块定义了各种损失函数。
MXNet 的initializer模块提供了模型参数初始化的各种方法。
从0开始的多类逻辑回归
获取数据
分类服饰
from mxnet import gluon
from mxnet import ndarray as nd
def transform(data, label):
return data.astype('float32')/255, label.astype('float32')
mnist_train = gluon.data.vision.FashionMNIST(train=True, transform = transform)
mnist_test = gluon.data.vision.FashionMNIST(train=False, transform = transform)
打印样本的形状和标签
data, label = mnist_train[0]
(‘example shape: ‘, data.shape, 'label:', label)
样本图片显示
import matplotlib.pyplot as plt
def show_images(image):
n = images.shape[0]
_, figs = plt.subplots(1, n, figsize=(15, 15))
for i in range(n):
figs[i].imshow(images[i].reshape((28, 28)).asnumpy())
figs[i].axes.get_xaxis().set_visible(False)
figs[i].axes.get_yaxis().set_visible(False)
plt.show()
def get_text_labels(label):
text_labels = [‘t-shirt', 'trouser', 'pullover', 'dress', ‘coat’,’sandal’, ’shirt’, ’sneaker’, bag', 'ankle boot']
return [text_labels[int(i)] for i in label]
data, label = mnist_train[0:9]
show_images(data)
print(get_text_labels(label))
数据读取
直接使用DataLoader函数
batch_size = 256
train_data = gluon.data.DataLoader(mnist_train, batch_size, shuffle=True)
test_data = gluon.data.DataLoader(mnist_test, batch_size, shuffle=True)
初始化模型参数
输入向量的长度为2828,输出向量长度为10,因此权重的大小为78410:
num_inputs = 784
num_outputs = 10
W = nd.random_normal(shape=(num_inputs, num_outputs))
b = nd.random_normal(shape=num_outputs)
params = [W, b]
为模型参数附上梯度:
for param in params:
param.attach_grad()
定义模型
这里使用softmax函数来将任意的输入归一成合法的概率值。
from mxnet import nd
def softmax(X):
exp = nd.exp(X)
# 假设exp是矩阵,这里对行进行求和,并要求保留axis 1
# 返回(nrows, 1)形状的矩阵
partition = exp.sum(axis=1, keepdims=True)
return exp / partiton
测试:
X = nd.random_normal(shape=(2,5))
x_prob = softmax(X)
print(X)
print(X_prob)
print(X_prob.sum(axis=1))
定义模型:
def net(X):
# -1 系统自己判断
return softmax(nd.dot(X.reshape((-1,num_inputs)), W) + b)
交叉熵损失函数
交叉熵损失函数,将两个概率分布的负交叉熵作为目标值,最小化这个值等价于最大化这两个概率的相似度。
def cross_entropy(yhat, y):
return - nd.pick(nd.log(yhat), y)
计算精度
给定一个概率输出,我们将预测概率最高的那个类作为预测的类,然后通过笔记真实标号我们可以计算精度:
def accuracy(output, label):
return nd.mean(output.argmax(axis=1)==label).asscalar()
可以评估一个模型在这个数据上的精度。
def evaluate_accuracy(data_iterator, net):
acc = 0.
for data, label in data_iterator:
output = net(data)
acc += accuracy(output, label)
return acc / len(data_iterator)
尝试测试:
evaluate_accuracy(test_data, net)
训练
import sys
sys.path.append('..')
from utils import SGD
from mxnet import autograd
learning_rate = .1
for epoch in range(5):
train_loss = 0.
train_acc = 0.
for data, label in train_data:
with autograd.record():
output = net(data)
loss = cross_entropy(output, label)
loss.backward()
#对梯度做平均。这样学习率会对batch size不那么敏感
SGD(params, learning_rate/batch_size)
train_loss += nd.mean(loss).asscalar()
train_acc += accuracy(output, label)
test_acc = evaluate_accuracy(test_data, net)
print(“Epoch %d. Loss: %f, Test acc %f” % (epoch, train_loss/len(train_data), \train_acc/len(train_data),test_acc/len(test_acc))
预测
data, label = mnist_test[0:9]
show_images(data)
print(‘true labels’)
print(get_text_labels(label))
predicted_labels = net(data).argmax(axis=1)
print(‘predicted labels’)
print(get_text_labels(predicted_labels.asnumpy()))
小结
我们可以使用 Softmax 回归做多类别分类。与训练线性回归相比,你会发现训练 Softmax 回归的步骤跟其非常相似:获取并读取数据、定义模型和损失函数并使用优化算法训练模型。事实上,绝大多数深度学习模型的训练都有着类似的步骤。
网友评论