Python深度学习(1)基础

《Python深度学习》笔记整理:第一部分 深度学习基础

代码基于 Keras 框架

深度学习基础

什么是深度学习

神经网络的数学基础

MNIST 分类

1
2
3
# 数据加载
from keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
  • 将训练数据(train_images 和 train_labels)输入神经网络;其次,网络学习将图像和标签关联在一起;最后,网络对 test_images 生成预测

  • Sequential()堆叠

  • 编译,添加损失函数、优化器、监控指标

    1
    network.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])
  • 数据变换为数组并归一化

    1
    2
    train_images = train_images.reshape((60000, 28 * 28))
    train_images = train_images.astype('float32') / 255

数据表示

  • 标量:0维张量

  • 向量:数组,一维张量

  • 张量

    • 多维 Numpy 数组,张量的维度(dimension)通常叫作轴(axis)
    • ndim属性查看张量轴数
    • dtype属性查看数据类型
    • shape属性返回一个整数元组,表示张量沿每个轴的维度大小
  • 张量切片:选择张量的特定元素;可以沿着每个张量轴在任意两个索引之间进行选择;可用负数索引,表示与当前轴终点的相对位置

  • 样本维度:所有数据张量的第一个轴(0 轴,因为索引从 0 开始)都是样本轴

  • 批量维度:批量张量的第一个轴(0 轴)

  • 现实的数据张量

    • 向量:2D 张量,形状为 (samples, features)
      • 每个数据点是一个向量,一个数据批量为向量组成的数组
      • 第一个轴为样本轴,第二个为特征轴
    • 时间序列数据或序列数据:3D 张量,形状为(samples, timesteps, features)
      • 样本轴,时间轴,特征轴
      • 每分钟股票价格数据集、推文数据集
    • 图像:4D张量,形状为(samples, height, width, channels)(samples, channels, height, width)
      • 样本轴,颜色通道轴,高度、宽度(TensorFlow 中约定通道在后,Theano 约定通道在前)
    • 视频:5D张量,形状为 (samples, frames, height, width, channels)(samples, frames, channels, height, width)
      • 视作一系列帧,每帧保存在张量(height, width, color_depth)

张量运算

  • 逐元素运算:运算独立地应用于张量中的每个元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # relu逐元素运算的简单实现(要求输入为2D张量!!)
    output = relu(dot(W, input) + b)
    def naive_relu(x):
    assert len(x.shape) == 2

    x = x.copy()
    for i in range(x.shape[0]):
    for j in range(x.shape[1]):
    x[i, j] = max(x[i, j], 0)
    return x

    def naive_add(x, y):
    assert len(x.shape) == 2
    assert x.shape == y.shape

    x = x.copy() # 避免覆盖输入张量
    for i in range(x.shape[0]):
    for j in range(x.shape[1]):
    x[i, j] += y[i, j]
    return x
  • 广播:维度小的张量和维度大的张量相加,前者会被broadcast以匹配较大张量的形状

    • 添加广播轴,使ndim一致(向前面的轴添加)
    • 小的张量沿广播轴重复,使shape一致
    • 实际过程不会创建新的2D张量,仅通过算法实现
  • 张量点积:将输入张量——元素个数相同——的元素合并,获得标量

    • $*$实现逐元素乘积

    • Keras 与 Numpy 使用dot()

    • 矩阵与向量点积,返回一个向量

    • 两个矩阵点积,获得形状(x.shape[0], y.shape[1]) 的矩阵

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      def naive_matrix_dot(x, y): 
      assert len(x.shape) == 2
      assert len(y.shape) == 2
      assert x.shape[1] == y.shape[0]

      z = np.zeros((x.shape[0], y.shape[1]))
      for i in range(x.shape[0]):
      for j in range(y.shape[1]):
      row_x = x[i, :]
      column_y = y[:, j]
      z[i, j] = naive_vector_dot(row_x, column_y)
      return z
  • 张量变形:reshape();变形前后的元素总数相同;特殊情况为转置np.transpose()

  • 张量运算中,张量元素可以被解释为某种几何空间内点的坐标

基于梯度的优化

  • 一开始权重取较小的随机值——随机初始化

  • 抽取数据批量,前向传播得到预测值,计算损失,计算相对网络参数的梯度,更新权重以降低损失

  • SGD:随机梯度下降,随机指每批数据都是随机抽取

  • 变体:优化器(optimizer)

  • 动量:解决收敛速度和局部最小点——优化过程想象成一个小球从损失函数曲线上滚下来,如果动量足够大,则不会卡在局部最低点

    • 不仅要考虑当前的斜率值(当前的加速度),还要考虑当前的速度(来自于之前的加速度)

    • 更新参数不仅要考虑当前的梯度值,还要考虑上一次的参数更新

      1
      2
      3
      4
      5
      6
      7
      8
      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
  • 利用链式法则计算每个参数对损失值的贡献大小——反向传播

  • 给定一个运算链,并且已知每个运算的导数,框架可以利用链式法则计算运算链的梯度函数,将网络参数值映射为梯度值

