目录

一、项目简介

二、模型训练+验证+保存

三、模型测试+保存csv文件

四、单张图片预测

五、模型评估

六、ONNX导出

七、ONNX推理

八、网络结构与数据增强可视化


上篇我介绍了具体步骤,今天就以我实际开发的一个具体项目来讲:

一、项目简介

  苯人的项目是基于CNN实现香蕉成熟度的小颗粒度分类,针对六种不同状态(新鲜成熟的、新鲜未熟的、成熟的、腐烂的、过于成熟的、生的)进行高精度视觉识别。由于香蕉的成熟度变化主要体现在颜色渐变、斑点分布及表皮纹理等细微差异上,传统图像处理方法难以准确区分。因此,本项目通过构建深层CNN模型,利用卷积层的局部特征提取能力捕捉香蕉表皮的细微变化,并结合高阶特征融合技术提升分类精度。

数据集长这样:

苯人是在 https://universe.roboflow.com/ 这个网站上下载的,kaggle我自己觉得不好用(其实是看不来),总之数据集有了,再说一嘴苯人是引用的 ResNet18网络模型,接下来就开始写代码吧:

二、模型训练+验证+保存

这里我就不像上篇那样这么详细了,主要是看流程:

import torch
import torch.nn as nn
from torchvision import transforms
from torchvision.datasets import ImageFolder
from torch.utils.data import DataLoader
from torchvision.models import resnet18 #导入网络结构
from torch import optim# 模型保存
last_model_path = './model/last.pth'
best_model_path = './model/best.pth'#数据预处理
train_transforms = transforms.Compose([transforms.Resize((256, 256)),                # 先稍微放大点transforms.RandomCrop(224),                   # 随机裁剪出 224x224transforms.RandomHorizontalFlip(p=0.5),       # 左右翻转transforms.RandomRotation(degrees=15),        # 随机旋转 ±15°transforms.ColorJitter(brightness=0.2,        # 明亮度contrast=0.2,          # 对比度saturation=0.2,        # 饱和度hue=0.1),              # 色调transforms.ToTensor(),transforms.Normalize(mean=[0.485, 0.456, 0.406],   # ImageNet均值std=[0.229, 0.224, 0.225])    # ImageNet标准差
])
val_transforms = transforms.Compose([transforms.Resize((224, 224)),transforms.ToTensor(),transforms.Normalize(mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225])
])#加载数据集
train_dataset = ImageFolder(root='./Bananas/train', transform= train_transforms)
valid_dataset = ImageFolder(root='./Bananas/valid', transform= val_transforms)#数据加载器
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
valid_loader = DataLoader(valid_dataset, batch_size=64, shuffle=False)#迁移模型结构
model = resnet18(pretrained = True)
in_features = model.fc.in_features #动态获得输入
model.fc = nn.Linear(in_features, 6) #改成6分类
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)# 优化:解冻最后两层和fc层(更有学习能力)
for name, param in model.named_parameters():if "layer4" in name or "fc" in name:param.requires_grad = Trueelse:param.requires_grad = False#再用 filter 筛选需要梯度更新的参数
param_grad_true = filter(lambda x:x.requires_grad, model.parameters())#实例化损失函数对象
criterion = nn.CrossEntropyLoss()
#优化器 这里使用AdamW
optimizer = optim.AdamW(param_grad_true, lr=1e-3, weight_decay=0.01)
# 优化:添加学习率调度器
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, 'max', patience=5, factor=0.5, verbose=True)#开始训练+每10个轮次验证一次
def train(model, train_loader, valid_loader, epochs, validate_every=10):import os# 创建模型保存目录os.makedirs('./model', exist_ok=True)model.train()best_val_acc = 0 #初始化最优准确率# 优化:增加早停机制early_stopping_patience = 10no_improve_epochs = 0for epoch in range(epochs):running_loss = 0 #初始化每轮训练的损失correct = 0 #初始化正确数与总个数total = 0for images, labels in train_loader:images, labels = images.to(device), labels.to(device)output = model(images) #得到预测值loss = criterion(output, labels) #计算损失optimizer.zero_grad() #梯度清零loss.backward() #反向传播optimizer.step() #根据梯度更新参数running_loss += loss.item() #当前epoch的总损失pred = torch.argmax(output, dim=1) #拿到当前图片预测是最大值的索引下标当做类别total += labels.size(0)correct += (pred == labels).sum().item()train_acc = correct/total * 100 #训练准确率print(f"[Epoch {epoch + 1}/{epochs}] Loss: {running_loss:.4f}, Accuracy: {train_acc:.2f}%")#验证部分if (epoch + 1) % validate_every == 0: #每10轮验证一次val_loss = 0val_total = 0val_correct = 0model.eval()with torch.no_grad(): #逻辑与训练函数差不多for val_images, val_labels in valid_loader:val_images, val_labels = val_images.to(device), val_labels.to(device)val_output = model(val_images)val_loss += (criterion(val_output, val_labels)).item()val_pred = torch.argmax(val_output, dim=1)val_total += val_labels.size(0)val_correct += (val_pred == val_labels).sum().item()val_acc = val_correct/val_total *100 #验证准确率# 优化:根据验证准确率调整学习率scheduler.step(val_acc)print(f"[Epoch {epoch + 1}/{epochs}] Loss: {running_loss:.4f}, Accuracy: {train_acc:.2f}%")#保存最优模型参数if val_acc > best_val_acc:best_val_acc = val_acctorch.save(model.state_dict(), best_model_path)print(f"保存了当前最优模型,验证正确率:{val_acc:.2f}%")# 优化:早停法else:no_improve_epochs += 1if no_improve_epochs >= early_stopping_patience:print(f"验证准确率连续{early_stopping_patience}轮没有提升,提前停止训练")break# 保存最近一次模型参数torch.save(model.state_dict(), last_model_path)model.train()train(model, train_loader, valid_loader, epochs=50) #训练50次看看

