强化学习分享(一) DQN算法原理及实现

       摘要:主要讲解DQN算法的原理,伪代码解读,基于pytorch版本的DQN小游戏编程,同时对该代码进行详细标注,以及奉上原码。

(一)强化学习算法介绍

       DQN,顾名思义,Deep Q Learning ;在传统强化学习Q-Learning的基础之上,用深度学习的神经网络来拟合函Q值函数,从而达到更好的学习效果。

关于强化学习的基础知识,可以先看这篇文献:强化学习入门:基本思想和经典算法 - 知乎 (zhihu.com)

       强化学习的主要应用范围包括:游戏交通拥堵能源分配广告推送,机器人控制,组合与序列控制问题目前我自己将要研究的是微电网电力资源分配问题,也是强化学习的一个小应用方向。关于策略,状态转移,奖励/回报,价值函数(动作价值和状态价值函数),折扣系数,学习率,目标函数(Target-value network),这些都默认大家知道了,勤动手,百度一下。

        强化学习根据算法的应用环境,分为基于模型的算法和无模型算法。

Model-Free 算法,是指算法中智能体不需要学习和理解环境的信息,环境给什么就用什么,信息的结果告诉它把地球炸了,他就会想着把地球炸掉。

Model-Based算法,是指去学习和理解环境。学会用一个模型来模拟环境,通过模拟的环境来得到反馈。后者比前者多了一个模拟环境的这个环节,通过模拟环境预判接下来会发生的情况,然后选择最优动作进行执行。

一般生活中的问题,都很难用一个准确的模型进行描述,因此,主要研究“无模型算法”。

图1.1 强化学习算法分类(按照有无模型分类)

  • Policy-Based的方法直接输出下一步动作的概率,根据概率来选取动作。但不一定概率最高就会选择该动作,还是会从整体进行考虑。适用于非连续和连续的动作。常见的方法有Policy gradients。
  • Value-Based的方法输出的是动作的价值,选择价值最高的动作。适用于非连续的动作。常见的方法有Q-learning、Deep Q Network和Sarsa。
  • 更为厉害的方法是二者的结合:Actor-Critic,Actor根据概率做出动作,Critic根据动作给出价值,从而加速学习过程,常见的有A2C,A3C,DDPG等。(需要好好钻研理论知识)。
  • 同时使用Actor-Critic的学习方式巧妙的实现了将值函数和策略分开进行学习的方法,让策略达到最优的时候去指导值函数的更新。

  • 图1.2 演员-评论家算法

  • 经典算法:Q-learning,Sarsa,DQN,Policy Gradient,A3C,DDPG,PPO。

(二)DQN算法原理

(1)关于Q-Learning算法

        该算法是DQN算法的前身,Q-learning算法是1989年Watkins提出来的,2015年nature论文所提出的DQN就是在Q-learning的基础上修改得到的。嗯,有兴趣可以深入了解它。

(2)关于DQN算法特点

        论文名称:《Human-level control through deep reinforcement learning》

DQN对Q-learning的修改主要体现在以下三个方面:

        (1)DQN利用深度卷积神经网络逼近值函数(作为一种逼近手段引入进去)。

        (2)DQN利用了经验回放对强化学习的学习过程进行训练(经验池)。

        (3)DQN独立设置了目标网络(Target)来单独处理时间差分算法中的TD偏差。它主要解决算法训练不稳定的问题。复制一个和原来Q网络结构一样的Target Q网络,用于计算Q目标值。

那接下来,就针对性的介绍这三个方面

深度卷积神经网络:

