【动手学强化学习】part6-策略梯度算法

阐述、总结【动手学强化学习】章节内容的学习情况,复现并理解代码。

文章目录

  • 一、算法背景
    • 1.1 算法目标
    • 1.2 存在问题
    • 1.3 解决方法
  • 二、REINFORCE算法
    • 2.1 必要说明
      • softmax()函数
      • 交叉熵
      • 策略更新思想
    • 2.2 伪代码
      • 算法流程简述
    • 2.3 算法代码
    • 2.4 运行结果
    • 2.5 算法流程说明
      • 初始化参数
      • 初始化“车杆”环境
      • 初始化policy_net网络模型
      • episode采样
      • 价值更新
      • 策略更新
  • 三、疑问
    • 3.1 为什么要采用softmax作为policy_net的输出?
    • 3.2 为什么REINFORCE算法比DQN算法波动更大?
    • 3.3 策略更新的步骤具体在算法哪一块?
  • 四、总结


一、算法背景

1.1 算法目标

给定“黑盒”模型,求解最优policy

1.2 存在问题

动态规划、时序差分-TD、值函数近似算法中策略的表示都是基于价值(value-based)的,即算法目标是学习值函数,然后根据值函数导出一个策略,学习过程中并不存在一个显式的策略。

1.3 解决方法

采用基于策略(policy-based)算法,直接显式地学习一个目标策略。
可以理解为,之前policy的表示是“表格式(tabular)”的,即存在一个“ π − t a b l e : π ( a ∣ s ) {\pi}-table:{\pi}(a|s) πtable:π(as) ”,现在通过“函数拟合”去学习一个关于policy的函数,即 π θ ( a ∣ s ) {\pi}_θ(a|s) πθ(as),参数为θ

二、REINFORCE算法

  • 🌟算法类型
    环境依赖:❌model-based ✅model-free
    价值估计:✅non-incremental ❌incremental(采用蒙特卡洛方法估计)
    价值表征:✅tabular representation ❌function representation(采用蒙特卡洛方法估计)
    学习方式:✅on-policy ❌off-policy
    策略表征:❌value-based ✅policy-based(通过函数拟合去计算 π θ ( a ∣ s ) {\pi}_θ(a|s) πθ(as) 概率)

2.1 必要说明

softmax()函数

softmax函数是一种常用的激活函数,主要用于将一个向量转换为概率分布,使得向量中的每个元素都被映射到 [0, 1] 区间内,并且所有元素的和为 1。
对于一个向量 z = [ z 1 , z 2 , … , z n ] \mathbf{z}=[z_1,z_2,\ldots,z_n] z=[z1,z2,,zn] ,softmax函数的定义为:
s o f t m a x ( z ) i = e z i ∑ j = 1 n e z j \mathrm{softmax}(\mathbf{z})_i=\frac{e^{z_i}}{\sum_{j=1}^ne^{z_j}} softmax(z)i=j=1nezjezi

  • 特点
    ①归一化:softmax 函数将向量的每个元素转换为一个介于 0 和 1 之间的值,并且所有元素的和为 1。这使得输出可以被解释为概率分布。
    ** 在这里就体现为在state状态下与环境交互时,action的使用概率,即 π θ ( a ∣ s ) {\pi}_θ(a|s) πθ(as)
    ②强调最大值:softmax 函数会放大较大的值并抑制较小的值。这意味着在多分类问题中,模型更倾向于选择具有最高输出值的类别。
    ** 在这里就体现为最优policy中action的选择。

    ③平滑性:softmax 函数是平滑的,这意味着它在所有点上都是可导的,这在训练神经网络时非常有用,因为可以通过梯度下降等优化算法来更新模型参数。

交叉熵

当采用softmax函数作为神经网络的输出时,一般采用交叉熵来衡量两个概率的区别,即真值与神经网络输出概率的区别,因此交叉熵可以作为神经网络的损失函数。参考【动手学深度学习】part9-softmax回归

  • 交叉熵定义为:
    假设模型的输出为 z,真实标签为 y,则整个过程可以表示为:
    p = s o f t m a x ( z ) L = H ( y , p ) = − ∑ i = 1 n y i log ⁡ ( p i ) \begin{aligned}\mathbf{p}&=\mathrm{softmax}(\mathbf{z})\\\\L=H(\mathbf{y},\mathbf{p})&=-\sum_{i=1}^ny_i\log(p_i)\end{aligned} pL=H(y,p)=softmax(z)=i=1nyilog(pi)

