深度學習 pytorch圖像分類(詳細版)

目錄

一、項目簡介

二、模型訓練+驗證+保存

三、模型測試+保存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;為電池制造與分選環節提供了高效、精準的解決方案。傳統電池分選依…