【强化学习】动手学强化学习:多臂老虎机问题

news2024/12/26 14:55:30

动手学强化学习:多臂老虎机问题

强化学习思维导图

强化学习

简介

强化学习关注智能体和环境交互过程中的学习,这是一种试错型学习(trial-and-error learning)范式。在正式学习强化学习之前,我们需要先了解多臂老虎机问题,它可以被看作简化版的强化学习问题。与强化学习不同,多臂老虎机不存在状态信息,只有动作和奖励,算是最简单的“和环境交互中的学习”的一种形式。多臂老虎机中的探索与利用(exploration vs. exploitation)问题一直以来都是一个特别经典的问题,理解它能够帮助我们学习强化学习。

  • Exploitation执行能够获得已知最优收益的决策
  • Exploration尝试更多可能的决策,不一定会是最优收益

形式化序列决策问题如下:
探索
探索:可能发现更好的策略
探索与利用

问题介绍

问题定义

在多臂老虎机(multi-armed bandit,MAB)问题(见图 2-1)中,有一个拥有K根拉杆的老虎机拉动每一根拉杆都对应一个关于奖励的概率分布R。我们每次拉动其中一根拉杆,就可以从该拉杆对应的奖励概率分布中获得一个奖励r。我们在各根拉杆的奖励概率分布未知的情况下,从头开始尝试,目标是在操作T次拉杆后获得尽可能高的累积奖励。由于奖励的概率分布是未知的,因此我们需要在“探索拉杆的获奖概率”和“根据经验选择获奖最多的拉杆”中进行权衡。“采用怎样的操作策略才能使获得的累积奖励最高”便是多臂老虎机问题。
多臂老虎机

形式化描述

多臂老虎机问题是概率论中一个经典问题,也属于强化学习的范畴。设想,一个赌徒面前有N个老虎机,事先他不知道每台老虎机的真实盈利情况,他如何根据每次玩老虎机的结果来选择下次拉哪台或者是否停止赌博,来最大化自己的从头到尾的收益。

多臂老虎机问题可以表示为一个元组<A,R>,其中:

  • A为动作集合,其中一个动作表示拉动一个拉杆。如果多臂老虎机一共有K跟拉杆,那么动作空间就是集合 { a 1 , . . . , a K } \{a_1,...,a_K\} {a1,...,aK},用 a t ∈ A a_t\in A atA表示任意一个动作;
  • R为奖励概率分布,拉动每一根拉杆的动作 a a a都对应一个奖励概率分布 R ( r ∣ a ) R(r|a) R(ra),不同拉杆的奖励分布通常是不同的。

假设每个时间步只能拉动一根拉杆,多臂老虎机的目标为最大化一段时间步T内累积的奖励: m a x ∑ t = 1 T r t , r t ∈ R ( ⋅ ∣ a t ) max \sum_{t=1}^T{r_t,r_t\in R(\cdot|a_t)} maxt=1Trt,rtR(at),其中 a t a_t at表示在 t t t时间步拉动某一拉杆的动作, r t r_t rt表示动作 a t a_t at的奖励。

在商业中,多臂老虎机问题有着广泛的应用,包括广告展示、医学试验和金融等领域。比如在推荐系统中,我们有N个物品,事先不知道用户U对N个物品的反应,我们需要每次推荐给用户某个物品,来最大化用户的价值(或者说尽量使得用户U转化),比如用户的购买。

累积懊悔

