昇思25天学习打卡营第18天|ShuffleNet图像分类

 一、简介:

ShuffleNetV1是旷视科技提出的一种计算高效的CNN模型,和MobileNet, SqueezeNet等一样主要应用在移动端,所以模型的设计目标就是利用有限的计算资源来达到最好的模型精度。ShuffleNetV1的设计核心是引入了两种操作:Pointwise Group Convolution和Channel Shuffle,这在保持精度的同时大大降低了模型的计算量。因此,ShuffleNetV1和MobileNet类似,都是通过设计更高效的网络结构来实现模型的压缩和加速。如下图所示,ShuffleNet在保持不低的准确率的前提下,将参数量几乎降低到了最小,因此其运算速度较快,单位参数量对模型准确率的贡献非常高。

 二、模型架构:

ShuffleNet最显著的特点在于对不同通道进行重排来解决Group Convolution带来的弊端。通过对ResNet的Bottleneck单元进行改进,在较小的计算量的情况下达到了较高的准确率。

1、Pointwise Group Convolution:

Group Convolution(分组卷积)原理如下图所示,相比于普通的卷积操作,分组卷积的情况下,每一组的卷积核大小为in_channels/g*k*k,一共有g组,所有组共有(in_channels/g*k*k)*out_channels个参数,是正常卷积参数的1/g。分组卷积中,每个卷积核只处理输入特征图的一部分通道,其有点在于参数量会有所降低,但是输出通道仍等于卷积核数量。

Depthwise Convolution(深度可分离卷积)将组数g分为和输入通道相等的in_channels,然后对每一个in_channels做卷积操作,每个卷积核只处理一个通道,记卷积核大小为1*k*k,则卷积核参数量为:in_channels*k*k,得到的feature maps通道数与输入通道数相等。

Pointwise Group Convolution(逐点分组卷积)在分组卷积的基础上,令每一组的卷积核大小为1×1,卷积核参数量为(in_channels/g*1*1)*out_channels。

在进行下面的代码实验之前还是需要先安装mindspore包,安装过程可以参考:昇思25天学习打卡营第1天|快速入门。

