zoukankan      html  css  js  c++  java
  • 《Python深度学习》第二章阅读笔记

    第2章 神经网络的数学基础

    张量(tensor)

    一般来说,当前所有机器学习系统都使用张量作为基本数据结构。张量是数字的容器,矩阵就是二维张量。张量是矩阵向任意维度的推广。张量的维度通常称作轴。

    仅包含一个数字的张量叫做标量(也叫 0D张量)

    在 Numpy 中,一个 float32 或 float64 的数字就是一个标量张量(或标量数组)。你可以用 ndim 属性来查看一个 Numpy 张量的轴的个数。标量张量有 0 个轴(ndim == 0)。张量轴的个数也叫作阶(rank)。

    例程如下:

    >>> import numpy as np 
    >>> x = np.array(12) 
    >>> x 
    array(12) 
    >>> x.ndim 
    0

    数组(也叫向量)是一维张量(1D张量),例程如下:

    >>> x = np.array([12, 3, 6, 14, 7]) 
    >>> x 
    array([12, 3, 6, 14, 7]) 
    >>> x.ndim 
    1 

    这个向量有5个元素,也被称为5D向量(注意不是5D张量)

    矩阵是2D张量,3D张量可以想象成立方体,以此类推

    深度学习处理的一般是0D到4D张量,但处理视频数据时可能会遇到5D张量

    张量的属性有:轴数(ndim属性)、形状(shape属性)、数据类型(dtype属性)

    向量数据

    向量数据是最常见的数据,对于这种数据集,每个数据点都被编码为一个向量,因此一个数据批量就被编码为2D张量(即向量组成的数组),其中第一个轴是样本轴,第二个轴是特征轴。举两个例子:

    人口统计数据集,其中包括每个人的年龄、邮编和收入。每个人可以表示为包含 3 个值 的向量,而整个数据集包含100 000 个人,因此可以存储在形状为 (100000, 3) 的 2D 张量中。

    文本文档数据集,我们将每个文档表示为每个单词在其中出现的次数(字典中包含 20 000 个常见单词)。每个文档可以被编码为包含20 000 个值的向量(每个值对应于 字典中每个单词的出现次数),整个数据集包含500 个文档,因此可以存储在形状为 (500, 20000) 的张量中。

    时间序列数据或序列数据

    当时间(或序列顺序)对于数据很重要时,应该将数据存储在带有时间轴的3D 张量中。 每个样本可以被编码为一个向量序列(即2D 张量),因此一个数据批量就被编码为一个3D 张量,根据管理,我们将时间轴作为第二个轴,例子如下:

    股票价格数据集。每一分钟,我们将股票的当前价格、前一分钟的最高价格和前一分钟的最低价格保存下来。因此每分钟被编码为一个3D 向量,整个交易日被编码为一个形状为 (390, 3) 的 2D 张量(一个交易日有 390 分钟),而 250 天的数据则可以保存在一 个形状为 (250, 390, 3) 的 3D 张量中。这里每个样本是一天的股票数据。

    推文数据集。我们将每条推文编码为280个字符组成的序列,而每个字符又来自于128 个字符组成的字母表。在这种情况下,每个字符可以被编码为大小为 128 的二进制向量 (只有在该字符对应的索引位置取值为1,其他元素都为0)。那么每条推文可以被编码为一个形状为 (280, 128) 的 2D 张量,而包含100 万条推文的数据集则可以存储在一 个形状为 (1000000, 280, 128) 的张量中。

    图像数据

    如果图像大小为256×256,那么128 张灰度图像组成的批 量可以保存在一个形状为 (128, 256, 256, 1) 的张量中,而128 张彩色图像组成的批量则可以保存在一个形状为 (128, 256, 256, 3) 的张量中

    图像张量的形状有两种约定:通道在后(channels-last)的约定(在 TensorFlow 中使用)和 通道在前(channels-first)的约定(在Theano 中使用)。Google 的 TensorFlow 机器学习框架将 颜色深度轴放在最后:(samples, height, width, color_depth)。与此相反,Theano 将图像深度轴放在批量轴之后:(samples, color_depth, height, width)。如果采 用 Theano 约定,前面的两个例子将变成 (128, 1, 256, 256) 和 (128, 3, 256, 256)。 Keras 框架同时支持这两种格式。

    视频数据

    视频可以看作一系列帧, 每一帧都是一张彩色图像。由于每一帧都可以保存在一个形状为 (height, width, color_ depth) 的 3D 张量中,因此一系列帧可以保存在一个形状为(frames, height, width, color_depth) 的 4D 张量中,而不同视频组成的批量则可以保存在一个5D 张量中,其形状为 (samples, frames, height, width, color_depth)。 例子如下:

    一个以每秒4 帧采样的60 秒 YouTube 视频片段,视频尺寸为144×256,这个 视频共有 240 帧。4 个这样的视频片段组成的批量将保存在形状为 (4, 240, 144, 256, 3) 的张量中。

    张量运算

    relu运算

    relu(x) = max(x, 0)

    relu运算是一个逐元素运算,即该元素独立地应用于张量中的每个元素,因此这种运算非常适合大规模并行运算。

    add运算

    add运算也是一个逐元素运算

    在实践中处理Numpy 数组时,这些逐元素运算(relu、add、逐元素乘法除法)都是优化好的Numpy 内置函数:

    import numpy as np  
    z = x + y                   #逐元素加法
    z = np.maximum(z, 0.)          #逐元素relu

    广播

    如果将两个形状不同的张量相加,较小的张量会被广播(broadcast),以匹配较大张量的形状。广播包含以下两步。

    (1) 向较小的张量添加轴(叫作广播轴),使其 ndim 与较大的张量相同。

    (2) 将较小的张量沿着新轴重复,使其形状与较大的张量相同。 

    来看一个具体的例子。假设 X 的形状是 (32, 10),y 的形状是 (10,)。首先,我们给 y 添加空的第一个轴,这样 y 的形状变为 (1, 10)。然后,我们将 y 沿着新轴重复32 次,这样得到的张量 Y 的形状为 (32, 10),并且 Y[i, :] == y for i in range(0, 32)。现在, 我们可以将 X 和 Y 相加,因为它们的形状相同。 

    在Python中,numpy会自动完成广播

    import numpy as np 
    
    x = np.random.random((64, 3, 32, 10))     #x是形状为(64,3,32,10)的随机张量
    y = np.random.random((32, 10))          #y是形状为(32,10)的随机张量
    z = np.maximum(x, y)                     #z是形状为(64,3,32,10)的张量,与x相同

    张量点积(张量积)

    在 Numpy、Keras、Theano 和 TensorFlow 中,都是用 * 实现逐元素乘积。TensorFlow 中的 点积使用了不同的语法,但在 Numpy 和 Keras 中,都是用标准的 dot 运算符来实现点积。

    import numpy as np 
     
    z = np.dot(x, y)

    两个向量的点积的结果是一个标量,可以用这个程序来说明一下向量点积的含义:

    def naive_vector_dot(x, y):     
        assert len(x.shape) == 1          
        assert len(y.shape) == 1     
        assert x.shape[0] == y.shape[0] 
        z = 0.     
        for i in range(x.shape[0]):        
            z += x[i] * y[i]     
        return z
        

    对一个矩阵x和一个向量y做点积,返回值是一个向量,其中每个元素是y和x的每一行之间的点击,可以用这个程序来说明一下矩阵与向量点积的含义:

    def naive_matrix_vector_dot(x, y):     
        z = np.zeros(x.shape[0])     
        for i in range(x.shape[0]):         
            z[i] = naive_vector_dot(x[i, :], y)     
        return z     

    两个矩阵的点积即是线代中的矩阵乘法,也就是result[i][j] = x的第i行和y的第j列之间的点积

    张量变形(reshape)

    张量变形是改变张量的行和列,以得到想要的形状,变形后张量的元素总个数是没有发生改变的,相对的顺序也没有发生改变,例子如下:

    >>> x = np.array([[0., 1.],  [2., 3.], [4., 5.]]) 
    >>> print(x.shape) 
    (3, 2) 
    >>> x = x.reshape((6, 1)) 
    >>> x 
    array([[ 0.],       
           [ 1.],       
           [ 2.],       
           [ 3.],        
           [ 4.],        
           [ 5.]]) 
    >>> x = x.reshape((2, 3)) 
    >>> x 
    array([[ 0.,  1.,  2.],        
           [ 3.,  4.,  5.]])     

    张量转置(transpose)

    转置就是线代里面的转置,比如x = np.transpose(x),结果就会把x从(20,300)转置成(300,20)

    神经网络的引擎:基于梯度的优化

    是利用网络中所有运算都是可微(differentiable)的这一事实,计算损失相对于网络系数的梯度(gradient),然后向梯度的反方向改变系数,从而使损失降低。 

    梯度可以理解成张量运算的导数,单变量函数 f(x) 的导数可以看作函数 f 曲线的斜率。同样,gradient(f) (W0) 也可以看作表示 f(W) 在 W0 附近曲率(curvature)的张量

    y_pred = dot(W, x)

    loss_value = loss(y_pred, y)

    这也就是说,如果输入数据 x 和 y 保持不变,那么这可以看作将 W 映射到损失值的函数。即我们可以假设 f(W) = loss_value

    对于一个函数 f(x),你可以通过将 x 向导数的反方向移动一小步来减小 f(x) 的值。同 样,对于张量的函数 f(W),你也可以通过将 W 向梯度的反方向移动来减小 f(W),比如 W1 = W0 - step * gradient(f)(W0),其中 step 是一个很小的比例因子。也就是说,沿着曲 率的反方向移动,直观上来看在曲线上的位置会更低(也就是loss_value更小了)。

    一个基于梯度优化的方案是小批量随机梯度下降(又称为小批量SGD)

    (1) 抽取训练样本 x 和对应目标 y 组成的数据批量。

    (2) 在 x 上运行网络,得到预测值 y_pred。

    (3) 计算网络在这批数据上的损失,用于衡量 y_pred 和 y 之间的距离。

    (4) 计算损失相对于网络参数的梯度[一次反向传播(backward pass)]。

    (5) 将参数沿着梯度的反方向移动一点,比如 W -= step * gradient,从而使这批数据上的损失减小一点。 

    如果使用小学习率(也叫步长)的SGD进行优化,那么优化过程可能陷入局部极小点,导致无法找到全局最小点

    使用动量方法可以避免这样的问题,这一方法的灵感来源于物理学。有一种有用的思维图像, 就是将优化过程想象成一个小球从损失函数曲线上滚下来。如果小球的动量足够大,那么它不会卡在峡谷里,最终会到达全局最小点。动量方法的实现过程是每一步都移动小球,不仅要考虑当前的斜率值(当前的加速度),还要考虑当前的速度(来自于之前的加速度)。这在实践中的是指, 更新参数 w 不仅要考虑当前的梯度值,还要考虑上一次的参数更新,其简单实现如下所示:

    past_velocity = 0. 
    momentum = 0.1        #不变的动量因子
    while loss > 0.01:        
        w, loss, gradient = get_current_parameters()     
        velocity = past_velocity * momentum - learning_rate * gradient     
        w = w + momentum * velocity - learning_rate * gradient     
        past_velocity = velocity     
        update_parameter(w)

    一个例子

    构建深度学习网络来识别手写数字

    (train_images, train_labels), (test_images, test_labels) = mnist.load_data() 
    # 把输入图像保存在28*28的float32的Numpy张量中 
    train_images = train_images.reshape((60000, 28 * 28))     train_images = train_images.astype('float32') / 255 
    # 把输出图像保存在28*28的float32的Numpy张量中
    test_images = test_images.reshape((10000, 28 * 28)) test_images = test_images.astype('float32') / 255 
    # 开始构建网络(这个网络包含了两个Dense层)
    network = models.Sequential() network.add(layers.Dense(512, activation='relu', input_shape=(28 * 28,))) 
    network.add(layers.Dense(10, activation='softmax')) # 进行网络编译(categorical_crossentropy 是损失函数,是用于学习权重张量的反馈信号,在训练阶段应使它最小化。
    # 减小损失是通过小批量随机梯度下降来实现的。 梯度下降的具体方法由第一个参数给定,即 rmsprop 优化器)
    network.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy']) # 开始训练网络
    # 在调用 fit 时:网络开始在训练数据上进行迭代(每个小批量包含 128 个样本),共迭代5次[在所有训练数据上迭代一次叫作一个轮次(epoch)]。
    network.fit(train_images, train_labels, epochs=5, batch_size=128)

    本章总结

    • 学习是指找到一组模型参数,使得在给定的训练数据样本和对应目标值上的损失函数最小化。
    • 学习的过程:随机选取包含数据样本及其目标值的批量,并计算批量损失相对于网络参数的梯度。随后将网络参数沿着梯度的反方向稍稍移动(移动距离由学习率指定)。
    • 整个学习过程之所以能够实现,是因为神经网络是一系列可微分的张量运算,因此可以利用求导的链式法则来得到梯度函数,这个函数将当前参数和当前数据批量映射为一个梯度值。
    • 将数据输入网络之前,需要先定义损失和优化器。
    • 损失是在训练过程中需要最小化的量,因此,它应该能够衡量当前任务是否已成功解决。
    • 优化器是使用损失梯度更新参数的具体方式,比如RMSProp 优化器、带动量的随机梯度下降(SGD)等。

    一些疑惑

    关于链式求导(反向传播算法),没有太理解,回头学懂了再来补充

  • 相关阅读:
    辨析六种单例模式
    理解模板模式
    常用DOM API总结
    自我剖析——一天的效率
    对两个数组合并,并去重
    Qt 创建一个QtDesinger第三方控件
    Qt中QComboBox中自定义界面使用stylesheet实现下拉按钮独立效果
    内核对象 windows操作系统
    python之基本内容
    python之总体理解
  • 原文地址:https://www.cnblogs.com/Briddle-ch/p/13259802.html
Copyright © 2011-2022 走看看