强化学习快速复习笔记--待更新

news2024/12/23 3:41:42

目录

      • 蒙特卡洛方法
      • 动态规划算法
        • 策略迭代
      • 时序差分方法
        • Sarsa算法
        • Q-learning算法
        • 如何区分在线学习和离线学习
        • DQN深度强化Q学习
          • 概念介绍
          • 代码解析
        • DQN改进算法
          • Double DQN网络

蒙特卡洛方法

求解价值函数和状态价值函数,可以使用蒙特卡洛方法和动态规划。首先介绍一下蒙特卡洛的方法,这个方法是统计模拟方法,基于概率统计来进行数值计算。

  • 优点: 不需要知道环境模型,直接从交互中学习
  • 缺点: 每一次更新都需要完整的轨迹,使用一些具有明确任务终止的情况,而且采样效率很低下。

如果用这个方法来估计策略在一个马尔可夫决策中的状态价值函数。需要采集到很多条的序列。,然后使用这些轨迹进行更新和迭代。计算方法如下:

请添加图片描述

首先采样到的序列如下:

第一条序列
 [('s1', '前往s2', 0, 's2'), ('s2', '前往s3', -2, 's3'), ('s3', '前往s5', 0, 's5')]
第二条序列
 [('s4', '概率前往', 1, 's4'), ('s4', '前往s5', 10, 's5')]
第五条序列
 [('s2', '前往s3', -2, 's3'), ('s3', '前往s4', -2, 's4'), ('s4', '前往s5', 10, 's5')]

这里采用的是增量式计算方法:

# 对所有采样序列计算所有状态的价值
def MC(episodes, V, N, gamma):
    for episode in episodes:
        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]	

强化学习的目的:学习到一个策略,能够让agent从初始点到达终点且取到最大回报。

动态规划算法

本方法要求马尔可夫决策过程是已知的,也就是智能体交互的环境完全知道,在这种条件下,智能体不需要和环境进行交互来采样数据,直接用动态规划就可以算出来最优价值和策略。类似于监督学习任务,直接给出数据的分布公式,可以通过期望的层面来最小化模型的泛化误差更新模型。

利用马尔可夫决策过程的模型MDP, 进行求解,根据贝尔曼方程进行迭代更新,逐渐找到最优策略和值函数。

  • 优点:理论基础牢固,能够找到全局最优解。在这样一个白盒环境中,不需要通过智能体和环境的大量交互来学习,可以直接用动态规划求解状态价值函数
  • 缺点:需要事先了解环境的完整模型(状态转移概率和即时的奖励), 大规模问题求解比较困难。

动态规划方法主要分为两种:策略迭代价值迭代

策略迭代

包括两个部分:策略评估策略提升

策略迭代是不需要和环境进行交互的,因为环境的状态状态转移概率和奖励函数都已经确定了。

  • 策略评估
    • 计算一下当前策略下的值函数,使用贝尔曼方程迭代更新值函数,知道值函数收敛为止。
  • 策略改进
    • 我门将当前的值函数选择在每一个状态上具有最大值的动作,从而更新策略为新的最优策略。策略改进也不需要与环境进行互动。只是对值函数的一种估计。
    • 更新策略,让每一个状态选择最大值的动作。
  • 重复评估和更新直到收敛。

问:策略选择动作的概率和状态转移概率是一样吗?

策略 π(a|s) 表示在给定状态 s 下选择动作 a 的概率,而状态转移概率 P(s’|s,a) 则表示在给定状态 s 和动作 a 后,从状态 s 转移到状态 s’ 的概率。

假设有一个网格世界,包含两个状态 s1 和 s2,以及两个动作 a1 和 a2。我们定义一个策略 π,其中在状态 s1 下选择动作 a1 的概率为 0.6,选择动作 a2 的概率为 0.4。状态转移概率如下:

P(s2|s1, a1) = 0.8 P(s1|s1, a1) = 0.2 P(s2|s1, a2) = 0.3 P(s1|s1, a2) = 0.7


时序差分方法

大部分时候环境模型是不可能获得的,这就需要智能体和环境进行交互,这类方法被成为 无模型强化学习

首先简单理解一下时序差分方法:

回顾一下蒙特卡洛的更新方法:

