
從項目標題來看這是一篇“技術(shù)復現(xiàn)型”的分享。只要動過PyTorch做圖像分類或文本分類的人應該都經(jīng)歷過這種場景訓練完模型打印出準確率看到一個90%甚至95%的數(shù)字心里挺高興結(jié)果模型一上線發(fā)現(xiàn)某些類別幾乎全錯。準確率這個指標太“宏觀”了它會掩蓋很多細節(jié)。所以我才堅持每次跑完分類任務都要畫一張混淆矩陣出來看看。這篇文章就把我平時在PyTorch項目里畫混淆矩陣和做可視化的一套完整流程整理出來從最底層的矩陣計數(shù)原理到最終的出版級圖像導出盡量覆蓋到每一個坑。1. 為什么要畫混淆矩陣從準確率說不清的問題說起1.1 準確率看著挺高模型卻可能在“裝模作樣”先講個我自己踩過的案例。當時做一個六分類的紋理識別任務訓練集里樣本分布很不均衡其中一類占了總樣本的將近一半。訓練結(jié)束后驗證集準確率到了92%看起來一切正常。結(jié)果把測試集結(jié)果按類別拆開一統(tǒng)計那個占了50%的類別準確率接近99%而另外幾個稀少類別準確率只有60%到70%。如果只看準確率根本發(fā)現(xiàn)不了這個問題?;煜仃嚨膬r值就在這它不給你一個籠統(tǒng)的“好”或“壞”而是把“哪一類被認對了、哪一類被認成了哪一類”全部攤開。有些讀者可能會問精確率、召回率、F1值不是也能看類別的單獨表現(xiàn)嗎確實能。但混淆矩陣是最原始的“計數(shù)表”精確率和召回率其實是基于這個計數(shù)表二次計算出來的。有了混淆矩陣你可以自己計算任何想要的指標還可以定位到具體的“錯誤對”比如A類被錯認成B類到底是A類的特征太接近B類還是數(shù)據(jù)標注本身就有問題。這是單獨一個F1值做不到的。1.2 混淆矩陣到底在告訴你什么對于一個N分類任務混淆矩陣是一個N乘N的方陣。行通常代表真實標簽列代表預測標簽。矩陣的第i行第j列的值表示“真實類別是i卻被模型預測成類別j”的樣本個數(shù)。對角線上的值越大說明這一類被正確識別的比例越高。對角線外的值就是錯誤分布它們會清晰地告訴你錯誤主要發(fā)生在哪些類別之間。舉個例子三分類問題假設(shè)有150個測試樣本每個類別各50個。如果矩陣長這樣真實\預測類別0類別1類別2類別04811類別15405類別22840對角線加起來是128所以準確率是85.3%。但這張矩陣透露的信息更多類別0表現(xiàn)最好只有兩個樣本出錯類別1有5個樣本被認成類別05個被認成類別2類別2則有8個樣本被認成類別1。這告訴你類別1和類別2之間的混淆比較嚴重可能是特征相似也可能是訓練樣本不足。1.3 什么時候必須畫混淆矩陣我個人的習慣是只要是分類任務無論是圖像分類、文本分類還是音頻分類跑了測試集之后都要畫一張混淆矩陣。不是說每個項目都要給客戶看而是你自己必須心里有數(shù)。尤其是以下幾種情況不畫混淆矩陣幾乎等于白跑測試第一類別不均衡時。就像前面舉的例子準確率會被大類“帶節(jié)奏”。第二類別數(shù)量較多時比如幾十個類即使每個類別準確率在大數(shù)上看起來差不多也可能存在個別類別之間系統(tǒng)性錯認只有矩陣才能暴露這種規(guī)律。第三迭代優(yōu)化過程中。每次調(diào)完參、加完數(shù)據(jù)畫一張矩陣對比兩次的錯誤分布能直觀看出優(yōu)化是否真的有效而不是只看準確率有沒有漲。第四需要向團隊或客戶匯報時。一張帶顏色深淺的混淆矩陣圖比一堆數(shù)字直觀得多。2. 動手前的準備環(huán)境、模型輸出與數(shù)據(jù)格式2.1 最小可用環(huán)境清單畫混淆矩陣本身對硬件沒有任何要求CPU機器完全能跑。核心依賴就是PyTorch、scikit-learn、matplotlib如果想畫得更精致一點可以再加seaborn。我用過的幾個版本組合供參考依賴庫我常用的版本區(qū)間說明Python3.8 ~ 3.11老版本要注意scikit-learn兼容性PyTorch1.13 或 2.x分類模型推理scikit-learn1.0 ~ 1.3提供confusion_matrix等函數(shù)matplotlib3.5 ~ 3.8基礎(chǔ)繪圖seaborn0.12 ~ 0.13熱力圖可視化更省事安裝命令我就不多寫了pip install torch scikit-learn matplotlib seaborn一步到位。如果是在已有的訓練環(huán)境里補裝注意不要隨意升級PyTorch版本避免影響之前訓練好的模型權(quán)重加載。2.2 模型輸出到底長什么樣logits、概率和預測類別剛?cè)腴T的朋友最容易在這里搞混。PyTorch分類模型在全連接層之后通常輸出一個向量這個向量在進入Softmax之前叫l(wèi)ogits維度是批量大小乘以類別數(shù)。舉例來說一個批次的輸入經(jīng)過模型后輸出形狀通常是[batch_size, num_classes]。對每個樣本來說這個向量里的值表示該樣本屬于各個類別的“原始得分”數(shù)值越大代表模型越傾向于這個類別。取最大值的索引就是預測類別。有一種常見操作是先把logits經(jīng)過Softmax轉(zhuǎn)成概率再取最大值索引。這兩者其實結(jié)果完全一致因為Softmax是單調(diào)遞增函數(shù)不會改變最大值的位置。所以如果你只是要取預測類別直接做pred torch.argmax(logits, dim1)就夠了不需要先算Softmax。但如果你想把概率值也記錄下來比如用于分析置信度那就需要先過Softmax了。2.3 拿到預測結(jié)果后先做這件事類別索引與名稱對應關(guān)系檢查模型輸出的是索引比如數(shù)字0、1、2而不是字符串“貓”“狗”“鳥”。所以動手畫圖前先確認索引和類別名稱的對應關(guān)系。我遇到過不止一次訓練時數(shù)據(jù)集是用字典映射的類別名稱在數(shù)據(jù)集類的classes屬性里導出模型做推理時忘了保存映射關(guān)系結(jié)果畫出來的矩陣行列全是數(shù)字根本不知道哪一行對應哪個類別。這里給一個最小驗證片段確保測試集的類別是連續(xù)且完整的# 以 torchvision.datasets.ImageFolder 加載的數(shù)據(jù)為例 dataset_classes test_dataset.classes print(dataset_classes) # 例如 [cat, dog, bird]如果你的數(shù)據(jù)是自定義Dataset一定要檢查__getitem__返回的標簽是從0開始的連續(xù)整數(shù)。如果標簽不連續(xù)比如0、1、5、9混淆矩陣的行列就會錯位。這一點后面還會詳細說。3. 零基礎(chǔ)到手寫實現(xiàn)混淆矩陣的兩種構(gòu)建方式3.1 方式一手寫計數(shù)徹底搞懂矩陣邏輯雖然現(xiàn)成庫一行就能搞定但我強烈建議新手先手寫一次。理解了矩陣填充邏輯后面用庫函數(shù)出現(xiàn)問題時你才有能力排查。首先準備真實標簽列表y_true和預測標簽列表y_pred兩者必須是等長的Python列表、NumPy數(shù)組或PyTorch張量。假設(shè)有4個類別先初始化一個4乘4的全零矩陣然后遍歷每一對真實標簽和預測標簽把對應位置加1import torch def build_confusion_matrix(y_true, y_pred, num_classes): matrix torch.zeros(num_classes, num_classes, dtypetorch.int64) for t, p in zip(y_true, y_pred): matrix[t, p] 1 return matrix # 示例數(shù)據(jù)5個樣本3個類別 y_true [0, 1, 2, 1, 0] y_pred [0, 2, 2, 1, 0] cm build_confusion_matrix(y_true, y_pred, num_classes3) print(cm)輸出tensor([[2, 0, 0], [0, 1, 1], [0, 0, 1]])解釋一下第一個樣本真實是0預測是0所以matrix[0,0]加1第二個樣本真實是1預測是2所以matrix[1,2]加1。這個邏輯非常簡單遍歷完所有樣本就得到了完整的計數(shù)矩陣。如果類別數(shù)很多用循環(huán)加法的效率不高但畫圖前統(tǒng)計測試集一般也就幾千個樣本性能完全不是問題。3.2 方式二使用適合快速統(tǒng)計的現(xiàn)成函數(shù)手寫矩陣只是教學手段實際項目里我一般直接用機器學習常用庫中現(xiàn)成的混淆矩陣統(tǒng)計函數(shù)。它省事、代碼少、不易出錯from sklearn.metrics import confusion_matrix y_true [0, 1, 2, 1, 0] y_pred [0, 2, 2, 1, 0] cm confusion_matrix(y_true, y_pred, labels[0, 1, 2]) print(cm)重點是這個labels參數(shù)。它用來指定類別索引的完整列表順序就是矩陣行列的順序。如果你的測試集某個類別恰好一個樣本都沒出現(xiàn)不傳labels的話矩陣會缺少那一行一列導致維度對不上。我一般在多分類任務中都會顯式傳入labelslist(range(num_classes))強制讓矩陣維度固定。如果真實標簽和預測標簽是PyTorch張量先轉(zhuǎn)成Python列表或NumPy數(shù)組再傳入。# 從 GPU 張量轉(zhuǎn)到 CPU再轉(zhuǎn) numpy y_true_np y_true.cpu().numpy() y_pred_np y_pred.cpu().numpy() cm confusion_matrix(y_true_np, y_pred_np, labelslist(range(num_classes)))這里我解釋一下為什么要先轉(zhuǎn)cpu().numpy()。confusion_matrix函數(shù)接收的是類數(shù)組結(jié)構(gòu)PyTorch張量雖然也能在某些情況下被兼容但為避免type error或device對不上的問題統(tǒng)一轉(zhuǎn)成NumPy數(shù)組最省心。3.3 手工方式 vs 現(xiàn)成函數(shù)怎么選有人可能會覺得既然現(xiàn)成函數(shù)這么方便為什么還要學手寫因為現(xiàn)成函數(shù)幫我們省掉的是“計數(shù)”這一步但沒有幫我們理解“矩陣的含義”。在實際問題排查中你需要跟別人解釋為什么某個位置數(shù)值異常偏高這時候只有真正理解矩陣每一格的含義才能基于業(yè)務邏輯給出合理解釋。兩種方式建議這樣搭配學習和調(diào)試階段手寫理解透徹后正式代碼里用現(xiàn)成函數(shù)。還有一個細節(jié)手寫矩陣用for循環(huán)效率較低如果測試集很大比如幾萬甚至幾十萬樣本建議用torch.zeros和index_put_這類批量操作或者直接用NumPy的np.add.at。不過對常規(guī)項目來說這個復雜度根本遇不到。4. 畫出真正能用的混淆矩陣圖配色、歸一化與標注4.1 基礎(chǔ)熱力圖讓數(shù)字變成顏色矩陣本身是數(shù)字表格直接看也能看但遠不如熱力圖直觀。熱力圖的核心思想是用顏色深淺表示數(shù)值大小顏色越深數(shù)值越大。對角線越深說明分類效果越好。用seaborn畫熱力圖是我用得最多的方式因為它的heatmap函數(shù)幾乎為混淆矩陣量身定制import matplotlib.pyplot as plt import seaborn as sns import numpy as np from sklearn.metrics import confusion_matrix # 假設(shè)已經(jīng)得到 cm 和類別名稱 class_names plt.figure(figsize(8, 6)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabelsclass_names, yticklabelsclass_names, cbarFalse) plt.xlabel(Predicted label) plt.ylabel(True label) plt.title(Confusion Matrix) plt.tight_layout() plt.savefig(confusion_matrix.png, dpi300) plt.show()參數(shù)說明annotTrue在每個格子里顯示數(shù)字。fmtd因為混淆矩陣是整數(shù)計數(shù)用d表示十進制整數(shù)格式。如果錯用了fmt.2f或者不寫可能報錯或者顯示成小數(shù)。cmapBlues藍色漸變顏色越深數(shù)值越大這是論文里最常見的配色。xticklabels和yticklabels傳入類別名稱列表讓坐標軸顯示可讀的名稱而不是數(shù)字索引。cbarFalse我比較喜歡關(guān)掉右側(cè)的顏色條因為它只表示顏色深淺對應數(shù)值大小但每個格子已經(jīng)標了數(shù)字顏色條的信息量有限還占空間。如果對顏色對比敏感可以保留。4.2 歸一化從計數(shù)到比例的轉(zhuǎn)換原始計數(shù)矩陣有個問題如果每個類別的樣本數(shù)不同深色的格子分布會失真。例如類別0有1000個測試樣本類別1只有100個即使兩者的識別率都是80%類別0對應的對角線格子數(shù)值是800顏色會非常深類別1只有80看起來很淺給人造成“類別1表現(xiàn)很差”的錯覺。解決方法是按行歸一化歸一化后每個格子的值表示“該行真實類別中有多少比例的樣本被預測到了這一列”。歸一化矩陣的計算方法把每一行的所有值除以該行的總和。在scikit-learn中可以直接指定normalizetruecm_normalized confusion_matrix(y_true, y_pred, labelslist(range(num_classes)), normalizetrue)在seaborn繪制時我通常這樣處理cm confusion_matrix(y_true, y_pred, labelslist(range(num_classes))) cm_norm cm.astype(float) / cm.sum(axis1, keepdimsTrue) sns.heatmap(cm_norm, annotTrue, fmt.2f, cmapBlues, xticklabelsclass_names, yticklabelsclass_names)注意歸一化后fmt要改成.2f顯示兩位小數(shù)。在最終展示時我更傾向于同時展示兩種形式一是原始計數(shù)矩陣用來精確知道樣本數(shù)量二是按行歸一化的比例矩陣用來比較不同類別之間的表現(xiàn)??蓞⒖嫉恼故痉桨甘窍却蛴≡加嫈?shù)矩陣再打印歸一化矩陣圖里選一種呈現(xiàn)。4.3 坐標軸字體、標題和圖像導出細節(jié)可視化不只是把圖畫出來還要考慮最終使用場景。如果圖是要放進論文、技術(shù)報告或PPT里的要注意以下幾點。首先類別名稱如果比較長比如“background_with_noise”這種橫坐標文字會重疊。解決方法是把坐標軸文字旋轉(zhuǎn)一下plt.xticks(rotation45, haright) plt.yticks(rotation0)旋轉(zhuǎn)角度一般45度就夠haright讓文字右對齊避免和相鄰文字貼在一起。其次字體大小要適配圖片尺寸。默認字號在8乘6英寸的圖里還行但如果類別很多比如30個類別格子很小數(shù)字會擠在一起。這時候要么調(diào)大figsize要么調(diào)小字體sns.set(font_scale1.2) # 全局放大字體 # 或者單獨控制 annot 字體大小 sns.heatmap(..., annot_kws{size: 10})最后是導出格式。我會優(yōu)先導出一個矢量圖加一個位圖。矢量圖用PDF或SVG用于論文或報告縮放不失真位圖用PNGdpi300以上用于網(wǎng)頁展示或微信群聊。示例代碼plt.savefig(confusion_matrix.pdf, formatpdf, bbox_inchestight) plt.savefig(confusion_matrix.png, formatpng, dpi300, bbox_inchestight)bbox_inchestight會自動裁剪圖片邊緣空白保證整張圖緊湊這一步我?guī)缀趺看味紩印?. 驗證集、測試集和全量推理數(shù)據(jù)到底用哪一份5.1 三份數(shù)據(jù)的分工不少初學者會把訓練過程中打印的驗證集準確率對應的混淆矩陣當作最終結(jié)果這其實不太嚴謹。驗證集的作用是調(diào)參和選模型它參與了你對超參數(shù)的選擇過程所以用它評估模型會略微偏樂觀。真正用于最終性能報告的應該是測試集也就是訓練過程中從未參與梯度更新和模型選擇的樣本。如果連測試集你也反復用來做決策它也會慢慢“污染”。所以我的習慣是訓練過程中的混淆矩陣用驗證集畫用于觀察每個epoch有沒有個別類別開始嚴重錯認最終匯報和論文里的混淆矩陣用測試集畫用于說明模型的真實泛化能力。5.2 推理時別用模型本身的Dropout和BatchNorm坑在PyTorch中如果模型處于訓練模式model.train()Dropout和BatchNorm的行為與推理模式不同。畫混淆矩陣時必須把模型切換到評估模式model.eval() with torch.no_grad(): outputs model(batch_data) preds torch.argmax(outputs, dim1)如果不加torch.no_grad()推理過程會構(gòu)建計算圖不僅占用大量顯存速度也慢畫個混淆矩陣把顯卡顯存跑爆就尷尬了。model.eval()則能保證BatchNorm使用訓練階段統(tǒng)計的均值和方差Dropout關(guān)閉這樣推理結(jié)果才穩(wěn)定可控。有一次我忘了加model.eval()連續(xù)跑兩次同一條測試數(shù)據(jù)得出的預測結(jié)果不一樣嚇了自己一跳最后排查發(fā)現(xiàn)是Dropout在作怪。5.3 數(shù)據(jù)順序打亂對矩陣沒有影響但對可視化有影響混淆矩陣本質(zhì)上是對所有樣本計數(shù)的匯總不關(guān)心樣本順序。所以DataLoader里是否shuffleTrue不影響矩陣結(jié)果。但要注意如果你同時還想輸出“哪些具體樣本被分錯了”以便人工檢查錯誤案例那就必須記錄樣本路徑或ID而不能只記預測結(jié)果。順序一旦打亂你只知道有幾個樣本被錯認卻不知道具體是哪幾張圖。我通常會在推理時把樣本的路徑和預測結(jié)果一起記錄下來便于后續(xù)人工核對。這步在圖像分類里特別重要因為矩陣只能告訴你錯誤數(shù)量不能告訴你錯誤原因。6. 從矩陣到洞察進階分析與實戰(zhàn)技巧6.1 發(fā)現(xiàn)系統(tǒng)性錯誤相鄰類別的強烈混淆混淆矩陣一個特別有用的場景是發(fā)現(xiàn)“系統(tǒng)性錯認”。比如在植物葉片分類中兩種外觀極其相似的病害類型常常互相混淆矩陣里會看到兩個對稱的非對角線位置數(shù)值都很高。這說明模型并沒有學會區(qū)分它們本質(zhì)上是特征表達不夠或者兩類樣本之間的差異確實微小。發(fā)現(xiàn)這種規(guī)律后我的處理步驟通常是這樣先統(tǒng)計這兩個類別的樣本量確認是否不平衡然后檢查訓練數(shù)據(jù)里這兩個類別的圖像是否采集條件差異很大比如光照、角度再考慮是否需要對這兩類做特征可視化比如用梯度加權(quán)類激活映射看模型關(guān)注區(qū)域是否有問題。6.2 類別不平衡如何讓矩陣“說謊”歸一化矩陣能解決一部分“比較”問題但解決不了根本問題樣本量太少的類別即使歸一化后準確率看著還行實際可信度也不高。比如某個類別只有10個測試樣本哪怕只有1個被分錯歸一化后準確率就只有90%而另一個類別有1000個樣本50個分錯準確率是95%。單看這兩個數(shù)字好像后者更好但樣本量差異導致前者的置信區(qū)間大得多。這種情況下我建議在矩陣圖下方額外標注每個類別的測試樣本數(shù)。代碼上可以準備一個樣本數(shù)列表用plt.text在對應的列下方標注或者干脆在類別名稱里帶上樣本量比如“類別A (n100)”。6.3 結(jié)合分類報告一起打包輸出混淆矩陣是基礎(chǔ)但給別人看的時候通常不能只甩一張矩陣圖。我在項目匯報時一般會同時輸出一份分類報告包含每個類別的精確率、召回率、F1值和樣本數(shù)。這樣既能從矩陣看全局又能從指標看單類表現(xiàn)論證更充分。from sklearn.metrics import classification_report report classification_report(y_true_np, y_pred_np, target_namesclass_names, digits3) print(report)這段代碼會輸出一個表格每個類一行最后一行為加權(quán)平均。配合矩陣圖放在結(jié)果部分信息量完全夠用。6.4 多標簽任務不是這個畫法這里要特別提醒一句本文討論的是單標簽多分類任務每個樣本只能屬于一個類別。如果你做的是多標簽任務比如一張圖同時包含“貓”和“狗”那混淆矩陣的畫法就完全不同了需要按標簽維度單獨畫或者使用多標簽專用的評估指標。矩陣的行列在這里就對應不上不要混淆。7. 常見問題與排查思路實錄7.1 畫圖時報錯“size mismatch”最常見的錯誤之一。原因通常是類別數(shù)對不上比如模型輸出10類但labels列表只給了8個或者測試集里某些類別缺失。排查順序先打印model最后一層輸出維度再打印訓練時保存的類別映射然后打印len(np.unique(y_true))逐項核對。7.2 歸一化矩陣里出現(xiàn)NaN這類問題一般發(fā)生在某個行和為0的情況下也就是某個類別一個測試樣本都沒有。因為分母為0除法結(jié)果變成NaN。解決方法是先檢查類別分布確保每個類別都有測試樣本如果確實沒有這一行就不該出現(xiàn)在矩陣里需要從labels里去掉該類別或者在計算時加一個極小值防止除零。7.3 圖片上中文類別名稱顯示成方塊matplotlib默認字體不支持中文直接寫中文標簽會變成一個個方框。解決方法有兩個一是直接用英文類別名最省事二是給matplotlib設(shè)置中文字體。代碼片段plt.rcParams[font.sans-serif] [SimHei] # 或 [Microsoft YaHei] plt.rcParams[axes.unicode_minus] False需要注意Linux服務器上可能沒有SimHei可以改用DejaVu Sans配合英文標簽。如果是長期做中文項目我建議從始至終統(tǒng)一使用英文類別名加一份映射字典避免字體問題的困擾。7.4 GPU顯存不足的坑推理整個測試集時如果一次性把全部數(shù)據(jù)丟給模型顯存很容易溢出。正確做法是使用DataLoader分批推理每次累積預測結(jié)果all_preds [] all_labels [] for images, labels in test_loader: images images.to(device) labels labels.to(device) with torch.no_grad(): outputs model(images) preds torch.argmax(outputs, dim1) all_preds.append(preds.cpu()) all_labels.append(labels.cpu()) all_preds torch.cat(all_preds).numpy() all_labels torch.cat(all_labels).numpy()這里我把預測結(jié)果先放到CPU再在最后合并。注意不要每步都torch.cat這會復制數(shù)據(jù)多次效率低應該先收集列表最后一次性合并。7.5 分類報告和矩陣對不上這個問題的根源往往是y_true和y_pred的來源不一致。比如y_true來自測試集的原始標簽而y_pred來自某個評測階段的輸出文件兩者順序沒有對齊。排查方法是對比前幾個樣本的路徑和預測結(jié)果確認是否一一對應。我自己的經(jīng)驗是永遠在同一個推理循環(huán)里同時收集真實標簽和預測標簽不要分開跑兩次再合并。8. 我常用的一個完整可交付模板最后分享一個我自己項目里常用的模板代碼整體打包可直接復制修改。它做了三件事推理并收集標簽、計算原始和歸一化矩陣、繪制并導出圖片。import torch import numpy as np import matplotlib.pyplot as plt import seaborn as sns from sklearn.metrics import confusion_matrix, classification_report def evaluate_and_plot(model, test_loader, class_names, device, save_path_prefixcm): model.eval() all_preds [] all_labels [] with torch.no_grad(): for images, labels in test_loader: images images.to(device) labels labels.to(device) outputs model(images) preds torch.argmax(outputs, dim1) all_preds.append(preds.cpu()) all_labels.append(labels.cpu()) y_pred torch.cat(all_preds).numpy() y_true torch.cat(all_labels).numpy() cm confusion_matrix(y_true, y_pred, labelslist(range(len(class_names)))) cm_norm cm.astype(float) / cm.sum(axis1, keepdimsTrue) print(Classification Report:) print(classification_report(y_true, y_pred, target_namesclass_names, digits3)) fig, axes plt.subplots(1, 2, figsize(16, 7)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabelsclass_names, yticklabelsclass_names, axaxes[0], cbarFalse) axes[0].set_title(Confusion Matrix (Counts)) axes[0].set_xlabel(Predicted label) axes[0].set_ylabel(True label) sns.heatmap(cm_norm, annotTrue, fmt.2f, cmapBlues, xticklabelsclass_names, yticklabelsclass_names, axaxes[1], cbarFalse) axes[1].set_title(Confusion Matrix (Row-Normalized)) axes[1].set_xlabel(Predicted label) axes[1].set_ylabel(True label) plt.tight_layout() plt.savefig(f{save_path_prefix}.pdf, formatpdf, bbox_inchestight) plt.savefig(f{save_path_prefix}.png, formatpng, dpi300, bbox_inchestight) plt.show() return cm, cm_norm, y_true, y_pred這個函數(shù)會把歸一化和原始計數(shù)矩陣并排畫在同一張圖里一次導出兩種視圖。傳給它的test_loader不需要shuffleclass_names從dataset.classes里提取。我自己做項目時會把保存的PDF圖直接放進技術(shù)報告里PNG圖放到文檔中省去重新繪制的麻煩。用下來這么久我覺得混淆矩陣這個工具比想象中更“耐看”。每次訓練完新模型我都會把它當作一個標準動作和打印準確率一樣自然。有時候矩陣看起來結(jié)果不理想反而能逼著我去找數(shù)據(jù)里的問題這比模型調(diào)參本身更有價值。希望這套流程能幫你省掉一些自己摸索的時間。