进击J6:ResNeXt-50实战
- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊
一、实验目的:
- 阅读ResNeXt论文,了解作者的构建思路
- 对比之前介绍的ResNet50V2、DenseNet算法
- 使用ResNeXt-50算法完成猴痘病识别
二、实验环境:
- 语言环境:python 3.8
- 编译器:Jupyter notebook
- 深度学习环境:Pytorch
- torch==2.4.0+cu124
- torchvision==0.19.0+cu124
三、模型介绍
ResNeXt是由何凯明团队在2017年CVPR会议上提出来的新型图像分类网络。ResNeXt是ResNet的升级版,在ResNet的基础上,引入了cardinality的概念,类似于ResNet,ResNeXt也有ResNeXt-50,ResNeXt-101的版本。
ResNeXt论文原文:Aggregated Residual Transformations for Deep Neural Networks。
这篇文章介绍了一种用于图像分类的简单而有效的网络架构,该网络采用了VGG/ResNets的策略,通过重复层来增加深度和宽度,并利用分裂-变换-合并策略以易于扩展的方式进行转换。文章还提出了一个新的维度——“基数”,它是指转换集合的大小,可以在保持复杂性不变的情况下提高分类准确性。作者在ImageNet-1K数据集上进行了实证研究,证明了这种方法的有效性。
下图是ResNet(左)与ResNeXt(右)block的差异。在ResNet中,输入的具有256个通道的特征经过1×1卷积压缩4倍到64个通道,之后3×3的卷积核用于处理特征,经1×1卷积扩大通道数与原特征残差连接后输出。
ResNeXt也是相同的处理策略,但在ResNeXt中,输入的具有256个通道的特征被分为32个组,每组被压缩64倍到4个通道后进行处理。32个组相加后与原特征残差连接后输出。这里cardinatity指的是一个block中所具有的相同分支的数目。
分组卷积
ResNeXt中采用的分组卷机简单来说就是将特征图分为不同的组,再对每组特征图分别进行卷积,这个操作可以有效的降低计算量。
在分组卷积中,每个卷积核只处理部分通道,比如下图中,红色卷积核只处理红色的通道,绿色卷积核只处理绿色通道,黄色卷积核只处理黄色通道。此时每个卷积核有2个通道,每个卷积核生成一张特征图。
总结:ResNeXt-50网络简单讲就是在ResNet结构的基础上采用了聚合残差结构和局部连接结构,同时引入了Random Erasing和Mixup等数据增强和正则化方法。
- Random Erasing是一种数据增强技术,随机删除图像中的一些像素,并用随机值填充,从而增强模型的泛化性能。该技术可以防止模型过分关注图像中的一些细节和特定的区域,从而更好地适应新的数据。此外,Random Erasing还可以增加数据集的多样性,从而降低过拟合的风险。
- Mixup则是一种数据增强和正则化技术,将两张图像的像素按比例混合,生成一张新的图像作为输入,从而提高模型的鲁棒性和泛化性能。Mixup的基本思想是在训练过程中使用凸组合的方法,将输入的不同样本进行线性组合,从而生成一些新的数据样本。这种方法可以有效地增加数据集的多样性,从而提高模型的泛化性能。此外,Mixup还可以作为一种正则化技术,可以降低模型的过拟合风险。
四、使用Pytorch实现ResNeXt-50
设置GPU、导入数据、划分数据集等步骤同前。
1. 构建模型
class Bottleneck(nn.Module):expansion = 4def __init__(self, in_channel, out_channel, stride=1, downsample=None,groups=1, width_per_group=64):super(Bottleneck, self).__init__()width = int(out_channel * (width_per_group / 64.)) * groupsself.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=width,kernel_size=1, stride=1, bias=False) # squeeze channelsself.bn1 = nn.BatchNorm2d(width)# -----------------------------------------self.conv2 = nn.Conv2d(in_channels=width, out_channels=width, groups=groups,kernel_size=3, stride=stride, bias=False, padding=1)self.bn2 = nn.BatchNorm2d(width)# -----------------------------------------self.conv3 = nn.Conv2d(in_channels=width, out_channels=out_channel*self.expansion,kernel_size=1, stride=1, bias=False) # unsqueeze channelsself.bn3 = nn.BatchNorm2d(out_channel*self.expansion)self.relu = nn.ReLU(inplace=True)self.downsample = downsampledef forward(self, x):identity = xif self.downsample is not None:identity = self.downsample(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)out += identityout = self.relu(out)return out
class ResNet(nn.Module):def __init__(self,block,blocks_num,num_classes=1000,include_top=True,groups=1,width_per_group=64):super(ResNet, self).__init__()self.include_top = include_topself.in_channel = 64self.groups = groupsself.width_per_group = width_per_groupself.conv1 = nn.Conv2d(3, self.in_channel, kernel_size=7, stride=2,padding=3, bias=False)self.bn1 = nn.BatchNorm2d(self.in_channel)self.relu = nn.ReLU(inplace=True)self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)self.layer1 = self._make_layer(block, 64, blocks_num[0])self.layer2 = self._make_layer(block, 128, blocks_num[1], stride=2)self.layer3 = self._make_layer(block, 256, blocks_num[2], stride=2)self.layer4 = self._make_layer(block, 512, blocks_num[3], stride=2)if self.include_top:self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) # output size = (1, 1)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')def _make_layer(self, block, channel, block_num, stride=1):downsample = Noneif stride != 1 or self.in_channel != channel * block.expansion:downsample = nn.Sequential(nn.Conv2d(self.in_channel, channel * block.expansion, kernel_size=1, stride=stride, bias=False),nn.BatchNorm2d(channel * block.expansion))layers = []layers.append(block(self.in_channel,channel,downsample=downsample,stride=stride,groups=self.groups,width_per_group=self.width_per_group))self.in_channel = channel * block.expansionfor _ in range(1, block_num):layers.append(block(self.in_channel,channel,groups=self.groups,width_per_group=self.width_per_group))return nn.Sequential(*layers)def forward(self, x):x = self.conv1(x)x = self.bn1(x)x = self.relu(x)x = self.maxpool(x)x = self.layer1(x)x = self.layer2(x)x = self.layer3(x)x = self.layer4(x)if self.include_top:x = self.avgpool(x)x = torch.flatten(x, 1)x = self.fc(x)return x
def resnext50_32x4d(num_classes=1000, include_top=True):# 预训练权重:https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pthgroups = 32width_per_group = 4return ResNet(Bottleneck, [3, 4, 6, 3],num_classes=num_classes,include_top=include_top,groups=groups,width_per_group=width_per_group)model = resnext50_32x4d(num_classes=4, include_top=True)
model.to(device)# 统计模型参数量以及其他指标
import torchsummary as summary
summary.summary(model,(3,224,224))
代码输出部分截图:
2. 编写训练与测试函数
# 编写训练函数
def train(dataloader, model, loss_fn, optimizer):size = len(dataloader.dataset)num_batches = len(dataloader)train_acc, train_loss = 0, 0for X, y in dataloader:X, y = X.to(device), y.to(device)pred = model(X)loss = loss_fn(pred, y)optimizer.zero_grad()loss.backward()optimizer.step()train_loss += loss.item()train_acc += (pred.argmax(1) == y).type(torch.float).sum().item()train_loss /= num_batchestrain_acc /= sizereturn train_acc, train_loss
# 编写测试函数
def test(dataloader, model, loss_fn):size = len(dataloader.dataset) # 测试集的大小num_batches = len(dataloader) # 批次数目, (size/batch_size,向上取整)test_loss, test_acc = 0, 0# 当不进行训练时,停止梯度更新,节省计算内存消耗with torch.no_grad():for imgs, target in dataloader:imgs, target = imgs.to(device), target.to(device)# 计算losstarget_pred = model(imgs)loss = loss_fn(target_pred, target)test_loss += loss.item()test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item()test_acc /= sizetest_loss /= num_batchesreturn test_acc, test_loss
3. 设置损失函数和学习率
import copyloss_fn = nn.CrossEntropyLoss()
learn_rate = 1e-4
opt = torch.optim.Adam(model.parameters(), lr=learn_rate)scheduler = torch.optim.lr_scheduler.StepLR(opt, step_size=1, gamma=0.9) # 定义学习率高度器epochs = 100 # 设置训练模型的最大轮数为100,但可能到不了100
patience = 10 # 早停的耐心值,即如果模型连续10个周期没有准确率提升,则跳出训练train_loss = []
train_acc = []
test_loss = []
test_acc = []
best_acc = 0 # 设置一个最佳的准确率,作为最佳模型的判别指标
no_improve_epoch = 0 # 用于跟踪准确率是否提升的计数器
epoch = 0 # 用于统计最终的训练模型的轮数,这里设置初始值为0;为绘图作准备,这里的绘图范围不是epochs = 100
4. 正式训练
# 开始训练
for epoch in range(epochs):model.train()epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, opt)model.eval()epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)if epoch_test_acc > best_acc:best_acc = epoch_test_accbest_model = copy.deepcopy(model)no_improve_epoch = 0 # 重置计数器# 保存最佳模型的检查点PATH = 'J6_best_model.pth'torch.save({'epoch': epoch,'model_state_dict': best_model.state_dict(),'optimizer_state_dict': opt.state_dict(),'loss': epoch_test_loss,}, PATH)else:no_improve_epoch += 1if no_improve_epoch >= patience:print(f"Early stop triggered at epoch {epoch + 1}")break # 早停train_acc.append(epoch_train_acc)train_loss.append(epoch_train_loss)test_acc.append(epoch_test_acc)test_loss.append(epoch_test_loss)scheduler.step() # 更新学习率lr = opt.state_dict()['param_groups'][0]['lr']template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%, Test_loss:{:.3f}, Lr:{:.2E}')print(template.format(epoch + 1, epoch_train_acc * 100, epoch_train_loss, epoch_test_acc * 100, epoch_test_loss, lr))
代码输出部分截图:
5. 结果可视化
# 结果可视化
# Loss与Accuracy图import matplotlib.pyplot as plt
# 隐藏警告
import warnings
warnings.filterwarnings("ignore") # 忽略警告信息
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
plt.rcParams['figure.dpi'] = 100 # 分辨率epochs_range = range(epoch)plt.figure(figsize=(12, 3))
plt.subplot(1, 2, 1)plt.plot(epochs_range, train_acc, label='Training Accuracy')
plt.plot(epochs_range, test_acc, label='Test Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_loss, label='Training Loss')
plt.plot(epochs_range, test_loss, label='Test Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
6. 预测
from PIL import Imageclasses = list(total_data.class_to_idx)def predict_one_image(image_path, model, transform, classes):test_img = Image.open(image_path).convert('RGB')plt.imshow(test_img) # 展示预测的图片test_img = transform(test_img)img = test_img.to(device).unsqueeze(0)model.eval()output = model(img)_, pred = torch.max(output, 1)pred_class = classes[pred]print(f'预测结果是:{pred_class}')import os
from pathlib import Path
import random#从所有的图片的随机选择一张图片image=[]
def image_path(data_dir):file_list=os.listdir(data_dir) #列出四个分类标签data_file_dir=file_list #从四个分类标签中随机选择一个data_dir=Path(data_dir)for i in data_file_dir:i=Path(i)image_file_path=data_dir.joinpath(i) #拼接路径data_file_paths=image_file_path.iterdir() #罗列文件夹的内容data_file_paths=list(data_file_paths) #要转换为列表image.append(data_file_paths)file=random.choice(image) #从所有的图像中随机选择一类file=random.choice(file) #从选择的类中随机选择一张图片return filedata_dir='./monkeypox_photos'
image_path=image_path(data_dir)# 预测训练集中的某张照片
predict_one_image(image_path=image_path,model=model,transform=train_transforms,classes=classes)
# 模型评估
# 将参数加载到model当中
best_model.load_state_dict(torch.load(PATH,map_location=device))
epoch_test_acc,epoch_test_loss=test(test_dl,best_model,loss_fn)
epoch_test_acc,epoch_test_loss
(0.8508158508158508, 0.39013327977487017)
总结
ResNeXt是在ResNet的网络架构上,使用类似于Inception的分治思想,即split-tranform-merge策略,将模块中的网络拆开分组,与Inception不同,每组的卷积核大小一致,这样其感受野一致,但由于每组的卷积核参数不同,提取的特征自然不同。然后将每组得到的特征进行concat操作后,再与原输入特征x或者经过卷积等处理(即进行非线性变换)的特征进行Add操作。这样做的好处是,在不增加参数复杂度的前提下提高准确率,同时还能提高超参数的数量。
另外,cardinality是基的意思,将数个通道特征进行分组,不同的特征组之间可以看作是由不同基组成的子空间,每个组的核虽然一样,但参数不同,在各自的子空间中学到的特征就多种多样,这点跟transformer中的Multi-head attention不谋而合(Multi-head attention allows the model to jointly attend to information from different representation subspaces.)而且分组进行特征提取,使得学到的特征冗余度降低,获取能起到正则化的作用。
ResNeXt-50与ResNet50V2、DenseNet的对比:
- 网络结构:
- ResNeXt-50:基于ResNet结构改进而来,采用聚合残差结构和局部连接结构。它通过重复构建块来构建,每个构建块聚合了一组具有相同拓扑结构的转换。引入了分组卷积的方法,可以将不同的通道分组处理,还使用了深度可分离卷积的方法进一步减少计算量。
- ResNet50V2:是ResNet系列中的经典模型,由50层卷积层、批量归一化、激活函数和池化层构成。引入了一种全新的残差块结构,即bottleneck结构,使得网络参数量大幅度降低,同时精度也有所提升。
- DenseNet:其特点是不同于传统的网络结构,每一层的输出不仅和前一层的输出有关,还和之前所有层的输出有关,这种密集连接的结构可以有效地缓解梯度消失和参数稀疏问题,提高了模型的泛化能力和精度。它由多个denseblock和transition层组成,denseblock内部采用密集连接,相邻denseblock之间通过transition层连接并降低特征图大小。
- 精度和计算量:
- ResNeXt-50:在相同的深度下具有更高的精度,并且在参数量和计算量上都显著降低。在较深的网络结构下,优势更加明显,可以达到更高的精度。
- ResNet50V2:能在保持较低参数量的同时,实现较高的精度。
- DenseNet:在参数和计算成本更少的情形下实现比ResNet更优的性能,通过特征在channel上的连接来实现特征重用,减少了网络的参数总量,但由于密集连接方式,计算量相对较大。
- 适用范围:
- ResNeXt-50:适用于各种图像分类任务,在对精度要求较高且计算资源相对充足的场景下表现良好。
- ResNet50V2:广泛适用于各种图像分类任务,尤其在对模型复杂度和精度有一定平衡要求的场景中应用较多。
- DenseNet:适用于对特征重用和模型紧凑性要求较高的任务,例如图像分类、目标检测等,但在计算资源有限的情况下,可能需要对其进行适当的调整或优化。
它们各自的优点和创新之处如下:
- ResNeXt-50:
- 优点:在不明显增加参数量的情况下提升了准确率,具有很好的可扩展性和可适应性,超参数数量相对较少,便于模型移植。
- 创新:提出aggregated residual transformations结构,利用分组卷积构建平行堆叠相同拓扑结构的blocks,代替原来ResNet的三层卷积的block;同时引入了cardinality的概念,即通过增加分组的数量(基数),可以在不增加模型复杂度的前提下提高性能,实验表明增加基数比增加深度或宽度更有效。
- ResNet50V2:
- 优点:通过改进残差结构,先进行批量归一化和激活函数计算后再卷积,并将addition后的ReLU计算放到残差结构内部,提高了模型的精度,同时降低了参数量。
- 创新:全新的残差块结构(bottleneck结构),减少了网络参数量,使得在保持较高精度的同时,模型更容易训练和优化,这种结构上的创新为后续许多网络的设计提供了借鉴思路。
- DenseNet:
- 优点:缓解了梯度消失问题,加强了特征传播,鼓励了特征复用,极大地减少了网络的参数总量,在参数较少的情况下能取得较好的性能,而且通过密集连接方式,提升了梯度的反向传播,使得网络更容易训练,对过拟合有一定的抑制作用。
- 创新:建立了前面所有层与后面层的密集连接机制,实现了特征重用,每个层都会与前面所有层在channel维度上连接并作为下一层的输入,这一创新的连接方式充分利用了特征信息,与传统的网络结构相比,在相同性能下可以减少参数数量,提高了模型的效率和泛化能力。此外,在denseblock中使用bottleneck层来减少计算量,以及在transition层采用特定的结构来处理特征图的尺寸匹配问题,也是其重要的创新点。
相关文章:

进击J6:ResNeXt-50实战
🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 一、实验目的: 阅读ResNeXt论文,了解作者的构建思路对比之前介绍的ResNet50V2、DenseNet算法使用ResNeXt-50算法完成猴痘病识别 二、实…...

新代机床采集数据
新代集團1995年成立於台灣新竹,事業版圖遍布全球,以台灣為中心向外發展,據點橫跨歐洲、美洲、亞洲三大洲。新代長期深耕於機床控制器的軟體及硬體技術研發,專注於運動控制領域,目前已成為亞太市場中深具影響力的控制器領導品牌之一。主營產品包括:機床數控系統、伺服驅動…...

景联文科技:专业数据标注公司,推动AI技术革新
数据标注作为AI技术发展的重要支撑,对于训练高质量的机器学习模型以及推动应用领域的创新具有不可替代的作用。 景联文科技作为专业的数据标注公司,致力于提供专业的数据标注服务,帮助客户解决AI链条中的数据处理难题,共同推动人工…...

k8s以及prometheus
#生成控制器文件并建立控制器 [rootk8s-master ~]# kubectl create deployment bwmis --image timinglee/myapp:v1 --replicas 2 --dry-runclient -o yaml > bwmis.yaml [rootk8s-master ~]# kubectl expose deployment bwmis --port 80 --target-port 80 --dry-runclient…...

android 权限说明
1. 权限的定义语法 注: 任何应用都可以定义权限 <permission 标签是定义权限 <uses-permission 标签是使用权限。 <permission android:description"string resource"android:icon"drawable resource"android:label"string res…...

<winsock>重叠IO模型
基于事件判断io完成 send程序 #include <stdio.h> #include <winsock2.h>#pragma comment(lib, "Ws2_32.lib") #pragma warning(disable : 4996)int main() {WSADATA wsaData;if (WSAStartup(MAKEWORD(2, 2), &wsaData) ! 0){printf("WSAStart…...

Android Tools | 如何使用Draw.io助力Android开发:从UI设计到流程优化
Android Tools | 如何使用Draw.io助力Android开发:从UI设计到流程优化 1. 引言 在Android开发中,视觉化设计与流程管理至关重要。虽然开发工具如Android Studio强大,但它并不适用于所有设计场景。Draw.io是一款免费的在线绘图工具ÿ…...

Java 每日一刊(第5期):变量守护者
前言 这里是分享 Java 相关内容的专刊,每日一更。 本期将为大家带来以下内容: 量子数据宇宙的变量守护者第一章:能源错配与基本数据类型第二章:引用类型与通讯网络的崩溃第三章:作用域冲突与系统崩溃终章࿱…...

【C++二分查找】2517. 礼盒的最大甜蜜度
本文涉及的基础知识点 C二分查找 贪心(决策包容性) LeetCode 2517. 礼盒的最大甜蜜度 给你一个正整数数组 price ,其中 price[i] 表示第 i 类糖果的价格,另给你一个正整数 k 。 商店组合 k 类 不同 糖果打包成礼盒出售。礼盒的 甜蜜度 是礼…...

【详解】数据库E-R图——医院计算机管理系统
题目 某医院病房计算机管理中需要如下信息: 科室:科室名,科室地址,科室电话,医生姓名 病房:病房号,床位号,所属科室名 医生:工作证号,姓名,性别&a…...

分类预测|基于改进的灰狼IGWO优化支持向量机SVM的数据分类预测matlab程序 改进策略:Cat混沌与高斯变异
分类预测|基于改进的灰狼IGWO优化支持向量机SVM的数据分类预测matlab程序 改进策略:Cat混沌与高斯变异 文章目录 一、基本原理原理流程1. **定义目标函数**2. **初始化GWO**3. **评估适应度**4. **更新狼的位置**5. **更新狼的等级**6. **重复迭代**7. **选择最佳解…...

圆锥曲线练习
设 A ( x 1 , y 1 ) , B ( x 2 , y 2 ) A\left( x_{1}, y_{1} \right), B\left( x_{2}, y_{2} \right) A(x1,y1),B(x2,y2) l : y k ( x 2 ) l: y k\left( x2 \right) l:yk(x2) 显然 y 0 y0 y0符合题意 当 k ≠ 0 k\neq 0 k0 联立 l l l和 C C C ( k 2 1 2 ) x…...

STM32时钟树
1 什么是时钟 2 时钟数简图...

NX—UI界面生成的文件在VS上的设置
UI界面保存生成的三个文件 打开VS创建项目,删除自动生成的cpp文件,将生成的hpp和cpp文件拷贝到项目的目录下,并且在VS项目中添加现有项目。 修改VS的输出路径,项目右键选择属性,链接器中的常规,文件路径D:…...

Wine容器内程序执行sh脚本问题研究
问题背景 wpf程序在wine环境执行sh脚本,不能等待脚本执行完成自动退出的问题进行了研究,需求很简单,在wpf程序使用cmd,或者bat ,又或者是直接执行sh脚本,想到脚本执行完成才处理后面的逻辑。但是实际验证过…...

《深度学习》OpenCV轮廓检测 模版匹配 解析及实现
目录 一、模型匹配 1、什么是模型匹配 2、步骤 1)提取模型的特征 2)在图像中查找特征点 3)进行特征匹配 4)模型匹配 3、参数及用法 1、用法 2、参数 1)image:待搜索对象 2)templ&am…...

