一、赛事背景 农作物病虫害严重制约着农业生产,因为农作物病虫害种类多、密度大,极易造成农作物大量减产。同时由于传统人眼识别病虫害的方法速度较慢、准确度较低,会导致农药的滥用,破坏自然环境。如今随着精准农业和智慧农业概念的兴起和发展,利用信息技术辅助农业生产,实现对农作物病虫害的智能识别和检测,以减少不必要的农药喷施,对保护生态系统均衡,保障农作物安全生产,提高农作物的质量方面,有着十分重要的促进作用。
二、赛事任务 最为有效的病虫害识别方法是图片识别,本次大赛提供了大量农民在田间地头拍摄的叶菜的病虫害图片,参赛选手需基于提供的样本构建模型,实现叶菜的病虫害图像识别,即为图片分类。
初赛一阶段:6月21日到8月18日。
初赛二阶段:8月19日到8月20日。系统将在8月19日00:00更换测试数据,参赛队伍需要再次下载数据
三、评审规则 1.数据说明 本次比赛为参赛选手提供了叶菜的病虫害图像数据:包括图像及其所属病虫害标签。数据主体为农民在不同环境条件下拍摄的叶菜农作物图像,每张图像的主体突出度,背景复杂程度、光照条件,图像清晰度均存在一定差别。选手可自己搜集训练数据提升模型识别效果,需要将搜集的图片上传至官方云盘。图片已按类别放在不同文件夹内,文件夹名称即为图片的category_id。
1:用药不当
2:疫病
3:炭疽病
四、数据展示
基线分数
# 数据集解压 !cd 'data/data98431' && unzip -q bingchonghai.zip
数据EDA
数据预处理及读取
# 导入所需要的库 from sklearn.utils import shuffle import os import pandas as pd import numpy as np from PIL import Image import paddle import paddle.nn as nn from paddle.io import Dataset import paddle.vision.transforms as T import paddle.nn.functional as F from paddle.metric import Accuracy import warnings warnings.filterwarnings("ignore") # 读取数据 train_images = pd.read_csv('work/train1.csv') train_images = shuffle(train_images) # 划分训练集和校验集 all_size = len(train_images) # print(all_size) train_size = int(all_size * 0.8) train_image_list = train_images[:train_size] val_image_list = train_images[train_size:] df = train_image_list train_image_path_list = df['image_id'].values label_list = df['category_id'].values label_list = paddle.to_tensor(label_list, dtype='int64') train_label_list = paddle.nn.functional.one_hot(label_list, num_classes=3) val_image_path_list = val_image_list['image_id'].values val_label_list = val_image_list['category_id'].values val_label_list = paddle.to_tensor(val_label_list, dtype='int64') val_label_list = paddle.nn.functional.one_hot(val_label_list, num_classes=3) # 定义数据预处理 data_transforms = T.Compose([ T.Resize(size=(224, 224)), T.RandomRotation(90), T.RandomCrop(224), T.RandomHorizontalFlip(0.5), T.RandomVerticalFlip(0.5), T.Transpose(), # HWC -> CHW T.Normalize( mean=[0, 0, 0], # 归一化 std=[255, 255, 255], to_rgb=True) ])
# 构建Dataset class MyDataset(paddle.io.Dataset): """ 步骤一:继承paddle.io.Dataset类 """ def __init__(self, train_img_list, val_img_list,train_label_list,val_label_list, mode='train'): """ 步骤二:实现构造函数,定义数据读取方式,划分训练和测试数据集 """ super(MyDataset, self).__init__() self.img = [] self.label = [] # 借助pandas读csv的库 self.train_images = train_img_list self.test_images = val_img_list self.train_label = train_label_list self.test_label = val_label_list if mode == 'train': # 读train_images的数据 for img,la in zip(self.train_images, self.train_label): self.img.append('data/data98431/bingchonghai/train/'+img) self.label.append(la) else: # 读test_images的数据 for img,la in zip(self.test_images, self.test_label): self.img.append('data/data98431/bingchonghai/train/'+img) self.label.append(la) def load_img(self, image_path): # 实际使用时使用Pillow相关库进行图片读取即可,这里我们对数据先做个模拟 image = Image.open(image_path) return image def __getitem__(self, index): """ 步骤三:实现__getitem__方法,定义指定index时如何获取数据,并返回单条数据(训练数据,对应的标签) """ image = self.load_img(self.img[index]) label = self.label[index] # label = paddle.to_tensor(label) return data_transforms(image), paddle.nn.functional.label_smooth(label) def __len__(self): """ 步骤四:实现__len__方法,返回数据集总数目 """ return len(self.img)
#train_loader train_dataset = MyDataset(train_img_list=train_image_path_list, val_img_list=val_image_path_list, train_label_list=train_label_list, val_label_list=val_label_list, mode='train') train_loader = paddle.io.DataLoader(train_dataset, places=paddle.CPUPlace(), batch_size=196, shuffle=True, num_workers=0) #val_loader val_dataset = MyDataset(train_img_list=train_image_path_list, val_img_list=val_image_path_list, train_label_list=train_label_list, val_label_list=val_label_list, mode='test') val_loader = paddle.io.DataLoader(val_dataset, places=paddle.CPUPlace(), batch_size=196, shuffle=True, num_workers=0)
模型训练及保存
# 调用飞桨框架的VisualDL模块,保存信息到目录中。 callback = paddle.callbacks.VisualDL(log_dir='visualdl_log_dir') callbacks = paddle.callbacks.EarlyStopping( 'loss', mode='min', patience=50, verbose=1, min_delta=0, baseline=0.99, save_best_model=True) from visualdl import LogReader, LogWriter args={ 'logdir':'./vdl', 'file_name':'vdlrecords.model.log', 'iters':0, } # 配置visualdl write = LogWriter(logdir=args['logdir'], file_name=args['file_name']) #iters 初始化为0 iters = args['iters'] #自定义Callback class Callbk(paddle.callbacks.Callback): def __init__(self, write, iters=0): self.write = write self.iters = iters def on_train_batch_end(self, step, logs): self.iters += 1 #记录loss self.write.add_scalar(tag="loss",step=self.iters,value=logs['loss'][0]) #记录 accuracy self.write.add_scalar(tag="acc",step=self.iters,value=logs['acc'])
# from work.se_resnext import SE_ResNeXt50_vd_32x4d from work.res2net import Res2Net50_vd_26w_4s # 模型封装 model_res = Res2Net50_vd_26w_4s(class_dim=3) model = paddle.Model(model_res) # 定义优化器 optim = paddle.optimizer.Adam(learning_rate=1e-4, parameters=model.parameters()) # 配置模型 model.prepare( optim, paddle.nn.CrossEntropyLoss(soft_label=True), Accuracy() ) model.load('work/Res2Net50_vd_26w_4s_pretrained.pdparams',skip_mismatch=True) save_dir = '/home/aistudio/work' # 模型训练与评估 model.fit(train_loader, val_loader, log_freq=1, save_freq=10, save_dir=save_dir, epochs=20, callbacks=[Callbk(write=write, iters=iters), callbacks], verbose=1, )
# 保存模型参数 # model.save('Hapi_MyCNN') # save for training model.save('Hapi_MyCNN1_res2', False) # save for inference
模型预测
- 模型融合与TTA
test time augmentation,TTA
可将准确率提高若干个百分点,它就是测试时增强(test time augmentation, TTA)。这里会为原始图像造出多个不同版本,包括不同区域裁剪和更改缩放程度等,并将它们输入到模型中;然后对多个版本进行计算得到平均输出,作为图像的最终输出分数。有作弊的嫌疑。这种技术很有效,因为原始图像显示的区域可能会缺少一些重要特征,在模型中输入图像的多个版本并取平均值,能解决上述问题。
模型融合
我可能是搞了一个比较邪的模型融合,哈哈哈,分别是单独训练,在预测的时候加权融合。
import os, time import matplotlib.pyplot as plt import paddle from PIL import Image import numpy as np from paddle.vision import transforms import time import pandas as pd import patta as tta paddle.set_device('gpu:0') model1 = paddle.jit.load("Hapi_MyCNN1") model2 = paddle.jit.load("Hapi_MyCNN1_res2") model = tta.ClassificationTTAWrapper(model, tta.aliases.ten_crop_transform(224,224)) def read_img(path): img = Image.open(path).convert('RGB') transform_valid = transforms.Compose([ transforms.Resize((224, 224)), # transforms.Transpose(), transforms.ToTensor(), transforms.Normalize( mean=[0, 0, 0], # 归一化 std=[255, 255, 255], to_rgb=True) ]) img_tensor = transform_valid(img).unsqueeze(0) return img_tensor def infer_img(path, model1, model2): ''' 模型预测 ''' model1.eval() model2.eval() data = read_img(path) out = model1(data) * 0.4 + model2(data) * 0.6 # print(out[0]) # print(paddle.nn.functional.softmax(out)[0]) # 若模型中已经包含softmax则不用此行代码。 lab = np.argmax(out.numpy()) # argmax():返回最大数的索引 # label_pre.append(lab) return int(lab) + 1 img_list = os.listdir('data/data98431/bingchonghai/test') pre_list = [] for i in range(len(img_list)): a = time.time() pre_list.append(infer_img(path='data/data98431/bingchonghai/test/' + img_list[i], model1=model1, model2=model2)) b = time.time() print(b-a) img = pd.DataFrame(img_list) img = img.rename(columns = {0:"image_id"}) img['category_id'] = pre_list img.to_csv('submit123456.csv', index=False)
总结
大家可以看到,我们的基线分数并不高,所以这很可能是一个失败的基线,模型预测的效果并不好,这个的难点就是细粒度分类,每个类别之间的区分度不是很大,所以仅仅是用这种方式还不够,大家还可以尝试一下元学习等方法,或者是不使用最后的softmax进行分类,使用距离进行类别判断。