基于ResNet框架的CNN

news2024/11/13 10:28:51

数据准备

DATA_URL = 'http://download.tensorflow.org/example_images/flower_photos.tgz'

一、训练集和验证集的划分

#spile_data.py

import os
from shutil import copy
import random


def mkfile(file):
    if not os.path.exists(file):
        os.makedirs(file)


file = 'flower_data/flower_photos'
flower_class = [cla for cla in os.listdir(file) if ".txt" not in cla] #['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips']
mkfile('flower_data/train') #生成train文件夹
for cla in flower_class:
    mkfile('flower_data/train/'+cla) #在train文件夹下生成各个类别的文件夹

mkfile('flower_data/val')
for cla in flower_class:
    mkfile('flower_data/val/'+cla)

split_rate = 0.1
for cla in flower_class:
    cla_path = file + '/' + cla + '/'
    images = os.listdir(cla_path)
    num = len(images)
    eval_index = random.sample(images, k=int(num*split_rate)) #在images中随机获取0.1的图片
    for index, image in enumerate(images):
        if image in eval_index:
            image_path = cla_path + image
            new_path = 'flower_data/val/' + cla
            copy(image_path, new_path)
        else:
            image_path = cla_path + image
            new_path = 'flower_data/train/' + cla
            copy(image_path, new_path)
        print("\r[{}] processing [{}/{}]".format(cla, index+1, num), end="")  # processing bar
    print()

print("processing done!")

二、ResNet网络

import torch.nn as nn
import math
import torch.utils.model_zoo as model_zoo
import mmd
from attention import ChannelAttention
from attention import SpatialAttention
import torch


__all__ = ['ResNet', 'resnet50']


model_urls = {
    'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth',
}

def conv3x3(in_planes, out_planes, stride=1,groups=1):
    """3x3 convolution with padding"""
    return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
                     padding=1, bias=False)

def conv1x1(in_planes, out_planes, stride=1):
    """1x1 convolution"""
    return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)


'''
Resnet中BasicBlock结构,ResNet中使用的网络结构。分2步走:3x3; 3x3
'''
class BasicBlock(nn.Module):
    expansion = 1  # 最后输出的通道数扩充的比例

    # BN层来加快网络模型的收敛速度/训练速度/解决梯度消失或者梯度爆炸的问题
    # 对batch中所有的同一个channel的数据元素进行标准化处理。一个batch共享一套参数
    # 即如果有C个通道,对N*H*W进行标准化处理,一共进行C次。

    def __init__(self, inplanes, planes, stride=1, downsample=None):
        super(BasicBlock, self).__init__()
        self.conv1 = conv3x3(inplanes, planes, stride)
        self.bn1 = nn.BatchNorm2d(planes)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = conv3x3(planes, planes)
        self.bn2 = nn.BatchNorm2d(planes)
        self.downsample = downsample
        self.stride = stride

    def forward(self, x):
        residual = x

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)

        # downsample是用一个1x1的卷积核处理,改变通道数,如果H/W尺度也不一样就设计stride
        if self.downsample is not None:
            residual = self.downsample(x)

        out += residual
        out = self.relu(out)

        return out


'''
Resnet中Bottleneck结构,ResNet中使用的网络结构。目的是为了降低参数量,分三步走:
1数据降维(1x1),2常规卷积核的卷积(3x3),3数据升维(1x1)
结果图片长宽不变,通道数扩大4倍
'''
class Bottleneck(nn.Module):
    expansion = 4  # 最后输出的通道数扩充的比例

    def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
                 base_width=64, norm_layer=None):
        super(Bottleneck, self).__init__()
        self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
        # BN层来加快网络模型的收敛速度/训练速度/解决梯度消失或者梯度爆炸的问题
        # 对batch中所有的同一个channel的数据元素进行标准化处理。一个batch共享一套参数
        # 即如果有C个通道,对N*H*W进行标准化处理,一共进行C次。
        self.bn1 = nn.BatchNorm2d(planes)  # 卷积层后加BatchNorm2d,按照channel进行归一化
        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
                               padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(planes)
        self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)
        self.bn3 = nn.BatchNorm2d(planes * 4)
        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample
        self.dropout=nn.Dropout()
        self.stride = stride

    def forward(self, x):
        residual = x

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)

        out = self.conv3(out)
        out = self.bn3(out)

        # downsample是用一个1x1的卷积核处理,改变通道数,如果H/W尺度也不一样就设计stride
        if self.downsample is not None:
            residual = self.downsample(x)

        out += residual
        out = self.relu(out)

        return out


