简明Pytorch分布式训练 — DistributedDataParallel 实践

上一次的Pytorch单机多卡训练主要介绍了Pytorch里分布式训练的基本原理,DP和DDP的大致过程,以及二者的区别,并分别写了一个小样作为参考。小样毕竟还是忽略了很多细节和工程实践时的一些处理方式的。实践出真知,今天(简单)写一个实际可用的 DDP 训练中样,检验一下自己对 DDP 的了解程度,也作为以后分布式训练的模板,逐渐完善。

文章目录

  • DDP 流程回顾
    • 一些前置
  • DDP 实践
    • 训练前
    • 训练时
    • 启动脚本
  • 更上一层楼
  • 结语
  • Reference

DDP 流程回顾

Pytorch单机多卡训练中主要从DDP的原理层面进行了介绍,这次从实践的角度来看看。

试问把大象装进冰箱需要几步?三步,先打开冰箱,再把大象装进去,最后关上冰箱门。那实践中DDP又分为几步呢?

  1. 开始训练/预测前的准备工作。
  2. 开始训练/验证!

看,DDP比把大象装进冰箱简单多了!

一些前置

虽然DDP很简单,但是得先了解一些前置知识。有一些DDP相关的函数是必须要了解的1,主要是和分布式通信相关的函数:

  • torch.distributed.init_process_group2。初始化分布式进程组。由于是单机多卡看的分布式,因此其实是将任务分布在多个进程中。
  • torch.distributed.barrier()3。用于进程同步。每个进程进入这个函数后都会被阻塞,当所有进程都进入这个函数后,阻塞解除,继续执行后续的代码。
  • torch.distributed.all_gather4。收集不同进程中的tensor。这个用到的比较多的,但有一点不太好理解。解释一下:某些变量是不同进程中都会有的,比如loss,如果要把各个进程中计算得到的loss汇总到一起,就需要进程间通信,把loss收集起来,all_gather干的就是收集不同进程中的某个tensor的事儿。
  • local_rank。节点上device的标识。在单机多卡的模式下,每个device的local_rank是唯一的,一般我们都在0号设备上操作。该参数不需要手动传参,在执行DDP时会自动设置该参数。当为非DDP时,该参数值为-1。
  • torch.nn.parallel.DistributedDataParallel5。在初始化好的分布式环境中,创建分布式的模型,负责。

好了,你已经具备DDP实践的主要前置知识了,一起开启DDP实践吧!

DDP 实践

训练前

训练前的准备工作,主要包含:

  • 初始化分布式进程组,使后续各个进程间能够通信。
  • 准备好数据和模型。
torch.cuda.set_device(args.local_rank)
device = torch.device("cuda", args.local_rank)
torch.distributed.init_process_group(backend="nccl", timeout=datetime.timedelta(seconds=3600))
args.device = device

X, Y = make_classification(n_samples=25000, n_features=N_DIM, n_classes=2, random_state=args.seed)  # 每个进程都会拥有这样一份数据集
X = torch.tensor(X).float()
Y = torch.tensor(Y).float()

train_X, train_Y = X[:20000], Y[:20000]
val_X, val_Y = X[20000:], Y[20000:]
train_dataset = SimpleDataset(train_X, train_Y)
val_dataset = SimpleDataset(val_X, val_Y)

model = SimpleModel(N_DIM)
if args.local_rank not in (-1, 0):
    torch.distributed.barrier()
else:
    # 只需要在 0 号设备上加载预训练好的参数即可,后续调用DDP时会自动复制到其他卡上
    if args.ckpt_path is not None:
        model.load_state_dict(torch.load(args.ckpt_path))
    torch.distributed.barrier()

训练时

与单卡训练不同,DDP训练主要有三个改动地方:

  • 创建数据加载器时,需要创建特定的采样器,以保证每个进程使用的数据是不重复的
  • 创建分布式的模型。
  • 收集一些辅助信息时,例如loss、prediction等,需要all_gather将不同进程中的数据收集到一起。
model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[args.local_rank], output_device=args.local_rank, find_unused_parameters=False)

train_sampler = RandomSampler(train_dataset) if args.local_rank == -1 else DistributedSampler(train_dataset)
train_dataloader = DataLoader(train_dataset, sampler=train_sampler, batch_size=args.batch_size)

...

def gather_tensors(target):
    """
    target should be a tensor
    """
    target_list = [torch.ones_like(target) for _ in range(torch.distributed.get_world_size())]
    torch.distributed.all_gather(target_list, target)
    ret = torch.hstack(target_list)
    
    return ret

