《动手做科研 》| 05. 如何开展和记录实验

地址链接:《动手做科研》05. 如何开展和记录实验

导读: 当我们开始训练多个具有不同超参数的模型,我们就需要对实验开始进行管理。我们将其分为三个部分:实验追踪、超参数搜索和配置设置。我们将使用 Weights & Biases 来演示实验记录和追踪;然后,我们将利用 Weights & Biases Sweeps 对训练超参数进行超参数搜索;最后,我们将使用 Hydra 来优雅地配置我们日益复杂的深度学习应用。

本次课程目的在于能够让你了解并实践如何将实验管理工具整合到你的模型训练工作流程中。

本教程目标

  1. 通过Weights & Biases管理实验记录
  2. 使用 Sweeps 执行超参数搜索。
  3. 使用 Hydra 管理复杂的配置。

本教程内容

0. 安装

conda create --name l8 python=3.9
conda install -n l8 ipykernel --update-deps --force-reinstall
conda install -n l8 pytorch torchvision torchaudio -c pytorch-nightly
conda install -n l8 -c conda-forge wandb
conda install -c conda-forge hydra-core

或者用pip install

1. 实验记录

大家是不是都曾遇到过这样的情况:如果没有良好的实验记录工具,我们最终也许会得到一个性能非常好的模型,但我们不记得其超参数选择,或者启动 100 个实验却无法轻松跟踪哪个模型表现最好,而实验跟踪工具能帮助我们解决这些问题。

Logging

通常来说我们在训练的过程中,通常会打印我们正在使用的超参数,以及模型训练时的损失+准确性。

import randomdef run_training_run_txt_log(epochs, lr):print(f"Training for {epochs} epochs with learning rate {lr}")offset = random.random() / 5for epoch in range(2, epochs):# 模拟训练过程acc = 1 - 2 ** -epoch - random.random() / epoch - offsetloss = 2 ** -epoch + random.random() / epoch + offsetprint(f"epoch={epoch}, acc={acc}, loss={loss}")# 进行一次学习率为0.1的训练运行
run_training_run_txt_log(epochs=10, lr=0.01)

下面展示我们如何用Weights & Biases管理实验记录

Weights and Biases

Weights & Biases 是:

“开发者构建更好模型、更快开发的机器学习平台。使用 W&B 的轻量级、可互操作的工具,可以快速追踪实验、版本化和迭代数据集、评估模型性能、复现模型、可视化结果并发现回归问题,并与同事分享发现。

你使用哪种实验追踪工具不是一个标准答案:有人喜欢 Weights and Biases,简称 wandb:你可以按其最初的意图读作 w-and-b,或者读作 wan-db(因为它像数据库一样保存东西)。替代选择包括 Tensorboard、Neptune 和 Tensorboard。”

让我们开始使用wandb吧!

系统可能会提示您创建账户,然后添加您的token。

# Log in to your W&B account
import wandb
wandb.login()

我们现在将在上面提供的函数进行修改,展示如何使用wandb。

