智能识别猫猫
鸡蛋饼是什么猫
今天,有一位爱猫人士找到了我:“9月25日啦,炉石传说重新开服了!”
我:“哦!我知道这个,你是说,我现在该去领金卡了吗?”
爱猫人士:“不!是我看到他们又在讨论狗贼嘘嘘家的猫是什么品种的了!。”
狗贼叔叔是一位知名的老战士,在那样一个简单的下午,他组了一套平时最常用的卡牌,然后普普通通的登了一个顶,然后就再也没有回来。他有一只心爱的小猫叫做“鸡蛋饼”,大概是这样的:
我:“嗯,然后呢?”
爱猫人士:“我已经受够了有人管它叫做橘猫了!这些人对猫的种类太没有认知,张口就来!”
其实,鸡蛋饼是一只乳色的英短,或者也称为英短金渐层,这也是一个不常见的稀有品种了。英短就是人们常说的“蓝胖子”,这种猫一般都是蓝的或者蓝白的,因此,虽然很多人天天见“蓝胖子”,但是,遇到了鸡蛋饼,就认不出来了,这也不奇怪。
我:”那这不是显得你的水平高嘛?“
爱猫人士:”不,我觉得,你应该写一篇文章,给大家科普一下猫的种类的知识!“
啊这,科普猫的种类?我自己还认不全呢,不过,我想到了一个好办法,我应该训练一个ai,用来识别猫的种类。
资源下载
没有猫,我们也不能凭空变出猫来,因此,首先,我们需要得到一些猫的图片。
猫狗分类:https://www.kaggle.com/datasets/zippyz/cats-and-dogs-breeds-classification-oxford-dataset
比如说,这个数据集看起来不错,直接用这个好了。
我大致查阅了一下,感觉有些不太妙,关于英短的品种
这全是“蓝胖子”啊,一只英短金渐层也没有,如果就拿这个去给ai训练,到时候认不出鸡蛋饼的可能性非常高啊!
不过,这也正常,鸡蛋饼毕竟是稀有品种,而且这个数据集也有几年的历史了,那个时候,像鸡蛋饼这样的猫比现在还要更稀少,所以没有包括也正常。不过,我并不打算添加额外的图片进去,就先这样好了。
预处理
这个数据集原本叫做“cats and dogs“,肯定不只有猫,那还有狗呢!狗现在可不是我们需要的,首先,我们要想办法把狗删掉,但是,要怎么做呢!我怎么知道谁是猫,谁是狗啊?
还好,数据集的作者早就想到了,猫都是首字母大写的,狗都是首字母小写的,这样的话,我们只需要把首字母大写的图片提取出来就好了。
import os
import shutil
import pandas as pd
from sklearn.model_selection import train_test_split
dataset_path = "images"
cat_folder = os.path.join(dataset_path, "cats")
cat_data = []
os.makedirs(cat_folder, exist_ok=True)
for filename in os.listdir(dataset_path):
if filename.endswith(".jpg"):
if filename[0].isupper():
shutil.move(os.path.join(dataset_path, filename), os.path.join(cat_folder, filename))
for filename in os.listdir(cat_folder):
if filename.lower().endswith('.jpg'):
filename_no_ext = os.path.splitext(filename)[0]
try:
cat_breed, number = filename_no_ext.rsplit('_', 1)
cat_data.append({'filename': filename, 'breed': cat_breed})
except ValueError:
print(f"文件名格式不正确: {filename}")
cat_df = pd.DataFrame(cat_data)
breed_to_num = {breed: idx for idx, breed in enumerate(cat_df["breed"].unique())}
cat_df["breed_num"] = cat_df["breed"].map(breed_to_num)
train_df, test_df = train_test_split(df, test_size=0.2, stratify=cat_df["breed_num"], random_state=240925)
train_df.to_csv("train_dataset.csv", index=False)
test_df.to_csv("test_dataset.csv", index=False)
print("训练集样本数:", len(train_df))
print("测试集样本数:", len(test_df))
模型训练
import torch
import torch.nn as nn
import torchvision.models as models
import pandas as pd
import os
from PIL import Image
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
class CatDataset(Dataset):
def __init__(self, csv_file, img_dir, transform=None):
self.data_frame = pd.read_csv(csv_file)
self.img_dir = img_dir
self.transform = transform
def __len__(self):
return len(self.data_frame)
def __getitem__(self, idx):
img_name = os.path.join(self.img_dir, self.data_frame.iloc[idx, 0])
image = Image.open(img_name).convert("RGB")
label = int(self.data_frame.iloc[idx, 2])
if self.transform:
image = self.transform(image)
return image, label
transform = 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 = CatDataset(csv_file="train_dataset.csv", img_dir="cats", transform=transform)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_dataset = CatDataset(csv_file="test_dataset.csv", img_dir="cats", transform=transform)
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)
class CatClassifier(nn.Module):
def __init__(self, num_classes):
super(CatClassifier, self).__init__()
self.model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
self.model.fc = nn.Linear(self.model.fc.in_features, num_classes)
def forward(self, x):
return self.model(x)
num_classes = len(train_dataset.data_frame["breed_num"].unique())
model = CatClassifier(num_classes)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
num_epochs = 10
for epoch in range(num_epochs):
model.train()
running_loss = 0.0
correct = 0
total = 0
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item() * images.size(0)
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
epoch_loss = running_loss / len(train_loader.dataset)
epoch_acc = correct / total
print(f"Epoch [{epoch + 1}/{num_epochs}], Loss: {epoch_loss:.4f}, Accuracy: {epoch_acc:.4f}")
加载与使用
现在,我们已经得到了一个能够识别猫的种类的模型了,要怎么使用呢?
# 注意:你划分的数据集有可能不是这样的,要根据实际情况进行修改
breed_num_to_name = {11: 'British_Shorthair', 0: 'Maine_Coon', 3: 'Sphynx', 10: 'Egyptian_Mau', 2: 'Birman', 8: 'Bombay', 1: 'Siamese', 5: 'Ragdoll', 7: 'Abyssinian', 9: 'Russian_Blue', 4: 'Bengal', 6: 'Persian'}
def predict_image(image_path, threshold=0.8):
transform = transforms.Compose([
transforms.Resize(224, 224)
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]),
])
image = Image.open(image_path).convert("RGB")
input_image = transform(image).unsqueeze(0).to(device)
with torch.no_grad():
outputs = model(input_image)
probabilities = torch.nn.functional.softmax(outputs, dim=1)
max_prob, predicted = torch.max(probabilities, 1)
max_prob = max_prob.item()
predicted_class = predicted.item()
if max_prob >= threshold:
predicted_breed = breed_num_to_name.get(predicted_class)
print(f"这是一个: {predicted_breed}")
else:
print("这个我可能不认识~")
predict_image("cats/British_Shorthair_241.jpg", threshold=0.8)
最终结果
我们的ai确实已经可以认出测试集中猫的品种了,此时我们拿出来鸡蛋饼的照片:
我们的ai居然表示:“这个我可能不认识~”,怎么能这样,一定是打开的方式不对!再换一张!
结果还是一样的,“这个我可能不认识~”。唉,枉费我花费了这么多心血,不过其实也是很正常的事情啦,毕竟我们的训练集中,一张英短金渐层也没,这个时候,想把鸡蛋饼归类为蓝胖子一类,还是太不容易了!
相关链接:
普通的一个下午:https://www.bilibili.com/video/BV16w411N7FT/
历史直播回放(up主饕餮):https://space.bilibili.com/10579668/
ai狗贼唱心会很痛:https://www.bilibili.com/video/BV11m411S7rf/
看看鸡蛋饼,突然又有一些伤感,“国服炉石今又在,不见当年叠甲人。”,再排队>15分钟我就要进去了,那位战士说他的补偿不要了,可以给我吗?