主要逻辑还是像上篇那样:数据预处理-->加载数据集-->数据加载器-->迁移模型结构-->改变全连接层-->配置训练细节(损失优化)-->训练函数-->每10轮训练后验证一次-->保存最近一次训练模型参数以及最优模型参数

改全连接层那里说一下,因为我做的是六分类,原来的模型结构是千分类,所以要把 out_features 改成6,同时冻结其他层只训练全连接层就好,但是因为第一次训练的效果不是很好,所以在优化的时候我又解冻了最后两层,增加了学习能力;另外还有优化就是对学习率,我增加了一个学习率调度器,动态学习率对模型来说效果更好;最后一个优化是增加了早停机制,即在验证准确率连续多少轮没有提升时自动停止训练,这样大大节省了训练时间

运行结果我就不贴了因为我搞忘截图了。。反正最后一轮准确率有98%,模型参数也保存了:
 

三、模型测试+保存csv文件

import torch
import os
import torch.nn as nn
from torchvision import transforms
from torchvision.datasets import ImageFolder
from torch.utils.data import DataLoader
from torchvision.models import resnet18
import numpy as np
import pandas as pd#最优模型参数路径
best_model_path = './model/best.pth'#数据预处理
val_transforms = transforms.Compose([transforms.Resize((224, 224)),transforms.ToTensor(),transforms.Normalize(mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225])
])#准备测试数据集
test_dataset = ImageFolder(root='./Bananas/test', transform=val_transforms)#数据加载器
test_load = DataLoader(test_dataset, batch_size=64, shuffle=False)#导入模型结构
model = resnet18(pretrained = False) #不用加载自带的参数
in_features = model.fc.in_features #同样动态接受输入特征
model.fc = nn.Linear(in_features, 6) #同样更改模型结构device = torch.device("cuda" if torch.cuda.is_available() else "cpu")#加载之前保存的最优模型参数
model.load_state_dict(torch.load(best_model_path, map_location = device))
model.to(device)#开始测试
model.eval()
correct = 0
total = 0
with torch.no_grad():for images, labels in test_load:images, labels = images.to(device), labels.to(device)out = model(images) #得到预测值pred = torch.argmax(out, dim=1)correct += (pred == labels).sum().item()total += labels.size(0)
test_acc = correct / total *100
print(f'测试集测试的准确率为:{test_acc:.2f}%')valid_dataset = ImageFolder(root='./Bananas/valid', transform= val_transforms)
valid_loader = DataLoader(valid_dataset, batch_size=64, shuffle=False)
classNames = valid_dataset.classes #拿到类名model.eval()
acc_total = 0
val_dataloader = DataLoader(valid_dataset, batch_size=64, shuffle=False)
total_data = np.empty((0,8))
with torch.no_grad():# 每个批次for x, y in val_dataloader:x = x.to(device)y = y.to(device)out = model(x)# [10,3]pred = torch.detach(out).cpu().numpy()# [10,]p1 = torch.argmax(out, dim=1)# 转化为numpyp2 = p1.unsqueeze(dim=1).detach().cpu().numpy()label = y.unsqueeze(dim=1).detach().cpu().numpy()batch_data = np.concatenate([pred, p2, label],axis=1)total_data = np.concatenate([total_data, batch_data], axis=0)# 构建csv文件的第一行(列名)
pd_columns = [*classNames, 'pred', 'label']os.makedirs('./results', exist_ok=True)
csv_path = os.path.relpath(os.path.join(os.path.dirname(__file__), 'results', 'number.csv'))pd.DataFrame(total_data, columns=pd_columns).to_csv(csv_path, index=False)
print("成功保存csv文件!")

