一、DINOv3 + KNN 构建自监督图像分类模型
DINOv3是Meta2025年8月推出的视觉开源模型。核心优势在于通过大规模无监督预训练获得高度通用且语义丰富的图像表征能力。最大的亮点是DINOv3的骨干网络在完全冻结的情况下,仅通过添加轻量级任务头就能在多种任务上取得优异表现,无需微调即可直接应用。
这一特性可以改变传统视觉任务的开发流程。以往,面对一个新的视觉任务,开发者通常需要准备大量带标签的数据,并投入计算资源对预训练模型进行微调,以使其适应特定的数据分布和类别。现在依据DINOv3骨干网络强达的语义特征提取能力,无需骨干网络额外的训练,仅结合简易的线性任务头 或KNN、SVM、朴素贝叶斯等下游算法,即可快速构建效果良好的模型,进一步简化了模型构建的流程和复杂度,还减少了对标注数据的依赖。
如下图所示,展示了DINOv3提取特征获得的余弦相似度图,每张图中都呈现了被红叉标记的区域,与其他区域的相似程度:
Github 地址:https://github.com/facebookresearch/dinov3
本文就基于DINOv3强大的特征提取能力,构建DINOv3 + KNN的图像分类模型,整个过程无需对模型进行任何微调训练,仅提供少量样本由DINOv3提取语义特征,然后使用KNN构建空间聚类模型,后续预测图像同样先由DINOv3提取语义特征,然后由KNN依据余弦相似度找到最相似样品,进而预测目标的类别,KNN构建时间基本在秒级,所以开发过程几乎没有训练等待时长,另外就算遇到预测错误的情况仅需增加样本添加至KNN即可,而不像传统做法要重新训练模型。
该过程,在下面猫狗分类的场景中,在各种自然背景以及各种角度下仅提供猫狗各20张,共40张图像,即可在测试集图像(共2000张)上达到99%的准确率。
下面实验整体处理过程如下图所示:
本次实验使用dinov3-vitb16-pretrain-lvd1689m模型,huggleface地址:
https://huggingface.co/facebook/dinov3-vitb16-pretrain-lvd1689m
如果网络连接不通也可以使用ModelScope,地址如下:
https://modelscope.cn/models/facebook/dinov3-vitb16-pretrain-lvd1689m
以下是使用DINOv3模型提取图像特征的示例过程。模型会为每张输入图像生成一个768维的向量作为其语义表征。
fromtransformersimportAutoImageProcessor,AutoModelimporttorchfromPILimportImagedefmain():# 加载模型model_path="facebook/dinov3-vitb16-pretrain-lvd1689m"processor=AutoImageProcessor.from_pretrained(model_path)model=AutoModel.from_pretrained(model_path)device=torch.device('cuda:0'iftorch.cuda.is_available()else"cpu")model.to(device)model.eval()## 特征提取images=Image.open('img/dog.png')withtorch.no_grad():inputs=processor(images=images,return_tensors="pt").to(device)outputs=model(**inputs)image_features=outputs.last_hidden_state image_features=image_features.mean(dim=1)image_features=image_features[0]print(len(image_features))print(image_features)if__name__=='__main__':main()运行效果:
二、实验数据集介绍
实验数据集使用ModelScope上公开的猫狗二分类数据集,包含各种自然场景、各种角度下的猫和狗的图像,具有很好的多样性和挑战性。数据集地址如下:
https://modelscope.cn/datasets/XCsunny/cat_vs_dog_class
数据集包括: 训练集:6000张,验证集:2000张,测试集:2000张,由于我们无需模型训练,本次直接取验证集下的猫和狗分别前20张,共40张图像,作为样本构建KNN,然后对测试集的2000张图像进行预测和评估。
三、构建 KNN 特征空间模型
这里使用scikit-learn库中的KNeighborsClassifier来实现,并选择余弦相似度作为距离度量。
importosimporttimefromtransformersimportAutoImageProcessor,AutoModelfromsklearn.neighborsimportKNeighborsClassifierimporttorchfromPILimportImageimportjoblibfromtqdmimporttqdm## 验证集地址,下面每种分类只使用20张val_img_path="cat_vs_dog_class/val"## 类别标识classify={"cats":0,"dogs":1}# 加载模型model_path="facebook/dinov3-vitb16-pretrain-lvd1689m"processor=AutoImageProcessor.from_pretrained(model_path)model=AutoModel.from_pretrained(model_path)device=torch.device('cuda:0'iftorch.cuda.is_available()else"cpu")model.to(device)model.eval()defget_features(images):'''特征提取'''withtorch.no_grad():inputs=processor(images=images,return_tensors="pt").to(device)outputs=model(**inputs)image_features=outputs.last_hidden_state image_features=image_features.mean(dim=1)returnimage_features[0].cpu()defbuild_knn(knn_cache_model="knn_model.joblib"):'''构建KNN模型'''ifos.path.exists(knn_cache_model):returnjoblib.load(knn_cache_model)knn=KNeighborsClassifier(n_neighbors=5,metric='cosine')features,labels=[],[]forclinos.listdir(val_img_path):label=classify[cl]forimg_nameintqdm(os.listdir(os.path.join(val_img_path,cl))[:20],desc="Build:"+cl):image=Image.open(os.path.join(val_img_path,cl,img_name))feature=get_features(image)features.append(feature)labels.append(label)knn.fit(features,labels)# 保存KNN结构joblib.dump(knn,knn_cache_model,compress=3)returnknndefmain():## 构建 KNNt=time.time()knn=build_knn()print(f"构建KNN耗时:{(time.time()-t)}s")## 使用单个图像测试test_image_path='img/dog.png'test_image=Image.open(test_image_path)test_features=get_features(test_image)prediction=knn.predict([test_features])print(f"{test_image_path}--> 预测类别:{list(classify.keys())[prediction[0]]}")if__name__=='__main__':main()运行效果,构建KNN模型只需4.5s:
图像预测测试:
为了直观地展示模型的预测效果,这里从测试集中抽取部分图像进行预测并可视化。
importosfromtransformersimportAutoImageProcessor,AutoModelimporttorchfromPILimportImageimportjoblibimportrandomimportmatplotlib.pyplotasplt# 测试集test_img_path="cat_vs_dog_class/test"classify=["cat","dog"]# 加载模型model_path="facebook/dinov3-vitb16-pretrain-lvd1689m"processor=AutoImageProcessor.from_pretrained(model_path)model=AutoModel.from_pretrained(model_path)device=torch.device('cuda:0'iftorch.cuda.is_available()else"cpu")model.to(device)model.eval()defget_features(images):'''特征提取'''withtorch.no_grad():inputs=processor(images=images,return_tensors="pt").to(device)outputs=model(**inputs)image_features=outputs.last_hidden_state image_features=image_features.mean(dim=1)returnimage_features[0].cpu()defget_knn(knn_cache_model="knn_model.joblib"):'''直接读取上一步创建好的KNN'''returnjoblib.load(knn_cache_model)defmain():knn=get_knn()## 准备测试数据test_images=[]forclinos.listdir(test_img_path):forimg_nameinos.listdir(os.path.join(test_img_path,cl))[:8]:test_images.append(Image.open(os.path.join(test_img_path,cl,img_name)))## 随机打乱random.shuffle(test_images)## 预测# 分成四个一组test_images=list(zip(*[iter(test_images)]*4))forgintest_images:plt.figure(figsize=(8,8))fori,imageinenumerate(g):plt.subplot(2,2,i+1)feature=get_features(image)prediction=classify[knn.predict([feature])[0]]plt.imshow(image)plt.title(prediction)plt.show()if__name__=='__main__':main()预测效果:
四、大规模测试集测试
主要观察模型在测试集2000张图像上的准确率、精确率、召回率、以及F1分数:
importosfromtransformersimportAutoImageProcessor,AutoModelimporttorchfromPILimportImageimportjoblibfromtqdmimporttqdmfromsklearn.metricsimportf1_score,precision_score,recall_score# 测试集test_img_path="cat_vs_dog_class/test"classify={"cats":0,"dogs":1}# 加载模型model_path="facebook/dinov3-vitb16-pretrain-lvd1689m"processor=AutoImageProcessor.from_pretrained(model_path)model=AutoModel.from_pretrained(model_path)device=torch.device('cuda:0'iftorch.cuda.is_available()else"cpu")model.to(device)model.eval()defget_features(images):'''特征提取'''withtorch.no_grad():inputs=processor(images=images,return_tensors="pt").to(device)outputs=model(**inputs)image_features=outputs.last_hidden_state image_features=image_features.mean(dim=1)returnimage_features[0].cpu()defget_knn(knn_cache_model="knn_model.joblib"):'''直接读取上一步创建好的KNN'''returnjoblib.load(knn_cache_model)defmain():knn=get_knn()# 用于存储所有预测和真实标签predictions=[]true_labels=[]forclinos.listdir(test_img_path):label=classify[cl]forimg_nameintqdm(os.listdir(os.path.join(test_img_path,cl)),desc="Prediction:"+cl):image=Image.open(os.path.join(test_img_path,cl,img_name))feature=get_features(image)prediction=knn.predict([feature])[0]predictions.append(prediction)true_labels.append(label)# 计算各项指标total=len(true_labels)ok=sum([1foriinrange(total)ifpredictions[i]==true_labels[i]])accuracy=ok/total*100# 计算F1分数f1=f1_score(true_labels,predictions)# 额外计算精确率和召回率precision=precision_score(true_labels,predictions)recall=recall_score(true_labels,predictions)# 输出结果print("评估结果")print("="*50)print(f"总样本数 (Total):{total}")print(f"正确数 (Correct):{ok}")print(f"准确率 (Accuracy):{accuracy:.2f}%")print(f"精确率 (Precision):{precision:.4f}")print(f"召回率 (Recall):{recall:.4f}")print(f"F1分数 (F1-Score):{f1:.4f}")if__name__=='__main__':main()运行效果如下,准确率达到99%,实测如果提供更多样本可以达到更高的准确率: