Keras深度学习实战(43)——深度Q学习算法

news2025/1/9 16:48:57

Keras深度学习实战(43)——深度Q学习算法

    • 0. 前言
    • 1. Q 学习简介
    • 2. 使用 Q 学习进行 FrozenLake 游戏
      • 2.1 FrozenLake 环境分析
      • 2.2 模型分析
      • 2.3 使用 Q 学习算法解决 FrozenLake 问题
    • 3. 使用深度 Q 学习进行 CartPole 游戏
      • 3.1 问题分析
      • 3.2 模型分析
      • 3.3 使用深度 Q 学习算法解决 CartPole 问题
    • 小结
    • 系列链接

0. 前言

在《强化学习基础》一节中,我们学习了强化学习的基本概念,并且介绍了如何在给定状态下采取随机动作。此外,我们还使用自定义环境,计算下一个状态、动作和奖励。在本节中,我们首先介绍 Q 学习与深度 Q 学习的基本思想,然后利用 OpenAIGym 库模拟 Frozen LakeCartPole 问题,并使用 Keras 实现 Q 学习解决这两个问题。

1. Q 学习简介

状态-动作值函数 (State-Action Value Function),简称 Q 函数,定义为从状态 s t s_t st 同时执行动作 a t a_t at 的双重设定下,在策略 π π π 的控制下能获得的期望回报值:

Q π ( s t , a t ) = E τ ∼ p ( τ ) [ R ( τ t : T ) ∣ τ a t = a t , τ s t = s t ] Q^π(s_t, a_t) = E_{τ∼p(τ)}[R(τ_{t:T})|τ_{a_t} = a_t , τ_{s_t}= s_t ] Qπ(st,at)=Eτp(τ)[R(τt:T)τat=at,τst=st]

在最优策略 π ∗ ( a ∣ s ) π^*(a|s) π(as) 下,具有以下关系:

Q ∗ ( s t , a t ) = m a x π Q π ( s t , a t ) π ∗ = a r g m a x a t Q ∗ ( s t , a t ) \begin{split} Q^*(s_t, a_t)=\undersetπ{max}Q^π(s_t, a_t)\\ π^*=\underset{a_t}{argmax}Q^*(s_t, a_t) \end{split} Q(st,at)=πmaxQπ(st,at)π=atargmaxQ(st,at)

Q 学习 (Q Learning) 算法通过下式估计 Q ∗ ( s t , a t ) Q^*(s_t, a_t) Q(st,at) 函数,并利用 π ε ( s t ) \pi^{\varepsilon}(s_t) πε(st) 策略来获得策略改进:

Q ∗ ( s t , a t ) ← Q ∗ ( s t , a t ) + α ( r ( s t , a t ) + γ m a x a t + 1 Q ∗ ( s t + 1 , a t + 1 ) − Q ∗ ( s t , a t ) ) Q^*(s_t, a_t)\larr Q^*(s_t, a_t)+\alpha(r(s_t,a_t)+\gamma\underset{a_{t+1}}{max}Q^*(s_{t+1},a_{t+1})-Q^*(s_t, a_t)) Q(st,at)Q(st,at)+α(r(st,at)+γat+1maxQ(st+1,at+1)Q(st,at))

深度 Q 网络 (Deep Q Network, DQN) 用深层的神经网络来参数化 Q ∗ ( s t , a t ) Q^*(s_t, a_t) Q(st,at) 函数,并利用梯度下降算法循环更新 Q 网络。

2. 使用 Q 学习进行 FrozenLake 游戏

2.1 FrozenLake 环境分析

FrozenLake 环境如下所示:

FrozenLake 环境

智能体从状态 S 开始,目标是通过尽可能避免状态 H 来达到状态 G,智能体可以处于 16 种可能的状态。此外,智能体可以采取四种可能的动作(向上,向下,向右或向左移动)。
我们将定义一个 q-表,其中有 16 行对应于 16 个状态,四列对应于在每种状态下可以采取的四个动作:

  • 在当前状态下采取动作的值=在当前状态下采取动作的值+ 1*(奖励+折扣系数*在下一个状态下采取的最佳动作的值-在当前状态下采取动作的值)