Java XML
1、XML文件介绍 配置文件:用来保存设置的一些东西。 拿IDEA来举例,比如设置的背景图片,字体信息,字号信息和主题信息等等。 (1)以前是用txt保存的,没有任何优点,而且不利于阅读&a…...

好用的视频压缩工具有哪些?这4款千万不要错过
视频压缩的方法有很多种,像我们手机里的视频剪辑工具,手机和电脑自带的压缩功能,在线压缩网站,专业压缩软件压缩等等。不同的场景和需求下大家可以选择不同的工具,但是如果碰到需要大量和经常压缩视频的话,…...

【Python爬虫系列】_016.关于登录和验证码
我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈 入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈 虚 拟 环 境 搭 建 :👉&…...

基于opencv实现双目立体匹配点云距离
双目相机或两个单目相机。 一、相机标定 MATLAB软件,打开双目标定app。 点击add images,弹出加载图像的窗口,分别导入左图和右图,设置黑白格长度(标定板的长度一般为20)。 点击确定,弹出加载…...

RabbitMQ高级篇,进阶内容
强烈建议在看本篇博客之前快速浏览文章:RabbitMQ基础有这一篇就够了 RabbitMQ高级篇 0. 前言1. 发送者的可靠性1.1 生产者重试机制1.2 生产者确认机制1.3 实现生产者确认 2. MQ的可靠性2.1 MQ持久化2.2 LazyQueue 3. 消费者的可靠性3.1 消费者确认机制3.2 失败重试策…...