from mindspore import nn
import mindspore.ops as ops
from mindspore import Tensorclass GroupConv(nn.Cell):def __init__(self, in_channels, out_channels, kernel_size,stride, pad_mode="pad", pad=0, groups=1, has_bias=False):super(GroupConv, self).__init__()self.groups = groupsself.convs = nn.CellList()for _ in range(groups):self.convs.append(nn.Conv2d(in_channels // groups, out_channels // groups,kernel_size=kernel_size, stride=stride, has_bias=has_bias,padding=pad, pad_mode=pad_mode, group=1, weight_init='xavier_uniform'))def construct(self, x):features = ops.split(x, split_size_or_sections=int(len(x[0]) // self.groups), axis=1)outputs = ()for i in range(self.groups):outputs = outputs + (self.convs[i](features[i].astype("float32")),)out = ops.cat(outputs, axis=1)return out

2、Channel Shuffle:

Group Convolution的弊端在于不同组别的通道无法进行信息交流,堆积GConv层后一个问题是不同组之间的特征图是不通信的,这就好像分成了g个互不相干的道路,每一个人各走各的,这可能会降低网络的特征提取能力。这也是Xception,MobileNet等网络采用密集的1x1卷积(Dense Pointwise Convolution)的原因。为了解决不同组别通道“近亲繁殖”的问题,ShuffleNet优化了大量密集的1x1卷积(在使用的情况下计算量占用率达到了惊人的93.4%),引入Channel Shuffle机制(通道重排)。这项操作直观上表现为将不同分组通道均匀分散重组,使网络在下一层能处理不同组别通道的信息。

如下图所示,对于g组(也就是上文提到的g个互不相干的道路),每组有n个通道的特征图,首先reshape成g行n列的矩阵,再将矩阵转置成n行g列,最后进行flatten操作,得到新的排列。这些操作都是可微分可导的且计算简单,在解决了信息交互的同时符合了ShuffleNet轻量级网络设计的轻量特征。

 

 这里将channel_shuffle的代码实现先展示出来,后面为了在shuffleNet中使用会再次重新实现

def channel_shuffle(self, x):batchsize, num_channels, height, width = ops.shape(x)group_channels = num_channels // self.groupx = ops.reshape(x, (batchsize, group_channels, self.group, height, width))x = ops.transpose(x, (0, 2, 1, 3, 4))x = ops.reshape(x, (batchsize, num_channels, height, width))return x

 三、构建ShuffleNet网络:

1、ShuffleNet模块:

如下图所示,ShuffleNet对ResNet中的Bottleneck结构进行由(a)到(b), (c)的更改:

  1. 将开始和最后的1×1卷积模块(降维、升维)改成Point Wise Group Convolution;

  2. 为了进行不同通道的信息交流,再降维之后进行Channel Shuffle;

  3. 降采样模块中,3×3 Depth Wise Convolution的步长设置为2,长宽降为原来的一般,因此shortcut中采用步长为2的3×3平均池化,并把相加改成拼接。

class ShuffleV1Block(nn.Cell):def __init__(self, inp, oup, group, first_group, mid_channels, ksize, stride):super(ShuffleV1Block, self).__init__()self.stride = stridepad = ksize // 2self.group = groupif stride == 2:outputs = oup - inpelse:outputs = oupself.relu = nn.ReLU()branch_main_1 = [GroupConv(in_channels=inp, out_channels=mid_channels,kernel_size=1, stride=1, pad_mode="pad", pad=0,groups=1 if first_group else group),nn.BatchNorm2d(mid_channels),nn.ReLU(),]branch_main_2 = [nn.Conv2d(mid_channels, mid_channels, kernel_size=ksize, stride=stride,pad_mode='pad', padding=pad, group=mid_channels,weight_init='xavier_uniform', has_bias=False),nn.BatchNorm2d(mid_channels),GroupConv(in_channels=mid_channels, out_channels=outputs,kernel_size=1, stride=1, pad_mode="pad", pad=0,groups=group),nn.BatchNorm2d(outputs),]self.branch_main_1 = nn.SequentialCell(branch_main_1)self.branch_main_2 = nn.SequentialCell(branch_main_2)if stride == 2:self.branch_proj = nn.AvgPool2d(kernel_size=3, stride=2, pad_mode='same')def construct(self, old_x):left = old_xright = old_xout = old_xright = self.branch_main_1(right)if self.group > 1:right = self.channel_shuffle(right)right = self.branch_main_2(right)if self.stride == 1:out = self.relu(left + right)elif self.stride == 2:left = self.branch_proj(left)out = ops.cat((left, right), 1)out = self.relu(out)return outdef channel_shuffle(self, x):batchsize, num_channels, height, width = ops.shape(x)group_channels = num_channels // self.groupx = ops.reshape(x, (batchsize, group_channels, self.group, height, width))x = ops.transpose(x, (0, 2, 1, 3, 4))x = ops.reshape(x, (batchsize, num_channels, height, width))return x

 2、网络构建:

ShuffleNet网络结构如下图所示,以输入图像224×224,组数3(g = 3)为例,首先通过数量24,卷积核大小为3×3,stride为2的卷积层,输出特征图大小为112×112,channel为24;然后通过stride为2的最大池化层,输出特征图大小为56×56,channel数不变;再堆叠3个ShuffleNet模块(Stage2, Stage3, Stage4),三个模块分别重复4次、8次、4次,其中每个模块开始先经过一次下采样模块(上图(c)),使特征图长宽减半,channel翻倍(Stage2的下采样模块除外,将channel数从24变为240);随后经过全局平均池化,输出大小为1×1×960,再经过全连接层和softmax,得到分类概率。

class ShuffleNetV1(nn.Cell):def __init__(self, n_class=1000, model_size='2.0x', group=3):super(ShuffleNetV1, self).__init__()print('model size is ', model_size)self.stage_repeats = [4, 8, 4]self.model_size = model_sizeif group == 3:if model_size == '0.5x':self.stage_out_channels = [-1, 12, 120, 240, 480]elif model_size == '1.0x':self.stage_out_channels = [-1, 24, 240, 480, 960]elif model_size == '1.5x':self.stage_out_channels = [-1, 24, 360, 720, 1440]elif model_size == '2.0x':self.stage_out_channels = [-1, 48, 480, 960, 1920]else:raise NotImplementedErrorelif group == 8:if model_size == '0.5x':self.stage_out_channels = [-1, 16, 192, 384, 768]elif model_size == '1.0x':self.stage_out_channels = [-1, 24, 384, 768, 1536]elif model_size == '1.5x':self.stage_out_channels = [-1, 24, 576, 1152, 2304]elif model_size == '2.0x':self.stage_out_channels = [-1, 48, 768, 1536, 3072]else:raise NotImplementedErrorinput_channel = self.stage_out_channels[1]self.first_conv = nn.SequentialCell(nn.Conv2d(3, input_channel, 3, 2, 'pad', 1, weight_init='xavier_uniform', has_bias=False),nn.BatchNorm2d(input_channel),nn.ReLU(),)self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, pad_mode='same')features = []for idxstage in range(len(self.stage_repeats)):numrepeat = self.stage_repeats[idxstage]output_channel = self.stage_out_channels[idxstage + 2]for i in range(numrepeat):stride = 2 if i == 0 else 1first_group = idxstage == 0 and i == 0features.append(ShuffleV1Block(input_channel, output_channel,group=group, first_group=first_group,mid_channels=output_channel // 4, ksize=3, stride=stride))input_channel = output_channelself.features = nn.SequentialCell(features)self.globalpool = nn.AvgPool2d(7)self.classifier = nn.Dense(self.stage_out_channels[-1], n_class)def construct(self, x):x = self.first_conv(x)x = self.maxpool(x)x = self.features(x)x = self.globalpool(x)x = ops.reshape(x, (-1, self.stage_out_channels[-1]))x = self.classifier(x)return x

 四、模型的训练核评估:

1、数据集准备:

采用CIFAR-10数据集对ShuffleNet进行预训练。

from download import downloadurl = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/cifar-10-binary.tar.gz"download(url, "./dataset", kind="tar.gz", replace=True)# 数据预处理:
import mindspore as ms
from mindspore.dataset import Cifar10Dataset
from mindspore.dataset import vision, transformsdef get_dataset(train_dataset_path, batch_size, usage):image_trans = []if usage == "train":image_trans = [vision.RandomCrop((32, 32), (4, 4, 4, 4)),vision.RandomHorizontalFlip(prob=0.5),vision.Resize((224, 224)),vision.Rescale(1.0 / 255.0, 0.0),vision.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010]),vision.HWC2CHW()]elif usage == "test":image_trans = [vision.Resize((224, 224)),vision.Rescale(1.0 / 255.0, 0.0),vision.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010]),vision.HWC2CHW()]label_trans = transforms.TypeCast(ms.int32)dataset = Cifar10Dataset(train_dataset_path, usage=usage, shuffle=True)dataset = dataset.map(image_trans, 'image')dataset = dataset.map(label_trans, 'label')dataset = dataset.batch(batch_size, drop_remainder=True)return datasetdataset = get_dataset("./dataset/cifar-10-batches-bin", 128, "train")
batches_per_epoch = dataset.get_dataset_size()

