Posts with tag 技术学习

unit3-01_stable_diffusion_introduction

2025-06-20
diffusion-models-classjulyfunnotes技术学习

Stable Diffusion Pipeline 的职责是?说白了 unet 预测的就是 vae 的 latent.pipe:unetvaetext_encoderimage_encoderfeature_extractortokenizer (is a nn.Module without params)schedulersafety_checker ?graph text --> t(tokenizer & text_encoder) --> e[text_embedding] e --> unet(unet) n["noisy_latents (4, 64, 64)"] --> unet timestep --> unet unet --> p["noise prediction (4, 64, 64)"] p --> v_d(vae decoder)Unet 架构AutoencoderKL [1, 3, 64, 64] -- ├─Encoder: 1-1 [1, 8, 8, 8] -- │ └─Conv2d: 2-1 [1, 128, 64, 64] 3,584 │ └─ModuleList: 2-2 -- -- │ │ └─DownEncoderBlock2D: 3-1 [1, 128, 32, 32] 738,944 │ │ └─DownEncoderBlock2D: 3-2 [1, 256, 16, 16] 2,690,304 │ │ └─DownEncoderBlock2D: 3-3 [1, 512, 8, 8] 10,754,560 │ │ └─DownEncoderBlock2D: 3-4 [1, 512, 8, 8] 9,443,328 │ └─UNetMidBlock2D: 2-3 [1, 512, 8, 8] -- │ │ └─ModuleList: 3-7 -- (recursive) │ │ └─ModuleList: 3-6 -- 1,051,648 │ │ └─ModuleList: 3-7 -- (recursive) │ └─GroupNorm: 2-4 [1, 512, 8, 8] 1,024 │ └─SiLU: 2-5 [1, 512, 8, 8] -- │ └─Conv2d: 2-6 [1, 8, 8, 8] 36,872 ├─Conv2d: 1-2 [1, 8, 8, 8] 72 ├─Conv2d: 1-3 [1, 4, 8, 8] 20 ├─Decoder: 1-4 [1, 3, 64, 64] -- │ └─Conv2d: 2-7 [1, 512, 8, 8] 18,944 │ └─UNetMidBlock2D: 2-8 [1, 512, 8, 8] -- │ │ └─ModuleList: 3-10 -- (recursive) │ │ └─ModuleList: 3-9 -- 1,051,648 │ │ └─ModuleList: 3-10 -- (recursive) │ └─ModuleList: 2-9 -- -- │ │ └─UpDecoderBlock2D: 3-11 [1, 512, 16, 16] 16,524,800 │ │ └─UpDecoderBlock2D: 3-12 [1, 512, 32, 32] 16,524,800 │ │ └─UpDecoderBlock2D: 3-13 [1, 256, 64, 64] 4,855,296 │ │ └─UpDecoderBlock2D: 3-14 [1, 128, 64, 64] 1,067,648 │ └─GroupNorm: 2-10 [1, 128, 64, 64] 256 │ └─SiLU: 2-11 [1, 128, 64, 64] -- │ └─Conv2d: 2-12 [1, 3, 64, 64] 3,45

async

2025-06-12
julyfunlang-modelnotes技术学习

大致概念:[80%-sure] 以 Rust 为例,运行到 func().await 时 (func 为 async fn)func() 内部代码完全交由 async runtime 处理func() 内部通常有等待IO/GPU/定时器的异步部分。这些部分不由 CPU 负责运算,不应该让 CPU 干等着,所以才写成异步函数.如果 func() 内部全是同步代码,没有 .await 或手动挂起点,写成异步(async fn)没有实际意义它是不会挂起的,会同步执行完毕。轮询poll 调用次数不是固定的,取决于任务的完成时机和外部事件。Rustasync fn my_async(x: u32) -> u32 { let a = x + 1; a * 2 } let fut = my_async(10); fut.await; // 开始轮询编译器生成的大致逻辑:enum MyAsyncState { Start { x: u32 }, Done, } struct MyAsyncFuture { state: MyAsyncState, } impl Future for MyAsyncFuture { type Output = u32; fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<u32> { match self.state { MyAsyncState::Start { x } => { let a = x + 1; let result = a * 2; self.state = MyAsyncState::Done; Poll::Ready(result) }, MyAsyncState::Done => { panic!("polled after completion"); } } } }另一个例子:async fn my_async(x: u32) -> u32 { let a = x + 1; tokio::time::sleep(Duration::from_secs(3)).await; a * 2 }enum MyAsyncState { Start { x: u32 }, Waiting { a: u32, sleep_future: Sleep }, Done, } impl Future for MyAsyncFuture { type Output = u32; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<u32> { loop { match self.state { MyAsyncState::Start { x } => { let a = x + 1; let sleep_future = tokio::time::sleep(Duration::from_secs(3)); self.state = MyAsyncState::Waiting { a, sleep_future }; }, MyAsyncState::Waiting { a, ref mut sleep_future } => { match Pin::new(sleep_future).poll(cx) { Poll::Pending => return Poll::Pending, Poll::Ready(_) => { let result = a * 2; self.state = MyAsyncState::Done; return Poll::Ready(result); } } }, MyAsyncState::Done => { panic!("polled after completion"); } } } }

unit2-02_class_conditioned_diffusion_model_example

2025-06-10
diffusion-models-classjulyfunnotes技术学习