策略更新思想

将policy_net的目标函数定义为最大化state value,即:
J ( θ ) = E [ ∑ t = 0 ∞ γ t R t + 1 ] = E s 0 [ V π θ ( s 0 ) ] J(\theta)=\mathbb{E}\left[\sum_{t=0}^\infty\gamma^tR_{t+1}\right]=\mathbb{E}_{s_0}[V^{\pi_\theta}(s_0)] J(θ)=E[t=0γtRt+1]=Es0[Vπθ(s0)]
V π θ ( s 0 ) = ∑ a ∇ θ π ( a ∣ s , θ ) q π ( s , a ) V^{\pi_\theta}(s_0)=\sum_a\nabla_\theta\pi(a|s,\theta)q_\pi(s,a) Vπθ(s0)=aθπ(as,θ)qπ(s,a)
为了求解最优policy使得state value最优,借鉴“梯度下降”(参考:【算法学习】优化算法-小批量随机梯度下降mini-batch SGD)的思想,求解 J ( θ ) J(\theta) J(θ)的梯度,即:
∇ θ J = ∑ s d ( s ) ∑ a ∇ θ π ( a ∣ s , θ ) q π ( s , a ) = ∑ s d ( s ) ∑ a π ( a ∣ s , θ ) ∇ θ ln ⁡ π ( a ∣ s , θ ) q π ( s , a ) = E S ∼ d [ ∑ a π ( a ∣ S , θ ) ∇ θ ln ⁡ π ( a ∣ S , θ ) q π ( S , a ) ] = E S ∼ d , A ∼ π [ ∇ θ ln ⁡ π ( A ∣ S , θ ) q π ( S , A ) ] ≐ E [ ∇ θ ln ⁡ π ( A ∣ S , θ ) q π ( S , A ) ] \begin{aligned} \nabla_{\theta}J& =\sum_sd(s)\sum_a\nabla_\theta\pi(a|s,\theta)q_\pi(s,a) \\ &=\sum_{s}d(s)\sum_{a}\pi(a|s,\theta)\nabla_{\theta}\ln\pi(a|s,\theta)q_{\pi}(s,a) \\ &=\mathbb{E}_{S\sim d}\left[\sum_{a}\pi(a|S,\theta)\nabla_{\theta}\ln\pi(a|S,\theta)q_{\pi}(S,a)\right] \\ &=\mathbb{E}_{S\sim d,A\sim\pi}\big[\nabla_\theta\ln\pi(A|S,\theta)q_\pi(S,A)\big] \\ &\doteq\mathbb{E}\big[\nabla_\theta\ln\pi(A|S,\theta)q_\pi(S,A)\big] \end{aligned} θJ=sd(s)aθπ(as,θ)qπ(s,a)=sd(s)aπ(as,θ)θlnπ(as,θ)qπ(s,a)=ESd[aπ(aS,θ)θlnπ(aS,θ)qπ(S,a)]=ESd,Aπ[θlnπ(AS,θ)qπ(S,A)]E[θlnπ(AS,θ)qπ(S,A)]
因此,policy_net的参数更新可以设置为:
θ t + 1 = θ t + α ∇ θ J ( θ ) = θ t + α E [ ∇ θ ln ⁡ π ( A ∣ S , θ t ) q π ( S , A ) ] \begin{aligned} \theta_{t+1}& =\theta_t+\alpha\nabla_\theta J(\theta) \\ &=\theta_t+\alpha\mathbb{E}\Big[\nabla_\theta\ln\pi(A|S,\theta_t)q_\pi(S,A)\Big] \end{aligned} θt+1=θt+αθJ(θ)=θt+αE[θlnπ(AS,θt)qπ(S,A)]
采用“随机近似(stochastic approximation)”去估计梯度,即:
θ t + 1 = θ t + α ∇ θ ln ⁡ π ( a t ∣ s t , θ t ) q π ( s t , a t ) \theta_{t+1}=\theta_t+\alpha\nabla_\theta\ln\pi(a_t|s_t,\theta_t)q_\pi(s_t,a_t) θt+1=θt+αθlnπ(atst,θt)qπ(st,at)

