BERT+PET方式数据处理

news2024/11/25 1:08:17

基于BERT+PET方式数据预处理介绍

在这里插入图片描述
在这里插入图片描述

BERT+PET方式数据预处理🐾

  • 本项目中对数据部分的预处理步骤如下:
    1. 查看项目数据集
    2. 编写Config类项目文件配置代码
    3. 编写数据处理相关代码

1 查看项目数据集🐾

  • 数据存放位置:/Users/***/PycharmProjects/llm/prompt_tasks/PET/data

  • data文件夹里面包含4个txt文档,分别为:train.txt、dev.txt、prompt.txt、verbalizer.txt


1.1 train.txt
  • train.txt为训练数据集,其部分数据展示如下:
水果	脆脆的,甜味可以,可能时间有点长了,水分不是很足。
平板	华为机器肯定不错,但第一次碰上京东最糟糕的服务,以后不想到京东购物了。
书籍	为什么不认真的检查一下, 发这么一本脏脏的书给顾客呢!
衣服	手感不错,用料也很好,不知道水洗后怎样,相信大品牌,质量过关,五星好评!!!
水果	苹果有点小,不过好吃,还有几个烂的。估计是故意的放的。差评。
衣服	掉色掉的厉害,洗一次就花了

train.txt一共包含63条样本数据,每一行用\t分开,前半部分为标签(label),后半部分为原始输入 (用户评论)。

如果想使用自定义数据训练,只需要仿照上述示例数据构建数据集即可。


1.2 dev.txt
  • dev.txt为验证数据集,其部分数据展示如下:
书籍	"一点都不好笑,很失望,内容也不是很实用"
衣服	完全是一条旧裤子。
手机	相机质量不错,如果阳光充足,可以和数码相机媲美.界面比较人性化,容易使用.软件安装简便
书籍	明明说有货,结果送货又没有了。并且也不告诉我,怎么评啊
洗浴	非常不满意,晚上洗的头发,第二天头痒痒的不行了,还都是头皮屑。
水果	这个苹果感觉是长熟的苹果,没有打蜡,不错,又甜又脆

dev.txt一共包含590条样本数据,每一行用\t分开,前半部分为标签(label),后半部分为原始输入 (用户评论)。

如果想使用自定义数据训练,只需要仿照上述示例数据构建数据集即可。

1.3 prompt.txt
  • prompt.txt为人工设定提示模版,其数据展示如下:
这是一条{MASK}评论:{textA}。

其中,用大括号括起来的部分为「自定义参数」,可以自定义设置大括号内的值。

示例中 {MASK} 代表 [MASK] token 的位置,{textA} 代表评论数据的位置。

你可以改为自己想要的模板,例如想新增一个 {textB} 参数:

{textA}和{textB}是{MASK}同的意思。
1.4 verbalizer.txt🐾
  • verbalizer.txt 主要用于定义「真实标签」到「标签预测词」之间的映射。在有些情况下,将「真实标签」作为 [MASK] 去预测可能不具备很好的语义通顺性,因此,我们会对「真实标签」做一定的映射。

  • 例如:

"中国爆冷2-1战胜韩国"是一则[MASK][MASK]新闻。	体育
  • 这句话中的标签为「体育」,但如果我们将标签设置为「足球」会更容易预测。

  • 因此,我们可以对「体育」这个 label 构建许多个子标签,在推理时,只要预测到子标签最终推理出真实标签即可,如下:

体育 -> 足球,篮球,网球,棒球,乒乓,体育
  • 项目中标签词映射数据展示如下:
电脑	电脑
水果	水果
平板	平板
衣服	衣服
酒店	酒店
洗浴	洗浴
书籍	书籍
蒙牛	蒙牛
手机	手机
电器	电器

verbalizer.txt 一共包含10个类别,上述数据中,我们使用了1对1的verbalizer, 如果想定义一对多的映射,只需要在后面用","分割即可, eg:

水果	苹果,香蕉,橘子

若想使用自定义数据训练,只需要仿照示例数据构建数据集

2 编写Config类项目文件配置代码🐾

  • 代码路径:/Users/***/PycharmProjects/llm/prompt_tasks/PET/pet_config.py

  • config文件目的:配置项目常用变量,一般这些变量属于不经常改变的,比如:训练文件路径、模型训练次数、模型超参数等等

具体代码实现:

# coding:utf-8
import torch
import sys
print(sys.path)

class ProjectConfig(object):
    def __init__(self):
      	# 是否使用GPU
        self.device = 'cuda:0' if torch.cuda.is_available() else 'cpu'
        # 预训练模型bert路径
        self.pre_model = '/home/prompt_project/bert-base-chinese'
        self.train_path = '/home/prompt_project/PET/data/train.txt'
        self.dev_path = '/home/prompt_project/PET/data/dev.txt'
        self.prompt_file = '/home/prompt_project/PET/data/prompt.txt'
        self.verbalizer = '/home/prompt_project/PET/data/verbalizer.txt'
        self.max_seq_len = 512
        self.batch_size = 8
        self.learning_rate = 5e-5
        # 权重衰减参数(正则化,抑制模型过拟合)
        self.weight_decay = 0
        # 预热学习率(用来定义预热的步数)
        self.warmup_ratio = 0.06
        self.max_label_len = 2
        self.epochs = 50
        self.logging_steps = 10
        self.valid_steps = 20
        self.save_dir = '/home/prompt_project/PET/checkpoints'


if __name__ == '__main__':
    pc = ProjectConfig()
    print(pc.prompt_file)
    print(pc.pre_model)

3 编写数据处理相关代码🐾

  • 代码路径:/Users/***/PycharmProjects/llm/prompt_tasks/PET/data_handle.

  • data_handle文件夹中一共包含三个py脚本:template.py、data_preprocess.py、data_loader.py

3.1 template.py
  • 目的:构建固定模版类,text2id的转换

  • 导入必备工具包

# -*- coding:utf-8 -*-
from rich import print # 终端层次显示
from transformers import AutoTokenizer
import numpy as np
import sys
sys.path.append('..')
from pet_config import *
  • 定义HardTemplate类
class HardTemplate(object):
    """
    硬模板,人工定义句子和[MASK]之间的位置关系。
    """

    def __init__(self, prompt: str):
        """
        Args:
            prompt (str): prompt格式定义字符串, e.g. -> "这是一条{MASK}评论:{textA}。"
        """
        self.prompt = prompt
        self.inputs_list = []                       # 根据文字prompt拆分为各part的列表
        self.custom_tokens = set(['MASK'])          # 从prompt中解析出的自定义token集合
        self.prompt_analysis()                         # 解析prompt模板

    def prompt_analysis(self):
        """
        将prompt文字模板拆解为可映射的数据结构。

        Examples:
         prompt -> "这是一条{MASK}评论:{textA}。"
         inputs_list -> ['这', '是', '一', '条', 'MASK', '评', '论', ':', 'textA', '。']
         custom_tokens -> {'textA', 'MASK'}
        """
        idx = 0
        while idx < len(self.prompt):
            str_part = ''
            if self.prompt[idx] not in ['{', '}']:
                self.inputs_list.append(self.prompt[idx])
            if self.prompt[idx] == '{':                  # 进入自定义字段
                idx += 1
                while self.prompt[idx] != '}':
                    str_part += self.prompt[idx]             # 拼接该自定义字段的值
                    idx += 1
            elif self.prompt[idx] == '}':
                raise ValueError("Unmatched bracket '}', check your prompt.")
            if str_part:
                self.inputs_list.append(str_part)
                # 将所有自定义字段存储,后续会检测输入信息是否完整
                self.custom_tokens.add(str_part)  
            idx += 1

    def __call__(self,
                 inputs_dict: dict,
                 tokenizer,
                 mask_length,
                 max_seq_len=512):
        """
        输入一个样本,转换为符合模板的格式。

        Args:
            inputs_dict (dict): prompt中的参数字典, e.g. -> {
                                                            "textA": "这个手机也太卡了", 
                                                            "MASK": "[MASK]"
                                                        }
            tokenizer: 用于encoding文本
            mask_length (int): MASK token 的长度

        Returns:
            dict -> {
                'text': '[CLS]这是一条[MASK]评论:这个手机也太卡了。[SEP]',
                'input_ids': [1, 47, 10, 7, 304, 3, 480, 279, 74, 47, 
                							27, 247, 98, 105, 512, 777, 15, 12043, 2],
                'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
                										0, 0, 0, 0, 0, 0, 0, 0, 0], 
                'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1,
                									1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                'mask_position': [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 
                									0, 0, 0, 0, 0, 0, 0, 0, 0]
            }
        """
        # 定义输出格式
        outputs = {
            'text': '', 
            'input_ids': [],
            'token_type_ids': [],
            'attention_mask': [],
            'mask_position': []
        }

        str_formated = ''
        for value in self.inputs_list:
            if value in self.custom_tokens:
                if value == 'MASK':
                    str_formated += inputs_dict[value] * mask_length
                else:
                    str_formated += inputs_dict[value]
            else:
                str_formated += value
        # print(f'str_formated-->{str_formated}')
        encoded = tokenizer(text=str_formated,
                            truncation=True,
                            max_length=max_seq_len,
                            padding='max_length')
        # print(f'encoded--->{encoded}')
        outputs['input_ids'] = encoded['input_ids']
        outputs['token_type_ids'] = encoded['token_type_ids']
        outputs['attention_mask'] = encoded['attention_mask']
        token_list = tokenizer.convert_ids_to_tokens(encoded['input_ids'])
        outputs['text'] = ''.join(token_list)
        mask_token_id = tokenizer.convert_tokens_to_ids(['[MASK]'])[0]
        condition = np.array(outputs['input_ids']) == mask_token_id
        mask_position = np.where(condition)[0].tolist()
        outputs['mask_position'] = mask_position
        return outputs


if __name__ == '__main__':
    pc = ProjectConfig()
    tokenizer = AutoTokenizer.from_pretrained(pc.pre_model)
    hard_template = HardTemplate(prompt='这是一条{MASK}评论:{textA}')
    print(hard_template.inputs_list)
    print(hard_template.custom_tokens)
    tep = hard_template(
                inputs_dict={'textA': '包装不错,苹果挺甜的,个头也大。', 'MASK': '[MASK]'},
                tokenizer=tokenizer,
                max_seq_len=30,
                mask_length=2)
    print(tep)

    print(tokenizer.convert_ids_to_tokens([3819, 3352]))
    print(tokenizer.convert_tokens_to_ids(['水', '果']))

3.2 data_preprocess.py🐾
  • 目的: 将样本数据转换为模型接受的输入数据

  • 导入必备的工具包

from template import *
from rich import print
from datasets import load_dataset
# partial:把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单
from functools import partial
from pet_config import *

  • 定义数据转换方法convert_example()
def convert_example(
        examples: dict,
        tokenizer,
        max_seq_len: int,
        max_label_len: int,
        hard_template: HardTemplate,
        train_mode=True,
        return_tensor=False) -> dict:
    """
    将样本数据转换为模型接收的输入数据。

    Args:
        examples (dict): 训练数据样本, e.g. -> {
                                                "text": [
                                                            '手机	这个手机也太卡了。',
                                                            '体育	世界杯为何迟迟不见宣传',
                                                            ...
                                                ]
                                            }
        max_seq_len (int): 句子的最大长度,若没有达到最大长度,则padding为最大长度
        max_label_len (int): 最大label长度,若没有达到最大长度,则padding为最大长度
        hard_template (HardTemplate): 模板类。
        train_mode (bool): 训练阶段 or 推理阶段。
        return_tensor (bool): 是否返回tensor类型,如不是,则返回numpy类型。

    Returns:
        dict (str: np.array) -> tokenized_output = {
                            'input_ids': [[1, 47, 10, 7, 304, 3, 3, 3, 3, 47, 27, 
                            							247, 98, 105, 512, 777, 15, 12043, 2], ...],
                            'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
                            										0, 0, 0, 0, 0, 0, 0, 0], ...],
                            'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
                            										1, 1, 1, 1, 1, 1, 1, 1], ...],
                            'mask_positions': [[5, 6, 7, 8], ...],
                            'mask_labels': [[2372, 3442, 0, 0], 
                            								[2643, 4434, 2334, 0], ...]
                        }
    """
    tokenized_output = {
        'input_ids': [],
        'token_type_ids': [],
        'attention_mask': [],
        'mask_positions': [],
        'mask_labels': []
    }

    for i, example in enumerate(examples['text']):
        if train_mode:
            label, content = example.strip().split('\t')
        else:
            content = example.strip()

        inputs_dict = {
            'textA': content,
            'MASK': '[MASK]'
        }
        encoded_inputs = hard_template(
            inputs_dict=inputs_dict,
            tokenizer=tokenizer,
            max_seq_len=max_seq_len,
            mask_length=max_label_len)
        tokenized_output['input_ids'].append(encoded_inputs["input_ids"])
        tokenized_output['token_type_ids'].append(encoded_inputs["token_type_ids"])
        tokenized_output['attention_mask'].append(encoded_inputs["attention_mask"])
        tokenized_output['mask_positions'].append(encoded_inputs["mask_position"])

        if train_mode:
            label_encoded = tokenizer(text=[label])  # 将label补到最大长度
            # print(f'label_encoded-->{label_encoded}')
            label_encoded = label_encoded['input_ids'][0][1:-1]
            label_encoded = label_encoded[:max_label_len]
            add_pad = [tokenizer.pad_token_id] * (max_label_len - len(label_encoded))
            label_encoded = label_encoded + add_pad
            tokenized_output['mask_labels'].append(label_encoded)

    for k, v in tokenized_output.items():
        if return_tensor:
            tokenized_output[k] = torch.LongTensor(v)
        else:
            tokenized_output[k] = np.array(v)

    return tokenized_output


if __name__ == '__main__':
    pc = ProjectConfig()
    train_dataset = load_dataset('text', data_files=pc.train_path)
    print(type(train_dataset))
    print(train_dataset)
    # print('*'*80)
    # print(train_dataset['train']['text'])
    tokenizer = AutoTokenizer.from_pretrained(pc.pre_model)
    hard_template = HardTemplate(prompt='这是一条{MASK}评论:{textA}')

    convert_func = partial(convert_example,
                           tokenizer=tokenizer,
                           hard_template=hard_template,
                           max_seq_len=30,
                           max_label_len=2)
    dataset = train_dataset.map(convert_func, batched=True)
    for value in dataset['train']:
        print(value)
        print(len(value['input_ids']))
        break


3.3 data_loader.py🐾
  • 目的:定义数据加载器

  • 导入必备的工具包

# coding:utf-8
from torch.utils.data import DataLoader
from transformers import default_data_collator
from data_preprocess import *
from pet_config import *

pc = ProjectConfig() # 实例化项目配置文件
tokenizer = AutoTokenizer.from_pretrained(pc.pre_model)

  • 定义获取数据加载器的方法get_data()
def get_data():
  	# prompt定义
    prompt = open(pc.prompt_file, 'r', encoding='utf8').readlines()[0].strip()  
    hard_template = HardTemplate(prompt=prompt)  # 模板转换器定义
    dataset = load_dataset('text', data_files={'train': pc.train_path,
                                               'dev': pc.dev_path})
    # print(dataset)
    # print(f'Prompt is -> {prompt}')
    new_func = partial(convert_example,
                       tokenizer=tokenizer,
                       hard_template=hard_template,
                       max_seq_len=pc.max_seq_len,
                       max_label_len=pc.max_label_len)

    dataset = dataset.map(new_func, batched=True)

    train_dataset = dataset["train"]
    dev_dataset = dataset["dev"]
    # print('train_dataset', train_dataset[:2])
    # print('*'*80)
    train_dataloader = DataLoader(train_dataset,
                                  shuffle=True,
                                  collate_fn=default_data_collator,
                                  batch_size=pc.batch_size)
    dev_dataloader = DataLoader(dev_dataset,
                                collate_fn=default_data_collator,
                                batch_size=pc.batch_size)
    return train_dataloader, dev_dataloader


if __name__ == '__main__':
    train_dataloader, dev_dataloader = get_data()
    print(len(train_dataloader))
    print(len(dev_dataloader))
    for i, value in enumerate(train_dataloader):
        print(i)
        print(value)
        print(value['input_ids'].dtype)
        break


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

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

相关文章

Innodb Buffer Pool缓存机制(四)预读与Mysql改进的LRU策略

一、什么是预读 InnoDB提供了预读(read ahead)。所谓预读&#xff0c;就是InnoDB认为执行当前的请求可能之后会读取某些页面&#xff0c;就预先把它们加载到Buffer Pool中。根据触发方式的不同&#xff0c;预读又可以细分为下边两种&#xff1a; 1.1 线性预读 InnoDB提供了一…

Rust-02-变量与可变性

在Rust中&#xff0c;变量和可变性是两个重要的概念。 变量&#xff1a;变量是用于存储数据的标识符。在Rust中&#xff0c;变量需要声明其类型&#xff0c;例如&#xff1a; let x: i32 5; // 声明一个名为x的变量&#xff0c;类型为i32&#xff08;整数&#xff09;&#…

ApiJson快速入门

前言 最近在正式迭代中插入了一个大屏演示项目&#xff0c;因为后端开发人员任务都安排满了&#xff0c;而演示项目逻辑比较简单&#xff0c;大多是直接查表就能搞定&#xff0c;所以只能想办法让前端直接和数据库交互&#xff0c;增加开发速度。在找工具时发现了ApiJson。尝试…

私有云和多云管理平台 | Cloudpods v3.11.4 正式发布

本次 3.11.4 更新亮点为&#xff1a;系统镜像引入社区镜像&#xff0c;用户可以一键导入各主流开源操作系统镜像&#xff0c;方便用户上手使用。持续迭代共享 LVM&#xff0c;支持快照&#xff0c;主备机等特性&#xff0c;修复迁移删除镜像缓存等 BUG。 功能优化 【费用】费…

大众汽车裁员加速,38万元遣散费起步

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》 几周前&#xff0c;大众汽车宣布了一项新的裁员计划。 一、裁员行动与额外福利并行 大众汽车近期在裁员行动上取得了显著进展&#xff0c;其遣散…

html接口响应断言

接口响应值除类json格式,还有html格式 断言步骤 第一步:替换空格replace 原本返回的格式和网页内容一致,每行前面有很多空格,需要去除这些空格 第二步:分割split 因为行与行之前有回车符,所以把回车符替换掉,返回值为数组 数组不能直接打印数组,需要格式转换 第三…

基于实验的电动汽车动力电池SOC

前言 本文为笔者在学习《基于MATLAB的新能源汽车仿真》过程中学习笔记&#xff0c;所涉及的表格数据和公式均为书籍里的。仿真数据是网上找的恒电流放电数据。本文仅作为笔者的入门学习记录。 一、分析动力电池SOC估算方法 SOC是指动力电池按照规定放电条件可以释放的容量占…

国标GB/T 28181详解:校时流程详细说明

目录 一、定义 二、作用 1. 时间同步性 2. 事件记录的准确性 3. 跨平台、跨设备协作 4. 降低时间误差 5. 安全性提升 三、基本要求 四、命令流程 五、协议接口 六、校时效果 1、未校时的情况 2、校时后的效果 七、参考 一、定义 GB28181协议要求所有的监控设…

多种策略提升线上 tensorflow 模型推理速度

前言 本文以最常见的模型 Bi-LSTM-CRF 为例&#xff0c;总结了在实际工作中能有效提升在 CPU/GPU 上的推理速度的若干方法&#xff0c;包括优化模型结构&#xff0c;优化超参数&#xff0c;使用 onnx 框架等。当然如果你有充足的 GPU &#xff0c;结合以上方法提升推理速度的效…

Edge浏览器视频画中画可拉动进度条插件Separate Window

平时看一些视频的时候&#xff0c;一般需要编辑一些其他的东西&#xff0c;于是开启画中画&#xff0c;但是画中画没有进度条&#xff0c;需要大幅度的倒退前进得回到原视频&#xff0c;很麻烦。这里推荐插件Separate Window&#xff0c;可实现画中画进度条拉动和播放sudu的调节…

商城项目【尚品汇】08异步编排

文章目录 1.线程的创建方式1.1继承Thread类&#xff0c;重写run方法1.2实现Runnable接口&#xff0c;重写run方法。1.3实现Callable接口&#xff0c;重新call方法1.4以上三种总结1.5使用线程池创建线程1.5.1线程池创建线程的方式1.5.2线程池的七大参数含义1.5.3线程池的工作流程…

LLVM Cpu0 新后端5 静态重定位 动态重定位

想好好熟悉一下llvm开发一个新后端都要干什么&#xff0c;于是参考了老师的系列文章&#xff1a; LLVM 后端实践笔记 代码在这里&#xff08;还没来得及准备&#xff0c;先用网盘暂存一下&#xff09;&#xff1a; 链接: https://pan.baidu.com/s/1yLAtXs9XwtyEzYSlDCSlqw?…

springboot集成uid-generator生成分布式id

一、简介 uid-generator是由百度技术部开发,GitHub地址 UidGenerator是Java实现的, 基于Snowflake算法的唯一ID生成器 Snowflake算法 Snowflake算法描述&#xff1a;指定机器 & 同一时刻 & 某一并发序列&#xff0c;是唯一的。据此可生成一个64 bits的唯一ID&#x…

【Linux】信号(二)

上一章节我们进行了信号产生的讲解。 本节将围绕信号保存展开。 目录 信号保存&#xff1a;信号的一些概念&#xff1a;关于信号保存的接口&#xff1a;sigset_t的解释&#xff1a;对应的操作接口&#xff1a;sigprocmask&#xff1a;sigpending&#xff1a;代码实践&#xf…

Macbook M芯片JDK的安装

Macbook M芯片JDK的安装 下载 搜索zulu&#xff1b; 进入这个网址 https://www.azul.com/downloads/#zulu 进入页面后向下滑动 选择对应选项 然后点击Download进行下载。 选择.dmg格式的安装包&#xff0c;点击。 安装 下载完成后&#xff0c;到下载目录&#xff0c;双击…

日进2000,我怎么做到的

昨天遇到一个有意思的项目&#xff0c;让我一天进账2000&#xff0c;一个字&#xff1a;爽。 这几天接洽了一位新客户&#xff0c;主要诉求就是优化系统&#xff0c;基于LNMP的系统优化。正好这个领域我比较熟悉&#xff0c;以前都是在公司做项目&#xff0c;也不怎么涉猎系统优…

LabVIEW硬件与仿真双模式设计液压系统

在实际项目中&#xff0c;结合LabVIEW与液压运动控制器&#xff0c;通过设计两种运行方式&#xff1a;硬件运行和仿真运行&#xff0c;使得系统既能进行实际操作又能进行仿真测试。此设计不仅方便了开发调试&#xff0c;也为教学培训和展示提供了极大的便利。本文将从项目背景、…

【优选算法】栈 {后缀表达式求值;中缀转后缀表达式;中缀表达式求值}

一、经验总结 何时使用栈结构解题&#xff1f; 做过相似的使用栈结构解得的题目嵌套处理&#xff1a;在从前向后处理的过程中&#xff0c;由于之后内容的不确定性而导致当前操作不能贸然进行&#xff0c;需要先进行保存&#xff0c;直到遇到区间结束标志&#xff08;如’)&am…

MLU370-M8 chattts-ui快速出击

目录 一、paas平台环境选择二、代码环境准备1.代码下载2.环境安装modelsopetransformersaccelerate 3.常规pip安装4.代码修改4.代码修改 三.算法启动 一、paas平台环境选择 驱动选择&#xff1a;5.10.22及以上 镜像选择&#xff1a;pytorch2.1 二、代码环境准备 1.代码下载…

HC-SR505人体感应灯

1硬件 1.1硬件组成 1.正点原子探索者开发板 2 HC-SR505迷你小型人体感应模块 3 继电器&#xff0b;5V小灯 HC-SR505迷你小型人体感应模块介绍 1.2 硬件连接 1.HC-SR505&#xff08;连接在PE0&#xff09; 2.继电器&#xff08;连接在PE1&#xff09; 2.主要代码 int ma…