Class-conditioned指的是类别-Conditioned. 或者说 class-label-conditioned.网络输入改成啥样了? 其实就是 concat.Unet 输入通道直接改成了 in_channels=1 + class_emb_sizeUNet2DModel( in_channels=1 + class_emb_size,forward 时广播 + torch.cat 一下.def forward(self, x, t, class_labels): bs, ch, w, h = x.shape # & self.class_emb = nn.Embedding(num_classes, class_emb_size) class_cond = self.class_emb(class_labels) # * # 广播 class_cond = class_cond.view(bs, class_cond.shape[1], 1, 1).expand(bs, class_cond.shape[1], w, h) net_input = torch.cat((x, class_cond), dim=1) # model 返回 ModelOutput. # sample: 就是预测的噪声张量. # additional_residuals: 存储额外残差信息. 一般没用. return self.model(net_input, t).sampl

unit2-01_finetuning_and_guidance

2025-06-10
diffusion-models-classjulyfunnotes技术学习

Generating process:x = torch.randn(4, 3, 256, 256).to(device) for i, t in tqdm(enumerate(scheduler.timesteps)): model_input = scheduler.scale_model_input(x, t) with torch.no_grad(): noise_pred = image_pipe.unet(model_input, t)["sample"] x = scheduler.step(noise_pred, t, sample=x).prev_sampleGuidancex = torch.randn(4, 3, 256, 256).to(device) for i, t in tqdm(enumerate(scheduler.timesteps)): x = x.detach().requires_grad_() model_input = scheduler.scale_model_input(x, t) noise_pred = image_pipe.unet(model_input, t)["sample"] x0 = scheduler.step(noise_pred, t, x).pred_original_sample loss = <custom_loss>(x0) * <guidance_loss_scale> cond_grad = -torch.autograd.grad(loss, x)[0] x = x.detach() + cond_grad x = scheduler.step(noise_pred, t, x).prev_sampleCLIP Guidance with torch.no_grad(): text_features = clip_model.encode_text(text) for i, t in tqdm(enumerate(scheduler.timesteps)): # print(i, t) # (1, tensor(1000)), (2, tensor(980))... model_input = scheduler.scale_model_input(x, t) # DDIM loaded with torch.no_grad(): # image_pipe is loaded by the same name noise_pred = image_pipe.unet(model_input, t)["sample"] cond_grad = 0 for cut in range(n_cuts): x = x.detach().requires_grad_() x0 = scheduler.step(noise_pred,t, sample=x).pred_original_sample loss = <clip_loss>(x0, text_features) * guidance_scale cond_grad -= torch.autograd.grad(loss, x)[0] / n_cuts if i % 25 == 0: print(f"Steps {i} loss: {loss.item()}") alpha_bar = scheduler.alphas_cumprod[i] # `alpha_bar` here is decreasing and works for textures. # Can be changed to some increasing coefficients! x = x.detach() + cond_grad * alpha_bar.sqrt() x = scheduler.step(noise_pred, t, x).prev_sampl

练习

2025-05-19
julyfunnotesrcore技术学习

UtilsRustSBI 起始代码: https://github.com/rustsbi/rustsbi-qemu/blob/main/rustsbi-qemu/src/main.rs#L146实验中运行的 qemu 是干嘛的?(make run 做了什么)执行步骤:cargo build 编译内核rust-objcopy 将 ELF 格式的内核转换为纯二进制格式 .bin 文件随后 Qemu:qemu-system-riscv64 \ -machine virt \ # 模拟 RISC-V 的 virt 机器 -nographic \ # 无图形界面,使用控制台 -bios rustsbi-qemu.bin \ # 加载 RustSBI 作为 BIOS -device loader,file=os.bin,addr=0x80200000 \ # 将内核二进制加载到指定内存地址 -s -S # 开启调试服务器(-s)并在启动时暂停(-S)QEMU 的职责:硬件模拟:模拟一个 RISC-V 64GC 架构的虚拟计算机启动流程:首先运行 RustSBI (bootloader)RustSBI 会初始化硬件环境然后将控制权转移到内核 (加载到 0x80200000 的 os.bin)调试支持:通过 -s -S 参数:-s:在 1234 端口开启 GDB 调试服务器-S:启动时暂停,等待调试器连接RUST 内核的作用:被加载到内存指定位置 (0x80200000)接收来自 RustSBI 的控制权作为操作系统内核运行,管理硬件资源和提供系统服务内存布局下面图“数据存储器”其实是 Data Memory. image.png|500Qemu 启动流程Step 1:Qemu CPU pc set to 0x1000,执行一些神秘指令,使得待会儿能跳转到 0x80000000:0x0000000000001000 in ?? () (gdb) x/10i $pc => 0x1000: auipc t0,0x0 0x1004: addi a2,t0,40 0x1008: csrr a0,mhartid 0x100c: ld a1,32(t0) 0x1010: ld t0,24(t0) 0x1014: jr t0 # 这里 t0 值已经变成 0x80000000,原理不知 0x1018: unimp 0x101a: 0x8000 0x101c: unimp 0x101e: unimpStep 2: 0x80000000这里是 RUSTSBI,可以理解为 bootloader (硬件启动的引导程序),会执行很多指令,所以实操时在 0x80200000 打了个断点快速过去,见下面输出.SBI 做的事情有: 对部分硬件例如串口等进行初始化.通过 mret 跳转到 payload 也就是 kernel 所在的起始地址。kernel 进行一系列的初始化后(内存管理,虚存管理,线程(进程)初始化等),通过 sret 跳转到应用程序的第一条指令开始执行。0x0000000080000000 in ?? () (gdb) x/10i $pc => 0x80000000: auipc ra,0x2 0x80000004: jalr 834(ra) 0x80000008: auipc ra,0x0 0x8000000c: jalr 116(ra) 0x80000010: j 0x80001690 0x80000014: unimp 0x80000016: addi sp,sp,-80 0x80000018: sd ra,72(sp) 0x8000001a: ld a1,40(a0) 0x8000001c: ld a2,32(a0)Step 3: 0x80200000 自己的 Rust 程序:(gdb) x/5i $pc => 0x80200000: li ra,100 0x80200004: unimp 0x80200006: unimp 0x80200008: unimp 0x8020000a: unimpWhat is RustSBI?[from comment] SBI 是 RISC-V Supervisor Binary Interface 规范的缩写,OpenSBI 是RISC-V官方用C语言开发的SBI参考实现;RustSBI 是用Rust语言实现的SBI。BIOS 是 Basic Input/Output System,作用是引导计算机系统的启动以及硬件测试,并向OS提供硬件抽象层。机器上电之后,会从ROM中读取引导代码,引导整个计算机软硬件系统的启动。而整个启动过程是分为多个阶段的,现行通用的多阶段引导模型为:ROM -> LOADER -> RUNTIME -> BOOTLOADER -> OSLoader 要干的事情,就是内存初始化,以及加载 Runtime 和 BootLoader 程序。而Loader自己也是一段程序,常见的Loader就包括 BIOS 和 UEFI,后者是前者的继任者。Runtime 固件程序是为了提供运行时服务(runtime services),它是对硬件最基础的抽象,对OS提供服务,当我们要在同一套硬件系统中运行不同的操作系统,或者做硬件级别的虚拟化时,就离不开Runtime服务的支持。SBI就是RISC-V架构的Runtime规范。BootLoader 要干的事情包括文件系统引导、网卡引导、操作系统启动配置项设置、操作系统加载等等。常见的 BootLoader 包括GRUB,U-Boot,LinuxBoot等。而 BIOS/UEFI 的大多数实现,都是 Loader、Runtime、BootLoader 三合一的,所以不能粗暴的认为 SBI 跟 BIOS/UEFI 有直接的可比性。如果把BIOS当做一个泛化的术语使用,而不是指某个具体实现的话,那么可以认为 SBI 是 BIOS 的组成部分之一。练习应用程序在执行过程中,会占用哪些计算机资源?占用 CPU 计算资源(CPU 流水线,缓存等),内存(内存不够还会占用外存)等请用相关工具软件分析并给出应用程序A的代码段/数据段/堆/栈的地址空间范围。readelf -all ch1-6_1 | nvim # 输出 Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .interp PROGBITS 00000000000002e0 000002e0 000000000000001c 0000000000000000 A 0 0 1 [ 2] .note.ABI-tag NOTE 00000000000002fc 000002fc 0000000000000020 0000000000000000 A 0 0 4 [ 3] .note.gnu.bu[...] NOTE 000000000000031c 0000031c 0000000000000024 0000000000000000 A 0 0 4 ... # 解释列解释:[Nr]: 节的编号。Name: 节的名称。Type: 节的类型。Address: 节在内存中的地址。Offset: 节在文件中的偏移量。Size: 节的大小。EntSize: 节中每个条目的大小(如果适用)。Flags: 节的标志,描述了节的属性。Link: 与该节相关的另一个节的编号。Info: 依赖于节类型的额外信息。Align: 节的对齐方式。行解释:[ 0]: 这是 NULL 节,每个 ELF 文件都有一个 NULL 节,它不占用空间,主要用于对齐和占位。Type: NULLAddress: 0x0000000000000000Offset: 0x00000000Size: 0x0000000000000000Flags: 0 (没有标志)Link: 0Info: 0Align: 0[ 1] .interp: 这个节包含程序解释器的路径名,通常是动态链接器。Type: PROGBITSAddress: 0x00000000000002e0Offset: 0x000002e0Size: 0x000000000000001c (28字节)Flags: A (可分配的)Link: 0Info: 0Align: 1还可以:cat /proc/[pid]/maps,因为 应用程序的堆栈是由内核为其动态分配的,需要在运行时查看。** 为何应用程序员编写应用时不需要建立栈空间和指定地址空间?应用程度对内存的访问需要通过 MMU 的地址翻译完成,应用程序运行时看到的地址和实际位于内存中的地址是不同的,栈空间和地址空间需要内核进行管理和分配。应用程序的栈指针在 trap return 过程中初始化。此外,应用程序可能需要动态加载某些库的内容,也需要内核完成映射。请用相关工具软件分析并给出应用程序A的代码段/数据段/堆/栈的地址空间范围。请用相关工具软件分析并给出应用程序A的代码段/数据段/堆/栈的地址空间范围

10.4.bahdanau-attention

2024-12-22
d2ljulyfunnotes技术学习

class Seq2SeqAttentionDecoder(AttentionDecoder): def __init__(self, vocab_size, embed_size, num_hiddens, num_layers, dropout=0, **kwargs): super(Seq2SeqAttentionDecoder, self).__init__(**kwargs) self.attention = d2l.AdditiveAttention( num_hiddens, num_hiddens, num_hiddens, dropout) self.embedding = nn.Embedding(vocab_size, embed_size) self.rnn = nn.GRU( embed_size + num_hiddens, num_hiddens, num_layers, dropout=dropout) self.dense = nn.Linear(num_hiddens, vocab_size) def init_state(self, enc_outputs, enc_valid_lens, *args): # outputs的形状为(batch_size,num_steps,num_hiddens). # hidden_state的形状为(num_layers,batch_size,num_hiddens) outputs, hidden_state = enc_outputs return (outputs.permute(1, 0, 2), hidden_state, enc_valid_lens) def forward(self, X, state): # enc_outputs的形状为(batch_size,num_steps,num_hiddens). # hidden_state的形状为(num_layers,batch_size, # num_hiddens) enc_outputs, hidden_state, enc_valid_lens = state # 输出X的形状为(num_steps,batch_size,embed_size) X = self.embedding(X).permute(1, 0, 2) outputs, self._attention_weights = [], [] for x in X: # query的形状为(batch_size,1,num_hiddens) query = torch.unsqueeze(hidden_state[-1], dim=1) # context的形状为(batch_size,1,num_hiddens) context = self.attention( query, enc_outputs, enc_outputs, enc_valid_lens) # 在特征维度上连结 x = torch.cat((context, torch.unsqueeze(x, dim=1)), dim=-1) # 将x变形为(1,batch_size,embed_size+num_hiddens) out, hidden_state = self.rnn(x.permute(1, 0, 2), hidden_state) outputs.append(out) self._attention_weights.append(self.attention.attention_weights) # 全连接层变换后,outputs的形状为 # (num_steps,batch_size,vocab_size) outputs = self.dense(torch.cat(outputs, dim=0)) return outputs.permute(1, 0, 2), [enc_outputs, hidden_state, enc_valid_lens] @property def attention_weights(self): return self._attention_weight

11-trpo算法

2024-11-11
hrljulyfunnotes技术学习

对 AC 做了非常多优化【策略目标】修改成新策略 $pi_(theta^prime)$ 下的形式并做近似近似过程要求 $theta^prime$ 和 $theta$ 不能差太多,故 KL 散度约束到 $delta$ 之内.【近似求解】对目标函数 1 阶近似,带 $g$,KL 约束 2 阶近似,带海森矩阵 $H$.可直接用 KKT 导出问题的解 $theta_(k + 1)$.【共轭梯度】直接假设每一步 KL 散度都更新到 $delta$,可简化 KKT 解直接用共轭梯度法解算 $H x = g$【线性搜索】由于多处近似,解可能不满足 KL 散度约束取 $(0, 1)$ 之间的超参数 $alpha$,求最小非负整数 $i$ 使得 $alpha^i$ 倍变化量满足 KL 散度限制.【广义优势估计】上面还没估计优势函数 [?]设时序差分误差 $delta_t = r_t + gamma V(s_(t + 1)) - V(s_t)$令 $A_t^((k)) = sum_(l = 0)^(k - 1) gamma^l delta_(t + l)$对 $A$ 进行指数加权平均.代码实践如果动作时连续的,策略网络可输出动作高斯分布的均值和标准差,形同: mu, std = self.actor(states) old_action_dists = torch.distributions.Normal(mu.detach(), std.detach()) old_log_probs = old_action_dists.log_prob(actions) critic_loss = torch.mean( F.mse_loss(self.critic(states), td_target.detach())) self.critic_optimizer.zero_grad() critic_loss.backward(

10-actor-critic算法

2024-11-11
hrljulyfunnotes技术学习

see: https://hrl.boyuai.com/chapter/2/actor-critic%E7%AE%97%E6%B3%95上一章用 $G_t$ 代替 $Q^pi (s, a)$,现在用时序差分残差公式代替之.因为 $Q = r + gamma V$.所以训练一个 $V$ 网路就行原文已经写的很像回忆提纲了训练一个价值网络:Input : 可微状态 $s$Output : $V(s)$Loss: $$1 / 2 (r + gamma V_omega (s_(t + 1)) - V_omega (s_t))^2$$其中 $r + gamma V_omega (s_(t + 1))$ 不参与梯度计算. 代码中使用 detach() 直接实现,不用双网络.和 DQN 一样训练数据来源于采样池.训练过程和 Actor 的关系?Actor 产生了采样池,Actor 变强后采样分布会变化采样数据为 $(s_i, a_i, r_i, s_i^prime)$.注意采样的 $a$ 并不影响 $V$ 梯度下降,乱采样也能训练出正确的 $V$ 网络先来看 Actor + Critic 包装器的 updateclass ActorCritic: # self.critic = ValueNet(state_dim, hidden_dim).to(device) # 价值网络 ... def update(self, transition_dict): states = torch.tensor(transition_dict['states'], dtype=torch.float).to(self.device) actions = torch.tensor(transition_dict['actions']).view(-1, 1).to( self.device) rewards = torch.tensor(transition_dict['rewards'], dtype=torch.float).view(-1, 1).to(self.device) next_states = torch.tensor(transition_dict['next_states'], dtype=torch.float).to(self.device) dones = torch.tensor(transition_dict['dones'], dtype=torch.float).view(-1, 1).to(self.device) # 时序差分目标 td_target = rewards + self.gamma * self.critic(next_states) * (1 - dones) td_delta = td_target - self.critic(states) # 时序差分误差 log_probs = torch.log(self.actor(states).gather(1, actions)) actor_loss = torch.mean(-log_probs * td_delta.detach()) # 均方误差损失函数,这里直接 detach() 来实现类似 Double DQN 的效果... (不演了是吧) critic_loss = torch.mean( F.mse_loss(self.critic(states), td_target.detach())) self.actor_optimizer.zero_grad() self.critic_optimizer.zero_grad() actor_loss.backward() # 计算策略网络的梯度 critic_loss.backward() # 计算价值网络的梯度 self.actor_optimizer.step() # 更新策略网络的参数 self.critic_optimizer.step() # 更新价值网络的参数 class PolicyNet(torch.nn.Module): def __init__(self, state_dim, hidden_dim, action_dim): super(PolicyNet, self).__init__() self.fc1 = torch.nn.Linear(state_dim, hidden_dim) self.fc2 = torch.nn.Linear(hidden_dim, action_dim) def forward(self, x): x = F.relu(self.fc1(x)) return F.softmax(self.fc2(x), dim=1) class ValueNet(torch.nn.Module): def __init__(self, state_dim, hidden_dim): super(ValueNet, self).__init__() self.fc1 = torch.nn.Linear(state_dim, hidden_dim) self.fc2 = torch.nn.Linear(hidden_dim, 1) def forward(self, x): x = F.relu(self.fc1(x)) return self.fc2(x)效果:抖动比基于蒙特卡洛的 REINFORCE 收敛更快,且非常稳定

其他一样.

2024-11-11
hrljulyfunnotes技术学习

之前的算法基于价值,没有显式策略(策略就是选最大动作价值的动作)。下述 REINFORCE 方法基于策略.设 $pi_theta$ 是策略,处处可微,要学习的参数为 $theta$目标是最大化 : $$J(theta) = EE_(s_0) [V^(pi_theta) (s_0) ]$$设状态访问分布为 $nu^pi$(无穷步状态概率加权向量,见第三章),有:提示: $(log f)^prime = f^prime / f$image.png另一证明:https://paddlepedia.readthedocs.io/en/latest/tutorials/reinforcement_learning/policy_gradient.html此图第一行少写了一个 $sum$这里 $T(s, a)$ 是执行 $a$ 后转移到 $s$ 的概率.image.png蒙特卡洛 REINFORCE考虑用蒙特卡洛方法估计,对有限步的环境来说,依据上式有:其中 $T$ 为最大步数. 小括号内就是 $G_t$,下面第二张图中以 $psi_t$ 表示.image.png训练步骤image.png网络结构:Input: 可微状态Output: 离散动作的概率多项分布损失函数: 上述梯度更新公式(去掉微分符号)class PolicyNet(torch.nn.Module): def __init__(self, state_dim, hidden_dim, action_dim): super(PolicyNet, self).__init__() self.fc1 = torch.nn.Linear(state_dim, hidden_dim) self.fc2 = torch.nn.Linear(hidden_dim, action_dim) def forward(self, x): x = F.relu(self.fc1(x)) return F.softmax(self.fc2(x), dim=1) class REINFORCE: def __init__(self, state_dim, hidden_dim, action_dim, learning_rate, gamma, device): self.policy_net = PolicyNet(state_dim, hidden_dim, action_dim).to(device) ... def take_action(self, state): # 根据动作概率分布随机采样 state = torch.tensor([state], dtype=torch.float).to(self.device) probs = self.policy_net(state) action_dist = torch.distributions.Categorical(probs) action = action_dist.sample() # 随机选一个 return action.item() def update(self, transition_dict): reward_list = transition_dict['rewards'] state_list = transition_dict['states'] action_list = transition_dict['actions'] G = 0 self.optimizer.zero_grad() for i in reversed(range(len(reward_list))): # 从最后一步算起 reward = reward_list[i] state = torch.tensor([state_list[i]], dtype=torch.float).to(self.device) action = torch.tensor([action_list[i]]).view(-1, 1).to(self.device) log_prob = torch.log(self.policy_net(state).gather(1, action)) G = self.gamma * G + reward loss = -log_prob * G # 每一步的损失函数 loss.backward() # 反向传播计算梯度 self.optimizer.step() # 梯度下降 # 其他一样.考虑最优情况的正确性:若 $G_1 = 200, G_2 = -200$,则会学到使得 $p_1$ 尽可能大(取 -log 后尽可能小,损失尽可能小),$p_2$ 尽可能小(取 -log 后极大,损失极小)softmax 应该能防止数值爆炸.优化问题,神经网络和强化学习..

8-DQN-改进算法

2024-11-09
hrljulyfunnotes技术学习

Double DQN上一章的 DQN 在计算 $y^"real"$ 时,使用的是:image.pngimage.png如 $w^-$ 有正向误差估计,则 $Q(s, a)$ 会被过高估计,而拿 $Q(s, a)$ 更新上一步 $Q$ 时,误差会逐渐累积。如何解决?将优化目标改为下式,也就是选取下一步最优动作时使用训练网络而不是目标网络. 这样可以缓解此问题.image.pngDouble DQN 代码区别 if self.dqn_type == 'DoubleDQN': # DQN与Double DQN的区别 max_action = self.q_net(next_states).max(1)[1].view(-1, 1) max_next_q_values = self.target_q_net(next_states).gather(1, max_action) else: # DQN的情况 max_next_q_values = self.target_q_net(next_states).max(1)[0].view(-1, 1)结果可视化打印了两个图.打印每个 episode 的回报移动平均对每个时间步,记录该 episode (模拟采样周期)内的移动最大值,这是用于观察是否有 $Q$ 超限的情况 while not done: # 每个模拟时间步 action = agent.take_action(state) max_q_value = agent.max_q_value( state) * 0.005 + max_q_value * 0.995 # 平滑处理 max_q_value_list.append(max_q_value) # 保存每个状态的最大Q值Dueling DQNimage.png这里 $eta, alpha, beta$ 都是网络参数的意思。$V$ 为状态价值函数,$A$ 为优势函数,数学上定义为 $A(s, a) = Q(s, a) - V(s)$.image.png注意训练数据依然是 $(s, a, r, s^prime)$ 的批量采样.好处:某些情况下动作对价值影响不大。如果分开建模,训练这些情况时注意力会集中在 $V$ 网络上.image.png不唯一性问题image.png平均化操作更稳定,训练时采用之.代码基本上与 DQN 的区别只有下述代码:class VAnet(torch.nn.Module): ''' 只有一层隐藏层的A网络和V网络 ''' def __init__(self, state_dim, hidden_dim, action_dim): super(VAnet, self).__init__() self.fc1 = torch.nn.Linear(state_dim, hidden_dim) # 共享网络部分 self.fc_A = torch.nn.Linear(hidden_dim, action_dim) self.fc_V = torch.nn.Linear(hidden_dim, 1) def forward(self, x): A = self.fc_A(F.relu(self.fc1(x))) V = self.fc_V(F.relu(self.fc1(x))) Q = V + A - A.mean(1).view(-1, 1) # Q值由V值和A值计算得到 return Q效果 : 学习更加稳定(回报曲线),得到回报的最大值也更优秀.DQN 对 Q 值过高估计的定量分析利用简单的累积分布函数知识(初中数学知识),如果 Q-net 的 $m$ 个动作的预测值会随机偏大偏小,则预测值的最大值的期望会比真实值最大值大.当神经网络的预测方差与动作优势值达到同量级时,会非常严重

常规 Q-learning

2024-11-08
hrljulyfunnotes技术学习

DQN 作用:对于连续的状态和离散的动作,可通过采样方式更新神经网络Input: 可微状态(并不含动作)Output: 离散动作的动作价值这就是学了个 $Q$,有了 $Q$ 策略就是选最大价值动作。本章训练环境为小车上平面倒立摆的控制,奖励函数: 坚持 1 帧获得奖励 1,倾斜度数或者偏离程度过大或坚持 200 帧则结束。image.png损失设计由于 Q-learning 是这样的:image.png所以对于一个批量的采样 ${(s_i, a_i, r_i, s_i^prime)}$,可以这样设计损失函数:注意下面损失函数里有 $Q$ 本身,无法方便求损失,所以后面设计了双网络(训练网络和目标网络)。这里 $w$ 是 MLP 权重。image.png损失函数为 $0$ 时,再训练时满足: 动作奖励函数 = 该动作奖励 + $gamma times$ (状态随机采样,动作最优)后续状态下最优动作奖励函数训练细节: 经验回放 experience replay将历次采样放入缓冲区,取缓冲区的若干次数据(而不是最近一次)作为一个小批量来优化 $Q_w$整个训练过程中,replay_buffer 不重置,每次训练拿出的数据大小为 batch_size目标网络 + 训练网络其实就是复制两份网络,训练网络每次批量优化时都会更新(目标网络暂不更新),其中损失函数使用目标网络计算,每隔 $C$ 步将目标网络同步到训练网络。注意神经网络形式化的损失函数总是 $sum (F(x_i) - y_i^"real")^2$,在下方原文中可以见到。$w^-$ : 目标网络的权重image.png# 常规 Q-learning ... action = agent.take_action(state) next_state, reward, done = env.step(action) agent.update(state, action, reward, next_state) state = next_state class ...: def update(self, s0, a0, r, s1): td_error = r + self.gamma * self.Q_table[s1].max( ) - self.Q_table[s0, a0] self.Q_table[s0, a0] += self.alpha * td_error# DQN class Qnet(torch.nn.Module): def __init__(self, state_dim, hidden_dim, action_dim): super(Qnet, self).__init__() self.fc1 = torch.nn.Linear(state_dim, hidden_dim) self.fc2 = torch.nn.Linear(hidden_dim, action_dim) def forward(self, x): x = F.relu(self.fc1(x)) return self.fc2(x) class ...: def update(self, transition_dict): states = torch.tensor(transition_dict['states'], dtype=torch.float).to(self.device) actions = torch.tensor(transition_dict['actions']).view(-1, 1).to( self.device) rewards = torch.tensor(transition_dict['rewards'], dtype=torch.float).view(-1, 1).to(self.device) next_states = torch.tensor(transition_dict['next_states'], dtype=torch.float).to(self.device) # dones[i]: 第 i 个状态是否为终止状态 dones = torch.tensor(transition_dict['dones'], dtype=torch.float).view(-1, 1).to(self.device) # q_values: 训练网络给出的 Q值, 作为 y^hat,上面存了梯度 # `gather`函数用于从输出中选择特定的Q值。`1`表示在第二个维度(动作维度)进行选择,`actions`是动作的索引 # [q] gather 也能存梯度么? q_values = self.q_net(states).gather(1, actions) # 下个状态的最大Q值,目标网络给出,这是 y^real 的一部分 max_next_q_values = self.target_q_net(next_states).max(1)[0].view(-1, 1) q_targets = rewards + self.gamma * max_next_q_values * (1 - dones) # 终止状态不考虑下步奖励 dqn_loss = torch.mean(F.mse_loss(q_values, q_targets)) # 均方误差损失函数 self.optimizer.zero_grad() # PyTorch中默认梯度会累积,这里需要显式将梯度置为0 dqn_loss.backward() # 反向传播更新参数 self.optimizer.step() if self.count % self.target_update == 0: self.target_q_net.load_state_dict( self.q_net.state_dict()) # 更新目标网络 self.count += 1 action = agent.take_action(state) next_state, reward, done, _ = env.step(action) replay_buffer.add(state, action, reward, next_state, done) state = next_state # 当buffer数据的数量超过一定值后,才进行Q网络训练 if replay_buffer.size() > minimal_size: b_s, b_a, b_r, b_ns, b_d = replay_buffer.sample(batch_size) transition_dict = { 'states': b_s, 'actions': b_a, 'next_states': b_ns, 'rewards': b_r, 'dones': b_d } agent.update(transition_dict)最后输出image.png这里输出的每一个值是单个 episode 重置环境后的若干步采样的平均 return。相当于把机器人放到现实环境里去跑了几秒钟。done

训练部分

2024-11-08
hrljulyfunnotes技术学习

时序差分算法用于评估价值函数,$alpha$ 为常数,和蒙特卡洛一样是基于采样。优点在于,每采样一步就可以更新状态估计。同样会收敛。下式中 $t$ 为模拟的时间步。$$V(s_t) <- V(s_t) + alpha [r_t + gamma V(s_(t + 1)) - V(s_t)]$$Sarsa用于求解最优策略。时序差分能估计 $V$,但没法直接拿到策略.然而时序差分算法也可以用来估计动作价值函数 $Q(s_t, a_t)$ :以 $1 - epsilon$ 概率采样动作价值最大的动作,剩下概率随机选一个每次采样步更新:$Q(s, a) <- Q(s, a) + alpha [r + gamma Q(s^prime, a^prime) - Q(s, a)]$字母定义见下代码最后通过 Q table 的最大值获取策略每个时间步(已知 s 和动作 a): 得到环境反馈 r, s' e-greedy 选一个 a' Q(s, a) <- Q(s, a) + alpha[r + gamma Q(s', a') - Q(s, a)] s, a = s', a'# 训练部分 for i_episode in num_episodes: state = env.reset() action = agent.take_action(state) done = False while not done: next_state, reward, done = env.step(action) next_action = agent.take_action(next_state) agent.update(state, action, reward, next_state, next_action) state = next_state action = next_action # 原文 Q-learning 处的写法似乎更好 ... state = env.reset() done = False while not done: action = agent.take_action(state) next_state, reward, done = env.step(action) agent.update(state, action, reward, next_state) state = next_state class CliffWalkingEnv: def step(self, action): ... return next_state, reward, done class Sarsa: def update(self, s0, a0, r, s1, a1): td_error = r + self.gamma * self.Q_table[s1, a1] - self.Q_table[s0, a0] self.Q_table[s0, a0] += self.alpha * td_error def take_action(self, state): if np.random.random() < self.epsilon: action = np.random.randint(self.n_action) else: action = np.argmax(self.Q_table[state]) return action效果:采取比较原理悬崖的方式抵达目标。n 步 Sarsa采样到至少 $n$ 步后,对最近 $n$ 步实施类似蒙特卡洛的反向更新。class Sarsa: def update(self, s0, a0, r, s1, a1, done): self.state_list.append(s0) self.action_list.append(a0) self.reward_list.append(r) if len(self.state_list) == self.n: # 若保存的数据可以进行n步更新 G = self.Q_table[s1, a1] # 得到Q(s_{t+n}, a_{t+n}) for i in reversed(range(self.n)): G = self.gamma * G + self.reward_list[i] # 不断向前计算每一步的回报 # 如果到达终止状态,最后几步虽然长度不够n步,也将其进行更新 if done and i > 0: s = self.state_list[i] a = self.action_list[i] self.Q_table[s, a] += self.alpha * (G - self.Q_table[s, a]) s = self.state_list.pop(0) # 将需要更新的状态动作从列表中删除,下次不必更新 a = self.action_list.pop(0) self.reward_list.pop(0) # n步Sarsa的主要更新步骤 self.Q_table[s, a] += self.alpha * (G - self.Q_table[s, a]) if done: # 如果到达终止状态,即将开始下一条序列,则将列表全清空 self.state_list = [] self.action_list = [] self.reward_list = []训练部分不变会传入一个环境产生的 done.效果:采取更保守的策略,更原理悬崖边。Q-learning$$Q(s_t, a_t) <- Q(s_t, a_t) + alpha [r + gamma max_a Q(s_(t + 1), a) - Q(s, a)]$$image.png更新 Q 的公式不同,但采样过程可以用 $epsilon$ 贪心走到下一步。class QLearning: def update(self, s0, a0, r, s1): td_error = r + self.gamma * self.Q_table[s1].max( ) - self.Q_table[s0, a0] self.Q_table[s0, a0] += self.alpha * td_error效果:生成的策略更激进,策略回报也更优。但是交互过程采用 $epsilon$ 贪婪,常会掉进悬崖,训练过程中采样的回报较差。在线策略和离线策略原文声称,on-policy 指模拟采样策略(行为策略)和更新公式(目标策略)一致的策略。例如 Sarsa.在线策略的目标就是老老实实最小化采样回报。而离线策略采样回报和严格按照策略执行的回报会不一致。不一致则为离线策略done

对于第一个 batch,每个 num_keys 保留 2

2024-11-06
d2ljulyfunnotes技术学习

$$ f(x) = sum_(i = 1)^n "softmax"(-1 / 2 ((x - x_i)w)^2)y_i $$该公式括号内部可称为”评分函数“masked_softmaxdef masked_softmax(X, valid_lens): """在最后一个轴上掩蔽元素后,执行softmax操作""" # X:3D张量,valid_lens:1D或2D张量 # 使用时,X.shape = (batch_size, num_queries, num_keys),表示注意力权重。此函数掩盖部分 keys 的注意力权重 if valid_lens is None: return nn.functional.softmax(X, dim=-1) else: shape = X.shape if valid_lens.dim() == 1: valid_lens = torch.repeat_interleave(valid_lens, shape[1]) else: valid_lens = valid_lens.reshape(-1) # 最后一轴上被掩蔽的元素使用一个非常大的负值替换,从而其softmax输出为0 X = d2l.sequence_mask(X.reshape(-1, shape[-1]), valid_lens, value=-1e6) return nn.functional.softmax(X.reshape(shape), dim=-1) # 对于第一个 batch,每个 num_keys 保留 2 # 对于第二个 batch,每个 num_keys 保留 3 masked_softmax(torch.rand(2, 2, 4), torch.tensor([2, 3])) >>> tensor([[[0.5980, 0.4020, 0.0000, 0.0000], [0.5548, 0.4452, 0.0000, 0.0000]], [[0.3716, 0.3926, 0.2358, 0.0000], [0.3455, 0.3337, 0.3208, 0.0000]]])masked_softmax(torch.rand(2, 2, 4), torch.tensor([[1, 3], [2, 4]])) >>> tensor([[[1.0000, 0.0000, 0.0000, 0.0000], [0.4125, 0.3273, 0.2602, 0.0000]], [[0.5254, 0.4746, 0.0000, 0.0000], [0.3117, 0.2130, 0.1801, 0.2952]]])加性注意力学习不同维度的查询与键的注意力。下面输出为实数. q 的大小为 query_size,k 大小为 key_sizeimage.pngimage.pngclass AdditiveAttention(nn.Module): """加性注意力""" def __init__(self, key_size, query_size, num_hiddens, dropout, **kwargs): super(AdditiveAttention, self).__init__(**kwargs) self.W_k = nn.Linear(key_size, num_hiddens, bias=False) self.W_q = nn.Linear(query_size, num_hiddens, bias=False) self.w_v = nn.Linear(num_hiddens, 1, bias=False) self.dropout = nn.Dropout(dropout) def forward(self, queries, keys, values, valid_lens): # 输入的 queries 的形状: (batch_size, num_queries, query_size) # 输入的 queries 的形状: (batch_size, num_keys, key_size) queries, keys = self.W_q(queries), self.W_k(keys) # - 在维度扩展 unsqueeze 后, # queries的形状:(batch_size,查询的个数,1,num_hidden) # key的形状:(batch_size,1,“键-值”对的个数,num_hiddens) # 使用广播方式进行求和 # - unsqueeze(n) 在第 n 维添加一维,长度为 1 features = queries.unsqueeze(2) + keys.unsqueeze(1) features = torch.tanh(features) # self.w_v仅有一个输出,因此从形状中移除最后那个维度。 # scores的形状:(batch_size,查询的个数,“键-值”对的个数) scores = self.w_v(features).squeeze(-1) self.attention_weights = masked_softmax(scores, valid_lens) # attension_weights.shape = (batch_size, num_queries, num_keys) # values的形状:(batch_size,“键-值”对的个数,值的维度) return torch.bmm(self.dropout(self.attention_weights), values) # return 的形状: (batch_size, num_queries, value_dim)# - queries: 2 个批量,分别 1 个 query,分别长度为 20 # - 这个 query 查询对所有 key 的权重,都是一样的(虽然参数随机生成,但过程中用到的参数一样) # - mask 仅保留前两个 key,所以其权重都是 1 / 2,结果 value 为前两个 key 所存 value 的均值 queries, keys = torch.normal(0, 1, (2, 1, 20)), torch.ones((2, 10, 2)) # values的小批量,两个值矩阵是相同的 values = torch.arange(40, dtype=torch.float32).reshape(1, 10, 4).repeat( 2, 1, 1) valid_lens = torch.tensor([2, 6]) attention = AdditiveAttention(key_size=2, query_size=20, num_hiddens=8, dropout=0.3) attention.eval() attention(queries, keys, values, valid_lens), values >>> (tensor([[[ 2.0000, 3.0000, 4.0000, 5.0000]], [[10.0000, 11.0000, 12.0000, 13.0000]]], grad_fn=<BmmBackward0>), tensor([[[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.], [12., 13., 14., 15.], [16., 17., 18., 19.], [20., 21., 22., 23.], [24., 25., 26., 27.], [28., 29., 30., 31.], [32., 33., 34., 35.], [36., 37., 38., 39.]], [[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.], [12., 13., 14., 15.], [16., 17., 18., 19.], [20., 21., 22., 23.], [24., 25., 26., 27.], [28., 29., 30., 31.], [32., 33., 34., 35.], [36., 37., 38., 39.]]]))这两个 batch 对 10 个 key 的注意力分配:image.png缩放点积注意力原文很简洁:image.pngclass DotProductAttention(nn.Module): """缩放点积注意力""" def __init__(self, dropout, **kwargs): super(DotProductAttention, self).__init__(**kwargs) self.dropout = nn.Dropout(dropout) # queries的形状:(batch_size,查询的个数,d: 即 query_size) # keys的形状:(batch_size,“键-值”对的个数,d) # values的形状:(batch_size,“键-值”对的个数,值的维度) # valid_lens的形状:(batch_size,)或者(batch_size,查询的个数) def forward(self, queries, keys, values, valid_lens=None): d = queries.shape[-1] # 设置transpose_b=True为了交换keys的最后两个维度 scores = torch.bmm(queries, keys.transpose(1,2)) / math.sqrt(d) self.attention_weights = masked_softmax(scores, valid_lens) return torch.bmm(self.dropout(self.attention_weights), values)[done

4-动态规划算法

2024-10-22
hrljulyfunnotes技术学习

策略评估已知策略 $pi$ ,状态转移函数 $P$ 和奖励函数也已知,求状态价值函数 $V^pi (s)$(一个期望)初始 $V^(t = 0)$ 任选,比如所有 $V^0 (s) = 0$建立方程 $V(s) = "加权" sum V("nxt"_s)$ (使用贝尔曼期望方程)这不就是带概率和 $r$ 的一个模拟么迭代直到 $V^k = V^(k + 1)$,可以证明会收敛策略提升用于求解最优策略。根据易证明的策略提升定理(到了下一步以后直接一直采用原策略,会获得一个期望,其不会更劣),评估 $pi$ 策略后,策略改为每一状态贪心选择最优的 $"argmax"_a Q^pi (s, a)$,策略就会在每个状态更优。策略提升就是反复贪心(提升)+ 重新迭代评估,直到策略不变。价值迭代上述方法是迭代评估 + 一轮提升,太慢。现在改为一轮评估 + 一轮提升。迭代过程中不维护 $a$,只维护状态价值函数。$$V^(k + 1) (s) = max_(a in A) { Q^k (s, a) }$$done

3-马尔科夫决策过程

2024-09-26
hrljulyfunnotes技术学习

ref: https://hrl.boyuai.com/chapter/1/%E9%A9%AC%E5%B0%94%E5%8F%AF%E5%A4%AB%E5%86%B3%E7%AD%96%E8%BF%87%E7%A8%8B状态转移矩阵 $cal(P)$,表示状态对转移概率,为方阵,第 $i$ 行第 $j$ 个表示 $s_i$ 转移到 $s_j$ 概率MRP 问题下方 $s$ 表示状态集合中的元素,$S_t$ 表示某时刻状态取值$R_t$: 随机变量,$t$ 时刻获得的奖励(实际)$r(s)$: 转移到状态 s 获得的奖励的期望$R_t$: 随机变量,$t$ 时刻获得的奖励$r(s)$: 转移到状态 s 获得的奖励的期望******$G_t$: 一个马尔科夫过程中,从状态 $S_t$ 开始到无穷步后衰减奖励之和$gamma$: 这里引入了衰减系数,则有 $G_t = R_t + gamma R_(t + 1) + gamma^2 R_(t + 2).. = sum_(k = 0)^oo gamma^k R_(t + k)$$V(s)$: 价值函数, 为一个状态的期望回报 $= EE[G_t | S_t = s] = EE[R_t + gamma V(S_(t + 1)) | S_t = s] = r(s) + gamma sum_(s^prime in S) p(s^prime | s) V(s^prime)$上一行等号最右边叫做贝尔曼方程,由 V 和邻接 V 组成$cal(V) eq.def [V(s_1) ... V(s_n)]^T$奖励函数列向量 $cal(R)$ 由 $r(s_i)$ 组成贝尔曼方程矩阵形式: $cal(V) = (I - gamma cal(P))^(-1) cal(R)$ 此法复杂度 $n^3$,不适用于大数据MDP 问题$A$:动作集合$r(s, a)$:期望奖励同时取决于状态和动作$P(s^prime | s, a)$ :状态转移概率也是,故马尔可夫矩阵不再有用策略$pi(a | s)$:状态 $s$ 下采取 $a$ 的概率$V^pi (s) eq.def EE_pi [G_t | S_t = s]$ 状态价值函数 state-value function,即还不确定 $a$$Q^pi (s, a) = EE_pi [G_t | S_t = s, A_t = a]$ 动作价值函数 action-value function,即确定了 $a$有 $V^pi (s) = sum_(a in A) pi(a | s) Q^pi (s, a)$有 $Q^pi (s, a) = r(s,a) + gamma sum_(s^prime in S) P(s^prime | s, a) V^pi (s^prime)$$s -->^pi a_i -->^P s^prime$贝尔曼期望方程代入即可,要么由 V 和邻接 V 组成,要么由 Q 和邻接 Q 组成将 $V^pi (s)$ 表示为关于下一个状态的等式将 $Q^pi (s, a)$ 同样表示为关于下一个状态和动作的等式image.png$A$ 和 $S$ 的例子image.png边缘化求状态价值函数边缘化:将 MDP 转化为 MRP:状态期望奖励 = 所有动作奖励 $times$ 动作概率,$"状态转移到 s' 概率" = "所有动作转移到 s' 概率" times "动作概率"$蒙特卡洛方法随机采样若干条序列,计算统计回报,用于计算状态价值函数$V(s) <- V(s) + 1 / N(s) (G - V(s))$, 这是增量更新,$G$ 由实际采样计算得到代码实现更新方法:先获得一个序列,对该序列从后往前计算def MC(episodes, V, N, gamma): # 这里 episode 是一个采样序列 for episode in episodes: G = 0 # 由于 G 的定义跟后续状态有关,只能从后往前计算. # 所以最后一个状态没有良好的 G(G = 0) for i in range(len(episode) - 1, -1, -1): (s, a, r, s_next) = episode[i] G = r + gamma * G N[s] = N[s] + 1 V[s] = V[s] + (G - V[s]) / N[s]占用度量$nu_0 (s)$ : MDP 的初始状态分布(在状态 $s$ 的概率)$P_t^pi (s)$ : 策略 $pi$ 下智能体 $t$ 时刻为状态 $s$ 的概率$nu^pi (s)$ : 状态访问分布,$nu^pi (s) = (1 - gamma) sum_(t = 0)^oo gamma^t P_t^pi (s)$ ,指策略下所有步在状态 $s$ 的概率的衰减加权和似乎需要确定 $nu_0$.性质(离散形式类似于 MDP 贝尔曼期望方程):image.png$rho^pi (s, a) space eq.def space (1 - gamma) sum_(t = 0)^oo gamma^t P_t^pi (s) pi (a | s)$ : 占用度量,就是动作状态访问分布,等于状态访问分布乘动作权重,表示策略下所有步在该(状态-动作对)的概率的衰减加权和定理 1: 占用度量相同 $<==>$ 策略相同定理 2: 合法占用度量 $rho$ ,可生成该占用度量的唯一策略为:$$pi_rho (s, a) = rho(s, a) / (sum_(a^prime) rho(s, a^prime)) $$最优策略定理:有限状态和动作集合中,必然存在一个策略,在任意状态下的状态价值函数均不劣于其他策略感性上确实能理解$V^(s), Q^(s, a)$ : ...重要:$V^* (s) = max_(a in cal(A)) Q^*(s, a)$,只需选最好的一个动作即可。这是一个循环依赖,需要解方程贝尔曼最优方程由上面导出image.pn

torch.Size([2, 1, 6])

2024-09-26
d2ljulyfunnotes技术学习

语法n 批量矩阵乘法X = torch.ones((2, 1, 4)) Y = torch.ones((2, 4, 6)) torch.bmm(X, Y).shape # torch.Size([2, 1, 6])Nadaraya-Watson 核回归思想若训练数据包含若干 $x_i, y_i$ 对,则测试输入为 $x$ 时,将对每个训练数据分配一个权重(注意力权重),最终测试输出为 $f(x) = sum_(i - 1)^n alpha (x, x_i) y_i$。Nadaraya 的想法如下: $$alpha(x, x_i) = K(x - x_i)$$$x$ 被称为查询,$x_i, y_i$ 为键值对,这就是在查询 $x$ 和 $x_i$ 键之间的注意力权重其中 $K$ 可设计。例如,设计为 $K(u) = 1 / (sqrt(2 pi)) exp(- u^2 / 2)$,则推导出 $$f(x) = sum_(i = 1)^n "softmax"(-1 / 2 (x - x_i)^2)y_i$$这对 $x$ 的分布有要求。因此改进为带参数的注意力汇聚模型,额外学习一个 $w$ 参数使得:$$ f(x) = sum_(i = 1)^n "softmax"(-1 / 2 ((x - x_i)w)^2)y_i $$使用平方损失函数 nn.MESLoss 和随机梯度下降 nn.optim.SGD(lr=0.5) 进行训练。得到的注意力热图为:image.png(此图中任意一条横线表示一个测试输入 $x$ 下对整条 $x$ 轴的权重分配)学到的 $w$ 很小,使注意力更多分配到最近的 $x$。done

1-概述

2024-09-20
julyfunnotes技术学习计网

part1: https://blog.csdn.net/weixin_50295745/article/details/129938124part2: https://blog.csdn.net/weixin_50295745/article/details/130569786 节点: 手机电脑路由器,链路: 有线和无线连接历史阶段1983 TCP/IP,分组交换网 ARPANET未商业化三级树形结构: 主干网 - 地区网 - 局域网ISP (Internet Service Provider).ISP 例如移动联通IXP 允许地区 ISP 交换不经过主干 ISP90 年代 www 出现树形: 主干 ISP - 主干 ISP(可互连接) - 地区 ISP - 本地 ISP - 校园网 / 公司 - 主机标准化机构不说了。边缘部分(终端,不负责转发数据)通信方式CS(Client/Server)P2P(Peer to Peer): 任何主机可以是客户端也可以是服务器分组交换打电话会独占链接,不适合网络传输。路由器存储转发过程:暂存收到的分组检查分组头部查找转发表(由路由器动态维护)按头部的目的地址,找到合适的接口转发出去 #qm what's 接口?分组交换缺点:排队延迟不保证带宽,不保证谁用的多谁用得少头部信息开销复用技术: #qm for what?频分复用 - 不同包调频时分复用 - 时间片轮转统计复用报文交换: 部分流水线化(太慢了)计网分类PAN (Personal Area Network) 10m 就是热点LAN (Local) 一公里,高速通信线路例如学校内MAN (Metropolitan) 城市WAN (Wide) 很远使用者: 分为公用网,专用网(校园公司网)把终端接入网络的网络: AN. 居民不太方便直接接入 ISP,需要一个路由器中转性能速率 bit/s 通常指额定速率,不是实际运行速率带宽 bit/s : 一个信道可通过的理论数据率,可以认为控制吞吐量: 就是实际带宽,还是 bit/s注意 1M bit/s 是 1e6,而 1MB 是 $2^20$时延发送时延 = 数据长度 / 速率传播时延 = 传播距离 / 传播速率 (光纤为 2 / 3 光速)排队时延处理时延三大类交换的区别报文交换可以一段路走到黑,也可以分开几段走分组交换数据报交换: 是最基本的分组交换虚电路交换: 不用分配带宽,不会占用固定线路(可交叉) #qm 啥玩意交换时间计算分组交换网络: 如果有 n 个交换节点,总时间 T = 第一个节点发送所有组数据 + 剩下 (n - 1) 节点转发完最后一组的时间 (因为至少要接收到一组数据才能开始转发)报文交换,每个节点都要消耗全部分组时间电路交换,建立好链接的前提下只有一个节点的时间时延带宽积传播时延 × 带宽。这是对于传输链路计算的,衡量链路是否被充分利用。可理解为发送端发出但未到达接收端的 bit 量(管道里滞留的信息量)往返时间 RTT从发送方送完数据到发送方收到接收方回复的时间。计算方式,在这里就是 $2 times "传播时延" + "校验时间" + "确认包的发送时延"$:计算有效数据率: $"有效数据率" = "数据长度" / "实际消耗总时间" = "数据长度" / "发送时间 + RTT"$,代表从开始发送到确认对方收到整个过程的速率。题目可能会直接给 RTT,让你计算有效数据率之类的。利用率信道利用率: 信道用多大比例的时间是被使用的。利用率也不是越高越好,$D_"网络当前时延" = D_(0 "空闲时延") / (1 - U_"利用率")$网络利用率: 全网络信道利用率的加权平均体系结构Arpanet 提出的抽象方法:抽象分层统一标准模块独立协议与划分层次ISO 提出了 OSI(open system interconnection) 7 层理想模型,但是实际上不采用。计网体系结构定义:定义层次定义层次内部协议,即这个层次要实现的功能协议只是规则,具体实现由厂商来做协议语法: 数据格式语义: 格式表达含义和功能同步: 完成一个任务需要的操作和时序,例如 TCP 的握手层内功能:差错控制: 可靠性流量控制: 发送速率必须使得接收端来得及接受分段和重装: 传输大数据时需要分段,接收端还原复用和分用: 发送端几个高层会话复用一个低层连接,接收端分用还原连接建立和释放: 交换数据前建立一条逻辑连接,数据传输完毕后释放物流分层示例:TCP/IP 四层模型不是严格的标准,现在通用的是五层模型:应用层: 进程间交换信息,为用户服务 | https传输层: 在两台主机的进程之间传输数据。上一层是用这个数据,传输层是传这个数据 | TCP(稳定链接,可靠数据) UDP (无连接,最快传输服务)网络层: 为不同主机提供通信服务,包括路由选择(通过算法为路由器生成转发表)和转发(依据转发表将分组发送到下一个路由器) | IP 协议,数据单位为 IP 数据报数据链路层: 实现相邻节点的可靠通信 | 以太网 or WiFi,数据单位为帧,做差错检验, MAC 地址物理层: 传输 bit | 协议规定物理接口的规格,几根引脚,如何链接应用层确定用法,传输层 tcp 确定进程,网络层 ip 确定主机,链路层从 bit 流中划分数据,物理层确定物理属性每一层都有一种数据传输单元 (PDU),主机虽然不同,但协议和 PDU 封装解封规则一样.路由器不需要最上面两层,顺着下面三层解析 IP 就行。每多一层协议,↑就会多一层 header实体,协议,服务,服务访问点实体就是那一层东西协议: 同一层实体之间沟通的约定服务: 下层实体为上层实体提供功能接口服务上下层沟通需要用到交换服务原语 #qm接口就是服务访问点 SAP (Service Access Point)OSI 七层架构与上述架构的区别将应用层拆层了三个,又称资源子网应用层: app表示层: 统一不同系统之间的数据格式差异会话层: 建立进程之间的通信会话传输层: 运行精简的的 tcp & udp 为会话层提供服务底下三层: 通信子网流量控制在不同层的区别传输层 - https: 端到端流量控制网络层 - ip: 整个网络链路层: 两个相邻节点tcp 流量控制详细见: https://blog.csdn.net/qq_17347313/article/details/107283556考点PDU & 功能| 层 | PDU | 功能 | | --- | ------------------- | --------- | | 物理层 | bit | bit 传输 | | 链路层 | 保证帧内无差错 | | | 网络层 | 分组(数据报) | 路由,连接异构网络 | | 传输层 | tcp: 报文段 udp: 用户数据报 | | | 会话层 | | | | | | | | 应用层 | | 各种应用协议 |数据控制差错控制(内部校验),可靠传输,流量控制(点对点局部控制):链路层网络层传输层都有拥塞控制:链路层没

2-物理层

2024-09-20
julyfunnotes技术学习计网

功能:屏蔽不同传输媒体和通信手段之间的差异,为上层提供一个统一的 bit 传输服务至于怎么屏蔽就是物理层的协议,又称规程接口特性:机械特性:形状,引线数目,固定和锁定装置电气特性:电压,阻抗,速率(如 20kb/s)功能特性:某一个电气特性代表什么含义 - 比如每个引脚高电平代表什么,低电平代表什么过程特性:规定接口上发生的各种事件的时间顺序数据通信系统的模型三大部分:发送端 = 信源 + 发送器传输网络接收端 = 接收器 + 信image.png信道编码调制信道通信:源和终点时间传输信息信道:传输的通道。分为单工,半双工,全双工传输的内容:信息:你要表达的意思消息:信息的载体。如图片数据:消息的实体。例如比特和模拟数据信号:消息的载体。数字信号和模拟信号调制最开始产生的信号叫做基带信号,信号之间会干扰(比如频率相同)调制方法:基带调制:对基带信号进行波形变化,例如用高低电平表示 0 1带通调制:使用载波将基带信号的频率提高,并且变成模拟信号调制是变成模拟信号,编码是变成数字信号考点:数字信号和模拟信号的转换关系数字转数字使用数字发送器搭配若干数字信号编码数字转模拟调制器调制方法模拟转数字PCM (脉冲编码调制),例如 CD 采样采样(两倍频率才能无损),量化(分级),二进制编码(128 级为 7 为编码)模拟转模拟放大器和调制器模拟信号加载到再摸信号传输,如电话机 ☎️数字信号编码: 01 => 电平不归零制(NRZ):用高低区分 01无时钟信息,需要搭配时钟使用否则无法区分连续的 0/1归零制(RZ):每个周期末端归零表示同步信息如图为单极性归零原理:image.png曼彻斯特:用一个跳变表示一个比特。比如高跳低位 1,低跳高为 0。自带同步信息所以最常用差分曼彻斯特编码反向不归零制(NRZI) - 什么玩意image.pngk'j#跳过 调制:将 01 => 正弦波ASK: 用幅度区分 0 1FSK:用频率区分 0 1调相:用相位区分 0 1信道的极限容量奈氏定律: #跳过香农定律:若带宽 $W$,信号平均功率 $S$,高斯噪声功率 $N$ 则最高速率 $$ C = W log_2 (1 + S / N) ("bit" slash s) $$信噪比 (dB) = $10 log_10 (S slash N) ("dB")$#跳过 速率计算物理层下的传输媒介导引型:电磁波沿着固态媒体传播非导引型:自由空间传播(无线技术)频谱:image.png导引型传输媒体双绞线:两根铜线发射的电磁波相互抵消UTP: 无屏蔽双绞线STP: 屏蔽双绞线,贵且需要接地,抗干扰如何购买:image.png同轴电缆:绝缘保护套层 + 外导体屏蔽层 + 绝缘层(厚)+ 内导体,但不用了光纤:电转光 => 全反射传输 => 光转电。光纤是数字信号,亮暗为 01多模光纤:折射次数多,强度衰减快,搭配发光二级管这样的低质量光源,局域网连接用的多,远距离需要中继器单模光纤:折射角度极小光源:半导体激光器远距离传输BASE10: 同轴电缆BASE-T:双绞线BASE-F: 光纤非导引型传输媒体微波:不能绕行,会穿透电离层,接力方法:卫星同步卫星:GPS地轨道卫星:星链地面微波接力:蜂窝网络信道复用技术频分复用 FDM: 一个用户自始至终占用一个频带。容量小时分复用 TDM: 用户按照时间片轮转占用,每次占用全部频段。容量也小统计时分复用 STDM: #跳过波分复用:光信号分频k'j率补充: 中继器、集线器、网桥、交换机、路由器、网关的区别https://blog.csdn.net/weixin_30297281/article/details/96976846?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7EPaidSort-1-96976846-blog-21070807.235%5Ev43%5Epc_blog_bottom_relevance_base9&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7EPaidSort-1-96976846-blog-21070807.235%5Ev43%5Epc_blog_bottom_relevance_base9&utm_relevant_index=1中继器: 仅放大信号集线器 HUB: 碰撞检测 + 再生整形放大,不记录 MAC,广播发送,工作于物理层网桥:是数据链路层设备,隔离子网。连接两个集线器交换机: 多对多通信路由器:网络层设

3-数据链路层

2024-09-20
julyfunnotes技术学习计网

概述数据链路层负责将 bit 分成若干帧封装成帧 + 透明传输只有链路层加帧首和帧尾。因为只有这层用于定界,其他首部只是附带信息MTU: 最大传送单元定界符 + 字节填充法指定帧开始符和帧结束符 SOH 和 EOT。帧内数据的 SOH, EOT 和 ESC 前面都加 ESC0 比特填充:定界符不限长度 #qm也可以在物理层特殊编码(0 1 以外)表示帧定界。差错控制纠错码:海明码等,找到错误位置,用于频繁出错处(如无线链路)检错码:奇偶校验 / CRC 校验,错了就丢弃 + arq 重发 (arq: 确认+重传机制确保可靠),用于光纤等image.png奇偶校验:奇偶校验核心就是补充校验位后让1的数量是奇/偶数个 比如奇校验,00101变成001011无差错和无错误:无差错是以接近 1 的概率无误PPP 协议用于两个节点之间建立点对点协议,适用于远程访问等场景PPP 子协议:LCP 链路控制协议 身份验证 链路管理 + NCP 网络控制协议 与网络层协议配合PPP 协议:image.png同步传输: 面向比特传输,定界符为 bit 序列。例如定界符是 01111110,则一旦发现五个连续 1,就插入一个 0,接收端遇到 5 个连续 1 就忽略后面的 0异步传输:以字节为单位,用字节填充法保证透明传输 (防止传输的内容对帧定界造成干扰)#跳过广播信道链路层一般称局域网 = 以太网。其实还有令牌环网和 FDDI。以太网适合多点接入的局域网本质上所有机器共享一条总线信道,一定会发生碰撞静态分配:适用于用户少的场景频分复用,时分复用 ...动态分配:bandwidth 会动态分配随机接入受控接入以太网为什么快:直接发帧,不建立逻辑连接和确认身份功能精简,消除比特差错,忽略传输差错曼彻斯特编码,自带同步信息便于广播只需要广播的时候附带目标 mac,只有 mac 地址匹配的才会做出响应网卡image.png功能:向上在操作系统中安装驱动,向下实现以太网协议CSMA / CD 协议 #在后面集线器和总线没啥区别,用 CDMA / CD 协议,半双工,在物理层工作;交换机摒弃 CSMA / CD,全双工,工作在链路层利用率分析image.pngimage.pngMAC 层IP 地址随着局域网变化而变化而 MAC 物理地址固化在适配器的 ROM (read-only) 上全球 48 位标准mac 帧是链路层帧的一种实现方式46 - 1500 字节的 MTU,总长度 64-1518B除了数据以外,有目标地址和原地址各占 6 字节2 字节表示上一层协议4 字节 FCS 校验码(比如使用 CRC)没有界定符,采用物理层前导码,尾部则是以太网强制两个帧之间有间隔image.pngmac 帧有以太网 V2 格式和 IEEE 的 802.3 格式,前者常用收发逻辑适配器收到一个帧检查目标 MAC 地址与自己是否匹配。如果不匹配,或出现 FCS 校验错误或者帧长度错误,就丢弃。单播帧 unicast:一对一广播帧 broadcast: 给局域网所有机器返送多播真 multicast 组播介质访问控制 MAC为了避免信道冲突,需要使用 Macimage.pngCDMA 协

4.5.权重衰减

2024-08-23
d2ljulyfunnotes技术学习

下面是手动实现,写起来符合直觉,如下:def l2_penalty(w): return torch.sum(w.pow(2)) / 2 def train(lambd): w, b = init_params() net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss num_epochs, lr = 100, 0.003 animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log', xlim=[5, num_epochs], legend=['train', 'test']) for epoch in range(num_epochs): for X, y in train_iter: # 增加了L2范数惩罚项, # 广播机制使l2_penalty(w)成为一个长度为batch_size的向量 l = loss(net(X), y) + lambd * l2_penalty(w) l.sum().backward() d2l.sgd([w, b], lr, batch_size) if (epoch + 1) % 5 == 0: animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss), d2l.evaluate_loss(net, test_iter, loss))) print('w的L2范数是:', torch.norm(w).item())集成实现:def train_concise(wd): net = nn.Sequential(nn.Linear(num_inputs, 1)) for param in net.parameters(): param.data.normal_() loss = nn.MSELoss(reduction='none') num_epochs, lr = 100, 0.003 # 偏置参数没有衰减 trainer = torch.optim.SGD([ {"params":net[0].weight,'weight_decay': wd}, {"params":net[0].bias}], lr=lr) animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log', xlim=[5, num_epochs], legend=['train', 'test']) for epoch in range(num_epochs): for X, y in train_iter: trainer.zero_grad() # loss 上面 loss = nn.MSELoss l = loss(net(X), y) l.mean().backward() trainer.step() if (epoch + 1) % 5 == 0: animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss), d2l.evaluate_loss(net, test_iter, loss))) print('w的L2范数:', net[0].weight.norm().item()

输入必须为 long 类型

2024-08-22
d2ljulyfunnotes技术学习

Embedding 层see: https://blog.csdn.net/zhaohongfei_358/article/details/122809709与独热编码不同,Embedding 可以将词表降维,且可进行学习。也就是做了 word2vecimport torch from torch import nn embedding = nn.Embedding(20, 5) # 输入必须为 long 类型 embedding(torch.LongTensor([0,1,2]))# 初始状态为随机编码,会随着梯度下降逐渐学习 tensor([[ 0.4471, 0.3875, -1.0195, -1.1125, 1.3481], [-1.7230, -0.1964, -0.0420, 0.5782, 0.4514], [-0.0310, -1.9674, -1.1344, -1.6752, 1.0801]], grad_fn=<EmbeddingBackward0>)类参数 padding_idx:指定填充的索引,这个索引的向量会被初始化为 0。关于形状:nl: num_layers ,GRU 的隐藏层个数ns: num_steps,时间步?n: batch_size各层Encoder.Embedding 学会将原始语言(en)词元转换为 vecEncoder.GRU 学会对 en vec 和上一时刻隐状态给出良好的神秘输出(无意义)和下一时刻 en 隐状态Decoder.Embedding 将学会将目标语言(fr)词元转换为 vecDecoder.GRU 学会对 fr vec 和上一时刻隐状态给出良好的神秘输出和下一时刻 fr 隐状态Decoder.Dense 学会将神秘输出转换为词元概率Li Mu:image.png训练时 Decoder 输入为正确答案:image.png推理时 Decoder 输入为上一时刻输出:image.png外层训练入口[train] 训练时,每对数据仅预测一步,获取损失optimizer = torch.optim.Adam(net.parameters(), lr=lr) loss = MaskedSoftmaxCELoss() for epoch in range(num_epochs): timer = d2l.Timer() for batch in data_iter: optimizer.zero_grad() X, X_valid_len, Y, Y_valid_len = [x.to(device) for x in batch] # X 和 Y 的形状:(n, ns) bos = torch.tensor([tgt_vocab['<bos>']] * Y.shape[0], device=device).reshape(-1, 1) # bos 的形状:(n, 1) dec_input = torch.cat([bos, Y[:, :-1]], 1) # 强制教学 # dec_input 的形状:(n, ns),第一个改为 <bos> # 强制教学把答案当做输入 # 使 embedding 学会正确编码法语, s.t. GPU 可以正确给出下一时刻的 output + state # s.t. 使得 Linear 可以根据 output 正确给出词元概率 && 这个 state 可以帮助下一时刻正确给出 output 和 state # enc_X=X, dec_X=dec_input, *args Y_hat, _ = net(X, dec_input, X_valid_len) l = loss(Y_hat, Y, Y_valid_len) l.sum().backward() # 损失函数的标量进行“反向传播” d2l.grad_clipping(net, 1) num_tokens = Y_valid_len.sum() optimizer.step()[train.net()] 其中 enc_X 输入为原始语言序列,dec_X 输入为目标语言序列答案。def forward(self, enc_X, dec_X, *args): enc_outputs = self.encoder(enc_X, *args) # enc_outputs: (output(ns, n, nh), state(nl, n, nh)) dec_state = self.decoder.init_state(enc_outputs, *args) # dec_state: 即 state(nl, n, nh) return self.decoder(dec_X, dec_state)损失函数就是预测出来概率与答案的交叉熵损失. 但是 <pad> 需要被排除在外.[train.loss] loss 传入解码器的预测概率(长度为词表大小)和答案序列计算损失,会对 valid_len 以外的 <pad> 进行 mask,使之不产生损失。外层预测入口首先将整个原始句子传入编码器,得到浓缩的所有信息 $bold(c)$传给解码器一步步预测。$bold(c)$ 作为第一步预测的隐状态,接下来每步隐状态会不断更新# def predict_seq2seq(net, src_sentence, src_vocab, tgt_vocab, num_steps, # device, save_attention_weights=False): """ 此处设置解码器网路内部 num_steps = 1,解码器输入的时间长度也为 1,每次只预测一个单词. """ src_tokens = src_vocab[src_sentence.lower().split(' ')] + [ src_vocab['<eos>']] enc_valid_len = torch.tensor([len(src_tokens)], device=device) src_tokens = d2l.truncate_pad(src_tokens, num_steps, src_vocab['<pad>']) # 添加批量轴 # 在 shape[0] 处加一维(长度为 1) # enc_X: shape = (1, len(src_tokens)) enc_X = torch.unsqueeze( torch.tensor(src_tokens, dtype=torch.long, device=device), dim=0) # 现将整个 enc_X 传入编码器 # enc_outputs: 包含 output, state enc_outputs = net.encoder(enc_X, enc_valid_len) # 此处 [0] output: (ns, n, nh) # [1] state: (nl, n, nh) dec_state = net.decoder.init_state(enc_outputs, enc_valid_len) # dec_state shape: (nl, n, nh) 不包含每个时间步 dec_X = torch.unsqueeze(torch.tensor( [tgt_vocab['<bos>']], dtype=torch.long, device=device), dim=0) # dec_X: shape (n, ns) ex (1, 1) output_seq, attention_weight_seq = [], [] for _ in range(num_steps): Y, dec_state = net.decoder(dec_X, dec_state) # Y shape = (1, 1, vocab_size) (n, ns, nvocab) # 我们使用具有预测最高可能性的词元,作为解码器在下一时间步的输入 dec_X = Y.argmax(dim=2) pred = dec_X.squeeze(dim=0).type(torch.int32).item() # 保存注意力权重(稍后讨论) if save_attention_weights: attention_weight_seq.append(net.decoder.attention_weights) # 一旦序列结束词元被预测,输出序列的生成就完成了 if pred == tgt_vocab['<eos>']: break output_seq.append(pred) return ' '.join(tgt_vocab.to_tokens(output_seq)), attention_weight_seq类class Seq2SeqEncoder(d2l.Encoder): def __init__(self, vocab_size, embed_size, num_hiddens, num_layers, dropout=0, **kwargs): super(Seq2SeqEncoder, self).__init__(**kwargs) # 嵌入层 self.embedding = nn.Embedding(vocab_size, embed_size) self.rnn = rnn.GRU(num_hiddens, num_layers, dropout=dropout) def forward(self, X, *args): # [def] n 是批量 # 输入 X 形状: (n, ns) X = self.embedding(X) # 输出'X'的形状:(batch_size,num_steps,embed_size) # 在循环神经网络模型中,第一个轴对应于时间步 X = X.swapaxes(0, 1) state = self.rnn.begin_state(batch_size=X.shape[1], ctx=X.ctx) output, state = self.rnn(X, state) # output的形状:(num_steps,batch_size,num_hiddens) 被舍弃 # state的形状:(num_layers,batch_size,num_hiddens) 编码全部输入信息 return output, state class Seq2SeqDecoder(d2l.Decoder): def __init__(self, vocab_size, embed_size, num_hiddens, num_layers, dropout=0, **kwargs): super(Seq2SeqDecoder, self).__init__(**kwargs) self.embedding = nn.Embedding(vocab_size, embed_size) self.rnn = nn.GRU(embed_size + num_hiddens, num_hiddens, num_layers, dropout=dropout) self.dense = nn.Linear(num_hiddens, vocab_size) def init_state(self, enc_outputs, *args): return enc_outputs[1] # 也就是 state def forward(self, X, state): # X: shape (n, ns) # state: shape (nl, n, nh),就是编码器对所有输入信息的编码. X = self.embedding(X).permute(1, 0, 2) # X: (ns, n, n_embed) ex (7, 4, 8) # 此处 state[-1] 是深度循环神经网络最后一层,即最靠近 output 的一层. # 将这一层复制 n_timestep 次. [这合理吗?] context = state[-1].repeat(X.shape[0], 1, 1) # context: (ns, n, nh) ex (7, 4, 16) X_and_context = torch.cat((X, context), 2) # X_and_context: (ns, n, n_embed + nh) ex (7, 4, 24) # 也即,每一时间步每一批量的输入为 n_embed + nh,即 embedding + 刚刚 state 最后一层. # [qm] 这里 rnn() 怎么又把 state 输入进去了,这不纯信息冗余吗 # 注意 rnn 是一次性循环所有时间步. output, state = self.rnn(X_and_context, state) # 此时 output 形状为 (ns, n, nh) 即每时间步每批量输出一个神秘的 nh output = self.dense(output).permute(1, 0, 2) # 最终 output的形状:(n, ns, n_vocab) 这里 ns 中的有效长度和输入 X 不同 # state的形状: (num_layers,batch_size,num_hiddens) return output, state class EncoderDecoder(nn.Module): def __init__(self, encoder, decoder, **kwargs): super(EncoderDecoder, self).__init__(**kwargs) self.encoder = encoder self.decoder = decoder def forward(self, enc_X, dec_X, *args): enc_outputs = self.encoder(enc_X, *args) dec_state = self.decoder.init_state(enc_outputs, *args) return self.decoder(dec_X, dec_state)手绘流程图公式解码时隐状态: $$s_(t^prime) = g(y_(t^prime - 1), bold(c), s_(t^prime - 1))$$其中 $c$ 是编码器对整个输入序列 $x_1, ... x_T$ 的编码.这里 $t^prime$ 只是为了和输入序列时间区分.output, state = self.rnn(X_and_context, state) output = self.dense(output).permute(1, 0, 2)BLEU用于评估预测序列的好坏,值域 $0 tilde 1$。给出预测序列中的 $n$ 元语法有多少出现在标签序列(答案)中。越长的语法权重越大。若预测的太短,会有惩罚。go . => va !, bleu 1.000 i lost . => j'ai perdu ., bleu 1.000 he's calm . => il est bon ?, bleu 0.537 i'm home . => je suis chez moi debout ., bleu 0.80

9.6.encoderdecoder

2024-08-22
d2ljulyfunnotes技术学习

from torch import nn class Encoder(nn.Module): """编码器-解码器架构的基本编码器接口""" def __init__(self, **kwargs): super(Encoder, self).__init__(**kwargs) def forward(self, X, *args): raise NotImplementedError class Decoder(nn.Module): """编码器-解码器架构的基本解码器接口""" def __init__(self, **kwargs): super(Decoder, self).__init__(**kwargs) def init_state(self, enc_outputs, *args): raise NotImplementedError def forward(self, X, state): raise NotImplementedError class EncoderDecoder(nn.Module): """编码器-解码器架构的基类""" def __init__(self, encoder, decoder, **kwargs): super(EncoderDecoder, self).__init__(**kwargs) self.encoder = encoder self.decoder = decoder def forward(self, enc_X, dec_X, *args): enc_outputs = self.encoder(enc_X, *args) dec_state = self.decoder.init_state(enc_outputs, *args) return self.decoder(dec_X, dec_state)编码器有输入 env_X,同时解码器还会将编码器输出转化为 state,并和自己的额外输入 dec_X 一起输入到解码器中。这里并没有说输入会是独热编码

X: (2, 8) (n, ns)

2024-08-21
d2ljulyfunnotes技术学习

为了防止词表太大,将低频词元替换为 <unk> 最后生成的 iter 接口为: ```python train_iter, src_vocab, tgt_vocab = load_data_nmt(batch_size=2, num_steps=8) for X, X_valid_len, Y, Y_valid_len in train_iter: print('X:', X.type(torch.int32)) print('X的有效长度:', X_valid_len) print('Y:', Y.type(torch.int32)) print('Y的有效长度:', Y_valid_len) break # X: (2, 8) (n, ns) # Y: 也是 (n, ns)# 小批量中训练数据的长度统一为 num_steps(上面有),不足的用 1 即 <pad> 填充 X: tensor([[62, 25, 4, 3, 1, 1, 1, 1], [99, 10, 4, 3, 1, 1, 1, 1]], dtype=torch.int32) # 可能代表的数据形如 [["I", "try", ".", <eos>, <pad>, <pad>, <pad>, <pad>], ["This", "is", ".", <eos>, <pad>, <pad>, <pad>, <pad>]] X的有效长度: tensor([4, 4]) Y: tensor([[186, 5, 3, 1, 1, 1, 1, 1], [ 0, 8, 4, 3, 1, 1, 1, 1]], dtype=torch.int32) Y的有效长度: tensor([3, 4]) # <bos> 在 train_seq2seq() 中再加入注意这并非要求网络输入层维数为 num_steps,比如 rnn 中也有 num_steps 但输入层维数就是词表大小(即一个单词的独热编码

零极点滤波器设计大纲

2024-08-20
julyfunnotes信号处理技术学习

see: https://github.com/julyfun/dsp-lab2/blob/main/lab1-to-3.typ滤除单一频率梳状滤波器,设计零点靠近圆而极点远离。推导见 typ 中的 $10.a$$$ H(z) &= (z ^ 2 - 2 cos(w_c) z + 1) / (z ^ 2 - 2 R cos(w_c) z + R ^ 2) &tilde.eq (z ^ 2 - 1.46 z + 1) / (z ^ 2 - 1.43 z + 0.968) $$$$y[n] - 1.43 y[n - 1] + 0.968 y[n - 2] = x[n] - 1.46 x[n - 1] + x[n - 2]$$保留 / 增强单一频率齿状滤波器。设计极点靠近圆而零点远离。此处有更精巧的设计,见 $10.c$$$ H(z) &tilde.eq 1 / 9.65 times (z ^ 2 - 1.70 z + 0.722) / (z ^ 2 - 1.97 z + 0.969) &tilde.eq (0.104 z ^ 2 - 0.130 z + 0.0748) / (z ^ 2 - 1.97 z + 0.969) $$$$ y[n] - 1.97 y[n - 1] + 0.969 y[n - 2] = 0.104 x[n] - 0.130 x[n - 1] + 0.0748 x[n - 2] $

2-多臂老虎机

2024-08-19
hrljulyfunnotes技术学习

符号$cal(A)$: 动作集合 $cal(R)$: 奖励概率分布,动作 $a$ 对应一个奖励分布 $cal(R)(r | a)$对动作 $a$,定义其期望奖励为 $Q(a)$最优期望奖励 $Q^* = max_(a in cal(A)) Q(a)$懊悔 $R(a) = Q^* - Q(a)$$limits(Q)^("hat")$: 对 $a$ 的期望奖励估值名称MAB: 多臂老虎机UCB: 上置信界法问题表述多臂老虎机是无状态的强化学习,即与环境交互不会改变环境。在下述算法里,每个老虎机的奖励服从伯努利分布,即以 $p_i$ 的概率获得 $1$$epsilon$ 贪心算法$$ a_t = cases( arg max_(a in cal(A)) Q^"hat" (a) & "采样概率" 1 - epsilon, "从" cal(A) "随机选择" & "采样概率" epsilon ) $$以 $epsilon$ 的概率随机探索一个。结果由于随机的部分,懊悔是线性增长的。随时间衰减的 $epsilon$ 贪心算法$epsilon_t = 1 / t$测试时 $K = 10$(老虎机个数),结果累计懊悔是 $ln$ 形式增长的。image.png上置信界算法在 https://hrl.boyuai.com/chapter/1/%E5%A4%9A%E8%87%82%E8%80%81%E8%99%8E%E6%9C%BA/#25-%E4%B8%8A%E7%BD%AE%E4%BF%A1%E7%95%8C%E7%AE%97%E6%B3%95 中已经最小化讲清楚了。用到了霍夫丁不等式。每一时刻设一个概率 $p = 1 / t$。对于每个动作 $a$ 算出一个 $U_t^"hat" (a)$ s.t. $p = e^(-2N_t(a)U_t(a)^2) (<=> U^"hat"_t (a) = sqrt((-log p) / (2 N_t (a))) )$,根据霍夫丁不等式必有: $Q_t (a) < Q^"hat"_t (a) + u$ 至少以 $1 - p$ 概率成立,称不等式右边为期望奖励上界(其实是大概率上界)。当 $t$ 增大时该可能性极大。实操时, $U^"hat"t (a) = sqrt((-log p) / (2 (N_t (a) + 1)))$,每次选择 $a = arg max(a in cal(A)) Q^"hat" (a) + c dot U^"hat" (a)$ 其中 $c$ 为控制不确定性比重的系数。ipynb 中 $c = 1$累计懊悔也是 $ln$ 形式:image.png汤普森采样算法若拉杆 $m_1$ 次奖励为 $1$,$m_2$ 次奖励为 $0$ ,则大胆假设拉杆的奖励概率(注意奖励概率为 $p$,每次要么得 $1$ 要么得 $0$)的概率分布为 $Beta(m_1 + 1, m_2 + 1)$那么每步怎么做决策呢?我们已经大胆假设了所有拉杆的奖励的期望的分布,那么就直接对所有拉杆进行一次 $Beta$ 分布上的采样。拉动采样最大的那

iter 每次迭代 X([n, d] or [n, c, h, w]), y([n])

2024-08-19
d2ljulyfunnotes技术学习

import torch from torch import nn from d2l import torch as d2l net = nn.Sequential(nn.Flatten(), nn.Linear(784, 256), nn.ReLU(), nn.Linear(256, 10)) def init_weights(m): if type(m) == nn.Linear: nn.init.normal_(m.weight, std=0.01) net.apply(init_weights) batch_size, lr, num_epochs = 256, 0.1, 10 loss = nn.CrossEntropyLoss() trainer = torch.optim.SGD(net.parameters(), lr=lr) train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size) # iter 每次迭代 X([n, d] or [n, c, h, w]), y([n]) # 例如: (torch.Size([256, 1, 28, 28]), torch.Size([256])) class Accumulator: 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] def evaluate_accuracy(net, data_iter): 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] def train_epoch_ch3(net, train_iter, loss, updater): # 将模型设置为训练模式 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) 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): #@save 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) 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 train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer

9.1.gru

2024-08-19
d2ljulyfunnotes技术学习

$$R_t^(in n times h) = sigma (X_t W_(x r) + H_(t - 1) W_(h r) + b_r)$$$$Z_t^(in n times h) = sigma (X_t W_(x z) + H_(t - 1) W_(h z) + b_z)$$以上都独占三组参数。候选隐状态,由重置门 $R_t$ 决定是否保留旧状态,若 $R_t$ 接近 $0$ 不保留,则倾向于捕获短期依赖关系。这里又占有三组参数:$$limits(H_t)^(tilde) = tanh(X_t W_(x h) + (R_t dot.circle H_(t - 1)) W_(h h) + b_h)$$更新门将决定是否忽略当前隐状态,最终生成真正的隐状态。若 $Z_t$ 接近 $1$ 则倾向于保留旧状态,捕获长期依赖关系:$$H_t = Z_t dot.circle H_(t - 1) + (1 - Z_t) dot.circle limits(H_t)^(tilde)$$从头实现def get_params(vocab_size, num_hiddens, device): num_inputs = num_outputs = vocab_size def normal(shape): return torch.randn(size=shape, device=device)*0.01 def 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 = params H, = state outputs = [] 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_tilda Y = H @ W_hq + b_q outputs.append(Y) # [qm] 这里批量何在? return torch.cat(outputs, dim=0), (H,)高级 api 接口num_inputs = vocab_size gru_layer = nn.GRU(num_inputs, num_hiddens) model = d2l.RNNModel(gru_layer, len(vocab)) model = model.to(device) d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)问题训练好后的网络如何决定是否保留隐藏状态?是因为 R 和 Z 中的权重分布,导致输入 ahh 的无效单词就会导致 $R_t$ 重置?根据上面的 R 公式,似乎就是依赖当前输入和隐状态来决定生成重置权重

RNN() 内部含有 W, b 参数之类的

2024-08-16
d2ljulyfunnotes技术学习

nn.RNN接口很简单。注意隐状态 state 是在 RNN 对象之外另开对象进行存储的。batch_size, num_steps = 32, 35 train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps) num_hiddens = 256 # RNN() 内部含有 W, b 参数之类的 rnn_layer = nn.RNN(len(vocab), num_hiddens) # state 形状 [隐藏层数,批量大小,隐藏单元数] state = torch.zeros((1, batch_size, num_hiddens)) # X.shape = [时间步,批量,词典大小(独热)] X = torch.rand(size=(num_steps, batch_size, len(vocab))) # Y.shape = [时间步,批量,隐藏单元数], state 形状不变 Y, state = self.rnn(X, state)封装class RNNModel(nn.Module): """循环神经网络模型""" def __init__(self, rnn_layer, vocab_size, **kwargs): super(RNNModel, self).__init__(**kwargs) self.rnn = rnn_layer self.vocab_size = vocab_size self.num_hiddens = self.rnn.hidden_size # 如果RNN是双向的(之后将介绍),num_directions应该是2,否则应该是1 if not self.rnn.bidirectional: self.num_directions = 1 self.linear = nn.Linear(self.num_hiddens, self.vocab_size) else: self.num_directions = 2 self.linear = nn.Linear(self.num_hiddens * 2, self.vocab_size) def forward(self, inputs, state): X = F.one_hot(inputs.T.long(), self.vocab_size) X = X.to(torch.float32) Y, state = self.rnn(X, state) # 全连接层首先将Y的形状改为(时间步数*批量大小,隐藏单元数) # 它的输出形状是(时间步数*批量大小,词表大小)。 output = self.linear(Y.reshape((-1, Y.shape[-1]))) return output, state def begin_state(self, device, batch_size=1): if not isinstance(self.rnn, nn.LSTM): # nn.GRU以张量作为隐状态 return torch.zeros((self.num_directions * self.rnn.num_layers, batch_size, self.num_hiddens), device=device) else: # nn.LSTM以元组作为隐状态 return (torch.zeros(( self.num_directions * self.rnn.num_layers, batch_size, self.num_hiddens), device=device), torch.zeros(( self.num_directions * self.rnn.num_layers, batch_size, self.num_hiddens), device=device))32:0

获取初始参数

2024-08-16
d2ljulyfunnotes技术学习

梯度裁剪类似于球形投影,使梯度的 L2 范数不超过阈值。训练注意训练时候每个批量的隐状态是分别存储的,不会相互影响。多久一次 backward?要将一个批量内所有样本的所有时间步(在程序开始时设定,比如样本 size = 32,单次输入时间步 num_steps 为 35)forward 以后(每次 forward 仅进行一个时间步的预测),才进行 backward。因此权重参数矩阵会累乘,容易导致梯度爆炸,故进行梯度裁剪。d2l 代码中,梯度裁剪在 backward 后,updater 之前执行。注意对每个批量而言,每次输入仅有一个 token,并非多个时间步 token 同时输入。手绘2:15:13手动代码import math import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2l batch_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_size def normal(shape): return torch.randn(size=shape, device=device) * 0.01 # 隐藏层参数 W_xh = normal((num_inputs, num_hiddens)) W_hh = normal((num_hiddens, num_hiddens)) b_h = torch.zeros(num_hiddens, device=device) # 输出层参数 W_hq = normal((num_hiddens, num_outputs)) b_q = torch.zeros(num_outputs, device=device) # 附加梯度 params = [W_xh, W_hh, b_h, W_hq, b_q] for param in params: param.requires_grad_(True) return params # 前向传播 forward # 一次 forward 就将各个批量的所有时间步都生成完了 def rnn(inputs, state, params): # inputs的形状:(时间步数量,批量大小,词表大小) W_xh, W_hh, b_h, W_hq, b_q = params H, = state outputs = [] # 循环每个时间步 for X in inputs: # X的形状:(批量大小,词表大小) # 注意这里隐状态包含所有批量各自的隐状态 # H 形状: (批量大小,隐藏单元个数) H = torch.tanh(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h) # 本步输出不会影响下一步输出,只有隐状态才会 # Y 形状: (批量大小,词表大小),因为前面已经指定 num_outputs = vocab_size Y = torch.mm(H, W_hq) + b_q outputs.append(Y) # 返回一个元组,即所有时间步的输出 y 和最后一个隐状态 return torch.cat(outputs, dim=0), (H,) # torch.cat 输出为 (ns * n, n_vocab) def init_rnn_state(batch_size, num_hiddens, device): return (torch.zeros((batch_size, num_hiddens), device=device), ) # 模型封装 class RNNModelScratch: def __init__(self, vocab_size, num_hiddens, device, get_params, init_state, forward_fn): self.vocab_size, self.num_hiddens = vocab_size, num_hiddens self.params = get_params(vocab_size, num_hiddens, device) self.init_state, self.forward_fn = init_state, forward_fn def __call__(self, X, state): X = F.one_hot(X.T, self.vocab_size).type(torch.float32) return self.forward_fn(X, state, self.params) def begin_state(self, batch_size, device): return self.init_state(batch_size, self.num_hiddens, device) num_hiddens = 512 net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params, init_rnn_state, rnn) state = net.begin_state(X.shape[0], d2l.try_gpu()) # 预测 def predict_ch8(prefix, num_preds, net, vocab, device): """在prefix后面生成新字符""" state = net.begin_state(batch_size=1, device=device) outputs = [vocab[prefix[0]]] get_input = lambda: torch.tensor([outputs[-1]], device=device).reshape((1, 1)) for y in prefix[1:]: # 预热期 _, state = net(get_input(), state) outputs.append(vocab[y]) for _ in range(num_preds): # 预测num_preds步 y, state = net(get_input(), state) outputs.append(int(y.argmax(dim=1).reshape(1))) return ''.join([vocab.idx_to_token[i] for i in outputs]) # 梯度裁剪 def grad_clipping(net, theta): #@save """裁剪梯度""" if isinstance(net, nn.Module): params = [p for p in net.parameters() if p.requires_grad] else: params = net.params norm = torch.sqrt(sum(torch.sum((p.grad ** 2)) for p in params)) if norm > theta: for param in params: param.grad[:] *= theta / norm # 训练一个 epoch def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter): """训练网络一个迭代周期(定义见第8章)""" state, timer = None, d2l.Timer() metric = d2l.Accumulator(2) # 训练损失之和,词元数量 for X, Y in train_iter: if state is None or use_random_iter: # 在第一次迭代或使用随机抽样时初始化state state = net.begin_state(batch_size=X.shape[0], device=device) else: if isinstance(net, nn.Module) and not isinstance(state, tuple): # state对于nn.GRU是个张量 state.detach_() else: # state对于nn.LSTM或对于我们从零开始实现的模型是个张量 for s in state: s.detach_() y = Y.T.reshape(-1) # y.shape: (ns * n) X, y = X.to(device), y.to(device) y_hat, state = net(X, state) l = loss(y_hat, y.long()).mean() if isinstance(updater, torch.optim.Optimizer): updater.zero_grad() l.backward() grad_clipping(net, 1) updater.step() else: l.backward() grad_clipping(net, 1) # 因为已经调用了mean函数 updater(batch_size=1) metric.add(l * y.numel(), y.numel()) return math.exp(metric[0] / metric[1]), metric[1] / timer.stop() # 训练多个 epoch def train_ch8(net, train_iter, vocab, lr, num_epochs, device, use_random_iter=False): """训练模型(定义见第8章)""" loss = nn.CrossEntropyLoss() animator = d2l.Animator(xlabel='epoch', ylabel='perplexity', legend=['train'], xlim=[10, num_epochs]) # 初始化 if isinstance(net, nn.Module): updater = torch.optim.SGD(net.parameters(), lr) else: updater = lambda batch_size: d2l.sgd(net.params, lr, batch_size) predict = lambda prefix: predict_ch8(prefix, 50, net, vocab, device) # 训练和预测 for epoch in range(num_epochs): ppl, speed = train_epoch_ch8( net, train_iter, loss, updater, device, use_random_iter) if (epoch + 1) % 10 == 0: print(predict('time traveller')) animator.add(epoch + 1, [ppl]) print(f'困惑度 {ppl:.1f}, {speed:.1f} 词元/秒 {str(device)}') print(predict('time traveller')) print(predict('traveller')) num_epochs, lr = 500, 1 train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu()

8.4.RNN

2024-08-16
d2ljulyfunnotes技术学习

RNN 基础模型$$bold(H)^(in n times h)t = phi(bold(X^(in n times d "输入维度")) bold(W(x h)^(in d times h)) + bold(H)(t - 1)^(in h times h) bold(W)(h h)^(in h times h) b_h)$$$$O_t = H_t W_(h q "输出维度") + b_q$$这里 $W_(x h), W_(h h)$ 极其类似于单隐藏层感知机中的隐藏层,只是前一刻的隐藏层输出会成为下一刻隐藏层的输入的一部分。而 $H_t$ 隐状态则存储在网络之外。Perplexity 困惑度$$exp(- 1 / n sum_(t = 1)^(n) log P(x_t | x_(t - 1), ..., x_1))$$可以直接利用神经网络输出的概率,评估它有多自信。当每个输出的概率均为 $1$ 时,困惑度为 $1$,当概率为 $0$ 时困惑度正无穷,当概率为均匀分布时困惑度为唯一词元数(也是未压缩情况下存储序列最好的编码方式)。看到一个语言模型报告其 perplexity 为 $109$ 时,直观理解为它每次输出认为有 $109$ 个词作为下一个词的合理选择。ref: http://sentiment-mining.blogspot.com/2016/11/perplexity.htm

DH 描述

2024-08-16
julyfunnotes技术学习机械臂

你先看懂这里面 Z 轴,O,X 轴和 Y 轴如何确定 https://blog.csdn.net/subtitle_/article/details/130982929?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0-130982929-blog-108599703.235^v43^pc_blog_bottom_relevance_base5&spm=1001.2101.3001.4242.1&utm_relevant_index=3再看这里面的图示 https://gitcode.csdn.net/662b5441c46af92642779966.html?dp_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MTQxMTEzNSwiZXhwIjoxNzIwNDIyMTA3LCJpYXQiOjE3MTk4MTczMDcsInVzZXJuYW1lIjoid2VpeGluXzYyNzczMTE2In0.w3noMfKhmKUEEJIHgdKL9j2Tx-x9h__gf_ZRV4LJUKM每组四个参数 $theta, d, alpha, a$,其中 $theta$ 是逆解中待确定的值,其他为固定值 确定了 6 组参数以后可以确定 ori 和 po

x shape: (n, ns)

2024-08-15
d2ljulyfunnotes技术学习

拉普拉斯平滑在统计单词 / 连续单词出现次数后,计算出现概率 $P$ 时,添加一个超参数小常量。效果:若常量趋于无穷大,则概率为 $1 / "单词总数"$(对于连续单词 AB,则是趋于 $1 / P("A")$)齐普夫定律分布满足对数坐标系上的下降直线。一元语法,n 元语法均遵守这个分布。构造的数据集形如for X, Y in seq_data_iter_sequential(my_seq, batch_size=2, num_steps=5): print('X: ', X, '\nY:', Y)# x shape: (n, ns) # y shape: (n, ns) X: tensor([[ 2, 3, 4, 5, 6], [18, 19, 20, 21, 22]]) Y: tensor([[ 3, 4, 5, 6, 7], [19, 20, 21, 22, 23]]) X: tensor([[ 7, 8, 9, 10, 11], [23, 24, 25, 26, 27]]) Y: tensor([[ 8, 9, 10, 11, 12], [24, 25, 26, 27, 28]]) X: tensor([[12, 13, 14, 15, 16], [28, 29, 30, 31, 32]]) Y: tensor([[13, 14, 15, 16, 17], [29, 30, 31, 32, 33]])44:2

7.7.densenet

2024-08-15
d2ljulyfunnotes技术学习

概述densenet 与 resnet 相比,拼接了连续多个 ConvBlock 构成一个 DenseBlock,并且使用的也不是相加而是 torch.cat。例如,对于一个 DenseBlock,若输入通道为 64,第一个卷积层输出通道数为 32,则会先拼接原始 64 通道和卷积输出的 32 通道,构成 96 通道输入到第二个卷积层,再输出通道数 32,则拼接为 96 + 32 = 128 通道。此处的标准 conv_block 变成了 BN + ReLU + Conv2d3x3为了保持通道数不爆炸,引入过渡块,直接 BN + Conv2d1x1 + AvgPool2x2 把通道数压下来(压一半)。最前面和最后面与 Googlenet 一致。训练时间 2m 36.6s (高宽已经 resize 到 96)loss 0.140, train acc 0.949, test acc 0.903练习为什么我们在过渡层使用平均汇聚层而不是最大汇聚层?不知道DenseNet的优点之一是其模型参数比ResNet小。为什么呢?DenseNet一个诟病的问题是内存或显存消耗过多。真的是这样吗?可以把输入形状换成$224 times 224$,来看看实际的显存消耗。有另一种方法来减少显存消耗吗?需要改变框架么?实现DenseNet论文 :cite:Huang.Liu.Van-Der-Maaten.ea.2017表1所示的不同DenseNet版本。应用DenseNet的思想设计一个基于多层感知机的模型。将其应用于 :numref:sec_kaggle_house中的房价预测任务

[解释] 输入通道 3,输出通道 96,输出大小 54 * 54,注意这里 kernel 没有刚好填到最右端,下取整为 54. 卷积核有 96 个(每个输出通道拥有一个卷积核,每个卷积核的形状是 3 * 11 * 11).

2024-08-15
d2ljulyfunnotes技术学习

四维度数据顺序为 n, c, h, w. ref: https://github.com/julyfun/numpytorch/blob/main/numpytorch/nn/modules/conv.pyFlatten 不会展开 batch 维度,(n, 512, 1, 1) 经过 Flatten 以后的形状为 (n, 512)卷积层中,输入通道到输出通道是全连接的。卷积核的形状是 out_channel * in_channel * k_h * k_w,这也是一层的参数量,每个输入-输出通道 pair 拥有仅一个 $k_h times k_w$ 的卷积核(或者说每个输出通道拥有一个 in_channel * k_h * k_w 的卷积核).torch.nn.Conv2d# [解释] 输入通道 3,输出通道 96,输出大小 54 * 54,注意这里 kernel 没有刚好填到最右端,下取整为 54. 卷积核有 96 个(每个输出通道拥有一个卷积核,每个卷积核的形状是 3 * 11 * 11). nn.Conv2d(3, 96, kernel_size=11, stride=4, padding=1),ClassesConvTranspose2dself.upconv2 = nn.ConvTranspose2d(128, 64, kernel_size=2, stride=2)他会在卷积逆操作的同时插入 0,放大分辨率。输入张量: 1 2 3 4 插入零后的张量: 1 0 2 0 0 0 0 0 3 0 4 0 0 0 0 0 卷积核: 1 0 0 1 转置卷积操作的输出: 1 0 2 0 0 0 0 0 3 0 4 0 0 0 0

7.6.resnet

2024-08-15
d2ljulyfunnotes技术学习

回忆提纲resnet 块一条路径为 Conv + BN + ReLU + Conv + BN,保持形状不变,将输入直接加到输出,再跟一个 ReLU.为什么有用? 当网络深度加深时,可能出现网络退化(不是梯度爆炸)。事实上,可能中间的某一层已经是最优网络。resnet 非常容易学习到恒等映射,避免了网络退化。ref: https://blog.csdn.net/fengdu78/article/details/128451148手绘:截图:通道数不断增加(在每一 block 的第一个卷积层增加):conv5 output shape: (1, 64, 112, 112) batchnorm4 output shape: (1, 64, 112, 112) relu0 output shape: (1, 64, 112, 112) pool0 output shape: (1, 64, 56, 56) sequential1 output shape: (1, 64, 56, 56) sequential2 output shape: (1, 128, 28, 28) sequential3 output shape: (1, 256, 14, 14) sequential4 output shape: (1, 512, 7, 7) pool1 output shape: (1, 512, 1, 1) dense0 output shape: (1, 10)loss 0.016, train acc 0.996, test acc 0.919耗时 3m 0.1s,train acc 十分惊人练习:numref:fig_inception中的Inception块与残差块之间的主要区别是什么?在删除了Inception块中的一些路径之后,它们是如何相互关联的?参考ResNet论文 :cite:He.Zhang.Ren.ea.2016中的表1,以实现不同的变体。对于更深层次的网络,ResNet引入了“bottleneck”架构来降低模型复杂性。请试着去实现它。bottleneck 结构: 通过前后两个 Conv1x1,先降低通道,再恢复通道。ref: https://blog.csdn.net/DanTaWu/article/details/111468257 在ResNet的后续版本中,作者将“卷积层、批量规范化层和激活层”架构更改为“批量规范化层、激活层和卷积层”架构。请尝试做这个改进。详见 :cite:He.Zhang.Ren.ea.2016*1中的图1。改变后,需要使用 batch_size = 64, lr = 0.005,即缩小学习率和 batchsize为什么即使函数类是嵌套的,我们仍然要限制增加函数的复杂性呢?计算资源问题。复杂度越高,优化算法找到良好局部最小值的可能性也会下降。更简单的模型更容易解释。更简单的模型泛化能力更好,因为它不依赖特定训练集的特征

7.5.BN

2024-08-06
d2ljulyfunnotes技术学习

mean 用法ref: https://blog.csdn.net/qq_40714949/article/details/115485140dim 写第几维,第几维就会被求和,并消失。x = torch.Tensor([1, 2, 3, 4, 5, 6]).view(2, 3) y_0 = torch.mean(x, dim=0) y_1 = torch.mean(x, dim=1) print(x) print(y_0) print(y_1)tensor([[1., 2., 3.], [4., 5., 6.]]) tensor([2.5000, 3.5000, 4.5000]) tensor([2., 5.])BN 层回忆提纲\mathrm{BN}(\mathbf{x}) = \boldsymbol{\gamma} \odot \frac{\mathbf{x} - \hat{\boldsymbol{\mu}}_\mathcal{B}}{\hat{\boldsymbol{\sigma}}_\mathcal{B}} + \boldsymbol{\beta}BN 层对上一层的输出进行规范化到 0 均值和 1 方差,同时学习一个缩放系数 gamma(初始为 1)和平移系数 beta(初始为 0)。符合直觉地,全连接层的每个元素对样本维求均值,mean.shape = (元素数) 。卷积层的每个通道对(样本,宽,高)求均值,mean.shape = (通道数)。下面 gamma 和 beta 是待学习参数,momentum 是上一次均值的权重(并不会直接采纳本次求得的均值,形同低通滤波)。非训练模式时,直接使用先前计算出来的 moving_mean, moving_var.import torch from torch import nn from d2l import torch as d2l def batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum): # 通过is_grad_enabled来判断当前模式是训练模式还是预测模式 if not torch.is_grad_enabled(): # 如果是在预测模式下,直接使用传入的移动平均所得的均值和方差 X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps) else: assert len(X.shape) in (2, 4) if len(X.shape) == 2: # 使用全连接层的情况,计算特征维上的均值和方差 mean = X.mean(dim=0) var = ((X - mean) ** 2).mean(dim=0) else: # 使用二维卷积层的情况,计算通道维上(axis=1)的均值和方差。 # 这里我们需要保持X的形状以便后面可以做广播运算 mean = X.mean(dim=(0, 2, 3), keepdim=True) var = ((X - mean) ** 2).mean(dim=(0, 2, 3), keepdim=True) # 训练模式下,用当前的均值和方差做标准化 X_hat = (X - mean) / torch.sqrt(var + eps) # 更新移动平均的均值和方差 moving_mean = momentum * moving_mean + (1.0 - momentum) * mean moving_var = momentum * moving_var + (1.0 - momentum) * var Y = gamma * X_hat + beta # 缩放和移位 return Y, moving_mean.data, moving_var.data class BatchNorm(nn.Module): # num_features:完全连接层的输出数量或卷积层的输出通道数。 # num_dims:2表示完全连接层,4表示卷积层 def __init__(self, num_features, num_dims): super().__init__() if num_dims == 2: shape = (1, num_features) else: shape = (1, num_features, 1, 1) # 参与求梯度,拉伸和偏移参数,分别初始化成1和0 self.gamma = nn.Parameter(torch.ones(shape)) self.beta = nn.Parameter(torch.zeros(shape)) # 非模型参数的变量初始化为0和1 self.moving_mean = torch.zeros(shape) self.moving_var = torch.ones(shape) def forward(self, X): # 如果X不在内存上,将moving_mean和moving_var # 复制到X所在显存上 if self.moving_mean.device != X.device: self.moving_mean = self.moving_mean.to(X.device) self.moving_var = self.moving_var.to(X.device) # 保存更新过的moving_mean和moving_var Y, self.moving_mean, self.moving_var = batch_norm( X, self.gamma, self.beta, self.moving_mean, self.moving_var, eps=1e-5, momentum=0.9) return Y net = nn.Sequential( nn.Conv2d(1, 6, kernel_size=5), BatchNorm(6, num_dims=4), nn.Sigmoid(), nn.AvgPool2d(kernel_size=2, stride=2), nn.Conv2d(6, 16, kernel_size=5), BatchNorm(16, num_dims=4), nn.Sigmoid(), nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(), nn.Linear(16*4*4, 120), BatchNorm(120, num_dims=2), nn.Sigmoid(), nn.Linear(120, 84), BatchNorm(84, num_dims=2), nn.Sigmoid(), nn.Linear(84, 10))由上述示例知,BN 是放在激活函数和卷积或线性层之间。为什么只检查 moving_mean 所在的显存,而不需要检查 gamma, beta 所在的显存?chat: 在PyTorch中,nn.Parameter对象自动与模块(nn.Module)注册,并且会随模块一起被移动到指定的设备(CPU或GPU)上。当你调用.to(device)、.cuda()或.cpu()方法在一个nn.Module上时,它不仅会移动模块的参数(即由nn.Parameter定义的参数)到指定的设备,还会移动缓冲区(如批量归一化层的运行时均值和方差)。然而,这里的关键点是self.moving_mean和self.moving_var并不是通过nn.Parameter定义的,它们是作为普通的torch.Tensor对象存在的,不会自动随着模块的.to(device)调用而移动。因此,在BatchNorm类的forward方法中,需要手动检查self.moving_mean和self.moving_var是否与输入X在同一设备上。如果它们不在同一设备上,就需要手动将它们移动到X所在的设备,以保证计算能够正常进行。这是为了确保所有用于计算的张量都在同一设备上,从而避免因设备不匹配导致的错误。verified. loss 0.272, train acc 0.899, test acc 0.864学习到的参数:net[1].gamma.reshape((-1,)), net[1].beta.reshape((-1,))(tensor([2.0412, 4.2506, 2.4837, 2.8996, 0.3911, 3.2350], device='cuda:0', grad_fn=<ViewBackward0>), tensor([-0.7424, 2.6919, 2.7948, -2.8892, -0.3490, 1.4538], device='cuda:0', grad_fn=<ViewBackward0>))pure pytorchnet = nn.Sequential( nn.Conv2d(1, 6, kernel_size=5), nn.BatchNorm2d(6), nn.Sigmoid(), nn.AvgPool2d(kernel_size=2, stride=2), nn.Conv2d(6, 16, kernel_size=5), nn.BatchNorm2d(16), nn.Sigmoid(), nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(), nn.Linear(256, 120), nn.BatchNorm1d(120), nn.Sigmoid(), nn.Linear(120, 84), nn.BatchNorm1d(84), nn.Sigmoid(), nn.Linear(84, 10))loss 0.264, train acc 0.904, test acc 0.853炼金术从 BN 开始就很难解释网络层作用的原理了。Exercise在使用批量规范化之前,我们是否可以从全连接层或卷积层中删除偏置参数?为什么?可以,BN 层会消除前一层 $b$ 的影响比较LeNet在使用和不使用批量规范化情况下的学习率。绘制训练和测试准确度的提高。使用 (10 epochs):loss 0.264, train acc 0.904, test acc 0.853不使用 BN:loss 0.502, train acc 0.807, test acc 0.804学习率有多高?带 BN 学习率 1.0,不带则 0.9我们是否需要在每个层中进行批量规范化?尝试一下?不删除BN层,则loss 0.268, train acc 0.901, test acc 0.861 只保留第二个和第四个BN层,则loss 0.289, train acc 0.894, test acc 0.787 只保留第一个和第三个BN层,则loss 0.286, train acc 0.893, test acc 0.820 只删除第一个BN层,则loss 0.272, train acc 0.899, test acc 0.769 只删除第二个BN层,则loss 0.269, train acc 0.901, test acc 0.774 只删除第三个BN层,则loss 0.272, train acc 0.901, test acc 0.857 只删除第四个BN层,则loss 0.283, train acc 0.895, test acc 0.857 可以看出删除全连接层后面的批量规范化对结果影响不大,因此实际上是可以删除的可以通过批量规范化来替换暂退法吗?行为会如何改变?似乎是可以的,BN层兼具稳定训练过程和正则化的效果,将全连接阶段的BN层全部替换为弃置概率为0.5的Dropout层, 则loss 0.486, train acc 0.827, test acc 0.763,对比而言精度不如使用BN层的情况,而且train acc与test acc的差距也更大,说明BN反而能够起到比dropout更好更稳定的正则化效果确定参数beta和gamma,并观察和分析结果。通过实验观察模型中gamma和beta的情况。从结果来看,随着网络加深,beta的值逐渐稳定到0附近,而gamma的值则逐步稳定到1到2之间,说明BN层确实有助于稳定中间结果的数据分布查看高级API中有关BatchNorm的在线文档,以查看其他批量规范化的应用。研究思路:可以应用的其他“规范化”转换?可以应用概率积分变换吗?全秩协方差估计可以么?BatchNorm vs LayerNorm不错的图示解释: https://blog.csdn.net/Little_White_9/article/details/123345062稍正式的解释,同一个图: https://snailcoder.github.io/2024/05/01/batchnorm-and-layernorm.htm

4.5.weight-decay

2024-08-06
d2ljulyfunnotes技术学习

默认情况下,PyTorch 同时衰减权重和偏移可以手动设置不衰减偏置:def train_concise(wd): net = nn.Sequential(nn.Linear(num_inputs, 1)) for param in net.parameters(): param.data.normal_() loss = nn.MSELoss(reduction='none') num_epochs, lr = 100, 0.003 # 偏置参数没有衰减 trainer = torch.optim.SGD([ {"params":net[0].weight,'weight_decay': wd}, {"params":net[0].bias}], lr=lr) animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log', xlim=[5, num_epochs], legend=['train', 'test']) for epoch in range(num_epochs): for X, y in train_iter: trainer.zero_grad() l = loss(net(X), y) l.mean().backward() trainer.step() if (epoch + 1) % 5 == 0: animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss), d2l.evaluate_loss(net, test_iter, loss))) print('w的L2范数:', net[0].weight.norm().item()

先定义一个丢弃层

2024-08-06
d2ljulyfunnotes技术学习

Dropout 法就是训练中传播时随机丢弃一些节点的权重。仅在训练期间有效,每次前向传播均会重新生成 mask手动实现# 先定义一个丢弃层 def dropout_layer(X, dropout): assert 0 <= dropout <= 1 # 在本情况中,所有元素都被丢弃 if dropout == 1: return torch.zeros_like(X) # 在本情况中,所有元素都被保留 if dropout == 0: return X mask = (torch.rand(X.shape) > dropout).float() return mask * X / (1.0 - dropout) dropout1, dropout2 = 0.2, 0.5 class Net(nn.Module): def __init__(self, num_inputs, num_outputs, num_hiddens1, num_hiddens2, is_training = True): super(Net, self).__init__() self.num_inputs = num_inputs self.training = is_training self.lin1 = nn.Linear(num_inputs, num_hiddens1) self.lin2 = nn.Linear(num_hiddens1, num_hiddens2) self.lin3 = nn.Linear(num_hiddens2, num_outputs) self.relu = nn.ReLU() def forward(self, X): H1 = self.relu(self.lin1(X.reshape((-1, self.num_inputs)))) # 只有在训练模型时才使用dropout if self.training == True: # 在第一个全连接层之后添加一个dropout层 H1 = dropout_layer(H1, dropout1) H2 = self.relu(self.lin2(H1)) if self.training == True: # 在第二个全连接层之后添加一个dropout层 H2 = dropout_layer(H2, dropout2) out = self.lin3(H2) return out net = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2)简洁实现net = nn.Sequential(nn.Flatten(), nn.Linear(784, 256), nn.ReLU(), # 在第一个全连接层之后添加一个dropout层 # 这里 dropout1 是一个实数 nn.Dropout(dropout1), nn.Linear(256, 256), nn.ReLU(), # 在第二个全连接层之后添加一个dropout层 nn.Dropout(dropout2), nn.Linear(256, 10)) def init_weights(m): if type(m) == nn.Linear: nn.init.normal_(m.weight, std=0.01) net.apply(init_weights)

样本 * 通道 * h * w

2024-08-05
d2ljulyfunnotes技术学习

阅读网络结构的函数参数# 样本 * 通道 * h * w # 输入为 X = torch.randn(1, 1, 224, 224) net = nn.Sequential( # [解释] 输入通道 1,输出通道 96,输出大小 54 * 54,注意这里 kernel 没有刚好填到最右端,下取整为 54. 卷积核有 96 个(每个输入-输出通道组合拥有一个卷及核) nn.Conv2d(1, 96, kernel_size=11, stride=4, padding=1), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2), # 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数 nn.Conv2d(96, 256, kernel_size=5, padding=2), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2), # 使用三个连续的卷积层和较小的卷积窗口。 # 除了最后的卷积层,输出通道的数量进一步增加。 # 在前两个卷积层之后,汇聚层不用于减少输入的高度和宽度 nn.Conv2d(256, 384, kernel_size=3, padding=1), nn.ReLU(), nn.Conv2d(384, 384, kernel_size=3, padding=1), nn.ReLU(), nn.Conv2d(384, 256, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2), nn.Flatten(), # 这里,全连接层的输出数量是LeNet中的好几倍。使用dropout层来减轻过拟合 nn.Linear(6400, 4096), nn.ReLU(), nn.Dropout(p=0.5), nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(p=0.5), # 最后是输出层。由于这里使用Fashion-MNIST,所以用类别数为10,而非论文中的1000 nn.Linear(4096, 10)

7.3.nin

2024-08-05
d2ljulyfunnotes技术学习

关于卷积层的提示注意输入通道和输出通道是全连接的,即:若单层图像的核有 X 个(取决于图像长宽,kernel_size, padding 和 stride),输入通道 n,输出通道为 m,则核函数有 X * n * m 个核参数则有 X \times n \times m \times \texttt{kernel_size} \times \texttt{kernel_size} 个ref: https://zh.d2l.ai/chapter_convolutional-neural-networks/channels.html特色相比 vgg,nin 主要特色两个:block 内部是 Conv + ReLU + Conv(kernel_size = 1) + ReLU + Conv(kernel_size = 1) + ReLU + MaxPool,给每个像素做了通道到通道的全连接最后直接是 384 通道到分类数通道的 nin block,没有 Linear 层全局平均汇聚层将图像的大小压缩至 1 * 1,不改变 n 和 channels,也不改变维数。def nin_block(in_channels, out_channels, kernel_size, strides, padding): return nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size, strides, padding), nn.ReLU(), nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU(), nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU()) net = nn.Sequential( nin_block(1, 96, kernel_size=11, strides=4, padding=0), nn.MaxPool2d(3, stride=2), nin_block(96, 256, kernel_size=5, strides=1, padding=2), nn.MaxPool2d(3, stride=2), nin_block(256, 384, kernel_size=3, strides=1, padding=1), nn.MaxPool2d(3, stride=2), nn.Dropout(0.5), # 标签类别数是10 nin_block(384, 10, kernel_size=3, strides=1, padding=1), nn.AdaptiveAvgPool2d((1, 1)), # 将四维的输出转成二维的输出,其形状为(批量大小,10) nn.Flatten())X = torch.rand(size=(1, 1, 224, 224)) for layer in net: X = layer(X) print(layer.__class__.__name__,'output shape:\t', X.shape)Copy to clipboard Sequential output shape: torch.Size([1, 96, 54, 54]) # 包含整个 nin_block MaxPool2d output shape: torch.Size([1, 96, 26, 26]) Sequential output shape: torch.Size([1, 256, 26, 26]) MaxPool2d output shape: torch.Size([1, 256, 12, 12]) Sequential output shape: torch.Size([1, 384, 12, 12]) MaxPool2d output shape: torch.Size([1, 384, 5, 5]) Dropout output shape: torch.Size([1, 384, 5, 5]) Sequential output shape: torch.Size([1, 10, 5, 5]) AdaptiveAvgPool2d output shape: torch.Size([1, 10, 1, 1]) Flatten output shape: torch.Size([1, 10]

7.4.googlenet

2024-08-05
d2ljulyfunnotes技术学习

https://www.cv-foundation.org/openaccess/content_cvpr_2015/html/Szegedy_Going_Deeper_With_2015_CVPR_paper.html[ ] 不错的论文解读: https://www.cnblogs.com/harrymore/p/17263443.html相比于 nin,google net 的 inception 块有 4 条路径。注意在合并时是直接在 channels 维度拼接而不是相加。但 googlenet 的最后重新引入了 Linear 层。import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2l class Inception(nn.Module): # c1--c4是每条路径的输出通道数 def __init__(self, in_channels, c1, c2, c3, c4, **kwargs): super(Inception, self).__init__(**kwargs) # 线路1,单1x1卷积层 self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1) # 线路2,1x1卷积层后接3x3卷积层 self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1) self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1) # 线路3,1x1卷积层后接5x5卷积层 self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1) self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2) # 线路4,3x3最大汇聚层后接1x1卷积层 self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1) self.p4_2 = nn.Conv2d(in_channels, c4, kernel_size=1) def forward(self, x): p1 = F.relu(self.p1_1(x)) p2 = F.relu(self.p2_2(F.relu(self.p2_1(x)))) p3 = F.relu(self.p3_2(F.relu(self.p3_1(x)))) p4 = F.relu(self.p4_2(self.p4_1(x))) # 在通道维度上连结输出 return torch.cat((p1, p2, p3, p4), dim=1) b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1), nn.ReLU(), nn.Conv2d(64, 192, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) b3 = nn.Sequential(Inception(192, 64, (96, 128), (16, 32), 32), Inception(256, 128, (128, 192), (32, 96), 64), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) b4 = nn.Sequential(Inception(480, 192, (96, 208), (16, 48), 64), Inception(512, 160, (112, 224), (24, 64), 64), Inception(512, 128, (128, 256), (24, 64), 64), Inception(512, 112, (144, 288), (32, 64), 64), Inception(528, 256, (160, 320), (32, 128), 128), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128), Inception(832, 384, (192, 384), (48, 128), 128), nn.AdaptiveAvgPool2d((1,1)), nn.Flatten()) net = nn.Sequential(b1, b2, b3, b4, b5, nn.Linear(1024, 10)

No more posts to load.