:畢業(yè)設(shè)計(jì)從特征提取到可復(fù)現(xiàn)落地)
簡(jiǎn)介這是一套面向高校學(xué)生與深度學(xué)習(xí)入門(mén)者的圖像檢索畢業(yè)設(shè)計(jì)完整項(xiàng)目基于VGG16卷積神經(jīng)網(wǎng)絡(luò)實(shí)現(xiàn)以圖搜圖功能。項(xiàng)目使用Python與Keras搭建將圖像轉(zhuǎn)換為高維特征向量再通過(guò)余弦相似度或歐氏距離完成相似圖像匹配覆蓋圖像預(yù)處理、特征提取、相似度計(jì)算與檢索全流程可直接運(yùn)行并根據(jù)自有數(shù)據(jù)集調(diào)整。資源包共30個(gè)文件約47.96MB包含8個(gè)Python腳本模型構(gòu)建、訓(xùn)練與檢索邏輯、5個(gè)gif與5個(gè)png演示圖、3個(gè)js與2個(gè)html及2個(gè)css前端頁(yè)面、1個(gè)db數(shù)據(jù)庫(kù)、1個(gè)md說(shuō)明文檔等目錄結(jié)構(gòu)清晰便于按模塊閱讀與二次開(kāi)發(fā)。目前已有430人學(xué)習(xí)下載。讀者可借此掌握VGG16特征提取、Keras預(yù)訓(xùn)練模型調(diào)用、圖像檢索算法與完整項(xiàng)目組織方式是理解深度學(xué)習(xí)落地應(yīng)用、完成畢業(yè)設(shè)計(jì)的實(shí)用參考。1. 基于 VGG16 的圖像檢索系統(tǒng)從特征提取到可復(fù)現(xiàn)的畢業(yè)設(shè)計(jì)落地做畢業(yè)設(shè)計(jì)最怕兩件事一是選題聽(tīng)起來(lái)高大上真動(dòng)手發(fā)現(xiàn)無(wú)從下手二是代碼跑不通數(shù)據(jù)找不到最后只能“借鑒”別人的倉(cāng)庫(kù)改個(gè)名字。基于 VGG16 的圖像檢索系統(tǒng)恰好卡在中間——它足夠經(jīng)典經(jīng)典到任何計(jì)算機(jī)畢業(yè)設(shè)計(jì)答辯老師都認(rèn)可這個(gè)方向又足夠具體具體到你可以用一份完整代碼和數(shù)據(jù)在本地跑出可演示的結(jié)果。這個(gè)系統(tǒng)的核心邏輯并不復(fù)雜用預(yù)訓(xùn)練的 VGG16 卷積網(wǎng)絡(luò)把每張圖片變成一個(gè)高維特征向量再通過(guò)計(jì)算向量之間的余弦相似度或歐氏距離從圖庫(kù)里找出與查詢圖最接近的 Top-K 張。它解決的是“以圖搜圖”這個(gè)真實(shí)需求適合計(jì)算機(jī)視覺(jué)入門(mén)、信息檢索課程設(shè)計(jì)以及需要快速搭建可演示系統(tǒng)的畢業(yè)設(shè)計(jì)場(chǎng)景。下面我會(huì)把選型理由、代碼實(shí)現(xiàn)、參數(shù)調(diào)優(yōu)和踩坑記錄一層層拆開(kāi)讓你拿到就能跑跑完能講清楚為什么這么做。2. VGG16 做圖像特征提取為什么選它而不是 ResNet 或 CLIP2.1 VGG16 作為特征提取器的結(jié)構(gòu)優(yōu)勢(shì)與局限VGG16 是牛津大學(xué)視覺(jué)幾何組在 2014 年提出的卷積神經(jīng)網(wǎng)絡(luò)拿過(guò) ImageNet 挑戰(zhàn)賽的亞軍。它的結(jié)構(gòu)極其規(guī)整13 個(gè)卷積層全部使用 3×3 小卷積核5 個(gè)最大池化層穿插其中最后接 3 個(gè)全連接層。這種“堆疊小卷積核”的設(shè)計(jì)讓網(wǎng)絡(luò)在保持感受野的同時(shí)減少了參數(shù)量也讓它提取的特征具有很好的層次性——淺層卷積響應(yīng)邊緣和紋理深層卷積響應(yīng)語(yǔ)義部件。在圖像檢索任務(wù)里我們通常不會(huì)用 VGG16 最后的 softmax 分類輸出而是取全連接層之前的特征。常見(jiàn)做法是取block5_pool之后的 512 維特征或者取fc2層的 4096 維特征。512 維特征計(jì)算快、存儲(chǔ)省適合圖庫(kù)規(guī)模在幾千到幾萬(wàn)張的場(chǎng)景4096 維特征表達(dá)能力更強(qiáng)但檢索時(shí)的距離計(jì)算開(kāi)銷會(huì)明顯上升。我一般會(huì)先用 512 維跑通全流程如果發(fā)現(xiàn)相似圖片排不到前面再切到 4096 維對(duì)比效果。VGG16 的局限也很明顯參數(shù)量約 1.38 億其中全連接層占了絕大部分導(dǎo)致模型文件超過(guò) 500MB推理速度比 MobileNet 慢不少。如果你的畢業(yè)設(shè)計(jì)需要部署到邊緣設(shè)備或者要求實(shí)時(shí)響應(yīng)VGG16 可能不是最優(yōu)選。但如果你追求的是“特征穩(wěn)定、代碼簡(jiǎn)單、答辯好講”VGG16 依然是性價(jià)比很高的選擇。ResNet 的殘差連接雖然訓(xùn)練更深網(wǎng)絡(luò)更容易但作為固定特征提取器時(shí)VGG16 在中小規(guī)模圖庫(kù)上的檢索精度并不遜色。CLIP 這類多模態(tài)模型效果更好但依賴大規(guī)模預(yù)訓(xùn)練權(quán)重和更復(fù)雜的推理流程對(duì)畢業(yè)設(shè)計(jì)的算力要求偏高。2.2 用 PyTorch 加載 VGG16 并導(dǎo)出特征向量的最小代碼下面這段代碼展示了如何加載預(yù)訓(xùn)練 VGG16、去掉分類頭、對(duì)單張圖片做預(yù)處理并輸出 512 維特征向量。代碼可以直接復(fù)制運(yùn)行前提是你已經(jīng)安裝了 PyTorch 和 torchvision。import torch import torch.nn as nn from torchvision import models, transforms from PIL import Image # 加載預(yù)訓(xùn)練 VGG16weights 參數(shù)指定使用 ImageNet 預(yù)訓(xùn)練權(quán)重 vgg16 models.vgg16(weightsmodels.VGG16_Weights.IMAGENET1K_V1) # 只保留 features 部分卷積層去掉 avgpool 和 classifier # features 的輸出是 (batch, 512, 7, 7)需要進(jìn)一步處理 feature_extractor vgg16.features # 設(shè)置為評(píng)估模式關(guān)閉 dropout 和 batchnorm 的訓(xùn)練行為 feature_extractor.eval() # 定義圖像預(yù)處理縮放到 224x224轉(zhuǎn)張量按 ImageNet 統(tǒng)計(jì)量歸一化 preprocess transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) def extract_feature(img_path): img Image.open(img_path).convert(RGB) input_tensor preprocess(img).unsqueeze(0) # 增加 batch 維度 with torch.no_grad(): features feature_extractor(input_tensor) # 形狀 (1, 512, 7, 7) # 全局平均池化把 7x7 空間維度壓成 1x1得到 512 維向量 features torch.mean(features, dim[2, 3]) # L2 歸一化方便后續(xù)用余弦相似度 features nn.functional.normalize(features, p2, dim1) return features.squeeze().numpy() if __name__ __main__: vec extract_feature(test.jpg) print(vec.shape) # 輸出 (512,)這段代碼的關(guān)鍵點(diǎn)有三個(gè)。第一vgg16.features只包含卷積層輸出是四維張量(batch, 512, 7, 7)其中 7×7 是空間尺寸。第二全局平均池化把每個(gè)通道的 7×7 特征圖取平均得到 512 維向量這比直接展平更魯棒也避免了全連接層的巨大參數(shù)量。第三L2 歸一化讓向量落在單位球面上之后用余弦相似度檢索時(shí)只需做點(diǎn)積計(jì)算效率更高。參數(shù)方面Resize(256)和CenterCrop(224)是 ImageNet 的標(biāo)準(zhǔn)預(yù)處理流程不要隨意改成 224 直接縮放否則會(huì)改變圖像長(zhǎng)寬比影響特征質(zhì)量。歸一化的均值和標(biāo)準(zhǔn)差必須和預(yù)訓(xùn)練時(shí)一致否則特征分布會(huì)偏移。如果你用的是自己訓(xùn)練的 VGG16 權(quán)重歸一化參數(shù)要換成訓(xùn)練時(shí)用的統(tǒng)計(jì)量。2.3 批量提取圖庫(kù)特征并保存為可檢索的索引文件單張?zhí)崛≈皇茄菔緦?shí)際系統(tǒng)需要對(duì)整個(gè)圖庫(kù)做批量處理。下面代碼遍歷指定目錄下所有圖片提取特征并保存為.npy文件同時(shí)保存文件名列表方便后續(xù)根據(jù)索引找回原圖。import os import numpy as np from tqdm import tqdm from extract_feature import extract_feature # 假設(shè)上面的函數(shù)保存在 extract_feature.py def build_index(image_dir, output_dir): os.makedirs(output_dir, exist_okTrue) image_paths [] features [] # 支持的圖片格式 valid_ext {.jpg, .jpeg, .png, .bmp, .webp} for fname in tqdm(os.listdir(image_dir)): ext os.path.splitext(fname)[1].lower() if ext not in valid_ext: continue fpath os.path.join(image_dir, fname) try: vec extract_feature(fpath) features.append(vec) image_paths.append(fpath) except Exception as e: print(f跳過(guò) {fpath}: {e}) features np.array(features, dtypenp.float32) np.save(os.path.join(output_dir, features.npy), features) with open(os.path.join(output_dir, paths.txt), w, encodingutf-8) as f: f.write(\n.join(image_paths)) print(f索引完成{len(image_paths)} 張圖片特征維度 {features.shape[1]}) if __name__ __main__: build_index(gallery_images, index_output)這段代碼做了幾件事遍歷目錄、過(guò)濾非圖片文件、逐張?zhí)崛√卣?、捕獲異常防止單張損壞圖片中斷整個(gè)流程、保存特征矩陣和路徑列表。tqdm用來(lái)顯示進(jìn)度條圖庫(kù)大時(shí)很有用。保存為.npy格式比 CSV 或 JSON 快得多加載時(shí)直接np.load即可。參數(shù)上image_dir是你的圖庫(kù)根目錄output_dir是索引輸出目錄。如果圖庫(kù)有子目錄結(jié)構(gòu)需要改成os.walk遞歸遍歷。特征矩陣的形狀是(N, 512)N 是圖片數(shù)量。路徑列表的順序必須和特征矩陣的行順序嚴(yán)格對(duì)應(yīng)否則檢索結(jié)果會(huì)張冠李戴。我一般會(huì)在保存前打印前 5 個(gè)路徑和對(duì)應(yīng)的特征范數(shù)確認(rèn)沒(méi)有全零向量或異常值。3. 相似度計(jì)算與檢索排序余弦距離、歐氏距離怎么選3.1 余弦相似度與歐氏距離在歸一化特征下的等價(jià)性特征向量做完 L2 歸一化之后余弦相似度和歐氏距離之間存在單調(diào)關(guān)系。具體來(lái)說(shuō)兩個(gè)單位向量之間的歐氏距離平方等于 2 減去 2 倍的余弦相似度。這意味著用余弦相似度排序和用歐氏距離排序得到的 Top-K 結(jié)果完全一致。既然等價(jià)為什么還要區(qū)分因?yàn)橛?jì)算效率不同。余弦相似度只需要做點(diǎn)積而歐氏距離需要計(jì)算差值平方和再開(kāi)方。在圖庫(kù)規(guī)模達(dá)到十萬(wàn)級(jí)時(shí)點(diǎn)積的矩陣運(yùn)算優(yōu)勢(shì)會(huì)體現(xiàn)出來(lái)。實(shí)際寫(xiě)代碼時(shí)我通常把圖庫(kù)特征矩陣轉(zhuǎn)置后與查詢向量做矩陣乘法一次性算出所有相似度而不是寫(xiě)循環(huán)逐個(gè)計(jì)算。NumPy 的dot底層調(diào)用 BLAS 庫(kù)速度比 Python 循環(huán)快兩個(gè)數(shù)量級(jí)。如果你用 FAISS 或 Annoy 這類近似最近鄰庫(kù)它們默認(rèn)也是用內(nèi)積或歐氏距離歸一化后兩者可以互換。3.2 用 NumPy 實(shí)現(xiàn) Top-K 檢索并返回圖片路徑下面代碼加載之前保存的特征索引對(duì)一張查詢圖做檢索返回最相似的 K 張圖片路徑和相似度分?jǐn)?shù)。import numpy as np from extract_feature import extract_feature def search(query_path, index_dir, top_k10): # 加載圖庫(kù)特征和路徑 features np.load(f{index_dir}/features.npy) # (N, 512) with open(f{index_dir}/paths.txt, r, encodingutf-8) as f: paths f.read().strip().split(\n) # 提取查詢圖特征已經(jīng)是 L2 歸一化的 query_vec extract_feature(query_path) # (512,) # 矩陣乘法計(jì)算余弦相似度f(wàn)eatures (N,512) dot query (512,) - (N,) similarities features.dot(query_vec) # 取 Top-K 索引argsort 默認(rèn)升序取最后 K 個(gè)再反轉(zhuǎn) top_indices np.argsort(similarities)[-top_k:][::-1] results [] for idx in top_indices: results.append({ path: paths[idx], score: float(similarities[idx]) }) return results if __name__ __main__: res search(query.jpg, index_output, top_k5) for r in res: print(f{r[score]:.4f} {r[path]})這段代碼的核心是features.dot(query_vec)這一行。features形狀是(N, 512)query_vec形狀是(512,)點(diǎn)積結(jié)果是(N,)的相似度數(shù)組。np.argsort返回升序排列的索引取最后 K 個(gè)再反轉(zhuǎn)就得到降序的 Top-K。相似度分?jǐn)?shù)越接近 1 表示越相似因?yàn)橄蛄恳呀?jīng)歸一化點(diǎn)積就是余弦值。參數(shù)top_k根據(jù)你的演示需求調(diào)整畢業(yè)設(shè)計(jì)答辯通常展示 5 到 10 張就夠了。如果圖庫(kù)里有重復(fù)圖片或近似重復(fù)圖片可能會(huì)占據(jù)多個(gè) Top-K 位置可以考慮做非極大值抑制但一般畢業(yè)設(shè)計(jì)不需要這么復(fù)雜。注意paths列表的長(zhǎng)度必須和features的行數(shù)一致如果之前構(gòu)建索引時(shí)跳過(guò)了損壞圖片這里不會(huì)出問(wèn)題因?yàn)楸4鏁r(shí)就是對(duì)應(yīng)的。3.3 檢索結(jié)果的可視化與評(píng)價(jià)指標(biāo)計(jì)算檢索系統(tǒng)不能只輸出路徑和分?jǐn)?shù)答辯時(shí)需要直觀展示。用 Matplotlib 把查詢圖和 Top-K 結(jié)果拼成一張圖是成本最低的可視化方案。import matplotlib.pyplot as plt from PIL import Image def visualize(query_path, results, save_pathresult.png): k len(results) fig, axes plt.subplots(1, k 1, figsize(3 * (k 1), 4)) # 第一張顯示查詢圖 axes[0].imshow(Image.open(query_path)) axes[0].set_title(Query) axes[0].axis(off) # 后續(xù)顯示檢索結(jié)果 for i, r in enumerate(results): axes[i 1].imshow(Image.open(r[path])) axes[i 1].set_title(f{r[score]:.3f}) axes[i 1].axis(off) plt.tight_layout() plt.savefig(save_path, dpi150) plt.show()評(píng)價(jià)指標(biāo)方面畢業(yè)設(shè)計(jì)至少應(yīng)該報(bào)告 Top-1 準(zhǔn)確率和 Top-5 準(zhǔn)確率。做法是準(zhǔn)備一個(gè)帶標(biāo)注的測(cè)試集每張查詢圖有已知的同類圖片。如果 Top-K 結(jié)果中包含同類圖片就算命中。計(jì)算代碼很簡(jiǎn)單但標(biāo)注數(shù)據(jù)需要你自己整理。我建議從圖庫(kù)中每類隨機(jī)抽 5 張作為查詢圖剩下的作為圖庫(kù)這樣既保證有同類圖片可檢索又不會(huì)讓查詢圖同時(shí)出現(xiàn)在圖庫(kù)中導(dǎo)致“自己搜自己”的虛高分?jǐn)?shù)。4. 避坑與排查VGG16 圖像檢索系統(tǒng)最常見(jiàn)的 5 個(gè)翻車點(diǎn)4.1 現(xiàn)象檢索結(jié)果全是同一張圖或相似度全部接近 1.0原因通常是特征沒(méi)有做 L2 歸一化或者歸一化維度搞錯(cuò)了。如果特征向量沒(méi)有歸一化點(diǎn)積結(jié)果會(huì)受向量模長(zhǎng)影響模長(zhǎng)大的向量與所有查詢向量的點(diǎn)積都偏大導(dǎo)致它排到前面。另一種可能是構(gòu)建索引時(shí)把查詢圖也放進(jìn)了圖庫(kù)查詢圖與自身的相似度必然是 1.0如果圖庫(kù)里有重復(fù)圖片也會(huì)出現(xiàn)類似情況。解決方法是檢查extract_feature函數(shù)里是否調(diào)用了nn.functional.normalize并確認(rèn)dim1而不是dim0。構(gòu)建索引前先對(duì)圖庫(kù)去重可以用文件哈?;蚋兄W隹焖偃ブ?。如果已經(jīng)構(gòu)建了索引加載后打印特征矩陣每行的 L2 范數(shù)正常應(yīng)該全部接近 1.0如果偏差超過(guò) 0.01 就說(shuō)明歸一化有問(wèn)題。4.2 現(xiàn)象GPU 顯存溢出報(bào)錯(cuò) CUDA out of memoryVGG16 參數(shù)量大如果批量提取特征時(shí)一次性把整個(gè)圖庫(kù)讀進(jìn) GPU顯存很容易爆。尤其是圖庫(kù)超過(guò) 5000 張、每張圖片預(yù)處理后是(1, 3, 224, 224)時(shí)累積的中間激活值會(huì)占用大量顯存。解決方法是分批處理每批 16 或 32 張?zhí)幚硗暌慌桶烟卣鬓D(zhuǎn)到 CPU 并釋放 GPU 緩存。代碼上可以用torch.cuda.empty_cache()手動(dòng)清理但更根本的是控制 batch size。如果顯卡顯存小于 6GB建議 batch size 設(shè)為 8 或 16。另外提取特征時(shí)務(wù)必用torch.no_grad()上下文否則 PyTorch 會(huì)保留計(jì)算圖顯存占用會(huì)翻好幾倍。4.3 現(xiàn)象檢索速度極慢單次查詢超過(guò) 5 秒如果圖庫(kù)有 10 萬(wàn)張圖片特征矩陣是(100000, 512)用 NumPy 做點(diǎn)積大約需要幾十毫秒不應(yīng)該超過(guò) 1 秒。如果慢到幾秒通常是兩個(gè)原因一是每次查詢都重新加載.npy文件磁盤(pán) I/O 成了瓶頸二是用了 Python 循環(huán)逐張計(jì)算相似度沒(méi)有用矩陣運(yùn)算。解決方法是把特征矩陣和路徑列表在系統(tǒng)啟動(dòng)時(shí)加載到內(nèi)存查詢時(shí)直接復(fù)用。如果內(nèi)存放不下考慮用 FAISS 建立索引它支持近似最近鄰搜索在億級(jí)向量上也能做到毫秒級(jí)響應(yīng)。對(duì)于畢業(yè)設(shè)計(jì)的圖庫(kù)規(guī)模NumPy 矩陣運(yùn)算完全夠用關(guān)鍵是把加載和計(jì)算分開(kāi)。4.4 現(xiàn)象不同圖片提取的特征幾乎一樣區(qū)分度低這通常是因?yàn)轭A(yù)處理出了問(wèn)題。比如把圖片直接縮放到 224×224 而沒(méi)有保持長(zhǎng)寬比導(dǎo)致圖像內(nèi)容變形或者歸一化時(shí)用了錯(cuò)誤的均值和標(biāo)準(zhǔn)差讓輸入分布偏離預(yù)訓(xùn)練時(shí)的分布。還有一種可能是取錯(cuò)了特征層如果取了block1_pool之后的淺層特征語(yǔ)義信息太弱不同類別的圖片特征差異不明顯。解決方法是嚴(yán)格按 ImageNet 標(biāo)準(zhǔn)做預(yù)處理先 Resize 到 256再 CenterCrop 到 224歸一化參數(shù)用mean[0.485, 0.456, 0.406]和std[0.229, 0.224, 0.225]。特征層選擇上block5_pool之后的 512 維特征在語(yǔ)義性和計(jì)算量之間平衡得最好。如果還是區(qū)分度低可以嘗試取fc2層的 4096 維特征或者對(duì)特征做 PCA 降維后再檢索。4.5 現(xiàn)象換一臺(tái)電腦或重新安裝環(huán)境后代碼跑不通畢業(yè)設(shè)計(jì)代碼需要在不同機(jī)器上演示環(huán)境依賴是常見(jiàn)翻車點(diǎn)。PyTorch 版本、torchvision 版本、NumPy 版本不匹配都可能導(dǎo)致 API 變化或權(quán)重加載失敗。比如models.vgg16(pretrainedTrue)在舊版本可用新版本推薦用weightsmodels.VGG16_Weights.IMAGENET1K_V1。解決方法是在項(xiàng)目根目錄放一個(gè)requirements.txt固定主要依賴的版本號(hào)。我一般會(huì)寫(xiě)torch1.12、torchvision0.13、numpy1.21、Pillow9.0、matplotlib3.5、tqdm4.60。如果答辯現(xiàn)場(chǎng)沒(méi)有網(wǎng)絡(luò)提前把預(yù)訓(xùn)練權(quán)重文件下載到本地加載時(shí)用weights參數(shù)指定本地路徑避免現(xiàn)場(chǎng)下載超時(shí)。另外路徑分隔符在 Windows 和 Linux 上不同代碼里統(tǒng)一用os.path.join而不是硬編碼斜杠。5. 進(jìn)階技巧用 PCA 降維和查詢擴(kuò)展把檢索精度再提一檔5.1 用 PCA 把 512 維特征壓到 128 維檢索速度翻倍512 維特征在幾萬(wàn)張圖庫(kù)上做檢索已經(jīng)夠快但如果你想把系統(tǒng)部署到內(nèi)存受限的環(huán)境或者想進(jìn)一步加速PCA 降維是性價(jià)比很高的選擇。PCA 的原理是找到特征方差最大的方向把原始特征投影到低維空間同時(shí)保留大部分信息量。對(duì)于 VGG16 的 512 維特征通常降到 128 維就能保留 95% 以上的方差檢索精度損失很小。實(shí)現(xiàn)上用 scikit-learn 的PCA類對(duì)圖庫(kù)特征矩陣做擬合然后把查詢特征也投影到同樣的主成分空間。注意 PCA 的均值中心化步驟擬合時(shí)用圖庫(kù)特征計(jì)算均值和主成分查詢時(shí)用同樣的均值做中心化不能重新計(jì)算。下面代碼展示了完整流程。from sklearn.decomposition import PCA import numpy as np # 加載圖庫(kù)特征 features np.load(index_output/features.npy) # (N, 512) # 擬合 PCA保留 128 維 pca PCA(n_components128, whitenTrue) features_pca pca.fit_transform(features) # (N, 128) # 保存 PCA 模型和降維后的特征 import joblib joblib.dump(pca, index_output/pca_model.pkl) np.save(index_output/features_pca.npy, features_pca) # 查詢時(shí)先提取 512 維特征再用同樣的 PCA 投影 query_vec extract_feature(query.jpg) # (512,) query_pca pca.transform(query_vec.reshape(1, -1)) # (1, 128) query_pca query_pca / np.linalg.norm(query_pca, axis1, keepdimsTrue) # 重新歸一化 # 檢索 similarities features_pca.dot(query_pca.squeeze()) top_indices np.argsort(similarities)[-10:][::-1]whitenTrue會(huì)讓降維后的特征每個(gè)維度方差為 1有助于提升余弦相似度的區(qū)分度。降維后需要重新做 L2 歸一化因?yàn)?PCA 變換會(huì)改變向量模長(zhǎng)。PCA 模型必須保存下來(lái)查詢時(shí)用同一個(gè)模型投影否則查詢特征和圖庫(kù)特征不在同一空間檢索結(jié)果會(huì)完全錯(cuò)誤。5.2 查詢擴(kuò)展用 Top-K 結(jié)果的均值特征做二次檢索查詢擴(kuò)展是一種經(jīng)典的檢索優(yōu)化技巧。它的思路是第一次檢索得到 Top-K 結(jié)果后把這些結(jié)果的特征向量取平均得到一個(gè)“擴(kuò)展查詢向量”再用這個(gè)新向量做第二次檢索。這樣做的好處是如果第一次檢索中有幾張同類圖片它們的平均特征會(huì)更接近該類別的中心從而把更多同類圖片拉進(jìn) Top-K。實(shí)現(xiàn)上第一次檢索用原始查詢特征取 Top-5 結(jié)果的特征計(jì)算加權(quán)平均相似度越高的權(quán)重越大然后歸一化得到擴(kuò)展查詢向量。第二次檢索用擴(kuò)展向量替換原始查詢向量。代碼改動(dòng)很小但效果在部分?jǐn)?shù)據(jù)集上能提升 3 到 5 個(gè)百分點(diǎn)的 Top-5 準(zhǔn)確率。def query_expansion(query_vec, features, top_k5, alpha0.5): # 第一次檢索 sims features.dot(query_vec) top_idx np.argsort(sims)[-top_k:][::-1] # 加權(quán)平均相似度作為權(quán)重 weights sims[top_idx] weights weights / weights.sum() expanded np.average(features[top_idx], axis0, weightsweights) # 與原始查詢向量混合 expanded alpha * expanded (1 - alpha) * query_vec expanded expanded / np.linalg.norm(expanded) return expandedalpha控制擴(kuò)展向量和原始向量的混合比例通常設(shè)在 0.3 到 0.7 之間。如果圖庫(kù)噪聲大alpha 取小一點(diǎn)避免被錯(cuò)誤結(jié)果帶偏。這個(gè)方法在畢業(yè)設(shè)計(jì)答辯時(shí)是一個(gè)很好的加分項(xiàng)因?yàn)樗w現(xiàn)了你對(duì)檢索算法的理解不止于“提取特征算距離”。5.3 一個(gè)我反復(fù)驗(yàn)證過(guò)的習(xí)慣先跑通 100 張圖的小閉環(huán)做了這么多版圖像檢索系統(tǒng)我最大的血淚經(jīng)驗(yàn)是不要一上來(lái)就把整個(gè)圖庫(kù)跑完。先挑 100 張圖片構(gòu)建索引用 5 張查詢圖測(cè)試確認(rèn) Top-5 結(jié)果肉眼看著合理再逐步擴(kuò)大圖庫(kù)規(guī)模。這個(gè)習(xí)慣幫我省下了大量等待批量提取的時(shí)間也讓我在早期就發(fā)現(xiàn)了預(yù)處理錯(cuò)誤、歸一化遺漏、路徑對(duì)應(yīng)錯(cuò)位等問(wèn)題。另一個(gè)習(xí)慣是每次修改特征提取邏輯后重新計(jì)算圖庫(kù)特征并覆蓋舊索引絕不混用不同版本的特征文件。特征文件和路徑列表必須成對(duì)出現(xiàn)版本一致這是系統(tǒng)可復(fù)現(xiàn)的底線。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取