神经网络入门

  • 组件分析

    • 全连接层——2D张量;循环层——3D张量;二维卷积层——4D张量
    • Keras 可自动匹配input_shape(第一层需要指定)
    • 对于具有多个损失函数的网络,需要将所有损失函数加权,变为一个标量值
    • 目标函数:
      • 二分类——二元交叉熵
      • 多分类——分类交叉熵
      • 回归——均方误差
      • 序列学习——联结主义时序分类(CTC,connectionist temporal classification)
  • 在 CPU 上运行时,TensorFlow 本身封装一个低层次的张量运算库 Eigen;在 GPU 上运行时,TensorFlow 封装一个高度优化的深度学习运算库 NVIDIA CUDA 深度神经网络库(cuDNN)

  • 定义模型方式:

    • Sequential 类——仅用于层的线性堆叠
    • 函数式 API——用于层组成的有向无环图(第七章之前都是前者)
  • 二分类问题

    • 加载 IMDB 数据集

    • 填充列表,转换为整型张量,Embedding/独热编码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      # 独热编码
      import numpy as np

      def vectorize_sequences(sequences, dimension=10000):
      results = np.zeros((len(sequences), dimension))
      for i, sequence in enumerate(sequences):
      results[i, sequence] = 1. # 将results[i]指定索引设为 1
      return results

      x_train = vectorize_sequences(train_data) # 训练数据向量化,单词序列变成一维二进制向量
      x_test = vectorize_sequences(test_data)
      # 标签向量化
      y_train = np.asarray(train_labels).astype('float32')
      y_test = np.asarray(test_labels).astype('float32')
    • 网络搭建

    • 编译,配置优化器、损失函数和指标(优化器为 rmsprop)

    • 模型训练:model.fit()返回一个 History 对象,其成员 history 是一个包含训练过程中的所有数据的字典dict_keys(['val_acc', 'acc', 'val_loss', 'loss'])——训练和验证过程中监控的指标

    • 模型预测:model.predict()

  • 多分类问题(新闻分类为例,reuters )

    • 数据集的每个样本是一个整数列表,表示单词索引

    • 数据向量化

    • 标签向量化:

      • 标签列表转为整数张量——使用损失函数为sparse_categorical_crossentropy

        1
        2
        y_train = np.array(train_labels) 
        y_test = np.array(test_labels)
* 独热编码标签——使用损失函数为`categorical_crossentropy`

  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def to_one_hot(labels, dimension=46): 
results = np.zeros((len(labels), dimension))
for i, label in enumerate(labels):
results[i, label] = 1.
return results

one_hot_train_labels = to_one_hot(train_labels)
one_hot_test_labels = to_one_hot(test_labels)

# 利用Keras内置方法实现
from keras.utils.np_utils import to_categorical

one_hot_train_labels = to_categorical(train_labels)
one_hot_test_labels = to_categorical(test_labels)
  • 网络搭建(softmax激活最后一层)

  • 编译模型,设置损失函数为categorical_crossentropy以衡量两个分布之间的距离

  • 留出验证集,训练模型

  • 回归问题(房价预测)

    • 加载数据集

      1
      2
      3
      from keras.datasets import boston_housing 

      (train_data, train_targets), (test_data, test_targets) = boston_housing.load_data()
    • 数据标准化——取值范围差异很大的数据输入到神经网络中,这样的操作存在问题,网络可能会自动适应这种取值范围不同的数据——对每个特征(列)做标准化

    • 网络搭建

      • 最后一层只有一个单元,无激活——标量回归 / 预测单一连续值的回归,以预测任意范围内的值
    • 编译网络,损失函数为均方误差MSE(预测值与目标值之差的平方),监控平均绝对误差MAE

    • 数据集小——采用 K 折验证

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      import numpy as np 

      k = 4
      num_val_samples = len(train_data) // k
      num_epochs = 100
      all_scores = []

      for i in range(k):
      print('processing fold #', i)
      val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples] # 准备验证数据
      val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples]

      partial_train_data = np.concatenate(
      [train_data[:i * num_val_samples],
      train_data[(i + 1) * num_val_samples:]],
      axis=0)
      partial_train_targets = np.concatenate(
      [train_targets[:i * num_val_samples],
      train_targets[(i + 1) * num_val_samples:]],
      axis=0)

      model = build_model() # 构建模型(已编译)
      model.fit(partial_train_data, partial_train_targets,
      epochs=num_epochs, batch_size=1, verbose=0) # 训练模型(静默模式,verbose=0)
      val_mse, val_mae = model.evaluate(val_data, val_targets, verbose=0) # 验证数据评估模型
      all_scores.append(val_mae)
    • 对于验证结果,如果绘制的纵轴范围较大,且数据方差相对较大,可以删除前 10 个数据点——因为它们的取值范围与曲线上的其他点不同,并将将每个数据点替换为前面数据点的指数移动平均值,以得到光滑的曲线