for epoch in train_iterator:
    if args.local_rank != -1:
        train_dataloader.sampler.set_epoch(epoch)  # 必须设置的!确保每一轮的随机种子是固定的!

    model.zero_grad()
    model.train()

    pbar = tqdm(train_dataloader, desc="Training", disable=args.local_rank not in [-1, 0])
    step_loss, step_label, step_pred = [], [], []
    for step, batch in enumerate(pbar):
        data, label = batch
        data, label = data.to(args.device), label.to(args.device)

        prediction = model(data)
        loss = loss_func(prediction, label.unsqueeze(1))
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        global_step += 1

        step_loss.append(loss.item())
        step_label.extend(label.squeeze().cpu().tolist())
        step_pred.extend(prediction.squeeze().detach().cpu().tolist())

        if step % args.logging_step == 0:
            if torch.distributed.is_initialized():  # 兼容非 DDP 的模式
                step_loss = torch.tensor(step_loss).to(args.device)
                step_label = torch.tensor(np.array(step_label)).to(args.device)
                step_pred = torch.tensor(np.array(step_pred)).to(args.device)
                step_loss = gather_tensors(step_loss)
                step_label = gather_tensors(step_label).squeeze().cpu().numpy()
                step_pred = gather_tensors(step_pred).squeeze().cpu().numpy()

单卡训练时,收集一些辅助信息是很方便的,因为所有的数据都在同一进程中,所以不需要同步操作。在DDP模式下,每个进程使用不同的数据训练,为了记录训练过程的信息以及计算指标等情况,需要把所有数据的结果收集起来,这就需要用到all_gather了。

启动脚本

一种常用的DDP启动方式:

#! /usr/bin/env bash
node_num=4

# DDP 模式
CUDA_VISIBLE_DEVICES="0,1,2,3" python -m torch.distributed.launch --nproc_per_node=$node_num --master_addr 127.0.0.2 --master_port 29502 ddp-demo.py \
--do_train \
--do_test \
--epochs 3 \
--batch_size 128 \
--lr 5e-6 \
--output_dir ./test_model \
--seed 2024

python -m torch.distributed.launch即把torch.distributed.launch这个模块作为一个脚本运行,后面接的 --nproc_per_node=$node_num --master_addr 127.0.0.2 --master_port 29502 ddp-demo.py都作为这个模块的参数,关于该模块的参数以及含义,可以参考:https://github.com/pytorch/pytorch/blob/main/torch/distributed/launch.py。

更上一层楼

果然,DDP训练比把大象装进冰箱要简单多了。

当然,这主要归功于Pytorch封装的好,还有很多细节是值得我们深究的。以下是一些我觉得可以进一步探索的:

  • 分布式训练时不同的后端之间的区别,比如gloompinccl。主要是加深对多进程通信的了解。
  • torch.nn.parallel.DistributedDataParallel内部的细节,比如梯度的同步过程及涉及到的算法等。
  • DistributedSampler是如何实现不同device读取不同的数据的。这一块其实原理很简单,源码也不多。
  • 多机多卡的实践6

结语

单卡或DP训练相比,DDP启动多个进程,每个进程读取对应的数据,每个step单独训练,能够大大提高显卡的利用率和降低训练时间。

初次接触DDP还是很容易让人茫然的,如果有过多进程的经验的会更好理解其中的一些概念以及分布式训练的流程。

目前写的这个demo虽然比较简单,但常用的内容都涉及到了,后续可以在这个版本上继续完善。

附完整代码:

import os
import time
import random
import logging
import argparse
import datetime
from tqdm import tqdm, trange

import numpy as np
from sklearn import metrics
from sklearn.datasets import make_classification

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader, Dataset
from torch.utils.data.distributed import DistributedSampler

import torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel as DDP


N_DIM = 200
logging.basicConfig(format='[%(asctime)s] %(levelname)s - %(name)s - %(message)s',
                    datefmt='%m-%d %H:%M:%S',
                    level=logging.INFO)
logger = logging.getLogger("DDP")

class SimpleModel(nn.Module):
    def __init__(self, input_dim):
        super(SimpleModel, self).__init__()
        self.fc = nn.Linear(input_dim, 1)

    def forward(self, x):
        return torch.sigmoid(self.fc(x))

class SimpleDataset(Dataset):
    def __init__(self, data, target):
        self.data = data
        self.target = target

    def __len__(self):
        return len(self.data)

    def __getitem__(self, idx):
        return self.data[idx], self.target[idx]

def set_seed(args):
    seed = args.seed
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    if args.n_gpu > 0:
        torch.cuda.manual_seed_all(seed)
    
def parse_args():
    parser = argparse.ArgumentParser()
    ## DDP:从外部得到local_rank参数。从外面得到local_rank参数,在调用DDP的时候,其会自动给出这个参数
    parser.add_argument("--local_rank", default=-1, type=int)
    parser.add_argument("--no_cuda", action="store_true", help="Whether to cuda.")
    parser.add_argument("--seed", default=2024, type=int)
    
    parser.add_argument("--ckpt_path", default="./ddp-outputs/checkpoint-100/model.pt", type=str)
    parser.add_argument("--output_dir", default="./ddp-outputs/", type=str)
    
    parser.add_argument("--epochs", default=3, type=int)
    parser.add_argument("--lr", default=1e-5, type=float)
    parser.add_argument("--batch_size", default=2, type=int)
    parser.add_argument("--val_step", default=50, type=int)
    parser.add_argument("--save_step", default=10, type=int)
    parser.add_argument("--logging_step", default=2, type=int)
    
    parser.add_argument("--do_train", action="store_true", help="Whether to run training.")
    parser.add_argument("--do_test", action="store_true", help="Whether to run eval on the test set.")
    
    args = parser.parse_args()
    return args


def gather_tensors(target):
    """
    target should be a tensor
    """
    target_list = [torch.ones_like(target) for _ in range(torch.distributed.get_world_size())]
    torch.distributed.all_gather(target_list, target)
    # ret = torch.cat(target_list, 0)
    ret = torch.hstack(target_list)
    
    return ret


def train(args, model, train_dataset, val_dataset=None):
    if args.n_gpu > 1:
        model = torch.nn.DataParallel(model)
    
    model = model.to(args.device)
    if args.local_rank != -1:
        model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[args.local_rank], output_device=args.local_rank, find_unused_parameters=False)
    
    logger.info(f"After DDP, Rank = {args.local_rank}, weight = {model.module.fc.weight.data[0, :10]}")
    
    # 采样器
    train_sampler = RandomSampler(train_dataset) if args.local_rank == -1 else DistributedSampler(train_dataset)
    train_dataloader = DataLoader(train_dataset, sampler=train_sampler, batch_size=args.batch_size)
    
    optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=args.lr)
    loss_func = nn.BCELoss().to(args.local_rank)
    
    total_steps = len(train_dataloader) * args.epochs
    global_step = 0
    train_iterator = trange(0, int(args.epochs), desc="Epoch", disable=args.local_rank not in [-1, 0])
    for epoch in train_iterator: #range(args.epochs):  # train_iterator:
        if args.local_rank != -1:
            train_dataloader.sampler.set_epoch(epoch)
        
        if args.local_rank in (-1, 0):
            logger.info('*' * 25 + f" Epoch {epoch + 1} / {args.epochs} " + '*'* 25)
        
        model.zero_grad()
        model.train()
        
        pbar = tqdm(train_dataloader, desc="Training", disable=args.local_rank not in [-1, 0])
        step_loss, step_label, step_pred = [], [], []
        for step, batch in enumerate(pbar):
            data, label = batch
            data, label = data.to(args.device), label.to(args.device)
            
            prediction = model(data)
            loss = loss_func(prediction, label.unsqueeze(1))
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            global_step += 1
            time.sleep(0.1)
            
            step_loss.append(loss.item())
            step_label.extend(label.squeeze().cpu().tolist())
            step_pred.extend(prediction.squeeze().detach().cpu().tolist())
            
            if step % args.logging_step == 0:
                if torch.distributed.is_initialized():  # 兼容非 DDP 的模式
                    step_loss = torch.tensor(step_loss).to(args.device)
                    step_label = torch.tensor(np.array(step_label)).to(args.device)
                    step_pred = torch.tensor(np.array(step_pred)).to(args.device)
                    step_loss = gather_tensors(step_loss)
                    step_label = gather_tensors(step_label).squeeze().cpu().numpy()
                    step_pred = gather_tensors(step_pred).squeeze().cpu().numpy()
                
                if args.local_rank in (-1, 0):
                    # logger.info(f"Gathered loss = {step_loss}")
                    # logger.info(f"Gathered label = {step_label}")
                    # logger.info(f"Label shape = {step_label.shape}, Pred = {step_pred}, {prediction.shape}")
                    if not all(step_label == 1) and not all(step_label == 0):
                        auc = metrics.roc_auc_score(step_label, step_pred)
                        # logger.info(f"Step AUC = {auc:.5f}")
                        pbar.set_description(f"loss={step_loss.mean():>.5f}, auc={auc:.5f}")
                
                step_loss, step_label, step_pred = [], [], []
            
            if step % args.val_step == 0 and val_dataset is not None:
                test_val(args, model, val_dataset)
            
            if global_step % args.save_step == 0 and global_step > 0 and args.local_rank in [-1, 0]:
                save_path = os.path.join(args.output_dir, f"checkpoint-{global_step}")
                if not os.path.exists(save_path):
                    os.makedirs(save_path)
                model_to_save = model.module if hasattr(model, 'module') else model
                torch.save(model_to_save.state_dict(), os.path.join(save_path, "model.pt"))
                torch.save(optimizer.state_dict(), os.path.join(save_path, "optimizer.pt"))
            
                
