Deep neural networks are completely flexible by design, and there really are no fixed rules when it comes to model architecture. -- David Foster
前言
神经网络 (neural network) 受到人脑的启发,可模仿生物神经元相互传递信号。神经网络就是由神经元组成的系统。如下图所示,神经元有许多树突 (dendrite) 用来输入,有一个轴突 (axon) 用来输出。它具有两个最主要的特性:兴奋性和传导性:
兴奋性是指当刺激强度未达到某一阈限值时,神经冲动不会发生;而当刺激强度达到该值时,神经冲动发生并能瞬时达到最大强度。
传导性是指相邻神经元靠其间一小空隙进行传导。这一小空隙,叫做突触 (synapse),其作用在于传递不同神经元之间的神经冲动,下图突触将神经元 A 和 B 连在一起。
试想很多突触连接很多神经元,不就形成了一个神经网络了吗?没错,类比到人工神经网络 (artificial neural network, ANN),也是由无数的人工神经元组成一起的,比如下左图的浅度神经网络 (shadow neural network) 和下右图的深度神经网络 (deep neural network)。
浅度神经网络适用于结构化数据 (structured data),比如像下图中 excel 里存储的二维数据。
深度神经网络适用于等非结构化数据 (unstructured data),如下图所示的图像、文本、语音类数据。
生成式 AI 模型主要是生成非结构化数据,因此了解深度神经网络是必要的。从本篇开始,我们会模型与代码齐飞,因为
Talk is cheap. Show me the code. -- Linus Torvalds
代码都用 TensorFlow 和 Keras 来实现。
1. 人工神经网络
1.1 神经网络初见
假设下面的神经网络已经被训练好,接着用来预测图片中是否含有笑脸。
单元 A 接收图像里的像素信息。
单元 B 结合了输入像素,当原始图像中有低级特征 (low-level feature) 比如边缘 (edge) 时,发出最强信号。
单元 C 结合了低级特征,当原始图像中有高级特征 (high-level feature) 比如牙齿 (teech) 时,发出最强信号。
单元 D 结合了高级特征,当原始图像中的人微笑时,发出最强信号。
当给这个神经网络“投喂”足够多的数据,即图像,它会“找到”一组权重 (weights) 使得最终预测结果尽可能准确。找权重这个过程其实就是训练神经网络。
对神经网络有个初步认识之后,接下来的任务就是用 Keras 来实现它。
1.2 Keras 训练模型
在 Keras 中实现神经网络需要了解三大要点:
模型 (models)
层 (layers),输入 (input) 和输出 (output)
优化器 (optimizer) 和损失函数 (loss)
用上面的关键词来总结 Keras 训练神经网络的流程:将多个层链接在一起组成模型,将输入数据映射为预测值。然后损失函数将这些预测值输出,并与目标进行比较,得到损失值 (用于衡量网络预测值与预期结果的匹配程度),优化器利用这个损失值来更新网络的权重。
到此终于可以展示点代码了,即便是引入工具库。首先从 tensorflow.keras 库中用于搭建神经网络的模块。
import numpy as np # 用于数组计算的模块
import matplotlib.pyplot as plt # 用于可视化的模块
from tensorflow.keras import models, layers, optimizers, utils, datasets
# models: 用于构建神经网络模型的模块
# layers: 用于构建模型中的层的模块
# optimizers: 用于优化损失函数的模块
# utils: 用于实现其他基本功能的模块
# datasets: 用于下载自带数据的模块
整个神经网络就是一个模型,大框架的代码都来自 models 模块;模型是由多个层组成,而不同的层的代码都来自 layers 模块;模型的第一层是输入层,负责接入输入,模型的最后一层是输出层,负责提供输出,一头一尾都在 models 模块;模型骨架好了,要使它中看又中用就需要 optimizers 模块来训练它了。
1.3 极简神经网络
学过机器学习的同学遇到的第一个模型一定是线性回归,还是单变量的线性回归。给定一组 x 和 y 的数据:
x = [-1, 0, 1, 2, 3, 4]
y = [-3, -1, 1, 3, 5, 7]
找出 x 和 y 之间的关系,当 x_new = 10 时,问 y_new 是多少?
如下图所示,将 x 和 y 以散点的形式画出来,不难发现下图的红线就是 x 和 y 之间的关系。现在想用 Keras 杀鸡用牛刀的构建一个神经网络来求出这条红线。
1.3.1 创建模型
用一层含一个神经元的神经网络即可,代码如下:
toy_model = models.Sequential(
layers.Dense(input_shape=[1], units=1)
)
首先用 models.Sequential() 创建一个空神经网络,然后不断添加层,这里我们添加了 layers.Dense(),叫做稠密层。函数里面的参数 input_shape=[1] 表示输入数据的维度为 1,units=1 表示输出只有 1 个神经元。可视化如下:
1.3.2 检查模型
检查一下模型信息,奇怪的是参数个数 (下图 Param #) 居然是 2 个而不是 1 个。因为从上图来看 y = wx,只应该有 w 一个参数啊。
toy_model.summary()
原因是在计算每层参数个数时,每个神经元默认会连接到一个值为 1 的偏置单元 (bias unit),因此其实上图更准确的样子如下:
这样就对了,此时 y = wx+b,有 w 和 b 两个参数了。
严格来说,其实 Dense() 函数里还是一个参数叫 activation,它字面意思是激活函数,本质上做的事情是将 wx+b 以非线性的模式转换再赋予给y。如果定义激活函数为 g,那么y=g(wx+b)。在 Keras 如果不给 activation 指定值,那么就不需要做任何非线性转换。加上激活函数这个概念,我们给出一个完整的图:
我们的目标就是求出上图中的参数,权重 w 和偏置 b。
1.3.3 编译模型
模型框架搭好后,接着就是优化问题了,在下面 complie() 函数设定参数 optimizer="sgd",即指定优化方法为随机梯度下降 ,设定参数 loss="mean_squared_error",即制定损失函数用均方误差函数。
toy_model.compile(
optimizer="sgd",
loss="mean_squared_error"
)
1.3.4 训练模型
训练模型