请添加图片描述
这里对于价值更新是要等整个序列都结束以后,才能计算这一次回报G。在实际计算中,对于一条序列而言,从后面往前开始累加G,依次更新每一个V。

时序差分方法利用了这里增量式更新的方法,可以在每一个时间步进行值函数更新。具体而言,时序差分方法用当前获得的奖励加上了下一个状态的价值估计来作为当前状态会获得的回报,即:
请添加图片描述
折扣因子后面一串就是时序差分(Temporal difference)误差,时序差分算法将步长的乘积作为状态价值的更新量。

时序差分的特点:

时序差分(Temporal Difference, TD)方法是一类强化学习算法,它结合了动态规划和蒙特卡洛方法的优点。与蒙特卡洛方法需要等待完整的回合结束后才能更新值函数不同,TD 方法可以在每个时间步对值函数进行更新。这使得 TD 方法具有以下特点:

  1. 增量式更新:每一个时间步都可以对值函数进行更新。
  2. 时序性:通过比较当前状态的值函数和下一个状态的值函数估计来更新值函数。
  3. 学习策略灵活:TD方法可以结合不同的策略,如贪心策略,或者softmax策略,进行值函数的更新和动作选择。
  4. Off-policy和On-policy:TD方法适用于离线学习和在线学习。离线学习可以直接使用历史数据进行值函数更新,不需要和环境交互。在线学习方法需要交互,根据当前策略选择动作和更新值函数。

具体到经典的算法Sarsa和Q-learning算法来理解。

Sarsa算法

Sarsa:在每个时间步,根据当前状态 s、采取的动作 a、即时奖励 r、转移到的下一个状态 s’ 以及在下一个状态选择的动作 a’。需要五元组数据,SARSA。

Q值更新公式:
Q ( s , a ) ← Q ( s , a ) + α ∗ ( r + γ ∗ Q ( s ′ , a ′ ) − Q ( s , a ) ) Q(s, a) ← Q(s, a) + α * (r + γ * Q(s', a') - Q(s, a)) Q(s,a)Q(s,a)+α(r+γQ(s,a)Q(s,a))
其中 α 是学习率,γ 是折扣因子。

伪代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a7GjzcnD-1689171098747)(C:\Users\风净尘\AppData\Roaming\Typora\typora-user-images\image-请添加图片描述
.png)]

SARSA中,对于时序差分的计算用的下一个状态采取贪心算法下动作得到的价值来计算。而Q-learning采用的式直接选取下一个状态下能够获得最大价值的动作。而不是和SARSA那样根据策略来选取的动作。

SARSA是在线学习方法,他用State-Action-Reward-State-Action的序列进行更新,并且每一个时间步都进行值函数的更新和策略的选择。

具体看看一个简单的例子来理解这个过程:

# Sarsa算法
for episode in range(num_episodes):
    state = 0  # 初始状态
    if np.random.rand() < epsilon:
        action = np.random.randint(num_actions)  # 使用ε-贪心策略随机选择动作
    else:
        action = np.argmax(Q[state])  # 使用当前值函数估计选择动作

    while state != num_states - 1:  # 当前状态不是终止状态时
        # 执行动作并观察下一个状态和即时奖励
        next_state = np.random.choice(np.where(maze[state] >= 0)[0])  # 随机选择下一个状态
        reward = maze[state, action]

        # 使用ε-贪心策略选择下一个动作
        if np.random.rand() < epsilon:
            next_action = np.random.randint(num_actions)  # 随机选择下一个动作
        else:
            next_action = np.argmax(Q[next_state])  # 使用当前值函数估计选择下一个动作

        # 使用Sarsa更新值函数估计
        Q[state, action] += alpha * (reward + gamma * Q[next_state, next_action] - Q[state, action])

        state = next_state
        action = next_action  # 更新当前状态和动作,Q-learning只需要更新状态即可。

Q-learning算法

Q-learning:在每个时间步,根据当前状态 s、采取的动作 a、即时奖励 r、转移到的下一个状态 s’,使用贝尔曼最优方程更新 Q 值:Q(s, a) ← Q(s, a) + α * (r + γ * max(Q(s’, a’)) - Q(s, a))。只需要一个四元组数据即可。
请添加图片描述