2、模型训练:

使用随机初始化的参数做预训练。首先调用ShuffleNetV1定义网络,参数量选择"2.0x",并定义损失函数为交叉熵损失,学习率经过4轮的warmup后采用余弦退火,优化器采用Momentum。最后用train.model中的Model接口将模型、损失函数、优化器封装在model中,并用model.train()对网络进行训练。将ModelCheckpointCheckpointConfigTimeMonitorLossMonitor传入回调函数中,将会打印训练的轮数、损失和时间,并将ckpt文件保存在当前目录下。

import time
import mindspore
import numpy as np
from mindspore import Tensor, nn
from mindspore.train import ModelCheckpoint, CheckpointConfig, TimeMonitor, LossMonitor, Model, Top1CategoricalAccuracy, Top5CategoricalAccuracydef train():mindspore.set_context(mode=mindspore.PYNATIVE_MODE, device_target="Ascend")net = ShuffleNetV1(model_size="2.0x", n_class=10)loss = nn.CrossEntropyLoss(weight=None, reduction='mean', label_smoothing=0.1)min_lr = 0.0005base_lr = 0.05lr_scheduler = mindspore.nn.cosine_decay_lr(min_lr,base_lr,batches_per_epoch*250,batches_per_epoch,decay_epoch=250)lr = Tensor(lr_scheduler[-1])optimizer = nn.Momentum(params=net.trainable_params(), learning_rate=lr, momentum=0.9, weight_decay=0.00004, loss_scale=1024)loss_scale_manager = ms.amp.FixedLossScaleManager(1024, drop_overflow_update=False)model = Model(net, loss_fn=loss, optimizer=optimizer, amp_level="O3", loss_scale_manager=loss_scale_manager)callback = [TimeMonitor(), LossMonitor()]save_ckpt_path = "./"config_ckpt = CheckpointConfig(save_checkpoint_steps=batches_per_epoch, keep_checkpoint_max=5)ckpt_callback = ModelCheckpoint("shufflenetv1", directory=save_ckpt_path, config=config_ckpt)callback += [ckpt_callback]print("============== Starting Training ==============")start_time = time.time()# 由于时间原因,epoch = 5,可根据需求进行调整model.train(5, dataset, callbacks=callback)use_time = time.time() - start_timehour = str(int(use_time // 60 // 60))minute = str(int(use_time // 60 % 60))second = str(int(use_time % 60))print("total time:" + hour + "h " + minute + "m " + second + "s")print("============== Train Success ==============")if __name__ == '__main__':train()

