iFLYTEK基于PaddleClas2.2的广告分类baseline非官方

简介: iFLYTEK基于PaddleClas2.2的广告分类baseline非官方

0 赛题背景

赛题链接:广告图片素材分类算法挑战赛

https://challenge.xfyun.cn/topic/info?type=ad-2021

讯飞AI营销是科大讯飞集团在数字广告领域发展的重要业务,基于深耕多年的人工智能技术和大数据积累,赋予营销智慧创新的大脑,以健全的产品矩阵和全方位的服务,帮助广告主用AI技术实现营销效能的全面提升,打造数字营销新生态。


目前,AI营销平台除自有媒体外,已在功能社交、休闲娱乐、专业进阶、衣食住行等类型的TOP媒体实现规模化覆盖,并且覆盖媒体数量仍在快速增长。如何确保在成百上千的媒体上投放符合要求的广告素材(例如,教育类APP需要投放适合青少年的广告内容),是素材审核迫切需要解决的问题。而作为最常见的广告素材类型之一,图片的自动分类将会极大提高审核效率。


0.1 赛事任务

本次分类算法任务中,讯飞AI营销将提供海量现网流量中的广告图片素材作为训练数据,参赛选手基于提供的训练数据构建模型,实现自动化广告图片素材分类。


0.2 实现思路

整体采用paddleclas2.2进行图像分类,然后训练过程中VisualDL可视化观察训练结果,然后适度调参。

本项目采用快速上手方案中的ShuffleNetV2_x0_25模型进行训练。


1. 数据说明

本次比赛为参赛选手提供的数据分为训练集、测试集、提交样例三类文件:

训练集:包含5万+广告素材图片,100多个类别,几十种图片尺寸;且图片已按类别放在不同文件夹内,文件夹名称即为素材图片的category_id。


测试集:包含6千余张广告素材图片,放在同一个文件夹内,图片文件的名称即为image_id。


提交样例:表头为image_id和category_id的CSV文件,选手提交数据时需要将测试集的图片id与模型预测的类别id按样例格式填入CSV中,进行提交。


比赛分为初赛和复赛两个阶段,初赛会公布训练集与初赛测试集;通过初赛的选手,进入复赛后会获取新的复赛测试集。


声明:本次提供的广告素材图片来源于讯飞、淘宝、京东、拼多多、苏宁、唯品会、快手、抖音、饿了么等众多广告主,任何人不可以任何形式使用、传播、披露、授权他人使用,请仅用于研究和学术目的。


2. 评估指标

本次比赛根据提交的结果文件,采用分类准确率作为评估指标:预测正确的图片占总测试集图片的比例。


3. 评测及排行

1、初赛和复赛均提供下载数据,选手在本地进行算法调试,在比赛页面提交结果。

2、每支团队每天最多提交3次。

3、排行按照得分从高到低排序,排行榜将选择团队的历史最优成绩进行排名。


4. 作品提交要求

  1. 文件格式:按照csv格式提交
  2. 文件大小:无要求
  3. 提交次数限制:每支队伍每天最多3次
  4. 文件详细说明:
    以csv格式提交,编码为UTF-8,第一行为表头;


提交格式见样例

5.不需要上传其他文件


5. 赛程规则和奖励

参见官方比赛网站。


1 数据处理

仍是老规矩

  1. 观察数据集结构
  2. 改造成两个txt文件
#解压数据集到跟目录先 数据量挺大的耐心等待45秒左右
!unzip -oq /home/aistudio/data/data102279/科大讯飞股份有限公司_广告图片素材分类算法挑战赛.zip


正如赛题所说,结构为,

训练集:包含137个类别,且图片已按类别放在不同文件夹内,文件夹名称即为素材图片的category_id。


测试集:包含6千余张广告素材图片,放在同一个文件夹内,图片文件的名称即为image_id。


那么就简单了,正如看过我上一个PaddleClas2.2实现奥特曼分类的人应该都知道如何解决了!