如何区分在线学习和离线学习

我们称采样数据的策略为行为策略(behavior policy),称用这些数据来更新的策略为目标策略(target policy)。在线策略(on-policy)算法表示行为策略和目标策略是同一个策略;而离线策略(off-policy)算法表示行为策略和目标策略不是同一个策略。Sarsa 是典型的在线策略算法,而 Q-learning 是典型的离线策略算法。判断二者类别的一个重要手段是看计算时序差分的价值目标的数据是否来自当前的策略,如图 5-1 所示。具体而言:

  • 对于 Sarsa,它的更新公式必须使用来自当前策略采样得到的五元组,因此它是在线策略学习方法;
  • 对于 Q-learning,它的更新公式使用的是四元组来更新当前状态动作对的价值,数据中的s和a是给定的条件,reward和s_new皆由环境采样得到,该四元组并不需要一定是当前策略采样得到的数据,也可以来自行为策略,因此它是离线策略算法。

img

在某些情况下,Sarsa 更加稳定,因为它考虑了当前策略下的动作选择,可以更好地控制探索和利用的权衡。Q-learning 则更倾向于选择具有最大 Q 值的动作,可能导致更快地收敛到最优策略,但也可能导致过度估计和不稳定性。

DQN深度强化Q学习

概念介绍

DQN就是用神经网络来拟合这个动作价值函数。DQN只能处理动作离散的情况,因为在动作选择中有max的操作。动作值函数更新公式如下:
Q ( s , a ) = Q ( s , a ) + α ∗ ( r + γ ∗ m a x ( Q ( s ′ , a ′ ) ) − Q ( s , a ) ) Q(s, a) = Q(s, a) + α * (r + γ * max(Q(s', a')) - Q(s, a)) Q(s,a)=Q(s,a)+α(r+γmax(Q(s,a))Q(s,a))
其中,Q(s, a)是状态s下采取动作a的动作值估计,在DQN中,我们使用深度神经网络来估计Q值函数。网络的输入是状态s,输出是每个可能动作的Q值。网络的参数通过优化目标来不断更新,目标函数如下:
T a r g e t = r + γ ∗ m a x ( Q ( s ′ , a ′ ) ) Target = r + γ * max(Q(s', a')) Target=r+γmax(Q(s,a))
其中,r是即时奖励,γ是折扣因子,s’是下一个状态,a’是在下一个状态下采取的动作。目标值表示在下一个状态下,通过选择最优的动作所能获得的最大累积奖励。

DQN算法的训练过程中,通过最小化预测Q值与目标Q值之间的均方误差来更新网络的参数。具体来说,我们使用以下损失函数:
L o s s = ( Q ( s , a ) − T a r g e t ) 2 Loss = (Q(s, a) - Target)^2 Loss=(Q(s,a)Target)2
利用神经网络的反向传播算法,更新网络参数,让预测的Q(s, a)逼近Target。

两个关键的方法:

  • 经验回放

DQN属于离线学习方法,所以可以对数据加以重复利用,维护一个回放缓冲区,每次将环境中采样得到的四元组(s, a, r, s’)存放在缓冲区,在训练过程中再对缓冲区的历史数据进行随机训练。这样可以使样本满足独立假设。打破样本之间的相关性。

  • 目标网络

为了让Q(s, a)逼近r + γ * max(Q(s’, a’)),神经网络更新参数的同时,目标r + γ * max(Q(s’, a’))也不断改变,容易让神经网络不太稳定,因此加入了目标网络思想,利用两套神经网络,先将目标网络的参数固定住,然后隔N步再进行更新。原来的训练网络Q(s, a)使用正常梯度下降方法来更新,每一步都要进行更新。

综合所述得到伪代码如下:

请添加图片描述

代码解析
  • 首先是经验回放的类设计