对于每一个动作 a a a ,我们定义其期望奖励为 Q ( a i ) = E r ∼ R ( r ∣ a i ) [ r ∣ a i ] Q(a^i)=\mathbb{E}_{r \sim \mathcal{R}(r\mid a^i)}[r|a^i] Q(ai)=ErR(rai)[rai] 。于是,至少存在一根拉杆,它的期望奖励不小于拉动其他任意一根拉杆,我们将该最优期望奖励表示为 Q ∗ = max ⁡ a i ∈ A Q ( a i ) Q^{*}=\max _{a^i \in \mathcal{A}} Q(a^i) Q=aiAmaxQ(ai)。为了更加直观、方便地观察拉动一根拉杆的期望奖励离最优拉杆期玍奖励的差距,我们引入懊悔 (regret) 概念。懊悔定义为拉动当前拉杆的动作 a a a与最优拉杆的期望奖励差,即 R ( a i ) = Q ∗ − Q ( a i ) R(a^i)=Q^{*}-Q(a^i) R(ai)=QQ(ai)累积懊悔 (cumulative regret) 即操作 T T T次拉杆后累积的懊悔总量,对于一次完整的 T T T 步决策 { a 1 , a 2 , … , a T } \left\{a_{1}, a_{2}, \ldots, a_{T}\right\} {a1,a2,,aT},累积懊悔为 σ R = E a ∼ π [ ∑ t = 1 T R ( a t i ) ] \sigma_{R}=\mathbb{E}_{a\sim \pi}[\sum_{t=1}^{T} R\left(a_{t}^i\right)] σR=Eaπ[t=1TR(ati)]MAB 问题的目标为最大化累积奖励,等价于最小化累积懊悔,即 min  σ R = max  E a ∼ π [ ∑ t = 1 T Q ( a t i ) ] \text{min } \sigma_R=\text{max } \mathbb{E}_{a\sim \pi}[\sum_{t=1}^TQ(a_t^i)] min σR=max Eaπ[t=1TQ(ati)]随着时间推移,total regret需要越来越小,那么探索一直都是必须的吗?

如果一直探索新策略:KaTeX parse error: Undefined control sequence: \proptoT at position 9: \sigma_R\̲p̲r̲o̲p̲t̲o̲T̲\cdot R, tota regret将线性递增,无法收敛;
如果一直不探索新策略: σ R ∝ T ⋅ R \sigma_R\propto T\cdot R σRTR, total regret将线性递增。

是否存在一个方法具有次线性(sublinear)收敛保证的regret?
下界

估计期望奖励

为了知道拉动哪一根拉杆能获得更高的奖励,我们需要估计拉动这根拉杆的期望奖励。由于只拉动一次拉杆获得的奖励存在随机性,所以需要多次拉动一根拉杆,然后计算得到的多次奖励的期望,其算法流程如下所示。

  • 对于 ∀ a ∈ A \forall a \in \mathcal{A} aA, 初始化计数器 N ( a ) = 0 N(a)=0 N(a)=0和期望奖励估值 Q ^ ( a ) = 0 \hat{Q}(a)=0 Q^(a)=0
  • for t = 1 → T t=1 \rightarrow T t=1T do
    • 选取某根拉杆,该动作记为 a t a_{t} at
    • 得到奖励 r t r_{t} rt
    • 更新计数器: N ( a t ) = N ( a t ) + 1 N\left(a_{t}\right)=N\left(a_{t}\right)+1 N(at)=N(at)+1
    • 更新期望奖励估值: Q ^ ( a t ) = Q ^ ( a t ) + 1 N ( a t ) [ r t − Q ^ ( a t ) ] \hat{Q}\left(a_{t}\right)=\hat{Q}\left(a_{t}\right)+\frac{1}{N\left(a_{t}\right)}\left[r_{t}-\hat{Q}\left(a_{t}\right)\right] Q^(at)=Q^(at)+N(at)1[rtQ^(at)]
  • end for