2、模型评估:

在已经划分好的测试集上进行模型评估:

from mindspore import load_checkpoint, load_param_into_netdef test():mindspore.set_context(mode=mindspore.GRAPH_MODE, device_target="Ascend")dataset = get_dataset("./dataset/cifar-10-batches-bin", 128, "test")net = ShuffleNetV1(model_size="2.0x", n_class=10)param_dict = load_checkpoint("shufflenetv1-5_390.ckpt")load_param_into_net(net, param_dict)net.set_train(False)loss = nn.CrossEntropyLoss(weight=None, reduction='mean', label_smoothing=0.1)eval_metrics = {'Loss': nn.Loss(), 'Top_1_Acc': Top1CategoricalAccuracy(),'Top_5_Acc': Top5CategoricalAccuracy()}model = Model(net, loss_fn=loss, metrics=eval_metrics)start_time = time.time()res = model.eval(dataset, dataset_sink_mode=False)use_time = time.time() - start_timehour = str(int(use_time // 60 // 60))minute = str(int(use_time // 60 % 60))second = str(int(use_time % 60))log = "result:" + str(res) + ", ckpt:'" + "./shufflenetv1-5_390.ckpt" \+ "', time: " + hour + "h " + minute + "m " + second + "s"print(log)filename = './eval_log.txt'with open(filename, 'a') as file_object:file_object.write(log + '\n')if __name__ == '__main__':test()

 还是那句话,只要能跑,就不要动(doge)

五、模型预测:

在CIFAR-10的测试集上对模型进行预测,并将预测结果可视化:

import mindspore
import matplotlib.pyplot as plt
import mindspore.dataset as dsnet = ShuffleNetV1(model_size="2.0x", n_class=10)
show_lst = []
param_dict = load_checkpoint("shufflenetv1-5_390.ckpt")
load_param_into_net(net, param_dict)
model = Model(net)
dataset_predict = ds.Cifar10Dataset(dataset_dir="./dataset/cifar-10-batches-bin", shuffle=False, usage="train")
dataset_show = ds.Cifar10Dataset(dataset_dir="./dataset/cifar-10-batches-bin", shuffle=False, usage="train")
dataset_show = dataset_show.batch(16)
show_images_lst = next(dataset_show.create_dict_iterator())["image"].asnumpy()
image_trans = [vision.RandomCrop((32, 32), (4, 4, 4, 4)),vision.RandomHorizontalFlip(prob=0.5),vision.Resize((224, 224)),vision.Rescale(1.0 / 255.0, 0.0),vision.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010]),vision.HWC2CHW()]
dataset_predict = dataset_predict.map(image_trans, 'image')
dataset_predict = dataset_predict.batch(16)
class_dict = {0:"airplane", 1:"automobile", 2:"bird", 3:"cat", 4:"deer", 5:"dog", 6:"frog", 7:"horse", 8:"ship", 9:"truck"}
# 推理效果展示(上方为预测的结果,下方为推理效果图片)
plt.figure(figsize=(16, 5))
predict_data = next(dataset_predict.create_dict_iterator())
output = model.predict(ms.Tensor(predict_data['image']))
pred = np.argmax(output.asnumpy(), axis=1)
index = 0
for image in show_images_lst:plt.subplot(2, 8, index+1)plt.title('{}'.format(class_dict[pred[index]]))index += 1plt.imshow(image)plt.axis("off")
plt.show()

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

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

