-
2022-03-19 22:59:42
文章首发及后续更新:https://mwhls.top/3722.html,无图/无目录/格式错误/更多相关请至首发页查看。
新的更新内容请到mwhls.top查看。
欢迎提出任何疑问及批评,非常感谢!
如有翻译问题欢迎评论指出,谢谢。
如何将数据集分为训练集与测试集?
nirvair asked:
import pandas as pd
import numpy as np
import cv2
from torch.utils.data.dataset import Dataset
class CustomDatasetFromCSV(Dataset):
def init(self, csv_path, transform=None):
self.data = pd.read_csv(csv_path)
self.labels = pd.get_dummies(self.data[‘emotion’]).as_matrix()
self.height = 48
self.width = 48
self.transform = transformdef getitem(self, index):
pixels = self.data[‘pixels’].tolist()
faces = []
for pixel_sequence in pixels:
face = [int(pixel) for pixel in pixel_sequence.split(’ ')]
# print(np.asarray(face).shape)
face = np.asarray(face).reshape(self.width, self.height)
face = cv2.resize(face.astype(‘uint8’), (self.width, self.height))
faces.append(face.astype(‘float32’))
faces = np.asarray(faces)
faces = np.expand_dims(faces, -1)
return faces, self.labelsdef len(self):
return len(self.data)这段代码是我从其它地方参考的,但我还想将数据集分为训练集和测试集。
能在这个类里面直接实现嘛?还是需要分开来实现?
Answers:
Fábio Perez – vote: 156
Pytorch 0.4.1 以上版本可以使用
random_split
:train_size = int(0.8 * len(full_dataset)) test_size = len(full_dataset) - train_size train_dataset, test_dataset = torch.utils.data.random_split(full_dataset, [train_size, test_size])
benjaminplanche – vote: 127
试试 Pytorch 的
SubsetRandomSampler
:import torch import numpy as np from torchvision import datasets from torchvision import transforms from torch.utils.data.sampler import SubsetRandomSampler # class CustomDatasetFromCSV(Dataset): def __init__(self, csv_path, transform=None): self.data = pd.read_csv(csv_path) self.labels = pd.get_dummies(self.data['emotion']).as_matrix() self.height = 48 self.width = 48 self.transform = transform # def __getitem__(self, index): # This method should return only 1 sample and label # (according to "index"), not the whole dataset # So probably something like this for you: pixel_sequence = self.data['pixels'][index] face = [int(pixel) for pixel in pixel_sequence.split(' ')] face = np.asarray(face).reshape(self.width, self.height) face = cv2.resize(face.astype('uint8'), (self.width, self.height)) label = self.labels[index] # return face, label # def __len__(self): return len(self.labels) # # dataset = CustomDatasetFromCSV(my_path) batch_size = 16 validation_split = .2 shuffle_dataset = True random_seed= 42 # # Creating data indices for training and validation splits: dataset_size = len(dataset) indices = list(range(dataset_size)) split = int(np.floor(validation_split * dataset_size)) if shuffle_dataset : np.random.seed(random_seed) np.random.shuffle(indices) train_indices, val_indices = indices[split:], indices[:split] # # Creating PT data samplers and loaders: train_sampler = SubsetRandomSampler(train_indices) valid_sampler = SubsetRandomSampler(val_indices) # train_loader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, sampler=train_sampler) validation_loader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, sampler=valid_sampler) # # Usage Example: num_epochs = 10 for epoch in range(num_epochs): # Train: for batch_index, (faces, labels) in enumerate(train_loader): # ...
Shital Shah – vote: 25
现有回答采用的是随机分割,但每个类的样本会有数量不平衡的缺点。当每类只需要少量样本时,这个问题尤为致命。例如,手写数字集 MNIST 有六万个样本,即每个数字六千个样本。假设每个训练集中你只需要30个样本,随机分割就会在类间产生不平衡(某数字比其它数字有更多的训练数据)。所以还得确保每个数字只有三十个标签,这叫做分层抽样。
译者注:他写的好长,我看懵了,差点没理解过来。简单来说就是随机抽样是在整个数据集里抽取指定数量样本;分层抽样是指在数据集的每个类别中抽取指定数量样本,再加起来作为一个训练集。一个解决方案是用 Pytorch 的接口,见示例代码。
另一种方式是自己实现 :)。例如,下面关于 MNIST 的一个简单实现中,
ds
是MNIST 数据集,k
是每个类的样本数。def sampleFromClass(ds, k): class_counts = {} train_data = [] train_label = [] test_data = [] test_label = [] for data, label in ds: c = label.item() class_counts[c] = class_counts.get(c, 0) + 1 if class_counts[c]
然后这样使用它:
def main(): train_ds = datasets.MNIST('../data', train=True, download=True, transform=transforms.Compose([ transforms.ToTensor() ])) train_ds, test_ds = sampleFromClass(train_ds, 3)
How do I split a custom dataset into training and test datasets?
nirvair asked:
import pandas as pd import numpy as np import cv2 from torch.utils.data.dataset import Dataset # class CustomDatasetFromCSV(Dataset): def __init__(self, csv_path, transform=None): self.data = pd.read_csv(csv_path) self.labels = pd.get_dummies(self.data['emotion']).as_matrix() self.height = 48 self.width = 48 self.transform = transform # def __getitem__(self, index): pixels = self.data['pixels'].tolist() faces = [] for pixel_sequence in pixels: face = [int(pixel) for pixel in pixel_sequence.split(' ')] # print(np.asarray(face).shape) face = np.asarray(face).reshape(self.width, self.height) face = cv2.resize(face.astype('uint8'), (self.width, self.height)) faces.append(face.astype('float32')) faces = np.asarray(faces) faces = np.expand_dims(faces, -1) return faces, self.labels # def __len__(self): return len(self.data)
This is what I could manage to do by using references from other repositories. However, I want to split this dataset into train and test.
这段代码是我从其它地方参考的,但我还想将数据集分为训练集和测试集。How can I do that inside this class? Or do I need to make a separate class to do that?
能在这个类里面直接实现嘛?还是需要分开来实现?
Answers:
Fábio Perez - vote: 156
Starting in PyTorch 0.4.1 you can use
random_split
:
Pytorch 0.4.1 以上版本可以使用random_split
:train_size = int(0.8 * len(full_dataset)) test_size = len(full_dataset) - train_size train_dataset, test_dataset = torch.utils.data.random_split(full_dataset, [train_size, test_size])
benjaminplanche - vote: 127
Using Pytorch\'s
SubsetRandomSampler
:
试试 Pytorch 的SubsetRandomSampler
:import torch import numpy as np from torchvision import datasets from torchvision import transforms from torch.utils.data.sampler import SubsetRandomSampler # class CustomDatasetFromCSV(Dataset): def __init__(self, csv_path, transform=None): self.data = pd.read_csv(csv_path) self.labels = pd.get_dummies(self.data['emotion']).as_matrix() self.height = 48 self.width = 48 self.transform = transform # def __getitem__(self, index): # This method should return only 1 sample and label # (according to "index"), not the whole dataset # So probably something like this for you: pixel_sequence = self.data['pixels'][index] face = [int(pixel) for pixel in pixel_sequence.split(' ')] face = np.asarray(face).reshape(self.width, self.height) face = cv2.resize(face.astype('uint8'), (self.width, self.height)) label = self.labels[index] # return face, label # def __len__(self): return len(self.labels) # # dataset = CustomDatasetFromCSV(my_path) batch_size = 16 validation_split = .2 shuffle_dataset = True random_seed= 42 # # Creating data indices for training and validation splits: dataset_size = len(dataset) indices = list(range(dataset_size)) split = int(np.floor(validation_split * dataset_size)) if shuffle_dataset : np.random.seed(random_seed) np.random.shuffle(indices) train_indices, val_indices = indices[split:], indices[:split] # # Creating PT data samplers and loaders: train_sampler = SubsetRandomSampler(train_indices) valid_sampler = SubsetRandomSampler(val_indices) # train_loader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, sampler=train_sampler) validation_loader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, sampler=valid_sampler) # # Usage Example: num_epochs = 10 for epoch in range(num_epochs): # Train: for batch_index, (faces, labels) in enumerate(train_loader): # ...
Shital Shah - vote: 25
Current answers do random splits which has disadvantage that number of samples per class is not guaranteed to be balanced. This is especially problematic when you want to have small number of samples per class. For example, MNIST has 60,000 examples, i.e. 6000 per digit. Assume that you want only 30 examples per digit in your training set. In this case, random split may produce imbalance between classes (one digit with more training data then others). So you want to make sure each digit precisely has only 30 labels. This is called stratified sampling.
现有回答采用的是随机分割,但每个类的样本会有数量不平衡的缺点。当每类只需要少量样本时,这个问题尤为致命。例如,手写数字集 MNIST 有六万个样本,即每个数字六千个样本。假设每个训练集中你只需要30个样本,随机分割就会在类间产生不平衡(某数字比其它数字有更多的训练数据)。所以还得确保每个数字只有三十个标签,这叫做分层抽样。
译者注:他写的好长,我看懵了,差点没理解过来。简单来说就是随机抽样是在整个数据集里抽取指定数量样本;分层抽样是指在数据集的每个类别中抽取指定数量样本,再加起来作为一个训练集。One way to do this is using sampler interface in Pytorch and sample code is here.
一个解决方案是用 Pytorch 的接口,见示例代码。Another way to do this is just hack your way through :). For example, below is simple implementation for MNIST where
ds
is MNIST dataset andk
is number of samples needed for each class.
另一种方式是自己实现 :)。例如,下面关于 MNIST 的一个简单实现中,ds
是MNIST 数据集,k
是每个类的样本数。def sampleFromClass(ds, k): class_counts = {} train_data = [] train_label = [] test_data = [] test_label = [] for data, label in ds: c = label.item() class_counts[c] = class_counts.get(c, 0) + 1 if class_counts[c]
You can use this function like this:
然后这样使用它:def main(): train_ds = datasets.MNIST('../data', train=True, download=True, transform=transforms.Compose([ transforms.ToTensor() ])) train_ds, test_ds = sampleFromClass(train_ds, 3)
更多相关内容 -
PyTorch常用代码段合集
2022-04-26 00:19:56作者丨Jack Stark@知乎来源丨https://zhuanlan.zhihu.com/p/104019160导读本文是PyTorch常用代码段合集,涵盖基本配置、张量处理、模型定义与操作、数据处理、模型训练与测试等5个方面,还给出了多个值得注意的Tips...作者丨Jack Stark@知乎
来源丨https://zhuanlan.zhihu.com/p/104019160
导读
本文是PyTorch常用代码段合集,涵盖基本配置、张量处理、模型定义与操作、数据处理、模型训练与测试等5个方面,还给出了多个值得注意的Tips,内容非常全面。
PyTorch最好的资料是官方文档。本文是PyTorch常用代码段,在参考资料[1](张皓:PyTorch Cookbook)的基础上做了一些修补,方便使用时查阅。
1. 基本配置
导入包和版本查询
可复现性
在硬件设备(CPU、GPU)不同时,完全的可复现性无法保证,即使随机种子相同。但是,在同一个设备上,应该保证可复现性。具体做法是,在程序开始的时候固定torch的随机种子,同时也把numpy的随机种子固定。
显卡设置
2. 张量(Tensor)处理
张量的数据类型
PyTorch有9种CPU张量类型和9种GPU张量类型。
张量基本信息
命名张量
张量命名是一个非常有用的方法,这样可以方便地使用维度的名字来做索引或其他操作,大大提高了可读性、易用性,防止出错。
数据类型转换
torch.Tensor与np.ndarray转换
除了CharTensor,其他所有CPU上的张量都支持转换为numpy格式然后再转换回来。
Torch.tensor与PIL.Image转换
np.ndarray与PIL.Image的转换
从只包含一个元素的张量中提取值
张量形变
打乱顺序
水平翻转
复制张量
张量拼接
将整数标签转为one-hot编码
得到非零元素
判断两个张量相等
张量扩展
矩阵乘法
计算两组数据之间的两两欧式距离
利用broadcast机制
3. 模型定义和操作
一个简单两层卷积网络的示例
卷积层的计算和展示可以用这个网站辅助。
双线性汇合(bilinear pooling)
多卡同步 BN(Batch normalization)
当使用 torch.nn.DataParallel 将代码运行在多张 GPU 卡上时,PyTorch 的 BN 层默认操作是各卡上数据独立地计算均值和标准差,同步 BN 使用所有卡上的数据一起计算 BN 层的均值和标准差,缓解了当批量大小(batch size)比较小时对均值和标准差估计不准的情况,是在目标检测等任务中一个有效的提升性能的技巧。
将已有网络的所有BN层改为同步BN层
类似 BN 滑动平均
如果要实现类似 BN 滑动平均的操作,在 forward 函数中要使用原地(inplace)操作给滑动平均赋值。
计算模型整体参数量
查看网络中的参数
可以通过model.state_dict()或者model.named_parameters()函数查看现在的全部可训练参数(包括通过继承得到的父类中的参数)
模型可视化(使用pytorchviz)
szagoruyko/pytorchvizgithub.com
类似 Keras 的 model.summary() 输出模型信息,使用pytorch-summary
sksq96/pytorch-summarygithub.com
模型权重初始化
注意 model.modules() 和 model.children() 的区别:model.modules() 会迭代地遍历模型的所有子层,而 model.children() 只会遍历模型下的一层。
提取模型中的某一层
modules()会返回模型中所有模块的迭代器,它能够访问到最内层,比如self.layer1.conv1这个模块,还有一个与它们相对应的是name_children()属性以及named_modules(),这两个不仅会返回模块的迭代器,还会返回网络层的名字。
部分层使用预训练模型
注意如果保存的模型是 torch.nn.DataParallel,则当前的模型也需要是
将在 GPU 保存的模型加载到 CPU
导入另一个模型的相同部分到新的模型
模型导入参数时,如果两个模型结构不一致,则直接导入参数会报错。用下面方法可以把另一个模型的相同的部分导入到新的模型中。
4. 数据处理
计算数据集的均值和标准差
得到视频数据基本信息
TSN 每段(segment)采样一帧视频
常用训练和验证数据预处理
其中 ToTensor 操作会将 PIL.Image 或形状为 H×W×D,数值范围为 [0, 255] 的 np.ndarray 转换为形状为 D×H×W,数值范围为 [0.0, 1.0] 的 torch.Tensor。
5. 模型训练和测试
分类模型训练代码
分类模型测试代码
自定义loss
继承torch.nn.Module类写自己的loss。
标签平滑(label smoothing)
写一个label_smoothing.py的文件,然后在训练代码里引用,用LSR代替交叉熵损失即可。label_smoothing.py内容如下:
或者直接在训练文件里做label smoothing
Mixup训练
L1 正则化
不对偏置项进行权重衰减(weight decay)
pytorch里的weight decay相当于l2正则
梯度裁剪(gradient clipping)
得到当前学习率
另一种方法,在一个batch训练代码里,当前的lr是optimizer.param_groups[0]['lr']
学习率衰减
优化器链式更新
从1.4版本开始,torch.optim.lr_scheduler 支持链式更新(chaining),即用户可以定义两个 schedulers,并交替在训练中使用。
模型训练可视化
PyTorch可以使用tensorboard来可视化训练过程。
安装和运行TensorBoard。
pip install tensorboard tensorboard --logdir=runs
使用SummaryWriter类来收集和可视化相应的数据,放了方便查看,可以使用不同的文件夹,比如'Loss/train'和'Loss/test'。
保存与加载断点
注意为了能够恢复训练,我们需要同时保存模型和优化器的状态,以及当前的训练轮数。
提取 ImageNet 预训练模型某层的卷积特征
提取 ImageNet 预训练模型多层的卷积特征
微调全连接层
以较大学习率微调全连接层,较小学习率微调卷积层
6. 其他注意事项
不要使用太大的线性层。因为nn.Linear(m,n)使用的是的内存,线性层太大很容易超出现有显存。
不要在太长的序列上使用RNN。因为RNN反向传播使用的是BPTT算法,其需要的内存和输入序列的长度呈线性关系。
model(x) 前用 model.train() 和 model.eval() 切换网络状态。
不需要计算梯度的代码块用 with torch.no_grad() 包含起来。
model.eval() 和 torch.no_grad() 的区别在于,model.eval() 是将网络切换为测试状态,例如 BN 和dropout在训练和测试阶段使用不同的计算方法。torch.no_grad() 是关闭 PyTorch 张量的自动求导机制,以减少存储使用和加速计算,得到的结果无法进行 loss.backward()。
model.zero_grad()会把整个模型的参数的梯度都归零, 而optimizer.zero_grad()只会把传入其中的参数的梯度归零.
torch.nn.CrossEntropyLoss 的输入不需要经过 Softmax。torch.nn.CrossEntropyLoss 等价于 torch.nn.functional.log_softmax + torch.nn.NLLLoss。
loss.backward() 前用 optimizer.zero_grad() 清除累积梯度。
torch.utils.data.DataLoader 中尽量设置 pin_memory=True,对特别小的数据集如 MNIST 设置 pin_memory=False 反而更快一些。num_workers 的设置需要在实验中找到最快的取值。
用 del 及时删除不用的中间变量,节约 GPU 存储。
使用 inplace 操作可节约 GPU 存储,如
减少 CPU 和 GPU 之间的数据传输。例如如果你想知道一个 epoch 中每个 mini-batch 的 loss 和准确率,先将它们累积在 GPU 中等一个 epoch 结束之后一起传输回 CPU 会比每个 mini-batch 都进行一次 GPU 到 CPU 的传输更快。
使用半精度浮点数 half() 会有一定的速度提升,具体效率依赖于 GPU 型号。需要小心数值精度过低带来的稳定性问题。
时常使用 assert tensor.size() == (N, D, H, W) 作为调试手段,确保张量维度和你设想中一致。
除了标记 y 外,尽量少使用一维张量,使用 n*1 的二维张量代替,可以避免一些意想不到的一维张量计算结果。
统计代码各部分耗时
使用TorchSnooper来调试PyTorch代码,程序在执行的时候,就会自动 print 出来每一行的执行结果的 tensor 的形状、数据类型、设备、是否需要梯度的信息。
https://github.com/zasdfgbnm/TorchSnoopergithub.com
模型可解释性,使用captum库:https://captum.ai/captum.ai
参考资料
张皓:PyTorch Cookbook(常用代码段整理合集),https://zhuanlan.zhihu.com/p/59205847?
PyTorch官方文档和示例
https://pytorch.org/docs/stable/notes/faq.html
https://github.com/szagoruyko/pytorchviz
https://github.com/sksq96/pytorch-summary
其他
觉得还不错就给我一个小小的鼓励吧!
-
pytorch量化测试代码,mobilenetv2在cifar10速度测试
2022-03-31 11:32:551、pytorch动态量化、静态量化、感知训练测试代码; -
pytorch神经网络训练及测试流程&代码
2022-04-24 09:59:22神经网络的训练及测试其实是个相对固定的流程,下面进行详细说明,包括命令行设置基本参数、如数据集路径等其他参数的设置、学习率、损失函数、模型参数的保存与加载及最终train.py与test.py的main()函数写法神经网络的训练及测试其实是个相对固定的流程,下面进行详细说明,包括命令行设置基本参数、如数据集路径等其他参数的设置、学习率、损失函数、模型参数的保存与加载及最终train.py与test.py的main()函数写法
当你已经设计好了一个神经网络模型MyModel,它可以在model_my.py中封装成MyNet:
class MyModel(nn.Module): def __init__(self, variable1, variable2, ...): super(MyModel, self).__init__() self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False) self.bn1 = nn.BatchNorm2d(64, affine=affine_par) self.relu = nn.ReLU(inplace=True) self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1, ceil_mode=True) ...... def forward(self, input1, input2, input3): ...... return ouput1, output2, output3, ... def MyNet([needed_variables]): model = MyModel(variable1, variable2, ...) return model
在train.py或test.py文件中,调用该函数以引入该网络:
from xxx import MyNet ...... model = MyNet([needed_variables]) ......
1、训练&测试的py文件都要有的函数
get_arguments():获取所需参数
def get_arguments(): parser = argparse.ArgumentParser(description="myNet") # 创建解析器,desciption是说明文字 # 进行参数的设置,以学习率为例,learning-rate是参数名字,type=float设置数据类型,default设置默认值,help的内容是说明文字 parser.add_argument("--learning-rate", type=float, default=0.001, help="Base learning rate for training.") ...... parser.add_argument("--dataset", type=str, default='davis16', help="duts, coco, or davis16.") # GPU的设置 parser.add_argument("--cuda", default=True, help="Run on CPU or GPU") parser.add_argument("--gpus", type=str, default="1", help="choose gpu device.") #使用1号GPU(注意,是从0开始排的) return parser.parse_args()
比如,当要运行mytrain.py文件时,进入到同一目录下,在终端命令行输入:
python mytrain.py --learning-rate 0.002 --dataset duts --gpus 3
,即可手动设置对应参数值,其余参数则使用默认值configure_dataset_model(args):添加对应数据集的路径等参数
def configure_dataset_model(args): if args.dataset == 'davis16': args.batch_size = 5 # 每次输入网络的图片数量 args.maxEpoches = 15 # maxIterations= maxEpoches*len(train_aug)/batch_size_per_gpu args.data_dir = 'mypath/dataset/DAVIS16' # 数据集的路径 args.data_list = 'mypath/dataset/DAVIS16/train_seqs.txt' # args.data_list = 'mypath/dataset/DAVIS16/test_seqs.txt' # 准备好的训练或测试的视频序列集名字,txt文件中每行都是一个视频序列名字,后面有举例 args.input_size = '473,473' # 统一输入图片的大小,可选 ...... args.restore_from = './pretrained/deep_labv3/deeplab_davis_12_0.pth' # 需要用到的预训练模型,根据需要更改,这里是训练阶段要用到deeplabv3模型 # args.restore_from = './snapshots/davis_iteration/mynet_555.pth' # 测试阶段用的是这个,我们网络训练好的模型 args.snapshot_dir = './snapshots/davis_iteration' # 保存训练模型的路径,测试阶段不需要 args.save_dir = './result/test/' # 测试阶段保存输出图片的路径 elif args.dataset == 'duts': ...... # 每个数据集都是同上操作 else: print("dataset error") # 做一个数据集不存在的报错反馈
# test_seqs.txt blackswan bmx-trees breakdance camel car-roundabout car-shadow cows dance-twirl dog drift-chicane drift-straight goat horsejump-high kite-surf libby motocross-jump paragliding-launch parkour scooter-black soapbox
1.1 初始化模型参数
这样一来,在main函数中我们就可以初始化所有模型参数
def main(): args = get_arguments() print("=====> Configure dataset and model") configure_dataset_model(args) print(args) # 设置训练的GPU print("=====> Set GPU for training") if args.cuda: print("====> Use gpu id: '{}'".format(args.gpus)) os.environ["CUDA_VISIBLE_DEVICES"] = args.gpus if not torch.cuda.is_available(): raise Exception("No GPU found or Wrong gpu id, please run without --cuda") # 如果没有GPU导致出现提示,需要在最开始运行的时候命令'--cuda False' # 训练时需要的模块:设置生成随机数的种子,使得每次运行该文件的输出结果都一样,而不是每次随机函数生成的结果一样 print("=====> Random Seed: ", args.random_seed) torch.manual_seed(args.random_seed) if args.cuda: torch.cuda.manual_seed(args.random_seed) ......
数据集的加载及预处理
为了模块化,我们新建一个pre_dataset.py文件作为数据预处理模块:
class PreData(Dataset): def __init__(self, data_path, data_list): self.fwflow_list = [] self.bwflow_list = [] self.img_list = [] self.label_list = [] with open(data_list) as f: seqs = f.readlines() seqs = [seq.strip() for seq in seqs] print(seqs) # 以DAVIS16为例: for i in seqs: self.img_list+=sorted(glob.glob(os.path.join(data_path, "JPEGImages/480p", i, "*.jpg")))[:-1] self.label_list+=sorted(glob.glob(os.path.join(data_path, "Annotations/480p", i, "*.png")))[:-1] self.fwflow_list+=sorted(glob.glob(os.path.join(data_path, "davis_flow", i, "*.png"))) self.bwflow_list+=sorted(glob.glob(os.path.join(data_path, "davis_bwflow", i, "*.png"))) def __len__(self): return len(self.img_list) def __getitem__(self, item): # 这里的网络输入为视频序列中两帧图片+对应光流图,按需修改这个函数 frame = [item] scope = 10 # 设置最大随机范围 other = np.random.randint(-scope, scope) while item + other >= self.dataset_len or item + other < 0 or other == 0: other = np.random.randint(-scope, scope) #print(item, other) name1 = self.img_list[item] name2 = self.img_list[item + other] #print(name1,name2) while name1.split('/')[-2] != name2.split('/')[-2]: other = np.random.randint(-scope, scope) while item + other >= self.dataset_len or item + other < 0 or other == 0: other = np.random.randint(-scope, scope) #print(item,other) name2 = self.img_list[item + other] # print('in') frame.append(item + other) # 当前帧和随机挑选视频序列的另一帧作为一组输入 videos, labels, fwflows, bwflows = [], [], [], [] for i in frame: video = imread(self.img_list[i]) fw = imread(self.fwflow_list[i]) bw = imread(self.bwflow_list[i]) label = imread(self.label_list[i]) if len(label.shape) == 3: label = label[:, :, 0] label = label[:, :, np.newaxis] videos.append(img_normalize(video.astype(np.float32) / 255.)) labels.append(label.astype(np.float32) / 255.) fwflows.append(img_normalize(fw.astype(np.float32) / 255.)) bwflows.append(img_normalize(bw.astype(np.float32) / 255.)) H, W = labels[0].shape[0], labels[0].shape[1] #print(H,W) return {'video': F.interpolate(torch.from_numpy(np.stack(videos, 0)).permute(0, 3, 1, 2), (self.H, self.W), mode='bilinear', align_corners=True), 'fwflow': F.interpolate(torch.from_numpy(np.stack(fwflows, 0)).permute(0, 3, 1, 2), (self.H, self.W), mode='bilinear', align_corners=True), 'bwflow': F.interpolate(torch.from_numpy(np.stack(bwflows, 0)).permute(0, 3, 1, 2), (self.H, self.W), mode='bilinear', align_corners=True), "label":torch.from_numpy(np.stack([labels[0]], 0)).permute(0, 3, 1, 2), "H":H, "W":W, 'name': self.img_list[item].split("/")[-2]+"/"+self.img_list[item].split("/")[-1]} #返回需要的数据 # 图像颜色的归一化函数(统一为灰度图),在上面的__getitem__中有用到 def img_normalize(image): if len(image.shape)==2: channel = (image[:, :, np.newaxis] - 0.485) / 0.229 image = np.concatenate([channel,channel,channel], axis=2) else: image = (image-np.array([0.485, 0.456, 0.406], dtype=np.float32).reshape((1, 1, 3)))\ /np.array([0.229, 0.224, 0.225], dtype=np.float32).reshape((1, 1, 3)) return image
1.2 加载数据集
这样在训练或测试文件的main函数中就可以加载数据集了:
def main(): ...... if args.dataset == 'davis': h, w = map(int, args.input_size.split(',')) input_size = (h, w) # 测试集为例: db_test = db.PairwiseImg(data_path=args.data_dir, data_list=args.data_list) testloader = data.DataLoader(db_test, batch_size= 1, shuffle=False, num_workers=0) elif args.dataset == 'duts': ......
2、训练还需要有的东西
设置学习率的自适应
有许多方法可用,下面只是一个例子 def adjust_learning_rate(optimizer, decay_count, decay_rate=.9): for param_group in optimizer.param_groups: param_group['lr'] = max(1e-5, 5e-4 * pow(decay_rate, decay_count)) print(param_group['lr'])
损失函数
举个例子,如果我需要用到二进制交叉熵损失bce,那么定义这样一个函数:
bce_loss = nn.BCELoss(reduction='mean') def bce_loss(pred, target): loss = 0 bce_out = bce_loss(pred, target) loss += bce_out return loss
按需封装成相应的函数,后续使用直接调用就行
训练网络准备
设置优化器
def main(): ......#(前面提到的初始化模型参数部分) param_group = [{'params': get_lr_params(model), 'lr': 1*args.learning_rate }, {'params': get_last_lr_params(model), 'lr': 10*args.learning_rate}] #针对特定层进行学习 optimizer = optim.SGD(param_group, lr=args.learning_rate, momentum=args.momentum, weight_decay=args.weight_decay) # SGD:随机梯度下降,也可按需改成别的优化方法 optimizer.zero_grad() # 将每个参数的梯度值都置为0,即初始化 ......
其中:
def get_lr_params(model): """ 返回网络的所有参数(不包括最后的分类层) """ b = [] if torch.cuda.device_count() == 1: # 当只使用一个GPU进行训练时,个人认为由于空间有限,所以只针对某个层进行学习 b.append(model.encoder.layer3) else: # 当使用多个GPU进行训练时 b.append(model.module.encoder.conv1) b.append(model.module.encoder.bn1) b.append(model.module.encoder.layer1) b.append(model.module.encoder.layer2) b.append(model.module.encoder.layer3) b.append(model.module.encoder.main_classifier) for i in range(len(b)): for j in b[i].modules(): jj = 0 for k in j.parameters(): jj+=1 if k.requires_grad: yield k # 也可以直接append参数,则改成: b = [] if torch.cuda.device_count() == 1: b.append(model.encoder.layer3.parameters()) else: b.append(model.module.encoder.conv1.parameters()) b.append(model.module.encoder.bn1.parameters()) b.append(model.module.encoder.layer1.parameters()) b.append(model.module.encoder.layer2.parameters()) b.append(model.module.encoder.layer3.parameters()) b.append(model.module.encoder.main_classifier.parameters()) for j in range(len(b)): for i in b[j]: yield i def get_last_lr_params(model): """ 返回网络最后分类层的的所有参数 """ ...... # 同上类似的操作
创建网络
def main(): ......#(初始化模型参数+设置优化器+加载数据集) print("=====> Building network") saved_state_dict = torch.load(args.restore_from) #载入deeplabv3模型(参数) model = MyNet([needed_variables]) #引入GNNNet网络 new_params = model.state_dict().copy() #保存GNNNet参数 calt = 0 for i in saved_state_dict["model"]: i_parts = i.split('.') # 针对多GPU的情况 print('i_parts: ', '.'.join(i_parts[1:-1])) new_params['encoder'+'.'+'.'.join(i_parts[1:])] = saved_state_dict["model"][i] print("=====> Loading init weights") model.load_state_dict(new_params) if args.cuda: if torch.cuda.device_count()>1: print("torch.cuda.device_count()=",torch.cuda.device_count()) model = torch.nn.DataParallel(model).cuda() print("more than 1 gpu") else: print("single GPU for training") model = model.cuda() model.train() #把模型设置成训练模式 cudnn.benchmark = True if not os.path.exists(args.snapshot_dir): os.makedirs(args.snapshot_dir) # 新建保存模型的文件夹 print('=====> Computing network parameters') total_paramters = netParams(model) print('Total network parameters: ' + str(total_paramters)) # 日志文件 logFileLoc = args.snapshot_dir + args.logFile if os.path.isfile(logFileLoc): logger = open(logFileLoc, 'a') else: logger = open(logFileLoc, 'w') logger.write("Parameters: %s" % (str(total_paramters))) logger.write("\n%s\t\t%s" % ('iter', 'Loss(train)\n')) logger.flush() # 计算网络参数数量的函数(上面用到了) def netParams(model): ''' Computing total network parameters Args: model: model return: total network parameters ''' total_paramters = 0 for parameter in model.parameters(): i = len(parameter.size()) #print(parameter.size()) p = 1 for j in range(i): p *= parameter.size(j) total_paramters += p return total_paramters
3、训练部分train.py的main函数
def main(): start = timeit.default_timer() # 开始时间 ......#(网络创建部分) print("=====> Begin to train") train_len=len(trainloader) print(" iteration numbers of per epoch: ", train_len) print(" epoch num: ", args.maxEpoches) print(" max iteration: ", args.maxEpoches*train_len) for epoch in range(1, int(args.maxEpoches)): running_loss = 0.0 ite_num_per = 0 iter_num = 0 datasampler.set_epoch(epoch) model.train() i=0 if epoch>15: adjust_learning_rate(optimizer, (epoch-20))# 学习率自适应 for data in trainloader: ite_num_per = ite_num_per + 1 i+=1 iter_num = iter_num + 1 img, fw_flow, bw_flow, label = data['video'].cuda(), \ data['fwflow'].cuda(),\ data['bwflow'].cuda(),\ data['label'].cuda() B, Seq, C, H, W = img.size() spatial_out, temporal_out = model(img, torch.cat((fw_flow, bw_flow), 2)) # 网络的输出,这个根据自己的网络模型来写 spatial_loss = bce_loss(spatial_out, label.view(B * Seq, 1, H, W)) temporal_loss = bce_loss(temporal_out, label.view(B * Seq, 1, H, W)) # 这里假设用的是我们前面写的bce_loss函数 loss = spatial_loss + temporal_loss running_loss += loss.item() # 总体损失 loss.backward() # 反向传播,计算当前梯度 optimizer.step() # 根据梯度更新网络参数 optimizer.zero_grad() # 清空之前的梯度 print("[epoch: {}/{}, iter: {}/{}, iter: {}] train loss: {:.5f}".format(epoch, epoch_num, i, len(dataloader), iter_num, running_loss / ite_num_per)) logger.write("Epoch[{}]({}/{}): Loss: {:.10f} lr: {:.5f}\n".format(epoch, i_iter, train_len, loss, lr)) # 写日志文件 logger.flush() # 刷新缓冲区 print("=====> saving model") torch.save({'state_dict': model.state_dict(), 'optimizer': optimizer.state_dict()}, args.snapshot_dir + "epoch_{}_loss_{:.5f}.pth".format(epoch, running_loss / ite_num_per)) # 保存当前模型参数 end = timeit.default_timer() # 结束时间 print(float(end-start)/3600, 'h') # 整体训练时长 logger.write("total training time: {:.2f} h\n".format(float(end-start)/3600)) logger.close()
4、测试部分test.py的main函数
测试部分相对简单,不需要额外的函数了,可以直接写出测试部分的main函数代码:
def main(): ......#(初始化模型参数+加载数据集) # 加载训练好的网络模型参数 print("=====> Loading network") model = MyNet([needed_variables]).cuda() for param in model.parameters(): param.requires_grad = False saved_state_dict = torch.load(args.restore_from) model_dict = model.state_dict() pretrained_dict = {k[7:]: v for k, v in saved_state_dict.items() if k[7:] in model_dict} model_dict.update(pretrained_dict) model.load_state_dict(model_dict) model.eval() start = timeit.default_timer() # 开始时间 num = 0 # 计算数量 for data in testloader: img, fw_flow, bw_flow, H, W = data['video'].cuda(), data['fwflow'].cuda(), data['bwflow'].cuda(), data["H"].cuda(), data["W"].cuda() flow = torch.cat((fw_flow, bw_flow), 2) with torch.no_grad(): out, _ = model(img, flow) # 对模型的输出结果进行相应处理,按照自己需要来写 out = F.interpolate(out[0], (H, W), mode='bilinear', align_corners=True) out = out[0, 0].cpu().numpy() out = (out - np.min(out) + 1e-12) / (np.max(out) - np.min(out) + 1e-12) * 255. out = out.astype(np.uint8) save_folder = args.save_dir + "davis16/" + data['name'][0].split("/")[-2] if not os.path.exists(save_folder): os.makedirs(save_folder) # 新建测试结果保存的文件夹 imwrite(save_folder + "/" + data['name'][0].split("/")[-1], out) # 将输出的图片保存到文件夹内 print('save: '+ data['name'][0]) num += 1 end = timeit.default_timer() # 结束时间 total_time = end-start print('total_time:' + str(total_time) + ', fps:' + str(num / total_time)) # fps是frame per second,每秒能够完成的图片数量
参考于以下文献提供的代码:
[1] Wang W, Lu X, Shen J, et al. Zero-shot video object segmentation via attentive graph neural networks[C]//Proceedings of the IEEE/CVF International Conference on Computer Vision. 2019: 9236-9245.
代码链接:https://github.com/carrierlxk/AGNN
[2] Ren S, Liu W, Liu Y, et al. Reciprocal transformations for unsupervised video object segmentation[C]//Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition. 2021: 15455-15464.
代码链接:https://github.com/OliverRensu/RTNet -
PyTorch学习笔记(18)--划分训练集和测试集的脚本
2021-12-03 20:23:11PyTorch学习笔记(18)–划分训练集和测试集的脚本文件 本博文是PyTorch的学习笔记,第18次内容记录,主要记录了如何自动的划分训练集和测试集。主要包括了2种方式,第1种方式针对的是数据集是按照类别存放...PyTorch学习笔记(18)–划分训练集和测试集的脚本文件
本博文是PyTorch的学习笔记,第18次内容记录,主要记录了如何自动的划分训练集和测试集。主要包括了2种方式,第1种方式针对的是数据集是按照类别存放在多个文件夹中,适用于分类问题,将同一类的图片划分为训练集和测试集,第2种方式针对数据不按照分类存放,而是直接放在同一个文件夹下,将数据分成训练集和测试集。
1.按分类存放
在进行训练集与测试集划分时,需要划分的文件夹是:flower_data/flower_photos,下面有5个分类的文件夹,分别为:daisy、dandelion、roses、sunflowers、tulips,进行分类的脚本为split_data.py,脚本文件split_data.py与数据文件夹flower_photos是并列的关系,都放在flower_data文件夹下,如下图所示:
脚本文件split_data.py的代码如下:# coding :UTF-8 # 文件功能: 代码实现自动将数据集划分为训练集和验证集的功能 # 开发人员: XXX # 开发时间: 2021/12/3 6:07 下午 # 文件名称: split_data.py # 开发工具: PyCharm import os from shutil import copy, rmtree import random def mk_file(file_path: str): if os.path.exists(file_path): # 如果文件夹存在,则先删除原文件夹再重新创建 rmtree(file_path) os.makedirs(file_path) def main(): # 保证随机可复现 random.seed(0) # 将数据集中10%的数据划分到验证集中 split_rate = 0.1 # 指向你解压后的flower_photos文件夹 cwd = os.getcwd() # 用于返回当前工作目录 data_root = os.path.join(cwd, "flower_data") origin_flower_path = os.path.join(data_root, "flower_photos") assert os.path.exists(origin_flower_path), "path '{}' does not exist.".format(origin_flower_path) flower_class = [cla for cla in os.listdir(origin_flower_path) if os.path.isdir(os.path.join(origin_flower_path, cla))] # 建立保存训练集的文件夹 train_root = os.path.join(data_root, "train") mk_file(train_root) for cla in flower_class: # 建立每个类别对应的文件夹 mk_file(os.path.join(train_root, cla)) # 建立保存验证集的文件夹 val_root = os.path.join(data_root, "val") mk_file(val_root) for cla in flower_class: # 建立每个类别对应的文件夹 mk_file(os.path.join(val_root, cla)) for cla in flower_class: cla_path = os.path.join(origin_flower_path, cla) images = os.listdir(cla_path) num = len(images) # 随机采样验证集的索引 eval_index = random.sample(images, k=int(num * split_rate)) for index, image in enumerate(images): if image in eval_index: # 将分配至验证集中的文件复制到相应目录 image_path = os.path.join(cla_path, image) new_path = os.path.join(val_root, cla) copy(image_path, new_path) else: # 将分配至训练集中的文件复制到相应目录 image_path = os.path.join(cla_path, image) new_path = os.path.join(train_root, cla) copy(image_path, new_path) print("\r[{}] processing [{}/{}]".format(cla, index + 1, num), end="") # processing bar print() print("processing done!") if __name__ == '__main__': main()
2.所有的按一个文件夹存放
当所有的图像放在一个文件夹下存放时,将这个文件夹下的图像分成训练集和测试集,windows版本代码如下:
# -*- coding: utf-8 -*- """ Created on Tue Jul 20 16:28:13 2021 @author: NN """ import os import random import shutil # 原始数据集路径 # origion_path = r'D:\蓝藻门' origion_path = r'E:\BaiduNetdiskDownload\data_20211112_train_new' names = os.listdir(origion_path) # 保存路径 # save_train_dir = r'D:\藻类识别神经网络\分类网络\train' # save_test_dir = r'D:\藻类识别神经网络\分类网络\test' # 数据集类别及数量 for i in names: file_list = origion_path + '\\' + i image_list = os.listdir(file_list) # 获取图片的原始路径 image_number = len(image_list) train_number = int(image_number * 0.75) train_sample = random.sample(image_list, train_number) # 从image_list中随机获取0.8比例的图像. test_sample = list(set(image_list) - set(train_sample)) # 创建保存路径 save_train_dir = r'D:\藻类数据\data_20211112\train' + '\\' + i save_test_dir = r'D:\藻类数据\data_20211112\test' + '\\' + i if not os.path.isdir(save_train_dir): os.makedirs(save_train_dir) if not os.path.isdir(save_test_dir): os.makedirs(save_test_dir) # 复制图像到目标文件夹 for j in train_sample: shutil.copy(file_list + '\\' + j, save_train_dir) for k in test_sample: shutil.copy(file_list + '\\' + k, save_test_dir)
-
pytorch实现自己制作训练集和测试集
2020-12-06 11:08:57pytorch可用于图像识别,但我们现在绝大部分用的是MINIST和cifar10图片,想要用自己的训练和测试图像路径,需要制作读取训练集和测试集的代码。本文讲述pytorch实现读取训练集和测试集通用代码。 首先讲一下读取图片... -
pytorch训练cifar100测试单GPU效率代码
2020-08-25 16:35:28pytorch训练cifar100测试单GPU效率代码,用于测试GPU效率,基于开源https://github.com/weiaicunzai/pytorch-cifar100 -
Pytorch训练代码框架
2022-03-21 19:37:53Pytorch训练代码框架 前言 自己在学习和coding的过程中,感觉每次搞一个模型,需要写一堆的过程代码(大部分是可复用的),有的时候还需要从之前或者各个博客cv一点代码,这样开发起来效率可能比较低,所以整理了一... -
PICK-pytorch:论文代码“ PICK
2021-03-19 11:30:19有关如何从采样训练/测试集并生成相应注释的更多详细信息,请参阅我们的。***** *****于2020年9月17日更新:现已提供有关大规模文档理解数据集训练示例。请参阅以获取更多详细信息。感谢贡献。***** PyTorch重新... -
pytorch实现自己制作图像训练集测试集代码(二)
2020-12-09 16:42:21第二篇pytorch实现自己制作图像集代码: from torchvision.datasets import ImageFolder from torchvision import transforms ROOT_TRAIN = r'D:/pycharm/pychanrm项目文件/data/已知类别' #训练路径 ROOT_TEST = r... -
【小白学PyTorch】 2.浅谈训练集验证集和测试集
2020-09-14 11:00:00一开始接触机器学习只知道训练集和测试集,后来听到了验证集这个词,发现验证集和之前所认识的测试集的用法是一样的,一直就把验证集和测试集给混淆了。 首先需要知道的是,在工程应用中,最终提交给客户的模型是... -
pytorch测试集看每类准确率遇到了一点bug
2019-09-23 10:11:48代码原本就用的.item(),不知道为何依然报这个错。。试着改了几次都不行 源代码就是网上常用的 ``` N_CLASSES=6; BATCH_SIZE=16 classes = ('Sun', 'Rain', 'SmallFog', 'MidFog','BigFog','Snow') ... -
violence-recognition-pytorch的测试代码
2021-03-23 15:07:59其中包括了暴恐识别,我用了https://github.com/swathikirans/violence-recognition-pytorch这个项目代码,将其中的代码修改为符合Pytorch1.4版本的风格,就愉快的训练了,但是发现训练完成后并没有测试代码,于是... -
MASTER-pytorch:论文代码“ MASTER
2021-04-16 16:58:42该项目不同于我们最初基于公司隐私代码库FastOCR构建的实现。 您还可以在存储库中找到Tensorflow重新实现,并且性能几乎相同。 (PS。徽标来自功夫熊猫大师奥格威大师的灵感) 基于MASTER的荣誉 第一名(2020/10)... -
Pytorch图像二分类代码 AlexNet
2022-02-22 21:15:22import torch import torch.nn as nn import torch.optim as optim import torch.utils.data import torch.nn.functional as F import torchvision from torchvision import transforms from PIL import Image ... -
PyTorch 代码模板 (CNN)
2022-03-24 13:23:46这是一篇自用的 PyTorch 代码模板,将模型和数据的相关代码进行替换就可以训练新的模型和数据,因为自己有时候需要测试写一些代码,但是从头写又记不住,直接在这里放一份模板,用的时候改一改就好了。 主要参考... -
pytorch生成带滑动的训练集测试集(batch)
2022-04-14 16:29:14话不多说,先上代码 Example1 from torch.utils import data import torch batch_size = 3 t1 = np.arange(0,20) t2 = np.arange(0,20)+100 生成序列0~19和100~190,分别模拟x_train和y_train tensor1 = ... -
Pytorch将数据集划分为训练集、验证集和测试集
2020-08-26 16:36:44但是Pytorch中没有提供数据集划分的操作,需要手动将原始的数据集划分为训练集、验证集和测试集,废话不多说,这里我写了一个工具类,帮助大家将数据集自动划分为训练集、验证集和测试集,还可以指定比例,代码如下... -
BP-Neural-Network:numpy实现的bp算法,代码是pytorch风格,可供pytorch入门学习,配合plotly.js获得良好的...
2021-05-16 22:18:20前端可实时监控训练集loss与测试集loss以及训练测试集拟合曲线结果展示2d可视化训练可拟合常用一元函数,支持的数学符号:sin,cos,power(乘方),pi 。loss曲线中橙色为验证集loss,蓝色为训练集loss3d可视化训练... -
pytorch 加载自制图像数据集并划分训练集和测试集:
2021-06-23 09:28:38一、pytorch 加载自制图像数据集并划分训练集和测试集步骤: 1、对数据集进行文件夹组织处理以适应ImageFolder的要求,其格式为: datasrc/dog/xxx.png datasrc/dog/xxy.png datasrc/dog/xzz.png datasrc/cat... -
pytorch模型加载跑测试集和训练过程中跑测试集结果不一致的问题?
2022-04-21 23:28:18又出问题pytorch模型加载跑测试集和训练过程中跑测试集结果不一致的问题? 虽然利用model.val()可以使得结果和训练时结果相似,但是误差相比训练时的测试还是有一定影响!(视情况而定),如何让他们的结果彻底相同... -
Pytorch极简入门教程(七)—— 划分训练集和测试集
2020-12-07 14:13:31# -*- coding: utf-8 -*- from torch.utils.data import TensorDataset from torch.utils.data import DataLoader import torch import pandas as pd import numpy as np import matplotlib.pyplot as plt ... -
解决Pytorch 训练与测试时爆显存(out of memory)的问题
2020-09-18 19:25:22今天小编就为大家分享一篇解决Pytorch 训练与测试时爆显存(out of memory)的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧 -
关于pytorch处理类别不平衡的问题
2020-12-23 11:03:16当训练样本不均匀时,我们可以...下面的代码展示了如何使用WeightedRandomSampler来完成抽样。 numDataPoints = 1000 data_dim = 5 bs = 100 # Create dummy data with class imbalance 9 to 1 data = torch.Float -
pytorch学习教程之自定义数据集
2020-12-16 20:48:54自定义数据集 在训练深度学习模型之前,样本集的制作非常重要。在pytorch中,提供了一些接口和类,方便我们定义自己的数据集合,... data/test——-测试集 data/train——训练集 data/val——–验证集 在test/tra -
pytorch 训练数据以及测试 全部代码(3)
2018-09-30 22:50:35上面这个也是核心代码.loss.backward()进行了求梯度运算得到梯度,optimizer.step()是进行模型参数的更新,更新完之后梯度要清空optimizer.zero_grad(),如此反复将模型进行优化.同时保存数据:第几次迭代,该迭代... -
Pytorch---- CIFAR10实战(训练集+测试集+验证集)完整版,逐行注释-----学习笔记
2021-12-14 17:02:40完整代码(训练集+测试集):程序结果:验证集完整代码(验证集): CIFAR10数据集准备、加载 解释一下里面的参数 root=数据放在哪。 train=是否为训练集 。 download=是否去网上下载。 里面的那个 transform 就是转换数据... -
matlab精度检验代码-mtcnn-pytorch:该项目在python3中运行
2021-05-22 02:35:57matlab精度检验代码mtcnn-pytorch 该项目用于mtcnn培训和测试。 使用python3,nvidia NGC pytorch图像18.06-py3 修改PNet:添加带有深度卷积的替换conv3,以--lr 0.005实现精度= 0.9348。 用--lr 0.005 --batch_size... -
pytorch 实现yolo3详细理解(五)训练自己数据集和csv数据集标签处理
2020-12-21 20:06:02摘要 前面基本已经将yolo3的大致细节都分析了,那么现在就要训练自己下载的数据集来看下效果,进行迁移学习,首先我会先对github...代码githubhttps://github.com/eriklindernoren/PyTorch-YOLOv3 custom数据集文件 在