2.2 伪代码

在这里插入图片描述

算法流程简述

①初始化policy_net:构建policy_net网络模型。
②采样episode:设定周期数num_episodes,循环迭代采样episode,根据 π θ ( a ∣ s ) {\pi}_θ(a|s) πθ(as) 采样(s,a,r,s’,done)直至terminal state,即done为true,得到一条episode,即(s,a,r,s’)链。
③价值更新:依据蒙特卡洛方法,基于episode采用first-visit更新对应q(s,a)值。
④策略更新:设定损失函数为交叉熵,基于“梯度下降”方法更新policy_net网络参数。
⑤终止判断:根据已产生episode个数是否达到num_episodes,判断算法是否终止,并输出policy。

2.3 算法代码

import gym
import torch
import torch.nn.functional as F
import numpy as np
import matplotlib.pyplot as plt
from tqdm import tqdm
import rl_utilsclass 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)self.optimizer = torch.optim.Adam(self.policy_net.parameters(),lr=learning_rate)  # 使用Adam优化器self.gamma = gamma  # 折扣因子self.device = devicedef take_action(self, state):  # 🌟根据动作概率分布随机采样,不再是根据Q值去选取action了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 = 0self.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 + rewardloss = -log_prob * G  # 每一步的损失函数loss.backward()  # 反向传播计算梯度self.optimizer.step()  # 梯度下降,更新policy_net参数learning_rate = 1e-3
num_episodes = 1000
hidden_dim = 128
gamma = 0.98
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")env_name = "CartPole-v0"
env = gym.make(env_name)
env.seed(0)
torch.manual_seed(0)
state_dim = env.observation_space.shape[0]
action_dim = env.action_space.n
agent = REINFORCE(state_dim, hidden_dim, action_dim, learning_rate, gamma,device)return_list = []
for i in range(10):with tqdm(total=int(num_episodes / 10), desc='Iteration %d' % i) as pbar:for i_episode in range(int(num_episodes / 10)):episode_return = 0transition_dict = {'states': [],'actions': [],'next_states': [],'rewards': [],'dones': []}state = env.reset()done = Falsewhile not done:action = agent.take_action(state)next_state, reward, done, _ = env.step(action)transition_dict['states'].append(state)transition_dict['actions'].append(action)transition_dict['next_states'].append(next_state)transition_dict['rewards'].append(reward)transition_dict['dones'].append(done)state = next_stateepisode_return += rewardreturn_list.append(episode_return)agent.update(transition_dict)if (i_episode + 1) % 10 == 0:pbar.set_postfix({'episode':'%d' % (num_episodes / 10 * i + i_episode + 1),'return':'%.3f' % np.mean(return_list[-10:])})pbar.update(1)episodes_list = list(range(len(return_list)))
plt.plot(episodes_list, return_list)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('REINFORCE on {}'.format(env_name))
plt.show()mv_return = rl_utils.moving_average(return_list, 9)
plt.plot(episodes_list, mv_return)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('REINFORCE on {}'.format(env_name))
plt.show()

2.4 运行结果

Iteration 0: 100%|██████████| 100/100 [00:09<00:00, 10.01it/s, episode=100, return=28.200]
Iteration 1: 100%|██████████| 100/100 [00:09<00:00, 10.21it/s, episode=200, return=78.300]
Iteration 2: 100%|██████████| 100/100 [00:14<00:00,  6.77it/s, episode=300, return=146.000]
Iteration 3: 100%|██████████| 100/100 [00:18<00:00,  5.30it/s, episode=400, return=160.100]
Iteration 4: 100%|██████████| 100/100 [00:24<00:00,  4.02it/s, episode=500, return=199.400]
Iteration 5: 100%|██████████| 100/100 [00:25<00:00,  3.91it/s, episode=600, return=200.000]
Iteration 6: 100%|██████████| 100/100 [00:25<00:00,  3.96it/s, episode=700, return=183.700]
Iteration 7: 100%|██████████| 100/100 [00:26<00:00,  3.79it/s, episode=800, return=187.000]
Iteration 8: 100%|██████████| 100/100 [00:23<00:00,  4.22it/s, episode=900, return=193.200]
Iteration 9: 100%|██████████| 100/100 [00:25<00:00,  3.88it/s, episode=1000, return=180.700]