以上 for 循环中的第四步如此更新估值,是因为这样可以进行增量式的期望更新,公式如下:
Q k = 1 k ∑ i = 1 k r i = 1 k ( r k + ∑ i = 1 k − 1 r i ) = 1 k ( r k + ( k − 1 ) Q k − 1 ) = 1 k ( r k + k Q k − 1 − Q k − 1 ) = Q k − 1 + 1 k [ r k − Q k − 1 ] Q_k=\frac{1}{k}\sum_{i=1}^kr_i=\frac{1}{k}(r_k+\sum_{i=1}^{k−1}r_i)=\frac{1}{k}(r_k+(k−1)Q_{k−1})=\frac{1}{k}(r_k+kQ_{k−1}−Q_{k−1})=Q_{k−1}+\frac{1}{k}[r_k−Q_{k−1}] Qk=k1i=1kri=k1(rk+i=1k1ri)=k1(rk+(k1)Qk1)=k1(rk+kQk1Qk1)=Qk1+k1[rkQk1]

如果将所有数求和再除以次数,其缺点是每次更新的时间复杂度和空间复杂度均为 O ( n ) O(n) O(n)。而采用增量式更新,时间复杂度和空间复杂度均为 O ( 1 ) O(1) O(1)

下面我们编写代码来实现一个拉杆数为 10 的多臂老虎机。其中拉动每根拉杆的奖励服从伯努利分布 (Bernoulli distribution),即每次拉下拉杆有 p p p的概率获得的奖励为 1 1 1 . 有 1 − p 1-p 1p的概率获得的奖励为 0 0 0。奖励为 1 1 1代表获奖, 奖励为 0 0 0代表没有获奖。

import numpy as np
import matplotlib.pyplot as plt

class BernoulliBandit:
    """伯努利多臂老虎机,输入k表示拉杆个数"""
    def __init__(self, K):
        self.probs = np.random.uniform(size=K) # 随机生成k个0~1的数,作为拉动每根拉杆的奖励
        # 概率
        self.best_idx = np.argmax(self.probs) # 奖励概率最大的拉杆
        self.best_prob = self.probs[self.best_idx] # 最大的奖励概率值
        self.K = K
    def step(self, k):
        # 当玩家选择了k号拉杆后,根据拉动该老虎机的k号拉杆获得奖励的概率返回1(获奖)或0(未获奖)
        if np.random.rand() < self.probs[k]:
            return 1
        else:
            return 0

执行结果
接下来我们用一个 Solver基础类来实现上述的多臂老虎机的求解方案。根据前文的算法流程,需要实现下列函数功能:根据策略选择动作、根据动作获取奖励、更新期望奖励估值、更新累积懊悔和计数。在下面的MAB算法基本框架中,我们将根据策略选择动作、根据动作获取奖励和更新期望奖励估值放在run_one_step()函数中,由每个继承Solver类的策略具体实现。而更新累积懊悔和计数则直接放在主循环run()中。

class Solver:
    """ 多臂老虎机算法基本框架 """

    def __init__(self, bandit):
        self.bandit = bandit
        self.counts = np.zeros(self.bandit.K)  # 每根拉杆的尝试次数
        self.regret = 0.  # 当前步的累积懊悔
        self.actions = []  # 维护一个列表,记录每一步的动作
        self.regrets = []  # 维护一个列表,记录每一步的累积懊悔

    def update_regret(self, k):
        # 计算累积懊悔并保存,k为本次动作选择的拉杆的编号
        self.regret += self.bandit.best_prob - self.bandit.probs[k]
        self.regrets.append(self.regret)

    def run_one_step(self):
        # 返回当前动作选择哪一根拉杆,由每个具体的策略实现
        raise NotImplementedError

    def run(self, num_steps):
        # 运行一定次数,num_steps为总运行次数
        for _ in range(num_steps):
            k = self.run_one_step()
            self.counts[k] += 1
            self.actions.append(k)
            self.update_regret(k)

贪心策略与 ϵ \epsilon ϵ-greedy策略

