基于PPO的自动驾驶小车绕圈任务
1.任务介绍
任务来源: DQN: Deep Q Learning |自动驾驶入门(?) |算法与实现
任务原始代码: self-driving car
在上一篇使用了DDPG算法完成自动驾驶小车绕圈任务之后,继续学习了PPO算法,并将其用在该任务上。
最终效果:
PPO目前的调试效果用在此任务中,比不过DDPG算法,尤其是在转弯还是有些不流畅,后面还会继续调试。
2.调试记录
主要问题:遇到的第一个主要问题,就是小车始终拐弯拐不过去,学习不到正确的策略。
2.1 clip_epsilon和lr调整
记录:尝试调大和调小clip_epsilon以及学习率,发现都不能解决问题
2.2 调整critic_loss的权重
记录:尝试调大和调小critic_loss的权重,也不能解决问题
2.3 去掉advantages标准化
记录:通过打印actor_loss、critic_loss、total_loss,发现actor_loss的量级和critic_loss的量级压根不是一个量级的,由于actor_loss非常小,导致梯度下降极为缓慢,所以学习不到正确的策略。
然后通过排查发现,由于使用了advantages标准化策略,才导致的actor_loss变得极其小
整体来说小车是学习到了正确的策略的,跑了48小时是有的,但是有几个问题,一是小车在直道速度比较高,但是在过弯的时候会把车速下降到很低,几乎静止的状态,然后打方向,且训练很久之后依然还是这样;其他的看起来整体的avg_dists是在整体上升的,不过整体训练学习到正确的策略需要比较长的时间,基本上在episodes 250之后dist才越来越远的。
2.4 TrajectoryBuffer用完之后清空
torch.OutOfMemoryError: CUDA out of memory. Tried to allocate 2.12 GiB. GPU 0 has a total capacity of 7.55 GiB of which 1.80 GiB is free. Including non-PyTorch memory, this process has 5.72 GiB memory in use. Of the allocated memory 5.03 GiB is allocated by PyTorch, and 585.83 MiB is reserved by PyTorch but unallocated. If reserved but unallocated memory is large try setting PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True to avoid fragmentation. See documentation for Memory Management (https://pytorch.org/docs/stable/notes/cuda.html#environment-variables)
记录:这是上面去掉advantages标准化训练到episodes 340的时候报的错,经过排查发现可能TrajectoryBuffer用完之后没有手动清空,导致内存泄漏
尝试代码:
class PPOAgent:def learn(self):...# 清空缓冲区self.buffer.clear()torch.cuda.empty_cache()def train():...# 清空临时Buffer并释放显存buffer.clear()del bufferagent.learn()...
现阶段主要问题:小车在直道速度比较高,但是在过弯的时候会把车速下降到很低,几乎静止的状态,然后打方向,连续性不太好。推断可能是后期探索不足导致的,所以后面主要围绕增加探索方向尝试。
2.5 actor网络结构改变
记录:尝试增加actor网络的层数以及神经元个数,发现都不能够解决问题;
2.6 增加熵正则化
记录:添加熵正则化的目的是鼓励探索,防止策略过早收敛;熵正则化项对应系数entropy_coef在经过一系列缩小放大的尝试之后,发现都没有效果;
尝试代码:
class PPOAgent:def learn(self):...mu, std = self.actor(batch_states.to('cpu'))dist = torch.distributions.Normal(mu, std)# entropy() 方法用于计算正态分布的熵。熵越大,表示分布的不确定性越高;熵越小,表示分布的不确定性越低。entropy = dist.entropy().mean()...entropy_loss = -self.entropy_coef * entropyactor_loss = actor_loss + entropy_loss......
2.7 actor采用动态标准差
记录:PPO算法代码最开始采用的是固定标准差方法,存在一些局限性,无法根据状态动态调整动作的探索强度(例如在直道加速、弯道减速时需不同标准差),所以小车在弯道部分探索不足,所以弯道会显得非常不连续;使用动态标准差之后弯道连续性有很大改善。
效果如下:能够看到使用动态标准差,增加探索率之后,小车整体过弯的流畅度是相较于之前有较大提升的,说明主要问题就是在标准差上,继续优化即可解决不流畅的问题
尝试代码:
class Actor(nn.Module):def __init__(self, input_dims, action_dim):...# 固定标准差# self.log_std = nn.Parameter(torch.zeros(action_dim)) # 对数标准差(可学习参数)# 动态标准差self.log_std = nn.Linear(256, action_dim) # 新增:动态生成log_std...def forward(self, state):...# 固定标准差# std = torch.exp(self.log_std) # 标准差# 动态标准差log_std = self.log_std(x) # 动态生成log_stdstd = torch.exp(log_std) # 保证标准差正数...
3.相比DDPG代码主要改进点
3.1 Actor网络更新
Actor网络主要是输出了两个值,一个均值一个标准差
class Actor(nn.Module):def __init__(self, input_dims, action_dim):super(Actor, self).__init__()self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")self.fc1 = nn.Linear(input_dims[0], 256)self.fc2 = nn.Linear(256, 256)self.mu = nn.Linear(256, action_dim) # 均值输出# 初始化最后一层权重为小值nn.init.orthogonal_(self.mu.weight, gain=0.01)nn.init.constant_(self.mu.bias, 0.0)# 固定标准差# self.log_std = nn.Parameter(torch.zeros(action_dim)) # 对数标准差(可学习参数)# 动态标准差self.log_std = nn.Linear(256, action_dim) # 新增:动态生成log_std# 初始化log_std层权重,限制初始标准差范围nn.init.orthogonal_(self.log_std.weight, gain=0.01)nn.init.constant_(self.log_std.bias, -1.0) # 初始log_std≈-1 → std≈0.36def forward(self, state):# print("state device: ", state.device)x = F.relu(self.fc1(state))x = F.relu(self.fc2(x))mu = torch.tanh(self.mu(x)) # 归一化到[-1,1]# 取exp是为了保证始终为正值,这个log_std是会随着反省传播自动学习更新的# 固定标准差# std = torch.exp(self.log_std) # 标准差# 动态标准差log_std = self.log_std(x) # 动态生成log_stdstd = torch.exp(log_std) # 保证标准差正数return mu, std
3.2 Critic网络变更
class Critic(nn.Module):def __init__(self, input_dims):super(Critic, self).__init__()self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")self.fc1 = nn.Linear(input_dims[0], 256).to(self.device)self.fc2 = nn.Linear(256, 256).to(self.device)self.v = nn.Linear(256, 1).to(self.device) # 状态价值输出def forward(self, state):x = F.relu(self.fc1(state))x = F.relu(self.fc2(x))return self.v(x)
3.3 ReplayBuffer替换为TrajectoryBuffer
class TrajectoryBuffer:def __init__(self):self.states = []self.actions = []self.log_probs = []self.rewards = []self.next_states = []self.dones = []def clear(self):self.states.clear()self.actions.clear()self.log_probs.clear()self.rewards.clear()self.next_states.clear()self.dones.clear()def store(self, state, action, log_prob, reward, next_state, done):self.states.append(state)self.actions.append(action)self.log_probs.append(log_prob)self.rewards.append(reward)self.next_states.append(next_state)self.dones.append(done)def get_all(self):return (torch.FloatTensor(np.array(self.states)),torch.FloatTensor(np.array(self.actions)),torch.FloatTensor(np.array(self.log_probs)),torch.FloatTensor(np.array(self.rewards)),torch.FloatTensor(np.array(self.next_states)),torch.FloatTensor(np.array(self.dones)))
3.4 DDPGAgent替换为PPOAgent
1.select_action由actor网络输出选择确定动作,变为actor网络输出概率分布,通过对概率分布选择随即动作;
class PPOAgent:def select_action(self, state):state = torch.FloatTensor(state).unsqueeze(0)mu, std = self.actor(state)# 创建一个均值为mu,标准差为std的高斯分布dist = torch.distributions.Normal(mu, std)action = dist.sample()# 为了在策略更新过程中使用,logπ_θ(a∣s)log_prob = dist.log_prob(action).sum(dim=1)return action.squeeze(0).detach().numpy(), log_prob.detach()
2.添加优势函数计算;
class PPOAgent:def compute_gae(self, rewards, values, dones, next_value):values = values.detach() # 分离梯度# 计算GAE优势估计advantages = []gae = 0next_value = next_value.detach().to(self.device)for t in reversed(range(len(rewards))):delta = rewards[t] + self.gamma * next_value * (1 - dones[t]) - values[t].item()gae = delta + self.gamma * self.gae_lambda * (1 - dones[t]) * gaeadvantages.insert(0, gae)next_value = values[t]advantages = torch.stack(advantages).to(self.device)return advantages.clone().detach().float()
3.learn函数实现;
class PPOAgent:def learn(self):# 从缓冲区获取所有轨迹数据states, actions, old_log_probs, rewards, next_states, dones = self.buffer.get_all()# 移至设备(GPU/CPU)states = states.to(self.device)actions = actions.to(self.device)old_log_probs = old_log_probs.to(self.device)rewards = rewards.to(self.device)next_states = next_states.to(self.device)dones = dones.to(self.device)# 计算优势估计(GAE)with torch.no_grad():values = self.critic(states).squeeze()next_value = self.critic(next_states[-1]).detach()advantages = self.compute_gae(rewards, values, dones, next_value)# 标准化优势,准化优势函数更多是工程上的实践,用于提高训练的稳定性,而非算法理论推导的必要步骤# advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8)# 多轮优化for _ in range(self.epochs):# 随机打乱数据索引indices = torch.randperm(len(states))# 分批次训练for start in range(0, len(indices), self.batch_size):end = start + self.batch_sizebatch_idx = indices[start:end]# 提取批次数据batch_states = states[batch_idx]batch_actions = actions[batch_idx]batch_old_log_probs = old_log_probs[batch_idx]batch_advantages = advantages[batch_idx]# 计算新策略的对数概率mu, std = self.actor(batch_states.to('cpu'))dist = torch.distributions.Normal(mu, std)new_log_probs = dist.log_prob(batch_actions.to('cpu')).sum(dim=1)# 计算策略损失(剪切)ratio = torch.exp(new_log_probs.to(self.device) - batch_old_log_probs)surr1 = ratio * batch_advantagessurr2 = torch.clamp(ratio, 1 - self.clip_epsilon, 1 + self.clip_epsilon) * batch_advantagesactor_loss = -torch.min(surr1, surr2).mean()# 计算Critic损失curr_values = self.critic(batch_states).squeeze() # x *targets = batch_advantages.squeeze() + values[batch_idx].detach()critic_loss = F.mse_loss(curr_values, targets)# 总损失entropy = dist.entropy().mean()total_loss = actor_loss + 0.5 * critic_loss - self.entropy_coef * entropy # 调整熵系数# 反向传播与优化self.optimizer.zero_grad()total_loss.backward()torch.nn.utils.clip_grad_norm_(self.actor.parameters(), 0.5)torch.nn.utils.clip_grad_norm_(self.critic.parameters(), 0.5)self.optimizer.step()# 清空缓冲区self.buffer.clear()torch.cuda.empty_cache()
4.任务思考
1.select_action函数中log_prob = dist.log_prob(action).sum(dim=1),为什么要对多维情况进行求和?求和之后不会影响计算的准确性么?这为什么是可行的??
合理的,并且不会影响计算的准确性,原因如下:
2.在actor的forward中对 std 取指数操作不是会改变std实际的值么?它后续又被用在构建高斯分布上,如果值改变了,那其实它不就影响了高斯分布在sample时候的值,进而影响action的输出,这为什么是可行的?
- 探索与利用的平衡:通过调整std的值,可以在探索(exploration)和利用(exploitation)之间找到平衡。较大的std鼓励算法探索更多的动作空间,而较小的std则让算法更倾向于选择高奖励的动作。
- 自适应调整:std作为一个可学习参数,会在训练过程中通过梯度下降自动调整。算法会根据环境的反馈,学习到合适的std值,以平衡探索和利用。
- 指数函数的单调性:指数函数是单调递增的,因此 log_std 的变化可以直接反映在 std 上。这意味着优化过程可以有效地调整std的值,而不会出现非法值。
3.PPO算法中使用KL散度惩罚和使用clip截断两种方式各有什么优劣?
4.PPO算法在计算loss的时候,为什么使用使用total_loss = actor_loss + 0.5 * critic_loss?这个做法相对于分开对loss进行反响传播有什么优势?还有就是相加的时候为什么还对critic_loss取了0.5的权重?
总损失合并的优势:
(1) 统一优化过程
- 同步更新:Actor和Critic的参数在一次反向传播中同时更新,确保两者的优化步骤一致。若分开反向传播,可能出现梯度更新顺序或步长不一致的问题,导致训练不稳定。
- 计算效率:合并损失后,仅需一次前向传播和一次反向传播,减少计算开销。分开更新则需要两次独立的前向-反向过程,增加资源消耗。
(2) 梯度平衡
- 协同优化:Actor依赖Critic提供的价值估计(如优势函数),Critic依赖Actor生成的经验数据。合并损失可确保两者的参数更新协同进行,避免因单独更新导致的目标不一致。
对 critic_loss 赋予0.5权重的意义:
(1) 平衡损失量级
- 问题:Critic的损失(如价值函数误差)通常远大于Actor的损失(如策略梯度损失)。若不调整权重,Critic_loss可能主导总损失,导致Actor无法有效学习。
- 解决:通过乘以0.5(或其他系数),降低Critic_loss的权重,使两者的梯度贡献趋于平衡。
(2) 经验性调参
- 实验验证:在PPO的原始论文和后续实践中,发现0.5的权重能在多数任务中平衡Actor和Critic的学习速度。例如,OpenAI的官方实现和Stable Baselines库均采用类似设计。
- 任务适应性:对于某些任务,可能需要调整该系数(如0.2或0.8),但0.5是一个鲁棒的默认值。
(3) 正则化效果
- 防止过拟合:Critic_loss的权重降低可视为一种正则化手段,避免Critic网络过度拟合当前经验,提升泛化能力。
分开反向传播的潜在问题:
(1) 梯度冲突
- 不一致的优化方向:若Actor和Critic分开更新,两者的梯度方向可能冲突,导致参数震荡或收敛缓慢。
- 优化器状态干扰:自适应优化器(如Adam)的内部状态(如动量、二阶矩估计)会因多次反向传播而混乱。
(2) 实现复杂度
- 代码冗余:分开更新需分别调用 loss.backward() 和 optimizer.step(),增加代码复杂性和维护成本。
5.Actor和Critic的loss采用相加的方法,如何分别对Actor和Critic进行反向传播?也就是说,虽然Loss是相加的,但在反向传播时,是否能够独立更新各自的网络参数?另外,梯度更新的幅度是否基于总Loss,还是各自Loss?
(1) 计算梯度
- 总Loss的反向传播:调用 total_loss.backward(),PyTorch会自动计算总Loss对所有参与计算图的参数的梯度。
- 梯度分离:
- Actor参数的梯度仅来自 actor_loss。
- Critic参数的梯度仅来自 0.5 * critic_loss。
(2) 参数更新
- 优化器(如Adam)根据各参数的梯度独立更新:
- Actor的参数基于 actor_loss 的梯度更新。
- Critic的参数基于 0.5 * critic_loss 的梯度更新。
(3) 更新幅度由各自的Loss和权重决定
- Actor的更新幅度:完全由 actor_loss 的梯度决定,与 critic_loss 无关。
- Critic的更新幅度:由 0.5 * critic_loss 的梯度决定,相当于对Critic的梯度进行了缩放。
为什么可以分开更新?
(1) 参数独立性
- Actor和Critic通常是两个独立的网络,没有共享参数,因此它们的梯度在计算图中是天然分离的。
- PyTorch会自动追踪每个参数的梯度来源,确保梯度仅来自对应的Loss项。
(2) 计算图的隐式分离
即使Loss被合并,计算图中的梯度路径是独立的:
Actor的参数 → 仅通过 actor_loss 贡献梯度。
Critic的参数 → 仅通过 critic_loss 贡献梯度。
6.PPO算法中,什么情况下要对优势函数做标准化?什么情况下不需要?
一般来说,标准化是一个常用的技巧,但并不是绝对必须的。很多PPO的实现默认会进行标准化,因为它通常能带来更稳定的训练效果,尤其是在复杂或高方差的环境中。
需要标准化的场景:
- 高方差优势估计
- 问题特征:当使用多步优势估计(如GAE)或环境奖励稀疏时,优势值可能在不同时间步或不同轨迹间差异极大。
- 示例:
- 在Atari游戏中,某些关键动作(如躲避子弹)可能产生极高的优势值,而普通动作优势值较低。
- 稀疏奖励任务中,仅有少数时间步的优势值显著非零。
- 解决方案:标准化使所有优势值处于同一量级,防止策略被少数高优势样本主导。
- 多智能体或复杂动态环境
- 问题特征:多个智能体交互或环境动态复杂时,不同策略的交互可能导致优势值分布不稳定。
- 示例:在《星际争霸》多单位协同作战中,不同单位的动作优势值可能因战场局势剧烈波动。
- 解决方案:标准化平衡不同策略分支的更新幅度。
- 混合离散-连续动作空间
- 问题特征:当动作空间包含离散和连续动作时,不同动作类型的优势值范围可能差异显著。
- 示例:自动驾驶中,离散动作(换道)和连续动作(转向角度)的优势值量级不同。
- 解决方案:标准化统一不同动作类型的优势尺度。
不需要标准化的场景:
- 奖励函数经过严格归一化
场景特征:若环境的奖励函数在设计时已归一化到固定范围(如 r \in [-1, 1]r∈[−1,1]),优势值自然处于可控范围。
示例:标准化后的MuJoCo控制任务(如Ant-v3),奖励函数已缩放。 - 使用自适应优化器
场景特征:当优化器(如Adam)具备自适应学习率调整能力时,可部分抵消优势值的尺度差异。
示例:在简单连续控制任务(如Pendulum-v1)中,Adam优化器配合PPO默认参数通常无需标准化。 - 策略熵正则化较强
场景特征:若策略熵正则化系数(entropy_coeff)较大,策略的探索性增强,优势值差异的影响被削弱。
示例:在探索优先的任务(如Montezuma’s Revenge)中,高熵策略可替代标准化效果。
7.PPO算法的actor网络使用固定标准差和通过神经网络层动态生成标准差,两种方式各有什么优劣?
5.完整代码
调试过程中的一些代码保留着,以提些许思考
import pygame
import numpy as np
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import mathWIDTH = 1920
HEIGHT = 1080
CAR_SIZE_X = 60
CAR_SIZE_Y = 60
BORDER_COLOR = (255, 255, 255, 255) # Color To Crash on Hit
current_generation = 0 # Generation counterclass Actor(nn.Module):def __init__(self, input_dims, action_dim):super(Actor, self).__init__()self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")self.fc1 = nn.Linear(input_dims[0], 256)self.fc2 = nn.Linear(256, 256)self.mu = nn.Linear(256, action_dim) # 均值输出# 初始化最后一层权重为小值nn.init.orthogonal_(self.mu.weight, gain=0.01)nn.init.constant_(self.mu.bias, 0.0)# 固定标准差# self.log_std = nn.Parameter(torch.zeros(action_dim)) # 对数标准差(可学习参数)# 动态标准差self.log_std = nn.Linear(256, action_dim) # 新增:动态生成log_std# 初始化log_std层权重,限制初始标准差范围nn.init.orthogonal_(self.log_std.weight, gain=0.01)nn.init.constant_(self.log_std.bias, -1.0) # 初始log_std≈-1 → std≈0.36def forward(self, state):# print("state device: ", state.device)x = F.relu(self.fc1(state))x = F.relu(self.fc2(x))mu = torch.tanh(self.mu(x)) # 归一化到[-1,1]# 取exp是为了保证始终为正值,这个log_std是会随着反省传播自动学习更新的# 固定标准差# std = torch.exp(self.log_std) # 标准差# 动态标准差log_std = self.log_std(x) # 动态生成log_std# log_std = torch.clamp(self.log_std(x), min=-5, max=5) # std ∈ [e^-5≈0.0067, e^2≈7.4]std = torch.exp(log_std) # 保证标准差正数return mu, stdclass Critic(nn.Module):def __init__(self, input_dims):super(Critic, self).__init__()self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")self.fc1 = nn.Linear(input_dims[0], 256).to(self.device)self.fc2 = nn.Linear(256, 256).to(self.device)self.v = nn.Linear(256, 1).to(self.device) # 状态价值输出def forward(self, state):x = F.relu(self.fc1(state))x = F.relu(self.fc2(x))return self.v(x)class TrajectoryBuffer:def __init__(self):self.states = []self.actions = []self.log_probs = []self.rewards = []self.next_states = []self.dones = []def clear(self):self.states.clear()self.actions.clear()self.log_probs.clear()self.rewards.clear()self.next_states.clear()self.dones.clear()def store(self, state, action, log_prob, reward, next_state, done):self.states.append(state)self.actions.append(action)self.log_probs.append(log_prob)self.rewards.append(reward)self.next_states.append(next_state)self.dones.append(done)def get_all(self):return (torch.FloatTensor(np.array(self.states)),torch.FloatTensor(np.array(self.actions)),torch.FloatTensor(np.array(self.log_probs)),torch.FloatTensor(np.array(self.rewards)),torch.FloatTensor(np.array(self.next_states)),torch.FloatTensor(np.array(self.dones)))class PPOAgent:def __init__(self, input_dims, action_dim, lr=3e-4, gamma=0.99,clip_epsilon=0.2, gae_lambda=0.95, epochs=4, batch_size=64):self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")self.actor = Actor(input_dims, action_dim)self.critic = Critic(input_dims)self.optimizer = optim.Adam([{'params': self.actor.parameters()},{'params': self.critic.parameters()}], lr=lr)self.gamma = gammaself.clip_epsilon = clip_epsilonself.gae_lambda = gae_lambdaself.epochs = epochsself.batch_size = batch_sizeself.buffer = TrajectoryBuffer()self.action_memory_for_end = []self.control_memory_for_end = []self.mem_cntr = 0self.actor_loss_value = []self.entropy_coef = 0.01def select_action(self, state):state = torch.FloatTensor(state).unsqueeze(0)mu, std = self.actor(state)# 创建一个均值为mu,标准差为std的高斯分布dist = torch.distributions.Normal(mu, std)action = dist.sample()# 为了在策略更新过程中使用,logπ_θ(a∣s)log_prob = dist.log_prob(action).sum(dim=1)return action.squeeze(0).detach().numpy(), log_prob.detach()def compute_gae(self, rewards, values, dones, next_value):values = values.detach() # 分离梯度# 计算GAE优势估计advantages = []gae = 0# next_value = next_value.detach().numpy()next_value = next_value.detach().to(self.device)for t in reversed(range(len(rewards))):delta = rewards[t] + self.gamma * next_value * (1 - dones[t]) - values[t].item()gae = delta + self.gamma * self.gae_lambda * (1 - dones[t]) * gaeadvantages.insert(0, gae)next_value = values[t]advantages = torch.stack(advantages).to(self.device)return advantages.clone().detach().float()def learn(self):# 从缓冲区获取所有轨迹数据states, actions, old_log_probs, rewards, next_states, dones = self.buffer.get_all()# 移至设备(GPU/CPU)states = states.to(self.device)actions = actions.to(self.device)old_log_probs = old_log_probs.to(self.device)rewards = rewards.to(self.device)next_states = next_states.to(self.device)dones = dones.to(self.device)# 计算优势估计(GAE)with torch.no_grad():# print("states: ", states.shape)values = self.critic(states).squeeze()next_value = self.critic(next_states[-1]).detach()advantages = self.compute_gae(rewards, values, dones, next_value)# 标准化优势,准化优势函数更多是工程上的实践,用于提高训练的稳定性,而非算法理论推导的必要步骤# advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8)# 多轮优化for _ in range(self.epochs):# 随机打乱数据索引indices = torch.randperm(len(states))# 分批次训练for start in range(0, len(indices), self.batch_size):end = start + self.batch_sizebatch_idx = indices[start:end]# 提取批次数据batch_states = states[batch_idx]batch_actions = actions[batch_idx]batch_old_log_probs = old_log_probs[batch_idx]batch_advantages = advantages[batch_idx]# 计算新策略的对数概率mu, std = self.actor(batch_states.to('cpu'))dist = torch.distributions.Normal(mu, std)new_log_probs = dist.log_prob(batch_actions.to('cpu')).sum(dim=1)# 计算策略损失(剪切)ratio = torch.exp(new_log_probs.to(self.device) - batch_old_log_probs)surr1 = ratio * batch_advantagessurr2 = torch.clamp(ratio, 1 - self.clip_epsilon, 1 + self.clip_epsilon) * batch_advantagesactor_loss = -torch.min(surr1, surr2).mean()# 计算Critic损失curr_values = self.critic(batch_states).squeeze() # x *targets = batch_advantages.squeeze() + values[batch_idx].detach()critic_loss = F.mse_loss(curr_values, targets)# 总损失entropy = dist.entropy().mean()total_loss = actor_loss + 0.5 * critic_loss - self.entropy_coef * entropy # 调整熵系数# 反向传播与优化self.optimizer.zero_grad()total_loss.backward()torch.nn.utils.clip_grad_norm_(self.actor.parameters(), 0.5)torch.nn.utils.clip_grad_norm_(self.critic.parameters(), 0.5)self.optimizer.step()# 清空缓冲区self.buffer.clear()torch.cuda.empty_cache()class Car:def __init__(self, boundary_x, boundary_y, num_radar):# Load Car Sprite and Rotateself.sprite = pygame.image.load('car.png').convert() # Convert Speeds Up A Lotself.sprite = pygame.transform.scale(self.sprite, (CAR_SIZE_X, CAR_SIZE_Y))self.rotated_sprite = self.sprite# self.position = [690, 740] # Starting Positionself.position = [830, 920] # Starting Positionself.angle = 0self.angle_memory = []self.speed = 0self.speed_memory = []self.speed_set = False # Flag For Default Speed Later onself.center = [self.position[0] + CAR_SIZE_X / 2, self.position[1] + CAR_SIZE_Y / 2] # Calculate Centerself.radars = [[(0, 0), 60]] * num_radar # List For Sensors / Radarsself.drawing_radars = [] # Radars To Be Drawnself.current_lateral_min_dist = 60self.alive = True # Boolean To Check If Car is Crashedself.distance = 0 # Distance Drivenself.time = 0 # Time Passedself.width = 0self.height = 0self.boundary_x = boundary_xself.boundary_y = boundary_ydef draw(self, screen):screen.blit(self.rotated_sprite, self.position) # Draw Spriteself.draw_radar(screen) # OPTIONAL FOR SENSORSdef draw_radar(self, screen):# Optionally Draw All Sensors / Radarsfor radar in self.radars:position = radar[0]pygame.draw.line(screen, (0, 255, 0), self.center, position, 1)pygame.draw.circle(screen, (0, 255, 0), position, 5)def check_collision(self, game_map):self.alive = Truefor point in self.corners:# If Any Corner Touches Border Color -> Crash# Assumes Rectangleif game_map.get_at((int(point[0]), int(point[1]))) == BORDER_COLOR:self.alive = Falsebreakdef check_radar(self, degree, game_map):length = 0x = int(self.center[0] + math.cos(math.radians(360 - (self.angle + degree))) * length)y = int(self.center[1] + math.sin(math.radians(360 - (self.angle + degree))) * length)# While We Don't Hit BORDER_COLOR AND length < 300 (just a max) -> go further and furtherwhile not game_map.get_at((x, y)) == BORDER_COLOR and length < 300:length = length + 1x = int(self.center[0] + math.cos(math.radians(360 - (self.angle + degree))) * length)y = int(self.center[1] + math.sin(math.radians(360 - (self.angle + degree))) * length)# Calculate Distance To Border And Append To Radars List TODO: update dist calculatedist = int(math.sqrt(math.pow(x - self.center[0], 2) + math.pow(y - self.center[1], 2)))self.radars.append([(x, y), dist])def update(self, game_map):# Set The Speed To 20 For The First Time# Only When Having 4 Output Nodes With Speed Up and Downif not self.speed_set:self.speed = 10self.speed_set = Trueself.width, self.height = game_map.get_size()# Get Rotated Sprite And Move Into The Right X-Direction# Don't Let The Car Go Closer Than 20px To The Edgeself.rotated_sprite = self.rotate_center(self.sprite, self.angle)self.position[0] += math.cos(math.radians(360 - self.angle)) * self.speedself.position[0] = max(self.position[0], 20)self.position[0] = min(self.position[0], WIDTH - 120)# Increase Distance and Timeself.distance += self.speedself.time += 1# Same For Y-Positionself.position[1] += math.sin(math.radians(360 - self.angle)) * self.speedself.position[1] = max(self.position[1], 20)self.position[1] = min(self.position[1], WIDTH - 120)# Calculate New Centerself.center = [int(self.position[0]) + CAR_SIZE_X / 2, int(self.position[1]) + CAR_SIZE_Y / 2]# print("center: {}".format(self.center))# Calculate Four Corners# Length Is Half The Sidelength = 0.5 * CAR_SIZE_Xleft_top = [self.center[0] + math.cos(math.radians(360 - (self.angle + 30))) * length,self.center[1] + math.sin(math.radians(360 - (self.angle + 30))) * length]right_top = [self.center[0] + math.cos(math.radians(360 - (self.angle + 150))) * length,self.center[1] + math.sin(math.radians(360 - (self.angle + 150))) * length]left_bottom = [self.center[0] + math.cos(math.radians(360 - (self.angle + 210))) * length,self.center[1] + math.sin(math.radians(360 - (self.angle + 210))) * length]right_bottom = [self.center[0] + math.cos(math.radians(360 - (self.angle + 330))) * length,self.center[1] + math.sin(math.radians(360 - (self.angle + 330))) * length]self.corners = [left_top, right_top, left_bottom, right_bottom]# Check Collisions And Clear Radarsself.check_collision(game_map)self.radars.clear()# From -90 To 120 With Step-Size 45 Check Radarfor d in range(-120, 126, 15): # -90,-45,0,45,90zself.check_radar(d, game_map)def get_data(self):# Get Distances To Borderreturn_values = [0] * len(self.radars)self.current_lateral_min_dist = 60for i, radar in enumerate(self.radars):return_values[i] = radar[1] / 300.0if radar[1] < self.current_lateral_min_dist:self.current_lateral_min_dist = radar[1]angle_rad = np.deg2rad(self.angle)return_values = return_values + [self.current_lateral_min_dist / 30,np.clip(self.speed / 20.0, 0.0, 1.0),np.sin(angle_rad), np.cos(angle_rad)]return return_valuesdef is_alive(self):# Basic Alive Functionreturn self.alivedef get_reward_optimized333(self, action, done):# 居中性奖励lateral_reward = max((self.current_lateral_min_dist / 60 - 0.4) * 2, 0.0)# action输出转角奖励steer_reward = 0.0if abs(action[0].item()) >= 2.5:steer_reward = -0.2 * abs(action[0].item()) + 0.5# 速度奖励speed_reward = 0.0if self.speed < 12.0:speed_reward = 0.05 * self.speed - 0.6elif self.speed >= 16.0:speed_reward = -0.15 * self.speed + 2.4# 速度基础speed_base_reward = self.speed / 15.0# 转角连续性angle_discount = 1.0if len(self.angle_memory) >= 5:self.angle_memory = self.angle_memory[1:]self.angle_memory.append(action[0].item())aaa = [0] * 4if len(self.angle_memory) >= 5:for i in range(1, 5):aaa[i - 1] = self.angle_memory[i] - self.angle_memory[i - 1]bbb = [0] * 3for j in range(1, 4):bbb[j - 1] = 1 if aaa[j - 1] * aaa[j] < 0 else 0if sum(bbb) >= 3 and lateral_reward > 0.0:angle_discount = 0.8total_reward = lateral_reward * angle_discount * speed_base_reward + speed_reward + steer_reward# print("total_reward: ", total_reward)total_reward = max(-1.0, min(total_reward, 1.0))# return total_rewardreturn total_reward if ~done else -1.0def rotate_center(self, image, angle):# Rotate The Rectanglerectangle = image.get_rect()rotated_image = pygame.transform.rotate(image, angle)rotated_rectangle = rectangle.copy()rotated_rectangle.center = rotated_image.get_rect().centerrotated_image = rotated_image.subsurface(rotated_rectangle).copy()return rotated_imagedef train():pygame.init()screen = pygame.display.set_mode((WIDTH, HEIGHT))game_map = pygame.image.load('map.png').convert() # Convert Speeds Up A Lotclock = pygame.time.Clock()num_radar = 17agent = PPOAgent(input_dims=[num_radar + 4], action_dim=2, lr=0.0005, gamma=0.99,clip_epsilon=0.2, gae_lambda=0.95, epochs=4, batch_size=128)scores = []average_scores = []distance = []average_distance = []alive_counts = []average_alive_counts = []actor_loss_values = []average_actor_loss = []n_games = 2000for i in range(n_games):car = Car([], [], num_radar)done = Falsescore = 0observation = car.get_data()alive_count = 0buffer = TrajectoryBuffer()while not done:action, log_prob = agent.select_action(observation)if len(agent.action_memory_for_end) >= 4:agent.action_memory_for_end = agent.action_memory_for_end[1:]agent.action_memory_for_end.append([round(action[0].item(), 2), round(action[1].item(), 2)])car.angle += action[0].item()car.angle = car.angle % 360car.speed = min(max(car.speed + action[1].item(), 0.0), 20.0)if len(agent.control_memory_for_end) >= 4:agent.control_memory_for_end = agent.control_memory_for_end[1:]agent.control_memory_for_end.append([round(car.angle, 2), round(car.speed, 2)])screen.blit(game_map, (0, 0))car.update(game_map)car.draw(screen)pygame.display.flip()clock.tick(60)done = not car.is_alive()observation_, reward = car.get_data(), car.get_reward_optimized333(action, done)buffer.states.append(observation)buffer.actions.append(action)# buffer.log_probs.append([log_prob[0][0].item(), log_prob[0][1].item()])buffer.log_probs.append(log_prob.item())buffer.rewards.append(reward)buffer.next_states.append(observation_)buffer.dones.append(float(done))observation = observation_score += rewardalive_count += 1# print(alive_count)agent.mem_cntr += 1# agent.buffer = bufferagent.buffer.states = buffer.states.copy()agent.buffer.actions = buffer.actions.copy()agent.buffer.log_probs = buffer.log_probs.copy()agent.buffer.rewards = buffer.rewards.copy()agent.buffer.next_states = buffer.next_states.copy()agent.buffer.dones = buffer.dones.copy()# 清空临时Buffer并释放显存buffer.clear()del bufferagent.learn()# 记录平均scorescores.append(score)avg_score = np.mean(scores[-100:])average_scores.append(avg_score)# 记录平均distancedistance.append(car.distance)avg_distance = np.mean(distance[-100:])average_distance.append(avg_distance)# 记录平均alive_countsalive_counts.append(alive_count)avg_alive_count = np.mean(alive_counts[-100:])average_alive_counts.append(avg_alive_count)# 记录平均actor_loss# actor_loss_values.append(agent.actor_loss_value)# avg_actor_loss = np.mean(actor_loss_values[-100:])# average_actor_loss.append(avg_actor_loss)avg_actor_loss = 0# 打印当前学习率(调试用)# current_actor_lr = agent.actor_lr_scheduler.get_last_lr()[0]# current_critic_lr = agent.critic_lr_scheduler.get_last_lr()[0]current_actor_lr = 0current_critic_lr = 0print(f'episode: {i}, score= {round(score, 2)}, actor_lr= {current_actor_lr},'f' critic_lr= {current_critic_lr}, dist= {round(car.distance, 2)}'f' avg_dist= {round(avg_distance, 2)}, avg_score= {round(avg_score, 2)},'f' avg_actor_loss= {round(avg_actor_loss, 2)}, alive_count= {alive_count},'f' mem_cntr= {agent.mem_cntr}')print("------action_memory_for_end: ", agent.action_memory_for_end)print("------control_memory_for_end: ", agent.control_memory_for_end)plt.subplot(1, 3, 1)plt.plot([i for i in range(0, n_games)], average_scores)plt.title("average_scores")plt.subplot(1, 3, 2)plt.plot([i for i in range(0, n_games)], average_distance)plt.title("average_distance")plt.subplot(1, 3, 3)plt.plot([i for i in range(0, n_games)], average_alive_counts)plt.title("average_alive_counts")plt.show()if __name__ == '__main__':train()
相关文章:
基于PPO的自动驾驶小车绕圈任务
1.任务介绍 任务来源: DQN: Deep Q Learning |自动驾驶入门(?) |算法与实现 任务原始代码: self-driving car 在上一篇使用了DDPG算法完成自动驾驶小车绕圈任务之后,继续学习了PPO算法&…...
Three.js + React 实战系列 - 客户评价区细解教程 Clients 组件✨(回答式评价 + 评分星级)
对个人主页设计和实现感兴趣的朋友可以订阅我的专栏哦!!谢谢大家!!! 在这篇博客中,我们将实现一个简洁的 Hear from My Clients 客户评价区域。这个区块在个人主页中可以突显用户体验和专业度,帮…...
2048游戏(含Python源码)
前言 相关参考游戏: 像素飞机大战(含Python源码)-CSDN博客https://blog.csdn.net/weixin_64066303/article/details/147693018?spm1001.2014.3001.5501使用DeepSeek定制Python小游戏——以“俄罗斯方块”为例-CSDN博客https://blog.csdn.n…...
百度golang开发一面
讲一下数据库的事务机制?acid特性是靠什么实现的? 持久性 redo log 原子性 undo log 隔离性 MVCC或next-lock锁 四个隔离级别是什么,分别解决什么问题? 可串行化实现原理 mysql锁机制?介绍锁的类型,以及原理…...
【Springboot知识】Springboot计划任务Schedule详解
文章目录 Spring Boot 定时任务从原理到实现详解一、核心原理分析1. 架构分层2. 核心组件3. 线程模型 二、基础实现步骤1. 添加依赖2. 主类配置3. 定时任务类 三、高级配置技巧1. 自定义线程池2. 动态配置参数3. 分布式锁集成(Redis示例) 四、异常处理机…...
大模型推理--从零搭建大模型推理服务器:硬件选购、Ubuntu双系统安装与环境配置
自从大模型火了之后就一直想自己组装一台机器去深入研究一下大模型,奈何囊中羞涩,迟迟也没有行动。在下了很大的勇气之后,终于花了接近4万块钱组装了一台台式机,下面给大家详细介绍一下我的装机过程。 1.硬件配置 研究了一周&am…...
如何使用QWidgets设计一个类似于Web Toast的控件?
如何使用QWidgets设计一个类似于Web Toast的控件? 前言 笔者这段时间沉迷于给我的下位机I.MX6ULL做桌面,这里抽空更新一下QT的东西。这篇文章是跟随CCMoveWidget一样的文章,尝试分享自己如何书写这份代码的思考的过程,和笔者…...
博图V20编译报错:备不受支持,无法编译。请更改为受支持的设备。
使用高版本博图打开低版本博图的工程文件时,hmi编译报错不通过,报错提示:备不受支持,无法编译。请更改为受支持的设备。 原因:当前版本的博图软件没有或不支持该组态设备的固件版本。 解决办法:1、安装报错…...
凸性(Convexity)
凸性(Convexity)是一个跨学科的重要概念,广泛应用于数学、优化理论、金融等领域。其核心含义是描述某种结构(如函数、集合)在特定条件下的“无凹陷”性质。 1. 数学中的凸性 1.1 凸函数与凹函数 在数学分析中&#…...
Vuex使用指南:状态管理
一、什么是状态管理?为什么需要 Vuex? 1. 状态管理的基本概念 在 Vue 应用中,状态指的是应用中的数据。例如: 用户登录状态购物车中的商品文章列表的分页信息 状态管理就是对这些数据的创建、读取、更新和删除进行有效管理。 …...
kotlin中枚举带参数和不带参数的区别
一 ✅ 代码对比总结 第一段(带参数 工具方法) enum class SeatPosition(val position: Int) {DRIVER_LEFT(0),DRIVER_RIGHT(1),SECOND_LEFT(2),SECOND_RIGHT(3);companion object {fun fromPosition(position: Int): SeatPosition? {return SeatPosi…...
【Python】Python好玩的第三方库之二维码生成,操作xlsx文件,以及音频控制器
前言 🌟🌟本期讲解关于python的三种第三方库的使用介绍~~~ 🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客 🔥 你的点赞就是小编不断更新的最大动力 🎆那么…...
VTK 交互类介绍
基本概念 交互器(Interactor): 处理用户输入事件的基础类 交互样式(InteractorStyle): 定义具体的交互行为 Widgets: 可交互的UI组件,如滑块、按钮等 Picker: 用于选择场景中的对象 常用交互类 类名功能描述vtkRenderWindowInteractor渲染窗口交互器vtkInteractorStyle交互样式…...
在Window10 和 Ubuntu 24.04LTS 上 Ollama 在线或离线安装部署
Ollama 是一个开源的大型语言模型(LLM)服务框架,旨在通过轻量化、跨平台的设计,简化大模型在本地环境中的部署与应用。其基于 Go 语言开发,通过 Docker 容器化技术封装模型运行环境,提供类似命令行工具的交…...
语音合成之十一 提升TTS语音合成效果:低质量数据清洗、增强与数据扩增
低质量数据清洗、增强与数据扩增 1. 引言:TTS的基石——数据质量2. 基础:TTS数据准备工作流2.1 规划:定义蓝图2.2 执行:从原始数据到训练就绪格式2.3 最佳实践与可复现性 3. 攻克缺陷:低质量语音数据的清洗与增强3.2 手…...
RGB三原色
本文来源 : 腾讯元宝 RGB三原色(红绿蓝)详解 RGB(Red, Green, Blue)是光学的三原色,通过不同比例的混合可以产生人眼可见的绝大多数颜色。它是现代显示技术(如屏幕、投影仪)…...
BUUCTF 大流量分析(一) 1
BUUCTF:https://buuoj.cn/challenges 文章目录 题目描述:密文:解题思路:flag: 相关阅读 CTF Wiki BUUCTF:大流量分析(一) 题目描述: 某黑客对A公司发动了攻击,以下是一段时间内我们…...
虚幻引擎5-Unreal Engine笔记之显卡环境设置使开发流畅
虚幻引擎5-Unreal Engine笔记之显卡环境设置使开发流畅 code review! 文章目录 虚幻引擎5-Unreal Engine笔记之显卡环境设置使开发流畅1.电源管理2.显卡优先设置3.拯救者支持FnQ性能模式切换,建议开发前切至“野兽模式”或高性能模式。4.NVIDIA 驱动设置5.VS2022中…...
suna工具调用可视化界面实现原理分析(一)
这是一个基于React构建的工具调用侧边面板组件,主要用于展示和管理自动化工具调用流程。以下是代码功能解析及关键组件分析: 一、核心功能模块 多工具视图切换系统 • 动态视图加载:通过getToolView函数根据工具名称(如execute-c…...
【将你的IDAPython插件迁移到IDA 9.x:核心API变更与升级指南】
文章目录 将你的 IDAPython 插件迁移到 IDA 9.x:核心 API 变更与升级指南为什么 API 会变化?关键不兼容性一:数据库信息访问 (inf_structure)关键不兼容性二:窗口/视图类型判断 (BWN_* 和 form_type)其他可能的 API 变更迁移策略建…...
《Python星球日记》第31天:Django 框架入门
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏:《Python星球日记》,限时特价订阅中ing 目录 一、Django…...
读《人生道路的选择》有感
读完戴维坎贝尔的《人生道路的选择》,深有感触,虽然只有短短的108也,但作者强调了在复杂的生活环境之中“选择”的重要性。这也是我想要探讨的话题,选择到底会对我们人生产生怎样的影响。 在我们人生当中,确实有许多的…...
opencv+opencv_contrib+cuda和VS2022编译
本文介绍使用OpenCV和OpenCV_Contrib源码及Cuda进行编译的过程,编译过程中会用到OpenCV、OpenCV_Contrib、Toolkit、Cmake、VS2022等工具,最终编译OpenCV的Cuda版本。 一、OpenCV下载地址 OpenCV官网下载地址:https://opencv.org/releases/#࿰…...
STC单片机与淘晶驰串口屏通讯例程之01【新建HDMI工程】
大家好,我是『芯知识学堂』的SingleYork,今天笔者给大家一起学习这款“SYK-0806-A2S1”控制板与淘晶驰串口屏通讯的例程,本例使用的是淘晶驰的4.3寸电阻触摸屏TJC4827T143_011R_I_P20,分辨率为480272,详细参数大家可以查看这个屏的手册。 先来看下本例程整体的效果: 那么…...
PE文件结构(导出表)
导出表 什么是导出表? 导出表是PE文件中记录动态链接库(DLL)对外提供的函数或数据的列表,包含函数名称、序号和内存地址等信息,供其他程序调用 我们写一个dll来查看一下导出函数 int exportFunc1(int a, int b) {ret…...
网络安全自动化:精准把握自动化边界,筑牢企业安全防
在当今数字化时代,网络攻击的威胁日益严峻,企业网络安全的重要性不言而喻。随着海量资产与复杂架构的出现,网络安全自动化成为了众多企业关注的焦点。网络安全维护看似简单的修补系统、删除旧账户、更新软件,在大型企业中却极易变…...
实战设计模式之中介者模式
概述 中介者模式是一种强大且灵活的设计模式,适用于需要优化对象间通信的场景。中介者模式通过引入一个中介对象,来封装一系列对象之间的交互。在没有中介者的情况下,这些对象之间可能会直接相互引用,导致系统中的类紧密耦合&…...
价格识别策略思路
该策略是一种基于价格形态和市场条件的交易算法,旨在通过识别特定的价格模式来生成买入和卖出信号。 价格形态识别 策略的核心在于识别价格的高点和低点形态。通过比较当前周期及其前几个周期的最高价和最低价, 策略定义了一系列条件来判断价格是否形成了…...
Kotlin带接收者的Lambda介绍和应用(封装DialogFragment)
先来看一个具体应用:假设我们有一个App,App中有一个退出应用的按钮,点击该按钮后并不是立即退出,而是先弹出一个对话框,询问用户是否确定要退出,用户点了确定再退出,点取消则不退出,…...
【NLP】32. Transformers (HuggingFace Pipelines 实战)
🤖 Transformers (HuggingFace Pipelines 实战) 本教程基于 Hugging Face 的 transformers 库,展示如何使用预训练模型完成以下任务: 情感分析(Sentiment Analysis)文本生成(Text …...
[ 设计模式 ] | 单例模式
单例模式是什么?哪两种模式? 单例模式就是一个类型的对象,只有一个,比如说搜索引擎中的索引部分,360安全卫士的桌面悬浮球。 饿汉模式和懒汉模式:饿汉模式是线程安全的,懒汉模式不是线程安全的…...
用网页显示工控仪表
一.起因 现在工控也越来越多的使用web页面来显示电压,电流,温度,转速等物理量.本例使用js控制网页显示速度仪表. 二.代码 <html> <head><script type"text/javascript">var ctx;var px0;var movePoint{x0:0,x1:0};function init(){drawFace();m…...
Spring项目改造Solon版,使用体验,对比
概述 对于Solon有些人可能并不了解,在官方概述中,称其是新一代Java企业级应用开发框架,从零开始构建,有自主的标准规范与开放生态。近16万行代码。 并有更快、更小、更简单的特点 什么样的Java项目用Solon好? 按正常…...
2.CFD 计算过程概述:Fluent在散热计算中的优势
1.主流散热软件 2.电子产品热设计的基本要求 3.失效率与温度之间的关系 4.电子产品热设计的基本要求 5.电子产品必须要做散热设计 6.主动散热与被动散热 7.高效山热方案 8.热交换模型 9.Fluent中传热模型...
【Java ee初阶】多线程(6)
一、阻塞队列 队列的原则:“先进先出”,队列分为普通队列,优先级队列等等。在数据结构中,堆是特殊的完全二叉树,一定不要把堆和二叉搜索树混淆。 阻塞队列是一种特殊的队列,也遵循“先进先出”的原则。 …...
Unity:Surface Effector 2D(表面效应器 2D)
目录 什么是表面效应器 2D? 🎯 它是做什么的? 🧪 从第一性原理解释它是怎么工作的 📦 重要参数解释 为什么不直接用 Rigidbody(刚体)来控制运动 ? 所以什么时候该用哪个&#…...
Spring 框架的底层原理
Spring 框架的底层原理主要包括以下几个方面: 核心容器(IoC 容器) IoC(控制反转)原理 : 依赖注入(DI) :这是 IoC 的实现方式之一。在传统的程序开发中,程序组…...
【Unity】AssetBundle热更新
1.新建两个预制体: Cube1:GameObject Material1:Material Cube1使用了Material1材质 之后设置打包配置 Cube1的打包配置为custom.ab Material1的打包配置为mat.ab 2.在Asset文件夹下创建Editor文件夹,并在Editor下创建BuildBundle…...
【算法笔记】动态规划基础(二):背包dp
目录 01背包例题状态表示状态计算初始化AC代码 完全背包例题状态表示状态计算初始化TLE代码 多重背包例题状态表示状态计算初始化AC代码 分组背包例题状态表示状态计算初始化AC代码 二维费用背包例题状态表示状态计算初始化AC代码 混合背包问题例题状态表示状态计算初始化TLE代…...
IP属地是我的定位吗?——解析两者区别
在互联网时代,我们经常看到社交媒体、论坛或APP上显示用户的“IP属地”,许多人会疑惑:IP属地是不是我的精确定位?它会不会暴露我的隐私? 本文将详细解析IP属地和定位的区别,并解答常见的相关问题&#…...
力扣每日一题1128等价多米诺骨牌对的数量
1128. 等价多米诺骨牌对的数量 题目: 给你一组多米诺骨牌 dominoes 。 形式上,dominoes[i] [a, b] 与 dominoes[j] [c, d] 等价 当且仅当 (a c 且 b d) 或者 (a d 且 b c) 。即一张骨牌可以通过旋转 0 度或 180 度得到另一张多米诺骨牌。 在 0 &l…...
SpringBoot集成CXF框架,实现WebService
SpringBoot官网地址:https://spring.io/projects/spring-ws 1、WebService服务端搭建 Maven依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.17&…...
android-ndk开发(2): macOS 安装 ndk
android-ndk开发(2): macOS 安装 ndk 2025/05/05 1. 概要 对于 android-ndk 在 r23 之前的版本,官方提供了 .zip 文件, 解压即安装。 对于 android-ndk 在 r23 以及之后的版本, 官方只提供了 .dmg 文件, 不能简单的解压完成安…...
科创大赛——知识点复习【c++】——第一篇
目录 输入 一、cin 二、scanf 三、gets 四、getchar 五、fgets 输出 一、cout 二、printf 基本数据类型 一,数据类型有哪些? 二,整型(Integer Types) 1,修饰符 2,整型数据的数据范…...
硬件工程师面试常见问题(14)
第六十六问:运放--输入偏置电流和输入失调电流 输入偏置电流lb:是由于运放两个输入极都有漏电流的存在。实际的运放,会有电流流入运放的输入端的。那么输入偏置电流就定义这两个电流的平均值。 输入失调电流 Ios:定义为两个差分输入端偏置电…...
Flink流水线任务在线演示
Flink流水线在线演示 1. 登录系统 访问系统登录页面,输入账号密码完成身份验证。 2. 创建任务 入口:通过顶部菜单栏选择 任务开发,或通过快捷入口 快速创建任务。 任务类型:选择 FlinkPipeline。 3. 配置任务 进入配置界面…...
C++笔记之接口`Interface`
C++笔记之接口Interface code review! 一个简洁简短的 C++ 接口实现示例: #include <iostream>// 1. 定义接口(抽象类) class Shape {public:...
css使用aspect-ratio制作4:3和9:16和1:1等等比例布局
文章目录 1. 前言2. 用法2.1 基本语法2.2. 与max-width、max-height等属性结合使用2.3. 动态计算比例 3. 应用场景4. 兼容性和替代方案5. 总结 1. 前言 在网页制作过程中,有时候我们只知道宽度,或者只知道高度,这时候需要制作一个4:3和9:16这…...
深入探索 Apache Spark:从初识到集群运行原理
深入探索 Apache Spark:从初识到集群运行原理 在当今大数据时代,数据如同奔涌的河流,蕴藏着巨大的价值。如何高效地处理和分析这些海量数据,成为各行各业关注的焦点。Apache Spark 正是为此而生的强大引擎,它以其卓越…...
0903Redux改造项目_用户信息_状态管理-react-仿低代码平台项目
文章目录 1 Redux管理用户信息1.1 定义store和reducer1.2 使用useSeletor 2 自定义Hook统一加载用户信息存储Redux3 根据用户登录状态动态跳转页面结语 1 Redux管理用户信息 1.1 定义store和reducer src/store/userReducer.ts代码如下所示: import { createSlice…...