图1.3 利用卷积神经网络逼近(Q函数)值函数

        这里值函数利用神经网络进行逼近,属于非线性逼近。(中间涉及到激活函数和归一化正则化等操作,不是简单的线性逼近)。这里的值函数对应着一组参数,在神经网络里参数是每层网络的权重,我们用  seta  表示(它里边是一组数据,而非一个数据)。用公式表示的话,值函数为:Q(s,a ; seta),注意,我们这时候对值函数进行更新时,其实更新的是参数,当网络结构确定时,seta 就代表值函数。DQN所用的网络结构是3个卷积层+两个全连接层。也就是说,我们在不断的训练求解一个Q值,实际上是在更新这个网络中的各层权重参数。用卷积神经网络来拟合任意的值函数,是区别与传统Q-learning的一个重要突破(传统Q-Learning是使用的Q表,S和A稍微一多起来,计算量会呈几何倍增长,效率太低了)

经验回放机制:

       利用神经网络逼近值函数的做法在强化学习领域早就存在了,可以追溯到上个世界90年代。那时,学者们发现利用神经网络,尤其是深度神经网络去逼近值函数这件事不太靠谱,因为常常出现不稳定不收敛的情况,所以这个方向一直没有突破,直到DeepMind出现。

        是什么原因,才使得该方法稳定且收敛下来了呢?----------他们将认识神经科学的成果应用到了深度神经网络的训练之中!(划重点,知识迁移带来的创新)

        人在睡觉的时候,海马体会把一天的记忆重放给大脑皮层。利用这个启发机制,DeepMind团队的研究人员构造了一种神经网络的训练方法:经验回放

图1.4 DQN算法的粗略工作模型 

        通过经验回放为什么可以令神经网络的训练收敛且稳定?

        原因是:对神经网络进行训练时,它收敛存在的假设是独立同分布。而通过强化学习采集到的经验之间存在着关联性,利用这些经验进行顺序训练,神经网络当然不稳定。经验回放可以打破经验数据间的关联。将采集的经验(原本是具有时间顺序关联的),存储到经验池中,随机抽取一批样本,进行训练,同时,随着采集经验数据的更新,也对池子中的样本库进行更新。这样子使用一个经验池存储多条经验s,a,r,s,再从中随机抽取一批数据送去训练。解决样本关联性和利用效率的问题。

DQN增加了目标网络:

        通过固定Q目标可以解决算法更新不平稳的问题,神经网络一般学习的是固定的目标值,而    Q-learning中Q同样为学习的变化量,变动太大不利于学习。所以DQN算法,使Q在一段时间内保持不变,使神经网络更易于学习,隔一段时间再去拷贝这个参数即可。

(1)这里也拿监督学习做对比,它输入x后输出预测的值y,目的是让预测值逼近这个真实值,这个真实值在监督学习中其实是稳定的:

 (2)而DQN输出的预测值也是要逼近Q_target的:

(3)但是这里的Q实际上也要过一遍网络,导致这个Q也是不断变化的,这就好像在练习射箭时,拿一只会动的兔子当靶子:

图1.5 DQN中的Q网络和Q-T网络

 固定Q目标做的就是让这个Q在一段时间内保持不变,隔一段时间再去拷贝这个参数即可。

强化学习的伪代码:

图1.6 DQN的伪代码程序

 先来逐行解读一下伪代码:

第1行:算法名称----带有经验回放的DQN算法。

第2行:初始化经验池以及容量N。

第3行:以一个随机权重,初始化动作-价值函数 Q。

第4行:开始一个一级循环(就是一个大循环),循环的条件是回合数,满足回合方可跳出。

第5行:初始化回合的第一个状态s1 ,预处理得到状态对应的特征输入。

第6行:从该回合的第一步出发,进行循环T步才结束。

第7行:用随机概率epsilon选择一个动作at(在代码中这里是比较变量和epsilon大小)。

第8行:否则,选择上一次的最大Q值对应的at作为本次动作。(注意,这里选最大动作时用到的值函数网络与逼近值函数所用的网络是一个网络,在代码中就是用的同一个类来实例化两个网络)上边的这两行,其实就是执行贪婪策略。

第9行:在仿真环境中执行动作at,并获取奖励rt和下一个状态xt+1(下一帧图像信息xt+1)。

