目录
划分数据集
读取数据集
使用数据集
预测结果生成
单次预测
结果生成
使用resnet网络预测植物幼苗分类。
kaggle链接:weiliutao | Novice | Kaggle
给的数据分为三部门,第一个是test文件夹,存放所有要预测的图片。第二个是train文件夹,里面是各个已经分好类别的植物幼苗图片文件夹,用来进行训练模型。还有一个提交样例的csv文件。
划分数据集
由于我们在训练网络时要关注模型在每一轮的正确率,因此需要将train(在实现时防止混淆我将这个名称改为train1)下的数据划分为训练集和验证集,即将train1文件夹划分为train和val文件夹,一般以0.9:0.1进行划分。train和val文件夹下仍然是各个种类幼苗的文件夹,使用一个划分脚本来实现。
代码源自:同济子豪兄的个人空间_哔哩哔哩_bilibili
import osfrom shutil import copy, rmtreeimport randomdef mk_file(file_path: str): if os.path.exists(file_path): # 如果文件夹存在,则先删除原文件夹在重新创建 rmtree(file_path) os.makedirs(file_path)def main(): # 保证随机可复现 random.seed(0) # 将数据集中10%的数据划分到验证集中 split_rate = 0.1 # 指向你解压后的flower_photos文件夹 #这里的os.getcwd方法是获取当前代码所在路径 #我的数据集在上上一级目录下的data_set的plant下的train_1中 cwd = os.getcwd() data_root = os.path.join(cwd, "../data_set/plant") origin_flower_path = os.path.join(data_root, "train_1") assert os.path.exists(origin_flower_path), "path '{}' does not exist.".format(origin_flower_path) flower_class = [cla for cla in os.listdir(origin_flower_path) if os.path.isdir(os.path.join(origin_flower_path, cla))] # 建立保存训练集的文件夹,生成在data_root目录下 train_root = os.path.join(data_root, "train") mk_file(train_root) for cla in flower_class: # 建立每个类别对应的文件夹 mk_file(os.path.join(train_root, cla)) # 建立保存验证集的文件夹 val_root = os.path.join(data_root, "val") mk_file(val_root) for cla in flower_class: # 建立每个类别对应的文件夹 mk_file(os.path.join(val_root, cla)) for cla in flower_class: cla_path = os.path.join(origin_flower_path, cla) images = os.listdir(cla_path) num = len(images) # 随机采样验证集的索引 eval_index = random.sample(images, k=int(num*split_rate)) for index, image in enumerate(images): if image in eval_index: # 将分配至验证集中的文件复制到相应目录 image_path = os.path.join(cla_path, image) new_path = os.path.join(val_root, cla) copy(image_path, new_path) else: # 将分配至训练集中的文件复制到相应目录 image_path = os.path.join(cla_path, image) new_path = os.path.join(train_root, cla) copy(image_path, new_path) print("\r[{}] processing [{}/{}]".format(cla, index+1, num), end="") # processing bar print() print("processing done!")if __name__ == '__main__': main()
读取数据集
在train模块中使用这个数据集时:首先将路径指定到存放train和val的文件夹下,也就是plant,这里我存储的位置是上上级目录的data_set文件夹下的plant。
data_root = os.path.abspath(os.path.join(os.getcwd(), "../..")) # get data root pathimage_path = os.path.join(data_root, "data_set", "plant") # flower data set pathassert os.path.exists(image_path), "{} path does not exist.".format(image_path)train_dataset = datasets.ImageFolder(root=os.path.join(image_path, "train"), transform=data_transform["train"])
找到文件夹后使用torchvision下的datasets包的ImageFolder方法读取‘train’文件中的训练图片,这里的transform是对图片做出的处理,先不管。
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=nw)validate_dataset = datasets.ImageFolder(root=os.path.join(image_path, "val"), transform=data_transform["val"])val_num = len(validate_dataset)validate_loader = torch.utils.data.DataLoader(validate_dataset, batch_size=batch_size, shuffle=False, num_workers=nw)
train_loader是使用DataLoader方法处理刚刚读取的train_dataset,batch_size是将图片按照给定的batch_size分组,shuffle是否打乱顺序,num_workers是使用cpu或GPU的个数。
有了数据集,便能使用神经网络进行训练。在训练时如何使用这些图片。
使用数据集
tqdm是为了记录时间。
通过enumerate遍历训练集(此时的训练集分为若干个batch_size大小的集合),每次step都会得到一个batch_size大小的集合,分别为images集合和labels集合,将images送到模型得到结果。再将结果与真实标签label作损失函数处理,再反向传播。
for epoch in range(epochs): # train net.train() running_loss = 0.0 train_bar = tqdm(train_loader, file=sys.stdout) for step, data in enumerate(train_bar): images, labels = data optimizer.zero_grad() logits = net(images.to(device)) loss = loss_function(logits, labels.to(device)) loss.backward() optimizer.step() # print statistics running_loss += loss.item() train_bar.desc = "train epoch[{}/{}] loss:{:.3f}".format(epoch + 1, epochs, loss) # validate net.eval() acc = 0.0 # accumulate accurate number / epoch with torch.no_grad(): val_bar = tqdm(validate_loader, file=sys.stdout) for val_data in val_bar: val_images, val_labels = val_data outputs = net(val_images.to(device)) # loss = loss_function(outputs, test_labels) predict_y = torch.max(outputs, dim=1)[1] acc += torch.eq(predict_y, val_labels.to(device)).sum().item() val_bar.desc = "valid epoch[{}/{}]".format(epoch + 1, epochs) val_accurate = acc / val_num print('[epoch %d] train_loss: %.3f val_accuracy: %.3f' % (epoch + 1, running_loss / train_steps, val_accurate)) if val_accurate > best_acc: best_acc = val_accurate torch.save(net.state_dict(), save_path) print('Finished Training')
预测结果生成
因为不会将一个列表写入到一个csv文件中,所以搜了一下写了个测试代码:
import pandas as pdimport numpy as np# predict_kind = np.array([np.arange(794) , np.arange(2)] , dtype=str)# predict_kind[0][0]='12323'# print(predict_kind)# import numpy as np# m = np.array([np.arange(2), np.arange(5)], dtype=str) # 创建一个二维数组# m[0][1] = "love"# print(m)# print(m[0][1])# a = np.array(794*2,dtype=object)# a[1] = "12sdsds"# print(a)a = [[] for i in range(5)]a[0].append("asdasda")a[0].append("ppppp")a[1].append(("dddd"))print(a)data1 = pd.DataFrame(a)data1.to_csv('d.csv')
测试了很多发现最后没有注释的可以实现,因为我想要两列数据,一列是图片名称,一列是预测值,在此处定义一个二维列表,每个一维维度都代表一行数据,也就是一行中的两个数据,将列表转换为DataFrame格式才能写入csv文件中。
单次预测
这里我稍微改了预测方法为带参方法,传入的是图片的路径,进行预处理后(和训练时方式一样)将这张图片拿给模型,模型给出概率最大的结果。
Image是PIL包中的类,可以通过给定图片路径拿到图片
data_transform是对图片做出的处理,这时候的图片是一个三维[C,H,W],在最前面加上一个维度。
def result(image_name): device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") data_transform = transforms.Compose( [transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])]) # load image img_path = image_name assert os.path.exists(img_path), "file: '{}' dose not exist.".format(img_path) img = Image.open(img_path) #plt.imshow(img) # [N, C, H, W] img = data_transform(img) # expand batch dimension img = torch.unsqueeze(img, dim=0) # read class_indict json_path = './class_indices.json' assert os.path.exists(json_path), "file: '{}' dose not exist.".format(json_path) with open(json_path, "r") as f: class_indict = json.load(f) # create model model = resnet101(num_classes=12).to(device) # load model weights weights_path = "./resNet101.pth" assert os.path.exists(weights_path), "file: '{}' dose not exist.".format(weights_path) model.load_state_dict(torch.load(weights_path, map_location=device)) # prediction model.eval() with torch.no_grad(): # predict class output = torch.squeeze(model(img.to(device))).cpu() predict = torch.softmax(output, dim=0) predict_cla = torch.argmax(predict).numpy() return class_indict[str(predict_cla)]
model(img)将图片给到模型,再压缩batch方向维度。通过softmax处理得到概率分布。通过argmax找到最大值所对应的索引。在class_indict找到索引对应的类别。
结果生成
通过os.listdir可以遍历文件夹下的所有文件名称,先将名称加入进去,将路径加上图片名称传给result,将每个预测结果存放到名称后面,最后转换为DataFrame存储到CSV文件中。
data_root = os.path.abspath(os.path.join(os.getcwd(), "../..")) # get data root path image_path = os.path.join(data_root, "data_set", "plant","test") # flower data set path predict_kind = [[] for i in range(794)] i=0 for filename in os.listdir(image_path): #image_name.append(filename) predict_kind[i].append(filename) path_image = os.path.join(image_path, filename) predict_kind[i].append(result(path_image)) #print(path_image) #predict_kind.append(result(path_image)) i+=1 print(i) #print(predict_kind) data1 = pd.DataFrame(predict_kind) data1.to_csv('predict_2.csv')