深度强化学习开端——DQN算法求解车杆游戏

2024-04-20 21:36

本文主要是介绍深度强化学习开端——DQN算法求解车杆游戏,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

深度强化学习开端——DQN算法求解车杆游戏

DQN,即深度Q网络(Deep Q-Network),是一种结合了深度学习和强化学习的算法,其主要用于解决序列决策问题,并且在许多复杂的决策任务中展现出了显著的效果。DQN算法的发明历史可以追溯到2013年,当时DeepMind团队首次提出了一种名为DQN(Deep Q-Network)的新型强化学习算法。这一算法标志着深度学习和强化学习成功结合的开始,为解决高维、连续状态空间的问题提供了一种有效的解决方案。

DQN算法通过深度神经网络对状态的价值函数进行近似,使算法能够自主学习游戏中的策略。然而,早期的DQN算法存在不稳定性问题,为了改进这些问题,DeepMind团队随后又提出了Double DQN、Dueling DQN等改进版本,以提高算法的稳定性和性能。

此外,DQN算法的扩展也在不断发展,研究者们提出了诸如Prioritized DQN、Rainbow等扩展算法,这些算法在复杂的游戏环境中表现出色,并引起了广泛的关注和讨论。值得注意的是,谷歌的AlphaGo中也使用了DQN算法。

接下来,让我们把DQN算法应用到车杆游戏的求解中,讨论并实践该游戏的策略。让我们开始吧!

注意:本文用到了PyTorch库,gym强化学习环境库,需要提前安装。

  • gym环境安装:https://github.com/Farama-Foundation/Gymnasium
  • gym环境介绍文档:https://gymnasium.farama.org/environments/classic_control/mountain_car/
  • pytorch官网:https://pytorch.org/

本文所使用的python版本为3.11.5

step1:车杆(Cart Pole)游戏介绍

官方地址:https://gymnasium.farama.org/environments/classic_control/cart_pole/
车杆游戏中,一个摆杆通过一个非驱动关节连接到一个小车上,小车沿着无摩擦轨道移动。摆杆直立放置在小车上,我们的目标是通过在小车上向左和向右施加力来使摆杆保持平衡。
Cart Pole
让我们先看看该环境的状态及动作空间吧!

import gymnasium as gym  # 导入gym包
env = gym.make('CartPole-v1')  # 创建山地车游戏环境
observation, info = env.reset()  # 初始化环境for _ in range(1000):action = env.action_space.sample()  # 随机选择一个动作observation, reward, terminated, truncated, info = env.step(action)  # 执行动作if terminated or truncated:  # 当达到终点时,重置环境observation, info = env.reset()env.close()  # 关闭环境

从代码输出中,结合官网文档,我们可以知道:
动作空间

动作是一个int64整数值,其可以取值{0, 1},代表小车被推的方向上的固定力。

  • 0: 将小车推向左边
  • 1: 将小车推向右边

注意:通过施加的力所减少或增加的速度不是固定的,它取决于摆杆所指的角度。摆杆的重心变化会影响移动其下方小车所需的能量。

观察空间

观察值是一个形状为(4,)的ndarray,其值对应于以下的位置和速度:

编号观察项目最小值最大值
0小车位置-4.84.8
1小车速度-InfInf
2摆杆角度~ -0.418 rad (-24°)~ 0.418 rad (24°)
3摆杆角速度-InfInf

注意:尽管上述范围表示了每个元素的观察空间可能的值,但这并不反映在一个未终止的情节中状态空间所允许的值。特别要注意:

  • 小车的x位置(索引0)可以在(-4.8, 4.8)之间取值,但如果小车离开(-2.4, 2.4)的范围,则情节将终止。
  • 摆杆角度可以在(-.418, .418)弧度(或±24°)之间观察到,但如果摆杆角度不在(-.2095, .2095)(或±12°)的范围内,则情节将终止。

奖励

由于目标是尽可能长时间地保持摆杆直立,因此为每一步(包括终止步骤)分配+1的奖励。对于v1版本,奖励的阈值是500;对于v0版本,奖励的阈值是200。即在v1版本坚持 500 个回合即可获得最高的分数。

情节结束

如果发生以下任一情况,情节将结束:

终止:摆杆角度大于±12°

终止:小车位置大于±2.4(小车中心到达显示屏边缘)

截断:情节长度大于500(对于v0版本为200)

step2:DQN算法架构的搭建