第10行:更新环境状态,和权重参数等。

第11行:在经验池中存储刚才的一组数据。

第12行:从经验池中取样一个batch(这个批量的大小是可以自己设置的)

第13行:进行判断,如果还没到达最终条件,根据迭代公式计算Q值;如果到达,以最后一次奖励作为本次的Q-T网络目标值。

第14行:针对Q-Target(目标函数)和Q值函数之差的Loss,运用梯度下降策略去求解最优。一般是每隔C步更新一次TD目标网络权值。

第15行:完成一个回合之后跳出循环。

第16行:完成所有回合之后跳出循环。

图1.7 DQN算法代码实现(按照代码板块划分) 

        结合上边的伪代码流程,大概解释一下这个图的编程顺序和运行流程。先建立一个经验池,方便注入数据和取样数据。建立一个类,以这个类初始化Q和Q-T网络,定期更换Q-T的权重参数。再编写loss函数,取样函数等,别忘了还要初始化env(仿真环境)。

运行的流程就是:Q网络根据随机初始化的权重,进行动作价值预测,选择一个价值最大的进行执行,获得环境的反馈之后,将环境反馈的经验放到经验池中。(随着池子容量越多,经验回放的优势逐渐凸显),随机抽取经验样本,放入DQN网络(Q预测和Q-T网络)中进行计算,由于经验回放的样本是随机抽取的,因此,DQN计算的结果更加有效。由于这两个网络的参数权重没有实时同步,因此同样的经验样本喂入,会存在差值,将计算的结果和Q预测的结果做差,得到Loss函数。只要不断的用梯度下降策略,就能减小Loss函数,带来的是权重参数的改变,将这个更新后的参数放入Q网络,就可以实现Q的优化。

纸上得来终觉浅,还是需要多看视频去多思考。

(三)DQN代码实现

编程思路:


#先来从头看一下DQN算法的伪代码
#1.初始化一下经验池
#2.初始化动作价值Q函数的随机权重
#3.用Q*初始化目标动作价值函数,2和3中的价值函数的差值,就是loss值,衡量算法是否优秀的标准
#4.接下来是两个大的循环,第一个循环是指需要进行多少局(回合),第二个循环是指每个循环需要经历多少步
#{第一个循环:
#    初始化状态序列
#    {
#    第二个循环内容
#    1.1以随机概率epsilon选择随机a动作
#    1.2否则按照最佳Q值对应的a执行操作
#    2.1执行动作与环境进行交互,得到奖励和获得下一步状态
#    2.2进行一个状态转移,然后把学习到的经验放到经验池中
#    2.3不断从经验池中去取出经验进行运算,帮助训练,指导下一步动作
#    2.4.1游戏结束的话,就得到一个奖励
#    2.4.2游戏没有结束的话,按照TDerror 的公式 去更新Q值
#    2.5通过梯度下降,更新算法的大脑参数
#    2.6过了C步之后,将Qnetwork和Qtargetwork做一个同步。
#    }
# }
###编程代码:先大致构建出伪代码,模块化编程,逐步细化###

本程序一共只有两个文件,一个是main文件,另一个是agent文件(都是.py文件)

main.py代码