def test_val(args, model, dataset):
    # 采样器
    test_sampler = RandomSampler(dataset) if args.local_rank == -1 else DistributedSampler(dataset)
    test_dataloader = DataLoader(dataset, sampler=test_sampler, batch_size=args.batch_size)
    
    ground_truth = []
    prediction = []
    pbar = tqdm(test_dataloader, desc="Testing", disable=args.local_rank not in [-1, 0])
    with torch.no_grad():
        for step, batch in enumerate(pbar):
            data, label = batch
            data, label = data.to(args.device), label.to(args.device)
            pred = model(data)
            time.sleep(0.05)

            ground_truth.extend(label.cpu().squeeze().tolist())
            prediction.extend(pred.cpu().squeeze().tolist())

        ground_truth = torch.tensor(ground_truth).to(args.device)
        prediction = torch.tensor(prediction).to(args.device)
    
        if args.local_rank != -1:  # 等待所有进程预测完,再进行后续的操作
            torch.distributed.barrier()

        if torch.distributed.is_initialized():  # 由于每张卡上都会有 prediction 和 ground_truth,为了汇集所有卡上的信息,需要 torch.distributed.all_gather
            ground_truth = gather_tensors(ground_truth)
            prediction = gather_tensors(prediction)
    
        if args.local_rank in [-1, 0]:
            print(f"GT : {ground_truth.shape}\tPred : {prediction.shape}")
            auc = metrics.roc_auc_score(ground_truth.detach().cpu(), prediction.detach().cpu())
            logger.info(f"Test Auc: {auc:.5f}")
        
     
def main(args):
    if args.local_rank == -1 or args.no_cuda:
        device = torch.device("cuda" if torch.cuda.is_available() and not args.no_cuda else "cpu")
        args.n_gpu = torch.cuda.device_count()
    else:
        torch.cuda.set_device(args.local_rank)
        device = torch.device("cuda", args.local_rank)
        torch.distributed.init_process_group(backend="nccl", timeout=datetime.timedelta(seconds=3600))
        args.n_gpu = 1
    args.device = device
    
    if args.local_rank in (-1, 0):
        logger.info('*' * 66)
        for k, v in vars(args).items():
            print(f"########## {k:>20}:\t{v}")
        logger.info('*' * 66)
    
    set_seed(args)
    
    model = SimpleModel(N_DIM)
    if args.local_rank not in (-1, 0):
        logger.info(f"Barrier in {args.local_rank}")
        torch.distributed.barrier()  # barrier 用于进程同步。每个进程进入这个函数后都会被阻塞,当所有进程都进入这个函数后,阻塞解除。
    else:
        if args.ckpt_path is not None:
            logger.info(f"Load pretrained model in {args.local_rank}")
            model.load_state_dict(torch.load(args.ckpt_path))
        logger.info(f"Barrier in {args.local_rank}")
        torch.distributed.barrier()
    logger.info(f"After barrier in {args.local_rank}")
    
    logger.info(f"Before DDP, Rank = {args.local_rank}, weight = {model.fc.weight.data[0, :10]}")
    
    X, Y = make_classification(n_samples=25000, n_features=N_DIM, n_classes=2, random_state=args.seed)  # 每个进程都会拥有这样一份数据集
    X = torch.tensor(X).float()
    Y = torch.tensor(Y).float()
    
    # 通过这个可以看到,在不同进程上的 X 的 id 是不同的,但是内容是一致的
    logger.info(f"Rank = {args.local_rank}, id(X) = {id(X)}, Y[:20] = {Y[:20]}")  
    
    train_X, train_Y = X[:20000], Y[:20000]
    val_X, val_Y = X[20000:], Y[20000:]
    train_dataset = SimpleDataset(train_X, train_Y)
    val_dataset = SimpleDataset(val_X, val_Y)
    
    if args.do_train:
        train(args, model, train_dataset, val_dataset)
    
    if args.do_test:
        test_val(args, model, val_dataset)