class ReplayBuffer:
    ''' 经验回放池 '''
    def __init__(self, capacity):
        # collections是python标准库,有list、deque、字典等数据结构。
        self.buffer = collections.deque(maxlen=capacity)  # 双端队列,先进先出,一端添加,一端删除

    def add(self, state, action, reward, next_state, done):  # 将数据加入buffer
        self.buffer.append((state, action, reward, next_state, done))

    def sample(self, batch_size):  # 从buffer中采样数据,数量为batch_size
        # random.sample(population, k) 在抽样的序列中,随机抽取k个元素,返回包含元素的列表。
        transitions = random.sample(self.buffer, batch_size)
        # 将每一个元素的对应位置值打包为一个元组,返回以这个元组为列的迭代器。
        state, action, reward, next_state, done = zip(*transitions)
        return np.array(state), action, reward, np.array(next_state), done

    def size(self):  # 目前buffer中数据的数量
        return len(self.buffer)
  • 然后是Q值网络
class Qnet(torch.nn.Module):
    ''' 只有一层隐藏层的Q网络 '''
    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))  # 隐藏层使用ReLU激活函数
        return self.fc2(x)
  • DQN算法类实现
class DQN:
    ''' DQN算法 '''
    def __init__(self, state_dim, hidden_dim, action_dim, learning_rate, gamma,
                 epsilon, target_update, device):
        self.action_dim = action_dim
        self.q_net = Qnet(state_dim, hidden_dim,
                          self.action_dim).to(device)  # Q网络
        # 目标网络
        self.target_q_net = Qnet(state_dim, hidden_dim,
                                 self.action_dim).to(device)
        # 使用Adam优化器
        self.optimizer = torch.optim.Adam(self.q_net.parameters(),
                                          lr=learning_rate)
        self.gamma = gamma  # 折扣因子
        self.epsilon = epsilon  # epsilon-贪婪策略
        self.target_update = target_update  # 目标网络更新频率
        self.count = 0  # 计数器,记录更新次数
        self.device = device

    def take_action(self, state):  # epsilon-贪婪策略采取动作
        if np.random.random() < self.epsilon:
            action = np.random.randint(self.action_dim)
        else:
            state = torch.tensor([state], dtype=torch.float).to(self.device)
            action = self.q_net(state).argmax().item()
        return action

    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)
		# 状态传入Q网络, 在输出的每一个状态对应的Q值中,选出actions对应的Q值,也就是类似监督学习中的yi
        q_values = self.q_net(states).gather(1, actions)  # Q值
        # 下个状态的最大Q值,这个输出类似监督学习的y标签,也就是目标值。
        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
                                                                )  # TD误差目标
        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

DQN改进算法

Double DQN网络

在传统DQN网络中,我们训练网络是用来一个Q网络进行估计动作价值函数的,并且利用贪婪策略来选择动作,这样容易到值对Q值的过高估计,从而影响到训练的稳定性和收敛性。

Double DQN网络利用了两个Q网络来解决这个问题,一个在线网络用来选择动作,另一个用来估计动作价值函数。具体来说,使用一个Q网络(称为"online"网络)选择下一个动作,然后使用另一个Q网络(称为"target"网络)估计该动作的值。通过将选择动作和估计值的过程分离,Double DQN可以更准确地估计动作值,并减少过高估计的影响。Double DQN的流程如下:

  1. 选择动作:从 online 网络中选择动作 a,即 a = argmax Q_online(s, a)。
  2. 执行动作a:获得奖励s’和r;
  3. 估计下一个状态动作值:先使用online网络选取s’下的动作a’ = argmax Q_online(s’, a; θ),然后利用目标网络来计算s’和a’下的Q值。
  4. 计算TD目标值:y = r + γ * Q_target(s’, a’; θ’)
  5. 计算损失函数:loss = (Q_online(s, a; θ) - y)^2
  6. 更新在线网络参数:使用梯度下降法根据损失函数对在线网络的参数θ进行更新。
  7. 更新目标网络参数:定期将在线网络的参数θ复制到目标网络的参数θ’。

这么看来,DQN与Double DQN的差异仅仅是体现在计算Q值得时候动作的选取,DQN中是直接用目标网络进行max选取动作,而Double DQN中利用在线网络选取动作,再用这个动作来在目标网络中计算Q值。

请添加图片描述

因此在代码差异上其实并不太大,DDQN代码如下:

# 下个状态的最大Q值
        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)

这个环境用的是倒立摆,由于动作空间是连续的,但是DQN只能处理离散动作空间,因此这里提一嘴如何将连续动作空间离散化。