import randomimport gym
#导入游戏库
import numpy as np
import torch
import torch.nn as nnfrom agent import Agentenv = gym.make("CartPole-v1")
#创建一个游戏环境
s=env.reset()#初始化游戏,返回一组观测值,s是一个向量
n_state=len(s)#这里是输入的个数
n_action=env.action_space#这里是输出的动作对应状态EPSILON_DECAY=10000#假设可以衰减10000次EPSILON_START=1.0#探索率从1开始衰减
EPSILON_END=0.02#探索率的最低值
n_episode=5000
#假设玩5000句
n_time_step=1000
#假设每局1000步
agent=Agent(n_input=n_state,n_output=n_action)
#实例化一个对象
TARGET_UPDATE_FREQUENCY=10
#q_target和q网络的同步频率为10,即10局更新一次
REWARD_BUFFER=np.empty(shape=n_episode)for episode_i in range(n_episode):for step_i in range(n_time_step):episode_reward = 0#奖励值从0开始epsilon = np.interp(episode_i * n_time_step+step_i,[0,EPSILON_DECAY],[EPSILON_START,EPSILON_END])#现在开始初始化探索率,探索率随着时间递减,最后保持不变random_sample=random.random()#表示从0到1之间随机取一个数if random_sample<=epsilon:a=env.action_space.sample()else:a=agent.online_net.action(s)#TODOs_,r,done,info = env.step(a)agent.memeo.add_memo(s,a,r,done,s_)#TODOs=s_episode_reward+=r#每一个回合有一个累计奖励的过程if done:#判断本局是否结束s=env.reset()#如果结束,那就重置环境REWARD_BUFFER[episode_i]=episode_reward  #把这一句累加的回合奖励记录下来breakbatch_s,batch_a,batch_r,batch_done,bacth_s_=agent.memo.sample()#通过这样一个采样函数,将结果赋值给batch_S#计算targetstarget_q_values=agent.target_net(batch_s)#TODOmax_target_q_values=target_q_values.max(dim=1,keepdim=True)[0]#得到最大的target数值targets=batch_r+agent.GAMMA *(1-batch_done)*max_target_q_values#这一步就是计算公式#TODO#计算q_valuesq_values=agent.online_net(batch_s)#给Q网络输入状态S,然后#TODOa_q_values=torch.gather(input=q_values,dim=1,index=batch_a)#思考一下为什么是选择第二列,还有序号为batch_a?#这一步做的是将所有的q_value给搜集起来,按顺序把各batch与对应各自的最大值q匹配起来放到一起。#计算loss函数loss=nn.functional.smooth_l1_loss(targets,a_q_values)#这下就求出了loss#计算梯度下降,来更新神经网络中的各个参数agent.optimizer.zero_grad()#TODOloss.backward()agent.optimizer.step()#通过这几步完成一次梯度下降#TODOif episode_i % TARGET_UPDATE_FREQUENCY==0:agent.target_net.load_state_dict (agent.online_net.state_dict())#这一步将online_net网络中的参数全部同步到target_net网络之中#观测每一个回合的奖励,看看训练的咋样了print("Episode:{}".format(episode_i))print("Avg.Reward:{}".format(np.mean(REWARD_BUFFER[:episode_i])))

agent.py代码 