运行结果:

测试集的准确率也有这么高说明没有过拟合,我们可以打开csv文件看一下:
 

预测的准确率还是挺高的,这里也说明一下测试的时候是加载之前训练保存的模型参数,所以

model = resnet18(pretrained = False)  这里的参数填false,然后再加载保存的模型参数:

model.load_state_dict(torch.load(best_model_path, map_location = device)).

四、单张图片预测

这里我们可以从网上找几张图片来预测一下:

import torch
import torch.nn as nn
from PIL import Image
from torchvision import transforms
from torchvision.datasets import ImageFolder
from torchvision.models import resnet18#最优模型参数路径
best_model_path = './model/best.pth'
10
#数据预处理
val_transforms = transforms.Compose([transforms.Resize((224, 224)),transforms.ToTensor(),transforms.Normalize(mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225])
])
train_transforms = transforms.Compose([transforms.Resize((256, 256)),                # 先稍微放大点transforms.RandomCrop(224),                   # 随机裁剪出 224x224transforms.RandomHorizontalFlip(p=0.5),       # 左右翻转transforms.RandomRotation(degrees=15),        # 随机旋转 ±15°transforms.ColorJitter(brightness=0.2,        # 明亮度contrast=0.2,          # 对比度saturation=0.2,        # 饱和度hue=0.1),              # 色调transforms.ToTensor(),transforms.Normalize(mean=[0.485, 0.456, 0.406],   # ImageNet均值std=[0.229, 0.224, 0.225])    # ImageNet标准差
])#加载图片
img_path = 'images/b8_rotten.jpg'
img = Image.open(img_path).convert('RGB')  # 确保是RGB三通道
img = val_transforms(img)  # 应用transform
img = img.unsqueeze(0)  # 加上 batch 维度#导入模型结构
model = resnet18(pretrained = False) #不用加载自带的参数
in_features = model.fc.in_features #同样动态接受输入特征
model.fc = nn.Linear(in_features, 6) #同样更改模型结构device = torch.device("cuda" if torch.cuda.is_available() else "cpu")#加载之前保存的最优模型参数
model.load_state_dict(torch.load(best_model_path, map_location = device))
model.to(device)#模型预测
model.eval()
with torch.no_grad():output = model(img)pred_class = torch.argmax(output, dim=1).item()train_dataset = ImageFolder(root='./Bananas/train', transform= train_transforms)
idx_to_class = {v: k for k, v in train_dataset.class_to_idx.items()}
pred_label = idx_to_class[pred_class]
print(f"模型预测这张图片是:{pred_label}")

运行结果:

注意要记得给原图片升维,因为要求传入的图片形状是(N, C, H, W)

五、模型评估

在CNN项目中,对模型评估的指标(准确率、召回率、F1等)应该基于测试集的结果进行最终评估,因为模型在测试集上的表现是最接近于真实情况的:

import pandas as pd
import os
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
import matplotlib
import matplotlib.pyplot as plt#设置中文字体
matplotlib.rcParams['font.sans-serif'] = ['SimHei']
matplotlib.rcParams['axes.unicode_minus'] = Falsecsv_path = os.path.relpath(os.path.join(os.path.dirname(__file__), 'results', 'number.csv'))
# 读取CSV数据
csvdata = pd.read_csv(csv_path, index_col=0)
# 拿到真实标签
true_label = csvdata["label"].values
# 拿到预测标签
true_pred = csvdata["pred"].values# 根据预测值和真实值生成分类报告
report = classification_report(y_true=true_label, y_pred=true_pred)
print(report)# 混淆矩阵可视化
cm = confusion_matrix(true_label, true_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=[str(i) for i in range(6)])
disp.plot(cmap='Greens', values_format='d')
plt.title("训练结果混淆矩阵视图")
plt.tight_layout()
plt.savefig("confusion_matrix.png")
plt.show()

