【强化学习】10 —— DQN算法

news2024/12/28 3:37:57

文章目录

  • 深度强化学习
    • 价值和策略近似
    • RL与DL结合产生的问题
    • 深度强化学习的分类
  • Q-learning回顾
  • 深度Q网络(DQN)
    • 经验回放
      • 优先经验回放
    • 目标网络
    • 算法流程
  • 代码实践
    • CartPole环境
    • 代码
    • 结果
  • 参考

深度强化学习

价值和策略近似

在这里插入图片描述
我们可以利用深度神经网络建立这些近似函数

在这里插入图片描述
深度强化学习使强化学习算法能够以端到端的方式解决复杂问题

RL与DL结合产生的问题

• 价值函数和策略现在变成了深度神经网络
• 相当高维的参数空间
• 难以稳定地训练
• 容易过拟合
• 需要大量的数据
• 需要高性能计算
• CPU(用于收集经验数据)和GPU(用于训练神经网络)之间的平衡

深度强化学习的分类

  • 基于价值的方法
    • 深度Q网络及其扩展
  • 基于随机策略的方法
    • 使用神经网络的策略梯度,自然策略梯度,信任区域策略优化(TRPO),
    近端策略优化(PPO),A3C
  • 基于确定性策略的方法
    • 确定性策略梯度(DPG),DDPG

Q-learning回顾

文章链接——http://t.csdnimg.cn/Abz4v
Q-learning不直接更新策略,是一种基于值的方法。我们先来回顾一下 Q-learning 的更新规则 Q ( s , a ) ← Q ( s , a ) + α [ r + γ max ⁡ a ′ ∈ A Q ( s ′ , a ′ ) − Q ( s , a ) ] Q(s,a)\leftarrow Q(s,a)+\alpha\left[r+\gamma\max_{a^{\prime}\in\mathcal{A}}Q(s^{\prime},a^{\prime})-Q(s,a)\right] Q(s,a)Q(s,a)+α[r+γaAmaxQ(s,a)Q(s,a)]

上述公式用时序差分(temporal difference,TD)学习目标来增量式更新 r + γ max ⁡ a ′ ∈ A Q ( s ′ , a ′ ) r+\gamma\max_{a'\in\mathcal A}Q(s',a') r+γmaxaAQ(s,a),也就是说要使 Q ( s , a ) Q(s,a) Q(s,a)和 TD 目标 r + γ max ⁡ a ′ ∈ A Q ( s ′ , a ′ ) r+\gamma\max_{a'\in\mathcal A}Q(s',a') r+γmaxaAQ(s,a)靠近。于是,对于一组数据 { ( s i , a i , r i , s i ′ ) } \{(s_i,a_i,r_i,s_i')\} {(si,ai,ri,si)},我们可以很自然地将 Q 网络的损失函数构造为均方误差的形式: ω ∗ = arg ⁡ min ⁡ ω 1 2 N ∑ i = 1 N [ Q ω ( s i , a i ) − ( r i + γ max ⁡ a ′ Q ω ( s i ′ , a ′ ) ) ] 2 \omega^*=\arg\min_\omega\frac{1}{2N}\sum_{i=1}^N\left[Q_\omega\left(s_i,a_i\right)-\left(r_i+\gamma\max_{a'}Q_\omega\left(s_i',a'\right)\right)\right]^2 ω=argωmin2N1i=1N[Qω(si,ai)(ri+γamaxQω(si,a))]2

PS1: Q w ( s , a ) Q_w(s,a) Qw(s,a)表示Q-learning学习一个由 w w w 作为参数的函数 Q w ( s , a ) Q_w(s,a) Qw(s,a)
PS2: Q w ( s i ′ , a ′ ) Q_w(s_i',a') Qw(si,a)无梯度