import random
import numpy as np
import torch
import torch.nn as nn #DQN中需要用到,torch中神经网络这个模块。
class Replaymemory():def __init__(self,n_s,n_a):self.n_s=n_sself.n_a=n_aself.MEMORY_SIZE=1000#经验池的大小self.BATCH_SIZE=64#每次批量的大小self.all_s=np.empty(shape=(self.MEMORY_SIZE,self.n_s),dtype=np.float32)#开辟一个空白空间,形状是2维的,与MEMORY_SIZE和n_s共同相关,n_s不一定清楚,且数据类型是32位的。self.all_a=np.random.randint(low=0,high=n_a,size=self.MEMORY_SIZE,dtype=np.uint8)# 这个游戏中,动作只有左和右两个状态,因此可以使用0和1代替,这里使用n_a变量,在赋值时令为2就可以。self.all_r=np.empty(self.MEMORY_SIZE,dtype=np.float32)#只是一维的存储空间,因为存储的只是每一局导出的数据。self.all_done = np.random.randint(low=0, high=2, size=self.MEMORY_SIZE, dtype=np.uint8)self.all_s_=np.empty(shape=(self.MEMORY_SIZE,self.n_s),dtype=np.float32)self.t_memo=0self.t_max=0#在这里做一个变量的标记#这一下子,上边就准备好了存储空间。后续直接进行调用就可以。#def add_memo(self,s,a,r,done,s_):#在这里对t_max进行一个判别self.all_s[self.t_memo]=sself.all_a[self.t_memo]=aself.aal_r[self.t_memo]=rself.all_done[self.t_memo]=doneself.all_s_[self.t_memo]=s_self.t_max = max(self.t_memo, self.t_memo + 1)self.t_memo=(self.t_memo+1)%self.MEMORY_SIZE#检查是否超过1000,如果是第1001,那么取余之后,会放在第一个位置def sample(self):#经验池中,大于64个经验的话,随机取64个,小于64个的话,有几个取几个。if self.t_max>self.BATCH_SIZE:idxes = random.sample(range(0,self.t_max), self.BATCH_SIZE)  # 从经验池空间的序号中,随机选择64个序号else:idxes=range(0,self.t_max)batch_s=[]batch_a=[]batch_r=[]batch_done=[]batch_s_=[]for idx in idxes:#遍历前面的随机取出的64个数据,装载到batch_s这边的5个类型数据中batch_s.append(self.all_s[idx])batch_a.append(self.all_a[idx])batch_r.append(self.all_r[idx])batch_done.append(self.all_done[idx])batch_s_.append(self.all_s_[idx])#torch识别不了numpy,所以需要把numpy转化成torch。batch_s_tensor=torch.as_tensor(np.asarray(batch_s),dtype=torch.float32)batch_a_tensor = torch.as_tensor(np.asarray(batch_a), dtype=torch.int64).unsqueeze(-1)#最后一句是升维batch_r_tensor = torch.as_tensor(np.asarray(batch_r), dtype=torch.float32).unsqueeze(-1)batch_done_tensor = torch.as_tensor(np.asarray(batch_done), dtype=torch.float32).unsqueeze(-1)batch_s__tensor = torch.as_tensor(np.asarray(batch_s_), dtype=torch.float32)return batch_s_tensor,batch_a_tensor,batch_r_tensor,batch_done_tensor,batch_s__tensorclass DQN(nn.Module):def __init__(self,n_input,n_output):super().init()self.net=nn.Sequential(nn.Linear(in_features=n_input,out_features=88)nn.Tanh()nn.Linear(in_features=88,out_features=n_output))def forward(self,x):#前向传播函数return self.net(x)def act(self,obs):#得到输出的动作obs_tensor=torch.as_tensor(obs,dtype=torch.float32)#先将数据转化成torch格式q_value = self(obs_tensor.unsqueeze(0))#把他转化成行向量max_q_idx=torch.argmax(input=q_value)#然后求他最大的q_value对应的序号action = max_q_idx.detach().item()#找到这个序号对应的actionreturn action#把这个序号对应的操作返回给主程序,执行对应操作。class Agent:def __init__(self,n_input,n_output):#在这里定义一下需要的输入和输出数量self.n_input=n_inputself.n_output=n_outputself.GAMA=0.99#这里衰减因子设置为0.99self.learning_rate=1e-3#这里设置的学习率self.memo=Replaymemory(self.n_input,self.n_output)#TODO,这里是要补充经验池self.online_net= DQN(self.n_input,self.n_output)       #TODO 这里是在线训练网络,这两步操作都叫做实例化self.target_net= DQN(self.n_input,self.n_output)       #TODO 这里是目标训练网络self.optimizer= torch.optim.Adam(self.online_net.parameters(),lr=self.learning_rate)#TODO 这里是优化器,优化这个网络参数

        这样子差不多就结束了,代码可以跑起来,不过要注意版本兼容的问题,最开始版本太新了,导致一直报错。觉得还是需要多实践,多看看代码。

        最后,在评论区奉上一个完整的DQN详细标注的完整代码文件,pycharm打开之后可以直接运行。

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

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

相关文章

飞凌嵌入式「国产」平台大盘点(二)全志系列