if __name__ == "__main__":
    args = parse_args()
    main(args)

在这里插入图片描述

Reference


  1. DISTRIBUTED COMMUNICATION PACKAGE - TORCH.DISTRIBUTED. ↩︎

  2. 官方文档:torch.distributed.init_process_group. ↩︎

  3. 官方文档:torch.distributed.barrier. ↩︎

  4. 官方文档:torch.distributed.all_gather. ↩︎

  5. 官方文档-torch.nn.parallel.DistributedDataParallel. ↩︎

  6. Multi Node PyTorch Distributed Training Guide For People In A Hurry. ↩︎

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微服务(基础篇-007-RabbitMQ)

目录 初识MQ(1) 同步通讯(1.1) 异步通讯(1.2) MQ常见框架(1.3) RabbitMQ快速入门(2) RabbitMQ概述和安装(2.1) 常见消息模型(2.2) 快速入门&#xff…

YARN集群 和 MapReduce 原理及应用

YARN集群模式 本文内容需要基于 Hadoop 集群搭建完成的基础上来实现 如果没有搭建&#xff0c;请先按上一篇: <Linux 系统 CentOS7 上搭建 Hadoop HDFS集群详细步骤> 搭建&#xff1a;https://mp.weixin.qq.com/s/zPYsUexHKsdFax2XeyRdnA 配置hadoop安装目录下的 etc…

docker 部署 nali 开源 IP 地理信息归属查询软件

前言 早前用到一个小巧开源的 IP 归属地查询软件&#xff0c;官方提供了 Dockerfile&#xff0c;使用了一段时间觉得还不错&#xff0c;非常简单便捷。 部署 docker 启动 由于该项目会在首次启动自动下载 IP 数据库,所以最好通过挂载目录的方式,将数据库目录挂在到本地,避免…

代码随想录 Day24 理论基础 77. 组合

理论基础 1. 回溯是配合递归算法进行使用的&#xff0c;一般是在递归的下面。回溯的算法是一种暴力的算法&#xff0c;虽然效率并不高&#xff0c;但是常常使用。因为很多时候使用多层for&#xff08;因为层数实在是套多了&#xff09;也不能将题目解答&#xff0c;这个时候就…

HCIP【GRE VPN、MGRE VPN与PPP验证综合实验】

目录 实验要求&#xff1a; 实验拓扑图&#xff1a; 实验思路&#xff1a; 实验步骤&#xff1a; 一、配IP地址 &#xff08;1&#xff09;配置所有设备接口的IP地址&#xff1a; &#xff08;2&#xff09;配置私网与公网接口的缺省路由使得公网可通&#xff1a; 二、P…

学习日记(SSM整合流程_SpringMVC_part_two)

目录 大致流程如下 1、创建工程 2、SSM配置类结构 3、功能模块 代码部分 整体结构 Jdbc.Config MyBatisConfig ServletConfig SpringConfig SpringMvcConfig BookController BookDao Book BusinessException SystemException Cord Result BookService BookserviceImpl jd…

计算机网络 - 基础篇总结

TCP/IP 网络模型有哪几层&#xff1f; 1.应用层 为用户提供应用功能 2.传输层 负责为应用层提供网络支持 使用TCP和UDP 当传输层的数据包大小超过 MSS&#xff08;TCP 最大报文段长度&#xff09; &#xff0c;就要将数据包分块&#xff0c;这样即使中途有一个分块丢失或损坏…

Python耗时统计-可嵌套-生成Timeline-chrome://tracing/预览

Python耗时统计-可嵌套-生成Timeline-chrome://tracing/预览 一.效果二.代码 本文演示了如何用chrome://tracing查看python嵌套代码的耗时成分 一.效果 二.代码 import time import os import threading import queuedef singleeton(cls):单例instance{}def _singleton(*args,…

【opencv】教程代码 —features2D(3)Homography—分解单应性矩阵

decompose_homography.cpp 分解单应性矩阵 left01.jpg boardSize&#xff1a;9x6 squareSize:0.025 left02.jpg 相机内参 #include <iostream> // 引入输入输出流库 #include <opencv2/core.hpp> // 引入OpenCV的核心功能头文件 #include <opencv2/highgui.hp…