我们将学习率设为 1,以使状态下某个动作的值更新不会急剧变化:

  • 在当前状态下采取动作的值=在当前状态下采取动作的值+学习率*(奖励+折扣系数*下一种状态采取的最佳可能行动的值-在当前状态下采取动作的值)

根据以上公式更新 q-表,以便确定在不同状态下可以采取的最佳动作。

2.2 模型分析

在实际解决问题之前,我们将首先探讨用于解决 FrozenLake 问题的模型策略:

  • 使用 OpenAI的Gym 实例化 FrozenLake 环境
  • 初始化形状为 16 x 4q-表,其中所有元素值为 0
  • 在给定状态下选择动作时,采用探索-利用法:
    • 在早期迭代中探索,因为我们不确定在游戏的最初几个回合中应采取的最佳动作。
    • 但是,随着我们对游戏的了解越来越多,我们在采取可能的最佳动作的同时,仍然采取一定概率的随机行动(随着回合的增加,概率逐渐降低)来利用所学到的知识
  • 在给定的回合中:
    • 根据我们是否尝试探索或利用来选择动作
    • 确定新的状态和奖励,并通过执行上一步中选择的动作来检查游戏是否结束
  • 初始化学习率参数和折扣因子
  • 通过使用上述公式来更新在 q-表中的状态下采取所选动作的值
  • 重复上述步骤,直到游戏结束
  • 重复进行 1000 回合以上游戏
  • 检查 q-表以识别在给定状态下要采取的最佳动作
  • 绘制智能体的路径,以使其根据 q-表在状态下执行动作

2.3 使用 Q 学习算法解决 FrozenLake 问题

(1) 导入 Gym 以及其他相关库,Gym 是用于开发和比较强化学习算法的工具包,它支持多种模拟环境,关于 Gym 更多介绍,可以参考《强化学习环境配置》:

import gym
from gym.envs.registration import register
import random

(2) 注册并创建环境,为了简化游戏,我们修改了 FrozenLake 环境的默认设定,最重要的设置是令冰面并不滑,每一步动作的结果都是确定,而冰面滑时,即时指定向左的动作,智能体也可能会滑动到其他位置,例如向下滑:

register(
    id = 'FrozenLakeNotSlippery-v1',
    entry_point = 'gym.envs.toy_text:FrozenLakeEnv',
    kwargs = {'map_name': '4x4', 'is_slippery': False},
    max_episode_steps = 100,
    reward_threshold = 0.8196
)
env = gym.make('FrozenLakeNotSlippery-v1')

(3) 检查创建的环境,reset() 方法用于重置游戏环境:

env.reset()
env.render()

渲染出的 FrozenLake 环境如下:

FrozenLake 环境

(4) 我们也可以检查环境中状态空间与动作空间数量,由于我们使用的 FrozenLake 环境包括 4 x 4 的网格,共有 16 个状态。因此,共有 16 个观测值:

print(env.observation_space)

print(env.action_space.n)

输出结果如下所示:

Discrete(16)
4

(5) 接下来,从动作空间中采样一个动作,并使用 step() 方法采取该动作,以生成新状态、奖励、是否完成游戏的标记以及有关该步骤的其他信息:

action = env.action_space.sample()

new_state, reward, is_done, info = env.step(action)

(6) 初始化 q-表,因为有 16 种状态,每种状态有 4 种可能的动作,因此 q-表的形状为 (16, 4)

import numpy as np
qtable = np.zeros((16,4))

(7) 初始化超参数后,运行 FrozenLake 游戏多个回合:

total_episodes=15000
learning_rate=0.8
max_steps=99
gamma=0.95
epsilon=1
max_epsilon=1
min_epsilon=0.01
decay_rate=0.005

rewards=[]
for episode in range(total_episodes):
    state=env.reset()
    step=0
    done=False
    total_rewards=0

(8) 定义要采取的策略。如果 eps (一个介于 01 之间的随机数)小于 0.5,我们将进行探索;否则,我们会进行利用(考虑 q-表中的最佳操作):

rewards=[]
for episode in range(total_episodes):
    state=env.reset()
    step=0
    done=False
    total_rewards=0
    for step in range(max_steps):
        exp_exp_tradeoff=random.uniform(0,1)
        
        # Exploitation:
        if exp_exp_tradeoff>epsilon:
            action=np.argmax(qtable[state,:])
        else:
            # Exploration
            action=env.action_space.sample()