DQN算法原理可参考动手学强化学习——DQN算法,DQN算法的实现具体需要以下几个部件:

  • 网络 Q θ ( s , a ) Q_\theta (s,a) Qθ(s,a):该网络可以是输入状态 s s s,输出每一个动作 a a a对应的Q值。也可以是输入状态 s s s和动作 a a a,输出对应的Q值。
  • 目标网络 Q ∗ ( s , a ) Q_* (s,a) Q(s,a):该网络与网络 Q θ ( s , a ) Q_\theta (s,a) Qθ(s,a)结构相同,但使用不同的参数。这是为了让训练过程更加稳定。
  • 经验回放池:该池用于存储训练过程中产生的经验,包括状态 s s s,动作 a a a,奖励 r r r,下一个状态 s ′ s' s,游戏是否结束 d d d

在更新网络时,需要从经验回放池中随机抽取一批经验,然后使用这些经验来更新网络。注意:DQN背后的原理为时序差分算法中的Q-learning 算法,其整个训练过程是一个迭代收敛的过程。
下方是DQN中最重要的公式:

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\left(s^{\prime}, a^{\prime}\right)-Q(s, a)\right] Q(s,a)Q(s,a)+α[r+γaAmaxQ(s,a)Q(s,a)]

这表明,在当前状态 s s s下,选择动作 a a a的Q值应该与当前奖励 r r r和下一个状态 s ′ s' s下,选择某个动作能够获得的最大的Q值 Q ( s ′ , a ′ ) Q\left(s^{\prime}, a^{\prime}\right) Q(s,a) 有关,即 Q ( s , a ) Q(s,a) Q(s,a)应尽可能和 r + γ max a ′ ∈ A Q ( s ′ , a ′ ) r+\gamma \text{max}_{a^{\prime} \in \mathcal{A}} Q\left(s^{\prime}, a^{\prime}\right) r+γmaxaAQ(s,a) 接近。

接下来让我们参考动手学强化学习——DQN 算法,一步步的构建DQN算法。

import random
import numpy as np
import collections
from tqdm import tqdm
import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt

首先让我们构建一个经验回放池,利用该经验回访池,我们将训练过程中的数据存储起来,以便更快速的更新网络。

class ReplayBuffer:''' 经验回放池 '''def __init__(self, capacity):'''此处,我们使用collections.deque来存储经验,该数据结构可以高效的从两端添加和删除元素。'''self.buffer = collections.deque(maxlen=capacity)  # 队列,先进先出def add(self, state, action, reward, next_state, done):  # 将数据加入buffer'''我们将状态$s$,动作$a$,奖励$r$,下一个状态$s'$,游戏是否结束$d$存储到经验回放池中。将(state, action, reward, next_state, done)打包为元组,放入buffer中。'''self.buffer.append((state, action, reward, next_state, done))def sample(self, batch_size):  # 从buffer中采样数据,数量为batch_sizetransitions = random.sample(self.buffer, batch_size)  # 随机从buffer中采样数据state, action, reward, next_state, done = zip(*transitions)  # 将数据解包return np.array(state), action, reward, np.array(next_state), donedef size(self):  # 目前buffer中数据的数量return len(self.buffer)

定义一个Q网络

class Qnet(torch.nn.Module):''' 只有一层隐藏层的Q网络 '''def __init__(self, state_dim, hidden_dim, action_dim):super(Qnet, self).__init__()self.fc1 = torch.nn.Linear(state_dim, hidden_dim)self.fc2 = torch.nn.Linear(hidden_dim, action_dim)def forward(self, x):x = F.relu(self.fc1(x))  # 隐藏层使用ReLU激活函数return self.fc2(x)

接来下开始实现 DQN 算法