运行结果:

可以看到,f1分数比较高,混淆矩阵的对角线数字也很大,说明模型表现良好。

六、ONNX导出

导出为ONNX格式主要是它兼容性很高,且可以被专用推理引擎优化,减少计算开销,代码如下:

import torch
from torchvision.models import resnet18
import torch.nn as nnbest_model_path = './model/best.pth'
onnx_path = './model/best.onnx' #保存路径#加载模型结构与权重参数
model = resnet18(pretrained = False)
in_features = model.fc.in_features
model.fc = nn.Linear(in_features, 6) #同样修改全连接层device = torch.device("cuda" if torch.cuda.is_available() else "cpu" )
model.load_state_dict(torch.load(best_model_path, map_location=device))#创建实例输入
x = torch.randn(1, 3, 224, 224)
out = model(x)
# print(out.shape) #确认输出不是None torch.Size([1, 6])#导出onnx
model.eval()
torch.onnx.export(model, x, onnx_path, verbose=False, input_names=["input"], output_names=["output"])
print("onnx导出成功!")import onnx
onnx_model = onnx.load(onnx_path)
onnx.checker.check_model(onnx_model)
print("onnx模型检查通过!")

 导出后我们可以通过这个网站来可视化一下:Netron,打开刚刚保存的ONNX文件:

然后就可以看到网络结构了,这里我只截一部分:

七、ONNX推理

代码如下:

from torchvision.datasets import ImageFolder
from torch.utils.data import DataLoader
from torchvision import transforms
from PIL import Image
import onnxruntime as ort
import torch#数据预处理
val_transforms = transforms.Compose([transforms.Resize((224, 224)),transforms.ToTensor(),transforms.Normalize(mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225])
])#加载路径
img_path = './images/b8_rotten.jpg'
onnx_path = './model/best.onnx'#加载并处理图片
img = Image.open(img_path).convert("RGB")
img_tensor = val_transforms(img) #经过数据预处理后转为了tensor
img_np = img_tensor.unsqueeze(0).numpy()
# print(img_tensor.shape) torch.Size([3, 32, 32])#加载onnx模型
sess = ort.InferenceSession(onnx_path)
out = sess.run(None, {"input": img_np})
# print(out)
# [array([[-6.8998175, -8.683616 , -5.1299562, -2.8295422,  8.335733 ,
#         -5.098113 ]], dtype=float32)]#后处理
valid_dataset = ImageFolder(root='./Bananas/valid', transform= val_transforms)
valid_loader = DataLoader(valid_dataset, batch_size=64, shuffle=False)
classNames = valid_dataset.classes #拿到类名
# print(classNames)
# ['freshripe', 'freshunripe', 'overripe', 'ripe', 'rotten', 'unripe']logits = out[0]
#用softmax函数将结果转成0-1之间的概率
probs = torch.nn.functional.softmax(torch.tensor(logits), dim=1)
pred_index = torch.argmax(probs).item()
pred_label = classNames[pred_index]print(f"\n 预测类别为:{pred_label}")
print("各类别概率:")
for i, cls in enumerate(classNames):print(f"{cls}: {probs[0][i]:.2%}")

注意传入ONNX模型的必须是numpy数组。

运行结果:
 

其实感觉预测得有点绝对,但是这个模型的准确率这么高我也是没想到

八、网络结构与数据增强可视化

如果想要更直观地看到训练变化的话可以加这一步:

import torch
from torch.utils.tensorboard import SummaryWriter
from torchvision.utils import make_grid
from torchvision import transforms
from torchvision.datasets import ImageFolder
from torchvision.models import resnet18
import torch.nn as nn# 可视化配置
writer = SummaryWriter("runs/501_tensorboard")# 网络结构可视化
print("添加网络结构图")
model = resnet18()
model.fc = nn.Linear(model.fc.in_features, 6) 
input = torch.randn(1, 3, 224, 224)  # ResNet18的输入尺寸
writer.add_graph(model, input)# 数据增强效果可视化
print("添加数据增强图像")
# 数据增强方式
train_transforms = transforms.Compose([transforms.Resize((256, 256)),                # 先稍微放大点transforms.RandomCrop(224),                   # 随机裁剪出 224x224transforms.RandomHorizontalFlip(p=0.5),       # 左右翻转transforms.RandomRotation(degrees=15),        # 随机旋转 ±15°transforms.ColorJitter(brightness=0.2,        # 明亮度contrast=0.2,          # 对比度saturation=0.2,        # 饱和度hue=0.1),              # 色调transforms.ToTensor(),transforms.Normalize(mean=[0.485, 0.456, 0.406],   # ImageNet均值std=[0.229, 0.224, 0.225])    # ImageNet标准差
])# 加载训练数据集
train_dataset = ImageFolder(root='./Bananas/train', transform= train_transforms)# 写入3轮不同的数据增强图像
for step in range(3):imgs = torch.stack([train_dataset[i][0] for i in range(64)])  # 取64张图grid = make_grid(imgs, nrow=8, normalize=True)writer.add_image(f"augmented_mnist_step_{step}", grid, global_step=step)writer.close()
print("所有可视化完成!")