'''
ResNet由以下组成:
1.conv1、norm1、relu(当指定了deep_stem,这三个将被stem代替)
2.maxpool
3.layer1~layer4(定义为ResLayer类,分别由多个BasicBlock或Bottleneck组成)
'''
class ResNet(nn.Module):
    # 参数block指明残差块是两层或三层,参数layers指明每个卷积层需要的残差块数量,num_classes指明分类数,zero_init_residual是否初始化为0
    def __init__(self, block, layers, num_classes=1000, zero_init_residual=False,
                 groups=1, width_per_group=64, norm_layer=None):
        super(ResNet, self).__init__()
        if norm_layer is None:
            norm_layer = nn.BatchNorm2d

        self.inplanes = 64
        self.groups = groups
        self.base_width = width_per_group
        self.conv1 = nn.Conv2d(12, self.inplanes, kernel_size=7, stride=2, padding=3,
                               bias=False)
        self.bn1 = norm_layer(self.inplanes)
        self.relu = nn.ReLU(inplace=True)

        # 网络的第一层加入注意力机制
        # self.ca = ChannelAttention(self.inplanes)
        # self.sa = SpatialAttention()

        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        self.layer1 = self._make_layer(block, 64, layers[0], norm_layer=norm_layer)
        self.layer2 = self._make_layer(block, 128, layers[1], stride=2, norm_layer=norm_layer)
        self.layer3 = self._make_layer(block, 256, layers[2], stride=2, norm_layer=norm_layer)
        self.layer4 = self._make_layer(block, 512, layers[3], stride=2, norm_layer=norm_layer)

        # 网络的卷积层的最后一层加入注意力机制
        # self.ca1 = ChannelAttention(self.inplanes)
        # self.sa1 = SpatialAttention()

        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))  # 自适应平均池化,指定输出(H,W)
        self.fc = nn.Linear(512 * block.expansion, num_classes)

        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
            elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)

        if zero_init_residual:
            for m in self.modules():
                if isinstance(m, Bottleneck):
                    nn.init.constant_(m.bn3.weight, 0)
                elif isinstance(m, BasicBlock):
                    nn.init.constant_(m.bn2.weight, 0)

    # 构造ResLayer类,layer1~layer4
    # block:BasicBlock/Bottleneck; planes:块的输入通道数; blocks:块的数目
    def _make_layer(self, block, planes, blocks, stride=1, norm_layer=None):
        if norm_layer is None:
            norm_layer = nn.BatchNorm2d
        downsample = None  # downSample的作用于在残差连接时 将输入的图像的通道数变成和卷积操作的尺寸一致
        if stride != 1 or self.inplanes != planes * block.expansion:
            # 通道数恢复成一致/长宽恢复一致
            downsample = nn.Sequential(
                conv1x1(self.inplanes, planes * block.expansion, stride),
                norm_layer(planes * block.expansion),
            )

        layers = []
        layers.append(block(self.inplanes, planes, stride, downsample, self.groups,
                            self.base_width, norm_layer))
        self.inplanes = planes * block.expansion
        for _ in range(1, blocks):
            layers.append(block(self.inplanes, planes, groups=self.groups,
                                base_width=self.base_width, norm_layer=norm_layer))

        return nn.Sequential(*layers)

    '''
    卷积/池化后的tensor维度为(batchsize,channels,x,y),其中x.size(0)指batchsize的值,
    通过x.view(x.size(0), -1)将tensor的结构转换为了(batchsize, channels*x*y)
    即将(channels,x,y)拉直,然后就可以和fc层连接
    
    因为最后avgpool(1,1)指定输出长*宽为1*1,通道为512*4,所以channels*x*y=2048
    '''
    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)

        # x = self.ca(x) * x
        # x = self.sa(x) * x

        x = self.maxpool(x)

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        # x = self.ca1(x) * x
        # x = self.sa1(x) * x

        x = self.avgpool(x)
        x = x.view(x.size(0), -1)
        #x=self.fc(x)

        return x

