美文网首页
动手学深度学习笔记(一)

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

作者: 文武_665b | 来源:发表于2018-09-08 19:58 被阅读0次

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

    总结

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

    相关文章

      网友评论

          本文标题:动手学深度学习笔记(一)

          本文链接:https://www.haomeiwen.com/subject/pfqlgftx.html