相关文章

如何在centos7安装Docker

在centOS7中我们可以使用火山引擎镜像源镜像安装Docker,以下是具体的安装步骤。 step 1: 安装必要的一些系统工具 sudo yum install -y yum-utils Step 2: 添加软件源信息 sudo yum-config-manager --add-repo https://mirrors.ivolces.com/docker/linux/centos/docker-ce.r…

力扣双指针算法题目:二叉树的层序遍历(BFS)

目录 1.题目 2.思路解析 3.代码 1.题目 . - 力扣(LeetCode) 2.思路解析 对二叉树进行层序遍历,顾名思义,就是按每一层的顺序对二叉树一层一层地进行遍历 思路如下 从第一层开始,先将二叉树地头放入队列q&#xff0…

2007-2022年中国各企业数字化转型与供应链效率

企业数字化转型与供应链效率是现代企业管理和发展的两个关键方面。以下是对中国各企业数字化转型与供应链效率数据的介绍: 数据简介 企业数字化转型:指企业通过采用数字技术与创新方法,改造业务流程、组织结构和产品服务,以提升…

UCOS-III 系统移植

1. 移植前准备 1.1 源码下载 UCOS-III Kernel Source: https://github.com/weston-embedded/uC-OS3.git Micriμm CPU Source : https://github.com/weston-embedded/uC-CPU.git Micriμm Lib Source: https://github.com/weston-embedded…

多方SQL计算场景下,如何达成双方共识,确认多方计算作业的安全性

安全多方计算在SQL场景下的限制 随着MPC、隐私计算等概念的流行, 诸多政府机构、金融企业开始考虑参与到多方计算的场景中, 扩展数据的应用价值。 以下面这个场景为例, 银行可能希望获取水电局和税务局的数据,来综合计算得到各…

DolphinScheduler-3.1.9 资源中心实践

前言 目前DolphinScheduler最新的稳定版本是 3.1.9 ,基于此做些探索,逐渐深化学习路径,以便于加深理解。 3.2.1 是最新的版本。目前的稳定版本是 3.1.9 基础环境:Hadoop3.3, Java 8, Python3, MacOS14.2.1 一、本地伪分布式安装…

学习笔记——动态路由——IS-IS中间系统到中间系统(开销)

四、IS-IS开销 1、IS-IS 开销简介 在IS-IS协议刚面世时,互联网网络结构还非常简单,因此IS-IS早期的版本中只使用了6bit来描述链路开销,链路开销的取值范围是1-63。一条路由的开销范围只有10bit,取值范围是0-1023。 随着计…

前端实现无缝自动滚动动画

1. 前言: 前端使用HTMLCSS实现一个无缝滚动的列表效果 示例图: 2. 源码 html部分源码: <!--* Author: wangZhiyu <w3209605851163.com>* Date: 2024-07-05 23:33:20* LastEditTime: 2024-07-05 23:49:09* LastEditors: wangZhiyu <w3209605851163.com>* File…

【ubuntu】安装(升级)显卡驱动,黑屏|双屏无法使用问题解决方法

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 ubuntu 安装(升级)显卡驱动&#xff0c;黑屏|双屏无法使用问题解决方法 由于项目需要&#xff0c;对显卡驱动进行升级。升级完就黑屏。。。。&#xff0…

Fast R-CNN(论文阅读)

论文名&#xff1a;Fast R-CNN 论文作者&#xff1a;Ross Girshick 期刊/会议名&#xff1a;ICCV 2015 发表时间&#xff1a;2015-9 ​论文地址&#xff1a;https://arxiv.org/pdf/1504.08083 源码&#xff1a;https://github.com/rbgirshick/fast-rcnn 摘要 这篇论文提出了一…