MATLAB 自定义中值滤波(54)

MATLAB 自定义中值滤波(54) 一、算法介绍二、算法实现1.原理2.代码一、算法介绍 中值滤波,是一种常见的点云平滑算法,改善原始点云的数据质量问题,MATLAB自带的工具似乎不太友好,这里提供自定义实现的点云中值滤波算法,具体效果如下所示: 中值滤波前: 中值滤波后:…

【Node.js】大文件上传

概述 大文件上传通常采用分片上传。如果因为某些原因上传突然中断&#xff0c;解决问题之后可以接着之前的分片上传&#xff0c;而不需要从头开始上传&#xff0c;也就是断点续传。此外还可以利用多个网络连接并行上传多个分片&#xff0c;提高上传速度。 注&#xff1a;前端不…

动手学机器学习逻辑斯谛回归+习题

逻辑斯谛函数下的线性模型 现需要用线性模型做分类问题&#xff0c;简单的阶跃函数在阈值处不可导&#xff0c;可导处导数均为0&#xff0c;性质不好 所以把0&#xff0c;1问题转化成P(y0|x)&#xff0c;P(y1|x)的问题&#xff0c;这样就把离散的分类任务变成了求概率分布的回…

ROS传感器图像转换

ros通过摄像头来获得图片&#xff0c;传感器数据类型为sensor_msgs中的Image&#xff0c;具体的数据类型组成&#xff1a; sensor_msgs/Image Documentationhttp://docs.ros.org/en/api/sensor_msgs/html/msg/Image.html但是我们一般使用opencv对图像进行处理&#xff0c;所以…

Python字符串字母大小写变换,高级Python开发技术

寻找有志同道合的小伙伴&#xff0c;互帮互助,群里还有不错的视频学习教程和PDF电子书&#xff01; ‘’’ demo ‘tHis iS a GOod boOK.’ print(demo.casefold()) print(demo.lower()) print(demo.upper()) print(demo.capitalize()) print(demo.title()) print(dem…

Python字典操作

假设我们有一个学生信息数据库&#xff0c;其中存储了每个学生的姓名、年龄、性别和成绩。我们可以使用字典来表示每个学生的信息&#xff0c;并将所有学生存储在一个字典列表中。 设计者&#xff1a;ISDF 版本&#xff1a;v1.0 日期&#xff1a;03/29/2024# 定义学生信息字典列…

如何划分训练集、测试集、验证集

训练集、测试集和验证集是在机器学习和数据科学中常用的术语&#xff0c;用于评估和验证模型的性能。它们通常用于监督学习任务中。 1. 训练集&#xff08;Training Set&#xff09;&#xff1a;训练集是用于训练机器学习模型的数据集。在训练期间&#xff0c;模型使用训练集中…

小狐狸ChatGPT付费AI创作系统V2.8.0独立版 + H5端 + 小程序前端

狐狸GPT付费体验系统的开发基于国外很火的ChatGPT&#xff0c;这是一种基于人工智能技术的问答系统&#xff0c;可以实现智能回答用户提出的问题。相比传统的问答系统&#xff0c;ChatGPT可以更加准确地理解用户的意图&#xff0c;提供更加精准的答案。同时&#xff0c;小狐狸G…

图形推理 总结

原则 1.图形相似且元素基本不变&#xff1a;此时多考虑图形的位置移动规律&#xff0c;如平移、旋转、翻转等。 2.图形相似但元素有同有异&#xff1a;这种情况下常考组合叠加-去异存同、去同存异等;元素遍历;部分传递等。 3.图形相异但较规则&#xff1a;常考对称、直曲性、…

JDK8的下载安装与环境变量配置教程

前言 官网下载&#xff1a;Java Archive Downloads - Java SE 8u211 and later 现在应该没人用32位的系统了吧&#xff0c;直接下载Windows x64 Installer jdk-8u391-windows-x64.exe 一、安装JDK 1. 打开jdk-8u391-windows-x64.exe 2. 直接下一步 3. 这个地方不要动他&…

神经网络与深度学习(一)

线性回归 定义 利用数理统计中回归分析&#xff0c;来确定两种或两种以上变量间相互依赖的定量关系的一种统计分析方法 要素 训练集&#xff08;训练数据&#xff09;输出数据拟合函数数据条目数 场景 预测价格&#xff08;房屋、股票等&#xff09;、预测住院时间&#…