深度学习基础从0到0.1

news/2025/10/19 17:14:01/文章来源:https://www.cnblogs.com/quan9i/p/19151094

线性回归

一元线性回归

线性回归,公式为Y=Wx+b,这里简单一点,假设偏置b=0,我们设置损失函数为loss=(y-yi)²,y是真实值,yi是预测值,代入可得loss=(y-W*x)²,带入x的值和y的值即可得到最终的loss函数,而后求其导数,导数为0时可取极值,进而得到w,通过这样我们就可以得到最佳的拟合直线,而这就是线性回归算法,不过这里更为简单,还没对w进行训练。

那么如果考虑偏置b的话,我们又该怎么做呢,实际上很简单,我们只需要分别对两个变量求偏导,令其等于0即可,而后联立方程组解出变量值,示例如下

image-20251014115656146

这里首先得出了loss函数,接下来对其进行求偏导
image

而后联立方程求解即可得出最终变量值。
image-20251014120013512

梯度下降

如果按照之前的思路,只要能够保证函数是线性的,我们无需再对模型进行多次微调,只需要令参数的偏导数全等于0求出函数即可,但为什么深度学习里不这么做呢,原因如下

1、参数可能有很多,成千上万,计算量过大
2、模型有可能不是线性的,引入了激活函数,比如Relu函数是分段的,对其求导,解方程将什么复杂

示例如下图,肉眼可见的方程是难以进行计算的

image-20251014121652593

因此,我们这里就需要学习梯度下降,通过它来进行调参。

这里以一元函数为例,如下图所示,我们假设初始点为Xo=4,

image-20251014122756677

接下来根据其导数进行调整,比如f'(x)>0,那说明函数是增大的,我们是想取f(x)的最小值,则我们需要向左调整,反之则向右调整;

那么我们如何设定这个调整的步长呢,这里有一个简单的方法,设置步长为负的f'(x),比如x=4的导数值f'(x)≈1,那我们就设置为-1,接下来x的值就会由4变为3,

可以发现离最小值更近了一步,在x=3处的f'(x)为0.7,接下来进行-0.7,变为2.3。这样不断逼近最终就抵达了最低点。

学习率

上面可以看出更新的步长貌似还不错,但是实际上还需要一个参数去调控步长,这个参数一把不大于1,比如0.01,因为有时候直接使用步长操作可能会错过全局最小值,所以我们引进学习率调控。这里就可以开始更新参数了,我们在求导后,加上了学习率,然后不断对参数进行更新迭代,直至达到最优版本。

image-20251014123706602

均方误差

之前我们定义的损失函数是所有样本的label和预测值的误差的平方和,实际上为了保证训练稳定,一般会除以样本数,而这其实就是我们的均方误差。

image-20251014123629650

多元线性回归

刚刚所看的是一元线性回归,那么多元线性回归又当如何呢?实际上是一样的处理过程,具体示例如下

数据如下:

温度 价格(元) 销量(个)
10 3 60
20 3 85
25 3 100
28 2.5 120
30 2 140
35 2.5 145
40 2.5 163

这里用X1表示温度,X2表示价格,Y表示销量。

Wo表示截距(初始值),W1表示温度权重,W2表示价格权重。

预测销量为Y=Wo+X1*W1+X2*W2

损失函数loss=1/7∑(y-yi)²,代入就是loss=1/7∑(Wo+X1*W1+X2*W2-yi)²

接下来就是用刚刚所学的梯度下降进行优化,而梯度下降首先就是求导数,所以我们分别对三个变量进行求偏导。

image-20251016101215521

在这之后设置学习率,而后更新参数即可

image-20251016101235518

具体实现代码如下

# Feature 数据
X = [[10, 3], [20, 3], [25, 3], [28, 2.5], [30, 2], [35, 2.5], [40, 2.5]]
y = [60, 85, 100, 120, 140, 145, 163]  # Label 数据
# 初始化参数
w = [0.0, 0.0, 0.0]  # w0, w1, w2
lr = 0.0001  # 学习率
num_iterations = 10000  # 迭代次数
# 梯度下降
for i in range(num_iterations):# 预测值y_pred = [w[0] + w[1] * x[0] + w[2] * x[1] for x in X]# 计算损失loss = sum((y_pred[j] - y[j]) ** 2 for j in range(len(y))) / len(y)# 计算梯度grad_w0 = 2 * sum(y_pred[j] - y[j] for j in range(len(y))) / len(y)grad_w1 = 2 * sum((y_pred[j] - y[j]) * X[j][0] for j in range(len(y))) / len(y)grad_w2 = 2 * sum((y_pred[j] - y[j]) * X[j][1] for j in range(len(y))) / len(y)# 更新参数w[0] -= lr * grad_w0w[1] -= lr * grad_w1w[2] -= lr * grad_w2# 打印损失if i % 100 == 0:print(f"Iteration {i}: Loss = {loss}")
# 输出最终参数
print(f"Final parameters: w0 = {w[0]}, w1 = {w[1]}, w2 = {w[2]}")