class DANNet(nn.Module):

    def __init__(self, num_classes=2):
        super(DANNet, self).__init__()
        self.sharedNet = resnet50(False)
        self.cls_fc = nn.Linear(2048, num_classes)  # channels*x*y=2048*1*1,见上面的备注

    def forward(self, source, target):
        loss = 0
        source = self.sharedNet(source)
        if self.training == True:
            target = self.sharedNet(target)
            # loss += mmd.mmd_rbf_accelerate(source, target)
            loss += mmd.mmd_rbf_noaccelerate(source, target)

        source = self.cls_fc(source)
        #target = self.cls_fc(target)

        return source, loss

def resnet50(pretrained=False, **kwargs):
    """Constructs a ResNet-50 model.
    Args:
        pretrained (bool): If True, returns a model pre-trained on ImageNet
    """
    model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs)
    if pretrained:
        model.load_state_dict(model_zoo.load_url(model_urls['resnet50']))
    return model

三、训练模型

#train.py

import torch
import torch.nn as nn
from torchvision import transforms, datasets
import json
import matplotlib.pyplot as plt
import os
import torch.optim as optim
from model import resnet34, resnet101
import torchvision.models.resnet


device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)

#数据增强操作,训练集:随机裁剪(RandomResizedCrop)、随机水平翻转(RandomHorizontalFlip)、转换为张量(ToTensor)以及归一化(Normalize)
#验证集:大小调整(Resize)、中心裁剪(CenterCrop)、转换为张量(ToTensor)以及归一化(Normalize)
data_transform = {
    "train": transforms.Compose([transforms.RandomResizedCrop(224),
                                 transforms.RandomHorizontalFlip(),
                                 transforms.ToTensor(),
                                 transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])]),#来自官网参数
    "val": transforms.Compose([transforms.Resize(256),#将最小边长缩放到256
                               transforms.CenterCrop(224),
                               transforms.ToTensor(),
                               transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])}


data_root = os.getcwd()
image_path = data_root + "/flower_data/"  # flower data set path

train_dataset = datasets.ImageFolder(root=image_path + "train",
                                     transform=data_transform["train"])
train_num = len(train_dataset) #3306

flower_list = train_dataset.class_to_idx   #{'daisy': 0, 'dandelion': 1, 'roses': 2, 'sunflowers': 3, 'tulips': 4}
cla_dict = dict((val, key) for key, val in flower_list.items())  #{0: 'daisy', 1: 'dandelion', 2: 'roses', 3: 'sunflowers', 4: 'tulips'}
# write dict into json file
json_str = json.dumps(cla_dict, indent=4)  #将cla_dict字典对象转换为JSON格式的字符串,并通过indent=4参数指定缩进为4个空格
with open('class_indices.json', 'w') as json_file:
    json_file.write(json_str)

batch_size = 16
train_loader = torch.utils.data.DataLoader(train_dataset,
                                           batch_size=batch_size, shuffle=True,
                                           num_workers=0)

validate_dataset = datasets.ImageFolder(root=image_path + "/val",
                                        transform=data_transform["val"])
val_num = len(validate_dataset)  #364
validate_loader = torch.utils.data.DataLoader(validate_dataset,
                                              batch_size=batch_size, shuffle=False,
                                              num_workers=0)