在上篇文章中&#xff0c;小编带大家一起盘点了飞凌嵌入式国产平台中的瑞芯微系列核心板。本篇文章&#xff0c;将继续为大家介绍的是全志系列核心板&#xff0c;话不多说&#xff0c;上干货&#xff01; 全志科技 飞凌嵌入式 飞凌嵌入式与全志科技的合作始于2019年&#xff…

AI 绘画Stable Diffusion 研究(四)sd文生图功能详解(上)

大家好&#xff0c;我是风雨无阻。 通过前面几篇AI 绘画Stable Diffusion 研究系列的介绍&#xff0c;我们完成了Stable Diffusion整合包的安装、模型ControlNet1.1 安装、模型种类介绍与安装&#xff0c;相信看过教程的朋友们&#xff0c;手上已经有可以操作实践的Stable Diff…

论文阅读-BotPercent: Estimating Twitter Bot Populations from Groups to Crowds

目录 摘要 引言 方法 数据集 BotPercent架构 实验结果 活跃用户中的Bot数量 Bot Population among Comment Sections Bot Participation in Content Moderation Votes Bot Population in Different Countries’ Politics 论文链接&#xff1a;https://arxiv.org/pdf/23…

实例030 窗口颜色的渐变

实例说明 在程序设计时&#xff0c;可以通过设置窗体的BackColor属性来改变窗口的背景颜色。但是这个属性改变后整个窗体的客户区都会变成这种颜色&#xff0c;并且非常单调。如果窗体的客户区可以向标题栏一样能够体现颜色的渐变效果&#xff0c;那么窗体风格将会另有一番风味…

2024年浙财MBA项目招生信息全面了解

2024年全国管理类硕士联考备考已经到了最火热的阶段&#xff0c;不少考生开始持续将注意力集中在备考的规划中&#xff01;杭州达立易考教育整合浙江省内的MBA项目信息&#xff0c;为大家详细梳理了相关报考参考内容&#xff0c;方便大家更好完成择校以及针对性的备考工作。本期…

京东开源的、高效的企业级表格可视化搭建解决方案:DripTable

DripTable 是京东零售推出的一款用于企业级中后台的动态列表解决方案&#xff0c;项目基于 React 和 JSON Schema&#xff0c;旨在通过简单配置快速生成页面动态列表来降低列表开发难度、提高工作效率。 DripTable 目前包含以下子项目&#xff1a;drip-table、drip-table-gene…

静态网页加速器:优化性能和交付速度的 Node.js 最佳实践

如何使用 Node.js 发布静态网页 在本文中&#xff0c;我们将介绍如何使用 Node.js 来发布静态网页。我们将创建一个简单的 Node.js 服务器&#xff0c;将 HTML 文件作为响应发送给客户端。这是一个简单而灵活的方法&#xff0c;适用于本地开发和轻量级应用。 1、创建静态网页…

Linux中提示No such file or directory解决方法

说明&#xff1a; 在linux下&#xff0c;./xxx.sh执行shell脚本时会提示No such file or directory。但shell明明存在&#xff0c;为什么就是会提示这个呢&#xff1f; 这种其实是因为编码方式不对&#xff0c;如你在win下编辑sh&#xff0c;然后直接复制到linux下面 实现&…

机器学习笔记之优化算法(九)收敛速度的简单认识