在这里插入图片描述
在这里插入图片描述

  • 结果分析
    对比DQN算法,得到的episode_return波动更大,但能获取到最优的性能,得益于蒙特卡洛方法,如:
    Iteration 5: 100%|██████████| 100/100 [00:25<00:00, 3.91it/s, episode=600, return=200.000]
    (carpole的最佳奖励分数为200)

2.5 算法流程说明

初始化参数

learning_rate = 1e-3
num_episodes = 1000
hidden_dim = 128
gamma = 0.98
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")

初始化“车杆”环境

env_name = "CartPole-v0"
env = gym.make(env_name)
env.seed(0)
torch.manual_seed(0)

初始化policy_net网络模型

state_dim = env.observation_space.shape[0]
action_dim = env.action_space.n
agent = REINFORCE(state_dim, hidden_dim, action_dim, learning_rate, gamma,device)
...
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)

设置为4-128-2的全连接网络,输入为state=4维,输出为action的选取概率=2维,网络模型可表示为:
y = s o f t m a x ( f c 2 ( r e l u ( f c 1 ( x ) ) ) ) , x = s t a t e s y=softmax\left(fc_2\left(relu\bigl(fc_1(x)\bigr)\right)\right),x=states y=softmax(fc2(relu(fc1(x)))),x=states

episode采样

        for i_episode in range(int(num_episodes / 10)):episode_return = 0transition_dict = {'states': [],'actions': [],'next_states': [],'rewards': [],'dones': []}state = env.reset()done = Falsewhile not done: #采样单个episode直至terminal state,即done=trueaction = agent.take_action(state)next_state, reward, done, _ = env.step(action)transition_dict['states'].append(state)transition_dict['actions'].append(action)transition_dict['next_states'].append(next_state)transition_dict['rewards'].append(reward)transition_dict['dones'].append(done)state = next_stateepisode_return += rewardreturn_list.append(episode_return)
...def take_action(self, state):  # 🌟根据动作概率分布随机采样,不再是根据Q值去选取action了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()

①获取初始state:state = env.reset()
②根据policy_net的输出采取action:agent.take_action(state)
③与环境交互,得到(s,a,r,s’,done):env.step(action)
④将样本添加至episode:transition_dict
⑤统计episode即时奖励累加值:episode_return += reward

价值更新

agent.update(transition_dict)   #训练
...def update(self, transition_dict):reward_list = transition_dict['rewards']state_list = transition_dict['states']action_list = transition_dict['actions']G = 0self.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

采样完一个episode后,倒序累加discounted reward:G,以🌟fist-visit方式来估计多个q(s,a),这里的G就用来估计q(s,a),以实现:
reversed(range(len(reward_list)))+G = self.gamma * G + reward
q ( s t , a t ) = ∑ k = t + 1 T γ k − t − 1 r k q({s_{t},a_{t}})=\sum_{k=t+1}^{T}\gamma^{k-t-1}r_{k} q(st,at)=k=t+1Tγkt1rk

策略更新

agent.update(transition_dict)   #训练
...self.optimizer.zero_grad()...log_prob = torch.log(self.policy_net(state).gather(1, action))G = self.gamma * G + rewardloss = -log_prob * G  # 每一步的损失函数loss.backward()  # 反向传播计算梯度self.optimizer.step()  # 梯度下降,更新policy_net参数

计算交叉熵损失函数:loss = -log_prob * G,对应上交叉熵的定义:
L = H ( y , p ) = − ∑ i = 1 n y i log ⁡ ( p i ) L=H(\mathbf{y},\mathbf{p})=-\sum_{i=1}^ny_i\log(p_i) L=H(y,p)=i=1nyilog(pi)
其中,G对应“真值”,log_prob 对应网络模型输出概率的对数。

随后采用Adam优化器进行:梯度清零+反向传播+参数更新

三、疑问

3.1 为什么要采用softmax作为policy_net的输出?

要保证 π θ ( a ∣ s ) {\pi}_θ(a|s) πθ(as) 中每个action的概率为正数,并且概率在(0,1)之间取值。

3.2 为什么REINFORCE算法比DQN算法波动更大?

