更新到2019.8.5
参考EfficientNet-Pytorch.
文章结构
efficientnet_pytorch模块
总结
pytorch中有为efficientnet专门写好的网络模型,写在efficientnet_pytorch模块中。
模块包含EfficientNet的op-for-op的pytorch实现,也实现了预训练模型和示例。
这个代码是简单,而且高度可扩展的(extensible),并且容易集成到自己的项目中。
目前可以达到的目的:
(1)加载与训练的EfficientNet模型
(2)使用EfficientNet模型进行分类或特征提取
(3)在ImageNet或者自己的的图片集上评估EfficientNet
即将推出的功能:
(1)使用简单的命令在ImageNet上从头开始训练新模型
(2)在自己的数据集上快速的对EfficientNet做finetune
(3)输出产品级别的EfficientNet
内容
About EfficientNet
EfficientNets是一系列图像分类模型,实现了state-of-art的accuracy,但是比以前的模型smaller和faster。作者是基于AutoML和Compound Scaling开发的EfficientNets,首先使用AutoML Mobile框架开发一个名为EfficientNet-B0的移动规模基线网络(mobile-size baseline network);然后,我们使用复合缩放( compound scaling)方法来扩展此基线以获得EfficientNet-B1到B7。
EfficientNets在ImageNet上实现了最先进的精度,效率提高了一个数量级:
在高精度制度下,我们的EfficientNet-B7在ImageNet上以66M参数和37B FLOPS实现了最先进的84.4%前1 / 97.1%前5精度,在CPU推理上小8.4倍,快6.1倍比以前最好的Gpipe。
在中等精度方案中,我们的EfficientNet-B1比ResNet-152小7.6倍,CPU推理速度快5.7倍,具有类似的ImageNet精度。
与广泛使用的ResNet-50相比,在类似的FLOPS约束下,我们的EfficientNet-B4将前1精度从ResNet-50的76.3%提高到82.6%(+ 6.3%)。
About EfficientNet PyTorch
EfficientNet PyTorch是PyTorch对EfficientNet的重新实现。 它与原始的TensorFlow实现一致,因此很容易从TensorFlow检查点加载权重。 同时,我们的目标是使PyTorch实现尽可能简单,灵活和可扩展。
Usage
加载EfficientNet
from efficientnet_pytorch import EfficientNet
model = EfficientNet.from_name('efficientnet-b0')
加载预训练EfficientNet
from efficientnet_pytorch import EfficientNet
model = EfficientNet.from_pretrained('efficientnet-b0')
Onnx+EfficientNet实践
即利用efficientnet_pytorch模块提供的网络模型,自己训练,然后转成onnx,再转成tensorRT的engine,最后直接使用engine进行推断,来检测推断速度和准确率。
1.只跑了一个iteration的model
数据集:自己公司的车辆数据集,train:400万,test:22000
模型版本:efficientnet-b0
num_classes:1852
创建onnx文件
设置固定的输入size为(224,224)
用torch.load载入模型权重,然后既可以用torch.onnx.export()方法转成onnx格式文件
在tensorRT上测试准确度和时间
def main():
onnx_path='./onnx_file/0net_params_8_5.onnx'
engine_path="./trt_file/"+(onnx_path.split('/')[2]).split('.')[0]+".trt"
test_dir = './label_txt/val.txt'
num_classes=1852
img_size=224
# the mean value and std value is pre-calculated
valiTransform = transforms.Compose([
transforms.Resize((img_size,img_size)),
transforms.ToTensor(),
transforms.Normalize([0.3497724, 0.35888246, 0.37229323],[0.2726704, 0.2739602, 0.2761853])
])
test_data = MyDataset(txt_path=test_dir, transform=valiTransform)
test_loader = DataLoader(dataset=test_data, batch_size=1)
test_data = MyDataset(txt_path=test_dir, transform=valiTransform)
criterion = nn.CrossEntropyLoss().cuda() # 选择损失函数
print("The length of test data set is %s"%(test_data.__len__()))
# initialize
sum_time=0
cls_num = num_classes
loss_sigma=0.0
conf_mat = np.zeros([cls_num, cls_num])
with get_engine(onnx_path,engine_path) as engine ,engine.create_execution_context() as context:
# because the input_size is fixed, the buffers should be allcated in advanced for all the data(img)
inputs_alloc, outputs_alloc, bindings, stream = common.allocate_buffers(engine)
for i,data in enumerate(test_loader):
# read the data
inputs,labels=data
inputs=inputs.numpy()
# get engine to inference
print("Reading engine from file {}".format(engine_path))
inputs_alloc[0].host = inputs
t_start=time.time()
trt_outputs = common.do_inference(context, bindings=bindings, inputs=inputs_alloc, outputs=outputs_alloc,stream=stream)
t_end=time.time()
t_time=t_end-t_start
sum_time += t_time
print('The {}th img, inference time : {}' .format(i, str(t_time)))
trt_outputs = np.array(trt_outputs)
outputs = torch.from_numpy(trt_outputs)
outputs.detach()
# 计算loss
loss = criterion(outputs, labels)
loss_sigma += loss.item()
# 统计
_, predicted = torch.max(outputs.data, 1)
# 统计混淆矩阵
for j in range(len(labels)):
cate_i = labels[j].cpu().numpy()
pre_i = predicted[j].cpu().numpy()
conf_mat[cate_i, pre_i] += 1.0
print('avg time: ' + str(sum_time / test_data.__len__()))
print('{} set Accuracy:{:.2%}'.format('test', conf_mat.trace() / conf_mat.sum()))
在预处理和后处理这里,直接用numpy处理,总写不太对,就偷懒了。。日后填坑
结果
在这个只跑了一个iteration的model上
使用tensorRT的engen:
前馈时间:0.003499486s
准确度:91.64%
而在pytorch上的:
前馈时间:0.01249988s
准确度:91.04%
pytorch积累
1. torch.max()方法
value, index=torch.max(tensor,num)
tensor为输入的tensor,num为操作的维度
两个返回值,value为最大值,index为索引。
eg:
tensor=torch.randn(4,5)
value,index=torch.max(tensor,0)
则tensor:
tensor([[ 0.3045, -0.3509, 0.8248, 1.1156, -1.0433],
[ 1.0962, 0.8329, 1.0494, -0.7632, -0.2968],
[ 0.8749, 0.5894, -2.2095, -0.8238, -0.5258],
[ 1.5428, -0.2508, -0.8234, -0.9917, -1.2586]])
value:
tensor([ 1.5428, 0.8329, 1.0494, 1.1156, -0.2968])
即按照每列的最大值计算,并且输出下索引index:
tensor([3, 1, 1, 0, 1])
把维度调成1的话,即value,index=torch,max(tensor,1)
,就是每行的最大值和索引。
2. torchvision.transform
这里学习了这个torchvision.transform
pytorch中的图像预处理包,一般用compose把多个步骤整合到一起。
eg:
transform.Compose([transform.Resize((224,224)), transform.ToTensor, transform.Normalize(...)])
.
3.torch.squeeze()和torch.unsqueeze()的用法
对数据维度进行亚索或者解压。
torch.squeeze() 这个函数主要对数据的维度进行压缩,去掉维数为1的的维度,比如是一行或者一列这种,一个一行三列(1,3)的数去掉第一个维数为一的维度之后就变成(3)行。squeeze(a)就是将a中所有为1的维度删掉。不为1的维度没有影响。a.squeeze(N) 就是去掉a中指定的维数为一的维度。还有一种形式就是b=torch.squeeze(a,N) a中去掉指定的定的维数为一的维度。
**torch.unsqueeze()**这个函数主要是对数据维度进行扩充。给指定位置加上维数为一的维度,比如原本有个三行的数据(3),在0的位置加了一维就变成一行三列(1,3)。a.squeeze(N) 就是在a中指定位置N加上一个维数为1的维度。还有一种形式就是b=torch.squeeze(a,N) a就是在a中指定位置N加上一个维数为1的维度。
4.数据读取
我的代码中用到了
test_data = MyDataset(txt_path=test_dir, transform=valiTransform)
test_loader = DataLoader(dataset=test_data, batch_size=1)
MyDataset
MyDataset是自己定义的数据结构,继承于torch.utils.data.Dataset
class MyDataset(Dataset):
def __init__(self,txt_path,transform=None,target_transform=None):
fh=open(txt_path,'r')
imgs=[]
for line in fh:
line=line.strip('\n')
words=line.split()
path=words[0]
if (len(words)>2):
for i in range(1,len(words)-1):
path+=' '+words[i]
imgs.append((path,int(words[-1])))
self.imgs=imgs
self.transform=transform
self.target_transform=target_transform
def __getitem__(self,index):
fn,label=self.imgs[index]
img=Image.open(fn).convert('RGB')
if self.transform is not None:
img=self.transform(img)
return img,label
def __len__(self):
return len(self.imgs)
torch.utils.data的学习
torch.utils.data主要包括三个类:pytorch实现自由的数据读取-torch.utils.data的学习
(1)class torch.utils.data.Dataset
创建数据集,有_getitem__(self, index)函数来根据索引序号获取图像和标签,有_len_(self)函数来获取数据集的长度。
其他数据集必须是它的子类
(2)class torch.utils.data.sample.Sampler(data_source)
参数: data_source (Dataset) – dataset to sample from
作用: 创建一个采样器, class torch.utils.data.sampler.Sampler是所有的Sampler的基类, 其中,iter(self)函数来获取一个迭代器,对数据集中元素的索引进行迭代,len(self)方法返回迭代器中包含元素的长度.
(3)class torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, batch_sampler=None, num_workers=0, collate_fn=, pin_memory=False, drop_last=False, timeout=0, worker_init_fn=None
- dataset (Dataset): 加载数据的数据集,也就是上面所说的 torch.utils.data.Datase对象
- batch_size (int, optional): 每批加载多少个样本,默认为1
- shuffle (bool, optional): 设置为“真”时,在每个epoch对数据打乱.(默认:False)
- sampler (Sampler, optional): 定义从数据集中提取样本的策略,返回一个样本Sampler对象。
- batch_sampler (Sampler, optional): like sampler, but returns a batch of indices at a time 返回一批样本. 与atch_size, shuffle, sampler和 drop_last互斥.
- num_workers (int, optional): 用于加载数据的子进程数。0表示数据将在主进程中加载。(默认:0)
- collate_fn (callable, optional): 合并样本列表以形成一个 mini-batch. # callable可调用对象
- pin_memory (bool, optional): 如果为 True, 数据加载器会将张量复制到 CUDA 固定内存中,然后再返回它们.
- drop_last (bool, optional): 设定为 True 如果数据集大小不能被批量大小整除的时候, 将丢掉最后一个不完整的batch,(默认:False).
- timeout (numeric, optional): 如果为正值,则为从工作人员收集批次的超时值。应始终是非负的。(默认:0)
- worker_init_fn (callable, optional): If not None, this will be called on each worker subprocess with the worker id (an int in
[0, num_workers - 1]
) as input, after seeding and before data loading. (default: None).
5.torch.topk()
torch.topk(input,k,dim=None,largest=True,sorted=True,out=None) -> (Tensor,LongTensor)
沿给定dim维度返回输入张量input中 k 个最大值。
如果不指定dim,则默认为input的最后一维。
如果为largest为 False ,则返回最小的 k 个值。
返回一个元组 (values,indices),其中indices是原始输入张量input中测元素下标。
如果设定布尔值sorted 为_True_,将会确保返回的 k 个值被排序。
参数:
input (Tensor) – 输入张量
k (int) – “top-k”中的k
dim (int, optional) – 排序的维
largest (bool, optional) – 布尔值,控制返回最大或最小值
sorted (bool, optional) – 布尔值,控制返回值是否排序
out (tuple, optional) – 可选输出张量 (Tensor, LongTensor) output buffer