运行代码后在终端输入 tensorboard --logdir=runs,回车后可以看到生成了一个网址,用浏览器直接访问即可,如果不行的话就在 runs 后面加当前文件的绝对路径,苯人的可视化是这样:

数据增强可视化: 

最后,我整个的项目文件夹长这样:

对上篇的补充就到此为止,下一篇写啥也没想好,前面拖得太多了。。

以上有问题可以指出(๑•̀ㅂ•́)و✧

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

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

相关文章

《AR眼镜上声学的应用与挑战》

《2025GAS声学大讲堂—音频产业创新技术公益讲座》智能眼镜专题讲座第3讲将于7月24日周四19点开讲,本次邀请了 珠海莫界科技有限公司 高级算法工程师 胡立天 演讲,讲座主题:《AR眼镜上声学的应用与挑战》(点击观看直播&#xff09…

编译支持cuda硬件加速的ffmpeg

本来以为很简单,因为印象中自己在windows机器上使用过。 目前的实在一个docker环境下的ubuntu系统里。 官方操作文档 按照官方操作文档Using FFmpeg with NVIDIA GPU Hardware Acceleration - NVIDIA Docs的描述,步骤很简单: 1、安装nv-c…

在资源受限单片机中使用printf等可变参函数时的陷阱(2025年7月22日)

今天分享一个我最近在项目调试中遇到的“大坑”,这个坑来自一个我们既熟悉又依赖的朋友——printf函数。故事的主角,是一颗资源极其有限的STM32F030单片机,它只有区区4KB的RAM。 一切始于便利 项目初期,为了能方便地监控程序运行状…

大数据之Hive:Hive中week相关的几个函数

目录1.dayofweek函数2.weekday函数3.weekofyear函数1.dayofweek函数 功能:统计某天为星期几 dayofweek(date) - Returns the day of the week for date/timestamp (1 Sunday, 2 Monday, ..., 7 Saturday).dayofweek返回值为:1-7,1 星期…

基于深度学习Transform的steam游戏特征分析与可视化【词云-情感词典分析-主题分析-词频分析-关联分析】

文章目录有需要本项目的代码或文档以及全部资源,或者部署调试可以私信博主一、项目背景与研究意义二、研究目标三、研究方法与实施流程第一阶段:数据采集与预处理第二阶段:多维度数据分析第三阶段:综合分析与策略建议输出四、预期…

Qwen3-8B 与 ChatGPT-4o Mini 的 TTFT 性能对比与底层原理详解

一、模型概述与上下文支持能力 1.1 Qwen3-8B 的技术特点 Qwen3-8B 是通义实验室推出的 80 亿参数大语言模型,支持 32,768 token 的上下文长度 。其核心优化点包括: FP8 量化技术:通过将权重从 32-bit 压缩至 8-bit,显著降低显存…

recvmsg函数的用法

recvmsg 是 Linux 网络编程中用于接收消息的高级系统调用&#xff0c;支持复杂数据结构和辅助数据的接收&#xff0c;适用于 TCP/UDP/UNIX 域套接字等场景‌。以下是其核心用法详解&#xff1a;‌1. 函数原型与参数‌#include <sys/socket.h> ssize_t recvmsg(int sockfd…

24GSPS高速DA FMC子卡

单通道 16bit 12GSPS/ 12bit 15.5GSPS/ 8bit 24GSPS双通道 16bit 6.2GSPS/ 12bit 7.75GSPS/ 8bit 12GS/sDAC FMC子卡基于TI公司的高速DAC数模转换器DAC39RF12ACK和时钟芯片LMX2594而设计的标准单槽位的FMC子卡。支持单通道模式或双通道模式&#xff0c;单通道模式下提供16bit 1…

LabVIEW动态调用VI

该组LabVIEW程序演示4 种动态调用 VI 的实现方案&#xff0c;围绕 HTTP GET 任务&#xff08;通过 URL 抓取数据&#xff09;&#xff0c;利用不同调用逻辑&#xff0c;适配多场景下的并行 / 串行执行需求&#xff0c;助力工程师灵活构建异步、并行化程序。各方案说明&#xff…

安装单机版本Redis

部署操作:步骤一: 安装Redis服务# 安装redis操作 dnf install redis -y步骤二&#xff1a; 修改Redis相关配置vim /etc/redis/redis.conf # 83行附件&#xff0c; 修改为 * -::* 任意的服务都可以连接redis服务 bind * -::*#908行附近&#xff1a; 打开requirepass&#xff…

Java(Set接口和HashSet的分析)

Set 接口基本介绍:注意:取出的顺序的顺序虽然不是添加的顺序&#xff0c;但是他的固定set接口的常用方法:和 List 接口一样, Set 接口也是 Collection 的子接口&#xff0c;因此&#xff0c;常用方法和 Collection 接口一样.set的遍历方式:HashSet的全面说明:HashSet的畅通方法…

vscode不识别vsix结尾的插件怎么解决?

当VS Code无法识别.vsix文件时&#xff0c;可能是由于文件损坏、版本不兼容或安装流程不正确导致的。以下是解决此问题的详细步骤&#xff1a; 1. 确认文件完整性 重新下载.vsix文件&#xff1a;删除现有文件&#xff0c;从可靠来源重新下载&#xff0c;确保下载过程未中断。检…

面试题:sql题一

SELECTp.product_id, -- 产品IDp.product_name, -- 产品名称SUM(s.sale_qty * s.unit_price) AS sum_price, -- 年销售总价YEAR(s.sale_date) AS year_date -- 销售年份 FROM products p JOIN sales s ON p.product_id s.produ…

【React-Three-Fiber实践】放弃Shader!用顶点颜色实现高性能3D可视化

在现代前端开发中&#xff0c;3D可视化已经成为提升用户体验的重要手段。然而&#xff0c;许多开发者在实现复杂视觉效果时&#xff0c;往往会首先想到使用Shader&#xff08;着色器&#xff09;。虽然Shader功能强大&#xff0c;但学习曲线陡峭&#xff0c;实现复杂度高。本文…

MSTP技术

一、STP/RSTP 的局限性STP&#xff08;生成树协议&#xff09;和 RSTP&#xff08;快速生成树协议&#xff09;存在一些明显的局限&#xff0c;主要包括&#xff1a;所有 VLAN 共享一颗生成树&#xff0c;这导致无法实现不同 VLAN 在多条 Trunk 链路上的负载分担。例如&#xf…

[IMX][UBoot] 16.Linux 内核移植

目录 1.修改 Makefile 2.新增配置文件 3.新增设备树文件 4.新建编译脚本 5.修改 CPU 频率 6.EMMC 适配 7.网络驱动适配 1.修改 Makefile 修改顶层 Makefile 中的架构信息 ARCH 和交叉编译器 CROSS_COMPILE&#xff0c;修改后不需要在执行 make 时手动指定这两个变量的值…

数据库 × 缓存双写策略深度剖析:一致性如何保障?

前言 缓存&#xff0c;几乎是现在互联网项目中最常见的一种加速工具了。 通过缓存&#xff0c;我们能大幅提升接口响应速度&#xff0c;减少数据库的访问压力&#xff0c;还能支撑各种复杂的业务功能&#xff0c;比如排行榜、风控系统、黑名单校验等等。 不管你用的是本地缓存…

主流Java Redis客户端深度对比:Jedis、Lettuce与Redisson性能特性全解析

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 持续学习&#xff0c;不断…

AI问答系统完整架构规划文档

📋 目录 现有代码架构分析 AI核心组件缺口分析 完整技术架构设计 开发路线图 技术实现要点 🏗️ 现有代码架构分析 当前项目结构 ai问答/ ├── main.py # FastAPI服务入口,API路由 ├── model.py # 基础LLM模型加载与推理 ├── rag.py …

圆柱电池自动分选机:全流程自动化检测的革新之路

在新能源产业快速发展的背景下&#xff0c;圆柱电池作为动力电池和储能领域的核心组件&#xff0c;其生产效率与质量把控至关重要。圆柱电池自动分选机的出现&#xff0c;通过全流程自动化检测技术&#xff0c;为电池制造与分选环节提供了高效、精准的解决方案。传统电池分选依…