STM32重定义printf,实现串口打印
在“usart.c”文件中加入以下代码 #ifdef __GNUC__#define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #else#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #endifPUTCHAR_PROTOTYPE{HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1, 0xFFFF);return ch; }…...

项目进度
变为负进度了,还是要用baseservlet,我就又重新写了一部分,看了好几遍视频,突然就想明白了,感觉每次要上课,就时间不连续思路总是断,今天晚自习算是搞懂了怎么写了,就是代码有点多&am…...

Android的内核
Android的内核是基于Linux的长期支持版本的“Android通用内核(ACK)”。 Android作为一个广泛使用的操作系统,其根基在于内核的设计和功能。下面将深入探讨Android内核的各个方面,从其基本结构到与Linux内核的关系,再到内核的版本管理及在设备…...

Github Wiki 超链接 转 码云Gitee Wiki 超链接
Github Wiki 超链接 转 码云Gitee Wiki 超链接 Github 是 :[[相对路径]] Gitee 是 :[链接文字](./相对路径) 查找:\[\[(.*?)\]\] 替换:[$1]\(./$1\) 或替换:**[$1]\(./$1\)** (码云的超链接,很…...

Android10源码刷入Pixel2以及整合GMS
一、ASOP源码下载 具体可以参考我之前发布的文章 二、下载相关驱动包 这一步很关键,关系到编译后的镜像能否刷入后运行 下载链接:Nexus 和 Pixel 设备的驱动程序二进制文件 如下图所示,将两个驱动程序上传到Ubuntu服务器,并进行解压,得到两个脚本: 下载解压后会有两…...

wpf触发与模板的使用示例:批量生产工具
批量生产工具 <Window x:Class"WpfM20UpdateFW.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schemas.microsoft.com/expressio…...

brew install node提示:Error: No such keg: /usr/local/Cellar/node
打开本地文件发现Cellar目录下无法生成 node文件,应该是下载时出现问题,重复下载无法解决问题,只能重新安装brew。 步骤1(安装 brew): /bin/zsh -c “$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/ra…...

记录一下gitlab社区版的安装教程
目录 1.更新系统软件包 2.安装必要的依赖 3.添加GitLab源 3.1对于GitLab Enterprise Edition(EE): 3.2对于GitLab Community Edition(CE): 4.安装GitLab 4.1安装GitLab Enterprise Edition(E…...

20. 如何在MyBatis中处理多表关联查询?常见的实现方式有哪些?
在MyBatis中处理多表关联查询是一项常见的需求,特别是在关系型数据库中存储复杂的实体关系时。MyBatis提供了多种方式来实现多表关联查询,常见的实现方式包括使用<association>和<collection>标签在<resultMap>中进行对象关系映射&…...