#net = resnet34()
net = resnet34(num_classes=5)
# load pretrain weights

# model_weight_path = "./resnet34-pre.pth"
# missing_keys, unexpected_keys = net.load_state_dict(torch.load(model_weight_path), strict=False)#载入模型参数

# for param in net.parameters():
#     param.requires_grad = False
# change fc layer structure

# inchannel = net.fc.in_features
# net.fc = nn.Linear(inchannel, 5)


net.to(device)  #将神经网络模型net移动到指定的设备上,这样模型就可以在GPU/CPU上计算

loss_function = nn.CrossEntropyLoss()  #损失函数
optimizer = optim.Adam(net.parameters(), lr=0.0001)  #优化器

best_acc = 0.0
save_path = './resNet34.pth'
#一个epoch表示对整个训练数据集进行一次完整的迭代训练
for epoch in range(3):
    # train
    net.train()
    running_loss = 0.0
    #step表示当前的步数(或者称为批次数),data则表示从train_loader中加载的数据对象
    for step, data in enumerate(train_loader, start=0):
        images, labels = data  #images:(16,3,224,224) labels:(16,)
        optimizer.zero_grad()
        logits = net(images.to(device)) #logits:(16,5)将输入的图像数据images传入神经网络net
        loss = loss_function(logits, labels.to(device)) #1.6871 计算模型输出logits进行标准化(softmax),再计算每个样本预测标签和真实标签的交叉熵,对于整个批次的样本,计算平均交叉熵损失
        loss.backward()
        optimizer.step()

        # print statistics
        running_loss += loss.item()  #累加每个批次的损失值
        # print train process
        rate = (step+1)/len(train_loader)
        a = "*" * int(rate * 50)
        b = "." * int((1 - rate) * 50)
        print("\rtrain loss: {:^3.0f}%[{}->{}]{:.4f}".format(int(rate*100), a, b, loss), end="")
    print()

    # validate
    net.eval()
    acc = 0.0  # accumulate accurate number / epoch
    with torch.no_grad():
        for val_data in validate_loader:
            val_images, val_labels = val_data
            outputs = net(val_images.to(device))  # eval model only have last output layer
            # loss = loss_function(outputs, test_labels)
            predict_y = torch.max(outputs, dim=1)[1]  #torch.max包含两个维度信息,第一个维度是最大值,第二个维度是最大值对应的索引
            acc += (predict_y == val_labels.to(device)).sum().item() #每一次的validate_loader中预测正确的个数,.item() 方法转换为标量
        val_accurate = acc / val_num
        if val_accurate > best_acc:
            best_acc = val_accurate
            torch.save(net.state_dict(), save_path)  #state_dict()方法返回模型的参数字典,save_path保存模型参数的文件路径
        print('[epoch %d] train_loss: %.3f  test_accuracy: %.3f' %
              (epoch + 1, running_loss / step, val_accurate))

print('Finished Training')

四、预测

#predict.py

import torch
from model import resnet34
from PIL import Image
from torchvision import transforms
import matplotlib.pyplot as plt
import json

data_transform = transforms.Compose(
    [transforms.Resize(256),
     transforms.CenterCrop(224),
     transforms.ToTensor(),
     transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])

# load image
img = Image.open("./roses.jpg")
plt.imshow(img)
# [N, C, H, W]
img = data_transform(img)
# expand batch dimension
img = torch.unsqueeze(img, dim=0)

# read class_indict
try:
    json_file = open('./class_indices.json', 'r')
    class_indict = json.load(json_file)
except Exception as e:
    print(e)
    exit(-1)

# create model
model = resnet34(num_classes=5)
# load model weights
model_weight_path = "./resNet34.pth"
model.load_state_dict(torch.load(model_weight_path))
model.eval()
with torch.no_grad():
    # predict class
    output = torch.squeeze(model(img))
    predict = torch.softmax(output, dim=0)
    predict_cla = torch.argmax(predict).numpy()
