【动手学深度学习】#2线性神经网络
主要参考学习资料:
《动手学深度学习》阿斯顿·张 等 著
【动手学深度学习 PyTorch版】哔哩哔哩@跟李牧学AI
目录
- 2.1 线性回归
- 2.1.1 线性回归的基本元素
- 线性模型
- 损失函数
- 解析解
- 随机梯度下降
- 2.1.3 最大似然估计
- 2.2 线性回归从零开始实现
- 2.2.1 生成数据集
- 2.2.2 读取数据集
- 2.2.3 初始化模型参数
- 2.2.4 定义模型
- 2.2.5 定义损失函数
- 2.2.6 定义优化算法
- 2.2.7 训练
- 2.3 线性回归的简洁实现
- 2.3.1 生成数据集
- 2.3.2 读取数据集
- 2.3.3 定义模型
- 2.3.4 初始化模型参数
- 2.3.5 定义损失函数
- 2.3.6 定义优化算法
- 2.3.7 训练
- 2.4 softmax回归
- 2.4.1 分类问题
- 2.4.2 网络架构
- 2.4.3 全连接层的参数开销
- 2.4.4 softmax运算
- 2.4.5 小批量样本的向量化
- 2.4.6 损失函数
- 2.4.8 模型预测和评估
- 2.5 图像分类数据集
- 2.5.1 读取数据集
- 2.5.2 读取小批量
- 2.5.3 整合所有组件
- 2.6 softmax回归的从零开始实现
- 2.6.1 初始化模型参数
- 2.6.2 定义softmax操作
- 2.6.3 定义模型
- 2.6.4 定义损失函数
- 2.6.5 分类精度
- 2.6.6 训练
- 2.6.7 预测
- 2.7 softmax回归的简洁实现
- 2.7.1 代码
- 2.7.2 重新审视softmax的实现
2.1 线性回归
2.1.1 线性回归的基本元素
线性模型
对于存储特征的向量 x \boldsymbol x x、存储权重的向量 w \boldsymbol w w和偏置 b b b,预测结果 y ^ \hat y y^表示为:
y ^ = w ⊤ x + b \hat y=\boldsymbol w^\top\boldsymbol x+b y^=w⊤x+b
该式是输入特征的一个仿射变换,其特点是通过加权和对特征进行线性变换,并通过偏置项进行平移。
将 n n n个样本的向量 x \boldsymbol x x合并为一个特征集合矩阵 X \boldsymbol X X,其中每一行是一个样本,每一列是一种特征。进而预测值 y ^ \hat{\boldsymbol y} y^可以通过矩阵-向量乘法表示为:
y ^ = X w + b \hat{\boldsymbol y}=\boldsymbol {Xw}+b y^=Xw+b
损失函数
回归问题中最常用的损失函数是平方误差函数。对于样本 i i i的预测值 y ^ ( i ) \hat y^{(i)} y^(i)及其相应的真实标签 y ( i ) y^{(i)} y(i),平方误差为:
l ( i ) ( w , b ) = 1 2 ( y ^ ( i ) − y ( i ) ) \displaystyle l^{(i)}(\boldsymbol w,b)=\frac12(\hat y^{(i)}-y^{(i)}) l(i)(w,b)=21(y^(i)−y(i))
常数 1 2 \displaystyle\frac12 21使该式求导后常系数为 1 1 1,形式更简单。
为了度量模型在整个数据集上的预测质量,我们需计算在训练集 n n n个样本上的损失均值:
L ( w , b ) = 1 n ∑ i = 1 n l ( i ) ( w , b ) = 1 2 n ∣ ∣ y − X w − b ∣ ∣ 2 L(\boldsymbol w,b)=\displaystyle\frac1n\sum^n_{i=1}l^{(i)}(\boldsymbol w,b)=\frac1{2n}||\boldsymbol y-\boldsymbol {Xw}-b||^2 L(w,b)=n1i=1∑nl(i)(w,b)=2n1∣∣y−Xw−b∣∣2
在训练模型时,我们希望寻找一组参数 ( w ∗ , b ∗ ) (\boldsymbol w^*,b^*) (w∗,b∗)使之最小化:
w ∗ , b ∗ = a r g m i n w , b L ( w , b ) \boldsymbol w^*,b^*=\underset{\boldsymbol w,b}{\mathrm{argmin}}L(\boldsymbol w,b) w∗,b∗=w,bargminL(w,b)
解析解
可以用公式表示的解叫做解析解。
将偏差加入权重:
X ← [ X , 1 ] \boldsymbol X\leftarrow[\boldsymbol X,1] X←[X,1]
w ← [ w b ] \boldsymbol w\leftarrow\begin{bmatrix}\boldsymbol w\\b\end{bmatrix} w←[wb]
将 ∣ ∣ y − X w ∣ ∣ 2 ||\boldsymbol y-\boldsymbol{Xw}||^2 ∣∣y−Xw∣∣2关于 w \boldsymbol w w的导数设为 0 0 0,得到解析解:
∂ ∂ w ∣ ∣ y − X w ∣ ∣ 2 = 0 \displaystyle\frac\partial{\partial\boldsymbol w}||\boldsymbol y-\boldsymbol{Xw}||^2=0 ∂w∂∣∣y−Xw∣∣2=0
w ∗ = ( X ⊤ X ) − 1 X ⊤ y \boldsymbol w^*=(\boldsymbol X^\top\boldsymbol X)^{-1}\boldsymbol X^\top\boldsymbol y w∗=(X⊤X)−1X⊤y
随机梯度下降
无法得到解析解时,可以使用梯度下降,其最简单的方法是计算损失函数关于模型参数的梯度,将参数沿梯度下降方向以学习率 η \eta η更新。每次更新参数遍历整个数据集很慢,为此通常会在计算更新时随机抽取批量大小为 ∣ B ∣ |B| ∣B∣的一小批样本 B B B,称为小批量随机梯度下降:
( w , b ) ← ( w , b ) − η ∣ B ∣ ∑ i ∈ B ∂ ( w , b ) l ( i ) ( w , b ) (\boldsymbol w,b)\leftarrow(\boldsymbol w,b)-\displaystyle\frac\eta{|B|}\sum_{i\in B}\partial_{(\boldsymbol w,b)}l^{(i)}(\boldsymbol w,b) (w,b)←(w,b)−∣B∣ηi∈B∑∂(w,b)l(i)(w,b)
批量大小和学习率通常预先手动指定,而不是通过模型训练得到的。这些可以调整但不在训练过程中更新的参数称为超参数,调参是选择超参数的过程。
线性回归恰好在整个域中只有一个最小值,但对复杂的模型来说损失平面上通常包含多个最小值。对调参而言,和使模型在训练集上的损失达到最小值相比更难的是在从未见过的数据上实现较小的损失,这一挑战称为泛化。
2.1.3 最大似然估计
在线性回归中使用均方误差损失函数的一个理由来自最大似然估计,似然即可能性。
最大似然估计认为,如果从一个分布参数未知的概率分布中随机抽取到一组样本,那么分布参数应该使该组样本在该概率分布下被抽取到的可能性达到最大。
假设在线性回归模型中的观测包含服从正态分布的噪声:
y = w ⊤ x + b + ϵ y=\boldsymbol w^\top\boldsymbol x+b+\epsilon y=w⊤x+b+ϵ
ϵ ∼ N ( 0 , σ 2 ) \epsilon\sim N(0,\sigma^2) ϵ∼N(0,σ2)
那么通过给定的 x \boldsymbol x x观测到特定 y y y的似然为:
P ( y ∣ x ) = 1 2 π σ 2 exp ( − 1 2 σ 2 ( y − w ⊤ x − b ) 2 ) P(y|\boldsymbol x)=\displaystyle\frac1{\sqrt{2\pi\sigma^2}}\exp\left(-\frac1{2\sigma^2}(y-\boldsymbol w^\top\boldsymbol x-b)^2\right) P(y∣x)=2πσ21exp(−2σ21(y−w⊤x−b)2)
则整个数据集的似然为:
P ( y ∣ X ) = ∏ i = 1 n P ( y ( i ) ∣ x ( i ) ) P(\boldsymbol y|\boldsymbol X)=\displaystyle\prod^n_{i=1}P(y^{(i)}|\boldsymbol x^{(i)}) P(y∣X)=i=1∏nP(y(i)∣x(i))
参数 w \boldsymbol w w和 b b b的最优值是使上式最大的值,根据极大似然估计选择的估计量称为极大似然估计量。将上式取负对数(对数简化乘积,负号为了贴合优化过程为最小化的惯例):
− log P ( y ∣ X ) = ∑ i = 1 n 1 2 log ( 2 π σ 2 ) + 1 2 σ 2 ( y ( i ) − w ⊤ x ( i ) − b ) 2 -\log P(\boldsymbol y|\boldsymbol X)=\displaystyle\sum^n_{i=1}\frac12\log(2\pi\sigma^2)+\frac1{2\sigma^2}(y^{(i)}-\boldsymbol w^\top\boldsymbol x^{(i)}-b)^2 −logP(y∣X)=i=1∑n21log(2πσ2)+2σ21(y(i)−w⊤x(i)−b)2
将 σ \sigma σ看做常数并忽略,保留的部分即均方误差。
2.2 线性回归从零开始实现
导入库:
import random
import torch
import matplotlib.pyplot as plt
2.2.1 生成数据集
使用线性模型参数 w = [ 2 , − 3.4 ] ⊤ \boldsymbol w=[2,-3.4]^\top w=[2,−3.4]⊤、 b = 4.2 b=4.2 b=4.2和噪声项 ϵ \epsilon ϵ生成每个样本包含两个特征的数据集及其标签:
def synthetic_data(w, b, num_examples): """生成 y = Xw + b + ε"""#生成元素服从标准正态分布、形状为(num_examples, len(w))的特征矩阵X = torch.normal(0, 1, (num_examples, len(w))) #计算线性模型的预测值y = torch.matmul(X, w) + b #向y添加均值为0、标准差为0.01的噪声以模拟随机误差y += torch.normal(0, 0.01, y.shape) #将特征和转换为列向量的标签返回,-1代表自动计算长度return X, y.reshape(-1, 1)#设置真实的权重和偏置
true_w = torch.tensor([2, -3.4])
true_b = 4.2
#生成1000个样本
features, labels = synthetic_data(true_w, true_b, 1000) #生成样本第二个特征和标签的散点图,部分python版本需detach才能转换为numpy
plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1)
plt.show()
2.2.2 读取数据集
def data_iter(batch_size, features, labels): """接收批量大小、特征矩阵和标签向量,生成大小为batch_size的小批量特征和标签"""#生成样本的下标列表num_examples = len(features) indices = list(range(num_examples)) #shuffle函数将下标列表打乱random.shuffle(indices) #从0开始抽取batch_size个乱序下标for i in range(0, num_examples, batch_size): #min函数防止索引超出列表batch_indices = torch.tensor(indices[i: min(i + batch_size, num_examples)]) #yield不断迭代直到返回完所有值yield features[batch_indices], labels[batch_indices] batch_size = 10
2.2.3 初始化模型参数
从标准正态分布中抽样随机数来初始化权重,并将偏置初始化为0,作为模型在训练之前的参数:
w = torch.normal(0, 1, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
2.2.4 定义模型
def linreg(X, w, b): """线性回归模型"""return torch.matmul(X, w) + b
2.2.5 定义损失函数
def squared_loss(y_hat, y): """均方损失"""return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
2.2.6 定义优化算法
def sgd(params, lr, batch_size): """小批量随机梯度下降"""#更新时停止梯度计算with torch.no_grad(): for param in params: #每个参数减去学习率乘以损失函数的均值param -= lr * param.grad / batch_size #手动清空梯度param.grad.zero_()
2.2.7 训练
#设置学习率
lr = 0.03
#设置数据集遍历次数
num_epochs = 3
#设置模型和损失函数
net = linreg
loss = squared_loss for epoch in range(num_epochs): for X, y in data_iter(batch_size, features, labels): #小批量损失,l形状(batch_size,1)l = loss(net(X, w, b), y) #向量先求和再梯度l.sum().backward() #更新权重和偏置sgd([w, b], lr, batch_size) #评价模型训练结果with torch.no_grad(): #计算整个数据集的损失train_l = loss(net(features, w, b), labels) #输出遍历次数和损失均值print(f'epoch: {epoch + 1}, loss: {float(train_l.mean()):f}')
运行结果:
epoch: 1, loss: 0.027973
epoch: 2, loss: 0.000102
epoch: 3, loss: 0.000055
2.3 线性回归的简洁实现
使用深度学习框架可以简洁地实现线性回归模型。
2.3.1 生成数据集
import torch
from torch.utils import data
#博主没能安装教材配套d2l库,于是将之前写好的synthetic_data函数放进自己的d2l.py并调用
from d2l import synthetic_data true_w = torch.Tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
2.3.2 读取数据集
def load_array(data_arrays, batch_size, is_train=True): """构造PyTorch数据迭代器"""#TensorDataset方法将数据打包成torch的dataset类dataset = data.TensorDataset(*data_arrays) #DataLoader方法对dataset类进行随机抽样,shuffle参数设置是否打乱return data.DataLoader(dataset, batch_size, shuffle=is_train) batch_size = 10
data_iter = load_array((features, labels), batch_size)
2.3.3 定义模型
#nn包含了大量定义好的神经网络层
from torch import nn #Sequential实例能将神经网络层按顺序组织,本次只用到有2个输入维度和1个输出维度单层线性神经网络
net = nn.Sequential(nn.Linear(2, 1))
2.3.4 初始化模型参数
#索引0选中网络中的第一层,weight.data和bias.data方法访问参数,用normal_和fill_方法重写参数
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
2.3.5 定义损失函数
#均方误差使用MSELoss类
loss = nn.MSELoss()
2.3.6 定义优化算法
#小批量随机梯度下降使用optim模块的SGD实例,parameter方法获得参数并传入,lr参数设置学习率
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
2.3.7 训练
num_epochs = 3
for epoch in range(num_epochs): for X, y in data_iter: #net自带模型参数,无需传入w和bl = loss(net(X), y) trainer.zero_grad() #torch损失函数自带求和,无需使用sum方法l.backward() #优化器的step方法自动更新模型trainer.step() l = loss(net(features), labels) print(f'epoch {epoch + 1}, loss {l:f}')
运行结果:
epoch 1, loss 0.000237
epoch 2, loss 0.000112
epoch 3, loss 0.000111
2.4 softmax回归
2.4.1 分类问题
回归问题关心估计一个连续值,而分类问题预测一个离散类别。
为了将回归问题转化为分类问题,我们先引入一种表示分类数据的简单方法:独热编码。
独热编码是一个分量和类别一样多的向量,类别对应的分量设置为 1 1 1,其他所有分量设置为 0 0 0。对于涉及三个类别的分类问题,其标签 y y y将是一个三维向量:
y ∈ { ( 1 , 0 , 0 ) , ( 0 , 1 , 0 ) , ( 0 , 0 , 1 ) } y\in\{(1,0,0),(0,1,0),(0,0,1)\} y∈{(1,0,0),(0,1,0),(0,0,1)}
2.4.2 网络架构
为了估计所有可能类别的条件概率,我们需要一个有多输出的模型,每个类别对应一个输出,每个输出都有自己的仿射函数。假设我们有 4 4 4个特征和 3 3 3个类别,则需要 12 12 12个权重标量, 3 3 3个偏置标量,进而为每个输入计算 3 3 3个未规范化的预测:
o 1 = x 1 w 11 + x 2 w 12 + x 3 w 13 + x 4 w 14 + b 1 o_1=x_1w_{11}+x_2w_{12}+x_3w_{13}+x_4w_{14}+b_1 o1=x1w11+x2w12+x3w13+x4w14+b1
o 2 = x 1 w 21 + x 2 w 22 + x 3 w 23 + x 4 w 24 + b 2 o_2=x_1w_{21}+x_2w_{22}+x_3w_{23}+x_4w_{24}+b_2 o2=x1w21+x2w22+x3w23+x4w24+b2
o 3 = x 1 w 31 + x 2 w 32 + x 3 w 33 + x 4 w 34 + b 3 o_3=x_1w_{31}+x_2w_{32}+x_3w_{33}+x_4w_{34}+b_3 o3=x1w31+x2w32+x3w33+x4w34+b3
可以用神经网络图来描述该计算过程:
每个输出都取决于所有输入的神经网络,其输出层被称为全连接层。
用线性代数更简洁地表达模型:
o = W x + b \boldsymbol o=\boldsymbol{Wx}+\boldsymbol b o=Wx+b
2.4.3 全连接层的参数开销
对于任何具有 d d d个输入和 q q q个输出的全连接层,参数开销为 O ( d q ) O(dq) O(dq),在实践中数字过高。在实际应用中可以灵活指定超参数 n n n使参数开销减少到 O ( d q / n ) O(dq/n) O(dq/n),以便在参数节省和模型有效性之间进行权衡。
2.4.4 softmax运算
我们希望模型的输出 y ^ j \hat y_j y^j可以视为属于类 j j j的概率,并选择具有最大输出值的类别 a r g m a x j y j \mathrm{argmax}_jy_j argmaxjyj作为预测。但未规范化的预测无法直接视为输出,因为我们没有限制这些输出数值的总和为 1 1 1,而且输出可以是负值,违反了概率论基本公理。
softmax函数能将未规范化的预测变换为非负数并且总和为 1 1 1,同时让模型保持可导的性质:
y ^ = s o f t m a x ( o ) \hat{\boldsymbol y}=\mathrm{softmax}(\boldsymbol o) y^=softmax(o)
其中 y ^ j = exp ( o j ) ∑ k exp ( o k ) \hat y_j=\displaystyle\frac{\exp(o_j)}{\displaystyle\sum_k\exp(o_k)} y^j=k∑exp(ok)exp(oj)
softmax不改变预测结果的大小次序,因此仍可以用下式选择最有可能的类别:
a r g m a x j y ^ j = a r g m a x j o j \mathrm{argmax}_j\hat y_j=\mathrm{argmax}_jo_j argmaxjy^j=argmaxjoj
尽管softmax是非线性函数,但softmax回归的输出仍然由输入特征的仿射变换决定,因此softmax回归是线性模型。
2.4.5 小批量样本的向量化
为了提高计算效率并充分利用GPU,我们通常针对小批量样本数据进行向量计算。
对于一个批量的样本 X \boldsymbol X X,softmax回归的向量计算表达式为:
O = X W + b \boldsymbol O=\boldsymbol{XW}+\boldsymbol b O=XW+b
Y ^ = s o f t m a x ( O ) \widehat{\boldsymbol Y}=\mathrm{softmax}(\boldsymbol O) Y =softmax(O)
X \boldsymbol X X的每一行代表一个数据样本,因此softmax运算也对 O \boldsymbol O O按行执行。
2.4.6 损失函数
softmax回归的损失函数为交叉熵损失:
l ( y , y ^ ) = − ∑ j = 1 q y j log y ^ j l(\boldsymbol y,\hat{\boldsymbol y})=-\displaystyle\sum^q_{j=1}y_j\log\hat y_j l(y,y^)=−j=1∑qyjlogy^j
为了理解它,我们将softmax函数代入:
l ( y , y ^ ) = − ∑ j = 1 q y j exp ( o j ) ∑ k = 1 q exp ( o k ) l(\boldsymbol y,\hat{\boldsymbol y})=-\displaystyle\sum^q_{j=1}y_j\frac{\exp(o_j)}{\displaystyle\sum^q_{k=1}\exp(o_k)} l(y,y^)=−j=1∑qyjk=1∑qexp(ok)exp(oj)
= ∑ j = 1 q y j log ∑ k = 1 q exp ( o k ) − ∑ j = 1 q y j o j =\displaystyle\sum^q_{j=1}y_j\log\sum^q_{k=1}\exp(o_k)-\sum^q_{j=1}y_jo_j =j=1∑qyjlogk=1∑qexp(ok)−j=1∑qyjoj
= log ∑ k = 1 q exp ( o k ) − ∑ j = 1 q y j o j =\displaystyle\log\sum^q_{k=1}\exp(o_k)-\sum^q_{j=1}y_jo_j =logk=1∑qexp(ok)−j=1∑qyjoj
再对其中一个预测 o j o_j oj求导:
∂ o j l ( y , y ^ ) = exp ( o j ) ∑ k = 1 q exp ( o k ) − y j = s o f t m a x ( o ) j − y j \displaystyle\partial_{o_j}l(\boldsymbol y,\hat{\boldsymbol y})=\frac{\exp(o_j)}{\displaystyle\sum^q_{k=1}\exp(o_k)}-y_j=\mathrm{softmax}(\boldsymbol o)_j-y_j ∂ojl(y,y^)=k=1∑qexp(ok)exp(oj)−yj=softmax(o)j−yj
其导数即softmax模型分配的概率与实际发生的情况之差,符合梯度下降的目标。
2.4.8 模型预测和评估
接下来我们将使用精度评估模型的性能,其等于正确预测数与预测总数的比例。
2.5 图像分类数据集
MINIST数据集是图像分类中广泛使用的数据集之一,但作为基准数据集过于简单。我们将使用类似但更复杂的Fashion-MINIST数据集。
2.5.1 读取数据集
import torch
import torchvision
from matplotlib import pyplot as plt
from torch.utils import data
from torchvision import transforms #ToTensor实例将图像数据从PIL类型变换成32位浮点数形式,并除以255使得所有像素的均值为0~1
trans = transforms.ToTensor()
#下载Fashion-MINIST数据集
minist_train = torchvision.datasets.FashionMNIST(root='./data', train=True, transform=trans, download=True)
minist_test = torchvision.datasets.FashionMNIST(root='./data', train=False, transform=trans, download=True) def get_fashion_minist_labels(labels): """返回Fashion-MINIST数据集的文本标签"""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): """绘制图像列表"""#根据网格的行列数和缩放比计算图像显示大小figsize = (num_cols * scale, num_rows * scale) #创建对应行列数和图像大小的网格_, axes = plt.subplots(num_rows, num_cols, figsize=figsize) #将二维子图数组展平为一维数组方便遍历axes = axes.flatten() #遍历图像并显示,zip将axes和imgs中的元素一一配对并由enumerate获取索引后传给循环变量for i, (ax, img) in enumerate(zip(axes, imgs)): #如果img是张量则转换为numpy数组显示if torch.is_tensor(img): ax.imshow(img.numpy()) else: ax.imshow(img) #隐藏坐标轴ax.axes.get_xaxis().set_visible(False) ax.axes.get_yaxis().set_visible(False) #如果提供了标题则设置标题if titles: ax.set_title(titles[i]) #返回子图数组return axes #读取前18个样本图像及标签
X, y = next(iter(data.DataLoader(minist_train, batch_size=18)))
#将图像移除通道维度后显示为2行9列
show_images(X.reshape(18, 28, 28), 2, 9, titles=get_fashion_minist_labels(y))
plt.show()
显示结果:
2.5.2 读取小批量
batch_size = 256 def get_dataloader_workers(): """使用4个进程来读取数据""" return 4 #读取小批量
train_iter = data.DataLoader(minist_train, batch_size, shuffle=True, num_workers=get_dataloader_workers())
2.5.3 整合所有组件
def load_data_fashion_minist(batch_size, resize=None): """下载Fashion-MINIST数据集并加载到内存中"""trans = [transforms.ToTensor()] #resize可选择调整图像大小的目标尺寸以匹配模型输入if resize: trans.insert(0, transforms.Resize(resize)) trans = transforms.Compose(trans) minist_train = torchvision.datasets.FashionMNIST(root='./data', train=True, transform=trans, download=True) minist_test = torchvision.datasets.FashionMNIST(root='./data', train=False, transform=trans, download=True) return (data.DataLoader(minist_train, batch_size=batch_size, shuffle=True, num_workers=get_dataloader_workers()), data.DataLoader(minist_test, batch_size=batch_size, shuffle=True, num_workers=get_dataloader_workers())) train_iter, test_iter = load_data_fashion_minist(batch_size)
2.6 softmax回归的从零开始实现
如果未能下载d2l包,请将前文sgd()、get_fashion_minist_labels()、show_images()、get_dataloader_workers()、load_data_fashion_minist()整合进d2l.py。
在从零开始实现中,为了保证函数的泛用性,函数中会考虑诸如模型是否使用了pytorch框架、传入的参数形状是否符合等问题,而仅在该案例中并不会遇到这些多余情况。
import torch
from matplotlib import pyplot as plt
from torch.utils import data
import d2l
import torchvision
from torchvision import transforms batch_size = 256
#由于博主的系统设定不支持多进程读取数据,因此使用最初始的读取数据集方法
trans = transforms.ToTensor()
minist_train = torchvision.datasets.FashionMNIST(root='./data', train=True, transform=trans, download=True)
minist_test = torchvision.datasets.FashionMNIST(root='./data', train=False, transform=trans, download=True)
train_iter = data.DataLoader(minist_train, batch_size=batch_size, shuffle=True)
test_iter = data.DataLoader(minist_test, batch_size=batch_size, shuffle=False)
2.6.1 初始化模型参数
#输入28*28像素
num_inputs = 784
#输出对10个类别的预测
num_outputs = 10
#正态分布初始化权重W,偏重初始化为0
W = torch.normal(0, 1, (num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)
2.6.2 定义softmax操作
def softmax(X): #对每一项求幂X_exp = torch.exp(X) #对每一行求和partition = X_exp.sum(dim=1, keepdim=True) #每一行的项除以所在行的和,使最终每行和为1return X_exp / partition
2.6.3 定义模型
def net(X): #将每个原始图像的输入展平为向量后参与模型运算#最终reshape为(batch_size, num_inputs)return softmax(torch.matmul(X.reshape(-1, W.shape[0]), W) + b)
2.6.4 定义损失函数
def cross_entropy(y_hat, y): #y_hat索引中,range()遍历每个样本的10个类别的预测值,y索引到预测值中正确类别的预测概率return - torch.log(y_hat[range(len(y_hat)), y])
2.6.5 分类精度
def accuracy(y_hat, y): """计算预测正确的样本数"""#确保y_hat形状符合要求if len(y_hat.shape) > 1 and y_hat.shape[1] > 1: #每个样本选择概率最大的类别作为预测值 y_hat = y_hat.argmax(axis=1) #记录每个样本预测的正误,type确保参与比较的两者类型一样cmp = y_hat.type(y.dtype) == y #将布尔型转换为数值型累加得到预测正确的样本数return float(cmp.type(y.dtype).sum())def evaluate_accuracy(net, data_iter): """计算在整个数据集上的精度"""#如果模型是使用torch搭建的则设为评估模式不计算梯度if isinstance(net, torch.nn.Module): net.eval() #对正确预测数和预测总数进行累加metric = Accumulator(2) with torch.no_grad(): for X, y in data_iter: metric.add(accuracy(net(X), y), y.numel()) return metric[0] / metric[1]
evaluate_accuracy()函数用到的Accumulator类可以对多个变量进行累加:
class Accumulator: def __init__(self, n): self.data = [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.] * len(self.data) def __getitem__(self, idx): return self.data[idx]
2.6.6 训练
由于博主无法下载d2l包,书中绘制图表动画的功能无法实现故略去,训练过程仅使用文字显示。
def train_epoch_ch3(net, train_iter, loss, updater): """训练模型一轮"""#若使用torch搭建的模型则设为训练模式计算梯度if isinstance(net, torch.nn.Module): net.train() #训练损失总和、训练准确度总和、样本数的累加器metric = Accumulator(3) #遍历训练集、计算梯度并更新参数for X, y in train_iter: y_hat = net(X) l = loss(y_hat, y) #使用pytorch内置的优化器和损失函数if isinstance(updater, torch.optim.Optimizer): updater.zero_grad() l.mean().backward() updater.step() #使用定制的优化器和损失函数else: l.sum().backward() updater(X.shape[0]) metric.add(float(l.sum()), accuracy(y_hat, y), y.numel()) #返回训练损失和精度return metric[0] / metric[2], metric[1] / metric[2] def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater): """训练模型"""for epoch in range(num_epochs): train_metrics = train_epoch_ch3(net, train_iter, loss, updater) test_acc = evaluate_accuracy(net, test_iter) l = train_metrics[0] print(f'epoch {epoch + 1}, loss {l:f}, test accuracy {test_acc * 100:.2f}%') train_loss, train_acc = train_metrics #学习率
lr = 0.2 #使用小批量随机梯度下降作为优化器
def updater(batch_size): return d2l.sgd([W, b], lr, batch_size) #设置训练轮数并开始训练
num_epochs = 30
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)
2.6.7 预测
def predict_ch3(net, test_iter, n=6): """预测标签"""for X, y in test_iter: break #提取真实值和预测值的文字标签trues = d2l.get_fashion_minist_labels(y) preds = d2l.get_fashion_minist_labels(net(X).argmax(dim=1)) #每张图标题上面为真实标签,下面为预测标签titles = [true + '\n' + pred for true, pred in zip(trues, preds)] d2l.show_images(X[0:n].reshape(n, 28, 28), 1, n, titles=titles[0:n]) plt.show() predict_ch3(net, test_iter)
训练过程(最后5轮):
epoch 26, loss 0.620540, test accuracy 80.41%
epoch 27, loss 0.612298, test accuracy 80.41%
epoch 28, loss 0.606352, test accuracy 81.04%
epoch 29, loss 0.599426, test accuracy 78.86%
epoch 30, loss 0.595474, test accuracy 80.38%
预测结果:
2.7 softmax回归的简洁实现
2.7.1 代码
import torch
from torch import nn
import d2l
import torchvision
from torchvision import transforms
import torch.utils.data as data batch_size = 256
trans = transforms.ToTensor()
minist_train = torchvision.datasets.FashionMNIST(root='./data', train=True, transform=trans, download=True)
minist_test = torchvision.datasets.FashionMNIST(root='./data', train=False, transform=trans, download=True)
train_iter = data.DataLoader(minist_train, batch_size=batch_size, shuffle=True)
test_iter = data.DataLoader(minist_test, batch_size=batch_size, shuffle=False) #pytorch不会隐式地调整输入形状,需通过展平层将多维张量展平到二维,其中只保留第0维不变
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10)) #初始化模型参数
def init_weights(m): #对线性神经网络层初始化权重if type(m) == nn.Linear: nn.init.normal_(m.weight, std=0.01) net.apply(init_weights) #交叉熵损失函数
loss = nn.CrossEntropyLoss(reduction='none') #小批量随机梯度下降优化器
trainer = torch.optim.SGD(net.parameters(), lr=0.1) num_epochs = 10 d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer) d2l.predict_ch3(net, test_iter)
训练过程(最后5轮):
epoch 6, loss 0.473789, test accuracy 83.04%
epoch 7, loss 0.464444, test accuracy 83.16%
epoch 8, loss 0.456795, test accuracy 83.38%
epoch 9, loss 0.452170, test accuracy 83.31%
epoch 10, loss 0.447256, test accuracy 83.39%
预测结果:
2.7.2 重新审视softmax的实现
回顾softmax函数:
y ^ j = exp ( o j ) ∑ k exp ( o k ) \hat y_j=\displaystyle\frac{\exp(o_j)}{\displaystyle\sum_k\exp(o_k)} y^j=k∑exp(ok)exp(oj)
由于存在幂运算,当 o j o_j oj非常大时,幂运算的结果可能超出类型表示范围,即上溢,此时无法得到一个明确定义的交叉熵值。解决该问题的一个方法是在进行softmax运算之前先从所有 o k o_k ok中减去 max ( o k ) \max(o_k) max(ok),其不会改变softmax的返回值。
但有些 o j − max ( o k ) o_j-\max(o_k) oj−max(ok)可能具有较大的负值,由于精度受限,幂运算的结果将有接近零的值,即下溢,这些值最终取对数会得到nan。但softmax中的幂运算在交叉熵中会取对数,因此可以将softmax和交叉熵结合在一起:
log ( y ^ i ) = log ( exp ( o j − max ( o k ) ) ∑ k exp ( o k − max ( o k ) ) ) \log(\hat y_i)=\log\left(\displaystyle\frac{\exp(o_j-\max(o_k))}{\displaystyle\sum_k\exp(o_k-\max(o_k))}\right) log(y^i)=log k∑exp(ok−max(ok))exp(oj−max(ok))
= o j − max ( o k ) − log ( ∑ k exp ( o k − max ( o k ) ) ) =o_j-\max(o_k)-\log\left(\displaystyle\sum_k\exp(o_k-\max(o_k))\right) =oj−max(ok)−log(k∑exp(ok−max(ok)))
相关文章:
【动手学深度学习】#2线性神经网络
主要参考学习资料: 《动手学深度学习》阿斯顿张 等 著 【动手学深度学习 PyTorch版】哔哩哔哩跟李牧学AI 目录 2.1 线性回归2.1.1 线性回归的基本元素线性模型损失函数解析解随机梯度下降 2.1.3 最大似然估计 2.2 线性回归从零开始实现2.2.1 生成数据集2.2.2 读取数…...
C语言动态内存管理(上)
欢迎拜访:雾里看山-CSDN博客 本篇主题:C语言动态内存管理(上) 发布时间:2025.3.16 隶属专栏:C语言 目录 为什么需要动态内存管理静态分配的局限性动态分配的优势 动态内存函数malloc函数介绍函数使用 free函数介绍函数使用 calloc…...
图解多头注意力机制:维度变化一镜到底
目录 一、多头注意力机制概述二、代码实现1. pyTorch 实现2. tensorFlow实现 三、维度变化全流程详解1. 参数设定2. 维度变化流程图3. 关键步骤维度变化 四、关键实现细节解析1. 多头拆分与合并2. 注意力分数计算3. 掩码处理技巧 五、完整运行示例六、总结与常见问题1. 核心优势…...
Navicat如何查看密码
近期遇到需要将大部分已存储的navicat数据库转发给其他人,于是乎进行导出文件 奈何对方不用navicat,无法进行文件的导入从而导入链接 搜罗navicat的密码查看,大部分都为php代码解析 以下转载GitHub上看到的一个python代码解析的脚本 这里是对…...
第4节:分类任务
引入: 独热编码(one-hot):对于分类任务的输出,也就是是或不是某类的问题,采取独热编码的形式将y由一离散值转化为连续的概率分布,最大值所在下标为预测类 输入的处理:对于任意一张…...
EasyCVR安防视频汇聚平台助力工业园区构建“感、存、知、用”一体化智能监管体系
在现代工业园区的安全管理和高效运营中,视频监控系统扮演着不可或缺的角色。然而,随着园区规模的扩大和业务的复杂化,传统的视频监控系统面临着诸多挑战,如设备众多难以统一管理、数据存储分散、智能分析能力不足、信息利用率低下…...
计算机网络——DNS
一、什么是DNS? DNS(Domain Name System,域名系统) 是互联网的核心服务,负责将人类可读的域名(如 www.baidu.com)转换为机器可识别的 IP地址(如 14.119.104.254)。它像一…...
STC89C52单片机学习——第20节: [8-2]串口向电脑发送数据电脑通过串口控制LED
写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做! 本文写于:2025.03.15 51单片机学习——第20节: [8-2]串口向电脑发送数据&电脑通过串口控制LED 前言…...
1.5[hardware][day5]
Link类跳转指令可以拆分为两个部分,一个是跳转,即下一个PC的生成,如果将分支条件的比较放到译码级来进行,则这部分只涉及取值级和译码级流水;另一个是Link操作,简单来说就是写寄存器,这部则主要…...
Java 多线程编程:提升系统并发处理能力!
多线程是 Java 中实现并发任务执行的关键技术,能够显著提升程序在多核处理器上的性能以及处理多任务的能力。本文面向初级到中级开发者,从多线程的基本定义开始,逐步讲解线程创建、状态管理、同步机制、并发工具以及新兴的虚拟线程技术。每部…...
Mininet 的详细设计逻辑
Mininet 是一个轻量级网络仿真工具,其核心目标是在单台物理机上快速构建复杂的虚拟网络拓扑,支持 SDN(软件定义网络)和传统网络协议的实验与验证。其设计逻辑围绕 虚拟化、模块化 和 灵活性 展开,以下是其详细设计架构…...
原生微信小程序实现导航漫游(Tour)
效果: 小程序实现导航漫游 1、组件 miniprogram/components/tour/index.wxml <!--wxml--> <view class"guide" wx:if"{{showGuide}}"><view style"{{guideStyle}}" class"guide-box"><view class&quo…...
Spring(6)——Spring、Spring Boot 与 Spring MVC 的关系与区别
Spring、Spring Boot 与 Spring MVC 的关系与区别 1. 核心定位 Spring 定位:基础框架,提供 IoC(控制反转) 和 DI(依赖注入) 核心功能,管理对象生命周期及依赖关系。功能:支持事务管…...
神聖的綫性代數速成例題2. 行列式的性質
性質 1:行列式與它的轉置行列式相等: 設為行列式,為其轉置行列式,則。 性質 2:交換行列式的兩行 (列),行列式變號: 若行列式經過交換第行和第行得到行列式,則。 性質 3ÿ…...
ModelScope推理QwQ32B
文章目录 ModelScope推理QwQ32Bmodel_scope下载QwQ32BModelScope 调用QwQ-32B ModelScope推理QwQ32B 以下载 qwq32b 为例子 需要安装的 python 包 transformers4.49.0 accelerate>0.26.0 torch2.4.1 triton3.0.0 safetensors0.4.5可以使用 conda 创建一个虚拟环境安装 cond…...
使用unsloth进行grpo强化学习训练
说明 unsloth框架可以进行各种sft训练,包括lora和grpo训练。我参考官方方法,使用模型Qwen2.5-3B-Instruct和数据集gsm8k,写了一个grpo训练的例子。 代码 这个代码加载模型Qwen2.5-3B-Instruct和数据集gsm8k。训练完成后先保存lora模型然后…...
【c++】【智能指针】shared_ptr底层实现
【c】【智能指针】shared_ptr底层实现 智能指针之前已经写过了,但是考虑到不够深入,应该再分篇写写。 1 shared_ptr 1.1 shared_ptr 是什么 std::shared_ptr是一个类模板,它的对象行为像指针,但是它还能记录有多少个对象共享它…...
python拉取大视频导入deepseek大模型解决方案
使用Python拉取大视频并导入大模型,需要综合考虑数据获取、存储、处理和资源管理,确保高效稳定地处理大视频数据,同时充分利用大模型的性能,以下是分步方案及代码示例: --- 1. 分块下载大视频(避免内存溢出…...
【Python】面向对象
编程的两大特点 面向过程:着重于做什么面向对象( oop):着重于谁去做 python是面向对象语言,面向对象三大特点:封装、继承、多态 面向对象:便于代码管理,方便迭代更新。 新式类、经…...
leetcode日记(100)填充每个节点的下一个右侧节点指针
和层序遍历差不多的思路,将节点储存在队列里,一边取出节点一边放入取出节点的左右节点,直到队列空。 /* // Definition for a Node. class Node { public:int val;Node* left;Node* right;Node* next;Node() : val(0), left(NULL), right(NU…...
docker入门篇
使用docker可以很快部署相同的环境,这也是最快的环境构建,接下来就主要对docker中的基础内容进行讲解.Docker 是一个用于开发、交付和运行应用程序的开源平台,它可以让开发者将应用程序及其依赖打包到一个容器中,然后在任何环境中运行这个容器࿰…...
python语法
1. 前面先写import导入模块,完整的语法是: [from 模块名] import [模块 | 类 | 变量 | 函数 | *] [as 别名] 语法还可以是: from 模块名 import 功能名 如果import整个模块的话,需要用.功能名(),来用这个功能ÿ…...
Dify使用部署与应用实践
最近在研究AI Agent,发现大家都在用Dify,但Dify部署起来总是面临各种问题,而且我在部署和应用测试过程中也都遇到了,因此记录如下,供大家参考。Dify总体来说比较灵活,扩展性比较强,适合基于它做…...
微信小程序接入DeepSeek模型(火山方舟),并在视图中流式输出
引言: DeepSeek,作为一款先进的自然语言处理模型,以其强大的文本理解和生成能力著称。它能够处理复杂的文本信息,进行深度推理,并快速给出准确的回应。DeepSeek模型支持流式处理,这意味着它可以边计算边输…...
前端性能优化指标及优化方案
前端性能优化的核心目标是 提高页面加载速度、降低交互延迟、减少资源占用。常见的 Web 性能指标包括 LCP、FID、CLS、TTFB、TTI、FCP 等。 关键性能指标(Web Vitals) 指标优化方案 (1)LCP(Largest Contentful Paint&…...
正则化介绍
简单介绍 正则化是用于控制模型的复杂度,防止模型在训练数据上过度拟合(overfitting)。正则化通过在模型的损失函数中引入额外的惩罚项,来对模型的参数进行约束,从而降低模型的复杂度。这个额外的惩罚通常与模型参数的…...
AI时代:数字媒体的无限可能
人工智能和数字媒体技术正深刻改变着我们的生活。通过大数据分析、机器学习等技术,人工智能不仅能精准预测用户需求,还能在医疗、金融等多个领域提供高效解决方案。与此同时,数字媒体技术的进步使得信息传播更加迅速和广泛。社会计算作为新兴…...
自动化爬虫drissionpage
自动化爬虫drissionpage官网 自动化测试框架:DrissionPage DrissionPage调用工具汇总 网络爬虫工具比较-DrissionPage、Selenium、Playwright...
禁毒知识竞赛主持稿串词
尊敬的各位领导、各位来宾、参赛选手们:大家好! 在市禁毒委员会的领导下,今年我市开展了以“参与禁毒战争,构建和谐社会”为主题的禁毒宣传月活动。为了进一步加强我市禁毒宣传力度,促进社会治安的好转和社会主义物质文…...
【JDK17】Jlink一秒生成精简的JRE
之前介绍了 Java17模块化的JDK,模块化后按需使用Jlink 用于精简生成 JRE 环境,这让快速的开发环境增强了编码的愉悦感。在实际生产环境中使用 mave 进行项目的构建,其次再是精简 JRE 缩小容器镜像体积,为实现一体化的流程…...
机器学习周报--文献阅读
文章目录 摘要Abstract 1 文章内容1.1 模型结构1.1.1 LSTMAT的结构设置1.1.2 AWPSO算法优化模型 1.2 实验与结果讨论1.2.1 处理缺失数据1.2.2 模型评估指标1.2.3 比较实验1.2.4 消融实验(ABLATION EXPERIMENTS) 2相关知识2.1 自适应权重粒子群优化&#…...
硬件地址反序?用位操作为LED灯序“纠偏”。反转二进制数即可解决
特别有意思,LED的灯序与其硬件地址刚好相反,没办法直接通过加1实现二进制进位的亮灯操作,查了一些资料说用数组和switch实现,觉得太麻烦了,思索良久,就想到了反转二进制数解决这个问题。 reverse_bits( )是…...
A* floyd算法 bellman-ford
求源点到目标点最短距离 排序的里面要加上与目标点一个预估距离,与dj算法差距只有这儿 预估要小于等于真实的最短距离,吸引力要适当 越接近实际距离越快 #include<bits/stdc.h> using namespace std;// 方向向量:上、右、下、左 const vector<int> …...
【数据挖掘】KL散度(Kullback-Leibler Divergence, KLD)
KL散度(Kullback-Leibler Divergence, KLD) 是衡量两个概率分布 P 和 Q之间差异的一种非对称度量。它用于描述当使用分布 Q 逼近真实分布 P 时,信息丢失的程度。 KL散度的数学定义 给定两个离散概率分布 P(x)和 Q(x),它们在相同的…...
Linux shell 进度条
概述 在 Linux Shell 中实现一个简单的进度条可以通过 printf 命令结合特殊字符来实现,以下是一个示例脚本,它模拟了一个从 0% 到 100% 的进度条。 作用 反馈任务进度:让用户直观了解任务执行的进展情况,比如文件拷贝、系统更新…...
ctfshow web刷题记录
RCE 第一题 eval代码执行 : 1、使用system 加通配符过滤 ?csystem("tac%20fl*") ; 2、反字节执行 xxx %20 echo 反字节 3、变量转移 重新定义一个变量 让他代替我们执行 4、伪协议玩法 ?cinclude$_GET[1]?>&1php://filter/readc…...
leetcode日记(101)填充每个节点的下一个右侧节点指针Ⅱ
意料之中有这题,将之前的思路换一下即可,层序遍历的思路将record(记录下一个循环的次数)手动加减。 /* // Definition for a Node. class Node { public:int val;Node* left;Node* right;Node* next;Node() : val(0), left(NULL)…...
大语言模型微调和大语言模型应用的区别?
1. 基本概念 微调(Fine-tuning) 定义:微调是指在预训练大语言模型的基础上,通过在特定领域或任务的数据上进一步训练,从而使模型在该特定任务上表现更优。 目的:适应具体的任务需求,比如法律文…...
Leetcode-131.Palindrome Partitioning [C++][Java]
目录 一、题目描述 二、解题思路 【C】 【Java】 Leetcode-131.Palindrome Partitioninghttps://leetcode.com/problems/palindrome-partitioning/description/131. 分割回文串 - 力扣(LeetCode)131. 分割回文串 - 给你一个字符串 s,请你…...
DeepSeek:开启机器人智能化的革命性突破
引言 在2025年全球机器人产业格局中,中国AI公司深度求索(DeepSeek)凭借开源机器人智能控制系统DeepSeek-R1,正在掀起一场从底层算法到应用生态的技术革命。不同于传统机器人依赖预设程序的局限,DeepSeek通过深度推理能…...
解决load()文件报错zipfile.BadZipFile: File is not a zip file
报错如下图: 有可能是资源没有关闭造成了错误,这个网上已经有很多解决方案了,大家可自行查阅。 如果你在别的地方都没有找到解决问题,那么可能是以下这种情况。 1、描述 我在服务器上的代码load()加载文件时,出现了…...
【Tools】Visual Studio Code安装保姆级教程(2025版)
00. 目录 文章目录 00. 目录01. Visual Studio Code概述02. Visual Studio Code下载03. Visual Studio Code安装04. Visual Studio Code配置05. 附录 01. Visual Studio Code概述 Visual Studio Code(简称 VS Code)是由微软开发的一款免费、开源且跨平台…...
Python库安装报错解决思路以及机器学习环境配置详细方案
文章目录 概要第三方库gdalmahotasgraphviznltk-datalazypredictscikit-surprisenb_extensions 机器学习GPU-torch安装torch_geometric安装ubuntu安装显卡驱动dlib安装torch-cluster、torch-scatter、torch-sparse和torch-geometricYOLOapextensorflow-gpu Python && P…...
ETIMEDOUT 网络超时问题
根据日志显示,你遇到的 **ETIMEDOUT 网络超时问题** 是由于 npm 无法连接到企业内部的 Nexus 仓库(http://192.168.55.12:8001)导致的。以下是具体原因和解决方案: 一、问题根源 Nexus 仓库不可达 日志中所有依赖包均尝试从 h…...
superset部署记录
具备网络条件的,完全可以一键部署,不需要折腾。网络条件不具备时,部署记录留存备查。 1、正常模式 详细介绍参考:【开源项目推荐】Apache Superset——最优秀的开源数据可视化与数据探索平台-腾讯云开发者社区-腾讯云 (tencent.c…...
todolist docker 小工具
参考链接 前排提示 没有中文,可使用浏览器 翻译 前提 安装docker安装docker-compose 下载仓库 git clone https://github.com/JordanKnott/taskcafe进行安装 cd taskcafe docker-compose -p taskcafe up -d服务启动后会监听在 3333 端口上,通过浏览器…...
PowerToys:解锁Windows生产力的终极武器
欢迎来到涛涛聊AI。今天想着把win键和加号的组合键映射为win键和Q键盘。经过搜索发现PowerToys。 在数字化办公的浪潮中,效率是职场人永恒的追求。微软推出的 PowerToys 作为Windows官方系统强化工具,凭借其强大的功能和开源免费的特性,已成为…...
内存管理:
我们今天来学习一下内存管理: 1. 内存分布: 我们先来看一下我们下面的图片: 这个就是我们的内存,我们的内存分为栈区,堆区,静态区,常量区; 我们的函数栈帧开辟消耗的内存就是我们…...
ElementUI 表格中插入图片缩略图,鼠标悬停显示大图
如何在 ElementUI 的表格组件 Table 中插入图片缩略图,通过鼠标悬停显示大图?介绍以下2种方式: 方法1:直接在模板元素中插入 <template><el-table :data"tableData"><el-table-column label"图片…...
从被动响应到主动预见:智能可观测性技术的变革与实践
思维导图 一、引言 🌃 想象一下,在一个深夜 🌙,你的关键业务系统突然出现故障 🚨。传统情况下,你可能会收到大量不相关的告警 📱💬💬💬,然后花费…...