通过 CartPole 游戏详细说明 PPO 优化过程

CartPole 介绍

在一个光滑的轨道上有个推车,杆子垂直微置在推车上,随时有倒的风险。系统每次对推车施加向左或者向右的力,但我们的目标是让杆子保持直立。杆子保持直立的每个时间单位都会获得 +1 的奖励。但是当杆子与垂直方向成 15 度以上的位置,或者推车偏离中心点超过 2.4 个单位后,这一轮局游戏结束。因此我们可以获得的最高回报等于 200 。我们这里就是要通过使用 PPO 算法来训练一个强化学习模型 actor-critic ,通过对比模型训练前后的游戏运行 gif 图,可以看出来我们训练好的模型能长时间保持杆子处于垂直状态。

库准备

python==3.10.9
tensorflow-gpu==2.10.0
imageio==2.26.1
keras==2.10,0
gym==0.20.0
pyglet==1.5.20
scipy==1.10.1

超参数设置

这段代码主要是导入所需的库,并设置了一些超参数。

    import numpy as npimport tensorflow as tffrom tensorflow import kerasfrom tensorflow.keras import layersimport gymimport scipy.signalimport timefrom tqdm import tqdmsteps_per_epoch = 5000  # 每个 epoch 中训练的步数epochs = 20  # 用于训练的 epoch 数gamma = 0.90  # 折扣因子,用于计算回报clip_ratio = 0.2  # PPO 算法中用于限制策略更新的比率policy_learning_rate = 3e-4  # 策略网络的学习率value_function_learning_rate = 3e-4  # 值函数网络的学习率train_policy_iterations = 80  # 策略网络的训练迭代次数train_value_iterations = 80  # 值函数网络的训练迭代次数lam = 0.97  # PPO 算法中的 λ 参数target_kl = 0.01  # PPO 算法中的目标 KL 散度hidden_sizes = (64, 64) # 神经网络的隐藏层维度 render = False    # 是否开启画面渲染,False 表示不开启

模型定义

(1)这里定义了一个函数 discounted_cumulative_sums,接受两个参数 xdiscount,该函数的作用是计算给定奖励序列 x 的折扣累计和,折扣因子 discount 是一个介于 0 和 1 之间的值,表示对未来奖励的折扣程度。 在强化学习中,折扣累计和是一个常用的概念,表示对未来奖励的折扣累加。

def discounted_cumulative_sums(x, discount):return scipy.signal.lfilter([1], [1, float(-discount)], x[::-1], axis=0)[::-1]

(2)这里定义了一个Buffer类,用于存储训练数据。类中有如下主要的函数:

  • init: 初始化函数,用于设置成员变量的初始值

  • store: 将观测值、行为、奖励、价值和对数概率存储到对应的缓冲区中

  • finish_trajectory: 结束一条轨迹,用于计算优势和回报,并更新 trajectory_start_index 的值

  • get: 获取所有缓冲区的值,用在训练模型过程中。在返回缓冲区的值之前,将优势缓冲区的值进行标准化处理,使其均值为 0 ,方差为 1

    class Buffer:def __init__(self, observation_dimensions, size, gamma=0.99, lam=0.95):self.observation_buffer = np.zeros( (size, observation_dimensions), dtype=np.float32 )self.action_buffer = np.zeros(size, dtype=np.int32)self.advantage_buffer = np.zeros(size, dtype=np.float32)self.reward_buffer = np.zeros(size, dtype=np.float32)self.return_buffer = np.zeros(size, dtype=np.float32)self.value_buffer = np.zeros(size, dtype=np.float32)self.logprobability_buffer = np.zeros(size, dtype=np.float32)self.gamma, self.lam = gamma, lamself.pointer, self.trajectory_start_index = 0, 0def store(self, observation, action, reward, value, logprobability):self.observation_buffer[self.pointer] = observationself.action_buffer[self.pointer] = actionself.reward_buffer[self.pointer] = rewardself.value_buffer[self.pointer] = valueself.logprobability_buffer[self.pointer] = logprobabilityself.pointer += 1def finish_trajectory(self, last_value=0):path_slice = slice(self.trajectory_start_index, self.pointer)rewards = np.append(self.reward_buffer[path_slice], last_value)values = np.append(self.value_buffer[path_slice], last_value)deltas = rewards[:-1] + self.gamma * values[1:] - values[:-1]self.advantage_buffer[path_slice] = discounted_cumulative_sums( deltas, self.gamma * self.lam )self.return_buffer[path_slice] = discounted_cumulative_sums(  rewards, self.gamma )[:-1]self.trajectory_start_index = self.pointerdef get(self):self.pointer, self.trajectory_start_index = 0, 0advantage_mean, advantage_std = (  np.mean(self.advantage_buffer),  np.std(self.advantage_buffer), )self.advantage_buffer = (self.advantage_buffer - advantage_mean) / advantage_stdreturn ( self.observation_buffer, self.action_buffer, self.advantage_buffer, self.return_buffer, self.logprobability_buffer, )

(3)这里定义了一个多层感知机(Multi-Layer Perceptron,MLP)的网络结构,有如下参数:

  • x:输入的张量
  • sizes:一个包含每一层的神经元个数的列表
  • activation:激活函数,用于中间层的神经元
  • output_activation:输出层的激活函数

该函数通过循环生成相应个数的全连接层,并将 x 作为输入传入。其中,units 指定每一层的神经元个数,activation 指定该层使用的激活函数,返回最后一层的结果。

def mlp(x, sizes, activation=tf.tanh, output_activation=None):for size in sizes[:-1]:x = layers.Dense(units=size, activation=activation)(x)return layers.Dense(units=sizes[-1], activation=output_activation)(x)

(4)这里定义了一个函数 logprobabilities,用于计算给定动作 a 的对数概率。函数接受两个参数,logitsa,其中 logits 表示模型输出的未归一化的概率分布,a 表示当前采取的动作。函数首先对 logits 进行 softmax 归一化,然后对归一化后的概率分布取对数,得到所有动作的对数概率。接着,函数使用 tf.one_hot 函数生成一个 one-hot 编码的动作向量,并与所有动作的对数概率向量相乘,最后对结果进行求和得到给定动作的对数概率。

def logprobabilities(logits, a):logprobabilities_all = tf.nn.log_softmax(logits)logprobability = tf.reduce_sum( tf.one_hot(a, num_actions) * logprobabilities_all, axis=1 )return logprobability

(5)这里定义了一个函数 sample_action。该函数接受一个 observation(观测值)参数,并在 actor 网络上运行该观测值以获得动作 logits(逻辑值)。然后使用逻辑值(logits)来随机采样出一个动作,并将结果作为函数的输出。

@tf.function
def sample_action(observation):logits = actor(observation)action = tf.squeeze(tf.random.categorical(logits, 1), axis=1)return logits, action

(6)这里定义了一个用于训练策略的函数train_policy。该函数使用带权重裁剪的 PPO 算法,用于更新 actor 的权重。

  • observation_buffer:输入的观测缓冲区
  • action_buffer:输入的动作缓冲区
  • logprobability_buffer:输入的对数概率缓冲区
  • advantage_buffer:输入的优势值缓冲区

在该函数内部,使用tf.GradientTape记录执行的操作,用于计算梯度并更新策略网络。计算的策略损失是策略梯度和剪裁比率的交集和。使用优化器policy_optimizer来更新actor的权重。最后,计算并返回 kl 散度的平均值,该值用于监控训练的过程。

@tf.function
def train_policy( observation_buffer, action_buffer, logprobability_buffer, advantage_buffer):with tf.GradientTape() as tape:   ratio = tf.exp( logprobabilities(actor(observation_buffer), action_buffer) - logprobability_buffer )min_advantage = tf.where(  advantage_buffer > 0, (1 + clip_ratio) * advantage_buffer, (1 - clip_ratio) * advantage_buffer, )policy_loss = -tf.reduce_mean( tf.minimum(ratio * advantage_buffer, min_advantage) )policy_grads = tape.gradient(policy_loss, actor.trainable_variables)policy_optimizer.apply_gradients(zip(policy_grads, actor.trainable_variables))kl = tf.reduce_mean( logprobability_buffer - logprobabilities(actor(observation_buffer), action_buffer) )kl = tf.reduce_sum(kl)return kl

(7)这里实现了价值函数(critic)的训练过程,函数接受两个参数:一个是 observation_buffer,表示当前存储的状态观察序列;另一个是 return_buffer,表示状态序列对应的回报序列。在函数内部,首先使用 critic 模型来预测当前状态序列对应的状态值(V), 然后计算当前状态序列的平均回报与 V 之间的均方误差,并对其进行求和取平均得到损失函数 value_loss。接下来计算梯度来更新可训练的变量值。

@tf.function
def train_value_function(observation_buffer, return_buffer):with tf.GradientTape() as tape:  value_loss = tf.reduce_mean((return_buffer - critic(observation_buffer)) ** 2)value_grads = tape.gradient(value_loss, critic.trainable_variables)value_optimizer.apply_gradients(zip(value_grads, critic.trainable_variables))

游戏初始化

这里用于构建强化学习中的 Actor-Critic 网络模型。首先,使用 gy m库中的 CartPole-v0 环境创建一个环境实例 env 。然后,定义了两个变量,分别表示观测空间的维度 observation_dimensions 和动作空间的大小 num_actions,这些信息都可以从 env 中获取。接着,定义了一个 Buffer 类的实例,用于存储每个时间步的观测、动作、奖励、下一个观测和 done 信号,以便后面的训练使用。

然后,使用 Keras 库定义了一个神经网络模型 Actor ,用于近似模仿策略函数,该模型输入是当前的观测,输出是每个动作的概率分布的对数。

另外,还定义了一个神经网络模型 Critic ,用于近似模仿值函数,该模型输入是当前的观测,输出是一个值,表示这个观测的价值。最后,定义了两个优化器,policy_optimizer 用于更新 Actor 网络的参数,value_optimizer 用于更新 Critic 网络的参数。

env = gym.make("CartPole-v0")
observation_dimensions = env.observation_space.shape[0]
num_actions = env.action_space.n
buffer = Buffer(observation_dimensions, steps_per_epoch)observation_input = keras.Input(shape=(observation_dimensions,), dtype=tf.float32)
logits = mlp(observation_input, list(hidden_sizes) + [num_actions], tf.tanh, None)
actor = keras.Model(inputs=observation_input, outputs=logits)
value = tf.squeeze( mlp(observation_input, list(hidden_sizes) + [1], tf.tanh, None), axis=1 )
critic = keras.Model(inputs=observation_input, outputs=value)policy_optimizer = keras.optimizers.Adam(learning_rate=policy_learning_rate)
value_optimizer = keras.optimizers.Adam(learning_rate=value_function_learning_rate)

保存未训练时的运动情况

在未训练模型之前,将模型控制游戏的情况保存是 gif ,可以看出来技术很糟糕,很快就结束了游戏。

import imageio
start = env.reset() 
frames = []
for t in range(steps_per_epoch):frames.append(env.render(mode='rgb_array'))start = start.reshape(1, -1)logits, action = sample_action(start)start, reward, done, _ = env.step(action[0].numpy())if done:breakwith imageio.get_writer('未训练前的样子.gif', mode='I') as writer:for frame in frames:writer.append_data(frame)

模型训练

这里主要是训练模型,执行 eopch 轮,每一轮中循环 steps_per_epoch 步,每一步就是根据当前的观测结果 observation 来抽样得到下一步动作,然后将得到的各种观测结果、动作、奖励、value 值、对数概率值保存在 buffer 对象中,待这一轮执行游戏运行完毕,收集了一轮的数据之后,就开始训练策略和值函数,并打印本轮的训练结果,不断重复这个过程,

observation, episode_return, episode_length = env.reset(), 0, 0
for epoch in tqdm(range(epochs)):sum_return = 0sum_length = 0num_episodes = 0for t in range(steps_per_epoch):if render:env.render()observation = observation.reshape(1, -1)logits, action = sample_action(observation)observation_new, reward, done, _ = env.step(action[0].numpy())episode_return += rewardepisode_length += 1value_t = critic(observation)logprobability_t = logprobabilities(logits, action)buffer.store(observation, action, reward, value_t, logprobability_t)observation = observation_newterminal = doneif terminal or (t == steps_per_epoch - 1):last_value = 0 if done else critic(observation.reshape(1, -1))buffer.finish_trajectory(last_value)sum_return += episode_returnsum_length += episode_lengthnum_episodes += 1observation, episode_return, episode_length = env.reset(), 0, 0( observation_buffer, action_buffer, advantage_buffer,  return_buffer, logprobability_buffer, ) = buffer.get()for _ in range(train_policy_iterations):kl = train_policy( observation_buffer, action_buffer, logprobability_buffer, advantage_buffer )if kl > 1.5 * target_kl:breakfor _ in range(train_value_iterations):train_value_function(observation_buffer, return_buffer)print( f"完成第 {epoch + 1} 轮训练, 平均奖励: {sum_length / num_episodes}" )

打印:

完成第 1 轮训练, 平均奖励: 30.864197530864196
完成第 2 轮训练, 平均奖励: 40.32258064516129
...
完成第 9 轮训练, 平均奖励: 185.1851851851852
完成第 11 轮训练, 平均奖励: 172.41379310344828
...
完成第 14 轮训练, 平均奖励: 172.41379310344828
...
完成第 18 轮训练, 平均奖励: 185.1851851851852
...
完成第 20 轮训练, 平均奖励: 200.0

保存训练后的运动情况

在训练模型之后,将模型控制游戏的情况保存是 gif ,可以看出来技术很娴熟,可以在很长的时间内使得棒子始终保持近似垂直的状态。

import imageio
start = env.reset()
frames = []
for t in range(steps_per_epoch):frames.append(env.render(mode='rgb_array'))start = start.reshape(1, -1)logits, action = sample_action(start)start, reward, done, _ = env.step(action[0].numpy())if done:breakwith imageio.get_writer('训练后的样子.gif', mode='I') as writer:for frame in frames:writer.append_data(frame)

训练后的样子.gif

如何系统的去学习大模型LLM ?

作为一名热心肠的互联网老兵,我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。

但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的 AI大模型资料 包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来

😝有需要的小伙伴,可以V扫描下方二维码免费领取🆓

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

img

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

img

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

img

在这里插入图片描述

四、AI大模型商业化落地方案

img

阶段1:AI大模型时代的基础理解

  • 目标:了解AI大模型的基本概念、发展历程和核心原理。
  • 内容
    • L1.1 人工智能简述与大模型起源
    • L1.2 大模型与通用人工智能
    • L1.3 GPT模型的发展历程
    • L1.4 模型工程
    • L1.4.1 知识大模型
    • L1.4.2 生产大模型
    • L1.4.3 模型工程方法论
    • L1.4.4 模型工程实践
    • L1.5 GPT应用案例

阶段2:AI大模型API应用开发工程

  • 目标:掌握AI大模型API的使用和开发,以及相关的编程技能。
  • 内容
    • L2.1 API接口
    • L2.1.1 OpenAI API接口
    • L2.1.2 Python接口接入
    • L2.1.3 BOT工具类框架
    • L2.1.4 代码示例
    • L2.2 Prompt框架
    • L2.2.1 什么是Prompt
    • L2.2.2 Prompt框架应用现状
    • L2.2.3 基于GPTAS的Prompt框架
    • L2.2.4 Prompt框架与Thought
    • L2.2.5 Prompt框架与提示词
    • L2.3 流水线工程
    • L2.3.1 流水线工程的概念
    • L2.3.2 流水线工程的优点
    • L2.3.3 流水线工程的应用
    • L2.4 总结与展望

阶段3:AI大模型应用架构实践

  • 目标:深入理解AI大模型的应用架构,并能够进行私有化部署。
  • 内容
    • L3.1 Agent模型框架
    • L3.1.1 Agent模型框架的设计理念
    • L3.1.2 Agent模型框架的核心组件
    • L3.1.3 Agent模型框架的实现细节
    • L3.2 MetaGPT
    • L3.2.1 MetaGPT的基本概念
    • L3.2.2 MetaGPT的工作原理
    • L3.2.3 MetaGPT的应用场景
    • L3.3 ChatGLM
    • L3.3.1 ChatGLM的特点
    • L3.3.2 ChatGLM的开发环境
    • L3.3.3 ChatGLM的使用示例
    • L3.4 LLAMA
    • L3.4.1 LLAMA的特点
    • L3.4.2 LLAMA的开发环境
    • L3.4.3 LLAMA的使用示例
    • L3.5 其他大模型介绍

阶段4:AI大模型私有化部署

  • 目标:掌握多种AI大模型的私有化部署,包括多模态和特定领域模型。
  • 内容
    • L4.1 模型私有化部署概述
    • L4.2 模型私有化部署的关键技术
    • L4.3 模型私有化部署的实施步骤
    • L4.4 模型私有化部署的应用场景

学习计划:

  • 阶段1:1-2个月,建立AI大模型的基础知识体系。
  • 阶段2:2-3个月,专注于API应用开发能力的提升。
  • 阶段3:3-4个月,深入实践AI大模型的应用架构和私有化部署。
  • 阶段4:4-5个月,专注于高级模型的应用和部署。
这份完整版的大模型 LLM 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

😝有需要的小伙伴,可以Vx扫描下方二维码免费领取🆓

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

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

相关文章

springboot与flowable(1):介绍、Flowable-ui使用

一、工作流引擎使用场景 工作流在企业管理系统中是高频使用的功能,一个最常见的例子是请假加班申请与审批的过程。事实上,工作流引擎能支持的业务场景远远不止单据审批,几乎所有涉及到业务流转、多人按流程完成工作的场景背后都可以通过工作流…

任务4.8.1 利用Spark SQL实现词频统计

实战:利用Spark SQL实现词频统计 目标 使用Apache Spark的Spark SQL模块,实现一个词频统计程序。 环境准备 本地文件准备 在本地/home目录下创建words.txt文件。 HDFS文件准备 创建HDFS目录/wordcount/input。将words.txt文件上传到HDFS的/wordcount…

在 Visual Studio 2022 中配置 OpenCV

在 Visual Studio 2022 中配置 OpenCV 软件准备系统环境配置VS 2022 环境配置测试 软件准备 Visual Studio 2022 下载链接 OpenCV 下载链接 Visual Studio 的版本与 OpenCV 的 vc 版本需对应好,可以向下兼容: VS 2015 – vc14VS 2017 – vc15VS 2019…

【启明智显芯片应用】Model3C芯片4.3寸拼图机应用方案

数据显示,618前期,早教启智、智能玩具、科学启蒙、数字阅读类产品销量增长迅猛。当下,90后新生代父母对于孩子的科学启蒙教育愈发重视,他们在给孩子选择学习产品时,越来越倾向于选择寓教于乐的益智类产品,而…

PNAS | 工作记忆中大脑节律的因果功能图

摘要 工作记忆是一个涉及大脑中多个功能解剖节点的关键认知过程。尽管有大量与工作记忆结构相关的神经影像学证据,但我们对控制整体表现的关键中枢的理解并不完整。因果解释需要在对特定功能解剖节点进行安全、暂时和可控的神经调节后进行认知测试。随着经颅交流电…

工业机器人远程运维,增强智慧工厂运营管理

1、需求背景 随着工业自动化技术的普及和工业机器人应用的增加,制造业对于生产线稳定性和效率的要求不断提高。然而,传统的现场监控方式存在着地理位置限制、实时监控难度大以及诊断能力有限等问题,迫切需要一种更具灵活性和效率的监控方式。…

充电宝哪个牌子好?10款主流款充电宝推荐

步入高速发展的快充时代,一个优质的充电宝已成为我们日常生活中的必备良品。本文将为大家介绍10主流款的充电宝品牌,它们包括西圣PB、品胜、京东京造、京东京造、飞利浦、倍思等知名品牌的系列产品。这些充电宝涵盖了不同的容量和快充协议,能…

redis windos修复版本

遇到的问题: Django的channel插件连接安装在windows上的redis报错: unknown command BZPOPMIN, channels-redis版本和redis不兼容导致.解决方案: 更新Redis版本. 微软官方维护的 Redishttps://github.com/microsoftarchive/redis/releases 2016年后就不更新了, 版本停留在了3.x…

2024年城市建设、运输与智慧交通国际会议(ICUCTST 2024)

2024 International Conference on Urban Construction, Transportation, and Smart Transportation 【1】大会信息 会议简称:ICUCTST 2024 大会地点:中国厦门 会议官网:www.icuctst.com 投稿邮箱:icuctstsub-paper.com 【2】会…

【论文】2405.Phased Consistency Model(港中文提出了AI绘画加速模型,一步采样生成图像,支持SD1.5,SDXL)

论文:https://arxiv.org/abs/2405.18407 (2024.05.30发布) 代码:https://github.com/G-U-N/Phased-Consistency-Model 一、论文要解决什么问题?效果如何? (需要先了解LCM模型) (阶段性一致性模型:朝着稳定…

后端面试题分享查看测试代码

问题描述 编写一个函数,该函数接受一个字符串作为参数,检查该字符串是否符合密码强度要求, 返回True或False。 要求 密码强度要求如下: 不能小于6个字符必须出现大写、小写、数字、特殊字符(!#$%^&*_-&#xf…

(免费领源码)基于 node.js#vue#mysql的网上游戏商城35112-计算机毕业设计项目选题推荐

摘 要 本论文主要论述了如何使用node.js语言开发一个基于vue的网上游戏商城,本系统将严格按照软件开发流程进行各个阶段的工作,本系统采用的数据库是Mysql,使用node.js的koa技术技术构建的一个管理系统,实现了本系统的全部功能。在…

Java--Math类和Random类

1.Math类简介 1.Math类中提供了大量用于数学运算的相关方法。 2.Math类是使用final修饰的终结类,不能产生子类 3.Math类中的方法都是static修饰的静态方法,可以通过类名.方法名直接调用 2.Math常用方法 1.abs(int):求绝对值(int,long,floa…

ABB机器人修改IO信号的具体方法介绍

ABB机器人修改IO信号的具体方法介绍 具体步骤可从参考以下内容: 导出IO配置文件 打开【控制面板】-【配置】-【I/O System】-【文件】-【‘EIO’另存为】,就可以保存IO配置文件【EIO.cfg】用RobotStudio软件打开EIO.cfg文件在软件界面,鼠标右击,选择【I/O信号数据编辑器】选…

【MyBatis-plus】saveBatch 性能调优和【MyBatis】的数据批量入库

总结最优的两种方法: 方法1: 使用了【MyBatis-plus】saveBatch 但是数据入库效率依旧很慢,那可能是是因为JDBC没有配置,saveBatch 批量写入并没有生效哦!!! 详细配置如下:批量数据入…

数据结构---外部排序

个人介绍 hello hello~ ,这里是 code袁~💖💖 ,欢迎大家点赞🥳🥳关注💥💥收藏🌹🌹🌹 🦁作者简介:一名喜欢分享和记录学习的…

【计算机毕业设计】266基于微信小程序的在线点餐

🙊作者简介:拥有多年开发工作经验,分享技术代码帮助学生学习,独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。🌹赠送计算机毕业设计600个选题excel文件,帮助大学选题。赠送开题报告模板&#xff…

【Nature子刊】最争气国人友好“灌水刊”,中科院3区升2区,录用仅1个月,2天见刊!

本周投稿推荐 SSCI • 中科院2区,6.0-7.0(录用友好) EI • 各领域沾边均可(2天录用) CNKI • 7天录用-检索(急录友好) SCI&EI • 4区生物医学类,0.5-1.0(录用…

【单片机毕业设计9-基于stm32c8t6的酒窖监测系统】

【单片机毕业设计9-基于stm32c8t6的酒窖监测系统】 前言一、功能介绍二、硬件部分三、软件部分总结 前言 🔥这里是小殷学长,单片机毕业设计篇9基于stm32的酒窖监测系统 🧿创作不易,拒绝白嫖可私 一、功能介绍 -------------------…

responses-validator接口断言之状态码

概述 responses-validator 专用于对 reqeuests 的响应对象进行断言, 同时,为了更适用 yaml 的场景,支持了多种灵活、可扩展的写法,可用于搭建yaml接口自动化测试框架。 根据 reqeuests 响应对象的特点,responses-val…