# 先把paddleclas安装上再说
# 安装paddleclas以及相关三方包(好像studio自带的已经够用了,无需安装了)
!git clone https://gitee.com/paddlepaddle/PaddleClas.git -b release/2.2
# 我这里安装相关包时,花了30几分钟还有错误提示,不管他即可
#!pip install --upgrade -r PaddleClas/requirements.txt -i https://mirror.baidu.com/pypi/simple
Cloning into 'PaddleClas'...
remote: Enumerating objects: 538, done.[K
remote: Counting objects: 100% (538/538), done.[K
remote: Compressing objects: 100% (323/323), done.[K
remote: Total 15290 (delta 344), reused 349 (delta 210), pack-reused 14752[K
Receiving objects: 100% (15290/15290), 113.56 MiB | 8.20 MiB/s, done.
Resolving deltas: 100% (10236/10236), done.
Checking connectivity... done.
# 先导入库 (这个项目不一定都用得到,我习惯了先把这一堆搬过来)
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 random
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/fluid/layers/utils.py:26: DeprecationWarning: `np.int` is a deprecated alias for the builtin `int`. To silence this warning, use `int` by itself. Doing this will not modify any behavior and is safe. When replacing `np.int`, you may wish to use e.g. `np.int64` or `np.int32` to specify the precision. If you wish to review your current use, check the release note link for additional information.
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  def convert_to_list(value, n, name, dtype=np.int):
# 忽略(垃圾)警告信息
# 在python中运行代码经常会遇到的情况是——代码可以正常运行但是会提示警告,有时特别讨厌。
# 那么如何来控制警告输出呢?其实很简单,python通过调用warnings模块中定义的warn()函数来发出警告。我们可以通过警告过滤器进行控制是否发出警告消息。
import warnings
warnings.filterwarnings("ignore")
# 更改文件夹名字,规范一些
!mv 初赛_测试集 testdata
!mv 训练集 traindata
# -*- coding: utf-8 -*-
# 根据官方paddleclas的提示,我们需要把图像变为两个txt文件
# train_list.txt(训练集)
# val_list.txt(验证集)
# 先把路径搞定 比如:训练集/0/77398.jpg ,读取到并写入txt 
dirpath = "traindata"
# 先得到总的txt后续再进行划分,因为要划分出验证集,所以要先打乱,因为原本是有序的
def get_all_txt():
    all_list = []
    i = 0
    for root,dirs,files in os.walk(dirpath): # 分别代表根目录、文件夹、文件
        for file in files:
            i = i + 1 
            # 文件中每行格式: 图像相对路径      图像的label_id(注意:中间有空格)。              
            #                traindata/0/77398.jpg        0
            #                traindata/101/6259.jpg     101
            #all_list.append(os.path.join(root,file)+" "+dirs+"\n")
            imgpath = os.path.join(root,file)
            category_id = os.path.join(root,file).split("/")
            all_list.append(imgpath+" "+category_id[1]+"\n")
    allstr = ''.join(all_list)
    f = open('all_list.txt','w',encoding='utf-8')
    f.write(allstr)
    return all_list , i
all_list,all_lenth = get_all_txt()
print(all_lenth)
51273
# 先把数据打乱,然后按照比例划分数据集 数据有5万多,所以9:1即可
random.shuffle(all_list)
train_size = int(all_lenth * 0.85)
train_list = all_list[:train_size]
val_list = all_list[train_size:]
print(len(train_list))
print(len(val_list))
43582
7691
# 运行cell,生成txt 
train_txt = ''.join(train_list)
f_train = open('train_list.txt','w',encoding='utf-8')
f_train.write(train_txt)
f_train.close()
print("train_list.txt 生成成功!")
# 运行cell,生成txt
val_txt = ''.join(val_list)
f_val = open('val_list.txt','w',encoding='utf-8')
f_val.write(val_txt)
f_val.close()
print("val_list.txt 生成成功!")
train_list.txt 生成成功!
val_list.txt 生成成功!
# 将图片移动到paddleclas下面的数据集里面
# 至于为什么现在移动,也是我的一点小技巧,防止之前移动的话,生成的txt的路径是全路径,反而需要去掉路径的一部分
!mv traindata/ PaddleClas/dataset/
!mv testdata/ PaddleClas/dataset/
!mv all_list.txt PaddleClas/dataset/traindata
!mv train_list.txt PaddleClas/dataset/traindata
!mv val_list.txt PaddleClas/dataset/traindata


2 采用paddleclas进行训练

数据集核实完搞定成功的前提下,可以准备更改原文档的参数进行实现自己的图片分类了!

#windows在cmd中进入PaddleClas根目录,执行此命令
%cd PaddleClas
!ls
/home/aistudio/PaddleClas
dataset  hubconf.py   MANIFEST.in    README_ch.md  requirements.txt
deploy   __init__.py  paddleclas.py  README_en.md  setup.py
docs   LICENSE      ppcls      README.md     tools


2.1 修改配置文件

主要是以下几点:分类数、图片总量、训练和验证的路径、图像尺寸、训练和预测的num_workers: 0才可以在aistudio跑通。


路径如下:

PaddleClas/ppcls/configs/ImageNet/ResNet/ResNet50.yaml

# global configs
Global:
  checkpoints: null
  pretrained_model: null
  output_dir: ./output/
  device: gpu
  save_interval: 10
  eval_during_train: True
  eval_interval: 3
  epochs: 150
  print_batch_step: 10
  use_visualdl: True
  # used for static mode and model export
  image_shape: [3, 224, 224]
  save_inference_dir: ./inference
  # training model under @to_static
  to_static: False
# model architecture
Arch:
  name: ResNet50
  class_num: 137
# loss function config for traing/eval process
Loss:
  Train:
    - CELoss:
        weight: 1.0
  Eval:
    - CELoss:
        weight: 1.0
Optimizer:
  name: Momentum
  momentum: 0.9
  lr:
    name: Piecewise
    learning_rate: 0.015
    decay_epochs: [30, 60, 90]
    values: [0.1, 0.01, 0.001, 0.0001]
  regularizer:
    name: 'L2'
    coeff: 0.0005
# data loader for train and eval
DataLoader:
  Train:
    dataset:
      name: ImageNetDataset
      image_root: ./dataset/
      cls_label_path: ./dataset/traindata/train_list.txt
      transform_ops:
        - DecodeImage:
            to_rgb: True
            channel_first: False
        - ResizeImage:
            resize_short: 256
        - CropImage:
            size: 224
        - RandFlipImage:
            flip_code: 1
        - NormalizeImage:
            scale: 1.0/255.0
            mean: [0.485, 0.456, 0.406]
            std: [0.229, 0.224, 0.225]
            order: ''
    sampler:
      name: DistributedBatchSampler
      batch_size: 128
      drop_last: False
      shuffle: True
    loader:
      num_workers: 0
      use_shared_memory: True
  Eval:
    dataset: 
      name: ImageNetDataset
      image_root: ./dataset/
      cls_label_path: ./dataset/traindata/val_list.txt
      transform_ops:
        - DecodeImage:
            to_rgb: True
            channel_first: False
        - ResizeImage:
            resize_short: 256
        - CropImage:
            size: 224
        - NormalizeImage:
            scale: 1.0/255.0
            mean: [0.485, 0.456, 0.406]
            std: [0.229, 0.224, 0.225]
            order: ''
    sampler:
      name: DistributedBatchSampler
      batch_size: 128
      drop_last: False
      shuffle: True
    loader:
      num_workers: 0
      use_shared_memory: True
Infer:
  infer_imgs: dataset/testdata/a237.jpg
  batch_size: 10
  transforms:
    - DecodeImage:
        to_rgb: True
        channel_first: False
    - ResizeImage:
        resize_short: 256
    - CropImage:
        size: 224
    - NormalizeImage:
        scale: 1.0/255.0
        mean: [0.485, 0.456, 0.406]
        std: [0.229, 0.224, 0.225]
        order: ''
    - ToCHWImage:
  PostProcess:
    name: Topk
    topk: 5
    class_id_map_file: dataset/label_list.txt
Metric:
  Train:
    - TopkAcc:
        topk: [1, 5]
  Eval:
    - TopkAcc:
        topk: [1, 5]


2.2 修改类别文件

其实,这个图片分类也没有给出具体的类别的名称,但是为了输出的结果好看,还是原样放上一份。

已经文件夹左侧

# 回到主目录
%cd ..
/home/aistudio
!mv label_list.txt PaddleClas/dataset
# 重新回到paddleclas下
%cd PaddleClas
!ls
[Errno 2] No such file or directory: 'PaddleClas'
/home/aistudio/PaddleClas
dataset  hubconf.py   MANIFEST.in    ppcls     README.md       tools
deploy   __init__.py  output       README_ch.md  requirements.txt
docs   LICENSE      paddleclas.py  README_en.md  setup.py


2.3 训练

# 开始训练  200轮
# 提示,运行过程中存在坏图的情况,但是不用担心,训练过程不受影响。后续预测的时候会有影响会告知你如何处理坏图
!python tools/train.py \
    -c ./ppcls/configs/ImageNet/ResNet/ResNet50.yaml


200轮的训练图如下

84dd9945cb7aad83e9345ece02c084de.png

73c4e108a6eca1759a72d876adf2aad3.png

b80b3f7603a585a324ad78adc5edc1f9.png


3 评估


简单的记录一下评估记录

Column 1 Column 2
best_model [Avg]CELoss: 1.32663, loss: 1.32663, top1: 0.71421, top5: 0.87973
epoch_60 [Avg]CELoss: 1.33068, loss: 1.33068, top1: 0.71005, top5: 0.87700
epoch_80 [Avg]CELoss: 1.33054, loss: 1.33054, top1: 0.71122, top5: 0.87882
epoch_120 [Avg]CELoss: 1.32960, loss: 1.32960, top1: 0.70940, top5: 0.87895
epoch_200 [Avg]CELoss: 1.32990, loss: 1.32990, top1: 0.70862, top5: 0.87635
# 模型评估
!python tools/eval.py \
    -c ./ppcls/configs/ImageNet/ResNet/ResNet50.yaml \
    -o Global.pretrained_model=./output/ResNet50/epoch_200


4 预测

训练完数据,肯定要进行预测,拿到结果才可,这里先拿一张一张图片进行验证,后续更改predict函数进行批量预测。


4.1 先验证一张

!python3 tools/infer.py \
    -c ./ppcls/configs/ImageNet/ResNet/ResNet50.yaml \
    -o Infer.infer_imgs=dataset/traindata/0/77398.jpg \
    -o Global.pretrained_model=output/ResNet50/best_model

输出:

[{'class_ids': [0, 38, 6, 21, 85], 
'scores': [0.99787, 0.00048, 0.00039, 0.00023, 0.00021],
'file_name': 'dataset/traindata/0/77398.jpg', 
'label_names': ['0', '38', '6', '21', '85']}]

我取用的图片是0分类里面的,这里先不考虑我预测是取自训练用的图,先保证整体正确即可。


从上面的输出可以看出0分类概率最大,得出该图片为0分类的图片正确。


4.2 批量预测

!python3 tools/infer.py \
    -c ./ppcls/configs/ImageNet/ResNet/ResNet50.yaml \
    -o Global.pretrained_model=output/ResNet50/best_model \
    -o Infer.infer_imgs=dataset/testdata

运行之后发现有损坏的文件出现,导致预测不能完整的进行,这时候,就要找出来是哪个文件有问题。


尝试去看预测文件:

PaddleClas/output/ShuffleNetV2_x0_25/infer.log是哪里出了问题,

结果意外发现:

paddleclas2.2不具备预测结果输出到日志的功能

自行加上,很简单


PaddleClas/ppcls/engine/trainer.py 580行


logger.info(result)

7bf8c8cf1852669e1d0112e801c6baa6.png


但这时还是没有找到哪个图片出现了问题,编写函数,再去找!

# 得到预测的数量是多少
path = "dataset/testdata"
def get_test_txt_list():
    """
        return test_text_list,test_txt_list , i 
        分别是:返回的数组,txt,文件数量
    """
    test_text_list = []
    for root,dirs,files in os.walk(path): # 分别代表根目录、文件夹、文件
        # 遍历每个文件
        i = 0
        for file in files:
            i = i + 1 
            test_text_list.append(os.path.join(root,file)+"\n")
    test_txt_list = ''.join(test_text_list)
    f = open('dataset/test_txt_list.txt','w',encoding='utf-8')
    f.write(test_txt_list)
    return test_text_list,test_txt_list , i
test_text_list,test_txt_list,test_lenth = get_test_txt_list()
print(test_lenth)


6409


通过下部分代码块找到问题图片

import cv2
i = 0
imgshape = []
wentitupian = []
for oneimg in test_text_list:
    oneimg = oneimg.split("\n")[0]
    img =cv2.imread(oneimg)
    try:
        imgshape.append(oneimg+"     "+str(img.shape[0])+" "+str(img.shape[1])+" "+str(img.shape[2])+"\n")
    except Exception:
        print(wentitupian.append(oneimg+"\n"))
    i = i+1
print(i)
print(wentitupian)
None
6409
['dataset/testdata/a2411.jpg\n']
#找到问题图片之后将其删除即可
!rm dataset/testdata/a2411.jpg
# 再次预测,得到全部结果,除了那个坏图
# 这里,运行之前我先把yaml的预测结果topk改成了1,防止生成太多数据,毕竟只有第一个数据才是我们需要的值
!python3 tools/infer.py \
    -c ./ppcls/configs/ImageNet/ResNet/ResNet50.yaml \
    -o Global.pretrained_model=output/ResNet50/best_model \
    -o Infer.infer_imgs=dataset/testdata


终于得到了我们想要的预测结果

9171528ffe7447cfeda4859aeddc3303.png


5 处理预测的log

得到的预测文件中包含了很多冗余信息,我们只需要文件名和类别即可


此外特别强调一下,每一行的日志不仅仅是一个文件的预测

刚开始我也以为,642行,预测结果仍然不够啊

后来发现每一行的结果是多个的!

# 将其复制一分出来,方便处理,看着也舒服
!cp output/ResNet50/infer.log output/ResNet50/infer.txt
import json
# 手动先把txt里面的如下之前全删了(包含这一行)再说
# [2021/07/30 16:15:03] root INFO: train with paddle 2.0.2 and device CUDAPlace(0)这行删了
# 这样下面处理就是统一的样式了
result = []
save_path = "output/ResNet50/submit_sample.csv"
f = open("output/ResNet50/infer.txt","r") 
lines = f.readlines()      #读取全部内容 ,并以列表方式返回
for line in lines:
    # 先把前面的时间都干掉,然后将单引号换成双引号,就变成了json格式的文本了
    txt = line.split(" root INFO: ")[1]
    print(txt)
    jsontxt = txt.replace("\'","\"")
    # print(jsontxt)
    list_txt = json.loads(jsontxt)
    for i in range(len(list_txt)):
        name, ids = list_txt[i]['file_name'],list_txt[i]['class_ids']
        onefilepath = name.split("/")[2]
        result.append([onefilepath,ids[0]])
df = pd.DataFrame(result, columns=['image_id', 'category_id'])
df.sort_values(by="image_id",inplace=True,ascending=True)  #千万记得排序!
df.to_csv(save_path, index=None)

提示:要进行排序,这里我是在csv文件进行了排序,因为挺复杂的,头有a后面有jpg

印象里可以用re进行解决

后续会处理了我再补上吧~


总结

项目总结

  1. 暂时基线是采用的是经典RESNET50网络进行的训练,虽然快,但是准确率并没有想象的那么好
  2. 参数还没有进行大调整,还有上升空间
  3. paddleclas2.2YYDS
相关文章
|
机器学习/深度学习 人工智能 自然语言处理
一文尽览 | 开放世界目标检测的近期工作及简析!(基于Captioning/CLIP/伪标签/Prompt)(上)
人类通过自然监督,即探索视觉世界和倾听他人描述情况,学会了毫不费力地识别和定位物体。我们人类对视觉模式的终身学习,并将其与口语词汇联系起来,从而形成了丰富的视觉和语义词汇,不仅可以用于检测物体,还可以用于其他任务,如描述物体和推理其属性和可见性。人类的这种学习模式为我们实现开放世界的目标检测提供了一个可以学习的角度。
一文尽览 | 开放世界目标检测的近期工作及简析!(基于Captioning/CLIP/伪标签/Prompt)(上)
|
分布式计算 数据可视化 大数据
用Spark分析Amazon的8000万商品评价(内含数据集、代码、论文)
尽管数据科学家经常通过分布式云计算来处理数据,但是即使在一般的笔记本电脑上,只要给出足够的内存,Spark也可以工作正常(在这篇文章中,我使用2016年MacBook Pro / 16GB内存,分配给Spark 8GB内存)。
19628 0
|
18天前
|
数据采集 搜索推荐
推荐系统实践之新闻推荐baseline理解
推荐系统实践之新闻推荐baseline理解
23 1
|
23天前
Midjourney-04 收集Prompt 百变小樱魔法卡 ClearCard 透明牌 提示词实践 Niji 5 Niji6 V6 详细记录 多种风格 附带文本关键词
Midjourney-04 收集Prompt 百变小樱魔法卡 ClearCard 透明牌 提示词实践 Niji 5 Niji6 V6 详细记录 多种风格 附带文本关键词
13 0
|
XML 人工智能 JSON
告别手动标注时代 | SAM 助力 Label-Studio 形成 SOTA 级半自动标注工具
告别手动标注时代 | SAM 助力 Label-Studio 形成 SOTA 级半自动标注工具
2178 0
告别手动标注时代 | SAM 助力 Label-Studio 形成 SOTA 级半自动标注工具
|
存储 负载均衡 计算机视觉
【训练Trick】让你在一张卡上训练1000万个id人脸数据集成为可能(附开源代码和论文链接)
【训练Trick】让你在一张卡上训练1000万个id人脸数据集成为可能(附开源代码和论文链接)
117 0
|
机器学习/深度学习 数据可视化 自动驾驶
分类器可视化解释StylEx:谷歌、MIT等找到了影响图像分类的关键属性
分类器可视化解释StylEx:谷歌、MIT等找到了影响图像分类的关键属性
119 0
|
机器学习/深度学习 人工智能 自动驾驶
一文尽览 | 开放世界目标检测的近期工作及简析!(基于Captioning/CLIP/伪标签/Prompt)(下)
人类通过自然监督,即探索视觉世界和倾听他人描述情况,学会了毫不费力地识别和定位物体。我们人类对视觉模式的终身学习,并将其与口语词汇联系起来,从而形成了丰富的视觉和语义词汇,不仅可以用于检测物体,还可以用于其他任务,如描述物体和推理其属性和可见性。人类的这种学习模式为我们实现开放世界的目标检测提供了一个可以学习的角度。
一文尽览 | 开放世界目标检测的近期工作及简析!(基于Captioning/CLIP/伪标签/Prompt)(下)
|
机器学习/深度学习 自然语言处理 算法
朴素贝叶斯进行--垃圾邮件分类、新闻分类、个人广告获取区域倾向的解读
朴素贝叶斯进行--垃圾邮件分类、新闻分类、个人广告获取区域倾向的解读
184 0
朴素贝叶斯进行--垃圾邮件分类、新闻分类、个人广告获取区域倾向的解读
|
数据采集 自然语言处理 安全
PaddleNLP基于ERNIR3.0文本分类以中医疗搜索检索词意图分类(KUAKE-QIC)为例【多分类(单标签)】
文本分类任务是自然语言处理中最常见的任务,文本分类任务简单来说就是对给定的一个句子或一段文本使用文本分类器进行分类。文本分类任务广泛应用于长短文本分类、情感分析、新闻分类、事件类别分类、政务数据分类、商品信息分类、商品类目预测、文章分类、论文类别分类、专利分类、案件描述分类、罪名分类、意图分类、论文专利分类、邮件自动标签、评论正负识别、药物反应分类、对话分类、税种识别、来电信息自动分类、投诉分类、广告检测、敏感违法内容检测、内容安全检测、舆情分析、话题标记等各类日常或专业领域中。 文本分类任务可以根据标签类型分为**多分类(multi class)、多标签(multi label)、层次分类