print(class_indict[str(predict_cla)], predict[predict_cla].numpy())
plt.show()

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

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

相关文章

代码随想录算法训练营第四十九天| 123.买卖股票的最佳时机III 188.买卖股票的最佳时机IV

文档讲解:代码随想录 视频讲解:代码随想录B站账号 状态:看了视频题解和文章解析后做出来了 123.买卖股票的最佳时机III class Solution:def maxProfit(self, prices: List[int]) -> int:if len(prices) 0:return 0dp [[0] * 5 for _ in…

【监控系统】日志可视化监控体系ELK搭建

1.ELK架构是什么 ELK是ElasticsearchLogstashKibana的简称。 Elasticsearch是一个开源的分布式搜索和分析引擎,可以用于全文检索、结构化检索和分析,它构建在Lucene搜索引擎库之上,是当前使用较为广泛的开源搜索引擎之一。 Logstash是一个…

Linux从 全栈开发 centOS 7 到 运维

Linux从 全栈开发centOS 7 到 运维 一 Linux 入门概述1.1 操作系统1.2 Linux 简介1.3 Linux 系统组成1.4 Linux 发行版1.4 Linux 应用领域1.5 Linux vs Windows 二 环境搭建【狂神说Java】服务器购买及宝塔部署环境说明为什么程序员都需要一个自己的服务器服务器如何购买买完服…

gitlab利用CI多工程持续构建

搭建CI的过程中有多个工程的时候,一个完美的构建过程往往是子工程上的更新(push 或者是merge)触发父工程的构建,这就需要如下建立一个downstream pipeline 子仓库1 .gitlab-ci.yml stages:- buildbuild_job:stage: buildtrigger:project: test_user/tes…

远程文件包含演示

远程文件包含 基本介绍 受害机器 10.9.47.181 攻击者机器1 10.9.47.41 攻击者机器2 10.9.47.217 实现过程 受害者机器开启phpstudy 并且开启允许远程连接 攻击者机器1上有一个文件,内容是phpinfo(); 攻击者机器1提供web服务使得受害者机器能够访问到攻击者…

APP源码|智慧校园电子班牌源码 智慧校园云平台

智慧校园云平台电子班牌系统包括:智慧校园信息管理平台、saas后台管理平台、微信客户端平台、智慧班牌智能终端软件。主要用于构建学校基础架构,进行成员管理、权限分配以及运营数据监管等,是“智慧校园”的“根基”,是各项应用和…

buildadmin+tp8表格操作(8) 表格下方添加 合计行

表格的下方可以自定义添加一个合计行&#xff0c;如果有其它的需求&#xff0c; 我们可以添加我们自已需要的行&#xff0c; 并不局限于合计行 以上就可以给表格的最下方添加一个合计行了 完整代码如下 <template><div class"default-main ba-table-box"&…

python 就是随便玩玩,生成gif图,生成汉字图片,超级简单

文章目录 主方法调用LetterDrawingWordDoingImage 上图 你也想玩的话&#xff0c;可以直接上码云去看 码云链接 主方法调用 import analysisdata.WordDoingImage as WordDoingImage import analysisdata.LetterDrawing as LetterDrawingif __name__ __main__:# 输入的文本&a…

矩阵理论——Gerschgorin定理,以及用python绘制Gerschgorin圆盘动图

矩阵理论——Gerschgorin定理&#xff0c;以及用python绘制Gerschgorin圆盘动图 在矩阵的特征值估计理论当中&#xff0c;有一节是盖尔圆盘定理&#xff1a; 对于一个n阶复数矩阵A&#xff0c;每个特征值lambda位于至少一个Gerschgorin圆盘中&#xff0c;这些圆盘的中心为矩阵…

如何判断sql注入流量特征

如何判断sql注入流量特征 以dvwa的sql注入为例 首先构造一个完整的sql注入请求包 GET /dvwa_2.0.1/vulnerabilities/sqli/?id1&SubmitSubmit HTTP/1.1 Host: 10.9.47.41 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.…

vue3+vite+SQL.js 读取db3文件数据

前言&#xff1a;好久没写博客了&#xff0c;最近一直在忙&#xff0c;没时间梳理。最近遇到一个需求是读取本地SQLite文件&#xff0c;还是花费了点时间才实现&#xff0c;没怎么看到vite方面写这个的文章&#xff0c;现在分享出来完整流程。 1.pnpm下载SQL.js(什么都可以下)…

SAP 通过游标来分批从数据库表读取2G数据

原文链接&#xff1a;https://blog.csdn.net/Buffalo_soldier/article/details/109772612 我们在写程序的时候可能会遇到用内表处理超大量数据的情况&#xff0c;比如取MSEG、BSEG表&#xff0c;内表里的数据如果超过2G就会报程序dump了&#xff0c;所以关键是要控制内表的数据…

【Spring Boot】使用WebSocket协议完成来单提醒及客户催单功能

1 WebSocket介绍 WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信(双向传输)——浏览器和服务器只需要完成一次握手&#xff0c;两者之间就可以创建持久性的连接&#xff0c; 并进行双向数据传输。 1.1 HTTP协议和WebSocket协议对比 1、HTTP是短…

【FFmpeg实战】ffmpeg播放器-音视频解码流程

音视频介绍 音视频解码流程 FFmpeg解码的数据结构说明 AVFormatContext&#xff1a;封装格式上下文结构体,全局结构体,保存了视频文件封装格式相关信息AVInputFormat&#xff1a;每种封装格式&#xff0c;对应一个该结构体AVStream[0]&#xff1a;视频文件中每个视频&#xff…

Java —— 抽象类和接口

目录 1. 抽象类 1.1 抽象类概念 1.2 抽象类语法与特性 1.3 抽象类的作用 2. 接口 2.1 接口的概念 2.2 接口的语法规则与特性 2.3 实现多个接口(解决多继承的问题) 2.4 接口间的继承 2.5 抽象类和接口的区别 2.6 接口的使用实例 2.7 Clonable 接口和深拷贝 2.7.1 Cloneable接口 …

【自动驾驶解决方案】C++取整与保留小数位

一、C基础 1.1double型保留小数为&#xff0c;并以字符输出 #include <iostream> #include <sstream> #include <iomanip> // 包含std::fixedint main() {//浮点数double number 3.1415926;//转换工具类streamstd::stringstream stream;stream << s…

【考研】数据结构(更新到顺序表)

线性表的定义和基本操作 学习目标 线性表定义&#xff1a;具有相同数据类型的n个数据元素的有序序列。 顺序表定义&#xff1a; 特点 基本操作 定义 静态&#xff1a; #include<stdio.h> #include<stdlib.h>#define MaxSize 10//静态 typedef struct{int …

Matplotlib实现Label及Title都在下方的最佳姿势

Matplotlib实现Label及Title都在下方的最佳姿势 1. 问题背景2. 基本思想&#xff08;可以不看&#xff09;3. 方法封装4. 调用实例5. 总结6. 起飞 1. 问题背景 用python绘制下面这种图的时候&#xff0c;一般用xlable作为子图的标题&#xff0c;这是因为plt.title()方法绘制的…

51单片机直流电机控制

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pwm波形&#xff1f;1.1高低电平交互&#xff0c;LED亮灭。1.2 驱动电机时&#xff1f;1.3 怎么调节电机的速度&#xff1f; 二、怎么用51单片机产生PWM波形…

LongAdder功能和原理

AtomicLong能保证并发情况下计数的准确性&#xff0c;其内部通过CAS来解决并发安全性的问题。 AtomicLong的缺点&#xff1a; 可以看到在高并发情况下&#xff0c;当有大量线程同时去更新一个变量&#xff0c;任意一个时间点只有一个线程能够成功&#xff0c;绝大部分的线程在尝…