学习参考:
- 动手学深度学习2.0
- Deep-Learning-with-TensorFlow-book
- pytorchlightning
①如有冒犯、请联系侵删。
②已写完的笔记文章会不定时一直修订修改(删、改、增),以达到集多方教程的精华于一文的目的。
③非常推荐上面(学习参考)的前两个教程,在网上是开源免费的,写的很棒,不管是开始学还是复习巩固都很不错的。
深度学习回顾,专栏内容来源多个书籍笔记、在线笔记、以及自己的感想、想法,佛系更新。争取内容全面而不失重点。完结时间到了也会一直更新下去,已写完的笔记文章会不定时一直修订修改(删、改、增),以达到集多方教程的精华于一文的目的。所有文章涉及的教程都会写在开头、一起学习一起进步。
一、交叉熵回归
交叉熵回归(Cross-Entropy Regression):交叉熵是一种用于衡量两个概率分布之间差异的指标,在机器学习中常用于分类问题的损失函数。交叉熵回归通常用于二分类问题,其中模型输出的是一个概率值,表示样本属于某一类的概率,损失函数是交叉熵损失函数。交叉熵损失函数可以表示为:
二、Softmax 回归
Softmax 回归:Softmax 回归是一种多分类模型,用于将模型的输出转化为多个类别的概率分布。在 Softmax 回归中,模型的最后一层是一个 Softmax 函数,将模型的输出转化为每个类别的概率,使得所有类别的概率之和为 1。
交叉熵回归通常用于二分类问题,而 Softmax 回归用于多分类问题。此外,Softmax 回归中的 Softmax 函数将模型输出转化为概率分布,而交叉熵回归中的损失函数用于衡量二分类模型输出与真实标签的差异。
1.分类问题
分类任务是机器学习和统计学中常见的一类任务,其目标是将数据集中的样本划分到预定义的类别或标签中。分类任务通常涉及预测离散的输出值,即将样本分为不同的类别。例如,将电子邮件分为“垃圾邮件”和“非垃圾邮件”、将图片识别为“猫”、“狗”或“鸟”等。
分类问题是指解决这类分类任务的问题。分类问题的关键在于构建一个分类模型,该模型可以根据输入的特征将样本正确地分配到各个类别中。常用的分类算法包括逻辑回归、支持向量机(SVM)、决策树、随机森林、K近邻(K-NN)等。
一种表示分类数据的简单方法:独热编码(one-hot encoding)。 独热编码是一个向量,它的分量和类别一样多。 类别对应的分量设置为1,其他所有分量设置为0。 从一个图像分类问题开始,假设每个图像属于类别“猫”“鸡”和“狗”中的一个、每次输入是一个 2×2 的灰度图像、每个图像对应四个特征𝑥1,𝑥2,𝑥3,𝑥4。标签 𝑦将是一个三维向量, 其中 (1,0,0) 对应于“猫”、 (0,1,0) 对应于“鸡”、 (0,0,1) 对应于“狗”。
2.网络结构
为了估计所有可能类别的条件概率,需要一个有多个输出的模型,每个类别对应一个输出。 为了解决线性模型的分类问题,需要和输出一样多的仿射函数(affine function)。 每个输出对应于它自己的仿射函数。
在识别猫、鸡、狗的任务中,为每个输入计算三个未规范化的预测(logit):𝑜1、𝑜2和𝑜3:
与线性回归一样,softmax回归也是一个单层神经网络。 由于计算每个输出 𝑜1 、 𝑜2 和 𝑜3取决于 所有输入 𝑥1 、 𝑥2 、 𝑥3 和 𝑥4 , 所以softmax回归的输出层也是全连接层。
通过向量形式表达为𝐨=𝐖𝐱+𝐛, 这是一种更适合数学和编写代码的形式。 由此,已经将所有权重放到一个3×4矩阵中。 对于给定数据样本的特征𝐱, 输出是由权重与输入特征进行矩阵-向量乘法再加上偏置𝐛得到的。
3.softmax运算
将优化参数以最大化观测数据的概率。 为了得到预测结果,将设置一个阈值,如选择具有最大概率的标签。
softmax函数能够将未规范化的预测变换为非负数并且总和为1,同时让模型保持 可导的性质。 为了完成这一目标,我们首先对每个未规范化的预测求幂,这样可以确保输出非负。 为了确保最终输出的概率值总和为1,我们再让每个求幂后的结果除以它们的总和。如下式:
因此, 𝐲̂ 可以视为一个正确的概率分布。 softmax运算不会改变未规范化的预测 𝐨之间的大小次序,只会确定分配给每个类别的概率。 因此,在预测过程中,仍然可以用下式来选择最有可能的类别。
尽管softmax是一个非线性函数,但softmax回归的输出仍然由输入特征的仿射变换决定。 因此,softmax回归是一个线性模型(linear model)。
4.小批量样本的矢量化
为了提高计算效率并且充分利用GPU,通常会对小批量样本的数据执行矢量计算。 softmax回归的矢量计算表达式为:
相对于一次处理一个样本, 小批量样本的矢量化加快了 𝐗和𝐖的矩阵-向量乘法。 由于 𝐗中的每一行代表一个数据样本, 那么softmax运算可以按行(rowwise)执行: 对于 𝐎 的每一行,先对所有项进行幂运算,然后通过求和对它们进行标准化。
5.损失函数
需要一个损失函数来度量预测的效果。 将使用最大似然估计,这与在线性回归中相同。
(1)对数似然
softmax函数给出了一个向量 𝐲̂ , 可以将其视为“对给定任意输入 𝐱的每个类的条件概率”。 例如, 𝑦̂ 1 = 𝑃(𝑦=猫∣𝐱)。 假设整个数据集 {𝐗,𝐘}具有 𝑛个样本, 其中索引 𝑖的样本由特征向量 𝐱(𝑖)和独热标签向量 𝐲(𝑖)组成。 可以将估计值与实际值进行比较:
根据最大似然估计,最大化 𝑃(𝐘∣𝐗) ,相当于最小化负对数似然:
其中,对于任何标签 𝐲和模型预测 𝐲̂ ,损失函数为:
上述损失函数 通常被称为交叉熵损失公式(cross-entropy loss)。
(2)softmax及其导数
由于softmax和相关的损失函数很常见, 因此需要更好地理解它的计算方式。将𝐲̂ =softmax(𝐨) 代入交叉熵损失公式中,得到:
考虑相对于任何未规范化的预测 𝑜𝑗 的导数,得到:
上述最终导数表示,导数是softmax模型分配的概率与实际发生的情况(由独热标签向量表示)之间的差异。
(3)交叉熵损失
定义损失 𝑙, 它是所有标签分布的预期损失值。 此损失称为交叉熵损失(cross-entropy loss),它是分类问题最常用的损失之一。
用一个概率向量表示来阐述整个结果的分布情况,如 (0.1,0.2,0.7), 而不是仅包含二元项的向量 (0,0,1) ,,即观察到的不仅仅是一个结果。
6.模型预测和评估
在训练softmax回归模型后,给出任何样本特征,便可以预测每个输出类别的概率。 通常使用预测概率最高的类别作为输出类别。 如果预测与实际类别(标签)一致,则预测是正确的。最后使用精度(accuracy)来评估模型的性能。 精度等于正确预测数与预测总数之间的比率。
三、Fashion-MNIST图片分类数据集
1.加载数据集
通过框架中的内置函数将Fashion-MNIST数据集下载并读取到内存中。Fashion-MNIST由10个类别的图像组成, 每个类别由训练数据集(train dataset)中的6000张图像 和测试数据集(test dataset)中的1000张图像组成。 因此,训练集和测试集分别包含60000和10000张图像。 测试数据集不会用于训练,只用于评估模型性能。
每个输入图像的高度和宽度均为28像素。 数据集由灰度图像组成,其通道数为1。 为了简洁起见、将高度 ℎ 像素、宽度 𝑤像素图像的形状记为 ℎ×𝑤或( ℎ , 𝑤 )。
%matplotlib inline
import tensorflow as tf
from d2l import tensorflow as d2l
d2l.use_svg_display()
#执行之后会启动下载数据集到本地,不建议开飞机再运行此代码下载、亲测会丢包导致数据不完整而报错。
mnist_train, mnist_test = tf.keras.datasets.fashion_mnist.load_data()
如果下载失败(通常是这个文件t10k-images-idx3-ubyte.gz失败),也可以直接访问 Fashion-MNIST 数据集的官方网站或者仓库:Fashion-MNIST.
-
下载数据集文件夹。
-
将这四个文件放置在您项目的数据集目录中,例如 ~/.keras/datasets/fashion-mnist/。
下载失败的话删除~/.keras/datasets/fashion-mnist/文件夹重新运行直到成功为止。
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-labels-idx1-ubyte.gz
29515/29515 [==============================] - 0s 1us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz
26421880/26421880 [==============================] - 6s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz
5148/5148 [==============================] - 0s 0s/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz
4422102/4422102 [==============================] - 1s 0us/step
Fashion-MNIST中包含的10个类别,分别为t-shirt(T恤)、trouser(裤子)、pullover(套衫)、dress(连衣裙)、coat(外套)、sandal(凉鞋)、shirt(衬衫)、sneaker(运动鞋)、bag(包)和ankle boot(短靴)。 以下函数用于在数字标签索引及其文本名称之间进行转换。
def get_fashion_mnist_labels(labels): #@save
"""返回Fashion-MNIST数据集的文本标签"""
text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat',
'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
return [text_labels[int(i)] for i in labels]
#创建一个函数来可视化这些样本
def show_images(imgs, num_rows, num_cols, titles=None, scale=1.5): #@save
"""绘制图像列表"""
figsize = (num_cols * scale, num_rows * scale)
_, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize)
axes = axes.flatten()
for i, (ax, img) in enumerate(zip(axes, imgs)):
ax.imshow(img.numpy())
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
if titles:
ax.set_title(titles[i])
return axes
查看数据:
X = tf.constant(mnist_train[0][:10])
y = tf.constant(mnist_train[1][:10])
show_images(X, 2, 5, titles=get_fashion_mnist_labels(y));
2.读取小批量数据
为了在读取训练集和测试集时更容易,使用内置的数据迭代器,而不是从零开始创建。在每次迭代中,数据迭代器每次都会读取一小批量数据,大小为batch_size。 通过内置数据迭代器,可以随机打乱了所有样本,从而无偏见地读取小批量。
batch_size = 256
train_iter = tf.data.Dataset.from_tensor_slices(
mnist_train).batch(batch_size).shuffle(len(mnist_train[0]))
数据迭代器是获得更高性能的关键组件。依靠实现良好的数据迭代器,利用高性能计算来避免减慢训练过程。
3.整合组件
def load_data_fashion_mnist(batch_size, resize=None): #@save
"""下载Fashion-MNIST数据集,然后将其加载到内存中"""
mnist_train, mnist_test = tf.keras.datasets.fashion_mnist.load_data()
# 将所有数字除以255,使所有像素值介于0和1之间,在最后添加一个批处理维度,
# 并将标签转换为int32。
process = lambda X, y: (tf.expand_dims(X, axis=3) / 255,
tf.cast(y, dtype='int32'))
resize_fn = lambda X, y: (
tf.image.resize_with_pad(X, resize, resize) if resize else X, y)
return (
tf.data.Dataset.from_tensor_slices(process(*mnist_train)).batch(
batch_size).shuffle(len(mnist_train[0])).map(resize_fn),
tf.data.Dataset.from_tensor_slices(process(*mnist_test)).batch(
batch_size).map(resize_fn))
通过指定resize参数来测试load_data_fashion_mnist函数的图像大小调整功能。
train_iter, test_iter = load_data_fashion_mnist(32, resize=64)
for X, y in train_iter:
print(X.shape, X.dtype, y.shape, y.dtype)
break
四、从0实现softmax回归
1.加载数据集
引入Fashion-MNIST数据集, 并设置数据迭代器的批量大小为256。
import tensorflow as tf
from IPython import display
from d2l import tensorflow as d2l
batch_size = 256
# load_data_fashion_mnist函数已封装在d2l模块里面可以直接调用。也可以调用上面的。
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
2.初始化模型参数
输出与类别一样多。 (因为数据集有10个类别,所以网络输出维度为10)。 因此,权重将构成一个 784×10 的矩阵, 偏置将构成一个 1×10 的行向量。 与线性回归一样,将使用正态分布初始化权重W,偏置初始化为0。
num_inputs = 784
num_outputs = 10
W = tf.Variable(tf.random.normal(shape=(num_inputs, num_outputs),
mean=0, stddev=0.01))
b = tf.Variable(tf.zeros(num_outputs))
3.定义softmax操作
实现softmax由三个步骤组成:
- 对每个项求幂(使用exp);
- 对每一行求和(小批量中每个样本是一行),得到每个样本的规范化常数;
- 将每一行除以其规范化常数,确保结果的和为1。
def softmax(X):
X_exp = tf.exp(X)
partition = tf.reduce_sum(X_exp, 1, keepdims=True)
return X_exp / partition # 这里应用了广播机制
对于任何随机输入,将每个元素变成一个非负数。 此外,依据概率原理,每行总和为1。
4.定义模型
定义softmax操作后,可以实现softmax回归模型。 下面的代码定义了输入如何通过网络映射到输出。 注意,将数据传递到模型之前,使用reshape函数将每张原始图像展平为向量。
def net(X):
return softmax(tf.matmul(tf.reshape(X, (-1, W.shape[0])), W) + b)
5.交叉熵损失函数
实现交叉熵损失函数
def cross_entropy(y_hat, y):
return -tf.math.log(tf.boolean_mask(
y_hat, tf.one_hot(y, depth=y_hat.shape[-1])))
6.分类精度
当预测与标签分类y一致时,即是正确的。 分类精度即正确预测数量与总预测数量之比。 虽然直接优化精度可能很困难(因为精度的计算不可导), 但精度通常是最关心的性能衡量标准,在训练分类器时几乎总会关注它。
def accuracy(y_hat, y): #@save
"""计算预测正确的数量"""
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
y_hat = tf.argmax(y_hat, axis=1)
cmp = tf.cast(y_hat, y.dtype) == y
return float(tf.reduce_sum(tf.cast(cmp, y.dtype)))
同样,对于任意数据迭代器data_iter可访问的数据集, 可以评估在任意模型net的精度。
def evaluate_accuracy(net, data_iter): #@save
"""计算在指定数据集上模型的精度"""
metric = Accumulator(2) # 正确预测数、预测总数
for X, y in data_iter:
metric.add(accuracy(net(X), y), d2l.size(y))
return metric[0] / metric[1]
定义一个实用程序类Accumulator,用于对多个变量进行累加。 在上面的evaluate_accuracy函数中, 在Accumulator实例中创建了2个变量, 分别用于存储正确预测的数量和预测的总数量。 当遍历数据集时,两者都将随着时间的推移而累加。
class Accumulator: #@save
"""在n个变量上累加"""
def __init__(self, n):
self.data = [0.0] * n
def add(self, *args):
self.data = [a + float(b) for a, b in zip(self.data, args)]
def reset(self):
self.data = [0.0] * len(self.data)
def __getitem__(self, idx):
return self.data[idx]
7.训练模型
定义一个函数来训练一个迭代周期。 请注意,updater是更新模型参数的常用函数,它接受批量大小作为参数。 它可以是d2l.sgd函数,也可以是框架的内置优化函数。
def train_epoch_ch3(net, train_iter, loss, updater): #@save
"""训练模型一个迭代周期(定义见第3章)"""
# 训练损失总和、训练准确度总和、样本数
metric = Accumulator(3)
for X, y in train_iter:
# 计算梯度并更新参数
with tf.GradientTape() as tape:
y_hat = net(X)
# Keras内置的损失接受的是(标签,预测),这不同于用户在本书中的实现。
# 本书的实现接受(预测,标签),例如我们上面实现的“交叉熵”
if isinstance(loss, tf.keras.losses.Loss):
l = loss(y, y_hat)
else:
l = loss(y_hat, y)
if isinstance(updater, tf.keras.optimizers.Optimizer):
params = net.trainable_variables
grads = tape.gradient(l, params)
updater.apply_gradients(zip(grads, params))
else:
updater(X.shape[0], tape.gradient(l, updater.params))
# Keras的loss默认返回一个批量的平均损失
l_sum = l * float(tf.size(y)) if isinstance(
loss, tf.keras.losses.Loss) else tf.reduce_sum(l)
metric.add(l_sum, accuracy(y_hat, y), tf.size(y))
# 返回训练损失和训练精度
return metric[0] / metric[2], metric[1] / metric[2]
训练函数实现之前,定义一个在动画中绘制数据的实用程序类Animator, 它能够简化本书其余部分的代码。
class Animator: #@save
"""在动画中绘制数据"""
def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,
ylim=None, xscale='linear', yscale='linear',
fmts=('-', 'm--', 'g-.', 'r:'), nrows=1, ncols=1,
figsize=(3.5, 2.5)):
# 增量地绘制多条线
if legend is None:
legend = []
d2l.use_svg_display()
self.fig, self.axes = d2l.plt.subplots(nrows, ncols, figsize=figsize)
if nrows * ncols == 1:
self.axes = [self.axes, ]
# 使用lambda函数捕获参数
self.config_axes = lambda: d2l.set_axes(
self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
self.X, self.Y, self.fmts = None, None, fmts
def add(self, x, y):
# 向图表中添加多个数据点
if not hasattr(y, "__len__"):
y = [y]
n = len(y)
if not hasattr(x, "__len__"):
x = [x] * n
if not self.X:
self.X = [[] for _ in range(n)]
if not self.Y:
self.Y = [[] for _ in range(n)]
for i, (a, b) in enumerate(zip(x, y)):
if a is not None and b is not None:
self.X[i].append(a)
self.Y[i].append(b)
self.axes[0].cla()
for x, y, fmt in zip(self.X, self.Y, self.fmts):
self.axes[0].plot(x, y, fmt)
self.config_axes()
display.display(self.fig)
display.clear_output(wait=True)
实现一个训练函数, 它会在train_iter访问到的训练数据集上训练一个模型net。 该训练函数将会运行多个迭代周期(由num_epochs指定)。 在每个迭代周期结束时,利用test_iter访问到的测试数据集对模型进行评估。
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater): #@save
"""训练模型(定义见第3章)"""
animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9],
legend=['train loss', 'train acc', 'test acc'])
for epoch in range(num_epochs):
train_metrics = train_epoch_ch3(net, train_iter, loss, updater)
test_acc = evaluate_accuracy(net, test_iter)
animator.add(epoch + 1, train_metrics + (test_acc,))
train_loss, train_acc = train_metrics
assert train_loss < 0.5, train_loss
assert train_acc <= 1 and train_acc > 0.7, train_acc
assert test_acc <= 1 and test_acc > 0.7, test_acc
作为一个从零开始的实现,使用定义的 小批量随机梯度下降来优化模型的损失函数,设置学习率为0.1。
class Updater(): #@save
"""用小批量随机梯度下降法更新参数"""
def __init__(self, params, lr):
self.params = params
self.lr = lr
def __call__(self, batch_size, grads):
d2l.sgd(self.params, grads, self.lr, batch_size)
updater = Updater([W, b], lr=0.1)
之前定义的小批量随机梯度下降如下,已经封装在d2l模块中了:
def sgd(params, grads, lr, batch_size): #@save
"""小批量随机梯度下降"""
for param, grad in zip(params, grads):
param.assign_sub(lr*grad/batch_size)
训练模型50个迭代周期。 请注意,迭代周期(num_epochs)和学习率(lr)都是可调节的超参数。 通过更改它们的值,可以提高模型的分类精度。
num_epochs = 50
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)
8.预测
模型已经准备好对图像进行分类预测。 给定一系列图像,将比较它们的实际标签(文本输出的第一行)和模型预测(文本输出的第二行)。
def predict_ch3(net, test_iter, n=6): #@save
"""预测标签(定义见第3章)"""
for X, y in test_iter:
break
trues = d2l.get_fashion_mnist_labels(y)
preds = d2l.get_fashion_mnist_labels(tf.argmax(net(X), axis=1))
titles = [true +'\n' + pred for true, pred in zip(trues, preds)]
d2l.show_images(
tf.reshape(X[0:n], (n, 28, 28)), 1, n, titles=titles[0:n])
predict_ch3(net, test_iter)
五、简洁实现softmax回归
softmax回归的输出层是一个全连接层,只需在Sequential中添加一个带有10个输出的全连接层。
import tensorflow as tf
from d2l import tensorflow as d2l
#加载数据集
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
# 初始化模型参数,仍然以均值0和标准差0.01随机初始化权重。
net = tf.keras.models.Sequential()
net.add(tf.keras.layers.Flatten(input_shape=(28, 28)))
weight_initializer = tf.keras.initializers.RandomNormal(mean=0.0, stddev=0.01)
net.add(tf.keras.layers.Dense(10, kernel_initializer=weight_initializer))
# 交叉熵损失函数,在交叉熵损失函数中传递未规范化的预测,并同时计算softmax及其对数。
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
# 使用学习率为0.1的小批量随机梯度下降作为优化算法
trainer = tf.keras.optimizers.SGD(learning_rate=0.1)
# 调用之前定义的训练函数训练模型
num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
这里为什么是d2l.train_ch3
函数来训练而不是类似tf内置的model.fit()
呢?因为train_ch3函数里面已经封装好了训练过程和可视化功能。利用Animator类来可视化训练进度。
预测:
d2l.predict_ch3(net, test_iter)