class DQN:''' DQN算法 '''def __init__(self, state_dim, hidden_dim, action_dim, learning_rate, gamma,epsilon, target_update, device):'''初始化:其中state_dim为状态维度,action_dim为动作维度,hidden_dim为隐藏层维度。learing_rate为学习率,gamma为折扣因子,epsilon为贪婪策略,target_update为目标网络更新频率。device决定在CPU或GPU上运行。gamma是折扣因子,它决定了未来奖励的权重。在强化学习中,未来的奖励通常被认为比当前的奖励更重要。因此,使用折扣因子可以对未来的奖励进行加权。可参考马尔科夫决策过程。epsilon是贪婪策略,它决定了在选择动作时,随机选择动作的概率。这是为了探索新动作,并避免过早收敛到局部最优解。'''self.action_dim = action_dimself.q_net = Qnet(state_dim, hidden_dim, self.action_dim).to(device)  # Q网络self.target_q_net = Qnet(state_dim, hidden_dim, self.action_dim).to(device)  # 目标网络self.optimizer = torch.optim.Adam(self.q_net.parameters(), lr=learning_rate) # 使用Adam优化器self.gamma = gamma  # 折扣因子self.epsilon = epsilon  # epsilon-贪婪策略self.target_update = target_update  # 目标网络更新频率self.count = 0  # 计数器,记录更新次数self.device = devicedef take_action(self, state):  # epsilon-贪婪策略采取动作if np.random.random() < self.epsilon:  # 当随机数小于epsilon时,这时候我们随机选择一个动作action = np.random.randint(self.action_dim)else:  # 否则,我们选择当前状态下价值最高的动作state = torch.tensor([state], dtype=torch.float).to(self.device)action = self.q_net(state).argmax().item()return actiondef update(self, transition_dict):'''更新网络参数,其中传入的transition_dict是一个字典,包含以下字段:states: 状态列表actions: 动作列表rewards: 奖励列表next_states: 下一个状态列表dones: 是否结束列表每个列表中元素都是一一对应的'''# 将transition_dict中的各量转换为符合神经网络输入的tensor格式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)dones = torch.tensor(transition_dict['dones'],dtype=torch.float).view(-1, 1).to(self.device)# 此处我们将结合时序差分算法中Q-learning算法,来更新网络参数q_values = self.q_net(states).gather(1, actions)  # 获取对应动作action的Q值# 下个状态的最大Q值max_next_q_values = self.target_q_net(next_states).max(1)[0].view(-1, 1)q_targets = rewards + self.gamma * max_next_q_values * (1 - dones)  # TD误差目标dqn_loss = torch.mean(F.mse_loss(q_values, q_targets))  # 均方误差损失函数self.optimizer.zero_grad()  # PyTorch中默认梯度会累积,这里需要显式将梯度置为0dqn_loss.backward()  # 反向传播更新参数self.optimizer.step()if self.count % self.target_update == 0:self.target_q_net.load_state_dict(self.q_net.state_dict())  # 更新目标网络self.count += 1

接下来让我们开启DQN算法的训练过程

# 初始化各项参数
lr = 2e-3  # 学习率
num_episodes = 500  # 总的训练步数
hidden_dim = 128  # 隐藏层维度
gamma = 0.98  # 折扣因子
epsilon = 0.01  # 贪婪策略的epsilon值
target_update = 10  # 目标网络更新频率
buffer_size = 10000  # 经验回放池的大小,越大越占用内存
minimal_size = 500  # 经验回放池中的最小经验数量,小于这个值不会进行训练
batch_size = 64  # 小批量随机梯度下降中的批量大小
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")  # 训练设备# 初始化一些算法部件
env = gym.make('CartPole-v1')  # 创建山地车游戏环境
replay_buffer = ReplayBuffer(buffer_size)  # 创建经验回放池
state_dim = env.observation_space.shape[0]  # 状态维度
action_dim = env.action_space.n  # 动作维度
agent = DQN(state_dim, hidden_dim, action_dim, lr, gamma, epsilon, target_update, device)  # 创建DQN算法智能体# 开启训练过程
return_list = []  # 记录每一轮的奖励
for i in range(10):with tqdm(total=int(num_episodes / 10), desc='Iteration %d' % i) as pbar:for i_episode in range(int(num_episodes / 10)):episode_return = 0  # 每轮的奖励state, info = env.reset()  # 重置环境,返回初始状态done = False# 开启一轮游戏while not done:# 智能体进行动作,完成一个回合action = agent.take_action(state)  # 智能体进行动作next_state, reward, terminated, truncated, info = env.step(action)  # 执行动作done = terminated or truncated  # 判断游戏是否结束replay_buffer.add(state, action, reward, next_state, done)  # 将数据加入经验池state = next_state  # 更新状态episode_return += reward  # 更新这一轮的奖励# 当buffer数据的数量超过一定值后,才进行Q网络训练if replay_buffer.size() > minimal_size:b_s, b_a, b_r, b_ns, b_d = replay_buffer.sample(batch_size)  # 随机从经验池中获取batch_size个数据transition_dict = {'states': b_s,'actions': b_a,'next_states': b_ns,'rewards': b_r,'dones': b_d}  # 将数据转换为字典格式# 更新智能体参数agent.update(transition_dict)return_list.append(episode_return)  # 记录这一轮的奖励if (i_episode + 1) % 10 == 0:pbar.set_postfix({'episode':'%d' % (num_episodes / 10 * i + i_episode + 1),'return':'%.3f' % np.mean(return_list[-10:])})pbar.update(1)
Iteration 0:   0%|          | 0/50 [00:00<?, ?it/s]C:\Users\sswun\AppData\Local\Temp\ipykernel_22684\3177917159.py:27: UserWarning: Creating a tensor from a list of numpy.ndarrays is extremely slow. Please consider converting the list to a single numpy.ndarray with numpy.array() before converting to a tensor. (Triggered internally at ..\torch\csrc\utils\tensor_new.cpp:264.)state = torch.tensor([state], dtype=torch.float).to(self.device)
Iteration 0: 100%|██████████| 50/50 [00:03<00:00, 13.03it/s, episode=50, return=96.400]
Iteration 1: 100%|██████████| 50/50 [00:30<00:00,  1.63it/s, episode=100, return=223.100]
Iteration 2: 100%|██████████| 50/50 [01:03<00:00,  1.27s/it, episode=150, return=403.800]
Iteration 3: 100%|██████████| 50/50 [01:05<00:00,  1.30s/it, episode=200, return=232.000]
Iteration 4: 100%|██████████| 50/50 [00:44<00:00,  1.12it/s, episode=250, return=338.800]
Iteration 5: 100%|██████████| 50/50 [01:18<00:00,  1.58s/it, episode=300, return=346.500]
Iteration 6: 100%|██████████| 50/50 [01:27<00:00,  1.76s/it, episode=350, return=500.000]
Iteration 7: 100%|██████████| 50/50 [01:28<00:00,  1.76s/it, episode=400, return=360.000]
Iteration 8: 100%|██████████| 50/50 [01:18<00:00,  1.57s/it, episode=450, return=266.300]
Iteration 9: 100%|██████████| 50/50 [01:22<00:00,  1.65s/it, episode=500, return=473.800]