倒立摆动作空间:力矩大小[-2,2]

倒立摆的Q值:倒立摆向上直立时候能选取的最大Q值就为0,如果Q值大于0说明出现过高估计。

离散化:将动作空间离散化为11个动作。动作[0, 1, 2, 3, … 9, 10]分别代表力矩[-2, -1.6, -1.2…, 1.2 , 1.6, 2.0]


在Gym中,要step环境需要把离散得动作专户为连续的数值,代码如下:

def dis_to_con(discrete_action, env, action_dim):  # 离散动作转回连续的函数
 action_lowbound = env.action_space.low[0]  # 连续动作的最小值
 action_upbound = env.action_space.high[0]  # 连续动作的最大值
 return action_lowbound + (discrete_action /
                           (action_dim - 1)) * (action_upbound -
                                                action_lowbound)

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

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

相关文章

25-分布式事务----Seate

1、seate 官网:Seata Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。 1.1、Seata术语 TC (Transaction Coordinator) - 事务协调者…

mysql 执行sql开启事务

SHOW VARIABLES LIKE autocommit;SET autocommit 0; INSERT INTO sugar.realmauctiondatum(Id, Name) VALUES (3, A); INSERT INTO sugar.realmauctiondatum(Id, Name) VALUES (1, A); COMMIT;如果没有调用COMMIT;退出session时会执行回滚

python 面向对象之继承

文章目录 前言继承的概念单继承多继承子类重写父类的同名方法和属性子类调用父类同名的方法和属性多层继承私有权限 前言 前面我们已经学习了 python 面向对象的类和对象&#xff0c;那么今天我将为大家分享面向对象的三大特性之一&#xff1a;继承。 继承具有以下特性&#…

怎么使用文件高速传输,推荐镭速高速文件传输解决方案

​​随着互联网的发展&#xff0c;文件传输越来越频繁&#xff0c;如何实现文件高速传输已经越来越成为企业发展过程中需要解决的问题&#xff0c;在当今的业务中&#xff0c;随着与客户和供应商以及内部系统的所有通信的数据量不断增加&#xff0c;对 高速文件传输解决方案的需…

全网最新项目:会说话的汤姆猫直播搭建教程(附教学流程)

今天为大家分享一个 汤姆猫直播搭建项目 &#xff0c;这个项目最近可以说在圈内爆火&#xff0c;我相信很多朋友以前应该都玩过&#xff0c;或者说给自己家小孩子玩过。 -------------------------------------------------------------------- 课程获取:www.yn521.cn/160852…

RabbitMQ【笔记整理+代码案例】

1. 消息队列 1.1. MQ 的相关概念 1.1.1. 什么是 MQ MQ(message queue)&#xff0c;从字面意思上看&#xff0c;本质是个队列&#xff0c;FIFO 先入先出&#xff0c;只不过队列中存放的内容是message 而已&#xff0c;还是一种跨进程的通信机制&#xff0c;用于上下游传递消息…

Python应用:什么是爬虫?

文章目录 什么是爬虫虫之初&#xff0c;性本善&#xff1f;出行社交电商搜索引擎政府部门总结 面向监狱编程爬虫的君子协议什么是君子协议君子协议是怎么产生的&#xff1f;君子协议是什么内容&#xff1f;如何查看一个网站的robots协议违反君子协议的案例 参考文献 2022年初的…

装饰器模式:灵活扩展功能的设计利器

装饰器模式是一种结构型设计模式&#xff0c;它允许我们在不改变现有对象结构的情况下&#xff0c;动态地将新功能附加到对象上。本文将深入探讨装饰器模式的原理、结构和使用方法&#xff0c;并通过详细的 Java 示例代码来说明。 1. 装饰器模式的定义 装饰器模式是一种允许我…

Python: 如何批量预处理FY4A L1 DISK和REGC产品?(辐射定标/裁剪/GLT校正/HDF5转TIFF文件等)

目录 01 前言 1.1 想要说 1.2 Requirements 1.3 程序适用数据集 02 函数说明 2.1 读取HDF5文件某一数据集 2.2 读取HDF5文件数据集属性 2.3 对FY4A数据集进行辐射定标 2.4 基于官方地理对照表获取经纬度数据(仅适用DISK) 2.5 依据行列号计算经纬度数据(仅适用DISK) …

