大四的时候在易车公司实习了两个月左右,7月份-11月份在商汤实习了4个月左右,收获颇多,小编来总结下都干了些啥以及有什么收获吧。其实主要是对自己经历的一个总结以及对于未来一份实习的经验积累。(大佬可忽略),马上就要找暑期实习了,给自己加个油吧!!
商汤智慧城市CV算法岗位实习总结(2022.7.7-2022.11.7)
主要工作
- 上衣长度分类任务
- 准确率和召回率计算确定问题(未知属性实验)
- 网安项目(特殊制服分类任务)
上衣长度分类任务
- 数据集及检查
数据集格式为图像以及label对应的json文件
训练集 | 测试集 | |
---|---|---|
赤膊 | 13659 | 1302 |
短袖 | 92081 | 6746 |
长袖 | 228790 | 3786 |
无法判断 | 35855 | 3940 |
拿到数据集之后的步骤为:
(1)首先对于json文件进行检查,查看是否有同一个样本标注多个类别或者标注错误的现象,检查出来后需进行人工纠正。
(2)bad case分析:使用kestrel模型(预训练好的模型)进行测试,将分类错误的图像统计出来,并进行分析。
总结出的bad case主要有以下几类:
(1)图像质量低,模糊不清
(2)遮挡现象或者与手臂颜色接近
(3)行人处于骑行状态
(4)非人
2. 重新确定分类标签
赤膊、长袖、短袖、无法判断
- 网络训练
backbone:resnet101
框架:PAR
训练得到的baseline精度为:
赤膊 | 短袖 | 长袖 | 无法判断 | mF1 | |
---|---|---|---|---|---|
baseline+erase+flip | 0.860 | 0.851 | 0.788 | 0.751 | 0.812 |
baseline+erase+flip+color | 0.927 | 0.880 | 0.828 | 0.802 | 0.861 |
baseline+erase+flip+color+more iter+lr | 0.964 | 0.911 | 0.866 | 0.844 | 0.896 |
遇到的问题以及解决方法
- 骑行状态下分类精度低的问题如何解决?
- 类别不平衡的问题如何解决?
- 能否改进网络进一步提高分类精度?
针对问题1:
解决方法:从业务线回流的数据中爬取了非机动车数据,尝试人工制作骑行数据集进行数据扩充。训练集增加骑手数据(长袖+短袖)30000张左右,测试集增加骑手数据(长袖+短袖)8000张左右。
加入新数据之前,与kestrel基线模型效果对比:
(1)骑手数据测试集表现
(2)通用测试集+骑手测试集表现
短袖(Our) | 长袖(Our) | 短袖(kestrel) | 短袖(kestrel) | |
---|---|---|---|---|
P | 0.761 | 0.918 | 0.855 | 0.935 |
R | 0.851 | 0.800 | 0.889 | 0.871 |
F1 | 0.803 | 0.855 | 0.872 | 0.902 |
赤膊(Our) | 短袖(Our) | 长袖(Our) | 无法判断(Our) | 赤膊(kestrel) | 短袖(kestrel) | 短袖(kestrel) | 无法判断(kestrel) | |
---|---|---|---|---|---|---|---|---|
P | 0.968 | 0.851 | 0.900 | 0.768 | 0.961 | 0.881 | 0.906 | 0.760 |
R | 0.953 | 0.903 | 0.823 | 0.800 | 0.945 | 0.904 | 0.861 | 0.797 |
F1 | 0.960 | 0.877 | 0.860 | 0.783 | 0.953 | 0.893 | 0.883 | 0.778 |
加入骑行数据进行训练微调之后,效果为:
短袖 | 长袖 | 骑手数据集(mF1) | 混合数据集(mF1) | 通用数据集(mF1) | |
---|---|---|---|---|---|
baseline | 87.20 | 90.20 | 48.08 | 87.68 | 88.28 |
baseline+非机动车骑行数据 | 90.00 | 93.99 | 50.35 | 88.86 | 88.63 |
结论:加入骑手数据微调后精度确实有提升。
针对问题2:
- 损失函数层面:
- class weighted cross entrophy
按照样本数量进行加权 - OHEM loss
算法的核心是选择一些hard examples(多样性和高损失的样本)作为训练的样本,针对性地改善模型学习效果。对于数据的类别不平衡问题,OHEM的针对性更强。
OHEM loss代码:
class OhemCELoss(nn.Module):
"""
Online hard example mining cross-entropy loss:在线难样本挖掘
if loss[self.n_min] > self.thresh: 最少考虑 n_min 个损失最大的 pixel,
如果前 n_min 个损失中最小的那个的损失仍然大于设定的阈值,
那么取实际所有大于该阈值的元素计算损失:loss=loss[loss>thresh]。
否则,计算前 n_min 个损失:loss = loss[:self.n_min]
"""
def __init__(self, thresh, n_min, ignore_lb=255, *args, **kwargs):
super(OhemCELoss, self).__init__()
self.thresh = -torch.log(torch.tensor(thresh, dtype=torch.float)).cuda() # 将输入的概率 转换为loss值
self.n_min = n_min
self.ignore_lb = ignore_lb
self.criteria = nn.CrossEntropyLoss(ignore_index=ignore_lb, reduction='none') #交叉熵
def forward(self, logits, labels):
N, C, H, W = logits.size()
loss = self.criteria(logits, labels).view(-1)
loss, _ = torch.sort(loss, descending=True) # 排序
if loss[self.n_min] > self.thresh: # 当loss大于阈值(由输入概率转换成loss阈值)的像素数量比n_min多时,取所以大于阈值的loss值
loss = loss[loss>self.thresh]
else:
loss = loss[:self.n_min]
return torch.mean(loss)
- Focal loss
核心思想是在交叉熵损失函数(CE)的基础上增加了类别的不同权重以及困难(高损失)样本的权重(如下公式),以改善模型学习效果。
其中 α \alpha α值用于平衡正负样本的比例, γ \gamma γ值作为衰减系数。
(1)对于正样本来说:
p p p越大, − l o g ( p ) -log(p) −log(p)越小,整体损失越小;相反, p p p越小, − l o g ( p ) -log(p) −log(p)越大,整体损失越大
(2)对于负样本来说:
p p p越大, − l o g ( 1 − p ) -log(1-p) −log(1−p)越大,整体损失越大;相反, p p p越小, − l o g ( 1 − p ) -log(1-p) −log(1−p)越小,整体损失越小
Focal loss代码:
def py_sigmoid_focal_loss(pred,
target,
weight=None,
gamma=2.0,
alpha=0.25,
reduction='mean',
avg_factor=None):
pred_sigmoid = pred.sigmoid()
target = target.type_as(pred)
pt = (1 - pred_sigmoid) * target + pred_sigmoid * (1 - target)
focal_weight = (alpha * target + (1 - alpha) *
(1 - target)) * pt.pow(gamma)
loss = F.binary_cross_entropy_with_logits(
pred, target, reduction='none') * focal_weight
return loss
- 模型层面:
- 采样+集成学习
(1)BalanceCascade
基本架构与EasyEnsemble相同,不同的地方在于每训练一个(Adaboost)分类器后就将正确分类的样本去掉,错误分类的样本放回到原样本空间中,通过调整阈值来筛选出分类错误的样本将其保留,阈值调整为使得模型错误率等于
(2)EasyEnsemble
Bagging体现于:每一次采样都使用Bagging的采样方法(Bootstrap)对多数类(数量较多的类)样本集进行采样,使其样本数等于少数类
Adaboost体现于:将多数类采样得到的样本集与少数类的样本集的全部样本组合在一起进行Adaboost模型的训练。
最终将T个Adaboost作为基模型进行Ensemble
通常,在数据集噪声较小的情况下,可以用BalanceCascade,可以用较少的基分类器数量得到较好的表现(基于串行的集成学习方法,对噪声敏感容易过拟合)。噪声大的情况下,可以用EasyEnsemble,基于串行+并行的集成学习方法,bagging多个Adaboost过程可以抵消一些噪声影响。
- 数据增强方面:
(1)单样本增强
- AutoAugmentation
使用强化学习的搜索策略对数据增强空间进行搜索,每个增强操作有两个超参数:进行该操作的概率和图像增强的幅度(magnitude,这个表示数据增强的强度,比如对于旋转,旋转的角度就是增强幅度,旋转角度越大,增强越大。
目前torchvision已经实现可以直接进行调用:
from torchvision.transforms import autoaugment, transforms
train_transform = transforms.Compose([
transforms.RandomResizedCrop(crop_size, interpolation=interpolation),
transforms.RandomHorizontalFlip(hflip_prob),
# 这里policy属于torchvision.transforms.autoaugment.AutoAugmentPolicy,
# 对于ImageNet就是 AutoAugmentPolicy.IMAGENET
# 此时aa_policy = autoaugment.AutoAugmentPolicy('imagenet')
autoaugment.AutoAugment(policy=aa_policy, interpolation=interpolation),
transforms.PILToTensor(),
transforms.ConvertImageDtype(torch.float),
transforms.Normalize(mean=mean, std=std)
])
(2)多样本增强
- MixUp
原理比较好理解:
代码:
def criterion(batch_x, batch_y, alpha=1.0, use_cuda=True):
'''
batch_x:批样本数,shape=[batch_size,channels,width,height]
batch_y:批样本标签,shape=[batch_size]
alpha:生成lam的beta分布参数,一般取0.5效果较好
use_cuda:是否使用cuda
returns:
mixed inputs, pairs of targets, and lam
'''
if alpha > 0:
#alpha=0.5使得lam有较大概率取0或1附近
lam = np.random.beta(alpha, alpha)
else:
lam = 1
batch_size = batch_x.size()[0]
if use_cuda:
index = torch.randperm(batch_size).cuda()
else:
index = torch.randperm(batch_size) #生成打乱的batch_size索引
#获得混合的mixed_batchx数据,可以是同类(同张图片)混合,也可以是异类(不同图片)混合
mixed_batchx = lam * batch_x + (1 - lam) * batch_x[index, :]
"""
Example:
假设batch_x.shape=[2,3,112,112],batch_size=2时,
如果index=[0,1]的话,则可看成mixed_batchx=lam*[[0,1],3,112,112]+(1-lam)*[[0,1],3,112,112]=[[0,1],3,112,112],即为同类混合
如果index=[1,0]的话,则可看成mixed_batchx=lam*[[0,1],3,112,112]+(1-lam)*[[1,0],3,112,112]=[batch_size,3,112,112],即为异类混合
"""
batch_ya, batch_yb = batch_y, batch_y[index]
return mixed_batchx, batch_ya, batch_yb, lam
def mixup_criterion(criterion, inputs, batch_ya, batch_yb, lam):
return lam * criterion(inputs, batch_ya) + (1 - lam) * criterion(inputs, batch_yb)
(3)基于深度学习的数据增强
生成模型如变分自编码网络(Variational Auto-Encoding network, VAE)和生成对抗网络(Generative Adversarial Network, GAN),其生成样本的方法也可以用于数据增强。这种基于网络合成的方法相比于传统的数据增强技术虽然过程更加复杂, 但是生成的样本更加多样。
- 欠采样/过采样
在实际操作中,对于每一个batchsize,按照代码原本的设置取到所有的样本的概率是相等的,比如假设数量多的类别为2400张,数量最少的只有100张,那么取到每一个样本的概率均为:1/(2400+100),我们现在改为按照类别存储样本,每一次随机选择类别,在每一个类别对应的样本中再进行选取,这样类别少的样本被选中的概率增加到1/2*1/100=1/200,通过此种方法可以达到相对平衡的训练过程,实验中发现对于极度不平衡的比例还是有效果的。
针对问题3:
- Dual attention
Dual attention主要包括position attention module和channel attention module两部分,分别执行空间上的注意力机制和通道上的注意力机制,最终将两者的结果进行加和,如图所示:
代码实现如下:
class PAM_Module(Module):
""" Position attention module"""
# Ref from SAGAN
def __init__(self, in_dim):
super(PAM_Module, self).__init__()
self.chanel_in = in_dim
self.query_conv = Conv2d(in_channels=in_dim, out_channels=in_dim // 8, kernel_size=1)
self.key_conv = Conv2d(in_channels=in_dim, out_channels=in_dim // 8, kernel_size=1)
self.value_conv = Conv2d(in_channels=in_dim, out_channels=in_dim, kernel_size=1)
self.gamma = Parameter(torch.zeros(1))
self.softmax = Softmax(dim=-1)
def forward(self, x):
"""
inputs :
x : input feature maps( B X C X H X W)
returns :
out : attention value + input feature
attention: B X (HxW) X (HxW)
"""
m_batchsize, C, height, width = x.size()
proj_query = self.query_conv(x).view(m_batchsize, -1, width * height).permute(0, 2, 1)
proj_key = self.key_conv(x).view(m_batchsize, -1, width * height)
energy = torch.bmm(proj_query, proj_key)
attention = self.softmax(energy)
proj_value = self.value_conv(x).view(m_batchsize, -1, width * height)
out = torch.bmm(proj_value, attention.permute(0, 2, 1))
out = out.view(m_batchsize, C, height, width)
out = self.gamma * out + x
return out
class CAM_Module(Module):
""" Channel attention module"""
def __init__(self, in_dim):
super(CAM_Module, self).__init__()
self.chanel_in = in_dim
self.gamma = Parameter(torch.zeros(1))
self.softmax = Softmax(dim=-1)
def forward(self, x):
"""
inputs :
x : input feature maps( B X C X H X W)
returns :
out : attention value + input feature
attention: B X C X C
"""
m_batchsize, C, height, width = x.size()
proj_query = x.view(m_batchsize, C, -1)
proj_key = x.view(m_batchsize, C, -1).permute(0, 2, 1)
energy = torch.bmm(proj_query, proj_key)
energy_new = torch.max(energy, -1, keepdim=True)[0].expand_as(energy) - energy
attention = self.softmax(energy_new)
proj_value = x.view(m_batchsize, C, -1)
out = torch.bmm(attention, proj_value)
out = out.view(m_batchsize, C, height, width)
out = self.gamma * out + x
return out
整体上来说很像在空间上的自注意力机制和通道上的自注意力机制结合,实验上证明还是有一定效果的。
3. Attribute Localization Module
在进行方法调研的时候,查阅到一篇属性分类的文章感觉很有启发,它的大概思想是从多个尺度上去定位到目标区域然后进行识别,其中用到了spatial transformer进行定位,之前对于这个模块没有太多了解,有兴趣的可以去看看:
https://blog.csdn.net/qq_39422642/article/details/78870629
主体框架为:
其中的属性定位模块为:
6. SENet(通道注意力机制)
代码:
from torch import nn
class SELayer(nn.Module):
def __init__(self, channel, reduction=16):
super(SELayer, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Sequential(
nn.Linear(channel, channel // reduction, bias=False),
nn.ReLU(inplace=True),
nn.Linear(channel // reduction, channel, bias=False),
nn.Sigmoid()
)
def forward(self, x):
b, c, _, _ = x.size()
y = self.avg_pool(x).view(b, c)
y = self.fc(y).view(b, c, 1, 1)
return x * y.expand_as(x)
4. Label Smoothing
Label Smoothing Regularization(LSR)就是为了缓解由label不够soft而容易导致过拟合的问题,使模型对预测less confident,把预测值过度集中在概率较大类别上,把一些概率分到其他概率较小类别上。
实际上实现过程为:
label smoothing在什么情况下比较有效果呢?
- 真实场景下,尤其数据量大的时候 数据里是会有噪音的, 为了避免模型错误的学到这些噪音可以加入label smoothing
- 避免模型过于自信,有时候我们训练一个模型会发现给出相当高的confidence,但有时候我们不希望模型太自信了(可能会导致over-fit 等别的问题),希望提高模型的学习难度,也会引入label smoothing
- 分类的中会有一些模糊的case,比如图片分类,有些图片即像猫又像狗, 利用soft-target可以给两类都提供监督效果
未知属性实验
- 根据要求的准确率确定相应的阈值(涉及到presicion/recall/F1)计算
- 探究类别如果是多种未知类混杂在一起是否会对精度产生影响
precision/recall/F1/accuracy/计算方式为:
精确率
定义为预测为正样本中真正正样本所占的比例,可以写作:
召回率
定义为所有真实类别为正样本中预测为正样本的比例:
F1-score
精确率和召回率两个指标通常是此消彼长的,很难兼得,在大规模数据集合中相互制约,需要综合考虑,因此有F1指标的定义:
AUC/ROC曲线
TPR表示,在所有正样本中,被预测为正样本的比例
FPR表示,在所有的负样本中,被预测成正样本的比例
简单来说,在不同的分类阈值的设置之下,将FPR作为横坐标,TPR作为纵坐标构成的曲线为ROC曲线,而曲线下的面积为AUC(Area of Under Curve),分类质量可以用AUC来评估,其表示正例排在负例前面的概率。
Accurarcy
所有预测正确的样本除以样本总数:
网安特殊制服分类
- 数据集来源
- 模型训练
首先我们的任务是对于网络中搜索到的制服图片进行分类,那么我们的数据集来源必须从网上进行爬取得到,爬虫需要确定相应的关键词。可以部分可视化一下:
训练的框架采用UP框架,backbone分别用的resnet101和resnet18。实验中发现两者精度确实是有一定差距,另外因为爬虫数据较少,如果我们额外加入一些实际生活中监控拍到的制服数据,实验中发现效果反而下降,可能是由于域不同引起的。具体结果就不给大家展示了,所用策略和之前基本相同。
实习总结
- 准确率、召回率、accurarcy、mF1计算
- 一些常用的数据增强方法
- 可以提升精度的注意力机制
易车CV算法岗位实习总结(2021.7.7-2021.9.7)
主要工作
- 车辆颜色/朝向/结构分类
- 车辆型号分类
颜色类别:13类
朝向分类: 8类
结构分类: 3类(局部外观,整车外观和内饰)
每个任务均尝试用了VGG和MobileNet进行实验,MobileNet更加轻量化,精度比VGG19略低,对于颜色分类任务来说,为了避免周围环境影响,用检测模型先把车辆检测出来再进行训练。
车辆型号分类
使用efficientNetv1_B7进行训练,取得了超越MobileNetv3的精度,主要面临的问题是类别数量极度不平衡。
实习总结
- 初步地了解了整个任务的工作流程,从数据集的爬取,网络训练,模型调优均独立完成,对于当时大四的自己来说收获很多。