可以看到,对应车杆游戏,DQN算法具有一定的收敛性,一般可以达到较好的效果,但是算法并不是十分稳定。

step3:DQN算法改进——Dueling DQN

Dueling DQN算法是DQN算法的改进,它通过引入一个优势函数来改进Q网络的输出。优势函数的引入使得网络能够更好地学习到状态价值函数。在 Dueling DQN 中,Q 网络被建模为:
Q η , α , β ( s , a ) = V η , α ( s ) + A η , β ( s , a ) Q_{\eta, \alpha, \beta}(s, a)=V_{\eta, \alpha}(s)+A_{\eta, \beta}(s, a) Qη,α,β(s,a)=Vη,α(s)+Aη,β(s,a)

其中, V η , α ( s ) V_{\eta, \alpha}(s) Vη,α(s) 表示状态价值函数, A η , β ( s , a ) A_{\eta, \beta}(s, a) Aη,β(s,a) 表示优势函数。 η , α , β \eta, \alpha, \beta η,α,β 分别是网络的参数。其中 η \eta η 为状态价值函数和优势函数的共享参数, α \alpha α β \beta β 分别表示状态价值函数和优势函数的各自独立部分参数。在这样的模型下,我们不再让神经网络直接输出Q值,而是训练神经网络的最后几层的两个分支,分别输出状态价值函数和优势函数,再求和得到Q值。

Dueling DQN 与 DQN 相比的差异只是在网络结构上,大部分代码依然可以继续沿用。我们定义状态价值函数和优势函数的复合神经网络VAnet。

class VAnet(torch.nn.Module):''' 只有一层隐藏层的A网络和V网络 '''def __init__(self, state_dim, hidden_dim, action_dim):super(VAnet, self).__init__()self.fc1 = torch.nn.Linear(state_dim, hidden_dim)  # 共享网络部分self.fc_A = torch.nn.Linear(hidden_dim, action_dim)self.fc_V = torch.nn.Linear(hidden_dim, 1)def forward(self, x):A = self.fc_A(F.relu(self.fc1(x)))V = self.fc_V(F.relu(self.fc1(x)))Q = V + A - A.mean(1).view(-1, 1)  # Q值由V值和A值计算得到# 注意:此处我们不是单纯的让Q = V + A,而是让Q = V + A - A.mean(1).view(-1, 1),# 这样做是因为Q值获得不唯一性问题,这样做可以使得Q值计算及网络更新更加稳定return Q

接下来让我们利用该网络再次进行训练