C++primer(第五版)第十八章(用于大型程序的工具)

不做大项目的话估计下面的都暂时用不到,包括下一章 大规模应用程序要求:能使用各种库进行协调开发(多人多文件编程);能在独立开发的子系统之间协同处理错误(说人话就是我用了你写的库结果报错了,我们得协调处理好出错的地方);能对比较复杂的应用概念进行建模(定义合理的类,函数…

(工具记录)Log4j2_RCE

0x00 简介 ApacheLog4j2是一个开源的Java日志框架&#xff0c;被广泛地应用在中间件、开发框架与Web应用中。 0x01 漏洞概述 该漏洞是由于Apache Log4j2某些功能存在递归解析功能&#xff0c;未经身份验证的攻击者通过发送特定恶意数据包&#xff0c;可在目标服务器上执行任意…

获取DNF人物坐标值

众所周知DNF是一个没有坐标值显示的游戏。 那么如何才能不碰内存和封包的情况下&#xff0c;获取DNF游戏中人物在当前房间的坐标值呢&#xff1f; 有兴趣的找我交流吧。

Go语言IO模式

Go语言IO模式 IO 操作是我们在编程中不可避免会遇到的&#xff0c;Go语言的 io 包中提供了相关的接口&#xff0c;定义了相应的规范&#xff0c;不同的数 据类型可以根据规范去实现相应的方法&#xff0c;提供更加丰富的功能。 本文主要介绍常见的 IO (输入和输出)模式&…

《MySQL》复合查询和连接

文章目录 查询单行子查询多行子查询合并查询 连接内连接外连接 点睛之笔&#xff1a;无论是多表还是单表&#xff0c;我们都可以认为只有一张表。 只要是表&#xff0c;就可以查询和连接成新表&#xff0c;所以select出来的结果都可以认为成一张表&#xff0c;既然是一张表&…

Python多线程使用详解

概要 多线程是一种并发编程的技术&#xff0c;通过同时执行多个线程来提高程序的性能和效率。在Python中&#xff0c;我们可以使用内置的threading模块来实现多线程编程。 一、创建线程 在使用多线程之前&#xff0c;我们首先需要了解如何创建线程。Python提供了threading模块…

你信不信,只要学几天javascript就可以使用纯原生实现五星评分效果 【附完整代码】

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;web开发者、设计师、技术分享博主 &#x1f40b; 希望大家多多支持一下, 我们一起进步&#xff01;&#x1f604; &#x1f3c5; 如果文章对你有帮助的话&#xff0c;欢迎评论 &#x1f4ac;点赞&#x1…

动态规划DP(七) 股票交易

1.股票交易 在股票买卖的最佳时机问题中&#xff0c;给定一个数组&#xff0c;数组中的每个元素代表某一天的股票价格。你可以进行多次买入和卖出&#xff0c;但是必须在再次购买前卖出之前的股票。目标是找到最大的利润。 动态规划可以用于解决股票交易类的问题&#xff0c;…

ssl证书和域名过期提醒平台

由于经常忘记了证书是否要过期&#xff0c;导致过期了出现一些访问上的问题 docker安装部署 当然可以接入mysql&#xff0c;默认使用的sqlite version: "3" services:domain-admin:image: mouday/domain-admin:latestcontainer_name: domain-adminvolumes:- ./data:/…

菜鸡shader:L8 UV扰动动画——火焰和简单水面

文章目录 卡通火焰代码最后效果 水面代码最后效果 这此做笔记的两个shader其实是课程的作业&#xff0c;课程主要也是讲UV扰动的概念&#xff0c;因为课程的shader在另一台电脑上&#xff0c;做笔记就暂时不带他们了&#xff0c;简单做下火焰和水面的shader。 卡通火焰 火焰这…

FreeRTOS(队列)

队列 什么是队列&#xff1f; 队列又称消息队列&#xff0c;是一种常用于任务间通信的数据结构&#xff0c;队列可以在任务与任务间、中断和任 务间传递信息。 为什么不使用全局变量&#xff1f; 如果使用全局变量&#xff0c;兔子&#xff08;任务1&#xff09;修改了变量 a …