深入浅出PyTorc——进阶训练技巧

news2024/11/16 21:53:38

1. 自定义损失函数

1.1 以函数方式定义

        手动写出损失的公式并用函数进行存储,方便调用。

def my_loss(output, target):
    loss = torch.mean((output - target)**2)
    return loss

1.2 以类方式定义

 1.2.1 损失函数的继承关系

        (1)Loss函数部分继承_loss,部分继承_WeightedLoss

        (2)_weightedLoss继承自_loss

        (3)_loss继承自nn.Module

1.2.2 实例

        (1)损失函数:Dice Loss

        (2)公式:DSC=\frac{2\left | X\cap Y \right |}{\left | X \right | + \left | Y \right |}

        (3)实现代码:

class DiceLoss(nn.Module):
    def __init__(self,weight=None,size_average=True):
        super(DiceLoss,self).__init__()
        
    def forward(self,inputs,targets,smooth=1):
        inputs = F.sigmoid(inputs)       
        inputs = inputs.view(-1)
        targets = targets.view(-1)
        intersection = (inputs * targets).sum()                   
        dice = (2.*intersection + smooth)/(inputs.sum() + targets.sum() + smooth)  
        return 1 - dice

# 使用方法    
criterion = DiceLoss()
loss = criterion(input,targets)

        (4)常见的损失函数DiceBCELoss、IoULoss、FocalLoss

class DiceBCELoss(nn.Module):
    def __init__(self, weight=None, size_average=True):
        super(DiceBCELoss, self).__init__()

    def forward(self, inputs, targets, smooth=1):
        inputs = F.sigmoid(inputs)       
        inputs = inputs.view(-1)
        targets = targets.view(-1)
        intersection = (inputs * targets).sum()                     
        dice_loss = 1 - (2.*intersection + smooth)/(inputs.sum() + targets.sum() + smooth)  
        BCE = F.binary_cross_entropy(inputs, targets, reduction='mean')
        Dice_BCE = BCE + dice_loss
        
        return Dice_BCE
--------------------------------------------------------------------
    
class IoULoss(nn.Module):
    def __init__(self, weight=None, size_average=True):
        super(IoULoss, self).__init__()

    def forward(self, inputs, targets, smooth=1):
        inputs = F.sigmoid(inputs)       
        inputs = inputs.view(-1)
        targets = targets.view(-1)
        intersection = (inputs * targets).sum()
        total = (inputs + targets).sum()
        union = total - intersection 
        
        IoU = (intersection + smooth)/(union + smooth)
                
        return 1 - IoU
--------------------------------------------------------------------
    
ALPHA = 0.8
GAMMA = 2

class FocalLoss(nn.Module):
    def __init__(self, weight=None, size_average=True):
        super(FocalLoss, self).__init__()

    def forward(self, inputs, targets, alpha=ALPHA, gamma=GAMMA, smooth=1):
        inputs = F.sigmoid(inputs)       
        inputs = inputs.view(-1)
        targets = targets.view(-1)
        BCE = F.binary_cross_entropy(inputs, targets, reduction='mean')
        BCE_EXP = torch.exp(-BCE)
        focal_loss = alpha * (1-BCE_EXP)**gamma * BCE
                       
        return focal_loss

注意: 最好全程使用PyTorch提供的张量计算接口,这样就不需要我们实现自动求导功能并且我们可以直接调用cuda

2. 动态调整学习率

        学习率的设置难处:

        (1)学习速率设置过小,会极大降低收敛速度,增加训练时间;

        (2)学习率太大,可能导致参数在最优解两侧来回振荡;

        (3)选定了一个合适的学习率后,经过许多轮的训练后,可能会出现准确率震荡或loss不再下降等情况,说明当前学习率已不能满足模型调优的需求。

2.1 使用官方scheduler

2.1.1 官方提供的API

  • lr_scheduler.LambdaLR

  • lr_scheduler.MultiplicativeLR

  • lr_scheduler.StepLR

  • lr_scheduler.MultiStepLR

  • lr_scheduler.ExponentialLR

  • lr_scheduler.CosineAnnealingLR

  • lr_scheduler.ReduceLROnPlateau

  • lr_scheduler.CyclicLR

  • lr_scheduler.OneCycleLR

  • lr_scheduler.CosineAnnealingWarmRestarts

