强化学习——马尔可夫决策过程(MDP)【附 python 代码】

一、马尔可夫过程

过程介绍
随机过程在某时刻 t 的状态 S t S_t St 通常取决于 t 时刻之前的状态。状态 S t + 1 S_{t+1} St+1 的概率表示为: P ( S t + 1 ∣ S 1 , . . . , S t ) P(S_{t+1}|S_1,...,S_t) P(St+1S1,...,St)
马尔可夫过程某时刻 t 的状态只取决于上一个时刻 t-1 的状态。状态 S t + 1 S_{t+1} St+1 的概率表示为: P ( S t + 1 ∣ S t ) = P ( S t + 1 ∣ S 1 , . . . , S t ) P(S_{t+1}|S_t)=P(S_{t+1}|S_1,...,S_t) P(St+1St)=P(St+1S1,...,St)

  马尔可夫过程也被称为马尔可夫链,通常用元组 < S , P > <S,P> <S,P> 来描述,其中 S 是有限数量的状态集合,P 是状态转移矩阵。假设有 n 个状态,则 S = { s 1 , s 2 , . . . , s n } S=\{s_1,s_2,...,s_n\} S={s1,s2,...,sn} ,
P = [ P ( s 1 ∣ s 1 ) ⋯ P ( s n ∣ s 1 ) ⋮ ⋱ ⋮ P ( s 1 ∣ s n ) ⋯ P ( s n ∣ s n ) ] P=\begin{bmatrix} P(s_1|s_1) & \cdots & P(s_n|s_1) \\ \vdots & \ddots & \vdots\\ P(s_1|s_n) & \cdots & P(s_n|s_n) \end{bmatrix} P= P(s1s1)P(s1sn)P(sns1)P(snsn)
  矩阵 P 中第 i 行第 j 列元素 P ( s j ∣ s i ) = P ( S t + 1 = s j ∣ S t = s i ) P(s_j|s_i)=P(S_{t+1}=s_j|S_t=s_i) P(sjsi)=P(St+1=sjSt=si) 表示从状态 s i s_i si 转移到状态 s j s_j sj 的概率,称 P ( s ′ ∣ s ) P(s'|s) P(ss) 为状态转移函数。从某个状态出发,到达其他状态的概率和必须为 1 。即状态转移矩阵 P 的每一行和为 1 。

示例:

在这里插入图片描述

S = { S i , 1 ≤ i ≤ 6 } 状态转移矩阵 P = [ 0.9 0.1 0 0 0 0 0.5 0 0.5 0 0 0 0 0 0 0.6 0 0.4 0 0 0 0 0.3 0.7 0 0.2 0.3 0.5 0 0 0 0 0 0 0 1 ] S=\{S_i,1\le i \le 6\} \\ \; \\ 状态转移矩阵\;P= \begin{bmatrix} 0.9 & 0.1 & 0 & 0 & 0 & 0 \\ 0.5 & 0 & 0.5 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0.6 & 0 & 0.4 \\ 0 & 0 & 0 & 0 & 0.3 & 0.7 \\ 0 & 0.2 & 0.3 & 0.5 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 1 \\ \end{bmatrix} S={Si,1i6}状态转移矩阵P= 0.90.500000.10000.2000.5000.30000.600.500000.300000.40.701

二、马尔可夫奖励过程

2.1 定义

  马尔可夫奖励过程由 < S , P , r , γ > <S,P,r,\gamma> <S,P,r,γ> 构成,其中:

  • S 是有限状态的集合
  • P 是状态转移矩阵
  • r 是奖励函数, r ( s ) r(s) r(s) 是指转移到该状态时可以获得的奖励期望
  • γ \gamma γ 是折扣因子,取值范围为: [ 0 , 1 ] [0,1] [0,1] 。引入折扣因子是因为远期利益具有一定的不确定性,有时希望能尽快获得有些奖励,所以需要对远期利益打一些折扣。接近 1 则更关注长期的累积奖励,接近 0 则更关注短期奖励。

2.2 回报

  回报是指从状态 S t S_t St 开始,一直到终止状态,所有奖励的衰减之和。具体公式如下:
G t = ∑ k = 0 ∞ γ k R t + k G_t=\sum^{\infty}_{k=0}\gamma^kR_{t+k} Gt=k=0γkRt+k
  其中 R t R_t Rt 是指在时刻 t 获得的奖励。

示例:

在这里插入图片描述
【状态旁的数字表示进入该状态获得的奖励】

新建项目 MDP,项目结构如下:

在这里插入图片描述

在 MDP 目录下新建文件 MRP.py ,文件代码如下:

import numpy as npnp.random.seed(0)P = [[0.9, 0.1, 0.0, 0.0, 0.0, 0.0],[0.5, 0.0, 0.5, 0.0, 0.0, 0.0],[0.0, 0.0, 0.0, 0.6, 0.0, 0.4],[0.0, 0.0, 0.0, 0.0, 0.3, 0.7],[0.0, 0.2, 0.3, 0.5, 0.0, 0.0],[0.0, 0.0, 0.0, 0.0, 0.0, 1.0]
]
P = np.array(P)
rewards = [-1, -2, -2, 10, 1, 0]
gamma = 0.5def compute_return(start, chain, gamma):G = 0for i in reversed(range(start, len(chain))):G = gamma * G + rewards[chain[i] - 1]return Gchain = [1, 2, 3, 6]
start = 0
G = compute_return(start, chain, gamma)
print('计算回报为:%s' % G)

运行 MRP.py 文件,运行结果如下:

D:\RL\MDP\.venv\Scripts\python.exe D:\RL\MDP\MRP.py 
计算回报为:-2.5进程已结束,退出代码为 0

2.3 价值函数

  • 价值:一个状态的期望回报。【就是给回报取个别名叫价值】
  • 价值函数:输入为某个状态,输出为该状态的价值。
    • 公式为: V ( s ) = E [ G t ∣ S t = s ] V(s)=E[G_t|S_t=s] V(s)=E[GtSt=s] ,等价于 V ( s ) = E [ R t + γ V ( S t + 1 ) ∣ S t = s ] V(s)=E[R_t+\gamma V(S_{t+1})|S_t=s] V(s)=E[Rt+γV(St+1)St=s] 。【即:当前状态的价值 = 当前状态获得的奖励 + 折扣因子 ∗ * 下一个状态的价值。下一个状态不是确定的状态,而是取决于转移矩阵 P 】
    • 一方面, E [ R t ∣ S t = s ] = r ( s ) E[R_t|S_t=s]=r(s) E[RtSt=s]=r(s) ;另一方面, E [ γ V ( S t + 1 ) ∣ S t = s ] E[\gamma V(S_{t+1})|S_t=s] E[γV(St+1)St=s] 可以从状态 s 出发的转移概率得到,即: E [ γ V ( S t + 1 ) ∣ S t = s ] = γ ∑ s ′ ∈ S P ( s ′ ∣ s ) V ( s ′ ) E[\gamma V(S_{t+1})|S_t=s] = \gamma \sum_{s'\in S}P(s'|s)V(s') E[γV(St+1)St=s]=γsSP(ss)V(s)所以
      V ( s ) = r ( s ) + γ ∑ s ′ ∈ S P ( s ′ ∣ s ) V ( s ′ ) V(s)=r(s)+\gamma \sum_{s'\in S}P(s'|s)V(s') V(s)=r(s)+γsSP(ss)V(s)这就是有名的贝尔曼方程,对每一个状态都成立。
    • 马尔可夫奖励过程有 n 个状态,即 S = { s 1 , s 2 , ⋯ , s n } S=\{s_1,s_2,\cdots,s_n\} S={s1,s2,,sn} ,所有状态的价值表示成一个列向量 V = [ V ( s 1 ) , V ( s 2 ) , ⋯ , V ( s n ) ] T V=[V(s_1),V(s_2),\cdots,V(s_n)]^T V=[V(s1),V(s2),,V(sn)]T ,同理,奖励函数列向量 R = [ r ( s 1 ) , r ( s 2 ) , ⋯ , r ( s n ) ] T R=[r(s_1),r(s_2),\cdots,r(s_n)]^T R=[r(s1),r(s2),,r(sn)]T
      于是,贝尔曼方程写成矩阵的形式: V = R + γ P V V=R+\gamma PV V=R+γPV ,解得: V = ( E − γ P ) − 1 R V=(E-\gamma P)^{-1}R V=(EγP)1R ,其中 E 是单位矩阵。
      【解析解复杂度为 O ( n 3 ) O(n^3) O(n3) ,只适合求解小规模的马尔可夫奖励过程,大规模的可以使用 DP算法,蒙特卡洛方法,时序差分算法】

示例:

修改文件 MRP.py ,文件代码如下:

import numpy as npnp.random.seed(0)P = [[0.9, 0.1, 0.0, 0.0, 0.0, 0.0],[0.5, 0.0, 0.5, 0.0, 0.0, 0.0],[0.0, 0.0, 0.0, 0.6, 0.0, 0.4],[0.0, 0.0, 0.0, 0.0, 0.3, 0.7],[0.0, 0.2, 0.3, 0.5, 0.0, 0.0],[0.0, 0.0, 0.0, 0.0, 0.0, 1.0]
]
P = np.array(P)
rewards = [-1, -2, -2, 10, 1, 0]
gamma = 0.5def compute_return(start, chain, gamma):G = 0for i in reversed(range(start, len(chain))):G = gamma * G + rewards[chain[i] - 1]return Gdef compute(P, rewards, gamma, states_num):rewards = np.array(rewards).reshape((-1, 1))  # 将 rewards 改写为列向量value = np.dot(np.linalg.inv(np.eye(states_num, states_num) - gamma * P), rewards)return valuedef compute_return_sample():chain = [1, 2, 3, 6]start = 0G = compute_return(start, chain, gamma)print('计算回报为:%s' % G)def compute_value_sample():V = compute(P, rewards, gamma, 6)print("马尔可夫奖励过程中每个状态的价值分别为:\n", V)compute_value_sample()

运行结果如下:

D:\RL\MDP\.venv\Scripts\python.exe D:\RL\MDP\MRP.py 
马尔可夫奖励过程中每个状态的价值分别为:[[-2.01950168][-2.21451846][ 1.16142785][10.53809283][ 3.58728554][ 0.        ]]进程已结束,退出代码为 0

三、马尔可夫决策过程

  在马尔可夫奖励过程加上动作,就得到了马尔可夫决策过程(MDP)。MDP 由元组 < S , A , P , r , γ > <S,A,P,r,\gamma> <S,A,P,r,γ> 构成,其中:

  • S 是状态的集合
  • A 是动作的集合
  • γ \gamma γ 是折扣因子
  • r ( s , a ) r(s,a) r(s,a) 是奖励函数,此时奖励同时取决于状态 s 和动作 a
  • P ( s ′ ∣ s , a ) P(s'|s,a) P(ss,a) 是状态转移函数,表示在状态 s 执行动作 a 之后到达状态 s ′ s' s 的概率

3.1 基本概念

  • 策略 π ( a ∣ s ) = P ( A t = a ∣ S t = s ) \pi(a|s)=P(A_t=a|S_t=s) π(as)=P(At=aSt=s) ,表示在输入状态为 s 的情况下采取动作 a 的概率。
    • 确定性策略:在每个状态下只输出一个确定性的动作,即只有该动作的概率为 1 ,其他动作的概率为 0 。
    • 随机性策略:在每个状态下输出的是关于动作的概率分布,根据该分布采样得到一个动作。
  • 状态价值函数 V π ( s ) = E π [ G t ∣ S t = s ] V^{\pi}(s)=E_{\pi}[G_t|S_t=s] Vπ(s)=Eπ[GtSt=s] ,表示从状态 s 出发遵循策略 π \pi π 能获得的期望回报
  • 动作价值函数 Q π ( s , a ) = E π [ G t ∣ S t = s , A t = a ] Q^{\pi}(s,a)=E_{\pi}[G_t|S_t=s,A_t=a] Qπ(s,a)=Eπ[GtSt=s,At=a] ,表示遵循策略 π \pi π 对当前状态 s 执行动作 a 得到的期望回报。
  • 关系:
    • 状态 s 的价值等于在该状态下基于策略 π \pi π 采取所有动作的概率与相应价值的乘积和,即: V π ( s ) = ∑ a ∈ A π ( a ∣ s ) Q π ( s , a ) V^{\pi}(s)=\sum_{a\in A}\pi(a|s)\;Q^{\pi}(s,a) Vπ(s)=aAπ(as)Qπ(s,a)
    • 在状态 s 下采取动作 a 的价值等于即时奖励加上经过衰减的所有可能的下一个状态的转移概率与相应价值的乘积,即: Q π ( s , a ) = r ( s , a ) + γ ∑ s ′ ∈ S P ( s ′ ∣ s , a ) V π ( s ′ ) Q^{\pi}(s,a)=r(s,a)+\gamma \sum_{s'\in S}P(s'|s,a)\;V^{\pi}(s') Qπ(s,a)=r(s,a)+γsSP(ss,a)Vπ(s)

3.2 贝尔曼期望方程

  • 状态价值函数:
    V π ( s ) = ∑ a ∈ A π ( a ∣ s ) Q π ( s , a ) = ∑ a ∈ A π ( a ∣ s ) [ r ( s , a ) + γ ∑ s ′ ∈ S P ( s ′ ∣ s , a ) V π ( s ′ ) ] \begin{align*} V^{\pi}(s)&=\sum_{a\in A}\pi(a|s)\;Q^{\pi}(s,a) \\ & =\sum_{a\in A}\pi(a|s)[r(s,a)+\gamma \sum_{s'\in S}P(s'|s,a)\;V^{\pi}(s')] \end{align*} Vπ(s)=aAπ(as)Qπ(s,a)=aAπ(as)[r(s,a)+γsSP(ss,a)Vπ(s)]
  • 动作价值函数:
    Q π ( s , a ) = r ( s , a ) + γ ∑ s ′ ∈ S P ( s ′ ∣ s , a ) V π ( s ′ ) = r ( s , a ) + γ ∑ s ′ ∈ S P ( s ′ ∣ s , a ) ∑ a ∈ A π ( a ′ ∣ s ′ ) Q π ( s ′ , a ′ ) \begin{align*} Q^{\pi}(s,a) & =r(s,a)+\gamma \sum_{s'\in S}P(s'|s,a)\;V^{\pi}(s')\\ & =r(s,a)+\gamma \sum_{s'\in S}P(s'|s,a)\;\sum_{a\in A}\pi(a'|s')\;Q^{\pi}(s',a') \end{align*} Qπ(s,a)=r(s,a)+γsSP(ss,a)Vπ(s)=r(s,a)+γsSP(ss,a)aAπ(as)Qπ(s,a)

V π ( s ) V^{\pi}(s) Vπ(s) Q π ( s , a ) Q^{\pi}(s,a) Qπ(s,a) 互相代入就可以得到】

示例:

在这里插入图片描述
【白色圆圈表示状态,蓝色圆圈表示动作,动作旁边的数字表示奖励,虚线旁边没有数字表示概率为1】

在 MDP 目录下新建 MDP.py 文件,文件代码如下:

S = ['S1', 'S2', 'S3', 'S4', 'S5']A = ['保持S1', '前往S1', '前往S2', '前往S3', '前往S4', '前往S5', '概率前往']P = {'S1-保持S1-S1': 1.0, 'S1-前往S2-S2': 1.0,'S2-前往S1-S1': 1.0, 'S2-前往S3-S3': 1.0,'S3-前往S4-S4': 1.0, 'S3-前往S5-S5': 1.0,'S4-前往S5-S5': 1.0, 'S4-概率前往-S2': 0.2, 'S4-概率前往-S3': 0.4, 'S4-概率前往-S4': 0.4}R = {'S1-保持S1': -1, 'S1-前往S2': 0,'S2-前往S1': -1, 'S2-前往S3': -2,'S3-前往S4': -2, 'S3-前往S5': 0,'S4-前往S5': 10, 'S4-概率前往': 1}gamma = 0.5MDP = (S, A, P, R, gamma)# 策略 1 :随机策略
Pi_1 = {'S1-保持S1': 0.5, 'S1-前往S2': 0.5,'S2-前往S1': 0.5, 'S2-前往S3': 0.5,'S3-前往S4': 0.5, 'S3-前往S5': 0.5,'S4-前往S5': 0.5, 'S4-概率前往': 0.5}# 策略 2 :给定策略
Pi_2 = {'S1-保持S1': 0.6, 'S1-前往S2': 0.4,'S2-前往S1': 0.3, 'S2-前往S3': 0.7,'S3-前往S4': 0.5, 'S3-前往S5': 0.5,'S4-前往S5': 0.1, 'S4-概率前往': 0.9}def join(s1, s2):return s1 + '-' + s2

将 MDP 转化为 MRP ,公式如下:

  1. r ′ ( s ) = ∑ a ∈ A π ( a ∣ s ) r ( s , a ) r'(s)=\sum_{a\in A}\pi (a|s)r(s,a) r(s)=aAπ(as)r(s,a)
  2. p ′ ( s ′ ∣ s ) = ∑ a ∈ A π ( a ∣ s ) P ( s ′ ∣ s , a ) p'(s'|s)=\sum_{a\in A}\pi (a|s)P(s'|s,a) p(ss)=aAπ(as)P(ss,a)

示例:

修改 MDP.py 文件,文件代码如下:

import numpy
import numpy as npS = ['S1', 'S2', 'S3', 'S4', 'S5']A = ['保持S1', '前往S1', '前往S2', '前往S3', '前往S4', '前往S5', '概率前往']P = {'S1-保持S1-S1': 1.0, 'S1-前往S2-S2': 1.0,'S2-前往S1-S1': 1.0, 'S2-前往S3-S3': 1.0,'S3-前往S4-S4': 1.0, 'S3-前往S5-S5': 1.0,'S4-前往S5-S5': 1.0, 'S4-概率前往-S2': 0.2, 'S4-概率前往-S3': 0.4, 'S4-概率前往-S4': 0.4}R = {'S1-保持S1': -1, 'S1-前往S2': 0,'S2-前往S1': -1, 'S2-前往S3': -2,'S3-前往S4': -2, 'S3-前往S5': 0,'S4-前往S5': 10, 'S4-概率前往': 1}gamma = 0.5MDP = (S, A, P, R, gamma)# 策略 1 :随机策略
Pi_1 = {'S1-保持S1': 0.5, 'S1-前往S2': 0.5,'S2-前往S1': 0.5, 'S2-前往S3': 0.5,'S3-前往S4': 0.5, 'S3-前往S5': 0.5,'S4-前往S5': 0.5, 'S4-概率前往': 0.5}# 策略 2 :给定策略
Pi_2 = {'S1-保持S1': 0.6, 'S1-前往S2': 0.4,'S2-前往S1': 0.3, 'S2-前往S3': 0.7,'S3-前往S4': 0.5, 'S3-前往S5': 0.5,'S4-前往S5': 0.1, 'S4-概率前往': 0.9}def join(s1, s2):return s1 + '-' + s2# 从字典中取出对应状态的参数数组
# 例如:从策略1中取出状态 S1 的动作概率分布,即[0.5,0.5]
def get_state_parameter(x, s):p = []for i in x.keys():if i.split('-')[0] == s:p.append(x[i])return p# 计算a,b两个向量的内积
def compute_sum(a, b):a = np.array(a)b = np.array(b)return np.inner(a, b)# 将 MDP 的奖励函数转为 MRP 的奖励函数
# 思想:对于每个状态,根据策略将所有动作的概率进行加权,得到的奖励和就可以被认为是在 MRP 中该状态的奖励
def R_MDP_to_MRP(R, Pi, S):MRP_R = []for i in range(len(S)):MRP_R.append(compute_sum(get_state_parameter(Pi, S[i]), get_state_parameter(R, S[i])))return MRP_R# 计算 MDP 的转移概率
def compute_P(P, Pi):for i in P.keys():P[i] *= Pi[join(i.split('-')[0], i.split('-')[1])]return P# 根据转移概率创建 MRP 的转移矩阵
def set_MRP_P(P, S):MRP_P = np.zeros((len(S), len(S)))for i in P.keys():start_index = S.index(i.split('-')[0])end_index = S.index(i.split('-')[2])MRP_P[start_index][end_index] = P[i]MRP_P[len(S) - 1][len(S) - 1] = 1.0  # 终止状态设置转移概率return MRP_P# 将 MDP 的转移函数转为 MRP 的转移矩阵
# 思想:对于每个状态转移到其他状态,计算策略的转移概率与状态转移函数的转移概率乘积和作为 MRP 的转移概率
def P_MDP_to_MRP(P, Pi, S):return set_MRP_P(compute_P(P, Pi), S)

使用 MRP 方法计算每个状态的价值:

示例:

新建 Main.py 文件,文件代码如下:

from MDP import *
from MRP import computeR=R_MDP_to_MRP(R, Pi_1, S)
P=P_MDP_to_MRP(P, Pi_1, S)print('使用策略 1,将 MDP 转化为 MRP')
print('转化后的 MRP 奖励函数:\n', R)
print('\n转化后的 MRP 状态转移矩阵:\n', P)V=compute(P,R,gamma,len(S))
print("\nMDP 中每个状态价值分别为\n",V)

运行 Main.py 文件,运行结果如下:

D:\RL\MDP\.venv\Scripts\python.exe D:\RL\MDP\Main.py 
使用策略 1,将 MDP 转化为 MRP
转化后的 MRP 奖励函数:[np.float64(-0.5), np.float64(-1.5), np.float64(-1.0), np.float64(5.5), np.float64(0.0)]转化后的 MRP 状态转移矩阵:[[0.5 0.5 0.  0.  0. ][0.5 0.  0.5 0.  0. ][0.  0.  0.  0.5 0.5][0.  0.1 0.2 0.2 0.5][0.  0.  0.  0.  1. ]]MDP 中每个状态价值分别为[[-1.22555411][-1.67666232][ 0.51890482][ 6.0756193 ][ 0.        ]]进程已结束,退出代码为 0

计算在状态 S 下采取动作 A 的价值

修改 MDP.py 文件,文件代码如下:

import numpy
import numpy as npS = ['S1', 'S2', 'S3', 'S4', 'S5']A = ['保持S1', '前往S1', '前往S2', '前往S3', '前往S4', '前往S5', '概率前往']P = {'S1-保持S1-S1': 1.0, 'S1-前往S2-S2': 1.0,'S2-前往S1-S1': 1.0, 'S2-前往S3-S3': 1.0,'S3-前往S4-S4': 1.0, 'S3-前往S5-S5': 1.0,'S4-前往S5-S5': 1.0, 'S4-概率前往-S2': 0.2, 'S4-概率前往-S3': 0.4, 'S4-概率前往-S4': 0.4}R = {'S1-保持S1': -1, 'S1-前往S2': 0,'S2-前往S1': -1, 'S2-前往S3': -2,'S3-前往S4': -2, 'S3-前往S5': 0,'S4-前往S5': 10, 'S4-概率前往': 1}gamma = 0.5MDP = (S, A, P, R, gamma)# 策略 1 :随机策略
Pi_1 = {'S1-保持S1': 0.5, 'S1-前往S2': 0.5,'S2-前往S1': 0.5, 'S2-前往S3': 0.5,'S3-前往S4': 0.5, 'S3-前往S5': 0.5,'S4-前往S5': 0.5, 'S4-概率前往': 0.5}# 策略 2 :给定策略
Pi_2 = {'S1-保持S1': 0.6, 'S1-前往S2': 0.4,'S2-前往S1': 0.3, 'S2-前往S3': 0.7,'S3-前往S4': 0.5, 'S3-前往S5': 0.5,'S4-前往S5': 0.1, 'S4-概率前往': 0.9}def join(s1, s2):return s1 + '-' + s2# 从字典中取出对应状态的参数数组
# 例如:从策略1中取出状态 S1 的动作概率分布,即[0.5,0.5]
def get_state_parameter(x, s):p = []for i in x.keys():if i.split('-')[0] == s:p.append(x[i])return p# 计算a,b两个向量的内积
def compute_sum(a, b):a = np.array(a)b = np.array(b)return np.inner(a, b)# 将 MDP 的奖励函数转为 MRP 的奖励函数
# 思想:对于每个状态,根据策略将所有动作的概率进行加权,得到的奖励和就可以被认为是在 MRP 中该状态的奖励
def R_MDP_to_MRP(R, Pi, S):MRP_R = []for i in range(len(S)):MRP_R.append(compute_sum(get_state_parameter(Pi, S[i]), get_state_parameter(R, S[i])))return MRP_R# 计算 MDP 的转移概率
def compute_P(P, Pi):P1 = P.copy()for i in P1.keys():P1[i] *= Pi[join(i.split('-')[0], i.split('-')[1])]return P1# 根据转移概率创建 MRP 的转移矩阵
def set_MRP_P(P, S):MRP_P = np.zeros((len(S), len(S)))for i in P.keys():start_index = S.index(i.split('-')[0])end_index = S.index(i.split('-')[2])MRP_P[start_index][end_index] = P[i]MRP_P[len(S) - 1][len(S) - 1] = 1.0  # 终止状态设置转移概率return MRP_P# 将 MDP 的转移函数转为 MRP 的转移矩阵
# 思想:对于每个状态转移到其他状态,计算策略的转移概率与状态转移函数的转移概率乘积和作为 MRP 的转移概率
def P_MDP_to_MRP(P, Pi, S):return set_MRP_P(compute_P(P, Pi), S)# MDP = (0--S, 1--A, 2--P, 3--R, 4--gamma)
# 计算在状态 S 下采取动作 A 的价值 Q 
def compute_Q(s, a, MDP, V):r = MDP[3][join(s, a)]sum_PV = 0for i in range(len(MDP[0])):p = join(join(s, a), MDP[0][i])if p in MDP[2].keys():sum_PV += MDP[2][p] * V[i]return r + MDP[4] * sum_PV

修改 Main.py 文件,文件代码如下:

from MDP import *
from MRP import computeR = R_MDP_to_MRP(R, Pi_1, S)
P = P_MDP_to_MRP(P, Pi_1, S)print('使用策略 1,将 MDP 转化为 MRP')
print('转化后的 MRP 奖励函数:\n', R)
print('\n转化后的 MRP 状态转移矩阵:\n', P)V = compute(P, R, gamma, len(S))
print("\nMDP 中每个状态价值分别为\n", V)print("\n在状态为 S4 时采取动作 概率前往 的价值为:", compute_Q(S[3], A[6], MDP, V))

运行 Main.py 文件,运行结果如下:

D:\RL\MDP\.venv\Scripts\python.exe D:\RL\MDP\Main.py 
使用策略 1,将 MDP 转化为 MRP
转化后的 MRP 奖励函数:[np.float64(-0.5), np.float64(-1.5), np.float64(-1.0), np.float64(5.5), np.float64(0.0)]转化后的 MRP 状态转移矩阵:[[0.5 0.5 0.  0.  0. ][0.5 0.  0.5 0.  0. ][0.  0.  0.  0.5 0.5][0.  0.1 0.2 0.2 0.5][0.  0.  0.  0.  1. ]]MDP 中每个状态价值分别为[[-1.22555411][-1.67666232][ 0.51890482][ 6.0756193 ][ 0.        ]]在状态为 S4 时采取动作 概率前往 的价值为: [2.15123859]进程已结束,退出代码为 0

   MRP 解析解的方法在状态动作集合比较大的时候比较不适用。

四、蒙特卡洛方法

用蒙特卡洛方法估计 MDP 中的状态价值

  用策略在 MDP 上采样很多序列,计算从这个状态出发的回报再求其期望即可,公式如下: V π ( s ) = E π [ G t ∣ S t = s ] ≈ 1 N ∑ i = 1 N G t ( i ) V^{\pi}(s)=E_{\pi}[G_t|S_t=s]\approx\frac1N\sum^N_{i=1}G^{(i)}_t Vπ(s)=Eπ[GtSt=s]N1i=1NGt(i)

具体过程如下:

  1. 使用策略 π \pi π 采样若干条序列:
    s 0 ( i ) ⟶ a 0 ( i ) r 0 ( i ) , s 1 ( i ) ⟶ a 1 ( i ) r 1 ( i ) , ⋯ , s T − 1 ( i ) ⟶ a T − 1 ( i ) r T − 1 ( i ) , s T ( i ) s^{(i)}_0\stackrel{a^{(i)}_0}{\longrightarrow}r^{(i)}_0,\;s^{(i)}_1\stackrel{a^{(i)}_1}{\longrightarrow}r^{(i)}_1,\;\cdots,\;s^{(i)}_{T-1}\stackrel{a^{(i)}_{T-1}}{\longrightarrow}r^{(i)}_{T-1},\;s^{(i)}_{T} s0(i)a0(i)r0(i),s1(i)a1(i)r1(i),,sT1(i)aT1(i)rT1(i),sT(i)
  2. 对每一条序列中的每一时间步 t 的状态 s 进行以下操作:
    • 更新状态 s 的计数器 N ( s ) + = 1 N(s)+=1 N(s)+=1
    • 更新状态 s 的总回报 M ( s ) + = G M(s)+=G M(s)+=G
  3. 每一个状态的价值被估计为回报的期望 V ( s ) = M ( s ) / N ( s ) V(s)=M(s)/N(s) V(s)=M(s)/N(s)
    实际中,采用增量式更新会更好,即:
    • N ( s ) + = 1 N(s)+=1 N(s)+=1
    • V ( s ) + = 1 N ( s ) ( G − V ( s ) ) V(s)+=\frac{1}{N(s)}(G-V(s)) V(s)+=N(s)1(GV(s))

示例:

新建 MonteCarlo.py 文件,文件代码如下:

import numpy as npfrom MDP import joinclass MonteCarlo:# 采样序列@staticmethoddef sample(MDP, Pi, timestep_max, number):S, A, P, R, gamma = MDPepisodes = []for _ in range(number):episode = []timestep = 0s = S[np.random.randint(len(S) - 1)]  # 随机选择除一个终止状态外的状态作为起点# 一次采样 【到达终止状态或者到达最大时间步】while s != S[len(S) - 1] and timestep <= timestep_max:timestep += 1rand, temp = np.random.rand(), 0# 在状态 s 下根据策略选择动作for a_opt in A:temp += Pi.get(join(s, a_opt), 0)if temp > rand:a = a_optr = R.get(join(s, a), 0)breakrand, temp = np.random.rand(), 0# 根据状态转移函数得到下一个状态for s_opt in S:temp += P.get(join(join(s, a), s_opt), 0)if temp > rand:s_next = s_optbreakepisode.append((s, a, r, s_next))s = s_nextepisodes.append(episode)return episodes# 计算价值@staticmethoddef compute(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 * GN[s] += 1V[s] += (G - V[s]) / N[s]

修改 Main.py 文件,文件代码如下:

from MDP import *
from MRP import compute
from MonteCarlo import MonteCarloS = ['S1', 'S2', 'S3', 'S4', 'S5']A = ['保持S1', '前往S1', '前往S2', '前往S3', '前往S4', '前往S5', '概率前往']P = {'S1-保持S1-S1': 1.0, 'S1-前往S2-S2': 1.0,'S2-前往S1-S1': 1.0, 'S2-前往S3-S3': 1.0,'S3-前往S4-S4': 1.0, 'S3-前往S5-S5': 1.0,'S4-前往S5-S5': 1.0, 'S4-概率前往-S2': 0.2, 'S4-概率前往-S3': 0.4, 'S4-概率前往-S4': 0.4}R = {'S1-保持S1': -1, 'S1-前往S2': 0,'S2-前往S1': -1, 'S2-前往S3': -2,'S3-前往S4': -2, 'S3-前往S5': 0,'S4-前往S5': 10, 'S4-概率前往': 1}gamma = 0.5MDP = (S, A, P, R, gamma)# 策略 1 :随机策略
Pi_1 = {'S1-保持S1': 0.5, 'S1-前往S2': 0.5,'S2-前往S1': 0.5, 'S2-前往S3': 0.5,'S3-前往S4': 0.5, 'S3-前往S5': 0.5,'S4-前往S5': 0.5, 'S4-概率前往': 0.5}# 策略 2 :给定策略
Pi_2 = {'S1-保持S1': 0.6, 'S1-前往S2': 0.4,'S2-前往S1': 0.3, 'S2-前往S3': 0.7,'S3-前往S4': 0.5, 'S3-前往S5': 0.5,'S4-前往S5': 0.1, 'S4-概率前往': 0.9}def MDP_to_MRP():(S, A, P, R, gamma) = MDPR = R_MDP_to_MRP(R, Pi_1, S)P = P_MDP_to_MRP(P, Pi_1, S)print('使用策略 1,将 MDP 转化为 MRP')print('转化后的 MRP 奖励函数:\n', R)print('\n转化后的 MRP 状态转移矩阵:\n', P)V = compute(P, R, gamma, len(S))print("\nMDP 中每个状态价值分别为\n", V)print("\n在状态为 S4 时采取动作 概率前往 的价值为:", compute_Q(S[3], A[6], MDP, V))# MDP_to_MRP(MDP, Pi_1)def MC():timestep_max = 20num = 1000V = {'S1': 0, 'S2': 0, 'S3': 0, 'S4': 0, 'S5': 0}N = {'S1': 0, 'S2': 0, 'S3': 0, 'S4': 0, 'S5': 0}episodes = MonteCarlo.sample(MDP, Pi_1, timestep_max, num)print("采样前 5 条序列为:\n")for i in range(5):if i >= len(episodes):breakprint("序列 %d:%s" % (i + 1, episodes[i]))MonteCarlo.compute(episodes, V, N, gamma)print('\n使用蒙特卡洛方法计算 MDP 的状态价值为\n', V)MC()

运行 Main.py 文件,运行结果如下:

D:\RL\MDP\.venv\Scripts\python.exe D:\RL\MDP\Main.py 
采样前 5 条序列为:序列 1[('S1', '前往S2', 0, 'S2'), ('S2', '前往S3', -2, 'S3'), ('S3', '前往S5', 0, 'S5')]
序列 2[('S4', '概率前往', 1, 'S4'), ('S4', '前往S5', 10, 'S5')]
序列 3[('S4', '前往S5', 10, 'S5')]
序列 4[('S2', '前往S1', -1, 'S1'), ('S1', '保持S1', -1, 'S1'), ('S1', '前往S2', 0, 'S2'), ('S2', '前往S3', -2, 'S3'), ('S3', '前往S4', -2, 'S4'), ('S4', '前往S5', 10, 'S5')]
序列 5[('S2', '前往S3', -2, 'S3'), ('S3', '前往S4', -2, 'S4'), ('S4', '前往S5', 10, 'S5')]使用蒙特卡洛方法计算 MDP 的状态价值为{'S1': -1.2250047295780488, 'S2': -1.6935084269467038, 'S3': 0.48519918492538405, 'S4': 5.97601313133763, 'S5': 0}进程已结束,退出代码为 0

五、占用度量

  定义 MDP 的初始状态分布为 v 0 ( s ) v_0(s) v0(s) ,用 P t π ( s ) P^{\pi}_t(s) Ptπ(s) 表示采取策略 π \pi π 使得智能体在 t 时刻状态为 s 的概率,所以 P 0 π ( s ) = V 0 ( s ) P^{\pi}_0(s)=V_0(s) P0π(s)=V0(s),然后定义一个策略的状态访问分布

V π ( s ) = ( 1 − γ ) ∑ t = 0 ∞ γ t P t π ( s ) V^{\pi}(s)=(1-\gamma)\sum^{\infty}_{t=0}\gamma^tP^{\pi}_t(s) Vπ(s)=(1γ)t=0γtPtπ(s)

  其中, 1 − γ 1-\gamma 1γ 是归一化因子。状态访问概率表示在一个策略下智能体和 MDP 交互会访问到的状态的分布。其有如下性质:
V π ( s ′ ) = ( 1 − γ ) V 0 ( s ′ ) + γ ∫ P ( s ′ ∣ s , a ) π ( a ∣ s ) V π ( s ) d s d a V^{\pi}(s')=(1-\gamma)V_0(s')+\gamma \int P(s'|s,a)\pi(a|s)V^{\pi}(s)dsda Vπ(s)=(1γ)V0(s)+γP(ss,a)π(as)Vπ(s)dsda

【解释说明: ∑ t = 0 ∞ γ t 是等比数列求和,即: ∑ t = 0 ∞ γ t = lim ⁡ n → ∞ 1 − r n 1 − r = 1 1 − r ,所以 ( 1 − γ ) ∑ t = 0 ∞ γ t = 1 \sum^{\infty}_{t=0}\gamma^t\;\;是等比数列求和,即:\sum^{\infty}_{t=0}\gamma^t=\lim_{n\rightarrow\infty}\frac{1-r^n}{1-r}=\frac{1}{1-r},所以(1-\gamma)\sum^{\infty}_{t=0}\gamma^t=1 t=0γt是等比数列求和,即:t=0γt=nlim1r1rn=1r1,所以(1γ)t=0γt=1
注:这只是解释说明为什么 1 − γ 1-\gamma 1γ 是归一化因子,不可认为
V π ( s ) = ( 1 − γ ) ∑ t = 0 ∞ γ t P t π ( s ) = 1 ⋅ P t π ( s ) = P t π ( s ) V^{\pi}(s)=(1-\gamma)\sum^{\infty}_{t=0}\gamma^tP^{\pi}_t(s)=1\cdot P^{\pi}_t(s)=P^{\pi}_t(s) Vπ(s)=(1γ)t=0γtPtπ(s)=1Ptπ(s)=Ptπ(s)

  此外,定义策略的占用度量为:

ρ π ( s , a ) = V π ( s ) π ( a ∣ s ) = ( 1 − γ ) ∑ t = 0 ∞ γ t P t π ( s ) π ( a ∣ s ) \begin{align*} \rho^{\pi}(s,a) & =V^{\pi}(s)\pi(a|s) \\ & =(1-\gamma)\sum^{\infty}_{t=0}\gamma^tP^{\pi}_t(s)\pi(a|s) \end{align*} ρπ(s,a)=Vπ(s)π(as)=(1γ)t=0γtPtπ(s)π(as)

  它表示状态动作对 ( s , a ) (s,a) (s,a) 被访问到的概率。它有两个定理:

  1. ρ π 1 = ρ π 2 ⇔ π 1 = π 2 \rho^{\pi_1}=\rho^{\pi_2}\Leftrightarrow \pi_1=\pi_2 ρπ1=ρπ2π1=π2
  2. 给定合法的占用度量 ρ \rho ρ ,可生成该占用度量的唯一策略 π ρ \pi_{\rho} πρ
    π ρ = ρ ( s , a ) ∑ a ′ ρ ( s , a ′ ) \pi_{\rho}=\frac{\rho(s,a)}{\sum_{a'}\rho(s,a')} πρ=aρ(s,a)ρ(s,a)

示例:

MDP.py 文件新增函数 occupancy:

# 计算状态动作(s, a)出现的频率,以此估计策略的占用度量
def occupancy(episodes, s, a, timestep_max, gamma):rho = 0total_times = np.zeros(timestep_max)occur_times = np.zeros(timestep_max)for episode in episodes:for i in range(len(episode)):(s_opt, a_opt, r, s_next) = episode[i]total_times[i] += 1if s == s_opt and a == a_opt:occur_times[i] += 1for i in reversed(range(timestep_max)):if total_times[i]:rho += gamma ** i * occur_times[i] / total_times[i]return (1 - gamma) * rho

修改 Main.py 文件,文件代码如下:

from MDP import *
from MRP import compute
from MonteCarlo import MonteCarloS = ['S1', 'S2', 'S3', 'S4', 'S5']A = ['保持S1', '前往S1', '前往S2', '前往S3', '前往S4', '前往S5', '概率前往']P = {'S1-保持S1-S1': 1.0, 'S1-前往S2-S2': 1.0,'S2-前往S1-S1': 1.0, 'S2-前往S3-S3': 1.0,'S3-前往S4-S4': 1.0, 'S3-前往S5-S5': 1.0,'S4-前往S5-S5': 1.0, 'S4-概率前往-S2': 0.2, 'S4-概率前往-S3': 0.4, 'S4-概率前往-S4': 0.4}R = {'S1-保持S1': -1, 'S1-前往S2': 0,'S2-前往S1': -1, 'S2-前往S3': -2,'S3-前往S4': -2, 'S3-前往S5': 0,'S4-前往S5': 10, 'S4-概率前往': 1}gamma = 0.5MDP = (S, A, P, R, gamma)# 策略 1 :随机策略
Pi_1 = {'S1-保持S1': 0.5, 'S1-前往S2': 0.5,'S2-前往S1': 0.5, 'S2-前往S3': 0.5,'S3-前往S4': 0.5, 'S3-前往S5': 0.5,'S4-前往S5': 0.5, 'S4-概率前往': 0.5}# 策略 2 :给定策略
Pi_2 = {'S1-保持S1': 0.6, 'S1-前往S2': 0.4,'S2-前往S1': 0.3, 'S2-前往S3': 0.7,'S3-前往S4': 0.5, 'S3-前往S5': 0.5,'S4-前往S5': 0.1, 'S4-概率前往': 0.9}def MDP_to_MRP():(S, A, P, R, gamma) = MDPR = R_MDP_to_MRP(R, Pi_1, S)P = P_MDP_to_MRP(P, Pi_1, S)print('使用策略 1,将 MDP 转化为 MRP')print('转化后的 MRP 奖励函数:\n', R)print('\n转化后的 MRP 状态转移矩阵:\n', P)V = compute(P, R, gamma, len(S))print("\nMDP 中每个状态价值分别为\n", V)print("\n在状态为 S4 时采取动作 概率前往 的价值为:", compute_Q(S[3], A[6], MDP, V))# MDP_to_MRP(MDP, Pi_1)def MC():timestep_max = 20num = 1000V = {'S1': 0, 'S2': 0, 'S3': 0, 'S4': 0, 'S5': 0}N = {'S1': 0, 'S2': 0, 'S3': 0, 'S4': 0, 'S5': 0}episodes = MonteCarlo.sample(MDP, Pi_1, timestep_max, num)print("采样前 5 条序列为:\n")for i in range(5):if i >= len(episodes):breakprint("序列 %d:%s" % (i + 1, episodes[i]))MonteCarlo.compute(episodes, V, N, gamma)print('\n使用蒙特卡洛方法计算 MDP 的状态价值为\n', V)# MC()def occupancy_instance():gamma = 0.5timestep_max = 1000num = 1000s = S[3]a = A[6]episodes_1 = MonteCarlo.sample(MDP, Pi_1, timestep_max, num)episodes_2 = MonteCarlo.sample(MDP, Pi_2, timestep_max, num)rho_1 = occupancy(episodes_1, s, a, timestep_max, gamma)rho_2 = occupancy(episodes_2, s, a, timestep_max, gamma)print('策略1对状态动作对 (%s, %s) 的占用度量为:%f' % (s, a, rho_1))print('策略2对状态动作对 (%s, %s) 的占用度量为:%f' % (s, a, rho_2))occupancy_instance()

运行 Main.py 文件,结果如下:

D:\RL\MDP\.venv\Scripts\python.exe D:\RL\MDP\Main.py 
策略1对状态动作对 (S4, 概率前往) 的占用度量为:0.109338
策略2对状态动作对 (S4, 概率前往) 的占用度量为:0.226629进程已结束,退出代码为 0

六、最优策略

  强化学习的目标通常是找到一个策略,使得智能体从初始状态出发能获得最多的期望回报。

  • 最优策略 π ∗ ( s ) \pi^*(s) π(s):在 MDP 中,至少存在一个不差于其他所有策略的策略。
    当且仅当对任意的状态 s 都有 V π ( s ) ≥ V π ′ ( s ) V^{\pi}(s)\ge V^{\pi'}(s) Vπ(s)Vπ(s) ,记 π ⪰ π ′ \pi \succeq \pi' ππ ,MDP 至少存在一个最优策略
  • 最优状态价值函数 V ∗ ( s ) V^*(s) V(s) V ∗ ( s ) = max ⁡ π V π ( s ) , ∀ s ∈ S V^*(s)=\max\limits_{\pi} V^{\pi}(s),\;\; \forall s\in S V(s)=πmaxVπ(s),sS
  • 最优动作价值函数 Q ∗ ( s , a ) Q^*(s,a) Q(s,a) Q ∗ ( s , a ) = max ⁡ π Q π ( s , a ) , ∀ s ∈ S , a ∈ A Q^*(s,a)=\max\limits_{\pi}Q^{\pi}(s,a),\;\;\forall s \in S,a \in A Q(s,a)=πmaxQπ(s,a),sS,aA
  • 最优状态价值函数与最优动作价值函数的关系:
    Q ∗ ( s , a ) = r ( s , a ) + γ ∑ s ′ ∈ S P ( s ′ ∣ s , a ) V ∗ ( s ′ ) V ∗ ( s ) = max ⁡ a ∈ A Q ∗ ( s , a ) Q^*(s,a)=r(s,a)+\gamma \sum_{s'\in S}P(s'|s,a)V^*(s') \\ \;\\ V^*(s)=\max\limits_{a\in A}Q^*(s,a) Q(s,a)=r(s,a)+γsSP(ss,a)V(s)V(s)=aAmaxQ(s,a)
  • 贝尔曼最优方程:
    Q ∗ ( s , a ) = r ( s , a ) + γ ∑ s ′ ∈ S P ( s ′ ∣ s , a ) max ⁡ a ′ ∈ A Q ∗ ( s ′ , a ′ ) V ∗ ( s ) = max ⁡ a ∈ A { r ( s , a ) + γ ∑ s ′ ∈ S P ( s ′ ∣ s , a ) V ∗ ( s ′ ) } Q^*(s,a)=r(s,a)+\gamma \sum_{s'\in S}P(s'|s,a)\max\limits_{a'\in A}Q^*(s',a') \\ \;\\ V^*(s)=\max\limits_{a\in A}\{r(s,a)+\gamma \sum_{s'\in S}P(s'|s,a)V^*(s')\} Q(s,a)=r(s,a)+γsSP(ss,a)aAmaxQ(s,a)V(s)=aAmax{r(s,a)+γsSP(ss,a)V(s)}
  • 得到最优策略的方法:
    • 第4章 动态规划算法
    • 第5章 时序差分算法

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

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

相关文章

JAVA-集合相关

HashMap如何解决哈希冲突的&#xff1f; 计算hash值&#xff0c;基于hashCode计算冲突之后&#xff0c;先是使用链式寻址法当链表长度大于8&#xff0c;且hash表的容量大于60的时候&#xff0c;再添加元素则转化成红黑树 为什么计算hash值是&#xff0c;是将hash地址的值右移1…

大数据Flink(一百一十八):Flink SQL水印操作(Watermark)

文章目录 Flink SQL水印操作&#xff08;Watermark&#xff09; 一、为什么要有WaterMark 二、​​​​​​​​​​​​​​Watermark解决的问题 三、​​​​​​​​​​​​​​代码演示 Flink SQL水印操作&#xff08;Watermark&#xff09; 一、​​​​​​​为什么…

43.常用C++编译器推荐——《跟老吕学C++》

43.常用C编译器推荐——《跟老吕学C》 常用C编译器推荐一、C编译器介绍1. GCC (GNU Compiler Collection)2. Clang2.1 Clang的特点2.2 Clang的应用场景2.3 Clang与GCC的比较 3. Microsoft Visual C (MSVC)MSVC的特点MSVC的使用场景MSVC与其他编译器的比较 4. Intel C Compiler4…

【Midjourney中文版】

Midjourney中文版打破了传统创作工具的界限&#xff0c;无需具备专业的艺术技能或复杂的软件操作能力&#xff0c;即可轻松创作出高质量的图片。它支持多种创作模式&#xff0c;包括文生图、图生图、图片混图融合等&#xff0c;满足多样化的创作需求。 打开Midjourney中文版后…

istio中如何使用serviceentry引入外部服务

假设需要引入一个外部服务&#xff0c;外部服务ip为10.10.102.90&#xff0c;端口为32033. 引入到istio中后&#xff0c;我想通过域名gindemo.test.ch:9090来访问这个服务。 serviceentry yaml内容如下&#xff1a; apiVersion: networking.istio.io/v1beta1 kind: ServiceEn…

【Pycharm】Pycharm创建Django提示pip版本需要升级

目录 1、现象 2、分析 3、本质 前言&#xff1a;经常使用pycharm创建django、flask等项目时候提示pip版本需要升级&#xff0c;解决方案 1、现象 使用Pycharm创建Django项目提示安装Django超时&#xff0c;报错建议pip升级22升级到24 2、分析 之前使用命令升级了pip到了24…

VS code EXPLORER 中不显示指定文件及文件夹设置(如.pyc, __pycache__, .vscode 文件)

VS code EXPLORER 中不显示指定文件及文件夹设置 引言正文方法1打开方式1打开方式2 方法2 引言 VS code 号称地表最强轻量级编译器&#xff0c;其最大的优势在于用户可以根据自己的需求下载适合自己的 extension。从而定制个性化的编译器。然而&#xff0c;本人今天遇到了一个…

出厂非澎湃OS手机解BL锁

脚本作者&#xff1a;酷安mlgmxyysd 脚本项目链接&#xff1a;https://github.com/MlgmXyysd/Xiaomi-HyperOS-BootLoader-Bypass/ 参考 B站作者&#xff1a;蓝空穹 https://www.bilibili.com/read/cv33210124/ 其他参考&#xff1a;云墨清风、水墨青竹、Magisk中文网 决定解BL…

设计模式 组合模式(Composite Pattern)

组合模式简绍 组合模式&#xff08;Composite Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许你将对象组合成树形结构来表示“部分-整体”的层次结构。组合模式使得客户端可以用一致的方式处理单个对象和组合对象。这样&#xff0c;可以在不知道对象具体类型的条…

通信工程学习:什么是ONT光网络终端

ONT&#xff1a;光网络终端 ONT&#xff08;Optical Network Terminal&#xff0c;光网络终端&#xff09;是光纤接入网络&#xff08;FTTH&#xff09;中的关键设备&#xff0c;用于将光纤信号转换为电信号或将电信号转换为光信号&#xff0c;以实现用户设备与光纤网络的连接。…

Koa (下一代web框架) 【Node.js进阶】

koa (中文网) 是基于 Node.js 平台的下一代 web 开发框架&#xff0c;致力于成为应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石&#xff1b; 利用 async 函数 丢弃回调函数&#xff0c;并增强错误处理&#xff0c;koa 没有任何预置的中间件&#xff0c;可快速…

计算机组成原理(笔记3)

IEEE754浮点数标准 这里只讲32位单精度 S——尾数符号&#xff0c;0正1负&#xff1b; M——尾数, 纯小数表示, 小数点放在尾数域的最前面。 一般采用原码或补码表示。 E——阶码&#xff0c;采用“移码”表示; 阶符采用隐含方式&#xff0c;即采用“移码”方法来表示正负指数…

Python 之数据库操作(Python Database Operations)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

基于SSM的在线家用电器销售系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSSMVueMySQL的在线家…

统信服务器操作系统【1050e版】安装手册

统信服务器操作系统1050e版本的安装 文章目录 功能概述一、准备环境二、安装方式介绍安装步骤步骤一:制作启动盘步骤二:系统的安装步骤三:安装引导界面步骤四:图形化界面安装步骤五:选择安装引导程序语言步骤六:进入安装界面步骤七:设置键盘步骤八:设置系统语言步骤九:…

链接升级:Element UI <el-link> 的应用

链接升级&#xff1a;Element UI 的应用 一 . 创建文字链接1.1 注册路由1.2 创建文字链接 二 . 文字链接的属性2.1 文字链接的颜色2.2 是否显示下划线2.3 是否禁用状态2.4 填写跳转地址2.5 加入图标 在本篇文章中&#xff0c;我们将深入探索Element UI中的<el-link>组件—…

Elasticsearch基础(七):Logstash如何开启死信队列

文章目录 Logstash如何开启死信队列 一、确保 Elasticsearch 输出插件启用 DLQ 支持 二、配置 Logstash DLQ 设置 三、查看死信队列 四、排查 CSV 到 Elasticsearch 数据量不一致的问题 Logstash如何开启死信队列 在 Logstash 中&#xff0c;死信队列&#xff08;Dead Le…

C++ nullptr 和NULL的区别

个人主页&#xff1a;Jason_from_China-CSDN博客 所属栏目&#xff1a;C系统性学习_Jason_from_China的博客-CSDN博客 所属栏目&#xff1a;C知识点的补充_Jason_from_China的博客-CSDN博客 概念概述&#xff1a; 在C中&#xff0c;nullptr 和 NULL 都是用来表示空指针&#xf…

微波无源器件 功分器3 一种用于多端口辐射单元的紧凑四路双极化正交模功分器的设计

摘要&#xff1a; 一种有着双极化能力并且能作为一个Fabry-Perot谐振腔天线的馈源包含四个输入端口的新型紧凑功分器的概念和设计被提出了。在四个圆波导中的双同相极化通过使用四个5端口十字转门结合两个8by1&#xff08;八合一&#xff09; 功分网络。功分器末端接了两个端口…

【RabbitMQ】工作模式

工作模式概述 简单模式 简单模式中只存在一个生产者&#xff0c;只存在一个消费者。生产者生产消息&#xff0c;消费者消费消息。消息只能被消费一次&#xff0c;也称为点对点模式。 简单模式适合在消息只能被单个消费者处理的场景下存在。 工作队列模式&#xff08;Work Qu…