# 初始化一些算法部件
lr = 1e-3  # 学习率
env = gym.make('CartPole-v1')  # 创建山地车游戏环境
replay_buffer = ReplayBuffer(buffer_size)  # 创建经验回放池
state_dim = env.observation_space.shape[0]  # 状态维度
action_dim = env.action_space.n  # 动作维度
agent = DQN(state_dim, hidden_dim, action_dim, lr, gamma, epsilon, target_update, device)  # 创建DQN算法智能体
# 将智能体中网络结构更新为VAnet
agent.q_net = VAnet(state_dim, hidden_dim, agent.action_dim).to(device)  # Q网络
agent.target_q_net = VAnet(state_dim, hidden_dim, agent.action_dim).to(device)  # 目标网络
agent.optimizer = torch.optim.Adam(agent.q_net.parameters(), lr=lr) # 使用Adam优化器# 开启训练过程
return_list = []  # 记录每一轮的奖励
for i in range(10):with tqdm(total=int(num_episodes / 10), desc='Iteration %d' % i) as pbar:for i_episode in range(int(num_episodes / 10)):episode_return = 0  # 每轮的奖励state, info = env.reset()  # 重置环境,返回初始状态done = False# 开启一轮游戏while not done:# 智能体进行动作,完成一个回合action = agent.take_action(state)  # 智能体进行动作next_state, reward, terminated, truncated, info = env.step(action)  # 执行动作done = terminated or truncated  # 判断游戏是否结束replay_buffer.add(state, action, reward, next_state, done)  # 将数据加入经验池state = next_state  # 更新状态episode_return += reward  # 更新这一轮的奖励# 当buffer数据的数量超过一定值后,才进行Q网络训练if replay_buffer.size() > minimal_size:b_s, b_a, b_r, b_ns, b_d = replay_buffer.sample(batch_size)  # 随机从经验池中获取batch_size个数据transition_dict = {'states': b_s,'actions': b_a,'next_states': b_ns,'rewards': b_r,'dones': b_d}  # 将数据转换为字典格式# 更新智能体参数agent.update(transition_dict)return_list.append(episode_return)  # 记录这一轮的奖励if (i_episode + 1) % 10 == 0:pbar.set_postfix({'episode':'%d' % (num_episodes / 10 * i + i_episode + 1),'return':'%.3f' % np.mean(return_list[-10:])})pbar.update(1)
Iteration 0: 100%|██████████| 50/50 [00:04<00:00, 11.09it/s, episode=50, return=36.400]
Iteration 1: 100%|██████████| 50/50 [00:30<00:00,  1.64it/s, episode=100, return=333.000]
Iteration 2: 100%|██████████| 50/50 [00:51<00:00,  1.03s/it, episode=150, return=413.100]
Iteration 3: 100%|██████████| 50/50 [01:01<00:00,  1.24s/it, episode=200, return=480.200]
Iteration 4: 100%|██████████| 50/50 [01:02<00:00,  1.25s/it, episode=250, return=500.000]
Iteration 5: 100%|██████████| 50/50 [01:03<00:00,  1.28s/it, episode=300, return=459.900]
Iteration 6: 100%|██████████| 50/50 [01:00<00:00,  1.21s/it, episode=350, return=434.400]
Iteration 7: 100%|██████████| 50/50 [00:52<00:00,  1.04s/it, episode=400, return=411.300]
Iteration 8: 100%|██████████| 50/50 [00:57<00:00,  1.15s/it, episode=450, return=433.000]
Iteration 9: 100%|██████████| 50/50 [00:59<00:00,  1.19s/it, episode=500, return=407.300]

对比DQN算法和Dueling DQN算法的训练结果,可以看到有时候Dueling DQN算法可能效果不如DQN,但是Dueling DQN算法的稳定性相对较好,在大部分情况下表现比DQN更好。

让我们看看智能体的表现吧!

import matplotlib.pyplot as plt
import gym
%matplotlib inline
from IPython import displaydef show_state(env, step=0, info=""):plt.figure(3)plt.clf()plt.imshow(env.render())plt.title("Step: %d %s" % (step, info))plt.axis('off')display.clear_output(wait=True)display.display(plt.gcf())env = gym.make('CartPole-v1', render_mode='rgb_array')
state, info = env.reset()
for _ in range(1000):action = agent.take_action(state)state, reward, terminated, truncated, info = env.step(action)done = truncated or terminatedshow_state(env, action, info)if done:state, info = env.reset()
env.close()

