动手学深度学习笔记(一)

2018-09-08  本文已影响0人  文武_665b

一、使用NDArray来处理数据

首先从 MXNet 导入ndarray模块。ndndarray的缩写形式。
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

总结

使用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

小结

线性回归

创建数据集

使用如下方法来生成数据
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 回归的步骤跟其非常相似:获取并读取数据、定义模型和损失函数并使用优化算法训练模型。事实上,绝大多数深度学习模型的训练都有着类似的步骤。

上一篇下一篇

猜你喜欢

热点阅读