2.1.2 使用方法

# 选择一种优化器
optimizer = torch.optim.Adam(...) 
# 选择上面提到的一种或多种动态调整学习率的方法
scheduler1 = torch.optim.lr_scheduler.... 
scheduler2 = torch.optim.lr_scheduler....
...
schedulern = torch.optim.lr_scheduler....
# 进行训练
for epoch in range(100):
    train(...)
    validate(...)
    optimizer.step()
    # 需要在优化器参数更新之后再动态调整学习率
	scheduler1.step() 
	...
    schedulern.step()

        注意 : 使用官方给出的torch.optim.lr_scheduler时,需要将scheduler.step()放在optimizer.step()后面进行使用。

2.2 自定义scheduler

2.2.1 自定义函数

        (1)方法:自定义函数adjust_learning_rate来改变param_grouplr的值。

        (2)实现代码:

def adjust_learning_rate(optimizer, epoch):
    lr = args.lr * (0.1 ** (epoch // 30))
    for param_group in optimizer.param_groups:
        param_group['lr'] = lr

2.2.2 具体调用方法

        直接调用我们已经定义好的adjust_learning_rate函数

def adjust_learning_rate(optimizer,...):
    ...
optimizer = torch.optim.SGD(model.parameters(),lr = args.lr,momentum = 0.9)
for epoch in range(10):
    train(...)
    validate(...)
    adjust_learning_rate(optimizer,epoch)

3. 模型微调

        (1)数据量有限,最终训练得到的模型的精度也可能达不到实用的要求。

        (2)模型微调(finetune):就是我们先找到一个同类的别人训练好的模型,把别人现成的训练好了的模型拿过来,换成自己的数据,通过训练调整一下参数。

3.1 torchvision

3.1.1 模型微调的流程

3.1.2 使用已有模型结构 

        以torchvision中的常见模型为例

        (1)实例化网络

import torchvision.models as models
resnet18 = models.resnet18()
# resnet18 = models.resnet18(pretrained=False)  等价于与上面的表达式
alexnet = models.alexnet()
vgg16 = models.vgg16()
squeezenet = models.squeezenet1_0()
densenet = models.densenet161()
inception = models.inception_v3()
googlenet = models.googlenet()
shufflenet = models.shufflenet_v2_x1_0()
mobilenet_v2 = models.mobilenet_v2()
mobilenet_v3_large = models.mobilenet_v3_large()
mobilenet_v3_small = models.mobilenet_v3_small()
resnext50_32x4d = models.resnext50_32x4d()
wide_resnet50_2 = models.wide_resnet50_2()
mnasnet = models.mnasnet1_0()

        (2)传递pretrained参数:通过True或者False来决定是否使用预训练好的权重,默认状态下pretrained = False

import torchvision.models as models
resnet18 = models.resnet18(pretrained=True)
alexnet = models.alexnet(pretrained=True)
squeezenet = models.squeezenet1_0(pretrained=True)
vgg16 = models.vgg16(pretrained=True)
densenet = models.densenet161(pretrained=True)
inception = models.inception_v3(pretrained=True)
googlenet = models.googlenet(pretrained=True)
shufflenet = models.shufflenet_v2_x1_0(pretrained=True)
mobilenet_v2 = models.mobilenet_v2(pretrained=True)
mobilenet_v3_large = models.mobilenet_v3_large(pretrained=True)
mobilenet_v3_small = models.mobilenet_v3_small(pretrained=True)
resnext50_32x4d = models.resnext50_32x4d(pretrained=True)
wide_resnet50_2 = models.wide_resnet50_2(pretrained=True)
mnasnet = models.mnasnet1_0(pretrained=True)

3.1.3 训练特定层

        requires_grad = False来冻结部分层

import torchvision.models as models
# 冻结参数的梯度
feature_extract = True
model = models.resnet18(pretrained=True)
set_parameter_requires_grad(model, feature_extract)
# 修改模型
num_ftrs = model.fc.in_features
model.fc = nn.Linear(in_features=num_ftrs, out_features=4, bias=True)

3.2 timm

        提供了许多计算机视觉的SOTA模型,可以当作是torchvision的扩充版本,并且里面的模型在准确度上也较高。

3.2.1 timm的安装

        (1)通过pip安装

pip install timm

        (2)通过git与pip进行安装

git clone https://github.com/rwightman/pytorch-image-models
cd pytorch-image-models && pip install -e .

3.2.2 查看timm中预训练模型种类

        (1)查看数量

import timm
avail_pretrained_models = timm.list_models(pretrained=True)
len(avail_pretrained_models)

        (2) 查看特定模型种类timm.list_models()

all_densnet_models = timm.list_models("*densenet*")
all_densnet_models



['densenet121',
 'densenet121d',
 'densenet161',
 'densenet169',
 'densenet201',
 'densenet264',
 'densenet264d_iabn',
 'densenetblur121d',
 'tv_densenet121']

        (3)查看模型参数default_cfg

model = timm.create_model('resnet34',num_classes=10,pretrained=True)
model.default_cfg


{'url': 'https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/resnet34-43635321.pth',
 'num_classes': 1000,
 'input_size': (3, 224, 224),
 'pool_size': (7, 7),
 'crop_pct': 0.875,
 'interpolation': 'bilinear',
 'mean': (0.485, 0.456, 0.406),
 'std': (0.229, 0.224, 0.225),
 'first_conv': 'conv1',
 'classifier': 'fc',
 'architecture': 'resnet34'}

3.2.3 使用和修改timm中预训练模型

        通过timm.create_model()的方法来进行模型的创建,我们可以通过传入参数pretrained=True,来使用预训练模型

import timm
import torch

# 初始化模型参数
model = timm.create_model('resnet34',pretrained=True)
x = torch.randn(1,3,224,224)
output = model(x)
output.shape


# 查看某一层模型参数(以第一层卷积为例)
model = timm.create_model('resnet34',pretrained=True)
list(dict(model.named_children())['conv1'].parameters())


# 修改模型(将1000类改为10类输出)
model = timm.create_model('resnet34',num_classes=10,pretrained=True)
x = torch.randn(1,3,224,224)
output = model(x)
output.shape


# 改变输入通道数(比如我们传入的图片是单通道的,但是模型需要的是三通道图片) 我们可以通过添加in_chans=1来改变
model = timm.create_model('resnet34',num_classes=10,pretrained=True,in_chans=1)
x = torch.randn(1,1,224,224)
output = model(x)

3.2.4 模型的保存

        timm库所创建的模型是torch.model的子类,我们可以直接使用torch库中内置的模型参数保存和加载的方法

torch.save(model.state_dict(),'./checkpoint/timm_model.pth')
model.load_state_dict(torch.load('./checkpoint/timm_model.pth'))

4. 半精度训练

4.1 半精度介绍

        (1)GPU性能:算力和显存

        (2)半精度:PyTorch默认的浮点数存储方式用的是torch.float32,即torch.float32——>torch.float16

        (3)图形表示:

4.2 半精度训练的设置

# 导入模型
from torch.cuda.amp import autocast

# 模型设置:在模型定义中,使用python的装饰器方法,用autocast装饰模型中的forward函数。
autocast()   
def forward(self, x):
    ...
    return x

# 训练过程:在训练过程中,只需在将数据输入模型及其之后的部分放入“with autocast():“即可
 for x in train_loader:
	x = x.cuda()
	with autocast():
        output = model(x)
        ...

注意: 半精度训练主要适用于数据本身的size比较大。当数据本身的size并不大时,使用半精度训练则可能不会带来显著的提升。

5. 数据增强-imgaug

5.1 imgaug简介和安装

5.1.1 imgaug简介

        imgaug是计算机视觉任务中常用的一个数据增强的包

5.1.2 imgaug的安装

        (1)conda

conda config --add channels conda-forge
conda install imgaug

        (2)pip

#  install imgaug either via pypi

pip install imgaug

#  install the latest version directly from github

pip install git+https://github.com/aleju/imgaug.git

5.2 imgaug的使用

5.2.1 单张图片处理

import imageio
import imgaug as ia
%matplotlib inline

# 图片的读取
img = imageio.imread("./Lenna.jpg")
# 可视化图片
ia.imshow(img)


# 一张一种数据增强处理
# imgaug包含了许多从Augmenter继承的数据增强的操作。在这里我们以Affine为例子
from imgaug import augmenters as iaa
# 设置随机数种子
ia.seed(4)
# 实例化方法
rotate = iaa.Affine(rotate=(-4,45))
img_aug = rotate(image=img)
ia.imshow(img_aug)


# # 一张多种种数据增强处理
from imgaug import augmenters as iaa
# 设置随机数种子
ia.seed(4)
# 实例化方法
rotate = iaa.Affine(rotate=(-4,45))
img_aug = rotate(image=img)
ia.imshow(img_aug)
iaa.Sequential(children=None, # Augmenter集合
               random_order=False, # 是否对每个batch使用不同顺序的Augmenter list
               name=None,
               deterministic=False,
               random_state=None)
# 构建处理序列
aug_seq = iaa.Sequential([
    iaa.Affine(rotate=(-25,25)),
    iaa.AdditiveGaussianNoise(scale=(10,60)),
    iaa.Crop(percent=(0,0.2))
])
# 对图片进行处理,image不可以省略,也不能写成images
image_aug = aug_seq(image=img)
ia.imshow(image_aug)

5.2.2 对批次图片进行处理

# 对批次的图片以同一种方式处理
images = [img,img,img,img,]
images_aug = rotate(images=images)
ia.imshow(np.hstack(images_aug))

aug_seq = iaa.Sequential([
    iaa.Affine(rotate=(-25, 25)),
    iaa.AdditiveGaussianNoise(scale=(10, 60)),
    iaa.Crop(percent=(0, 0.2))
])
# 传入时需要指明是images参数
images_aug = aug_seq.augment_images(images = images)
#images_aug = aug_seq(images = images) 
ia.imshow(np.hstack(images_aug))



# 对批次的图片分部分处理
iaa.Sometimes(p=0.5,  # 代表划分比例
              then_list=None,  # Augmenter集合。p概率的图片进行变换的Augmenters。
              else_list=None,  #1-p概率的图片会被进行变换的Augmenters。注意变换的图片应用的Augmenter只能是then_list或者else_list中的一个。
              name=None,
              deterministic=False,
              random_state=None)



# 对不同大小的图片进行处理
# 构建pipline
seq = iaa.Sequential([
    iaa.CropAndPad(percent=(-0.2, 0.2), pad_mode="edge"),  # crop and pad images
    iaa.AddToHueAndSaturation((-60, 60)),  # change their color
    iaa.ElasticTransformation(alpha=90, sigma=9),  # water-like effect
    iaa.Cutout()  # replace one squared area within the image by a constant intensity value
], random_order=True)

# 加载不同大小的图片
images_different_sizes = [
    imageio.imread("https://upload.wikimedia.org/wikipedia/commons/e/ed/BRACHYLAGUS_IDAHOENSIS.jpg"),
    imageio.imread("https://upload.wikimedia.org/wikipedia/commons/c/c9/Southern_swamp_rabbit_baby.jpg"),
    imageio.imread("https://upload.wikimedia.org/wikipedia/commons/9/9f/Lower_Keys_marsh_rabbit.jpg")
]

# 对图片进行增强
images_aug = seq(images=images_different_sizes)

# 可视化结果
print("Image 0 (input shape: %s, output shape: %s)" % (images_different_sizes[0].shape, images_aug[0].shape))
ia.imshow(np.hstack([images_different_sizes[0], images_aug[0]]))

print("Image 1 (input shape: %s, output shape: %s)" % (images_different_sizes[1].shape, images_aug[1].shape))
ia.imshow(np.hstack([images_different_sizes[1], images_aug[1]]))

print("Image 2 (input shape: %s, output shape: %s)" % (images_different_sizes[2].shape, images_aug[2].shape))
ia.imshow(np.hstack([images_different_sizes[2], images_aug[2]]))

 5.2.3 imgaug在PyTorch的应用

import numpy as np
from imgaug import augmenters as iaa
from torch.utils.data import DataLoader, Dataset
from torchvision import transforms

# 构建pipline
tfs = transforms.Compose([
    iaa.Sequential([
        iaa.flip.Fliplr(p=0.5),
        iaa.flip.Flipud(p=0.5),
        iaa.GaussianBlur(sigma=(0.0, 0.1)),
        iaa.MultiplyBrightness(mul=(0.65, 1.35)),
    ]).augment_image,
    # 不要忘记了使用ToTensor()
    transforms.ToTensor()
])

# 自定义数据集
class CustomDataset(Dataset):
    def __init__(self, n_images, n_classes, transform=None):
		# 图片的读取,建议使用imageio
        self.images = np.random.randint(0, 255,
                                        (n_images, 224, 224, 3),
                                        dtype=np.uint8)
        self.targets = np.random.randn(n_images, n_classes)
        self.transform = transform

    def __getitem__(self, item):
        image = self.images[item]
        target = self.targets[item]

        if self.transform:
            image = self.transform(image)

        return image, target

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


def worker_init_fn(worker_id):
    imgaug.seed(np.random.get_state()[1][0] + worker_id)


custom_ds = CustomDataset(n_images=50, n_classes=10, transform=tfs)
custom_dl = DataLoader(custom_ds, batch_size=64,
                       num_workers=4, pin_memory=True, 
                       worker_init_fn=worker_init_fn)

6. 使用argparse进行调参

6.1 argparse简介

        (1)argsparse是python的命令行解析的标准模块,内置于python,我们直接进行调用即可。

        (2)argparse的作用就是将命令行传入的其他参数进行解析、保存和使用。

6.2 argparse的使用

6.2.1 设置步骤

        (1)创建ArgumentParser()对象;

        (2)调用add_argument()方法添加参数

        (3)使用parse_args()解析参数

6.2.2 实际代码实现

# demo.py
import argparse

# 创建ArgumentParser()对象
parser = argparse.ArgumentParser()

# 添加参数
parser.add_argument('-o', '--output', action='store_true', 
    help="shows output")
# action = `store_true` 会将output参数记录为True
# type 规定了参数的格式
# default 规定了默认值
parser.add_argument('--lr', type=float, default=3e-5, help='select the learning rate, default=1e-3') 

parser.add_argument('--batch_size', type=int, required=True, help='input batch size')  
# 使用parse_args()解析函数
args = parser.parse_args()

if args.output:
    print("This is some output")
    print(f"learning rate:{args.lr} ")

6.3  更加高效使用argparse修改超参数

        一般在进行一个项目创建时,我们喜欢直接创建config.py文件对参数进行存储,方便对参数进行查询、调用,修改和删除。

        config.py文件

import argparse  
  
def get_options(parser=argparse.ArgumentParser()):  
  
    parser.add_argument('--workers', type=int, default=0,  
                        help='number of data loading workers, you had better put it '  
                              '4 times of your gpu')  
  
    parser.add_argument('--batch_size', type=int, default=4, help='input batch size, default=64')  
  
    parser.add_argument('--niter', type=int, default=10, help='number of epochs to train for, default=10')  
  
    parser.add_argument('--lr', type=float, default=3e-5, help='select the learning rate, default=1e-3')  
  
    parser.add_argument('--seed', type=int, default=118, help="random seed")  
  
    parser.add_argument('--cuda', action='store_true', default=True, help='enables cuda')  
    parser.add_argument('--checkpoint_path',type=str,default='',  
                        help='Path to load a previous trained model if not empty (default empty)')  
    parser.add_argument('--output',action='store_true',default=True,help="shows output")  
  
    opt = parser.parse_args()  
  
    if opt.output:  
        print(f'num_workers: {opt.workers}')  
        print(f'batch_size: {opt.batch_size}')  
        print(f'epochs (niters) : {opt.niter}')  
        print(f'learning rate : {opt.lr}')  
        print(f'manual_seed: {opt.seed}')  
        print(f'cuda enable: {opt.cuda}')  
        print(f'checkpoint_path: {opt.checkpoint_path}')  
  
    return opt  
  
if __name__ == '__main__':  
    opt = get_options()

        train.py调用其中的参数进行参数的设置

# 导入必要库
...
import config

opt = config.get_options()

manual_seed = opt.seed
num_workers = opt.workers
batch_size = opt.batch_size
lr = opt.lr
niters = opt.niters
checkpoint_path = opt.checkpoint_path

# 随机数的设置,保证复现结果
def set_seed(seed):
    torch.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    random.seed(seed)
    np.random.seed(seed)
    torch.backends.cudnn.benchmark = False
    torch.backends.cudnn.deterministic = True

...


if __name__ == '__main__':
	set_seed(manual_seed)
	for epoch in range(niters):
		train(model,lr,batch_size,num_workers,checkpoint_path)
		val(model,lr,batch_size,num_workers,checkpoint_path)

参考:深入浅出PyTorch——第六章:PyTorch进阶训练技巧

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

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

相关文章

POI及EasyExcel

1.谈谈 POI 和 easyExcel 应用场景 1.将用户信息导出为excel表格(导出数据....)2.将Excel表中的信息录入到网站数据库(习题上传....) 开发中经常会设计到excel的处理,如导出Excel,导入Excel到数据库中&…

详解Spring面试IoC和AOP

Spring IOC 和 AOP 文章目录Spring IOC 和 AOP前言什么是 IoC?**为什么叫控制反转**不用IoC:所有对象开发者自己创建使用IoC:对象不用卡法这创建,而是交给Spring框架完成基于XML和基于注解基于XML:基于注解&#xff1a…

TeeChart Pro for .NET 2022.10.24 Crack

.NNET 的 TeeChart 图表控件提供了一个很好的通用组件套件,可以满足无数的图表需求,也针对重要的垂直领域,例如金融、科学和统计领域。 数据可视化 数十种完全可定制和交互式的图表类型、地图和仪表指示器,以及完整的功能集&#…

大一html5期末大作业 :基于html实现传统文化网页设计题材

Web前端开发技术 描述 网页设计题材,DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 国家级非遗 | 非遗文化 | 非遗网页设计 | 非遗保护 | 非遗之旅 | 非遗传承HTML期末大学生网页设计作业 HTML:结构 CSS:样式 在操作方面上运用了html5和…

第八章:堆的讲解与实现

第八章:堆的实现与堆相关的算法一、堆1、什么是堆?2、堆的实现(1)堆的定义(2)接口函数初始化销毁插入删除判断是否为空返回堆顶返回堆中的元素个数打印一、堆 1、什么是堆? 在前面的章节中&am…

机器学习算法交叉验证最频繁犯的6个错误

交叉验证是保证模型有效的方法,同时也是防止模型过拟合的方法。但在有限的数据集中,交叉验证容易出现一些错误使用。 本文将介绍在使用交叉验证中,常见的一些错误情况,希望读者在阅读后可以避免再次犯错。 文章目录技术提升什么是…

轻松玩转树莓派Pico之四、Ubuntu下在线debug环境搭建

目录 1、openocd编译安装 1)安装依赖 2)下载 3)编译 4)安装GDB 2、Picoprobe编译与连接 1)下载、编译 2)开发板连接 3)Picoprobe连接至Linux 3、手工运行openocd和gdb 4、VSCode在线调…