借助蒙特卡洛方法采样轨迹来估计动作价值,这种做法的一大优点是可以得到无偏的梯度。但是,正是因为使用了蒙特卡洛方法, REINFORCE 算法的梯度估计的方差很大,可能会造成一定程度上的不稳定

3.3 策略更新的步骤具体在算法哪一块?

算法示例中没有显示地去计算policy_net的梯度,而是在Adam optimizer中进行的梯度计算,具体代码为反向传播的这一步:loss.backward() # 反向传播计算梯度

四、总结

  • REINFORCE算法采用policy_net网络去显示地表征policy,采用蒙特卡洛的方法去估计q(s,a),智能体根据当前策略直接和环境交互,通过采样得到的轨迹数据直接计算出策略参数的梯度,进而更新当前策略,使其向最大化策略期望回报的目标靠近。

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

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

相关文章

LSTM(Long Short-Term Memory,长短期记忆网络)在高端局效果如何

lstm 杂乱数据分析 LSTM&#xff08;Long Short-Term Memory&#xff0c;长短期记忆网络&#xff09;在高端局&#xff0c;即复杂的机器学习和深度学习应用中&#xff0c;展现出了其独特的优势和广泛的应用价值。以下是对LSTM在高端局中的详细解析&#xff1a; 一、LSTM的优势…

大语言模型驱动的跨域属性级情感分析——论文阅读笔记

前言 论文PDF下载地址&#xff1a;7156 最近想搜一下基于大语言模型的情感分析论文&#xff0c;搜到了这篇在今年发表的论文&#xff0c;于是简单阅读之后在这里记一下笔记。 如图1所示&#xff0c;在餐厅领域中的"快"是上菜快&#xff0c;属于正面情感&#xff0c;但…

jfif图片怎么改成jpg?几种非常简单的jfif转jpg方法

jfif图片怎么改成jpg&#xff1f;随着图像技术的日新月异&#xff0c;用户在图像的编辑、处理与分享过程中&#xff0c;常常需要根据实际需求&#xff0c;灵活转换图像格式&#xff0c;以适应多样化的应用场景。正是这一需求&#xff0c;催生了将jfif格式向jpg格式转换的广泛实…

一些剪视频需要下载视频、chatTTS文字转语音的相关代码

可以在YouTube下载视频&#xff0c;下载字幕&#xff0c;以及需要文字转音频的一些代码&#xff0c;自己写的&#xff0c;目前也是能实现一点小需求~ 是需要下载FFmpeg、yt-dlp.exe、chrome_cookies插件&#xff0c;需要下载的自行search&#xff0c;不再赘述 人机验证 需要…

电能表预付费系统-标准传输规范(STS)(22)

6.5.2.3 DecoderKey classification 6.5.2.3.1 Classification of decoder keys STS DecoderKeys are classified according to the KT values given in Table 32 and inherit their type from that of the VendingKey, from which they are derived. STS decoderkey根据表32…

msvcr100.dll丢失怎么办,总结六种解决msvcr100.dll丢失的方法

​msvcr100.dll是Microsoft Visual C 2010 Redistributable Package中的一个关键动态链接库文件。它包含了运行由Visual C 2010编译的应用程序所需的一系列函数和类。简单来说&#xff0c;许多使用 Visual C 2010 编译的应用程序在启动或运行过程中会依赖 msvcr100.dll 文件。如…

Java基础 —— IO流详解

IO流 在Java中&#xff0c;IO&#xff08;输入/输出&#xff09;流是用于在程序与外部世界&#xff08;如文件、网络、内存等&#xff09;之间传输数据的机制。IO流分为两大类&#xff1a;输入流&#xff08;InputStream/Reader&#xff09;和输出流&#xff08;OutputStream/…

软硬件开发面试问题大汇总篇——针对非常规八股问题的提问与应答(代码规范与生态管理)

软硬件开发&#xff0c;对于编码规范、生态管理等等综合问题的考察尤为重要。 阐述下环形缓冲区的用途 环形缓冲区&#xff08;Ring Buffer&#xff09;是一种固定大小的数据结构&#xff0c;常用于实现数据的流式传输或临时存储。在环形缓冲区中&#xff0c;当到达缓冲区的末尾…

计算机网络:数据链路层 —— 虚拟局域网 VLAN