BAT-致敬精简

什么是bat bat是windows的批处理程序&#xff0c;可以批量完成一些操作&#xff0c;方便快速。 往往我们可以出通过 winR键来打开指令窗口&#xff0c;这里输入的就是bat指令 这里就是bat界面 节约时间就是珍爱生命--你能想象以下2分钟的操作&#xff0c;bat只需要1秒钟 我…

考虑数据库粒度的设计-提升效率

目录 概要 场景 设计思路 小结 概要 公开的资料显示&#xff0c;数据库粒度是&#xff1a;“在数据库领域&#xff0c;特别是数据仓库的设计中&#xff0c;粒度是一个核心概念&#xff0c;它直接影响到数据分析的准确性和存储效率。粒度的设定涉及到数据的详细程度和精度&…

【JVM基础篇】Java的四种垃圾回收算法介绍

文章目录 垃圾回收算法垃圾回收算法的历史和分类垃圾回收算法的评价标准标记清除算法优缺点 复制算法优缺点 标记整理算法&#xff08;标记压缩算法&#xff09;优缺点 分代垃圾回收算法&#xff08;常用&#xff09;JVM参数设置使用Arthas查看内存分区垃圾回收执行流程分代GC算…

【SpringBoot】IDEA查看spring bean的依赖关系

前因&#xff1a;在研究springcloud config组件时&#xff0c;我发现config-server包下的EnvironmentController可以响应客户端的请求&#xff0c;但EnvironmentController并不在启动类所在的包路径下&#xff0c;所以我推测它是作为某个Bean方法在生效&#xff0c;寻找bean的依…

DAY1: 实习前期准备

文章目录 VS Code安装的插件C/CCMakeGitHub CopilotRemote-SSH收获 VS Code 下载链接&#xff1a;https://code.visualstudio.com 安装的插件 C/C 是什么&#xff1a;C/C IntelliSense, debugging, and code browsing. 为什么&#xff1a;初步了解如何在VS Code里使用C输出…

关于小爱同学自定义指令执行

1.前言 之前买了小爱同学音响&#xff0c;一直想让其让我的生活变得更智能&#xff0c;编写一些程序来完成一些自动化任务&#xff0c;但是经过搜索发现&#xff0c;官方开发者平台不能用了&#xff0c;寻找api阶段浪费了我很长时间。最后在github 开源项目发现了俩个比较关键…

13.SQL注入-宽字节

SQL注入-宽字节 含义&#xff1a; MySQL是用的PHP语言&#xff0c;然后PHP有addslashes()等函数&#xff0c;这类函数会自动过滤 ’ ‘’ null 等这些敏感字符&#xff0c;将它们转义成’ ‘’ \null&#xff1b;然后宽字节字符集比如GBK它会自动把两个字节的字符识别为一个汉…

内容营销专家刘鑫炜:网站排名需考虑哪些SEO优化技巧?

网站排名的SEO优化技巧包括&#xff1a; 1. 关键词研究&#xff1a;了解目标受众的搜索关键词&#xff0c;将这些关键词合理地应用在网站的标题、描述、正文和标签中&#xff0c;有助于提高网站排名。 2. 内容优化&#xff1a;创建高质量、有价值的内容&#xff0c;可以吸引搜…

Qt源码解析之QObject

省去大部分virtual和public方法后&#xff0c;Qobject主要剩下以下成员&#xff1a; //qobject.h class Q_CORE_EXPORT Qobject{Q_OBJECTQ_PROPERTY(QString objectName READ objectName WRITE setObjectName NOTIFY objectNameChanged)Q_DECLARE_PRIVATE(QObject) public:Q_I…

STM32-OC输出比较和PWM

本内容基于江协科技STM32视频内容&#xff0c;整理而得。 文章目录 1. OC输出比较和PWM1.1 OC输出比较1.2 PWM&#xff08;脉冲宽度调制&#xff09;1.3 输出比较通道&#xff08;高级&#xff09;1.4 输出比较通道&#xff08;通用&#xff09;1.5 输出比较模式1.6 PWM基本结…