逻辑回归

一元逻辑回归

之前所学的线性回归是为了拟合出一条直线/平面来拟合数据,以此达到预测数值的效果,而逻辑回归则不同,它是为了分类问题,以这里的一元逻辑回归为例:

气温 是否出门
-10 0
3 1
-3 0
5 1
-4 0
7 1
-6 0
8 1

以上是数据,当我们构造出图像时,如下所示

image-20251016102456975

此时的他并不是一个具体的数值,而是非0即1的,这个时候我们就无法再使用线性回归来预测,所以就引进了逻辑回归来进行解决。

这里就引入了激活函数sigmoid,效果图如下

image-20251016102616897

这里还不够拟合,所以我们可以在e-x前加入w,而后进行训练不断逼近即可。

image-20251016102822348

神经网络

梯度消失/爆炸

梯度消失是指,当参数进行多次迭代更新后,参数的变化已经变的微乎其微,比如100次的乘上0.1,这个数就会无比的小,贴近0,这个时候更新参数十分缓慢,这个就是梯度消失。

而梯度爆炸是指,当参数多次迭代后,由于导数值较大,比如100次的乘上1.2,这个数就会十分巨大,这会使我们的更新跳跃幅度过大,不利于更新。

卷积

当参数过多时,比如我们要处理一张灰白图片,他的大小是6*6*1,所以我们如果一个像素一个参数处理,就需要处理36个参数,而如果我们使用3*3的卷积核,对每个像素先进行卷积操作,最终就只剩下4*4*1的大小,而且我们只需要处理10个参数(3*3的卷积核+偏置b),就大大缩小了计算量,而且无论多少个像素,我们需要处理的参数也就只有这10个,因此,我们引入了卷积来处理图像。

其优点具体如下

1、图片输入特征多 图片输入特征多,但是一个3x3的卷积操作只有10个参数,就可以对整个图片进行扫描。2、特征局部性 卷积操作的每个运算只在特定相邻区域内进行,并不要所有输入特征都参与运算。3、平移不变性 卷积操作在整个图片上进行滑动检测,就是假设图片的特征具有平移不变性。

特征图的尺寸计算

对联分别的输出尺寸变化公式如下

输出尺寸 = (输入尺寸 - 卷积核大小 + 2*填充) / 步长 + 1

而有时会出现除不尽的情况,这个时候我们通常是进行向下取整来进行处理。

1*1卷积层

1*1卷积层存在的意义是他可以以最低成本改变通道数,示例如下

我们定义一个1x1卷积层,其卷积核的数量(即输出通道数)为 C_out。每个卷积核的尺寸是 [1, 1, C_in]。也就是说,每个卷积核都有 C_in 个权重值(每个输入通道对应一个权重)和一个偏置项。这个1x1的窗口在特征图的空间维度(高度和宽度) 上滑动。因为窗口是1x1,所以它每次只“看”一个像素点。但是,这个像素点有 C_in 个通道,因此它看到的是一个包含 C_in 个数值的向量。
对于特征图上的每一个位置 (i, j),取出该位置所有 C_in 个通道的值,形成一个向量 [v1, v2, ..., v_Cin]。用第一个1x1卷积核(它也有 C_in 个权重 [w1_1, w2_1, ..., w_Cin_1])与这个向量进行点积(即对应元素相乘后求和),再加上偏置,就得到了输出特征图在位置 (i, j) 的第一个通道的值。
输出值_1 = (v1 * w1_1) + (v2 * w2_1) + ... + (v_Cin * w_Cin_1) + 偏置_1用第二个卷积核(权重为 [w1_2, w2_2, ...])与同一个输入向量再进行点积,得到输出特征图在 (i, j) 的第二个通道的值。...重复此过程,直到用完所有的 C_out 个卷积核。经过上述操作,输入的 C_in 个通道的信息,在每个像素点上都被混合、加权,并投影到了一个全新的 C_out 维空间。因为计算是在每个空间位置上独立、并行地完成的,所以输出的空间尺寸(高度和宽度)保持不变,但通道数从 C_in 变成了 C_out。

全局平均池化层

我们知道一般遇到的是最大池化层,从n*n的区域中拿出一个最大的值,即为最大池化操作,它只关注了局部的特征。而这里的全局平均池化是什么意思呢?

实际上,他就类似我们的平均数,比如当前有十个数,它就会求和再除10,这样就得到了全局平均池化的输出,这个不会引入额外参数,而且有每个像素的特征。

