-
基于paddlehub实现驾驶员分心驾驶状态检测
- 一个项目教你学会如何使用PaddleHub在自己数据集上实现迁移学习
- 本项目已更新至PaddleHub2.0,使用旧版PaddleHub的同学可在本项目版本中查看上一版本
背景
- 在我们的生活中,驾驶员疲劳驾驶的情况较常见,但因疲劳驾驶多出现于长途驾驶以及对应的限制制度的落地实施,疲劳驾驶情况呈下降趋势。而分心驾驶引发的交通事故却呈上升趋势,且分心驾驶具有不容易通过交警监管、驾驶员对此概念模糊等问题。在我们生活中常常发生的是,分心驾驶时驾驶员自身可能都不会有所察觉。当驾驶员处于分心驾驶状态时,由于注意力不集中,驾驶员在面对突发状况时会反应不及时出现呆滞和慌乱,以至于出现判断和操作机动车上的失误,从而引发交通事故。
- paddlehub是一个功能强大的paddle预训练模型应用管理工具,同时提供Python API和命令行两种调用模式。Paddlehub的强大不必多说,但是官方提供做自己数据集迁移学习的demo较少,目前网上提供资料也不多且较散,这里我就以一个kaggle上经典的project为例,给大家讲解一下如何使用paddlehub进行自我数据集的迁移学习,实现驾驶员分心驾驶状态检测,并介绍paddlehub2.0.0的使用
本文涉及内容
-
- 数据分析
-
- 数据处理
-
- 训练配置
-
- 开始训练
-
- 导出结果
1.数据分析
import os
import cv2
import paddle
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
!unzip -o -q data/data38429/distracted_driver_detection.zip
print("done")
done
数据集说明
数据集来源于kaggle,解压后生成三个文件:
- train
- test
- driver_imgs_list.csv
其中train文件夹下含有c0-c9十个子文件夹,包含训练对应的10种分心驾驶状态的图片,test文件夹包含测试集的图片,driver_imgs_list.csv文件则是标签信息,具体信息如下:
- subject:拍摄的司机编号
- classname:分心行为对应的标签
- img:对应的图片数据名
下面通过程序可视化数据分布情况,可以看到,司机一共是26个,每个司机的数据分布并没有一个明显的特征
通过分析10类(c0-c9共十个类别)样本数据可以看到,样本数分布较为均匀,很奈斯,这样训练时每个类别都可学的差不太多,可以避免因数据不均衡让模型对某些类的学习产生过拟合。
#csv文件路径
dir = "driver_imgs_list.csv"
#读取csv中subject的数据
#subject对应的是司机的编号
img_csv = os.path.join(dir)
csv_data = pd.read_csv(img_csv)
class_name = csv_data['classname']
#储存所有类别
class_names = []
for name in class_name:
if(name not in class_names):
class_names.append(name)
#计算每个类别有多少张图片
class_num = csv_data.groupby('classname')['img'].count()
#显示每个类别图片数量
print(class_num)
#显示总共多少类
print("class count = {}".format(len(class_names)))
#可视化
plt.bar(x=class_names, height=class_num, width=0.8)
plt.show()
classname
c0 2489
c1 2267
c2 2317
c3 2346
c4 2326
c5 2312
c6 2325
c7 2002
c8 1911
c9 2129
Name: img, dtype: int64
class count = 10
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/cbook/__init__.py:2349: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
if isinstance(obj, collections.Iterator):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0PbWRkX0-1616043558900)(output_6_2.png)]
十类对应的实际状态分别是:
C0:驾驶员正常驾驶 | C1:右手使用手机打字 | C2:使用右手打电话 | C3:左手使用手机打字 | C4:使用左手打电话 |
---|---|---|---|---|
C5:调试车辆多媒体 | C6:驾驶员喝水或进食 | C7:驾驶员拿后排东西 | C8:整理头发和化妆 | C9:和其他乘客谈话 |
---|---|---|---|---|
2.数据处理
2.1 生成train.txt 和 val.txt标签文件
因为提供的数据集中只有train、test没有val,所以人为的在训练集中划分一部分数据作为val集并生成储存图片位置与类别的txt文件。
-
与旧版本相比,paddlehub2.0.0生成txt文件没有硬性格式要求了,只要保证之后定义数据集的自己在getitem方法里实现就可以
-
没啥特殊需求的话这里生成txt文件的格式还是建议大家采用旧paddlehub读取规则,简单直观方便读取:图片1路径(空格)图片1标签
path = "train/"
folders_name = os.listdir(path)
a = open("train_list.txt", "w")
b = open("val_list.txt", "w")
count = 0
val_count = 0
train_count = 0
for name in folders_name:
image_names = os.listdir(path+name)
for img_name in image_names:
if(count % 20 == 0):
b.write(path+name+"/"+img_name+name.replace("c"," ")+'\n')
val_count = val_count + 1
else:
a.write(path+name+"/"+img_name+name.replace("c"," ")+'\n')
train_count = train_count + 1
count = count + 1
a.close()
b.close()
print("train_list生成完毕,train数据集共{}个数据".format(train_count))
print("val_list生成完毕,val数据集共{}个数据".format(val_count))
print("合计{}个数据".format(count))
train_list生成完毕,train数据集共21302个数据
val_list生成完毕,val数据集共1122个数据
合计22424个数据
2.2 生成predict.txt待预测数据
将test集图片信息写入predict_list.txt文件中,并生成储存标签信息的label_list.txt文件,预测文件的格式就很简单了,常规的路径+文件名即可。
f = open("predict_list.txt", "w")
predict_data = []
path = "test/"
folders_name = os.listdir(path)
for name in folders_name:
f.write(path+name+'\n')
predict_data.append(path+name)
print("predict_list.txt文件成功生成")
f.close()
predict_list.txt文件成功生成
2.3 读取label全部标签种类
这里就是2.0.0和旧版的区别了,旧版label_list是在训练时读入,新版2.0.0则是在装载模型时载入,同时要用列表类型传入。这里就用列表类型变量储存标签
label = []
folders_name = os.listdir("train")
folders_name.sort()
for name in folders_name:
label.append(name)
print("label_list成功读取")
print(label)
label_list成功读取
['c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9']
3.训练配置
3.1 安装PaddleHub2.0.0
pip install paddlehub==2.0.0 --upgrade -i https://pypi.tuna.tsinghua.edu.cn/simple
!pip install paddlehub==2.0.0 -i https://mirror.baidu.com/pypi/simple
3.2 搜索模型
对于PaddleHub里的预训练模型,我们可以在命令行中输入:
hub search + model_name
model_name是我们想搜索的模型名字,这样就可以搜索到全部含模型名的预训练模型,再根据我们自己的需求选取模型即可。
import paddlehub as hub
!hub search resnet
3.3 装载选取的模型resnet50_vd_imagenet_ssld
-
这里咱们是分类问题,就选择了在imagenet上训练好的resnet50的D版本分类预训练参数
-
装载模型时也要注意,PaddleHub2.0中标签读取集成在了装载模型中,后面的label_list 参数就是读取标签数据的,如果不加label_list,默认是模型训练时输入的标签
model = hub.Module(name = "resnet50_vd_imagenet_ssld",label_list = label)
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/fluid/dygraph/layers.py:1263: UserWarning: Skip loading for out.weight. out.weight receives a shape [2048, 1000], but the expected shape is [2048, 10].
warnings.warn(("Skip loading for {}. ".format(key) + str(err)))
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/fluid/dygraph/layers.py:1263: UserWarning: Skip loading for out.bias. out.bias receives a shape [1000], but the expected shape is [10].
warnings.warn(("Skip loading for {}. ".format(key) + str(err)))
load pretrained parameters success
本步可能遇到的问题:
-
1. AttributeError: ‘Parameter’ object has no attribute ‘gradient_clip_attr’
hub库中部分模型仍未更新到PaddlePaddle2.0,所以无法和PaddleHub2.0兼容😂
-
2. TypeError: init() got an unexpected keyword argument ‘label_list’
问题原因同上
-
解决方案:装载已升级模型即可,目前作者测试的常见的分类模型已升级的只有:resnet50_vd_imagenet_ssld
3.4 设置预处理操作
-
paddlehub2.0和paddlepaddle2.0相匹配,预处理操作也和paddle2.0高阶API保持一致,支持随机调整图像的亮度、对比度、饱和度、图像大小、归一化等,更多预处理操作可参考官方API文档
-
这里根据我自己的需求,就选了图像大小修改和归一化。
#定义数据预处理方式
import paddlehub.vision.transforms as T
transforms = T.Compose([T.Resize((416, 416)), T.Normalize(mean=[0.485, 0.456, 0.406], std = [0.229, 0.224, 0.225])], )
3.5 定义自己数据类
划重点了划重点了,咱们在上一个模块将自己的数据集生成了txt文件,这里就要用上了。
这一步和使用paddle2.0自定义数据集相似,继承paddle2.0中的Dataset类,配置属性和方法,然后实例化。
-
mode :模式,即读取哪个数据集,这里咱们上面生成了训练、测试和预测三个集合,那这里mode就设三种状态保证都能把数据都出去
-
file:刚才生成的txt文件,注意要和mode相匹配
-
transforms:预处理操作参数,根据需求提前配置好
-
def getitem(self, idx)方法:定义了在idx时如何获取数据,并返回单条的数据,同时完成对应的数据预处理操作
-
def len(self)::实现__len__方法,返回数据集总数目
class DriverDataset(paddle.io.Dataset):
def __init__(self, transforms, mode = 'train'):
self.mode = mode
self.transforms = transforms #预处理方式
self.num_classes = 10 #类别数
if self.mode == 'train':
self.file = 'train_list.txt'
elif self.mode == 'val':
self.file = 'val_list.txt'
else:
self.file = 'predict_list.txt'
with open(self.file, 'r') as file:
self.data = file.read().split('\n')
def __getitem__(self, idx):#这里的读取就要和之前生成txt那里相匹配
img_path, grt = self.data[idx].split(' ')
im = self.transforms(img_path)
return im, int(grt)
def __len__(self):
return len(self.data)
定义好后我们实例化它,生成对应的三个数据集
train_dataset = DriverDataset(transforms,"train")
eval_dataset = DriverDataset(transforms,"val")
predict_dataset = DriverDataset(transforms,"predict")
3.6 设置优化策略和运行配置
-
接下来先配置优化策略,此处优化策略选择和paddle2.0通用,都是在paddle.optimizer下进行选择,因为这里演示的任务是常规的图像分类任务,就直接选了Adam优化器,学习率设为固定学习率:0.001,更多优化器可参考此处
-
配置好优化策略后就可以建立trainer了,主要控制Fine-tune的训练。在旧版是先建立config再建立task,而且不同的任务有对应不同task类型,新版里是建立trainer,具体参数如下:
参数名 | 作用 |
---|---|
model | 被优化模型 |
optimizer | 选择的优化器 |
use_gpu | 是否使用gpu |
use_vdl | 是否使用vdl可视化 |
checkpoint_dir | 模型参数保存的地址 |
compare_metrics | 保存最优模型的衡量指标 |
配置训练策略总的来说和旧版的paddlehub差不太多,但是加大了和paddle2.0的联系,也可以说是paddlepaddle2.0API更好用了,确实是一个比较大的提升。
from paddlehub.finetune.trainer import Trainer
optimizer = paddle.optimizer.Adam(learning_rate=0.001, parameters=model.parameters())
trainer = Trainer(model, optimizer, use_gpu=True,checkpoint_dir='driver')
[2021-02-28 14:32:56,740] [ WARNING] - PaddleHub model checkpoint not found, start from scratch...
本步可能遇到的问题
这时因为在装载模型时使用的是未升级的模型。同时装载模型忘记添加label_list就会在训练时就会报错如上,解决办法也是更换模型。
4.开始训练
最后配置训练过程参数就可以开始快乐炼丹了。
train_dataset
: 训练使用的数据集;epochs
: 训练总轮数;batch_size
: 训练一个batch的大小;num_workers
: works的数量,默认为0;eval_dataset
: 验证集;log_interval
: 打印日志的间隔;save_interval
: 保存模型的间隔频次;
训练时PaddleHub也会将log全部打印出来,嘿嘿嘿,是不是很方便且easy?
trainer.train(train_dataset, epochs=5, batch_size=32, eval_dataset=eval_dataset, log_interval=20, save_interval=1)
5.模型预测
训练完成后所有参数数据都会保存在大家设置的checkpoint_dir文件夹下,大家就可以根据自己的需求去嘿嘿嘿了
预测也很简单,直接调用model.predict读取想要读取的模型参数来进行预测并进行后续操作。
result = model.predict(['test1.jpg'])
。
[外链图片转存中...(img-tBQW6B6g-1616043558920)]
```python
result = model.predict(['test1.jpg'])
print(result)
[{'c1': 0.98667055}]
-
预测图片如图为右手使用手机,对应类别为C1,模型预测状态判断为C1,置信度为0.98,还是较精准
-
对于单帧图片预测,我们可以直接根据置信度来判断,当置信度大于0.8且状态判断是在C1-C9中的话,我们就认为在这帧图像中,驾驶员处于分心驾驶状态
总结
-
千万不要以为PaddleHub只能完成图片分类任务嗷,PaddleHub功能远比我这里展示的强大,更多其他任务可以查看PaddleHub文档
-
就目前来看【2021年2月14日】,paddlehub中仍有很多模型并没有转换成paddlepaddle2.0版本的,即无法使用paddlehub2.0进行操作,建议如果对模型选取有需求的伙伴可参考本项目上一个版本使用paddlehub1.8完成。
-
本项目以简单常见的图片分类任务为例,希望能解决大家刚开始使用PaddleHub的一些问题快速入门,欢迎大家一起来讨论共同进步,fighting!
关于作者
-
姓名:Fitz
-
武汉理工大学信息工程专业2018级本科在读
-
感兴趣方向:计算机视觉、推理部署、迁移学习
-
AIstudio主页 : Fitzie
-
欢迎大家有问题一起交流讨论,共同进步~
运行代码请点击:https://aistudio.baidu.com/aistudio/projectdetail/782743
运行方式如下: