解锁数据潜力:信息抽取、数据增强与UIE的完美融合

解锁数据潜力:信息抽取、数据增强与UIE的完美融合

1.信息抽取(Information Extraction)

1.1 IE简介

信息抽取是 NLP 任务中非常常见的一种任务,其目的在于从一段自然文本中提取出我们想要的关键信息结构。

举例来讲,现在有下面这样一个句子:

新东方烹饪学校在成都。

我们想要提取这句话中所有有意义的词语,例如:

机构新东方烹饪学校
城市成都

这个关键词提取任务就叫做**命名实体识别(Named Entity Recognition, NER)**任务,文中的「新东方烹饪学校」和「成都」就被称为实体(Entity)。

如果我们还想进一步的知道这些词语之间的关系,例如:

实体 1关系名实体 2
新东方烹饪学校所在地成都

这种提取实体之间关系的任务就叫做**关系抽取(Relation Extraction, RE)**任务。

1.2 信息抽取的几种方法

1.2.1 序列标注(Sequence Labeling)

序列标注通常是指对文中的每一个字(以下简称 token)进行分类,即本质是 token classification 任务。

我们对第一小节中的例子做序列标注任务,得到的结果如下:

...
B - 机构I - 机构I - 机构 * NI - 机构I - 机构OB - 城市I - 城市

可以看到,我们对句子中的每一个字(token)都打上了一个类别标签,我们期望模型要做的事就是去学会每一个字所属的类别是什么。

Note: 这里用的标注方法是「BIO 标记法」,其中「B-」代表该位置 token 是某一个实体词语(span)的起始 token;「I-」代表该位置 token 处于某一个词语的中间(或结尾),「O」则代表该位置 token 不在任何一个实体词语中。除了「BIO 标记法」外,还有许多其他的标注方式(如 BIOES 等),其本质思路都很类似。

1.2.2 指针网络(Pointer Network)

序列标注模型有一个天然的缺陷,无法解决解决实体重叠(overlap)的问题。

举例来讲,如果今天我们不仅要提取「机构」,还同时要提取「机构类型」,那么我们期望的提取结果应该为:

机构新东方烹饪学校
机构类型学校
城市成都

可以看到,对于「学校」这两个字,即属于「新东方烹饪学校」(机构)这个词,也存在于「学校」(机构类型)这个词,那我们在给这两个字打标签的时候,究竟应该打成哪个类别呢?

B - 机构I - 机构I - 机构OB - 城市I - 城市

由此我们可以看到,因为在进行分类时我们通常对一个字(token)只赋予一个标签,这就导致了 token classification 不能很好的解决实体重叠(一字多标签)的复杂情况。

Note: 存在一些技巧可以解决该问题,例如可以从单字单分类(CE)衍生到单字多分类(BCE),这里不展开讨论。

指针网络(Pointer Network)通过分别对每一个实体单独做预测来解决了实体之前的重叠冲突问题。

例如,我们现在要同时预测「机构」和「机构类型」这两个实体,那么我们就可以设计一个多头网络(Multi-Head)来分别预测这两个实体的实体词。

其中,

「机构」实体头中「起始」向量代表这一句话中是「机构」词语的首字(例子中为「新」);

「机构」实体中「结束」向量代表这一句话中时「机构」词语的尾字(例子中为「校」)。

通过「起始」和「结束」向量中的首尾字索引就能找到对应实体的词语。

可以看到,通过构建多头的任务,指针网络能够分别预测「机构」和「机构类型」中的实体词起始 / 终止位置,即「学校」这个词语在两个任务层中都能被抽取出来。

1.3. UIE —— 基于 prompt 的指针网络

1.3.1 UIE 中的 prompt 是什么?

多头指针网络能够很好的解决实体重叠问题,但缺点在于:不够灵活。

假定今天我们已经通过指针网络训练好了一个提取「机构」、「机构类型」的模型,即将交付时甲方突然提出一个新需求:我们想再多提取一个「机构简称」的属性。

草(一种植物)。

从 2.2 节中的示意图中我们可以看到,每一个实体类型会对应一个单独的网络头。

这就意味着我们不仅需要重标数据,还需要为新属性添加一个新的网络头,即模型结构会随着实体类型个数改变而发生变化。

那,能不能有一种办法去固定住模型的结构,不管今天来多少种类型要识别都能使用同样的模型结构完成呢?

我们思考一下,模型结构变化的部分是和实体类型强绑定的「头」部分。

而不同「头」之间结构其实是完全一样的:一个「起始」向量 + 一个「终止」向量。

既然「头」结构完全一样,我们能不能干脆直接使用一个「头」去提取不同实体类型的信息呢?

不同「头」之间的区别在于它们关注的信息不同:「机构头」只关注「机构」相关的实体词,「城市头」只关注「城市」相关的实体词。

那么我们是不是可以直接在模型输入的时候就告诉模型:我现在需要提取「某个头」的信息。

这个用来告诉模型做具体任务的参数就叫 prompt,我们把它拼在输入中一并喂给模型即可。

通过上图可以看到,我们将不同的「实体类型」作为 prompt 参数喂给模型,用于「激活」模型参数跟当前「实体类型」相关的参数,从而输出不同的抽取结果。

Note: 「通过一个输入参数去激活一个大模型中的不同参数,从而完成不同任务的思路」并不是首次出现,在 meta-learning 中也存在相关的研究,这里的 prompt 参数和 meta-parameter 有着非常类似的思路。

通过引入 prompt,UIE 也能很方便的解决实体之间的关系抽取(Relation Extraction)任务,例如:

1.3.2 UIE 的实现

看完了基本思路,我们来一起看看 UIE 是怎么实现的吧。

  1. 模型部分

UIE 的模型代码比较简单,只需要在 encoder 后构建一个起始层和一个结束层即可:

class UIE(nn.Module):def __init__(self, encoder):"""init func.Args:encoder (transformers.AutoModel): backbone, 默认使用 ernie 3.0Reference:https://github.com/PaddlePaddle/PaddleNLP/blob/a12481fc3039fb45ea2dfac3ea43365a07fc4921/model_zoo/uie/model.py"""super().__init__()self.encoder = encoderhidden_size = 768self.linear_start = nn.Linear(hidden_size, 1)self.linear_end = nn.Linear(hidden_size, 1)self.sigmoid = nn.Sigmoid()def forward(self,input_ids: torch.tensor,token_type_ids: torch.tensor,attention_mask=None,pos_ids=None,) -> tuple:"""forward 函数,返回开始/结束概率向量。Args:input_ids (torch.tensor): (batch, seq_len)token_type_ids (torch.tensor): (batch, seq_len)attention_mask (torch.tensor): (batch, seq_len)pos_ids (torch.tensor): (batch, seq_len)Returns:tuple:  start_prob -> (batch, seq_len)end_prob -> (batch, seq_len)"""sequence_output = self.encoder(input_ids=input_ids,token_type_ids=token_type_ids,position_ids=pos_ids,attention_mask=attention_mask,)["last_hidden_state"]start_logits = self.linear_start(sequence_output)       # (batch, seq_len, 1)start_logits = torch.squeeze(start_logits, -1)          # (batch, seq_len)start_prob = self.sigmoid(start_logits)                 # (batch, seq_len)end_logits = self.linear_end(sequence_output)           # (batch, seq_len, 1)end_logits = torch.squeeze(end_logits, -1)              # (batch, seq_len)end_prob = self.sigmoid(end_logits)                     # (batch, seq_len)return start_prob, end_prob
  1. ** 训练部分**

训练部分主要关注一下 loss 的计算即可。

由于每一个 token 都是一个二分类任务,因此选用 BCE Loss 作为损失函数。

分别计算起始 / 结束向量的 BCE Loss 再取平均值即可,如下所示:

criterion = torch.nn.BCELoss()
...start_prob, end_prob = model(input_ids=batch['input_ids'].to(args.device),token_type_ids=batch['token_type_ids'].to(args.device),attention_mask=batch['attention_mask'].to(args.device))
start_ids = batch['start_ids'].to(torch.float32).to(args.device)    # (batch, seq_len)
end_ids = batch['end_ids'].to(torch.float32).to(args.device)        # (batch, seq_len)
loss_start = criterion(start_prob, start_ids)                       # 起止向量loss -> (1,)
loss_end = criterion(end_prob, end_ids)                             # 结束向量loss -> (1,)
loss = (loss_start + loss_end) / 2.0                                # 求平均 -> (1,)
loss.backward()
...

该项目将借用transformers库来实现paddlenlp版本中UIE,已实现:

  • UIE 预训练模型自动下载

  • UIE Fine-Tuning 脚本

  • 信息抽取、事件抽取数据增强(DA)策略(提升 recall)

  • 信息抽取、事件抽取自分析负例生成(Auto Neg)策略(提升 precision)

  • 环境安装

本项目基于 pytorch + transformers 实现,运行前请安装相关依赖包:

pip install -r ../requirements.txttorch
transformers==4.22.1
datasets==2.4.0
evaluate==0.2.2
matplotlib==3.6.0
rich==12.5.1
scikit-learn==1.1.2
requests==2.28.1

2. 数据集准备

项目中提供了一部分示例数据,数据来自DuIE数据集中随机抽取的100条,数据在 data/DuIE

若想使用自定义数据训练,只需要仿照示例数据构建数据集构建prompt和content即可:

{"content": "谭孝曾是谭元寿的长子,也是谭派第六代传人", "result_list": [{"text": "谭元寿", "start": 4, "end": 7}], "prompt": "谭孝曾的父亲"}
{"content": "在圣保罗书院中学毕业后,曾钰成又在中学会考及大学入学考试中名列全港前十名", "result_list": [{"text": "曾钰成", "start": 12, "end": 15}], "prompt": "人物"}
{"content": "在圣保罗书院中学毕业后,曾钰成又在中学会考及大学入学考试中名列全港前十名", "result_list": [{"text": "圣保罗书院", "start": 1, "end": 6}], "prompt": "曾钰成的毕业院校"}
...

doccano导出数据如下所示:

{"text": "谭孝曾是谭元寿的长子,也是谭派第六代传人", "entities": [{"id": 42517, "label": "人物", "start_offset": 0, "end_offset": 3, "text": "谭孝曾"}, {"id": 42518, "label": "人物", "start_offset": 4, "end_offset": 7, "text": "谭元寿"}], "relations": [{"id": 0, "from_id": 42517, "to_id": 42518, "type": "父亲"}]}
...

可以运行 doccano.py 来将标注数据(doccano)转换为训练数据(prompt)。

3. 模型训练

修改训练脚本 train.sh 里的对应参数, 开启模型训练:

python train.py \--pretrained_model "uie-base-zh" \--save_dir "checkpoints/DuIE" \--train_path "data/DuIE/train.txt" \--dev_path "data/DuIE/dev.txt" \--img_log_dir "logs/" \--img_log_name "UIE Base" \--batch_size 32 \--max_seq_len 256 \--learning_rate 5e-5 \--num_train_epochs 20 \--logging_steps 10 \--valid_steps 100 \--device cuda:0

正确开启训练后,终端会打印以下信息:

...0%|          | 0/1 [00:00<?, ?ba/s]
100%|██████████| 1/1 [00:00<00:00,  6.91ba/s]
100%|██████████| 1/1 [00:00<00:00,  6.89ba/s]
global step 10, epoch: 1, loss: 0.00244, speed: 2.08 step/s
global step 20, epoch: 1, loss: 0.00228, speed: 2.17 step/s
global step 30, epoch: 1, loss: 0.00191, speed: 2.17 step/s
global step 40, epoch: 1, loss: 0.00168, speed: 2.14 step/s
global step 50, epoch: 1, loss: 0.00149, speed: 2.11 step/s
global step 60, epoch: 1, loss: 0.00138, speed: 2.15 step/s
global step 70, epoch: 2, loss: 0.00123, speed: 2.29 step/s
global step 80, epoch: 2, loss: 0.00112, speed: 2.12 step/s
global step 90, epoch: 2, loss: 0.00102, speed: 2.15 step/s
global step 100, epoch: 2, loss: 0.00096, speed: 2.15 step/s
Evaluation precision: 0.80851, recall: 0.84444, F1: 0.82609
best F1 performence has been updated: 0.00000 --> 0.82609
...

logs/UIE Base.png 文件中将会保存训练曲线图:

4. 模型预测

完成模型训练后,运行 inference.py 以加载训练好的模型并应用:

 if __name__ == "__main__":from rich import printsentences = ['谭孝曾是谭元寿的长子,也是谭派第六代传人。']# NER 示例for sentence in sentences:ner_example(model,tokenizer,device,sentence=sentence, schema=['人物'])# SPO 抽取示例for sentence in sentences:information_extract_example(model,tokenizer,device,sentence=sentence, schema={'人物': ['父亲'],})

NER和事件抽取在schema的定义上存在一些区别:

  • NER的schema结构为 List 类型,列表中包含所有要提取的 实体类型

  • 信息抽取的schema结构为 Dict 类型,其中 Key 的值是所有 主语Value 对应该主语对应的所有 属性

  • 事件抽取的schema结构为 Dict 类型,其中 Key 的值是所有 事件触发词Value 对应每一个触发词下的所有 事件属性

python inference.py

得到以下推理结果:

[+] NER Results: 
{'人物': ['谭孝曾', '谭元寿']
}[+] Information-Extraction Results: 
{'谭孝曾': {'父亲': ['谭元寿']}, '谭元寿': {'父亲': []}
}

5. 数据增强(Data Augmentation)

信息抽取/事件抽取的数据标注成本较高,因此我们提供几种针对小样本下的数据增强策略。

包括:

  • 正例:SwapSPO、Mask Then Fill

  • 负例:自分析负例生成(Auto Neg)

所有实现均在 Augmenter.py 中,为了便于使用,我们将其封装为 web 服务以方便调用:

平台使用 streamlit 搭建,因此使用前需要先安装三方包:

pip install streamlit==1.17.0

随后,运行以下命令开启标注平台:

streamlit run web_da.py --server.port 8904

在浏览器中访问 ip + 端口(默认8904)即可打开标注平台。


5.1 正例:SwapSPO 策略介绍

Swap SPO 是一种基于规则的简单数据增强策略。

将同一数据集中相同 P 的句子分成一组,并随机交换这些句子中的 S 和 O。

  • 策略输入:

《夜曲》周杰伦 作曲 的一首歌。

《那些你很冒险的梦》 是当下非常火热的一首歌,作曲林俊杰

  • Swap SPO 后的输出:

《夜曲》 是当下非常火热的一首歌,作曲周杰伦

5.2 正例:Mask Then Fill 策略介绍

Mask Then Fill 是一种基于生成模型的信息抽取数据增强策略。

对于一段文本,我们其分为「关键信息段」和「非关键信息段」,包含关键词片段称为「关键信息段」。

下面例子中标粗的为 关键信息片段,其余的为 非关键片段

大年三十 我从 北京 的大兴机场 飞回成都

我们随机 [MASK] 住一部分「非关键片段」,使其变为:

大年三十 我从 北京 [MASK] 飞回成都

随后,将该句子喂给 filling 模型(T5-Fine Tuned)还原句子,得到新生成的句子:

大年三十 我从 北京 首都机场作为起点,飞回成都

Note: filling 模型是一个生成模型,示例中我们使用中文 T5 微调得到 DuIE 数据集下的模型(暂未开源)。您可以参考 这里 微调一个更适合您自己数据集下的 filling 模型,并将训练好的模型路径填写至 web_da.py 中对应的位置。

...
device = 'cpu'                                                 # 指定设备
generated_dataset_height = 800                                 # 生成样本展示高度                
max_show_num = 500                                             # 生成样本最大保存行数
max_seq_len = 128                                              # 数据集单句最大长度
batch_size = 128                                               # 负例生成时的batch_sizefilling_model_path = '这里'                                     # fine-tuned filling model
...

5.3 负例:自分析负例生成(Auto Neg)策略介绍

信息抽取中通常会存在 P混淆 的问题,例如:

王文铭,76岁,是西红市多鱼村的大爷。

当我们同时生成 年龄去世年龄 这种非常近义的 prompt 进行抽取时,可能会出现 误召回 的情况:

prompt: 王文铭的年龄     answer: 76-> 正确
prompt: 王文铭的去世年龄  answer: 76-> 错误

因此,我们基于一个已训练好的模型,自动分析该模型在 训练集 下存在哪些易混淆的 P,并为这些 P 自动生成负例,以提升模型的 Precision 指标。

将新生成的负例加入 原始训练数据集,重新训练模型即可。

5.4 各种 DA 策略的实验效果

在 DuIE 100 条数据下测试,各种 DA 策略的效果如下所示(以下 P / R / F1 均取 F1 最高的 Epoch 指标):

DA PolicyPrecision(best)Recall(best)F1(best)
baseline0.80850.84440.8260
Swap SPO0.8409(↑)0.82220.8314(↑)
Auto Neg0.8297(↑)0.8666(↑)0.8478(↑)
Mask Then Fill0.9000(↑)1.0000(↑)0.9473(↑)
Mask Then Fill & Auto Neg0.9777(↑)0.9777(↑)0.9777(↑)
  • 原作者实现地址:https://github.com/universal-ie/UIE

  • paddle官方:https://github.com/PaddlePaddle/PaddleNLP/tree/develop/model_zoo/uie

  • https://github.com/HarderThenHarder/transformers_tasks/blob/main/UIE

更多优质内容请关注公号:汀丶人工智能;会提供一些相关的资源和优质文章,免费获取阅读。

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

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

相关文章

从NLP到聊天机器人

一、说明 今天&#xff0c;当打电话给银行或其他公司时&#xff0c;听到电话另一端的机器人向你打招呼是很常见的&#xff1a;“你好&#xff0c;我是你的数字助理。请问你的问题。是的&#xff0c;机器人现在不仅可以说人类语言&#xff0c;还可以用人类语言与用户互动。这是由…

windows权限维持—黄金白银票据隐藏用户远控RustDeskGotoHttp

windows权限维持—黄金白银票据&隐藏用户&远控&RustDesk&GotoHttp 1. 前置1.1. 初始问题1.1.1. 解决办法 2. 隐藏用户2.1. 工具原理2.2. 案例操作2.2.1. 单机添加用户2.2.1.1. 工具添加用户2.2.1.2. 工具查看隐藏用户2.2.1.3. 本地查看隐藏用户 2.2.2. 域内添加…

NeuralNLP-NeuralClassifier的使用记录(二),训练预测自己的【中文文本多分类】

NeuralNLP-NeuralClassifier的使用记录&#xff0c;训练预测自己的【中文文本多分类】 数据准备&#xff1a; ​ 与英文的训练预测一致&#xff0c;都使用相同的数据格式&#xff0c;将数据通过代码处理为JSON格式&#xff0c;以下是我使用的一种&#xff0c;不同的原数据情况…

java+springboot+mysql理发会员管理系统

项目介绍&#xff1a; 使用javaspringbootmysql开发的理发会员管理系统&#xff0c;系统包含超级管理员&#xff0c;系统管理员、客户、发型师角色&#xff0c;功能如下&#xff1a; 超级管理员&#xff1a;管理员管理&#xff1b;会员管理&#xff1b;发型师管理&#xff1b…

如何保证数据库的数据和Redis的数据一致性

实际项目中有可能会使用Redis缓存数据&#xff0c;那么在更新数据的时候如何保证数据库中的数据和Redis缓存的数据一致&#xff0c;缓存同步策略的选择是一个很重要的问题。网上有各种说法&#xff0c;大概总结有以下几种&#xff0c;看看每种方案是否可行以及存在的问题和适用…

安装软件包

安装软件包 创建一个名为 /home/curtis/ansible/packages.yml 的 playbook : 将 php 和 mariadb 软件包安装到 dev、test 和 prod 主机组中的主机上 将 RPM Development Tools 软件包组安装到 dev 主机组中的主机上 将 dev 主机组中主机上的所有软件包更新为最新版本 vim packa…

关于Firmae缺失binwalk模块

问题 david707:~/FirmAE$ sudo ./run.sh -c weyow ./WAM_9900-20.06.03V.trx [*] ./WAM_9900-20.06.03V.trx emulation start!!! Traceback (most recent call last):File "./sources/extractor/extractor.py", line 19, in <module>import binwalk ModuleNot…

Docker容器:docker基础概述、安装、网络及资源控制

文章目录 一.docker容器概述1.什么是容器2. docker与虚拟机的区别2.1 docker虚拟化产品有哪些及其对比2.2 Docker与虚拟机的区别 3.Docker容器的使用场景4.Docker容器的优点5.Docker 的底层运行原理6.namespace的六项隔离7.Docker核心概念 二.Docker安装 及管理1.安装 Docker1.…

【k8s】基于Prometheus监控Kubernetes集群安装部署

目录 基于Prometheus监控Kubernetes集群安装部署 一、环境准备 二、部署kubernetes集群 三、部署Prometheus监控平台 四、部署Grafana服务 五、grafana web操作 基于Prometheus监控Kubernetes集群安装部署 一、环境准备 IP地址 主机名 组件 192.168.100.131 k8s-ma…

时序预测 | MATLAB实现WOA-CNN-GRU鲸鱼算法优化卷积门控循环单元时间序列预测

时序预测 | MATLAB实现WOA-CNN-GRU鲸鱼算法优化卷积门控循环单元时间序列预测 目录 时序预测 | MATLAB实现WOA-CNN-GRU鲸鱼算法优化卷积门控循环单元时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 时序预测 | MATLAB实现WOA-CNN-GRU鲸鱼算法优化卷积…

基于LVQ神经网络的人脸朝向识别

1案例背景 1.1人脸识别概述 人脸识别作为一个复杂的模式识别问题,近年来受到了广泛的关注,识别领域的各种方法在这个问题上各显所长,而且发展出了许多新方法,大大丰富和拓宽了模式识别的方向。人脸识别、检测,跟踪、特征定位等技术近年来一直是研究的热点。人脸识别是人脸应用…

【制作npm包1】申请npm账号、认识个人包和组织包

概述 在开发当中经常有一种现象&#xff0c;重复代码写了N多遍&#xff0c;再次写同样的逻辑就再次翻查以前的代码逻辑。效率低下且容易出错&#xff0c;封装一个npm包的价值也不仅仅是给别人用&#xff0c;封装一套属于自己或者本部门的npm包也是相当有必要。 也许经常看到一…

RabbitMQ的5种消息队列

RabbitMQ的5种消息队列 1、七种模式介绍与应用场景 1.1 简单模式(Hello World) 一个生产者对应一个消费者&#xff0c;RabbitMQ 相当于一个消息代理&#xff0c;负责将 A 的消息转发给 B。 应用场景&#xff1a;将发送的电子邮件放到消息队列&#xff0c;然后邮件服务在队列…

【git】初次使用git上传代码到github远程仓库

目录 0.前言1.新建代码库2.添加SSH公钥2.1 前置准备2.2 Git 基本信息设置2.3 添加SSH Key 3.本地仓库上传到github3.1 建立本地仓库并初始化3.2 初始化仓库3.3 建立本地与github上新建项目链接3.4 同步github新建项目到本地3.5 添加本地文件到缓存区3.6 为上传文件添加注释3.7 …

注册中心Eureka和Nacos,以及负载均衡Ribbon

1.初识微服务 1.1.什么是微服务 微服务&#xff0c;就是把服务拆分成为若干个服务&#xff0c;降低服务之间的耦合度&#xff0c;提供服务的独立性和灵活性。做到高内聚&#xff0c;低耦合。 1.2.单体架构和微服务架构的区别&#xff1a; 单体架构&#xff1a;简单方便&#…

DevExpress WinForms数据编辑器组件,提供丰富的数据输入样式!(一)

DevExpress WinForms超过80个高影响力的WinForms编辑器和多用途控件&#xff0c;从屏蔽数据输入和内置数据验证到HTML格式化&#xff0c;DevExpress数据编辑库提供了无与伦比的数据编辑选项&#xff0c;包括用于独立数据编辑或用于容器控件(如Grid, TreeList和Ribbon)的单元格。…

云原生 envoy xDS 动态配置 java控制平面开发 支持restful grpc实现 EDS 动态endpoint配置

envoy xDS 动态配置 java控制平面开发 支持restful grpc 动态endpoint配置 大纲 基础概念Envoy 动态配置API配置方式动静结合的配置方式纯动态配置方式实战 基础概念 Envoy 的强大功能之一是支持动态配置&#xff0c;当使用动态配置时&#xff0c;我们不需要重新启动 Envoy…

spring boot 整合mongodb

1、安装依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId></dependency>2、配置数据库连接 spring:data:mongodb:host: localhostport: 27017username: xxxxxxp…

2682. 找出转圈游戏输家

题目描述&#xff1a; n 个朋友在玩游戏。这些朋友坐成一个圈&#xff0c;按 顺时针方向 从 1 到 n 编号。从第 i 个朋友的位置开始顺时针移动 1 步会到达第 (i 1) 个朋友的位置&#xff08;1 < i < n&#xff09;&#xff0c;而从第 n 个朋友的位置开始顺时针移动 1 步…

【广州华锐视点】帆船航行VR模拟实操系统

帆船航行VR模拟实操系统由广州华锐视点开发&#xff0c;是一种创新的教学工具&#xff0c;它利用虚拟现实技术&#xff0c;为学生提供了一个沉浸式的学习环境。通过这种系统&#xff0c;学生可以在虚拟的环境中进行帆船航行的实训&#xff0c;从而更好地理解和掌握帆船航行的技…