完全贪心算法即在每一时刻采取期望奖励估值最大的动作,这就是纯粹的利用,而没有探索。所以通常要对完全贪心算法进行一些修改,比较经典的方法是 ϵ \epsilon ϵ-greedy策略。 ϵ \epsilon ϵ-greedy算法是在完全贪心算法的基础上增加了噪声,每次以概率1- ϵ \epsilon ϵ选择遗忘经验中期望奖励估值最大的那根拉杆(利用),以概率 ϵ \epsilon ϵ随机选择一根拉杆(探索)。贪心策略与 ϵ \epsilon ϵ贪心策略对比如下:
贪心策略比较
ϵ \epsilon ϵ-greedy策略的具体实现中,令 ϵ \epsilon ϵ随时间衰减,即探索的概率将会不断降低。但是 ϵ \epsilon ϵ不会在有限的步数内衰减到0,因为基于有限步数观测的完全贪心算法仍然是一个局部信息的贪心算法,永远距离最优解有一个固定的差距。
衰减贪心策略
这样的缺点是很难找到合适的衰减规划。

不同 ϵ \epsilon ϵ-greedy策略对比:Total Regret
Total regret
上图表明,基本上无论 ϵ \epsilon ϵ取值多少,累积懊悔都是线性增长的。随着 ϵ \epsilon ϵ的增大,累积懊悔增长的速率也会增大。

接下来尝试 ϵ \epsilon ϵ的值随时间衰减的 ϵ \epsilon ϵ贪心算法,采取的具体衰减形式为反比例衰减,公式为 ϵ t = 1 t \epsilon_t=\frac{1}{t} ϵt=t1

class DecayingEpsilonGreedy(Solver):
    """epsilon值随时间衰减的epsilon贪心算法,继承Solver类"""

    def __init__(self, bandit, init_prob=1.0):
        super(DecayingEpsilonGreedy, self).__init__(bandit)
        self.estimates = np.array([init_prob] * self.bandit.K)
        self.total_count = 0

    def run_one_step(self):
        self.total_count += 1
        if np.random.random() < 1 / self.total_count:  # epsilon值随时间衰减
            k = np.random.randint(0, self.bandit.K)
        else:
            k = np.argmax(self.estimates)

        r = self.bandit.step(k)
        self.estimates[k] += 1. / (self.counts[k] + 1) * (r - self.estimates[k])
        return k

np.random.seed(1)
decaying_epsilon_greedy_solver = DecayingEpsilonGreedy(bandit_10_arm)
decaying_epsilon_greedy_solver.run(5000)
print('epsilon值衰减的贪婪算法的累积懊悔为:',decaying_epsilon_greedy_solver.regret)
plot_results([decaying_epsilon_greedy_solver],["DecayingEpsilonGreedy"])

decaying
从图中可以看出,随着时间做反比衰减的 ϵ \epsilon ϵ贪心算法能够使累积懊悔与时间步的关系变成次线性(sublinear)的,这明显优于固定 ϵ \epsilon ϵ值的 ϵ \epsilon ϵ贪心算法。

上置信界算法

一根拉杆的不确定性越大,它就越具有探索的价值,因为探索之后可能发现它的期望奖励很大。引入不确定性度量 U ( a ) U(a) U(a),它会随着一个动作被尝试次数的增加而减小。可以使用一种基于不确定性的策略来综合考虑现有的期望奖励估值和不确定性,其核心问题是如何估计不确定性。

上置信界(upper confidence bound, UCB)算法是一种经典的基于不确定性的策略算法,它的思想用到了一个非常著名的数学原理:霍夫丁不等式(Hoeffding’s inequality)。在霍夫丁不等式中,令 X 1 , . . . , X n X_1,...,X_n X1,...,Xn为n个独立同分布的随机变量,取值范围为[0,1],其经验期望为 x ˉ n = 1 n ∑ j = 1 ∗ X j \bar{x}_n=\frac{1}{n}\sum_{j=1}^*X_j xˉn=n1j=1Xj,则有: P ( E [ X ] ≥ x ˉ n + u ) ≤ e − 2 n u 2 P(E[X]\ge \bar{x}_n+u)\le e^{-2nu^2} P(E[X]xˉn+u)e2nu2

在概率论中,霍夫丁不等式给出了随机变量的和与其期望值偏差的概率上限,该不等式被Wassily Hoeffding于1963年提出并证明。霍夫丁不等式是Azuma-Hoeffding不等式的特例,它比Sergei Bernstein于1923年证明的Bernstein不等式更具一般性。这几个不等式都是McDiarmid不等式的特例。

Q ^ ( a t ) \hat{Q}(a_t) Q^(at)代入 x ˉ t \bar{x}_t xˉt,不等式中的参数 u = U ^ ( a t ) u=\hat{U}(a_t) u=U^(at)代表不确定性度量。给定一个概率 p = e − 2 N ( a t ) U ( a t ) 2 p=e^{-2N(a_t)U(a_t)^2} p=e2N(at)U(at)2,根据上述不等式, Q ( a t ) < Q ^ ( a t ) + U ^ ( a t ) Q(a_t)<\hat{Q}(a_t)+\hat{U}(a_t) Q(at)<Q^(at)+U^(at)至少以概率1-p成立。当p很小时, Q ( a t ) < Q ^ ( a t ) + U ^ ( a t ) Q(a_t)<\hat{Q}(a_t)+\hat{U}(a_t) Q(at)<Q^(at)+U^(at)就以很大概率成立, Q ^ ( a t ) + U ^ ( a t ) \hat{Q}(a_t)+\hat{U}(a_t) Q^(at)+U^(at)便是期望奖励上届。此时上置信界算法便选取期望奖励上届最大的动作,即 a = a r g max ⁡ a ∈ A [ Q ^ ( a ) + U ^ ( a ) ] a=arg \max_{a\in A}[\hat{Q}(a)+\hat{U}(a)] a=argmaxaA[Q^(a)+U^(a)]。那其中 U ^ ( a ) \hat{U}(a) U^(a)具体是什么呢?根据等式 p = e − 2 N ( a t ) U ( a t ) 2 p=e^{-2N(a_t)U(a_t)^2} p=e2N(at)U(at)2,解之即得 U ^ ( a t ) = − l o g p 2 N ( a t ) \hat{U}(a_t)=\sqrt{\frac{-log p}{2N(a_t)}} U^(at)=2N(at)logp 。因此,设定一个概率p后,就可以计算相应的不确定性度量 U ( a t ) ^ \hat{U(a_t)} U(at)^了。

UCB 算法在每次选择拉杆前,先估计每根拉杆的期望奖励的上界,使得拉动每根拉杆的期望奖励只有一个较小的概率超过这个上界,接着选出期望奖励上界最大的拉杆,从而选择最有可能获得最大期望奖励的拉杆。

编写代码来实现UCB算法,在具体实现的过程中,设置 p = 1 t p=\frac{1}{t} p=t1,并且在分母中为拉动每根拉杆的次数加上常数1,以免出现分母为0的情形,即此时 U ^ ( a t ) = log ⁡ t 2 ( N ( a t ) + 1 ) \hat{U}(a_t)=\sqrt{\frac{\log t}{2(N(a_t)+1)}} U^(at)=2(N(at)+1)logt 。同时设定一个系数 c c c来控制不确定性比重,此时 a t = a r g max ⁡ a ∈ A [ Q ^ ( a ) ] a_t=arg\max_{a\in A}[\hat{Q}(a)] at=argmaxaA[Q^(a)]

class UCB(Solver):
    """ UCB算法,继承Solver类 """

    def __init__(self, bandit, coef, init_prob=1.0):
        super(UCB, self).__init__(bandit)
        self.total_count = 0
        self.estimates = np.array([init_prob] * self.bandit.K)
        self.coef = coef

    def run_one_step(self):
        self.total_count += 1
        ucb = self.estimates + self.coef * np.sqrt(
            np.log(self.total_count) / (2 * (self.counts + 1)))  # 计算上置信界
        k = np.argmax(ucb)  # 选出上置信界最大的拉杆
        r = self.bandit.step(k)
        self.estimates[k] += 1. / (self.counts[k] + 1) * (r - self.estimates[k])
        return k


np.random.seed(1)
coef = 1  # 控制不确定性比重的系数
UCB_solver = UCB(bandit_10_arm, coef)
UCB_solver.run(5000)
print('上置信界算法的累积懊悔为:', UCB_solver.regret)
plot_results([UCB_solver], ["UCB"])

UCB

汤普森采样算法

MAB中还有一种经典算法——汤普森采样(Thompson sampling),先假设拉动每根拉杆的奖励服从一个特定的概率分布,然后根据拉动每根拉杆的期望奖励来进行选择。但是由于计算所有拉杆的期望奖励的代价比较高,汤普森采样算法使用采样的方式,即根据当前每个动作 a a a的奖励概率分布进行一轮采样,得到一组各根拉杆的奖励样本,再选择样本中奖励最大的动作。

汤普森采样是一种计算所有拉杆的最高奖励概率的蒙特卡洛采样方法

实际情况中,通常用Beta分布对当前每个动作的奖励概率分布进行建模。具体来说,如果某拉杆被选择了k次,其中 m 1 m_1 m1次奖励为1, m 2 m_2 m2次奖励为0,则该拉杆的奖励服从参数为 ( m 1 + 1 , m 2 + 1 ) (m_1+1, m_2+1) (m1+1,m2+1)的Beta分布。

Beta分布是一种连续型概率密度分布,表示为 x ∼ B e t a ( a , b ) x\sim Beta(a, b) xBeta(a,b),由两个参数a, b决定,称为形状参数。由于其定义域为(0,1),一般被用于建模伯努利试验事件成功的概率的概率分布。简单来说,beta分布可以看作一个概率的概率密度分布,当你不知道一个东西的具体概率是多少时,它可以给出了所有概率出现的可能性大小。

class ThompsonSampling(Solver):
    """汤普森采样算法,继承Solver类"""
    def __init__(self, bandit):
        super(ThompsonSampling, self).__init__(bandit)
        self._a = np.ones(self.bandit.K)  # 列表,表示每根拉杆奖励为1的次数
        self._b = np.ones(self.bandit.K)  # 列表,表示每根拉杆奖励为0的次数

    def run_one_step(self):
        samples = np.random.beta(self._a, self._b)  # 按照Beta分布采样一组奖励样本
        k = np.argmax(samples)  # 选出采样奖励最大的拉杆
        r = self.bandit.step(k)

        self._a[k]  += r  # 更新Beta分布的第一个参数
        self._b[k] += (1-r)  # 更新Beta分布的第二个参数
        return k

np.random.seed(1)
thompson_sampling_solver = ThompsonSampling(bandit_10_arm)
thompson_sampling_solver.run(5000)
print('汤普森采样算法的累积懊悔为:',thompson_sampling_solver.regret)
plot_results([thompson_sampling_solver], ["ThompsonSampling"])

Thompson sampling
通过实验得出以下结论:
ϵ \epsilon ϵ-greedy算法的累积懊悔是随时间线性增长的,而另外3种算法( ϵ \epsilon ϵ-衰减贪婪算法、上置信界算法、汤普森采样算法)的累积懊悔都是随时间次线性增长的(具体为对数形式增长)。

参考资料

  1. 《动手学强化学习》张伟楠、沈键、俞勇著
  2. 《ElitesAI·强化学习》张伟楠主讲
  3. 机器学习数学原理(8)——霍夫丁不等式
  4. 深入理解Beta分布:从定义到公式推导

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

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

相关文章

chatgpt赋能python:Python中的查找方法

Python中的查找方法 Python是一种常用的编程语言&#xff0c;它有很多强大的查找方法。这些方法可以让开发人员轻松地搜索数据、列表和文本。 以下是Python中最常用的查找方法&#xff1a; 列表查找方法 在Python中&#xff0c;可以使用多种方法来查找列表中的元素。以下是…

Python零基础入门(四)——变量,数据类型与运算符

系列文章目录 个人简介&#xff1a;机电专业在读研究生&#xff0c;CSDN内容合伙人&#xff0c;博主个人首页 Python入门专栏&#xff1a;《Python入门》欢迎阅读&#xff0c;一起进步&#xff01;&#x1f31f;&#x1f31f;&#x1f31f; 码字不易&#xff0c;如果觉得文章不…

采集数据发布到WordPress网站指定分类栏目

将采集的文章数据发布到WordPressCMS网站指定分类栏目&#xff0c;设置方法如下&#xff1a; 目录 1. 获取分类名称或ID 2. 对接网站发布 1. 获取分类名称或ID 在wordpress后台获取对应的分类栏目名称或者ID都可以。 点击左侧菜单【文章】 --> 然后点击展开菜单中的【…

自然语言处理的最新研究与发展

第一章&#xff1a;引言 自然语言处理&#xff08;Natural Language Processing&#xff0c;简称NLP&#xff09;是人工智能领域中一项重要而受关注的技术&#xff0c;它致力于实现计算机与人类自然语言之间的有效交互。随着深度学习技术的快速发展&#xff0c;NLP领域也取得了…

LNMP架构——Discuz! Board 管理中心

文章目录 一.LNMP架构基本概念1.什么是LNMP2.LNMP实现过程3.LNMP的特点 二.安装Nginx 服务&#xff11;.安装依赖包&#xff12;.创建运行用户&#xff13;.编译安装&#xff14;.优化路径&#xff15;.添加 Nginx 系统服务 三.安装 MySQL 服务&#xff11;.安装Mysql环境依赖包…

java运动会管理系统

目录 一、项目介绍 1、主要功能介绍 二、分析与设计 三、问题与分析 四、小结 五、代码 一、项目介绍 1、主要功能介绍 对于管理者&#xff1a; 1、登录系统来发布运动会的项目以及对应项目的比赛规则 2、管理者也可以修改运动会比赛时间和地点&#xff0c;如果管理者…

漏刻有时数据可视化Echarts组件开发(27):端午地图粽情之你的家乡吃甜还是吃咸?

端午地图粽情之你的家乡吃甜还是吃咸&#xff1f; 前言Echarts创意来源Echarts核心代码1.引入外部文件2.构建HTML容器3.Echarts组件开发预置各省数据初始化DOM配置选项geo组件series组件自适应浏览器完整option选项配置代码 前言 中国各地对粽子的口味偏好存在一定的差异&…

vue中的数据响应化

1、Vue的设计思想 MVVM框架的三要素&#xff1a;数据响应式、模板引擎及其渲染 数据响应式&#xff1a;监听数据变化并在视图中更新 Object.defineProperty()Proxy 模版引擎&#xff1a;提供描述视图的模版语法 插值&#xff1a;{{}}指令&#xff1a;v-bind&#xff0c;v-on…

chatgpt赋能python:Python构造器:理解类和对象的初始化方法

Python构造器&#xff1a;理解类和对象的初始化方法 Python是一门面向对象的编程语言&#xff0c;它的核心思想是数据和操作是紧密耦合的&#xff0c;而这些操作被封装到对象中。对象是一个具有属性和方法的实体&#xff0c;而类则是一种可以创建相同类型对象的蓝图。在Python…

chatgpt赋能python:Python有宏吗?

Python 有宏吗&#xff1f; 什么是宏&#xff1f; 在编程中&#xff0c;宏指的是一种代码编写方式&#xff0c;可以将一部分代码封装成可以被调用的函数或者语句&#xff0c;以便于在程序中重复使用。相对于普通的函数&#xff0c;宏更为灵活&#xff0c;可以达到更高的效率&…

RuntimeError: launcher ‘pdsh‘ not installed解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

【吃透网络安全】2023软考网络管理员考点网络安全(三)计算机系统安全评估

涉及知识点 计算机系统安全评估准则&#xff0c;计算机系统安全评估历史&#xff0c;软考网络管理员常考知识点&#xff0c;软考网络管理员网络安全&#xff0c;网络管理员考点汇总。 后面还有更多续篇希望大家能给个赞哈&#xff0c;这边提供个快捷入口&#xff01; 第一节…

二叉树题目:二叉树的后序遍历

文章目录 题目标题和出处难度题目描述要求示例数据范围进阶 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 解法三思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;二叉树的后序遍历 出处&#xff1a;145. 二叉树的后序遍历 难度 3 级 题目…

沁恒CH32V103 边玩边学1-开发环境与GPIO项目

为什么选择这块板子&#xff1f; 它基于 RISC-V 架构&#xff0c;来看看 GPT 给出的介绍&#xff1a; RISC-V 是一种开源的指令集架构(ISA),与 x86 和 ARM 相似。它具有以下主要特点 • 简单 - RISC-V 采用精炼而简单的 RISC 指令设计,只有一些基本的常用指令。这使得 RISC-V…

CRM未来发展的6大方向

在数字化时代&#xff0c;几乎所有的企业都受到了数字化的洗礼&#xff0c;CRM作为企业数字化转型中的不可缺少的业务系统之一&#xff0c;也受到越来越企业的关注。 纵观CRM发展的趋势&#xff0c;当下CRM系统已经从早期的主要以记录&收集客户资料、管理销售的单点式管理延…

css基础知识四:说说设备像素、css像素、设备独立像素、dpr、ppi 之间的区别?

一、背景 在css中我们通常使用px作为单位&#xff0c;在PC浏览器中css的1个像素都是对应着电脑屏幕的1个物理像素 这会造成一种错觉&#xff0c;我们会认为css中的像素就是设备的物理像素 但实际情况却并非如此&#xff0c;css中的像素只是一个抽象的单位&#xff0c;在不同…

深入浅出Node.js中的node_modules

文章目录 1. 什么是node_modulesnode_modules是什么npm包管理器和node_modules的关系 2. 如何安装和使用node_modulesnpm安装和使用node_modules的基本命令package.json文件的作用和结构npm包版本号的含义及如何管理包版本 3. 如何发布自己的npm包npm包的结构和规范如何将自己的…

端午出行电脑没网怎么办?无线网卡解决网络问题

无线网卡是一种可以让电脑或其他设备通过无线信号连接网络的硬件设备&#xff0c;无线网卡有多种类型和接口&#xff0c;例如USB无线网卡&#xff0c;PCI-E无线网卡&#xff0c;PCMCIA无线网卡等。端午出行在即&#xff0c;不妨看看驱动人生准备的无线网卡攻略&#xff0c;让大…

什么是kafka,如何学习kafka,整合SpringBoot

目录 一、什么是Kafka&#xff0c;如何学习 二、如何整合SpringBoot 三、Kafka的优势 一、什么是Kafka&#xff0c;如何学习 Kafka是一种分布式的消息队列系统&#xff0c;它可以用于处理大量实时数据流。学习Kafka需要掌握如何安装、配置和运行Kafka集群&#xff0c;以及如…

Kubernetes设计架构

一&#xff1a;Kubernetes是什么 Kubernetes是容器集群管理系统&#xff0c;是一个开源的平台&#xff0c;可以实现容器集群的自动化部署、自动扩缩容、维护等功能 通过Kubernetes可以&#xff1a; 快速部署应用 快速扩展应用 无缝对接新的应用功能 节省资源&#xff0c;优化硬…