文章目录 局域网虚拟局域网 VLAN虚拟局域网 VLAN 概述实现机制IEEE 802.1Q帧以太网交换机的接口类型Access 接口Trunk 接口Hybrid 接口不进行人为的VLAN划分划分两个不同VLANTrunk接口去标签后进行转发Trunk接口直接转发 局域网 局域网&#xff08;Local Area Network&#xf…

【Vulnhub靶场】DC-6

DC-6靶场下载地址&#xff1a;https://download.vulnhub.com/dc/DC-6.zip​​​​​​ 目标 本机IP&#xff1a;192.168.118.128 靶机IP&#xff1a;192.168.118.0/24 信息收集 主机发现 arp-scan 192.168.118.0/24 根据上图得出目标主机为192.168.118.143 扫描端口&#x…

深入理解Java基础概念的高级应用(1/5)

目录 1. Java内存模型&#xff1a;堆、栈与方法区 示例代码&#xff1a;对象存储位置 2. 类加载器的工作原理 示例代码&#xff1a;自定义类加载器 3. JVM如何执行字节码 字节码指令示例 4. Java基础数据类型的存储与操作 自动装箱与拆箱 示例代码&#xff1a;基础类型…

Python小游戏14——雷霆战机

首先&#xff0c;你需要确保安装了Pygame库。如果你还没有安装&#xff0c;可以使用pip来安装&#xff1a; bash pip install pygame 代码如下&#xff1a; python import pygame import sys import random # 初始化Pygame pygame.init() # 设置屏幕大小 screen_width 800 scr…

Android在kts中使用navigation及Args

Android在kts中使用navigation及Args 前言&#xff1a; ​ 之前在项目中使用过navigation&#xff0c;但都是以Groory的方式&#xff0c;最近一年多使用kts后忍不住把项目都改成kts的方式&#xff0c;不过其中也遇到不少坑&#xff0c;今天就讲解一下如何在kts中使用navigati…

解决蓝牙键盘按键错乱的问题

最近发现我的蓝牙键盘按下的键盘与实际不符&#xff0c;于是就上网搜索答案&#xff0c;网上的方法都试了一遍 最后想着准备退货&#xff0c;没想到客服直接给我解决了 原因很简单&#xff0c;就是之前误触了键盘的某些按键导致的 每个键盘品牌的按键因该都不同的&#xff0c;可…

VBA技术资料MF220:删除模块内容

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

英伟达GPU算力【自用】

GPU&#xff08;图形处理单元&#xff09;算力的提升是驱动当代科技革命的核心力量之一&#xff0c;尤其在人工智能、深度学习、科学计算和超级计算机领域展现出了前所未有的影响力。2024年的GPU技术发展&#xff0c;不仅体现在游戏和图形处理的传统优势上&#xff0c;更在跨行…

ZooKeeper 客户端API操作

文章目录 一、节点信息1、创建节点2、获取子节点并监听节点变化3、判断节点是否存在4、客户端向服务端写入数据写入请求直接发给 Leader 节点写入请求直接发给 follow 节点 二、服务器动态上下线监听1、监听过程2、代码 三、分布式锁1、什么是分布式锁?2、Curator 框架实现分布…

qt 滚动条 美化

qt QScrollBar 滚动条分为竖直与水平滚动条&#xff0c;两者设置上类似&#xff0c;但也有一些不同&#xff0c;下面主要讲述美化及注意事项。 一、竖直滚动条 竖直滚动条分为7个部分&#xff1a; sub-line、 up-arrow 、sub-page、 hanle、 add-line、 dow-arrow、 add-pag…

线性回归模型与检验 6个适用条件

当因变量与自变量间存在线性相关关系时&#xff0c;可以使用线性回归分析方法确定它们之间的相互依赖的定量关系。此处所说的定量关系&#xff0c;并非严格的因果关系&#xff0c;而是自变量X对因变量Y的影响或预测的作用。 例如分析广告费、产品单价、产品满意度、服务满意度…

说它是谁就是谁—Python语言中的鸭子类型

鸭子类型&#xff08;Duck Typing&#xff09;是动态类型语言中的一种类型推断风格&#xff0c;尤其在Python语言中得到了广泛的应用。它的核心思想是&#xff1a;“如果它走起路来像鸭子&#xff0c;叫起来像鸭子&#xff0c;那么它就是鸭子”。这句话的意思是&#xff0c;我们…