机器学习基础

机器学习分支

  • 监督学习:给定一组样本(通常由人工标注),学习将输入数据映射到已知目标(标注)
    • 分类与回归
    • 序列生成:给定一张图像,预测描述图像的文字
    • 语法树检测:给定一个句子,预测其分解生成的语法树
    • 目标检测:给定一张图像,在图中特定目标的周围画一个边界框
    • 图像分割:给定一张图像,在特定物体上画一个像素级的掩模
  • 无监督学习:目的在于数据可视化、数据压缩、数据去噪或理解数据中的相关性;降维与聚类是常用的无监督方法
  • 自监督学习:没有人工标注标签的监督学习。标签仍然存在,但从输入数据中使用启发式算法生成,如自编码器、时序监督学习(过去的帧来预测下一帧,上一个词预测下一个词)
  • 强化学习:智能体(agent)接收有关其环境的信息,并学会选择使某种奖励最大化的行动。例如,网络“观察”视频游戏的屏幕并输出游戏操作,目的是尽可能得高分

评估模型

  • 训练集、验证集和测试集
    • 简单的留出验证
    • K 折验证
    • 带有打乱数据的重复 K 折验证——多次使用 K 折验证,在每次将数据划分为 K 个分区之前都先将数据打乱
  • 评估注意事项
    • 数据代表性:在将数据划分为训练集和测试集之前,通常应该随机打乱数据
    • 时间箭头:根据过去预测未来,必须始终确保测试集中所有数据的时间都晚于训练集数据
    • 数据冗余:如果数据中的某些数据点出现了两次,打乱数据并划分成训练集和验证集会导致训练集和验证集之间的数据冗余;确保训练集和验证集之间没有交集

数据预处理、特征工程与特征学习

  • 数据预处理

    • 神经网络的所有输入和目标都必须是浮点数张量

    • 值标准化——输入数据应当:

      • 取值较小:大部分值都应该在 0~1 范围内

      • 同质性:所有特征的取值都应该在大致相同的范围内

        1
        2
        3
        # x 是一个形状为(samples, features)的二维矩阵
        x -= x.mean(axis=0)
        x /= x.std(axis=0)
    • 通常将缺失的属性值设置为 0 ;如果测试数据中有缺失值,而训练集没有,应该人为生成一些有缺失项的训练样本

  • 特征工程:将数据输入模型之前,利用自己关于数据和神经网络的知识对数据进行硬编码的变换,而不是将原始数据直接输入网络

过拟合

  • 减小网络规模

    • 评估一系列不同的网络架构(在验证集上评估,而不是在测试集上),以便为数据找到最佳的模型大小
    • 开始时选择相对较少的层和参数,然后逐渐增加层的大小或增加新层
  • 添加权重正则化

    • 给定训练数据和一种网络架构,很多组权重值都可以解释这些数据,但简单模型比复杂模型更不容易过拟合——参数值分布的熵更小的模型

    • 强制让模型权重只能取较小的值,限制模型复杂度,使权重值的分布更加规则——损失函数中添加与较大权重值相关的成本

    • 惩罚项只在训练时添加,网络的训练损失比测试损失大很多

    • L1 正则化: 添加的成本与权重系数的绝对值(L1范数)成正比

    • L2 正则化(权重衰减): 添加的成本与权重系数的平方( L2 范数)成正比

      1
      2
      3
      4
      5
      6
      7
      8
      9
      from keras import regularizers 

      model = models.Sequential()
      # 添加 L2 权重正则化
      model.add(layers.Dense(16, kernel_regularizer=regularizers.l2(0.001),
      activation='relu', input_shape=(10000,))) # 该层权重矩阵的每个系数都会使网络总损失增加 0.001 * weight_coefficient_value
      model.add(layers.Dense(16, kernel_regularizer=regularizers.l2(0.001),
      activation='relu'))
      model.add(layers.Dense(1, activation='sigmoid'))
      1
      2
      3
      4
      5
      6
      # 不同的权重正则化项
      from keras import regularizers

      regularizers.l1(0.001)

      regularizers.l1_l2(l1=0.001, l2=0.001)
  • dropout

    • 对某一层使用 dropout,就是在训练过程中随机将该层的一些输出特征舍

    • dropout 比率通常为 0.2~0.5。测试时没有单元被舍弃,该层的输出值需要按 dropout 比率缩小

      1
      2
      3
      4
      5
      model = models.Sequential() 
      model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))
      model.add(layers.Dropout(0.5))
      model.add(layers.Dense(16, activation='relu'))
      model.add(layers.Dropout(0.5))

工作流程

  • 数据集获取

  • 确定指标

  • 确定评估方法

  • 数据准备与格式化,特征工程

  • 开发比随机基准(dumb baseline)好的模型——数字分类中,基准为0.1;二分类中基准为0.5

  • 扩大模型规模,使其过拟合

  • 模型正则化与超参数调节