import randomdef run_training_run(epochs, lr):print(f"Training for {epochs} epochs with learning rate {lr}")wandb.init(# Set the project where this run will be loggedproject="example", # Track hyperparameters and run metadataconfig={"learning_rate": lr,"epochs": epochs,})offset = random.random() / 5print(f"lr: {lr}")for epoch in range(2, epochs):# simulating a training runacc = 1 - 2 ** -epoch - random.random() / epoch - offsetloss = 2 ** -epoch + random.random() / epoch + offsetprint(f"epoch={epoch}, acc={acc}, loss={loss}")wandb.log({"acc": acc, "loss": loss})wandb.finish()run_training_run(epochs=10, lr=0.01)

我们在这里使用 3 个函数:wandb.init、wandb.log 和 wandb.finish——它们各自的作用是什么?

  • 我们在脚本开头调用一次 wandb.init() 来初始化新项目。这会在 W&B 中创建新的运行并启动后台进程来同步数据。
  • 我们调用 wandb.log(dict) 将指标、媒体或自定义对象的字典记录到步骤中。我们可以看到我们的模型和数据如何随着时间的推移而演变。
  • 我们调用wandb.finish来使运行完成,并完成所有数据的上传。

让我们看看在 wandb 网站上看到了什么,应该看到我们的准确性和损失曲线。
在这里插入图片描述

在我们的信息选项卡中,我们还应该能够看到配置和摘要,告诉我们 acc 和 loss 的最终值。
在这里插入图片描述

我们已经获得了两个不错的功能:

  1. 能够看到循环每一步的准确性和损失如何变化。
  2. 能够看到与运行相关的配置(超参数)。
  3. 能够看到我们的运行最终获得的准确率acc和loss损失。
多次实验

我们现在要增加一些复杂性。当我们通常训练模型时,我们会尝试不同的超参数。我们将调整的最重要的超参数之一是学习率,另一个可能是训练的轮数(epochs)。那么我们如何记录多个运行呢?

def run_multiple_training_runs(epochs, lrs):for epoch in epochs:for lr in lrs:run_training_run(epoch, lr)# Try different values for the learning rate
epochs = [100, 120, 140]
lrs = [0.1, 0.01, 0.001, 0.0001]
run_multiple_training_runs(epochs, lrs)

正如你所看到的,这使用了我们上面已经写好的函数,以不同的学习率和epoch多次调用它。让我们看看我们得到了什么。

我们可以访问 wandb 的网站并进入表格选项卡。
在这里插入图片描述

在左边我们可以看到每一次的实验,点击进去,可以看到每一组的实验参数配置,比如我们选择第一组,happy-water-13,可以看到下面这个界面:在这里插入图片描述

模型保存和加载

我们经过不懈的努力终于获得了我们期望的结果!现在我们决定要使用其中一个训练好的模型。我们可以在 W&B 上查找该运行的配置,然后重新训练模型并保存它!但是,如果我们在运行时就保存了与其关联的模型,那该多好,这样我们就可以直接加载它,对吗?

那么,我们应该如何实现这一点呢?我们可以使用 Weights & Biases 的 Artifacts 功能来跟踪数据集、模型、依赖项和结果,贯穿于整个机器学习流程的每一步。Artifacts 可以轻松获得文件更改的完整且可审核的历史记录。根据文档:

Artifacts 可以被视为一个版本化的目录。Artifacts 要么是运行的输入,要么是运行的输出。常见的 artifacts 包括整个训练集和模型。可以将数据集直接存储到 artifacts 中,或者使用 artifact 引用指向其他系统中的数据,如 Amazon、或你自己的系统。

使用 4 行简单的代码来记录 wandb Artifacts非常容易:

wandb.init()
artifact = wandb.Artifact(<enter_filename>, type='model')
artifact.add_file(<file_path>)
wandb.run.log_artifact(artifact)

如果我们有一行用于保存 PyTorch 模型的代码:

model_path = f"model_{epoch}.pt"
torch.save(model.state_dict(), model_path)

我们可以修改它以将artifacts上传到 wandb 上。

# Log the model to wandb
model_path = f"model_{epoch}.pt"
torch.save(model.state_dict(), model_path)
artifact = wandb.Artifact(model_path, type='model')
artifact.add_file(model_path)
wandb.run.log_artifact(artifact)

现在我们可以看到我们的模型checkpoint保存在 W&B 中:
在这里插入图片描述

我们还可以看到相关的元数据:
在这里插入图片描述

练习

撰写代码,以便您可以在训练时保存前 3 个最佳模型。提示:参见[这里](How to save all your trained model weights locally after every epoch.ipynb)。

如果我们有保存的模型,我们现在可以加载该模型。假设我们原来的加载过程是从本地保存的checkpoint加载:

model.load_state_dict(torch.load("model_9.pt"))

我们现在可以使用

run = wandb.init()
artifact = run.use_artifact('YOUR_PATH/model_9.pt:v1', type='model')
artifact_dir = artifact.download()
model.load_state_dict(torch.load(artifact_dir + "/model_9.pt"))

2. 超参数搜索

当我们有多种超参数选择时,我们想要都尝试一下,这意味着使用不同的超参数值运行模型。

搜索选项

我们可以决定如何采样超参数的值,包括贝叶斯优化、网格搜索和随机搜索。 在网格搜索中,我们为每个超参数定义一组可能的值,然后搜索会为每个可能的超参数值组合训练一个模型。 例如:使用 epochs = [100, 120, 140] 和 lrs = [0.1, 0.01, 0.001, 0.0001],我们的网格将是 list(itertools.product(epochs, lrs)),即 [(100, 0.1), (100, 0.01), (100, 0.001), (100, 0.0001), (120, 0.1), (120, 0.01), (120, 0.001), (120, 0.0001), (140, 0.1), (140, 0.01), (140, 0.001), (140, 0.0001)]。 在随机搜索中,我们为每个超参数提供一个统计分布,从中采样值。在这里,我们通常会控制或限制使用的超参数组合数量。 在贝叶斯优化中,使用先前迭代的结果来决定下一组超参数值,采用一种序列模型优化(SMBO)算法。

这种方法在参数数量增加时不太可扩展。

Weights & Biases 超参数优化 (Sweeps)

正如文档所述: “Weights & Biases 超参数优化有两个组件:控制器和一个或多个代理。控制器选择新的超参数组合。通常,控制器由 Weights & Biases 服务器管理。代理向 Weights & Biases 服务器查询超参数,并使用这些超参数进行模型训练。然后将训练结果报告给控制器。代理可以在一台或多台机器上运行一个或多个进程。”

一旦我们有了 Weights & Biases 的训练代码,添加超参数优化只需三步:

  1. 定义超参数优化配置
  2. 初始化超参数优化(控制器)
  3. 启动超参数优化代理

让我们看看它的实际操作。假设我们有以下代码:

import wandb
def my_train_func():# read the current value of parameter "a" from wandb.configwandb.init()a = wandb.config.awandb.log({"a": a, "accuracy": a + 1})
sweep_configuration = {"name": "my-awesome-sweep","metric": {"name": "accuracy", "goal": "maximize"},"method": "grid","parameters": {"a": {"values": [1, 2, 3, 4]}}
}

注意事项:

  • 使用的是网格搜索
  • 我们指定了要优化的指标——这仅被某些搜索策略和停止标准使用。请注意,我们必须在 Python 脚本中将变量 accuracy(在此示例中)记录到 W&B 中,这一点我们已经完成。
  • 我们为 “a” 指定了值。

步骤 2:初始化超参数优化

在这一步中,我们启动上述的超参数优化控制器:

sweep_id = wandb.sweep(sweep=sweep_configuration, project='my-first-sweep')

步骤 3:启动超参数优化代理

最后,我们启动代理,提供超参数优化 ID、要调用的函数以及(可选)要运行的次数(count)。

wandb.agent(sweep_id, function=my_train_func, count=4)

把以上步骤放到一起

import wandb
sweep_configuration = {"name": "my-awesome-sweep","metric": {"name": "accuracy", "goal": "maximize"},"method": "grid","parameters": {"a": {"values": [1, 2, 3, 4]}}
}def my_train_func():# read the current value of parameter "a" from wandb.configwandb.init()a = wandb.config.awandb.log({"a": a, "accuracy": a + 1})sweep_id = wandb.sweep(sweep_configuration)# run the sweep
wandb.agent(sweep_id, function=my_train_func)
课堂练习

修改以下代码,以便你可以在其上运行超参数优化(sweep)。选择 val_loss 作为你要优化的指标。为 batch_size、epochs 和 learning rate 选择合理的选项。 现在,对于 learning rate,使用一个分布,该分布在 exp(min) 和 exp(max) 之间进行采样,使得自然对数在 min 和 max 之间均匀分布。

将你的解决方案与此处的解决方案进行比较。

import numpy as np 
import randomdef train_one_epoch(epoch, lr, bs): acc = 0.25 + ((epoch/30) +  (random.random()/10))loss = 0.2 + (1 - ((epoch-1)/10 +  random.random()/5))return acc, lossdef evaluate_one_epoch(epoch): acc = 0.1 + ((epoch/20) +  (random.random()/10))loss = 0.25 + (1 - ((epoch-1)/10 +  random.random()/6))return acc, lossdef main():run = wandb.init(project='my-first-sweep')# this is key: we define values from `wandb.config` instead of # defining hard valueslr  =  wandb.config.lrbs = wandb.config.batch_sizeepochs = wandb.config.epochsfor epoch in np.arange(1, epochs):train_acc, train_loss = train_one_epoch(epoch, lr, bs)val_acc, val_loss = evaluate_one_epoch(epoch)wandb.log({'epoch': epoch, 'train_acc': train_acc,'train_loss': train_loss, 'val_acc': val_acc, 'val_loss': val_loss})

3. 使用Hydra进行配置

我们不希望用硬编码的路径名、模型名和超参数来训练深度学习模型。我们希望能够使用一个配置文件,根据使用的数据集、模型或配置进行修改。硬编码是什么?是指在编写程序时,直接将具体的值(如字符串、数字、路径等)写入源代码中,而不是通过变量、配置文件、数据库查询或其他动态方法来获取这些值。(这其实不是一个好习惯,但是经常有人这样做)

错误的方法

首先,让我们从一些错误的配置深度学习运行的方法开始。假设我们想从命令行控制数据集的 batch_size。可能在某台机器上工作时,你可以使用较大的 batch_size,而在另一台机器上则不行。最基本的做法是记住更改硬编码的 batch size。

batch_size = 128
# batch_size = 4

像上面那种方法并不是一个好的选择,因为每次都要去更改源码。

第二种解决方案是在运行脚本时将batch_size的值作为参数传递进去。这样我们就可以根据所用的显卡来改变它。我们可以通过sys.argv使用命令行参数来实现这一点。

main.py

import sys
batch_size = sys.argv[1]

如果我们希望batch_size 设置成16,我们可以这样调用:python main.py 16。如果我们需要配置多个设置,直接使用sys.argv工作可能就不那么用户友好,这时我们可能希望使用一个解析器。其中最流行的是argparse模块:

import argparseparser = argparse.ArgumentParser()
parser.add_argument('batch_size', metavar='B', type=int,help='batch_size for the model')args = parser.parse_args()
print(args.batch_size)

练习:让脚本接收批处理大小(batch_size)、学习率(learning_rate)和丢弃率(dropout)作为参数,并为每个参数使用合适的类型。如果未提供这些参数,除了学习率(learning_rate)外,其他都应使用默认值,学习率是必须提供的。

这样操作在当前情境下或许可行,但是一旦我们有上百个参数时,显式地为每个希望不同于默认值的参数指定值就会变得非常困难!要是能有一种方式将这些配置存储在一个文件中就好了。

Hydra

Hydra是一个开源的Python框架,它简化了研究和其他复杂应用程序的开发。Hydra这个名字来源于其能够运行多个类似任务的能力——就像一个多头的九头蛇一样。

我们将遵循Hydra的教程,但会加入一些我自己的理解和调整。

from omegaconf import DictConfig, OmegaConf
import hydra@hydra.main(version_base=None)
def run(cfg: DictConfig) -> None:print(OmegaConf.to_yaml(cfg)) # {}if __name__ == "__main__":run()

在这个示例中,Hydra创建了一个空的配置(cfg)对象,并将其传递给hydra.main装饰器。

提示: “OmegaConf是一个基于YAML的分层配置系统,支持从多种来源(文件、CLI参数、环境变量)合并配置,无论配置是如何创建的,都能提供一致的API。” “装饰器是Python中一个重要的部分。简单来说:它们是修改其他函数功能的函数。它们有助于使我们的代码更简洁、更符合Python风格。大多数初学者不知道在哪里使用它们,所以我将分享一些装饰器可以使你的代码更精简的场景。”

我们可以通过命令行使用“+”来添加新的配置值。

# should return “batch_size: 16”
python run.py +batch_size=16

这里就是Hydra开始起作用的地方。由于在命令行中输入参数十分繁琐,我们可以开始使用配置文件。Hydra的配置文件是YAML文件,并且应该具有.yaml的文件扩展名。

我们在与run.py相同的目录下创建一个config.yaml文件,并用我们的配置信息填充它。

config.yaml

batch_size: 16

现在,我们需要告诉Hydra在哪里找到这个配置文件。请注意,config_name应当与我们的文件名匹配,并且config_path是相对于应用程序的相对路径。

@hydra.main(version_base=None, config_path=".", config_name="config")

我们现在可以使用命令 python run.py 来运行 run.py,并且应该能看到打印出的 batch_size。这里的一个很酷的功能是,我们可以通过命令行来覆盖配置文件中的值(这次,我们可以省略“+”,因为配置值并不是新的:

python run.py batch_size=32 # should print 32

让我们开始让我们的配置变得更加有用:

loss: cross_entropy
batch_size: 64
num_workers: 4
name: ??? # Missing value, must be populated prior to accessoptim: # Config is hierarchicalname: adamlr: 0.0001weight_decay: ${optim.lr} # Value interpolationmomentum: 0.9

这里有一些新内容: 我们正在使用层次结构(例如 cfg.optim.name) 我们正在进行值的插值(例如 cfg.optim.weight_decay) 我们指定了一个必须填充的缺失值

让我们看看它是如何工作的:

from omegaconf import DictConfig, OmegaConf
import hydra@hydra.main(version_base=None, config_path=".", config_name="config")
def run(cfg: DictConfig):assert cfg.optim.name == 'adam'          # attribute style accessassert cfg["optim"]["lr"] == 0.0001      # dictionary style accessassert cfg.optim.weight_decay == 0.0001  # Value interpolationassert isinstance(cfg.optim.weight_decay, float) # Value interpolation typeprint(cfg.name)                       # raises an exceptionif __name__ == "__main__":run()

我们应该会遇到 “omegaconf.errors.MissingMandatoryValue: Missing mandatory value: name” 这个错误。我们可以通过在运行程序时指定一个名称来解决这个问题。

python run.py name=exp1 # Should print ‘exp1’

现在我们来增加一点复杂性。假设我们想要创建一个优化器类。

class Optimizer:"""Optimizer class."""algo: strlr: floatdef __init__(self, algo: str, lr: float) -> None:self.algo = algoself.lr = lrdef __str__(self):return str(self.__class__) + ": " + str(self.__dict__)

现在我们可以使用当前的配置实例化这个优化器类了。

@hydra.main(version_base=None, config_path=".", config_name="config")
def run(cfg: DictConfig):opt = Optimizer(cfg.optim.name, cfg.optim.lr)print(str(opt))

我们应当看到 <class '__main__.Optimizer'>: {'algo': 'adam', 'lr': 0.0001}

我们能否直接通过Hydra实例化优化器呢?Hydra提供了hydra.utils.instantiate()(及其别名hydra.utils.call())用于实例化对象和调用函数。建议在创建对象时使用instantiate,在调用函数时使用call

我们可以使用一个简单的配置:

config2.yml

optimizer:_target_: run.Optimizeralgo: SGDlr: 0.01

我们可以这样进行实例化:

from hydra.utils import instantiate
@hydra.main(version_base=None, config_path=".", config_name="config2")
def run(cfg: DictConfig):opt = instantiate(cfg.optimizer)print(opt)

来自官方教程的专业提示: call/instantiate支持以下功能: 命名参数:配置字段(除了像_target_这样的保留字段)作为命名参数传递给目标。配置中的命名参数可以通过在instantiate()调用站点传递同名的命名参数来覆盖。 位置参数:配置中可以包含一个_args_字段,表示要传递给目标的位置参数。位置参数可以通过在instantiate()调用时传递位置参数一起来覆盖。

我们甚至可以进行递归实例化。

config3.yaml

trainer:_target_: run.Traineroptimizer:_target_: run.Optimizeralgo: SGDlr: 0.01dataset:_target_: run.Datasetname: Imagenetpath: /datasets/imagenet

以下代码可以在实例化我们的Trainer的同时,也实例化我们的DatasetOptimizer

from omegaconf import DictConfig, OmegaConf
import hydra
from hydra.utils import instantiateclass Dataset:name: strpath: strdef __init__(self, name: str, path: str) -> None:self.name = nameself.path = pathclass Optimizer:"""Optimizer class."""algo: strlr: floatdef __init__(self, algo: str, lr: float) -> None:self.algo = algoself.lr = lrdef __str__(self):return str(self.__class__) + ": " + str(self.__dict__)class Trainer:def __init__(self, optimizer: Optimizer, dataset: Dataset) -> None:self.optimizer = optimizerself.dataset = dataset@hydra.main(version_base=None, config_path=".", config_name="config3")
def run(cfg: DictConfig):opt = instantiate(cfg.trainer)print(opt)

练习

展示你将用来实例化一个包含两个线性层的torch.nn.Sequential对象的config.yaml文件和train.py文件。

针对yaml文件的解决方案,也可以直接看下面:

_target_: torch.nn.Sequential
_args_:- _target_: torch.nn.Linearin_features: 9216out_features: 100- _target_: torch.nn.Linearin_features: ${..[0].out_features}out_features: 10

相较于单一的配置文件,我们常常需要多个配置文件。在机器学习中,这些文件用于指定不同的数据集、模型或日志行为等我们可能想要使用的设置。因此,我们通常会使用“配置组”(Config Group),它为每种数据集、模型配置选项或日志行为等持有一个文件。

一个机器学习应用的配置组可能看起来像这样:

configs/
├── dataset
│   ├── cifar10.yaml
│   └── mnist.yaml
├── defaults.yaml
├── hydra
│   ├── defaults.yaml
│   └── with_ray.yaml
├── model
│   ├── small.yaml
│   └── large.yaml
├── normalization
│   ├── batch.yaml
│   ├── default.yaml
│   ├── group.yaml
│   ├── instance.yaml
│   └── nonorm.yaml
├── train
│   └── defaults.yaml
└── wandb└── defaults.yaml

请通读配置组文档和默认值文档,以便理解配置组和默认值的概念。

总体而言,我们会执行以下操作:

  1. 创建一个目录,有时称为confs/configs/,用于存放所有配置文件。
  2. 我们可以指定要使用的配置文件。例如,如果我们想在数据集中使用cifar10.yaml,我们将使用命令python run.py dataset=cifar10

这意味着通过命令行参数,我们可以灵活地选择不同的配置文件来适应不同的需求,比如切换数据集、模型或调整日志行为等,而无需直接修改主代码文件。配置组允许我们组织和管理这些配置文件,使其更加有序和易于维护。

cifar10.yaml

---
name: cifar10
dir: cifar10/
train_batch: 32
test_batch: 10
image_dim:- 32- 32- 3
num_classes: 10
  1. defaults.yaml文件用于指定默认使用的数据集或模型。

defaults.yaml

---
defaults:- dataset: mnist- model: ${dataset}- train: defaults- wandb: defaults- hydra: defaults- normalization: default
model:num_groups: -1

练习:

配置一个小型模型和一个大型模型。大型模型实例化一个torch.nn.Sequential对象,包含三个线性层;小型模型则包含两个线性层,将小型模型设为默认模型。

小贴士:Hydra与W&B的集成:今天我们已经了解了两个工具,W&B和Hydra。如何让这两个工具协同工作呢?这里有一些使用模式需要了解。

请参考这个教程,查看如何将两者结合使用的示例代码。

参考资料

Bohrium (dp.tech)

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

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

相关文章

支持AI的好用的编辑器aieditor

一、工具概述 AiEditor 是一个面向 AI 的下一代富文本编辑器&#xff0c;她基于 Web Component&#xff0c;因此支持 Layui、Vue、React、Angular 等几乎任何前端框架。她适配了 PC Web 端和手机端&#xff0c;并提供了 亮色 和 暗色 两个主题。除此之外&#xff0c;她还提供了…

【源码+文档+调试讲解】乡镇篮球队管理系统设计与实现

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本乡镇篮球队管理系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信…

基础篇| 全网最全详解12个大模型推理框架

01 什么是框架? 开始介绍之前, 我们先了解一下什么是框架?xx框架-IT人经常听到的名词。但是又有多少人知道框架的意思? 框架&#xff08;framework&#xff09;是一个框子:指其约束性&#xff0c;也是一个架子——指其支撑性。是一个基本概念上的结构&#xff0c;用于去解…

新作品,一个通用的 Cloudflare Workers HTTP 反向代理

本文介绍我最近写的新作品&#xff1a;使用 Cloudflare Workers/Pages 搭建 HTTP 反向代理&#xff0c;代码已经全部开源在 GitHub&#xff0c;按照 README 里面的脚本搭建就可以了&#xff0c;非常简单。 GitHub&#xff1a;https://github.com/jonssonyan/cf-workers-proxy …

数字图像处理 第三章 灰度变换和空间滤波(上)

文章目录 本章简介一、背景知识 P62 - P641.1 灰度变换和空间滤波基础 P62 - P63二、一些基本的灰度变换函数 P64 - P712.1 图像反转 P642.2 对数变换 P64 - P662.3 幂律(伽马变换 P66 - P682.4 分段线性变换函数 P68 - P71本章知识点总结本章简介 本章讨论在空间域中的图像增强…

【C/C++】关于 extern “C“ 的理解

详细解释 #ifdef __cplusplus extern "C" 在C中&#xff0c;#ifdef __cplusplus 和 extern "C" 是用于处理C和C混合编程中的名称修饰&#xff08;name mangling&#xff09;问题的预处理器指令和关键字。 #ifdef __cplusplus __cplusplus 是一个预处理器…

人工智能大模型发展带来的风险挑战和对策

经过近70年的发展&#xff0c;人工智能技术发展经历了三次起伏&#xff0c;2022年以来&#xff0c;以ChatGPT、Sora等为代表的预训练大模型持续取得突破&#xff0c;推动着人工智能技术从感知向认识&#xff0c;从分析判断式向生成式&#xff0c;从专用向通用进入快速发展的新阶…

PythonDjangoMysql外卖app系统32762-计算机毕业设计项目选题推荐(附源码)

摘 要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;餐饮外卖当然也不例外。 外卖app系统主要功能模块包括后台首页&#xff0c;轮播图&#xff0c;资源管理&#xff08;餐饮…

【CTFWP】ctfshow-web40

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 题目介绍&#xff1a;题目分析&#xff1a;payload&#xff1a;payload解释&#xff1a;payload2&#xff1a;payload2解释&#xff1a;flag 题目介绍&#xff1a; …

高等数学 第七讲 一元函数积分学的概念和性质_不定积分_定积分_变限积分_反常积分

1.不定积分 文章目录 1.不定积分1.1 原函数1.1.1 原函数与不定积分的定义1.1.2 原函数存在定理 2.定积分2.1 定积分的定义2.2 定积分的精确定义2.3 定积分的几何意义2.4 定积分的存在定理2.5 定积分的性质 3.变限积分3.1 变限积分的定理3.2 变限积分的性质 4.反常积分(待更新) …

红酒标签设计:艺术与品味的结合

在红酒的世界里&#xff0c;每一瓶酒都如同一位优雅的舞者&#xff0c;在酒柜的舞台上静静诉说着自己的故事。而红酒的标签&#xff0c;则是这位舞者身上较华丽的舞裙&#xff0c;它不仅是红酒的身份证明&#xff0c;更是艺术与品味的很好结合。今天&#xff0c;我们就来聊聊红…

重载云台摄像机如何通过国标28181接入到统一视频接入平台(视频国标接入平台)

目录 一、国标GB/T 28181介绍 1、国标GB/T28181 2、内容和特点 二、重载云台摄像机 1、定义 2、结构与设计 3、功能和优势 4、特点 5、应用场景 二、接入准备工作 1、确定网络环境 &#xff08;1&#xff09;公网接入 &#xff08;2&#xff09;专网传输 2、检查重…

STC单片机UART映射printf

文章目录 使用STC-ISP生成UART初始化函数 增加如下函数&#xff0c;注意使用printf函数需要添加 #include <stdio.h> 头文件 #include <stdio.h>void Uart1_Init(void) //9600bps12.000MHz {SCON 0x50; //8位数据,可变波特率AUXR | 0x01; //串口1选择定时器2为…

Vue2从基础到实战(v-bind对于样式控制的增强-操作style,v-model在其他表单元素的使用)

v-bind对于样式控制的增强-操作style 语法&#xff1a;style"样式对象" <div class"box" :style"{ CSS属性名1: CSS属性值, CSS属性名2: CSS属性值 }"></div> 代码解析&#xff1a; HTML结构&#xff1a; 包含了一个div元素&…

OSI七层网络模型:构建网络通信的基石

在计算机网络领域&#xff0c;OSI&#xff08;Open Systems Interconnection&#xff09;七层模型是理解网络通信过程的关键框架。该模型将网络通信过程细分为七个层次&#xff0c;每一层都有其特定的功能和职责&#xff0c;共同协作完成数据从发送端到接收端的传输。接下来&am…

申请美区 Apple ID 完整步骤图解,轻松免费创建账户

苹果手机在下载一些软件时需要我们登录其 Apple ID 才能下载&#xff0c;但是由于一些限制国内的 Apple ID 在 App Store 中有一些限制不能下载某些软件&#xff0c;如何解决这个问题&#xff1f;那就是申请一个美区 Apple ID&#xff0c;怎么申请国外苹果账户呢&#xff1f;下…

WebLogic: CVE-2020-14882/14883【getshell】

记录第一次getshell公网设备 漏洞介绍 CVE-2020-14882&#xff1a;允许 未授权 的用户绕过管理控制台 &#xff08;Console&#xff09;的权限验证访问后台 CVE-2020-14883&#xff1a;允许后台任意用户通过HTTP协议 执行任意命令 使用这两个漏洞组成的利用链&#xff0c;可通过…

Java----代理

什么是代理&#xff1f; 在Java中&#xff0c;代理是一种用于创建一个或多个服务的中间层&#xff0c;它可以拦截并处理程序对实际服务对象的请求。代理模式是一种设计模式&#xff0c;属于结构型模式&#xff0c;它允许程序员在不修改实际对象代码的情况下&#xff0c;增强或控…

【C语言】C语言期末突击/考研--数据的类型

目录 一、编程环境的搭建 二、数据的类型、数据的输入输出 2.1.数据类型 2.2.常量 2.3.变量 2.4.整型数据 2.4.1.符号常量 2.4.2.整型变量 2.5.浮点型数据 2.5.1.浮点型常量 2.5.2.浮点型变量 2.6.字符型数据 2.6.1字符型常量 2.6.2.字符数据在内存中的存储形式及…