强化学习分享(一) 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 random

import gym
#导入游戏库
import numpy as np
import torch
import torch.nn as nn

from agent import Agent


env = 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)#TODO

        s_,r,done,info = env.step(a)
        agent.memeo.add_memo(s,a,r,done,s_)#TODO
        s=s_
        episode_reward+=r#每一个回合有一个累计奖励的过程

        if done:#判断本局是否结束
            s=env.reset()#如果结束,那就重置环境
            REWARD_BUFFER[episode_i]=episode_reward  #把这一句累加的回合奖励记录下来
            break
        batch_s,batch_a,batch_r,batch_done,bacth_s_=agent.memo.sample()#通过这样一个采样函数,将结果赋值给batch_S
        #计算targets
        target_q_values=agent.target_net(batch_s)#TODO
        max_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_values
        q_values=agent.online_net(batch_s)#给Q网络输入状态S,然后#TODO
        a_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()#TODO
        loss.backward()
        agent.optimizer.step()#通过这几步完成一次梯度下降#TODO

    if 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_s
        self.n_a=n_a
        self.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=0
        self.t_max=0#在这里做一个变量的标记
    #这一下子,上边就准备好了存储空间。后续直接进行调用就可以。#

    def add_memo(self,s,a,r,done,s_):
        #在这里对t_max进行一个判别

        self.all_s[self.t_memo]=s
        self.all_a[self.t_memo]=a
        self.aal_r[self.t_memo]=r
        self.all_done[self.t_memo]=done
        self.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__tensor


class 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()#找到这个序号对应的action
        return action#把这个序号对应的操作返回给主程序,执行对应操作。

class Agent:
    def __init__(self,n_input,n_output):#在这里定义一下需要的输入和输出数量
        self.n_input=n_input
        self.n_output=n_output

        self.GAMA=0.99#这里衰减因子设置为0.99
        self.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打开之后可以直接运行。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringCloud《Eureka、Ribbon、Feign、Hystrix、Zuul》作用简单介绍

概述 SpringCloud是一个全家桶&#xff0c;包含多个组件。 本文主要介绍几个重要组件&#xff0c;也就是Eureka、Ribbon、Feign、Hystrix、Zuul这几个组件。 一、业务场景介绍 业务流程&#xff0c;支付订单功能 订单服务改变为已支付订单服务调用库存服务&#xff0c;扣减…

中国农村程序员学习此【正则表达式进阶】发明cahtGPT,购买大平层,开上帕拉梅拉,迎娶白富美出任CEO走上人生巅峰

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录 限制可能的用户名匹配空白字符匹配非空白字符指定匹配的上限和下限只指定匹配的下限指定匹配的确切数量检查全部或无正向先行断言和负向先行断言检查混合字符组使用捕获组重用模式使用捕获组搜索和替换删除…

分类预测 | MATLAB实现WOA鲸鱼算法同步优化特征选择结合支持向量机分类预测

分类预测 | MATLAB实现WOA鲸鱼算法同步优化特征选择结合支持向量机分类预测 目录 分类预测 | MATLAB实现WOA鲸鱼算法同步优化特征选择结合支持向量机分类预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 MATLAB实现WOA鲸鱼算法同步优化特征选择结合支持向量机分类预测…

Windows 系统无法复制粘贴则么处理

Windows 系统无法复制粘贴如何恢复呢&#xff1f; 今天小编遇到一个问题&#xff0c;就是自己的电脑突然无法复制、粘贴&#xff0c;这样的问题我遇到很多次&#xff0c;重启电脑虽然能解决问题&#xff0c;但是不能一直通过重启来解决这样的问题&#xff0c;很浪费时间的。今…

Filebeat+ELK 部署

Node1节点&#xff08;2C/4G&#xff09;&#xff1a;node1/192.168.8.10 Elasticsearch Kibana Node2节点&#xff08;2C/4G&#xff09;&#xff1a;node2/192.168.8.11 Elasticsearch Apache节点&#xff1a;apache/192.168.8.13 …

2023年电赛---运动目标控制与自动追踪系统(E题)发挥题思路

前言 &#xff08;1&#xff09;因为博客编辑字数超过1W字会导致MD编辑器非常卡顿。所以我将发挥题和基础题的思路拆开了。 &#xff08;2&#xff09;更新日记&#xff1a; <1>2023年8月4日&#xff0c;9点20分。分离发挥题思路和基础题思路&#xff0c;增加了博主Huiye…

2020-2023中国高等级自动驾驶产业发展趋势研究

1.1 概念界定 2020-2023中国高等级自动驾驶产业发展趋势研究Trends in China High-level Autonomous Driving from 2020 to 2023自动驾驶发展过程中&#xff0c;中国出现了诸多专注于研发L3级以上自动驾驶的公司&#xff0c;其在业界地位也越来越重要。本报告围绕“高等级自动…

【雕爷学编程】MicroPython动手做(29)——物联网之SIoT 2

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

Java实现数据库表中的七种连接【Mysql】

Java实现数据库表中的七种连接【Mysql】 前言版权推荐Java实现数据库表中的七种连接左外连接右外连接其他连接 附录七种连接SQL测试Java测试转换方法类 Cla1类 Cla2类Cla3 最后 前言 2023-8-4 16:51:42 以下内容源自《【Mysql】》 仅供学习交流使用 版权 禁止其他平台发布时…

vue 混入(mixin)的使用

在 vue 组件内&#xff0c;如果想将一些公共功能&#xff0c;如组件、方法、钩子函数等复用&#xff0c;混入是一个很好的选择。 现在开始我们的混入使用吧 1、我们可以创建一个目录mixins&#xff0c;在创建一个comment.js文件如图&#xff1a; // 在 common.js 里写你想共享…

SQL 表别名 和 列别名

列表名 列表名之后 order by 可以用别名 也可以用原名&#xff0c; where 中不能用别名的 SQL语句执行顺序&#xff1a; from–>where–>group by -->having — >select --> order 第一步&#xff1a;from语句&#xff0c;选择要操作的表。 第二步&#xff1…

matlab编程实践18、19

浅水方程 浅水方程可以建立起海啸和浴缸中波浪的数学模型。浅水方程建立了水或者其它不可压缩液体受扰动时传播的模型。隐含的假设是&#xff0c;液体的深度和波浪的长度、扰动等相比是很小的。 在这样的记号下&#xff0c;浅水方程为双曲守恒定律的一个例子。 使用拉克斯-冯特…

ssm机动车维修站车辆维护管理系统java汽车报修备案jsp源代码mysql

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 ssm机动车维修站车辆维护管理系统 系统有2权限&#…

iOS16.0:屏幕旋转

此文写于2022年08月03日&#xff0c;距离iOS16正式版推出还有一个多月的时间&#xff0c;iOS16 beta版本有很多API的修改&#xff0c;今天讨论的是屏幕旋转&#xff0c;基于Xcode 14.0 beta4。 之前的屏幕旋转会报错&#xff1a; [Orientation] BUG IN CLIENT OF UIKIT: Settin…

MinIO

MinIO 1.MinIO安装 Minio 是个基于 Golang 编写的开源对象存储服务&#xff0c;存储非结构化数据&#xff0c;如&#xff1a;图片&#xff0c;视频&#xff0c;音乐等 官网地址&#xff1a;https://min.io/ 中文地址&#xff1a;http://minio.org.cn 官网文档&#xff08; …

Istio 安全 mTLS认证 PeerAuthentication

这里定义了访问www.ck8s.com可以使用http也可以使用https访问&#xff0c;两种方式都可以访问。 那么是否可以强制使用mtls方式去访问&#xff1f; mTLS认证 PeerAuthentication PeerAuthentication的主要作用是别人在和网格里的pod进行通信的时候&#xff0c;是否要求mTLS mTL…

SpringBoot中事务失效的原因

SpringBoot中事务失效的原因 文章目录 SpringBoot中事务失效的原因一、事务方法非public修饰二、非事务方法调用事务方法三、事务方法的异常被捕获四、事务异常类型不对五、事务传播行为不对六、没有被Spring管理6.1、暴漏代理对象6.2、使用代理对象 常见的事务失效原因包括如下…

LeetCode-26-删除有序数组中的重复项

一&#xff1a;题目描述&#xff1a; 给你一个 升序排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 考虑 nums 的唯…

【Linux】-进程概念之进程优先级(如何去进行调度以及进程切换),还不进来看看??

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树&#x1f388; &#x1f389;作者宣言&#xff1a;认真写好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee✨ &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法&#x1f384; 如 果 你 …

adb 调试oppo k11过程记录

学习使用appium工具&#xff0c;自动化测试andriod应用程序。 过程记录 背景交代 手机&#xff1a;oppo k11 系统&#xff1a; macOS 手机开启use调试 具体细节&#xff0c;可百度 安装软件 adbappiumappium-inspector adb安装 下载adb工具包platform-tools, 解压。 直…