车杆游戏
可以看到智能体表现已经很好了,但不难发现,智能体并未学习到一个最好的策略,读者可以结合游戏本身,对算法进行一些改进。

这篇关于深度强化学习开端——DQN算法求解车杆游戏的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

SpringCloud动态配置注解@RefreshScope与@Component的深度解析

《SpringCloud动态配置注解@RefreshScope与@Component的深度解析》在现代微服务架构中,动态配置管理是一个关键需求,本文将为大家介绍SpringCloud中相关的注解@Re... 目录引言1. @RefreshScope 的作用与原理1.1 什么是 @RefreshScope1.

SpringBoot实现MD5加盐算法的示例代码

《SpringBoot实现MD5加盐算法的示例代码》加盐算法是一种用于增强密码安全性的技术,本文主要介绍了SpringBoot实现MD5加盐算法的示例代码,文中通过示例代码介绍的非常详细,对大家的学习... 目录一、什么是加盐算法二、如何实现加盐算法2.1 加盐算法代码实现2.2 注册页面中进行密码加盐2.

Python 中的异步与同步深度解析(实践记录)

《Python中的异步与同步深度解析(实践记录)》在Python编程世界里,异步和同步的概念是理解程序执行流程和性能优化的关键,这篇文章将带你深入了解它们的差异,以及阻塞和非阻塞的特性,同时通过实际... 目录python中的异步与同步:深度解析与实践异步与同步的定义异步同步阻塞与非阻塞的概念阻塞非阻塞同步

Java时间轮调度算法的代码实现

《Java时间轮调度算法的代码实现》时间轮是一种高效的定时调度算法,主要用于管理延时任务或周期性任务,它通过一个环形数组(时间轮)和指针来实现,将大量定时任务分摊到固定的时间槽中,极大地降低了时间复杂... 目录1、简述2、时间轮的原理3. 时间轮的实现步骤3.1 定义时间槽3.2 定义时间轮3.3 使用时

Redis中高并发读写性能的深度解析与优化

《Redis中高并发读写性能的深度解析与优化》Redis作为一款高性能的内存数据库,广泛应用于缓存、消息队列、实时统计等场景,本文将深入探讨Redis的读写并发能力,感兴趣的小伙伴可以了解下... 目录引言一、Redis 并发能力概述1.1 Redis 的读写性能1.2 影响 Redis 并发能力的因素二、

最新Spring Security实战教程之表单登录定制到处理逻辑的深度改造(最新推荐)

《最新SpringSecurity实战教程之表单登录定制到处理逻辑的深度改造(最新推荐)》本章节介绍了如何通过SpringSecurity实现从配置自定义登录页面、表单登录处理逻辑的配置,并简单模拟... 目录前言改造准备开始登录页改造自定义用户名密码登陆成功失败跳转问题自定义登出前后端分离适配方案结语前言

Java进阶学习之如何开启远程调式

《Java进阶学习之如何开启远程调式》Java开发中的远程调试是一项至关重要的技能,特别是在处理生产环境的问题或者协作开发时,:本文主要介绍Java进阶学习之如何开启远程调式的相关资料,需要的朋友... 目录概述Java远程调试的开启与底层原理开启Java远程调试底层原理JVM参数总结&nbsMbKKXJx

如何通过Golang的container/list实现LRU缓存算法

《如何通过Golang的container/list实现LRU缓存算法》文章介绍了Go语言中container/list包实现的双向链表,并探讨了如何使用链表实现LRU缓存,LRU缓存通过维护一个双向... 目录力扣:146. LRU 缓存主要结构 List 和 Element常用方法1. 初始化链表2.

Redis 内存淘汰策略深度解析(最新推荐)

《Redis内存淘汰策略深度解析(最新推荐)》本文详细探讨了Redis的内存淘汰策略、实现原理、适用场景及最佳实践,介绍了八种内存淘汰策略,包括noeviction、LRU、LFU、TTL、Rand... 目录一、 内存淘汰策略概述二、内存淘汰策略详解2.1 ​noeviction(不淘汰)​2.2 ​LR

Python与DeepSeek的深度融合实战

《Python与DeepSeek的深度融合实战》Python作为最受欢迎的编程语言之一,以其简洁易读的语法、丰富的库和广泛的应用场景,成为了无数开发者的首选,而DeepSeek,作为人工智能领域的新星... 目录一、python与DeepSeek的结合优势二、模型训练1. 数据准备2. 模型架构与参数设置3