(9) 获取新状态和奖励,并通过在给定步骤中采取动作获取游戏是否完成:

        new_state,reward,done,info=env.step(action)

(10) 基于当前状态状态执行的操作更新 q-表,并使用在当前状态下执行动作后获得的新状态来更新状态:

        # Update the Q
        qtable[state,action]=qtable[state,action]+\
                learning_rate*(reward+gamma*np.max(qtable[new_state,:])-qtable[state,action])
        total_rewards+=reward
        state=new_state

(11) 当本次游戏回合结束后,我们继续进行下一个新的游戏回合,同时,我们更新用于决定要进行探索还是利用的随机因子 (eps):

        if done:
            break
    episode+=1
    epsilon=min_epsilon+(max_epsilon-min_epsilon)*np.exp(decay_rate*episode)
    rewards.append(total_rewards)

(12) 一旦建立了 q-表,我们就可以根据 q-表得到的最佳动作指导智能体以进行动作:

print("Score over Time:",sum(rewards)/total_episodes)
print(qtable)

打印出的 q-表如下所示:

[[0.73509189 0.77378094 0.77378094 0.73509189]
 [0.73509189 0.         0.81450625 0.77378094]
 [0.77378094 0.857375   0.77378094 0.81450625]
 [0.81450625 0.         0.77378094 0.77378094]
 [0.77378094 0.81450625 0.         0.73509189]
 [0.         0.         0.         0.        ]
 [0.         0.9025     0.         0.81450625]
 [0.         0.         0.         0.        ]
 [0.81450625 0.         0.857375   0.77378094]
 [0.81450625 0.9025     0.9025     0.        ]
 [0.857375   0.95       0.         0.857375  ]
 [0.         0.         0.         0.        ]
 [0.         0.         0.         0.        ]
 [0.         0.9025     0.95       0.857375  ]
 [0.9025     0.95       1.         0.9025    ]
 [0.         0.         0.         0.        ]]

(13) 继续使用一个新回合,使用得到的 q-表计算智能体到达最终目标经过的最佳路径:

env.reset()
for episode in range(1):
    state=env.reset()
    step=0
    done=False
    print("-----------------------")
    print("Episode",episode)
    for step in range(max_steps):
        env.render()
        action=np.argmax(qtable[state,:])
        print(action)
        new_state,reward,done,info=env.step(action)
        
        if done:
            env.render()
            print("Number of Steps",step+1)
            break
        state=new_state
env.close()

得到的最佳路径如下所示:

Episode 0

SFFF
FHFH
FFFH
HFFG
1
  (Down)
...
2
  (Right)
SFFF
FHFH
FFFH
HFFG
Number of Steps 6

除此之外,我们也可以使用冰面较滑的环境进行相同的实验。

3. 使用深度 Q 学习进行 CartPole 游戏

在前面的部分中,我们学习了基于 q-表值执行动作的方法。但是,达到最佳值需要耗费大量时间,因为智能体必须通过进行多次游戏才能得到最佳 q-表。接下来,我们将学习如何使用神经网络,以便比使用 q-表时更快地达到最佳值。

3.1 问题分析

在本节中,我们将使用 CartPole 环境,其中智能体可能的动作包括向右或向左移动推车,以便平衡推车上的杆。此外,环境中的状态信息包括推车位置,推车速度,杆与推车的夹角和杆顶端处的速度。关于 CartPole 环境更详细的说明可以参考官方网页或《强化学习环境配置》。
CartPole 环境中,杆通过一个未固定的接头连接到小推车,小推车沿着无摩擦的轨道移动。该系统通过向推车施加 +1-1 的力来控制推车移动。杆开始时垂直于推车,智能体的目标是防止杆跌落。杆保持直立的每个时间戳都可以得到 +1 的奖励。当杆与垂直方向的夹角超过 15 度或推车距离中心点超过 2.4 个单位以上时,回合结束。

3.2 模型分析

为了保持杆的平衡,我们使用深度 Q 学习算法,利用神经网络来预测智能体需要采取的最佳动作,训练神经网络的策略如下:

  • 存储有关状态值、采取动作和获得奖励的信息:
    • 如果游戏未结束,则奖励为 1,否则为 0
  • 开始时,神经网络基于随机初始化的权重进行预测,模型的输出层具有两个节点,对应于两个可能动作的新状态值
  • 新状态值基于最大化新状态值的动作
  • 如果游戏没有结束,使用奖励和新状态的最大状态值与折扣因子之积来更新当前状态值
  • 从先前获得的更新后的当前状态值中覆盖动作的值:
    • 如果当前步骤中执行的动作导致游戏结束,则当前状态下的动作值为 0
    • 否则,当前步骤中的值是一个正数
    • 令模型找出要采取的正确动作
  • 另外,可以使用这种方法来指定奖励为 0 时动作是错误的;但是,由于不确定在奖励为 1 时是否是正确的动作,因此仅针对执行的动作对其进行更新,并保持新状态的值不变
  • 将状态值追加到输入数组,以及在当前状态下采取动作的值作为输出数组
  • 对模型进行拟合,以使数据样本的均方误差最小
  • 最后,随着回合数的增加不断减少探索

3.3 使用深度 Q 学习算法解决 CartPole 问题

(1) 导入所需库,创建 CartPole 环境并存储动作空间大小和状态空间大小:

import gym
import numpy as np
import random
from keras.models import Sequential
from keras.layers import Dense
from keras.optimizers import Adam
from collections import deque

env = gym.make('CartPole-v0')
state_size = env.observation_space.shape[0]
action_size = env.action_space.n

CartPole 环境如下所示:

CartPole

(2) 构建并编译神经网络模型:

model=Sequential()
model.add(Dense(24,input_dim=state_size,activation='relu'))
model.add(Dense(24,activation='relu'))
model.add(Dense(2,activation='linear'))
model.compile(loss='mse',optimizer=Adam(lr=0.01))

(3) 初始化超参数以及所需列表:

memory = deque(maxlen=2000)
gamma = 0.95 # discount rate
epsilon = 1.0 # exploration rate
epsilon_min = 0.01
epsilon_decay = 0.995
done = False
batch_size=32

(4) 定义函数,该函数接受神经网络模型,批大小和 epsilon (用于确定探索还是利用)作为参数。首先,获取 batch_size 大小的随机样本,其中 memory 包括状态 (state)、动作 (action)、奖励 (reward) 和新状态 (next_state) 等。如果游戏未结束,更新所采取动作的奖励;否则,奖励为 0。此外,模型可以预测采取某项动作的值(因为该模型在输出中有 2 个节点——每个节点都提供对采取某一动作的输出)。函数返回更新的模型和探索/利用系数 (epsilon):

def replay(model, batch_size,epsilon):
    epsilon_min = 0.01
    epsilon_decay = 0.995
    minibatch = random.sample(memory, batch_size)
    for state, action, reward, next_state, done in minibatch:
        target = reward
        if not done:
            target = (reward + gamma *np.amax(model.predict(next_state)[0]))
        new_action_value = model.predict(state)
        new_action_value[0][action] = target
        model.fit(state,new_action_value, epochs=1, verbose=0)
    if epsilon > epsilon_min:
        epsilon *= epsilon_decay
    return model,epsilon

(5) 进行 CartPole 游戏多个回合,并记录智能体获得的分数。此外,确保模型根据 epsilon 值指示智能体采取的动作:

episodes=200
maxsteps=200
score_list = []
for e in range(episodes):
    state = env.reset()
    state = np.reshape(state, [1, state_size])

在以上代码中,共进行 200 个回合 CartPole 游戏,在每次回合开始时重置环境,我们还需要整形状态 state,以便可以将其传递给神经网络模型。

(6) 接下来,我们将基于探索参数 (epsilon) 采取行动,大约有 epsilon 的概率会采取随机动作 (env.actionspace.sample()),在其他情况下,会利用模型的预测采取相应动作:

    for step in range(maxsteps):
        if np.random.rand()<=epsilon:
            action=env.action_space.sample()
        else:
            action = np.argmax(model.predict(state)[0])