机器学习笔记之优化算法——收敛速度的简单认识 引言收敛速度的判别标准 Q \mathcal Q Q-收敛速度 R \mathcal R R-收敛速度关于算法复杂度与收敛速度 引言 本节对收敛速度简单介绍。 收敛速度的判别标准 我们之前几节介绍了线搜索方法 ( Line Search Method ) (\text{Line …

【Linux命令200例】awk文本处理工具的系统与实战讲述(常用)

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌&#xff0c;阿里云社区专家博主&#xff0c;2023年6月csdn上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f3c6;本文已…

美团基础架构面经总结汇总

美团基础架构的面经。 问的全是基础,一个编程语言的问都没有。 问题记录 MySQL-MVCC InooDB是通过 MVCC 实现可重复读的隔离级别的,MVCC 就是多版本并发控制,它其实记录了历史版本的数据,解决了读写并发冲突问题。有一个版本编码,然后它进入了各种操作下的数据状态,能…

redis数据未到过期时间被删除

1. 问题描述 使用了jeecgboot开发后端代码&#xff0c;代码设置的redis过期时间为24小时&#xff0c;部署使用的宝塔面板&#xff0c;在redis中看到的过期时间也是为24小时&#xff0c;但是并未到过期时间&#xff0c;数据就被删除。 2. 解决办法 观察了一下redis中的数据&a…

DataGrip实时模板的配置

DataGrip实时模板的配置 File→Setting→Editor→Live Templates→点击“”→Live Template 即可新写入一条模板语句 写入新模板 缩写&#xff1a;seld select $END$ from dual; 注意:一开始都是默认无应用的&#xff0c;需要手动配置应用于所有SQL 点击difine&#xff08;…

【计算机网络】NAT技术

文章目录 1. NAT技术简介2. 使用NAT技术转换IP的过程3. NAPT4. NAT技术的缺陷5. NAT和代理服务器 1. NAT技术简介 NAT&#xff08;Network Address Translation&#xff0c;网络地址转换&#xff09;技术&#xff0c;是解决IP地址不足的主要手段&#xff0c;并且能够有效避免外…

Linux下安装配置Redis

文章目录 安装依赖库上传安装包并解压 启动默认启动指定配置启动开机自启 安装 依赖库 Redis是基于C语言编写的&#xff0c;因此首先需要安装Redis所需要的gcc依赖&#xff1a; yum install -y gcc tcl上传安装包并解压 将Redis安装包上传到服务器的任意目录&#xff0c;例…

JavaScript |(三)内建对象 | 数组 | string对象 | 尚硅谷JavaScript基础实战

学习来源&#xff1a;尚硅谷JavaScript基础&实战丨JS入门到精通全套完整版 文章目录 &#x1f4da;数组&#x1f407;数组介绍⭐️数组&#xff08;Array&#xff09;⭐️基本操作⭐️数组的字面量 &#x1f407;数组中的常用方法⭐️push()⭐️pop()⭐️unshift()⭐️shif…

无人驾驶实战-第一课(自动驾驶概述)

在七月算法上报了《无人驾驶实战》课程&#xff0c;老师讲的真好。好记性不如烂笔头&#xff0c;记录一下学习内容。 课程入口&#xff0c;感兴趣的也可以跟着学一下。 ————————————————————————————————————————— 无人驾驶汽车的定义…

音频光耦合器

音频光耦合器是一种能够将电信号转换为光信号并进行传输的设备。它通常由发光二极管&#xff08;LED&#xff09;和光敏电阻&#xff08;光电二极管或光敏电阻器&#xff09;组成。 在音频光耦合器中&#xff0c;音频信号经过放大和调节后&#xff0c;被转换为电流信号&#xf…

Windows11+Opencv+Clion编译源码

Windows11OpencvClion编译源码 参考&#xff1a;https://www.robotsfan.com/posts/69395e08.html 注意事项 编译过程中使用的软件&#xff0c;开源码等所有工具的安装路径一定不要有中文和空格。cmake过程会下载一些文件&#xff0c;如果是局域网的话可能下载不下来&#xf…

Restful开发规范以及开发流程

目录 一、RestFul开发规范 二、开发流程 一、RestFul开发规范 RESTful&#xff08;Representational State Transfer&#xff09;是一种用于设计和开发网络应用程序的架构风格&#xff0c;它强调使用标准HTTP方法和状态码来进行资源的管理和交互。以下是一些常见的RESTful开发…