Niantic CEO:AR有望取代二维码,理想的AR眼镜还需3-5年

早前,Niantic CEO John Hanke就曾谈到过对于元宇宙的愿景,相比于VR的沉浸式体验,他认为未来元宇宙应该是将虚拟和现实融合的AR体验。尽管如此,现在还没有一款足够普及的AR眼镜产品,仅依赖手机、平板电脑并不能展现沉浸…

《Linux-常见指令详解》

目录 Linux背景 开源 操作系统的理解 问题 1.空文件占磁盘空间吗? 2.创建的文件是在内存中还是磁盘中? 3.绝对路径和相对路径的区别 4.Linux下的文件后缀可以随便写,不同于Windows 常见指令和权限 查看 Linux 主机 ip 使用 XSh…

Alibaba 官方上线,SpringBoot+SpringCloud 全彩指南(第五版)

Alibaba 作为国内一线互联网大厂,其中 springcloudAlibaba 更是阿里微服务最具代表性的技术之一,很多人只知道 springcloudAlibaba 其实面向微服务技术基本上都有的下面就给大家推荐一份 Alibaba 官网最新版:SpringBootSpringCloud 微服务全栈…

面试系列分布式事务:谈谈2PC的理解

2PC其实就是两阶段提交的分布式事务中事务类型,两阶段提交就是分两个阶段提交: 第一阶段询问各个事务数据源是否准备好。 第二阶段才真正将数据提交给事务数据源。 为了保证该事务可以满足ACID,就引入一个协调者(Cooradinator&…

【OpenCV-Python】教程:3-9 轮廓(2)轮廓特征

OpenCV Python 轮廓特征 【目标】 轮廓矩轮廓周长、轮廓面积轮廓拟合、轮廓凸包、轮廓凹凸性检查外接矩形、最小包围圈椭圆拟合、直线拟合 【代码】 周长、面积、矩 第一幅图像为原始轮廓图像,第二幅图像为轮廓点拟合图像(精度为周长的1/10&#xff…

Docker——Windows版本Docker安装

目录 一、简介 1.1 Docker如何解决大型项目依赖关系复杂,不同组件依赖的兼容性问题? 1.2 Docker如何解决开发、测试、生产环境有差异的问题 1.3 Docker 和 虚拟机的区别 1.4 Docker架构 1.5 总结 二、Docker安装(Windows版) 2.1…

电容笔可以用什么代替?好用电容笔品牌推荐

在互联网办公、互联网教学等领域,电容笔再次成为全球流行的电子产品。用平替电容笔来代替苹果的电容笔吗?实际上,我们可以考虑买一支平替电容笔,而不用再买昂贵的苹果 Pencil。一支平替电容笔,一两百块钱,比…

spring web 简单项目数据库查询 纯注解版替代web.xml

关键词句:第一个spring 简单项目 纯注解 包括替代web.xml 文件 第一个spring mvc web 简单项目 纯注解 用纯注解写spring web 简单项目 纯注解写web 项目 spring 写数据库 查询 注解方式 spring 数据接链接池 用的阿里的 spring jdbc jdbcTemplate类使用…

python--星际大战(基础版)

实现功能: 运用python的pygame模块实现上方出现一群体的敌机,每个敌机会随机不定时发射子弹,下方是玩家飞机,通过控制方向和发射子弹来摧毁所以敌机,在游戏开始前会有一个计时器(3秒)计时结束&…

3、Pinpoint-Agent端注册服务到Server端

0、此节简介 此章节大部分操作是在另一台服务器上,vm7。 Agent端配置 项目发布 注册到Pinpoint服务端 接口调用,服务端查看详情 1、Agent端配置 Agent推荐使用和Pinpoint服务端同样的版本。 1.1、下载Pinpoint-Java-Agent包 Github下载链接:h…

第七章 规范化:Eslint + Prettier + Husky

第七章 规范化:Eslint Prettier Husky 为了项目能够长期健康的发展。代码的规范性建设非常重要。只有纪律严明的队伍才能不断打胜仗。 规范制定容易,执行的难度很大。 项目规范可以分为: 编码规范;项目结构规范;…

hyper-v虚拟机ubuntu ssh配置

环境描述: 本地机:win10系统 linux机:hyper-v 虚拟机中的ubuntu 1、ssh 安装和启用 //安装ssh sudo apt-get install ssh//启用ssh service ssh start//查看ssh状态 service ssh status 2、网络工具安装和ip查看 //安装网络工具 sudo ap…