(7) 接下来,在每个时间戳执行一个动作并获取下一个状态、奖励以及有关游戏是否结束的信息。如果游戏结束,使用 -10 作为奖励值,表明执行了不正确的动作。此外,提取下一个状态并将其添加到 memory 中。这样,就为要训练的模型创建了一个数据集,该模型采用当前状态和奖励来计算两个可能动作的奖励:

        next_state, reward, done, _ = env.step(action)
        reward = reward if not done else -10
        next_state = np.reshape(next_state, [1, state_size])
        memory.append((state, action, reward, next_state, done))

(8) 如果游戏结束,我们将记录得分(游戏过程中的时间戳数);否则,将更新模型。此外,仅当 memory 中的数据样本数与大于预定义的批大小时,才会更新模型:

        state = next_state
        if done:
            print("episode: {}/{}, score: {}, exp prob: {:.2}".format(e, episodes, step, epsilon))
            score_list.append(step)
            break
        if len(memory) > batch_size:
            model,epsilon=replay(model, batch_size,epsilon)

(9) 绘制随着时间的增加,每个回合的得分情况:

import matplotlib.pyplot as plt
plt.plot(range(1,len(score_list)+1),score_list)
plt.title('Game score over increasing episodes')
plt.show()

游戏过程得分情况

小结

强化学习使用马尔可夫决策过程的形式化框架,使用状态、动作和奖励定义学习型智能体与环境的交互过程。Q 学习作为一种经典的强化学习算法,可以直接优化一个可迭代计算的 Q 函数,能够比较可用动作的预期效用,而不需要环境模型。在本节中,我们首先介绍 Q 学习与深度 Q 学习的基本思想,并使用 Keras 实现 Q 学习解决 Frozen LakeCartPole 问题。

系列链接

Keras深度学习实战(1)——神经网络基础与模型训练过程详解
Keras深度学习实战(2)——使用Keras构建神经网络
Keras深度学习实战(3)——神经网络性能优化技术
Keras深度学习实战(4)——深度学习中常用激活函数和损失函数详解
Keras深度学习实战(5)——批归一化详解
Keras深度学习实战(6)——深度学习过拟合问题及解决方法
Keras深度学习实战(7)——卷积神经网络详解与实现
Keras深度学习实战(8)——使用数据增强提高神经网络性能
Keras深度学习实战(9)——卷积神经网络的局限性
Keras深度学习实战(10)——迁移学习详解
Keras深度学习实战(11)——可视化神经网络中间层输出
Keras深度学习实战(12)——面部特征点检测
Keras深度学习实战(13)——目标检测基础详解
Keras深度学习实战(14)——从零开始实现R-CNN目标检测
Keras深度学习实战(15)——从零开始实现YOLO目标检测
Keras深度学习实战(16)——自编码器详解
Keras深度学习实战(17)——使用U-Net架构进行图像分割
Keras深度学习实战(18)——语义分割详解
Keras深度学习实战(19)——使用对抗攻击生成可欺骗神经网络的图像
Keras深度学习实战(20)——DeepDream模型详解
Keras深度学习实战(21)——神经风格迁移详解
Keras深度学习实战(22)——生成对抗网络详解与实现
Keras深度学习实战(23)——DCGAN详解与实现
Keras深度学习实战(24)——从零开始构建单词向量
Keras深度学习实战(25)——使用skip-gram和CBOW模型构建单词向量
Keras深度学习实战(26)——文档向量详解
Keras深度学习实战(27)——循环神经详解与实现
Keras深度学习实战(28)——利用单词向量构建情感分析模型
Keras深度学习实战(29)——长短时记忆网络详解与实现
Keras深度学习实战(30)——使用文本生成模型进行文学创作
Keras深度学习实战(31)——构建电影推荐系统
Keras深度学习实战(32)——基于LSTM预测股价
Keras深度学习实战(33)——基于LSTM的序列预测模型
Keras深度学习实战(34)——构建聊天机器人
Keras深度学习实战(35)——构建机器翻译模型
Keras深度学习实战(36)——基于编码器-解码器的机器翻译模型
Keras深度学习实战(37)——手写文字识别
Keras深度学习实战(38)——图像字幕生成
Keras深度学习实战(39)——音乐音频分类
Keras深度学习实战(40)——音频生成
Keras深度学习实战(41)——语音识别
Keras深度学习实战(42)——强化学习基础

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

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

相关文章

通讯录怎么恢复?在 手机上检索找回已删除的电话号码的3种方式

不幸的是&#xff0c;我从手机中删除了一些号码&#xff0c;因此它也从帐户中删除了。我想恢复它们或将我的帐户恢复到一周前我拥有这些号码的日期。— 来自 Android 用户 像上述用户一样&#xff0c;您可能已经删除了一些电话号码&#xff0c;但希望有一天能恢复它们。这种事故…

python数据分析及可视化(十八)Power BI(数据获取、整理、清洗以及可视化、Power Query的基本操作、删除及增加列)

Power BI 微软推出的数据分析和可视化工具&#xff0c;用于在组织中提供见解&#xff0c;是商业分析工具&#xff0c;让视觉对象分析触手可及&#xff0c;可以创建交互式数据可视化效果和报表&#xff0c;连接数百个数据源、简化、准备数据等&#xff0c;并提供相应的分析&…

虚拟机Ubuntu设置固定IP与主机相互通讯

虚拟机Ubuntu设置固定IP与主机相互通讯1. 写在最前1.1 最好了解的预备知识1.2 虚拟机与主机三种连接方式1.3 写在最前2. VMware 虚拟机Ubuntu系统与主机共享IP2.1 配置VMware桥接网卡2.2 设置虚拟机为固定IP2.3 Vmware 虚拟机与主机互相通讯3. VirtualBox虚拟机Ubuntu系统与主机…

ContrastMask: Contrastive Learning to Segment Every Thing

摘要 部分监督实例分割是一种通过学习有限的base类和带注释的掩码来从novel类别中分割对象的任务&#xff0c;从而消除了沉重的注释负担。解决这一问题的关键是建立一个有效的类不可知掩码分割模型。与以前只在base类别上学习此类模型的方法不同&#xff0c;在本文中&#xff…

Nginx root 以及alias差别

1. 前言 今天的目的主要是梳理下在 nginx 中 root 以及 alias 在用法上有什么不同。其实这个问题看起来很简单。但是对于前端同学而言还是很困难的&#xff0c;毕竟有的前端同学都没弄过服务器 2. 结论 root 以及 alias 都是对 url 发起根目录进行控制。但是颗粒度有所不同roo…

【深基18.例3】查找文献(C++,图的遍历)

题目描述 小K 喜欢翻看洛谷博客获取知识。每篇文章可能会有若干个&#xff08;也有可能没有&#xff09;参考文献的链接指向别的博客文章。小K 求知欲旺盛&#xff0c;如果他看了某篇文章&#xff0c;那么他一定会去看这篇文章的参考文献&#xff08;如果他之前已经看过这篇参…

JavaScript 中如何代理 Set(集合) 和 Map(映射)

ECMAScript6 中 Set 和 Map 的代理方法上一节&#xff1a;《JavaScript 中如何代理数组 》| 下一节&#xff1a;《JavaScript 中的反射&#xff08;Reflect&#xff09;原理与应用 》今日正在编写中&#xff0c;未完待续… jcLee95 邮箱 &#xff1a;291148484163.com CSDN…

Git分支操作

实操记录 假定非管理人员操作&#xff1a; 直推&#xff1a; 新建特性分支cbry&#xff1a; 刷新分支&#xff1a; checkout切换&#xff1a; 本地文件查看&#xff1a; 再merge&#xff1a; 就此&#xff0c;master的代码就合并到特性分支cbry&#xff1a; 新增内容&#xff…

数字化技术转型

这篇老生常谈&#xff08;我写过N次&#xff09;&#xff0c;是应一位IM群中的朋友的困惑问答汇集而成的。&#xff08;1&#xff09;学科分类我上学学的是计算机系。我上的大学一开始并没有计算机系&#xff0c;后来是电子工程系和数学系的老师抽调组成了计算机系。后来&#…

申请大学用的是IB预估分?

IB课程体系以其独特的优越性成为越来越多国际高中生的选择。如今全球共有3300多所高校接受IB成绩申请&#xff0c;其中包括美国常春藤盟校、英国G5在内的多所名校。 但是&#xff0c;大家知道吗&#xff0c;国内学习IB课程的学生是需要用预估分来申请大学的。今天&#xff0c;小…

多用户及时通信系统

目录1. QQ用户登录1.1 用户登录11.2 用户登录21.3 用户登录32. 拉取在线用户3. 无异常退出4. 私聊系统5. 群聊3. 发送文件3.1 服务端推送新闻3.2 离线留言和离线发文件1. QQ用户登录 1.1 用户登录1 qqcommon包下 User类序列化 Message消息类序列化 MessType接口 qqclient.ut…

拉伯杠杆平台|沪指上涨,大金融板块领涨,有股票连续5涨停!

A股周二上午全体小幅上涨&#xff0c;大金融集体上涨&#xff0c;推动指数上行&#xff0c;商场全体动摇不大。A50期货高开高走&#xff0c;盘中暴拉超2.6%。 不过&#xff0c;部分个股仍然动摇不小&#xff0c;有多只股票接连涨停。 别的&#xff0c;新股持续分解&#xff0c…

ATAC-seq分析:数据介绍(2)

1. 简介 ATACseq (Assay for Transposase-Accessible Chromatin using sequencing) 使用转座酶在测序前有效地片段化可访问的 DNA&#xff08;DNA可极性&#xff09;。结果提供了一种绘制可访问/开放染色质基因组范围的方法。 与其他技术相比&#xff0c;ATACseq 有几个优点&am…

嵌入式开发学习之--串口通讯(下)

提示&#xff1a;本篇来做一个关于串口的输入输出实验。 文章目录前言一、项目概况1.1、项目需求1.2、项目来源1.3、开发环境1.4、项目意义1.5、项目效果展示二、开发步骤2.1、涉及硬件电路2.2、项目代码2.2.1、串口配置总结前言 前一篇文章我们介绍了串口的几种类型以及串口标…

Linux Shell 编程,运算符,条件与分支,循环

Linux Shell 编程&#xff0c;运算符&#xff0c;条件与分支&#xff0c;循环1.Shell运算符2.判断语句3.for循环4.while循环1.Shell运算符 学习如何在shell中进行各种运算操作 案例&#xff1a;计算&#xff08;57&#xff09;3的值&#xff1a; #!/bin/bash res$(((57)*3)) …

游戏物体GameObject

在unity中所有游戏物体都是GameObject&#xff0c;这也是编程中的对象。 创建物体 在hierarchy窗口中&#xff0c;右击可以创建一个物体&#xff0c;当然也可以创建空物体。 物体属性 创建完物体后&#xff0c;一般可以在此处用这些工具来改变物体。 移动&#xff0c;旋转和…

戴尔科技集团助力中国石油大学打造现代数据中心

小的时候      总是幻想着      能够躺在床上上课      没想到现在竟然实现了      没错,对于当代大学生尤其是19级、20级来说,大学生活似乎是个虚无缥缈的词汇,因为相比与在校生活,在家上网课的时间可能会更长,一不留神就上了三年的“家里蹲”。      即使…

安装配置高度安全的匿名操作系统,利用暗网情报数据抓取工具获取普通人根本查看不到的信息

安装配置高度安全的匿名操作系统,利用暗网情报数据抓取工具获取普通人根本查看不到的信息。 Whonix匿名操作系统,Whonix 是一个专注于匿名,隐私和安全的操作系统。它基于Tor匿名网络,Debian GNU / Linux和隔离安全性。DNS泄漏是不可能的,即使具有root权限的恶意软件也无法…

“多点”开花,独立走向新零售

12月7日&#xff0c;亚洲最大的数字零售服务商多点Dmall正式向港交所递交招股说明书&#xff0c;在零售行业逐渐向线上线下一体化、店仓一体模式迈进之时&#xff0c;多点Dmall成为很多传统零售商转型路上的首选合作伙伴&#xff0c;给予了资本市场一定想象空间。 但也有观点认…

【Lua】ToLua逻辑热更新

1 前言 Lua基础语法 中系统介绍了 Lua 的语法体系&#xff0c;本文将进一步介绍 Unity3D 中基于 ToLua 实现逻辑热更新。 逻辑热更新是指&#xff1a;在保持程序正常运行的情况下&#xff0c;在后台修改代码逻辑&#xff0c;修改完成并推送到运行主机上&#xff0c;主机无缝接入…