猫狗分类实战

import os
import random
from PIL import Image
from torchvision import transforms  # ✅ 从 torchvision 导入
import torch
from torch import device, nn
from torch.utils.data import DataLoader, Dataset, TensorDatasetdef verify_images(image_folder):classes = ["Cat", "Dog"]class_to_idx = {"Cat": 0, "Dog": 1}samples = []for cls_name in classes:cls_dir = os.path.join(image_folder, cls_name)for fname in os.listdir(cls_dir):if not fname.lower().endswith(('.jpg', '.jpeg', '.png')):continuepath = os.path.join(cls_dir, fname)try:with Image.open(path) as img:img.verify()samples.append((path, class_to_idx[cls_name]))except Exception:print(f"Warning: Skipping corrupted image {path}")return samplesclass ImageDataset(Dataset):def __init__(self, samples, transform=None):self.samples = samplesself.transform = transformdef __len__(self):return len(self.samples)def __getitem__(self, idx):path, label = self.samples[idx]with Image.open(path) as img:img = img.convert("RGB")    if self.transform:img = self.transform(img)return img, label
class CNNmodel(nn.Module):def __init__(self): super().__init__()self.model = nn.Sequential(nn.Conv2d(3, 16, 3, padding=1), nn.ReLU(),nn.MaxPool2d(2, 2), nn.Conv2d(16, 32, 3, padding=1),nn.ReLU(),nn.MaxPool2d(2, 2),nn.Conv2d(32, 64, 3, padding=1),nn.ReLU(),nn.MaxPool2d(2, 2),nn.Conv2d(64, 128, 3, padding=1),nn.ReLU(),nn.MaxPool2d(2, 2), nn.Conv2d(128,1,1),nn.AdaptiveAvgPool2d(1),nn.Flatten(),nn.Sigmoid())def forward(self, x):return self.model(x)def evaluate(model,test_dataloader):model.eval()val_correct = 0val_total = 0with torch.no_grad():for inputs,labels in test_dataloader:inputs = inputs.to(device)labels = labels.float().unsqueeze(1).to(device) outputs = model(inputs)preds = (outputs >= 0.5).float()val_correct += (preds == labels).sum().item()val_total += labels.size(0)val_acc = val_correct / val_totalreturn val_accif __name__ == "__main__":DATA_DIR = r"D:\Computer Graphic\review\examples\datasets\archive\PetImages"BATCH_SIZE = 64IMG_SIZE = 128EPOCHS = 10LR = 0.001PRINT_STEP = 100device = torch.device("cuda" if torch.cuda.is_available() else "cpu")all_samples = verify_images(DATA_DIR)random.seed(42)random.shuffle(all_samples)train_size = int(len(all_samples) * 0.8)train_samples = all_samples[:train_size]valid_samples = all_samples[train_size:]data_transform = transforms.Compose([transforms.Resize((IMG_SIZE,IMG_SIZE)),#统一图片大小transforms.ToTensor(),#转换为张量transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])  #标准化])train_dataset = ImageDataset(train_samples,data_transform)valid_dataset = ImageDataset(valid_samples,data_transform)train_dataloader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True,num_workers=4)valid_dataloader = DataLoader(valid_dataset, batch_size=BATCH_SIZE, shuffle=False,num_workers=4)model = CNNmodel().to(device)criterion = nn.BCELoss()optimizer = torch.optim.Adam(model.parameters(), lr=LR)for epoch in range(EPOCHS):print(f"\nEpoch {epoch+1}/{EPOCHS}")model.train()running_loss = 0.0for step,(inputs,labels) in enumerate(train_dataloader):inputs = inputs.to(device)labels = labels.float().unsqueeze(1).to(device)optimizer.zero_grad()outputs = model(inputs)loss = criterion(outputs, labels)loss.backward()optimizer.step()running_loss += loss.item()if (step + 1) % PRINT_STEP == 0:avg_loss = running_loss / PRINT_STEPprint(f"Step {step+1}- Loss: {avg_loss:.4f}")running_loss = 0.0val_acc = evaluate(model, valid_dataloader)print(f"Validation Accuracy after epoch {epoch+1}: {val_acc:.4f}")

image-20251017155221905

图像增强

什么是图像增强,图像增强是在保持图像语义不变的情况下,生成多样化的新数据。例如旋转,裁剪,颜色亮度等的改变,都是图像增强的一种方式。

几何变化

旋转,这个是我们所熟悉的操作,将物体按照一定角度进行旋转操作后,表达语义仍然不变。

image-20251017160038919

那么如何在PyTorch里实现旋转操作呢,具体如下。

首先我们定义一个函数,它可以对图片应用PyTorch里Transform对象的操作,进而展示图片。

def imshow(img_path,transform):img = Image.open(img_path)fig,ax = plt.subplots(1,2,figsize=(15,4))ax[0].set_title(f"Original Image {img.size}")ax[0].imshow(img)img = transform(img)ax[1].set_title(f"Transformed Image {img.size}")ax[1].imshow(img)plt.show()

而后通过以下代码即可实现在-30-30度之间随机旋转

path = r"D:\Computer Graphic\review\examples\datasets\archive\PetImages\Cat\238.jpg"
transform = transforms.RandomRotation(degrees=30)
imshow(path,transform)

image-20251017161412161

翻转

具体有水平和垂直,水平如下

path = r"D:\Computer Graphic\review\examples\datasets\archive\PetImages\Cat\2239.jpg"
transform = transforms.RandomHorizontalFlip(p=1.0)#p代表翻转概率
imshow(path,transform)

image-20251017161300456

垂直翻转如下

transform = transforms.RandomVerticalFlip(p=1.0)

image-20251017161914867-1760864774125

裁剪

不难理解,随机裁剪出图像区域

transform = transforms.RandomCrop(size=(120, 120))

image-20251017162045721

透视变换

改变图片的形状,进行一定的角度扭曲

transform = transforms.RandomPerspective(distortion_scale=0.5,  # 控制变形强度,0~1,越大越扭曲p=1.0,                 # 应用该变换的概率interpolation=transforms.InterpolationMode.BILINEAR
)

image-20251017162209921

颜色变化

颜色具体可调的是亮度、对比度、饱和度和色调

transforms.ColorJitter(brightness=0.5,#图像亮暗程度,设置为x时从[1-x,1+x]随机旋转contrast=0.5,#图像对比度,范围[1-x,1+x]saturation=0.5,#图像饱和度,范围[1-x,1+x]hue=0.1#色调,范围[-0.5,0.5],设置为0.1则为[-0.1,0.1]
)

image-20251017162442631

模糊

对图像进行高斯模糊

 # 对图像进行高斯模糊,kernel size 为 5,sigma 可调节模糊强度
transform = transforms.GaussianBlur(kernel_size=5, sigma=(0.1, 3.0))

其中kernel_size是指高斯模糊卷积核的大小,它决定模糊的范围,必须为奇数,设置越大则模糊效果越明显。后面传入的元组(0.1,3.0),表示在里面随机旋转一个值,sigma越大越模糊

image-20251017162657385

遮罩

遮罩是指随机遮挡一个或多个连续的方形区域,让模型忽略局部信息更关注上下文特征,有利于提升模型鲁棒性。

PyTorch里没有直接实现,这里需要自己进行实现

from PIL import Image
import numpy as np
import randomdef cutout_pil_multi(image, mask_size=50, num_masks=3):"""对图像应用多个 Cutout 遮挡块参数:- image: PIL.Image 对象- mask_size: 每个遮挡块的大小(正方形边长)- num_masks: 遮挡块的数量"""image_np = np.array(image).copy()h, w = image_np.shape[0], image_np.shape[1]for _ in range(num_masks):y = random.randint(0, h - 1)x = random.randint(0, w - 1)y1 = max(0, y - mask_size // 2)#防止变为负数y2 = min(h, y + mask_size // 2)#防止溢出x1 = max(0, x - mask_size // 2)x2 = min(w, x + mask_size // 2)# 遮挡区域设置为黑色image_np[y1:y2, x1:x2, :] = 0return Image.fromarray(image_np)

而后调用函数即可

imshow(path, cutout_pil_multi)

image-20251017162924945

这个时候我们就可以利用已有的图像增强技术给猫狗分类实战加上,相当于扩充了训练集。

    train_transform = transforms.Compose([  transforms.Resize((150,150)),transforms.RandomCrop(size=(IMG_SIZE,IMG_SIZE)),transforms.RandomHorizontalFlip(p=0.5),transforms.ColorJitter(brightness=0.5,contrast=0.3,saturation=0.4,hue=0.1),transforms.RandomRotation(degrees=15),transforms.ToTensor(),transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])valid_transform = transforms.Compose([transforms.Resize((IMG_SIZE,IMG_SIZE)),#统一图片大小transforms.ToTensor(),#转换为张量transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])  #标准化])train_dataset = ImageDataset(train_samples,train_transform)valid_dataset = ImageDataset(valid_samples,valid_transform)

image-20251017165214439

语义分割

什么是语义分割呢,将图片所有的像素都赋予语义信息的任务就是语义分割,如下图所示

image-20251017205004872

循环神经网络(RNN)

循环神经网络常用于序列问题,序列问题是指数据之间存在顺序的问题,输入、输出或两者都是有序的数据序列,元素上存在时间或位置上的联系,在考虑这类问题时,我们需要考虑前面/未来的信息。

事实上,他的工作方式如下图所示

image-20251018104602583

它会将上一层的隐藏层输出,传给下一层的输入,将这两个拼接就组成了下一个的输入,然后传入隐藏层再到输出层得到预测结果,之后循环往复,直至最终。

由此可得当前隐状态的计算公式

image-20251018105525315

其中,xt表示t时刻输入,yt表示t时刻输出,ht表示t时刻更新后记忆,即隐状态。wh表示隐藏层权重,bh表示隐藏层偏置,wy表示输出层权重,by表示输出层偏置。

输出层使用了Softmax函数作为激活函数

image-20251018110059661

通过观测可以得出,其实只有第一层隐藏层我们在循环使用,第二层是普通层,因此第一层也被我们称为循环层。循环层的递归调用就是RNN的本质。每一时间步对之前所有的时间步的循环层的调用,输出关键隐状态ht。对于普通层,可以看成是每一时间步利用ht向量作为输入,进行的额外的分类或者回归任务。普通层不是RNN的核心,它只是为了完成每一步的特定任务添加的任务层。

LSTM

之前的RNN只能记住前一时刻的信息,只有短时记忆,而在现实生活中例如语音识别、天气预报、股票预测这些情况下我们都需要进行长期时间的记忆,这个时候我们就引入了长短期记忆网络(LSTM)。

image-20251019123312860

我们对新输入的信息进行存储作为Z,而后再传入上一时间状态的Ht-1和这一时刻的信息Xt,经过sigmoid函数作为输入门,即输入信息的控制函数,将Z和这个相乘得到新的输入,此操作将判定一部分新信息可以进入记忆,一部分则被丢弃。这个时候它就是受控新信息,接下来他就该进入长时记忆了,那么如何进入长时记忆呢,这里我们首先需要取出长时记忆,然后选择一部分需要遗忘的记忆,以此来给出空间存储新的记忆,这个时候依然使用sigmoid函数作为遗忘门函数进行筛选,然后将长期记忆乘上遗忘门的函数再加上tanh激活函数处理的新信息Z乘上控制函数,作为待输出记忆,也就是要存储的长期记忆,而后对这部分再进行tanh激活函数处理,再经过sigmoid输出门处理,判断那部分需要输出,得到的输出就是隐状态的输出了。多个时间步就是以Ct-1作为前一步的长时记忆,经过当前时间步处理后,生成新的长时记忆Ct和隐状态ht,传至下一个记忆中心。公式如下所示

image-20251019160048912

不过这里的wh、wi、wo实际上都是两个权重,以wh为例,我们的wh包含了前一时刻ht-1隐状态输出的权重wh,还包含了这一时刻输入xt的权重wx

代码实现如下

import torch
from torch import nn
from d2l import torch as d2lbatch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
def get_lstm_params(vocab_size, num_hiddens, device):num_inputs = num_outputs = vocab_sizedef normal(shape):return torch.randn(size=shape, device=device)*0.01def three():return (normal((num_inputs, num_hiddens)),normal((num_hiddens, num_hiddens)),torch.zeros(num_hiddens, device=device))W_xi, W_hi, b_i = three()  # 输入门参数W_xf, W_hf, b_f = three()  # 遗忘门参数W_xo, W_ho, b_o = three()  # 输出门参数W_xc, W_hc, b_c = three()  # 候选记忆元参数# 输出层参数W_hq = normal((num_hiddens, num_outputs))b_q = torch.zeros(num_outputs, device=device)# 附加梯度params = [W_xi, W_hi, b_i, W_xf, W_hf, b_f, W_xo, W_ho, b_o, W_xc, W_hc,b_c, W_hq, b_q]for param in params:param.requires_grad_(True)return params
def init_lstm_state(batch_size, num_hiddens, device):return (torch.zeros((batch_size, num_hiddens), device=device),torch.zeros((batch_size, num_hiddens), device=device))
def lstm(inputs, state, params):[W_xi, W_hi, b_i, W_xf, W_hf, b_f, W_xo, W_ho, b_o, W_xc, W_hc, b_c,W_hq, b_q] = params(H, C) = stateoutputs = []for X in inputs:I = torch.sigmoid((X @ W_xi) + (H @ W_hi) + b_i)F = torch.sigmoid((X @ W_xf) + (H @ W_hf) + b_f)O = torch.sigmoid((X @ W_xo) + (H @ W_ho) + b_o)C_tilda = torch.tanh((X @ W_xc) + (H @ W_hc) + b_c)C = F * C + I * C_tildaH = O * torch.tanh(C)Y = (H @ W_hq) + b_qoutputs.append(Y)return torch.cat(outputs, dim=0), (H, C)
vocab_size, num_hiddens, device = len(vocab), 256, d2l.try_gpu()
num_epochs, lr = 500, 1
model = d2l.RNNModelScratch(len(vocab), num_hiddens, device, get_lstm_params,init_lstm_state, lstm)
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)

GRU

GRU,即门控循环单元,他相较LSTM变得更加精简了一些,他只有重置门和更新门,去除了LSTM的记忆细胞,也实现了记录长时记忆和更新的效果。

image-20251019162613893

这里通过重置门实现遗忘部分信息,这里的重置门实际上就是LSTM的遗忘门,他将上一时刻的记忆ht-1和当前时刻的输入Xt进行拼接,而后经过线性层,再加上偏置进行sigmoid激活函数处理,就得到了Gr。

这个得到的Gr再乘上ht-1,就是我门经过重置后的长时记忆。重置后的长期记忆和当前输入xt合并,然后经过一个线性层(权重为wh),加tanh激活,就得到当前层的备用输出ht~。

此时得到的备用输出还是无法直接输出,因为GRU只能靠隐状态来传递长时记忆,这里需要将长期保留的记忆加进来再作为当前时间步的隐状态作为输出。这里怎样决定哪些维度保留长期记忆,哪些维度作为备用输出的隐状态呢,答案是使用更新门函数进行处理,这个函数同时决定保留多少长期记忆,更新多少当前步产生的记忆。

首先用sigmoid更新门生成一个更新向量,而后和备用输出相乘,获得要更新到长期记忆里的信息。然后用1减去更新向量,这样就得到了对长期记忆的保留向量。用保留向量与长期记忆按位点乘,就得到了保留的长期记忆,在和更新信息相加,就得到了这一步输出的长期记忆,ht。

image-20251019163653216-1760865022018

其实现代码如下

import torch
from torch import nn
from d2l import torch as d2lbatch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)def get_params(vocab_size, num_hiddens, device):num_inputs = num_outputs = vocab_sizedef normal(shape):return torch.randn(size=shape, device=device)*0.01def three():return (normal((num_inputs, num_hiddens)),normal((num_hiddens, num_hiddens)),torch.zeros(num_hiddens, device=device))W_xz, W_hz, b_z = three()  # 更新门参数W_xr, W_hr, b_r = three()  # 重置门参数W_xh, W_hh, b_h = three()  # 候选隐状态参数# 输出层参数W_hq = normal((num_hiddens, num_outputs))b_q = torch.zeros(num_outputs, device=device)# 附加梯度params = [W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q]for param in params:param.requires_grad_(True)return params
def init_gru_state(batch_size, num_hiddens, device):return (torch.zeros((batch_size, num_hiddens), device=device), )def gru(inputs, state, params):W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q = paramsH, = stateoutputs = []for X in inputs:Z = torch.sigmoid((X @ W_xz) + (H @ W_hz) + b_z)R = torch.sigmoid((X @ W_xr) + (H @ W_hr) + b_r)H_tilda = torch.tanh((X @ W_xh) + ((R * H) @ W_hh) + b_h)H = Z * H + (1 - Z) * H_tildaY = H @ W_hq + b_qoutputs.append(Y)return torch.cat(outputs, dim=0), (H,)vocab_size, num_hiddens, device = len(vocab), 256, d2l.try_gpu()
num_epochs, lr = 500, 1
model = d2l.RNNModelScratch(len(vocab), num_hiddens, device, get_params,init_gru_state, gru)
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)

深度循环神经网络

之前的都是只有一层隐藏层,而这里则是指使用多层隐藏层,每个隐状态都连续地传递到当前层的下一个时间步和下一层的当前时间步。

它与之前的LSTM代码几乎一致,唯一不同的是这里多加了隐藏层,之前我们得到隐状态后即为结束,这里需要传到下一个隐藏层。

import torch
from torch import nn
from d2l import torch as d2lbatch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)def get_multi_lstm_params(vocab_size, num_hiddens, num_layers, device):"""初始化多层LSTM参数"""num_inputs = vocab_sizenum_outputs = vocab_sizedef normal(shape):return torch.randn(size=shape, device=device) * 0.01def three(in_size, out_size):"""返回门控机制的三组参数"""return (normal((in_size, out_size)),normal((out_size, out_size)),torch.zeros(out_size, device=device))params = []# 初始化各层参数for i in range(num_layers):# 第一层输入为vocab_size,其他层输入为上一层的隐藏层大小in_size = num_inputs if i == 0 else num_hiddens# 输入门、遗忘门、输出门、候选记忆元参数W_xi, W_hi, b_i = three(in_size, num_hiddens)W_xf, W_hf, b_f = three(in_size, num_hiddens)W_xo, W_ho, b_o = three(in_size, num_hiddens)W_xc, W_hc, b_c = three(in_size, num_hiddens)params.extend([W_xi, W_hi, b_i, W_xf, W_hf, b_f, W_xo, W_ho, b_o, W_xc, W_hc, b_c])# 输出层参数(连接最后一层隐藏层到输出)W_hq = normal((num_hiddens, num_outputs))b_q = torch.zeros(num_outputs, device=device)params.extend([W_hq, b_q])# 开启梯度计算for param in params:param.requires_grad_(True)return paramsdef init_multi_lstm_state(batch_size, num_hiddens, num_layers, device):"""初始化多层LSTM的隐藏状态和记忆元"""return [(torch.zeros((batch_size, num_hiddens), device=device),torch.zeros((batch_size, num_hiddens), device=device)) for _ in range(num_layers)]def multi_lstm(inputs, state, params, num_layers):"""多层LSTM前向传播"""# 解析状态:每层包含(H, C)layer_states = stateoutputs = []# 每层参数数量:12个参数(4个门×3组参数)per_layer_params = 12current_inputs = inputs# 逐层计算for layer in range(num_layers):layer_params = params[layer * per_layer_params : (layer + 1) * per_layer_params]H, C = layer_states[layer]layer_outputs = []# 时序步计算for X in current_inputs:# 输入门I = torch.sigmoid((X @ layer_params[0]) + (H @ layer_params[1]) + layer_params[2])# 遗忘门F = torch.sigmoid((X @ layer_params[3]) + (H @ layer_params[4]) + layer_params[5])# 输出门O = torch.sigmoid((X @ layer_params[6]) + (H @ layer_params[7]) + layer_params[8])# 候选记忆元C_tilda = torch.tanh((X @ layer_params[9]) + (H @ layer_params[10]) + layer_params[11])# 更新记忆元C = F * C + I * C_tilda# 更新隐藏状态H = O * torch.tanh(C)layer_outputs.append(H)# 当前层输出作为下一层输入current_inputs = layer_outputs# 更新该层状态layer_states[layer] = (H, C)# 输出层计算(使用最后一层的输出)W_hq, b_q = params[-2], params[-1]final_outputs = [(H @ W_hq) + b_q for H in current_inputs]return torch.cat(final_outputs, dim=0), layer_states# 模型超参数
vocab_size = len(vocab)
num_hiddens = 256  # 每层隐藏单元数
num_layers = 2     # 隐藏层数(可根据需要调整)
device = d2l.try_gpu()
num_epochs, lr = 500, 1  # 多层网络可能需要调整学习率和迭代次数# 定义模型
def model_fn(vocab_size, num_hiddens, device, num_layers):return d2l.RNNModelScratch(vocab_size, num_hiddens, device,lambda vs, nh, dev: get_multi_lstm_params(vs, nh, num_layers, dev),lambda bs, nh, dev: init_multi_lstm_state(bs, nh, num_layers, dev),lambda inputs, state, params: multi_lstm(inputs, state, params, num_layers))model = model_fn(vocab_size, num_hiddens, device, num_layers)# 训练模型
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/940459.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

比赛与好题记录(2025 9-10)

随便写写。 P14115 [IAMOI R4] 木桶效应 有一个木桶,由 \(n\) 块竖直木板组成,第 \(i\) 块木板高度为 \(a_i\)。 我们可以在这些木板上加装额外的木板:有 \(m\) 块 高度为 1 的木板; 有 \(k\) 块 高度为 \(h\) 的木…

QOJ #12313. Three Indices 题解

Description 一个字符串 \(t\) 被称为字符串 \(w\) 的 平滑变换(smooth transformation),如果存在一个整数 \(m \ge 1\) 和一系列字符串 \(w_0, w_1, \ldots, w_m\),满足以下条件:\(w_0 = w\),并且当 \(0 < i…

全面详解 C++std::vector用法指南

std::vector是 C++ 标准模板库(STL)中最重要、最常用的容器之一,它提供了​​动态数组​​功能,能够自动管理内存,支持快速随机访问,并在尾部高效添加/删除元素。 一、基础概念与特性 1.1 核心特性​​动态数组​…

022304105叶骋恺数据采集第一次作业

作业1 代码与运行结果 import urllib.request from bs4 import BeautifulSoupurl ="http://www.shanghairanking.cn/rankings/bcur/2020" response = urllib.request.urlopen(url, timeout=3) html= respons…

智能预加载:基于用户行为和路由预测

智能预加载:基于用户行为和路由预测 核心概念 智能预加载通过分析用户行为模式、路由关系和页面重要性,在用户实际访问前预先加载资源,显著提升用户体验。 实现架构 1. 行为数据收集层 class UserBehaviorTracker {…

函数简单传入参数的汇编分析 - 指南

函数简单传入参数的汇编分析 - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco…

2025年振动电机厂家推荐排行榜,新型/高频/防爆/低噪声/节能振动电机公司精选!

2025年振动电机厂家推荐排行榜,新型/高频/防爆/低噪声/节能振动电机公司精选!随着工业自动化和智能化的快速发展,振动电机作为许多机械设备中的关键部件,其性能和可靠性直接影响到整个系统的运行效率。为了帮助企业…

数据类型转换以及内存溢出

数据类型转换以及内存溢出数据类型转换以及内存溢出 public class Demo05 {public static void main(String[] args) {int i = 128;double b = i;//内存溢出//强制转换 (类型)变量名 高--低//自动转换 低--高…

2025年UV胶点胶机厂家推荐排行榜,全自动/智能/视觉定位/纽扣/拉链头/拉片/商标/钥匙扣/五金/徽章/线圈/硅胶点胶机公司推荐!

2025年UV胶点胶机厂家推荐排行榜,全自动/智能/视觉定位/纽扣/拉链头/拉片/商标/钥匙扣/五金/徽章/线圈/硅胶点胶机公司推荐!随着工业自动化技术的快速发展,UV胶点胶机在各个行业中的应用越来越广泛。从纽扣、拉链头…

25-deepin-linux-wsl-nginx-installation

windows11 #wsl #nginx 在 Deepin Linux 和 WSL 环境中安装配置 Nginx 静态资源服务器 概述 本文详细介绍了在 Deepin Linux 和 WSL (Windows Subsystem for Linux) 环境中安装 Nginx 并配置静态资源服务器的完整过程,…

美股数据接口对接指南:快速获取指数实时行情

美股数据接口对接指南:快速获取纳斯达克、道琼斯指数实时行情 在金融科技应用、量化交易或数据可视化项目中,接入可靠的美股市场数据是常见的需求。本文将详细介绍如何通过API接口,高效、稳定地获取包括纳斯达克综合…

2025国际冷链运输推荐腾翼搏时,专业温控保障生物药品安全!

2025国际冷链运输推荐腾翼搏时,专业温控保障生物药品安全!随着全球生物医药行业的快速发展,对冷链物流的需求日益增长。特别是在2025年,预计全球医药市场的规模将进一步扩大,生物药品、临床样本、CAR-T细胞治疗产…

鸿蒙设备开发-gpio控制

正在施工 说明 比harmony4.0的时候文档好太多了,基本每个文件夹(sdk组件)下都有对应的详细文档。 设备 用的RK35xx , 其实用什么设备都可以,都是kernel向上提供接口,只要可以运行kernel,性能可以基本都可以适配。…

QT肝8天01--工程介绍

QT肝8天01--工程介绍2025-10-19 16:57 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-f…

AI Agent和Agentic AI

https://hub.baai.ac.cn/view/46037本文来自博客园,作者:transformert,转载请注明原文链接:https://www.cnblogs.com/ac-network/p/19151066

升级Win11 25H2 专业工作站版 最强系统

全新 Windows 11 25H2 专业工作站版,是微软面向高性能用户与专业创作者推出的旗舰级系统版本。它不仅继承了 Windows 11 的全新设计与安全体系,更在底层性能优化、资源调度、文件系统、虚拟化支持等方面进行了深度增…

如何在Java中进行多线程编程

在Java中进行多线程编程有多种方式,最常用的包括继承Thread类、实现Runnable接口、实现Callable接口,以及使用线程池等。以下是具体介绍和代码示例: 1. 继承Thread类 通过继承Thread类并重写run()方法来定义线程执行…

Java中java.util.Random的用法

java.util.Random是Java中用于生成伪随机数的类,提供了多种生成不同类型随机数的方法。 基本用法 1. 创建Random对象 import java.util.Random;// 创建Random对象(使用默认种子,通常是系统时间) Random random = n…

我的学习开始及历程

学习嵌入式 Linux 驱动的历程回顾第一篇的随笔中我写到:「带来更多的体会和收获,就不会是毫无意义的普通文字了。」 所以就当作重新梳理自己来写这个随笔了。嵌入式的开始接触 大约是大一的时候,比较疯狂的「迷恋」…

2025年磨粉机厂家推荐排行榜,雷蒙磨粉机,环辊磨粉机,摆式磨粉机,矿石磨粉机,超微磨粉机,高压磨粉机公司推荐!

2025年磨粉机厂家推荐排行榜:雷蒙磨粉机、环辊磨粉机、摆式磨粉机、矿石磨粉机、超微磨粉机、高压磨粉机公司推荐!随着工业技术的不断进步,磨粉机在矿业、化工、建材等多个领域发挥着越来越重要的作用。为了帮助企业…