比较直观的想法是使用神经网络来逼近上述 Q w ( s , a ) Q_w(s,a) Qw(s,a),但是深度神经网络存在以下问题:

  • 算法不稳定
    • 连续采样得到的 { ( s i , a i , r i , s i ′ ) } \{(s_i,a_i,r_i,s_i')\} {(si,ai,ri,si)}不满足独立分布。
    • 会导致 Q w ( s , a ) Q_w(s,a) Qw(s,a)的频繁更新(Q-policy-data_distribution都在变)。

解决办法

  • 经验回放
  • 使用双网络结构:评估网络(evaluation network)和目标网络(target network)

深度Q网络(DQN)

在这里插入图片描述

经验回放

在一般的有监督学习中,假设训练数据是独立同分布的,我们每次训练神经网络的时候从训练数据中随机采样一个或若干个数据来进行梯度下降,随着学习的不断进行,每一个训练数据会被使用多次。在原来的 Q-learning 算法中,每一个数据只会用来更新一次 Q Q Q值。为了更好地将 Q-learning 和深度神经网络结合,DQN 算法采用了经验回放(experience replay)方法,具体做法为维护一个回放缓冲区,将每次从环境中采样得到的四元组数据(状态、动作、奖励、下一状态)存储到回放缓冲区中,训练 Q 网络的时候再从回放缓冲区中随机采样若干数据来进行训练。这么做可以起到以下两个作用。

(1)使样本满足独立假设。在 MDP 中交互采样得到的数据本身不满足独立假设,因为这一时刻的状态和上一时刻的状态有关。非独立同分布的数据对训练神经网络有很大的影响,会使神经网络拟合到最近训练的数据上。采用经验回放可以打破样本之间的相关性,让其满足独立假设。

(2)提高样本效率。每一个样本可以被使用多次,十分适合深度神经网络的梯度学习。

优先经验回放

优先经验回放可以防止数据过拟合,可以更多地关注差距较大的那些值。
Schaul, Tom, et al. “Prioritized experience replay.” arXiv preprint arXiv:1511.05952 (2015).
衡量标准

  • 以 𝑄 函数的值与 Target 值的差异来衡量学习的价值,即 p t = ∣ r t + γ max ⁡ a ′ Q θ ( s t + 1 , a ′ ) − Q θ ( s t , a t ) ∣ p_t=|r_t+\gamma\underset{a^{\prime}}{\operatorname*{max}}Q_\theta(s_{t+1},a^{\prime})-Q_\theta(s_t,a_t)| pt=rt+γamaxQθ(st+1,a)Qθ(st,at)
  • 为了使各样本都有机会被采样,存储 e t = ( s t , a t , s t + 1 , r t , p t + ϵ ) e_{t}=(s_{t},a_{t},s_{t+1},r_{t},p_{t}+\epsilon) et=(st,at,st+1,rt,pt+ϵ)
  • 选中的概率,样本 e t e_t et 被选中的概率为 P ( t ) = p t α ∑ k p k α P(t)=\frac{p_t^\alpha}{\sum_kp_k^\alpha} P(t)=kpkαptα
  • 重要性采样(Importance Sampling),权重为 ω t = ( N × P ( t ) ) − β max ⁡ i ω i \omega_{t}=\frac{\left(N\times P(t)\right)^{-\beta}}{\max_{i}\omega_{i}} ωt=maxiωi(N×P(t))β

算法伪代码
在这里插入图片描述

目标网络

DQN 算法最终更新的目标是让 Q w ( s , a ) Q_w(s,a) Qw(s,a)逼近 r + γ max ⁡ a ′ ∈ A Q ( s ′ , a ′ ) r+\gamma\max_{a'\in\mathcal A}Q(s',a') r+γmaxaAQ(s,a)由于 TD 误差目标本身就包含神经网络的输出,因此在更新网络参数的同时目标也在不断地改变,这非常容易造成神经网络训练的不稳定性。为了解决这一问题,DQN 便使用了目标网络(target network)的思想:既然训练过程中 Q 网络的不断更新会导致目标不断发生改变,不如暂时先将 TD 目标中的 Q 网络固定住。为了实现这一思想,我们需要利用两套 Q 网络。

(1)原来的训练网络 Q w ( s , a ) Q_w(s,a) Qw(s,a),用于计算原来的损失函数 1 2 [ Q ω ( s , a ) − ( r + γ max ⁡ a ′ Q ω − ( s ′ , a ′ ) ) ] 2 \frac{1}{2}[Q_{\omega}\left(s,a\right)-\left(r+\gamma\max_{a^{\prime}}Q_{\omega^{-}}\left(s^{\prime},a^{\prime}\right)\right)]^{2} 21[Qω(s,a)(r+γmaxaQω(s,a))]2中的 Q w ( s , a ) Q_w(s,a) Qw(s,a)项,并且使用正常梯度下降方法来进行更新。

(2) 目标网络 Q w − ( s , a ) Q_{w^{-}}(s,a) Qw(s,a),用于计算原先损失函数 1 2 [ Q ω ( s , a ) − ( r + γ max ⁡ a ′ Q ω − ( s ′ , a ′ ) ) ] 2 \frac{1}{2}[Q_{\omega}\left(s,a\right)-\left(r+\gamma\max_{a^{\prime}}Q_{\omega^{-}}\left(s^{\prime},a^{\prime}\right)\right)]^{2} 21[Qω(s,a)(r+γmaxaQω(s,a))]2中的 ( r + γ max ⁡ a ′ Q ω − ( s ′ , a ′ ) ) \left(r+\gamma\max_{a^{\prime}}Q_{\omega^{-}}\left(s^{\prime},a^{\prime}\right)\right) (r+γmaxaQω(s,a))项,其中 w − w^{-} w表示目标网络中的参数。如果两套网络的参数随时保持一致,则仍为原先不够稳定的算法。为了让更新目标更稳定,目标网络并不会每一步都更新。具体而言,目标网络使用训练网络的一套较旧的参数,训练网络 Q w ( s , a ) Q_w(s,a) Qw(s,a)在训练中的每一步都会更新,而目标网络 Q w − ( s , a ) Q_{w^{-}}(s,a) Qw(s,a)的参数每隔 C C C步才会与训练网络同步一次,即 w − ← w w^{-}\leftarrow w ww。这样做使得目标网络相对于训练网络更加稳定。

算法流程

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

代码实践

CartPole环境

Cart Pole gymnasium文档
pytorch官方教程REINFORCEMENT LEARNING (DQN) TUTORIAL
(使用stable_baselines3)强化学习训练的模型怎么存储?比如OpenAI-gym训练好的模型? -
https://www.zhihu.com/question/67825049/answer/2794069082
在这里插入图片描述
在车杆环境中,有一辆小车,智能体的任务是通过左右移动保持车上的杆竖直,若杆的倾斜度数过大,或者车子离初始位置左右的偏离程度过大,或者坚持时间到达 500 帧,则游戏结束。智能体的状态是一个维数为 4 的向量,每一维都是连续的,其动作是离散的,动作空间大小为 2。在游戏中每坚持一帧,智能体能获得分数为 1 的奖励,坚持时间越长,则最后的分数越高,坚持 500 帧即可获得最高的分数。

状态空间Box([-4.8000002e+00 -3.4028235e+38 -4.1887903e-01 -3.4028235e+38], [4.8000002e+00 3.4028235e+38 4.1887903e-01 3.4028235e+38], (4,), float32)

维度意义最小值最大值
0车的位置-2.42.4
1车的速度-InfInf
2杆的角度~ -41.8°~ 41.8°
3杆尖端的速度-InfInf

动作空间Discrete(2)

标号动作
0向左移动小车
1向右移动小车

代码

import random
import gymnasium as gym
import numpy as np
import collections
from tqdm import tqdm
import torch
import torch.nn.functional as F
import util

class ReplayBuffer:
    ''' 经验回放池 '''
    def __init__(self, capacity):
        self.buffer = collections.deque(maxlen=capacity)  # 队列,先进先出

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

    # 从buffer中采样数据,数量为batch_size
    def sample(self, batch_size):
        transitions = random.sample(self.buffer, batch_size)
        state, action, reward, next_state, terminated, truncated = zip(*transitions)
        return np.array(state), action, reward, np.array(next_state), terminated, truncated

    # 目前buffer中数据的数量
    def size(self):
        return len(self.buffer)

class Qnet1(torch.nn.Module):
    ''' 只有一层隐藏层的Q网络 '''
    def __init__(self, state_dim, hidden_dim, action_dim):
        super(Qnet1, self).__init__()
        self.fc = torch.nn.Sequential(
            torch.nn.Linear(state_dim, hidden_dim),
            torch.nn.ReLU(),
            torch.nn.Linear(hidden_dim, action_dim)
        )

    def forward(self, x):
        return self.fc(x)

class DQN:
    ''' DQN算法 '''
    def __init__(self, state_dim, hidden_dim, action_dim, learning_rate, gamma,
                 epsilon, target_update_rate, device, numOfEpisodes, env,
                 buffer_size, minimal_size, batch_size):
        self.action_dim = action_dim
        # Q网络
        self.q_net = Qnet1(state_dim, hidden_dim, self.action_dim).to(device)
        # 目标网络
        self.target_q_net = Qnet1(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
        # 目标网络更新频率
        self.target_update_rate = target_update_rate
        # 计数器,记录更新次数
        self.count = 0
        self.device = device
        self.numOfEpisodes = numOfEpisodes
        self.env = env
        self.buffer_size = buffer_size
        self.minimal_size = minimal_size
        self.batch_size = batch_size

    # Choose A from S using policy derived from Q (e.g., epsilon-greedy)
    def ChooseAction(self, state):
        if np.random.random() < self.epsilon:
            action = np.random.randint(self.action_dim)
        else:
            state = torch.tensor(np.array([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)
        terminateds = torch.tensor(transition_dict['terminateds'],
                                   dtype=torch.float).view(-1, 1).to(self.device)
        truncateds = torch.tensor(transition_dict['truncateds'],
                                   dtype=torch.float).view(-1, 1).to(self.device)
        #Q值?
        q_values = self.q_net(states).gather(1, actions)
        # 下个状态的最大Q值
        max_next_q_values = self.target_q_net(next_states).max(1)[0].view(-1, 1)
        # TD误差目标
        q_targets = rewards + self.gamma * max_next_q_values * (1 - terminateds + truncateds)
        # 均方误差损失函数
        dqn_loss = torch.mean(F.mse_loss(q_values, q_targets))
        # PyTorch中默认梯度会累积,这里需要显式将梯度置为0
        self.optimizer.zero_grad()
        # 反向传播更新参数
        dqn_loss.backward()
        self.optimizer.step()

        if self.count % self.target_update_rate == 0:
            self.target_q_net.load_state_dict(
                self.q_net.state_dict())  # 更新目标网络
        self.count += 1


    def DQNtrain(self):
        replay_buffer = ReplayBuffer(self.buffer_size)
        returnList = []
        for i in range(10):
            with tqdm(total=int(self.numOfEpisodes / 10), desc='Iteration %d' % i) as pbar:
                for episode in range(int(self.numOfEpisodes / 10)):
                    # initialize state
                    state, info = self.env.reset()
                    terminated = False
                    truncated = False
                    episodeReward = 0
                    # Loop for each step of episode:
                    while (not terminated) or (not truncated):
                        action = self.ChooseAction(state)
                        next_state, reward, terminated, truncated, info = self.env.step(action)
                        replay_buffer.add(state, action, reward, next_state, terminated, truncated)
                        if terminated or truncated:
                            break
                        state = next_state
                        episodeReward += reward
                        # 当buffer数据的数量超过一定值后,才进行Q网络训练
                        if replay_buffer.size() > self.minimal_size:
                            b_s, b_a, b_r, b_ns, b_te, b_tr = replay_buffer.sample(self.batch_size)
                            transition_dict = {
                                'states': b_s,
                                'actions': b_a,
                                'next_states': b_ns,
                                'rewards': b_r,
                                'terminateds': b_te,
                                'truncateds': b_tr
                            }
                            self.Update(transition_dict)
                    returnList.append(episodeReward)
                    if (episode + 1) % 10 == 0:  # 每10条序列打印一下这10条序列的平均回报
                        pbar.set_postfix({
                            'episode':
                                '%d' % (self.numOfEpisodes / 10 * i + episode + 1),
                            'return':
                                '%.3f' % np.mean(returnList[-10:])
                        })
                    pbar.update(1)
        return returnList

def test01():
    device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
    env = gym.make("CartPole-v1", render_mode="human")
    # random.seed(0)
    # np.random.seed(0)
    # torch.manual_seed(0)
    returnLists1 = []
    ReturnList = []
    agent = DQN(state_dim=env.observation_space.shape[0],
                hidden_dim=128,
                action_dim=2,
                learning_rate=2e-3,
                gamma=0.98,
                epsilon=0.01,
                target_update_rate=10,
                device=device,
                numOfEpisodes=500,
                env=env,
                buffer_size=10000,
                minimal_size=500,
                batch_size=64)
    returnLists1.append(agent.DQNtrain())
    ReturnList.append(util.smooth(returnLists1, sm=100))
    labelList = ['DQN']
    util.PlotReward(500, ReturnList, labelList, 'CartPole-v1')
    np.save("D:\LearningRL\Hands-on-RL\DQN_CartPole\ReturnData\DQN_CartPole_v0_2.npy", returnLists1)
    env.close()

if __name__ == "__main__":
    test01()

结果

一次不太理想的结果
在这里插入图片描述
pytorch教程中的结果
在这里插入图片描述

一次比较“好”的结果
在这里插入图片描述
在这里插入图片描述
左(保留训练效果不理想),右(剔除训练效果不理想)

部分结果动图
在这里插入图片描述

参考

[1] 伯禹AI
[2] https://www.davidsilver.uk/teaching/
[3] 动手学强化学习
[4] Reinforcement Learning

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

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

相关文章

缺少d3dx9_43.dll怎么解决 win系统如何运行dll文件?

大家好&#xff01;今天我来给大家分享一下关于d3dx9_43.dll缺失的4种详细解决方案。 首先&#xff0c;让我们了解一下d3dx9_43.dll是什么文件。其实&#xff0c;d3dx9_43.dll是DirectX的一个组件&#xff0c;它主要负责处理游戏中的一些特效和动画效果。如果这个文件丢失了&a…

MacOS系统电脑怎么彻底清理系统垃圾注册表App Cleaner可以深度清理吗

App Cleaner & Uninstaller 是一款适用于 Mac 操作系统的软件应用程序&#xff0c;允许用户轻松卸载不需要的应用程序、删除剩余文件和文件夹以及管理启动项。该应用程序会分析与您要删除的应用程序关联的文件&#xff0c;并帮助识别其所有组件&#xff0c;以便您可以一次将…

C++学习day--23 枚举、类型定义、头文件

1、枚举 1.1 枚举的概念 枚举是 C/C 语言中的一种基本数据类型&#xff0c; 它可以用于声明一组常数 。当一个变量有几个固 定的可能取值时&#xff0c;可以将这个变量定义为枚举类型。 比如&#xff0c;你可以用一个枚举类型的变量来表示季节&#xff0c;因为季节只有 4 …

C++之左值、右值、std::forward、std::move总结(二百五十)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

033-第三代软件开发-固定区域截图

第三代软件开发-固定区域截图 文章目录 第三代软件开发-固定区域截图项目介绍固定区域截图QWidget 版本QML 版本 自由截图自由截图二 关键字&#xff1a; Qt、 Qml、 关键字3、 关键字4、 关键字5 项目介绍 欢迎来到我们的 QML & C 项目&#xff01;这个项目结合了 QM…

粤嵌实训医疗项目--day03(Vue + SpringBoot)

往期回顾 粤嵌实训医疗项目day02&#xff08;Vue SpringBoot&#xff09;-CSDN博客 粤嵌实训医疗项目--day01&#xff08;VueSpringBoot&#xff09;-CSDN博客 目录 一、SpringBoot AOP的使用 二、用户模块-注册功能&#xff08;文件上传&#xff09; 三、用户模块-注册实现…

基于stm32的ADC读取烟雾报警器的数值

本文想要设计一个设计一个有stm32控制的烟雾报警系统。通过MQ-2烟雾报警器将获取模拟的数值传递给stm32的ADC外设并在串口助手上显示对应的电压值。烟雾报警器浓度越高&#xff0c;他的电压就越高&#xff0c;但是不会超过3.3V。设置一个电压临界值&#xff0c;当传输回来的电压…

Python环境下LaTeX数学公式转图像方案调研与探讨

目录 引言方案一&#xff1a;基于LaTeX环境方案二&#xff1a;基于KaTeX(推荐) 方案三&#xff1a;基于Matplotlib写在最后 引言 近来&#xff0c;涉及到一些公式识别的项目&#xff0c;输入是公式的图像&#xff0c;输出是LaTeX格式的数学公式字符串。 这类项目一般都采用深…

小程序如何设置首选配送公司

小程序的一个重要环节就是配送服务。为了提供更好的发货体验&#xff0c;避免商家总是要在众多的配送公司中选择想要&#xff0c;小程序支持设置首选配送。下面将具体介绍一下小程序如何设置。 在小程序管理员后台->配送设置->首选配送处&#xff0c;指定需要设置的首选…

如何使用gpt提高效率

如何使用gpt提高效率 自动化替代人力工作减少创意工作需求技术依赖风险实际应用领域内容生成自动回答问题自动化编程个性化推荐 博主 默语带您 Go to New World. ✍ 个人主页—— 默语 的博客&#x1f466;&#x1f3fb; 《java 面试题大全》 &#x1f369;惟余辈才疏学浅&…

Linux进程控制(一)

前言&#xff1a;Linux进程控制是指在Linux操作系统中&#xff0c;对进程的创建、运行、管理和终止等方面进行控制的一系列机制和技术。Linux作为一个多任务操作系统&#xff0c;能够同时运行多个进程任务的执行&#xff0c;继前面我们对Linux进程创建的学习之后&#xff0c;今…

【多线程面试题 六】、 如何实现线程同步?

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a; 如何实现线程同步&…

Linux touch命令:创建文件及修改文件时间

既然知道了如何在 Linux 系统中创建目录&#xff0c;接下来你可能会想在这些目录中创建一些文件&#xff0c;可以使用 touch 命令。 需要注意的是&#xff0c;touch 命令不光可以用来创建文件&#xff08;当指定操作文件不存在时&#xff0c;该命令会在当前位置建立一个空文件&…

“第五十三天”

今天没有做什么&#xff0c;不过这个在打印&#xff0c;的时候一直卡着&#xff0c;我一直在想把逗号打印在后面&#xff0c;所以一直想办法确定最后一个是哪一位&#xff0c;这里居然没有绕过来其实可以看做是前面&#xff0c;这样第一个不打印逗号&#xff0c;后面打印就可以…

【笔录】TVP技术沙龙:寻宝AI时代

目录 引言大模型的应用案例大模型三问模型落地可行性考量维度AIGC的几个可行应用方向 引言 大模型是10倍的机会&#xff0c;但并不是平均主义的机会&#xff0c;没有低垂的果实。 企业想在大模型的赛道上跑出成绩&#xff0c;应该怎么做&#xff0c;又要选择哪些赛道&#xff1…

postgresSQL 数据库本地创建表空间读取本地备份tar文件与SQL文件

使用pgAdmin4&#xff0c;你安装PG得文件夹****/16/paAdmin 4 /runtime/pgAdmin4.exe 第一步&#xff1a;找到Tablespaces 第二步&#xff1a;创建表空间名称 第三步&#xff1a;指向数据文件 第四步&#xff1a;找到Databases&#xff0c;创建表空间 第五步&#xff1a;输入数…

矩阵按键简单使用

1、写矩阵按键&#xff08;下面在按这个格式把别的行也写入进去&#xff09; 2、在LCD中显示

听GPT 讲Rust源代码--library/std(7)

题图来自 Programming languages: How Google is using Rust to reduce memory safety vulnerabilities in Android[1] File: rust/library/std/src/sys/unix/kernel_copy.rs 在Rust的标准库中&#xff0c;kernel_copy.rs文件位于sys/unix目录下&#xff0c;其主要作用是实现特…

全志T113-S3 裸机SMHC eMMC读写问题记录

由于全志的资料实在太少&#xff0c;很多只能通过很长时间测试才能知道问题&#xff0c;目前还没有实现时钟初始化&#xff0c;只使用了默认的24MHz时钟&#xff0c;测试eMMC读写过程中遇到2个问题&#xff1b; 问题1&#xff1a;读取扇区的时候&#xff0c;会遇到数据停止位错…

python 笔记:h5py 读取HDF5文件

1 HDF5文件 HDF5 是 Hierarchical Data Format version 5 的缩写&#xff0c;是一种用于存储和管理大量数据的文件格式一个h5py文件可以看作是 “dataset” 和 “group” 二合一的容器 dataset : 数据集&#xff0c;像 numpy 数组一样工作group : 包含了其它 dataset 和 其它 …