使用Tensorflow2.0构建简单的神经网络
全连接层
𝑾矩阵叫做全连接层的权值矩阵,𝒃向量叫做全连接层的偏置向量
张量方式实现
- 定义好权值张量𝑾和偏置张量𝒃,并利用 TensorFlow 提供的批量矩阵相乘函数 tf.matmul()即可完成网络层的计算。
x = tf.random.normal([2,784]) w1 = tf.Variable(tf.random.truncated_normal([784, 256], stddev=0.1)) b1 = tf.Variable(tf.zeros([256])) o1 = tf.matmul(x,w1) + b1 o1 = tf.nn.relu(o1) <tf.Tensor: id=31, shape=(2, 256), dtype=float32, numpy= array([[ 1.51279330e+00, 2.36286330e+00, 8.16453278e-01,1.80338228e+00, 4.58602428e+00, 2.54454136e+00,...
|
层方式实现
- layers.Dense(units, activation),通过 layer.Dense 类,只需要指定输出节点数 Units 和激活函数类型 activation 即可
- 输入节点数会根据第一次运算时的输入 shape 确定,同时根据输入、输出节点数 自动创建并初始化权值张量𝑾和偏置张量𝒃,因此在新建类 Dense 实例时,并不会立即创 建权值张量𝑾和偏置张量𝒃,而是需要调用 build 函数或者直接进行一次前向计算,才能完 成网络参数的创建。
x = tf.random.normal([4,28*28]) from tensorflow.keras import layers
fc = layers.Dense(512, activation=tf.nn.relu) h1 = fc(x) <tf.Tensor: id=72, shape=(4, 512), dtype=float32, numpy= array([[0.63339347, 0.21663809, 0. , ..., 1.7361937 , 0.39962345,2.4346168 ],...
fc.kernel fc.bias fc.trainable_variables fc.variables
|
神经网络
通过堆叠网络层可以实现一个复杂的神经网络。
张量方式实现
w1 = tf.Variable(tf.random.truncated_normal([784, 256], stddev=0.1)) b1 = tf.Variable(tf.zeros([256]))
w2 = tf.Variable(tf.random.truncated_normal([256, 128], stddev=0.1)) b2 = tf.Variable(tf.zeros([128]))
w3 = tf.Variable(tf.random.truncated_normal([128, 64], stddev=0.1)) b3 = tf.Variable(tf.zeros([64]))
w4 = tf.Variable(tf.random.truncated_normal([64, 10], stddev=0.1)) b4 = tf.Variable(tf.zeros([10]))
|
在计算时,只需要按照网络层的顺序,将上一层的输出作为当前层的输入即可,重复 直至最后一层,并将输出层的输出作为网络的输出。
在使用 TensorFlow 自动求导功能计算梯度时,需要将前向计算过程放置在 tf.GradientTape()环境中,从而利用 GradientTape 对象的 gradient()方法自动求解参数的梯 度,并利用 optimizers 对象更新参数。
with tf.GradientTape() as tape: h1 = x@w1 + tf.broadcast_to(b1, [x.shape[0], 256]) h1 = tf.nn.relu(h1) h2 = h1@w2 + b2 h2 = tf.nn.relu(h2) h3 = tf.nn.relu(h3)
|
层方式实现
from tensorflow.keras import layers,Sequential fc1 = layers.Dense(256, activation=tf.nn.relu) fc2 = layers.Dense(128, activation=tf.nn.relu) fc3 = layers.Dense(64, activation=tf.nn.relu) fc4 = layers.Dense(10, activation=None)
|
x = tf.random.normal([4,28*28]) h1 = fc1(x) h2 = fc2(h1) h3 = fc3(h2) h4 = fc4(h3)
|
- 也可以通过 Sequential 容器封装成一个网络大类 对象,调用大类的前向计算函数一次即可完成所有层的前向计算
from tensorflow.keras import layers,Sequential
model = Sequential([ layers.Dense(256, activation=tf.nn.relu) , layers.Dense(128, activation=tf.nn.relu) , layers.Dense(64, activation=tf.nn.relu) , layers.Dense(10, activation=None) , ]) out = model(x)
|
优化目标
- 前向传播的最后一步就是完成误差的计算。
激活函数
Sigmoid(Logistic 函数)
- 概率分布 (0,1)区间的输出和概率的分布范围[0,1]契合,可以通过 Sigmoid 函数将输出 转译为概率输出
- 信号强度 一般可以将 0~1 理解为某种信号的强度,如像素的颜色强度,1 代表当前通 道颜色最强,0 代表当前通道无颜色;抑或代表门控值(Gate)的强度,1 代表当前门控 全部开放,0 代表门控关闭
x = tf.linspace(-6.,6.,10) tf.nn.sigmoid(x)
|
Sigmoid 函数在输入值较大或较小时容易出现梯度值接 近于 0 的现象,称为梯度消失。
ReLU
LeakyReLU
- ReLU 函数在𝑥 < 0时导数值恒为 0,也可能会造成梯度弥散现象,所以提出LeakyReLU。
tf.nn.leaky_relu(x, alpha=0.1)
|
Tanh
Tanh 函数能够将𝑥 ∈ 𝑅的输入“压缩”到(−1,1)区间。
输出层的设计
- 𝑜𝑖 ∈ 𝑅𝑑 输出属于整个实数空间,或者某段普通的实数空间,比如函数值趋势的预 测,年龄的预测问题等。
- 𝑜𝑖 ∈ [0,1] 输出值特别地落在[0, 1]的区间,如图片生成,图片像素值一般用[0, 1]区间 的值表示;或者二分类问题的概率,如硬币正反面的概率预测问题。
- 𝑜𝑖 ∈ [0, 1], 𝑖 𝑜𝑖 = 1 输出值落在[0, 1]的区间,并且所有输出值之和为 1,见的如 多分类问题,如 MNIST 手写数字图片识别,图片属于 10 个类别的概率之和应为 1。
- 𝑜𝑖 ∈ [−1, 1] 输出值在[-1, 1]之间
普通实数空间
- 正弦函数曲线预测、年龄的预测、股票走势的预测
- 输出层可以不加激活函数。误差的计算直接基于最后一层 的输出𝒐和真实值𝒚进行计算
𝑔代表了某个具体的误差计算函数,例如 MSE 等。
[0, 1]区间
输出值属于[0, 1]区间也比较常见,比如图片的生成、二分类问题等。在机器学习中, 一般会将图片的像素值归一化到[0,1]区间,如果直接使用输出层的值,像素的值范围会分 布在整个实数空间。为了让像素的值范围映射到[0,1]的有效实数空间,需要在输出层后添 加某个合适的激活函数𝜎,其中 Sigmoid 函数刚好具有此功能。
[0,1]区间,和为 1
z = tf.constant([2.,1.,0.1]) tf.nn.softmax(z)
|
-
通过类 layers.Softmax(axis=-1) 可以方便添加 Softmax 层,其中 axis 参数指定需要进行计算的维度
-
Softmax 与交叉熵损失函数同时实现,tf.keras.losses.categorical_crossentropy(y_true, y_pred, from_logits=False),其中 y_true 代表了 One-hot 编码后的真实标签,y_pred 表示网络的预测值,当 from_logits 设置为 True 时, y_pred 表示须为未经过 Softmax 函数的变量 z;当 from_logits 设置为 False 时,y_pred 表示 为经过 Softmax 函数的输出。
z = tf.random.normal([2,10]) y_onehot = tf.constant([1,3]) y_onehot = tf.one_hot(y_onehot, depth=10)
loss = tf.reduce_mean(loss)
|
- 利用 losses.CategoricalCrossentropy(from_logits)类方式同时实 现 Softmax 与交叉熵损失函数的计算
criteon = keras.losses.CategoricalCrossentropy(from_logits=True) loss = criteon(y_onehot,z) loss
|
[-1, 1]
- 如果希望输出值的范围分布在(−1, 1)区间,可以简单地使用 tanh 激活函数
x = tf.linspace(-6.,6.,10) tf.tanh(x)
|
误差计算
在搭建完模型结构后,下一步就是选择合适的误差函数来计算误差。常见的误差函数 有均方差、交叉熵、KL 散度、Hinge Loss 函数等。
均方差函数主要用于回归问题,交叉熵函数主要用于分类问题。
均方差误差函数
均方差(Mean Squared Error,简称 MSE)误差函数把输出向量和真实向量映射到笛卡尔 坐标系的两个点上,通过计算这两个点之间的欧式距离(准确地说是欧式距离的平方)来衡 量两个向量之间的差距:
o = tf.random.normal([2,10]) y_onehot = tf.constant([1,3]) y_onehot = tf.one_hot(y_onehot, depth=10) loss = keras.losses.MSE(y_onehot, o) loss = tf.reduce_mean(loss)
|
- 通过层方式实现,对应的类为 keras.losses.MeanSquaredError(),和其他层的类一 样,调用__call__函数即可完成前向计算
criteon = keras.losses.MeanSquaredError() loss = criteon(y_onehot,o)
|
交叉熵误差函数
- 熵:代表系统的混乱程度,熵越大代表不确定性越大,信息量越大。
汽车油耗预测实战
采用 Auto MPG 数据集,它记录了各种汽车效能指标与气缸数、重量、马力等其 它因子的真实数据
from __future__ import absolute_import, division, print_function, unicode_literals
import pathlib import os os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
import matplotlib.pyplot as plt import pandas as pd import seaborn as sns import tensorflow as tf
from tensorflow import keras from tensorflow.keras import layers, losses
print(tf.__version__)
dataset_path = keras.utils.get_file("auto-mpg.data", "http://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data")
column_names = ['MPG','Cylinders','Displacement','Horsepower','Weight', 'Acceleration', 'Model Year', 'Origin'] raw_dataset = pd.read_csv(dataset_path, names=column_names, na_values = "?", comment='\t', sep=" ", skipinitialspace=True)
dataset = raw_dataset.copy()
dataset.tail() dataset.head() dataset
dataset.isna().sum() dataset = dataset.dropna() dataset.isna().sum() dataset
origin = dataset.pop('Origin')
dataset['USA'] = (origin == 1)*1.0 dataset['Europe'] = (origin == 2)*1.0 dataset['Japan'] = (origin == 3)*1.0 dataset.tail()
train_dataset = dataset.sample(frac=0.8,random_state=0) test_dataset = dataset.drop(train_dataset.index)
sns.pairplot(train_dataset[["Cylinders", "Displacement", "Weight", "MPG"]], diag_kind="kde")
train_stats = train_dataset.describe() train_stats.pop("MPG") train_stats = train_stats.transpose() train_stats
train_labels = train_dataset.pop('MPG') test_labels = test_dataset.pop('MPG')
def norm(x): return (x - train_stats['mean']) / train_stats['std'] normed_train_data = norm(train_dataset) normed_test_data = norm(test_dataset)
print(normed_train_data.shape,train_labels.shape) print(normed_test_data.shape, test_labels.shape)
class Network(keras.Model): def __init__(self): super(Network, self).__init__() self.fc1 = layers.Dense(64, activation='relu') self.fc2 = layers.Dense(64, activation='relu') self.fc3 = layers.Dense(1)
def call(self, inputs, training=None, mask=None): x = self.fc1(inputs) x = self.fc2(x) x = self.fc3(x)
return x
model = Network()
model.build(input_shape=(None, 9)) model.summary() optimizer = tf.keras.optimizers.RMSprop(0.001) train_db = tf.data.Dataset.from_tensor_slices((normed_train_data.values, train_labels.values)) train_db = train_db.shuffle(100).batch(32)
train_mae_losses = [] test_mae_losses = [] for epoch in range(200): for step, (x,y) in enumerate(train_db): with tf.GradientTape() as tape: out = model(x) loss = tf.reduce_mean(losses.MSE(y, out)) mae_loss = tf.reduce_mean(losses.MAE(y, out))
if step % 10 == 0 : print(epoch, step, float(loss)) grads = tape.gradient(loss, model.trainable_variables) optimizer.apply_gradients(zip(grads, model.trainable_variables))
train_mae_losses.append(float(mae_loss)) out = model(tf.constant(normed_test_data.values)) test_mae_losses.append(tf.reduce_mean(losses.MAE(test_labels, out)))
plt.figure() plt.xlabel('Epoch') plt.ylabel('MAE') plt.plot(train_mae_losses, label='Train')
plt.plot(test_mae_losses, label='Test') plt.legend()
plt.legend() plt.savefig('auto.svg') plt.show()
|