• Pytorch 实验中非常有效的代码段


    1. 大幅度提升 Pytorch 的训练速度

    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    torch.backends.cudnn.benchmark = True

    但加了这一行,似乎运行结果可能会存在细微差异,由于随机数种子带来的不同。

    2. 把原有的记录文件加个后缀变为 .bak 文件,避免直接覆盖

    # from co-teaching train code
    txtfile = save_dir + "/" + model_str + "_%s.txt"%str(args.optimizer) ## good job! nowTime=datetime.datetime.now().strftime('%Y-%m-%d-%H:%M:%S') if os.path.exists(txtfile): os.system('mv %s %s' % (txtfile, txtfile+".bak-%s" % nowTime)) # bakeup 备份文件

    3. 计算 Accuracy 返回list, 调用函数时,直接提取值,而非提取list

    # from co-teaching code but MixMatch_pytorch code also has it
    def accuracy(logit, target, topk=(1,)): """Computes the precision@k for the specified values of k""" output = F.softmax(logit, dim=1) # but actually not need it maxk = max(topk) batch_size = target.size(0) _, pred = output.topk(maxk, 1, True, True) # _, pred = logit.topk(maxk, 1, True, True) pred = pred.t() correct = pred.eq(target.view(1, -1).expand_as(pred)) res = [] for k in topk: correct_k = correct[:k].view(-1).float().sum(0, keepdim=True) res.append(correct_k.mul_(100.0 / batch_size)) # it seems this is a bug, when not all batch has same size, the mean of accuracy of each batch is not the mean of accu of all dataset return res prec1, = accuracy(logit, labels, topk=(1,)) # , indicate tuple unpackage prec1, prec5 = accuracy(logits, labels, topk=(1, 5))

    4. 善于利用 logger 文件来记录每一个 epoch 的实验值

    # from Pytorch_MixMatch code
    class
    Logger(object): '''Save training process to log file with simple plot function.''' def __init__(self, fpath, title=None, resume=False): self.file = None self.resume = resume self.title = '' if title == None else title if fpath is not None: if resume: self.file = open(fpath, 'r') name = self.file.readline() self.names = name.rstrip().split(' ') self.numbers = {} for _, name in enumerate(self.names): self.numbers[name] = [] for numbers in self.file: numbers = numbers.rstrip().split(' ') for i in range(0, len(numbers)): self.numbers[self.names[i]].append(numbers[i]) self.file.close() self.file = open(fpath, 'a') else: self.file = open(fpath, 'w') def set_names(self, names): if self.resume: pass # initialize numbers as empty list self.numbers = {} self.names = names for _, name in enumerate(self.names): self.file.write(name) self.file.write(' ') self.numbers[name] = [] self.file.write(' ') self.file.flush() def append(self, numbers): assert len(self.names) == len(numbers), 'Numbers do not match names' for index, num in enumerate(numbers): self.file.write("{0:.4f}".format(num)) self.file.write(' ') self.numbers[self.names[index]].append(num) self.file.write(' ') self.file.flush() def plot(self, names=None): names = self.names if names == None else names numbers = self.numbers for _, name in enumerate(names): x = np.arange(len(numbers[name])) plt.plot(x, np.asarray(numbers[name])) plt.legend([self.title + '(' + name + ')' for name in names]) plt.grid(True) def close(self): if self.file is not None: self.file.close() # usage logger = Logger(new_folder+'/log_for_%s_WebVision1M.txt'%data_type, title=title) logger.set_names(['epoch', 'val_acc', 'val_acc_ImageNet']) for epoch in range(100): logger.append([epoch, val_acc, val_acc_ImageNet]) logger.close()

    也可以使用如下方法记录, (2020.12.15) 利用到 pandas.DataFrame:

    #来自 https://colab.research.google.com/github/facebookresearch/moco/blob/colab-notebook/colab/moco_cifar10_demo.ipynb#scrollTo=dvuxcmejkKt8
    # logging
    results = {'train_loss': [], 'test_acc@1': []}
    if not os.path.exists(args.results_dir):
        os.mkdir(args.results_dir)
    # dump args
    with open(args.results_dir + '/args.json', 'w') as fid:
        json.dump(args.__dict__, fid, indent=2)
    
    # training loop
    for epoch in range(epoch_start, args.epochs + 1):
        train_loss = train(model, train_loader, optimizer, epoch, args)
        results['train_loss'].append(train_loss)      
        test_acc_1 = test(model.encoder_q, memory_loader, test_loader, epoch, args)
        results['test_acc@1'].append(test_acc_1)
        # guixj append
        print("epoch: %d"%epoch, "test_acc_1:", test_acc_1)
    
        # save statistics
        data_frame = pd.DataFrame(data=results, index=range(epoch_start, epoch + 1))
        data_frame.to_csv(args.results_dir + '/log.csv', index_label='epoch')

    5. 利用 argparser 命令行工具来进行代码重构,使用不同参数适配不同数据集,不同优化方式,不同setting, 避免多个高度冗余的重复代码 

     # argparser 命令行工具有一个坑的地方是,无法设置 bool 变量, --flag FALSE, 然后会解释为 字符串,仍然当做 True

    但最近发现可以使用如下命令来进行修补,来自 ICML-19-SGC github 上代码

    [一个非常好的解释]

    1 parser.add_argument('--test', action='store_true', default=False, help='inductive training.')
    • 当命令行出现 test 字样时,则为 args.test = true
    • 若未出现 test 字样,则为 args.test = false
    import argparse  # from https://github.com/Diego999/pyGAT/blob/master/train.py
     
    parser = argparse.ArgumentParser()
    parser.add_argument('--no-cuda', action='store_true', default=False, help='Disables CUDA training.')
    parser.add_argument('--fastmode', action='store_true', default=False, help='Validate during training pass.')
    parser.add_argument('--sparse', action='store_true', default=False, help='GAT with sparse version or not.')
    parser.add_argument('--seed', type=int, default=72, help='Random seed.')
    parser.add_argument('--epochs', type=int, default=10000, help='Number of epochs to train.')
    parser.add_argument('--lr', type=float, default=0.005, help='Initial learning rate.')
    parser.add_argument('--weight_decay', type=float, default=5e-4, help='Weight decay (L2 loss on parameters).')
    parser.add_argument('--hidden', type=int, default=8, help='Number of hidden units.')
    parser.add_argument('--nb_heads', type=int, default=8, help='Number of head attentions.')
    parser.add_argument('--dropout', type=float, default=0.6, help='Dropout rate (1 - keep probability).')
    parser.add_argument('--alpha', type=float, default=0.2, help='Alpha for the leaky_relu.')
    parser.add_argument('--patience', type=int, default=100, help='Patience')
    
    args = parser.parse_args()
    args.cuda
    = not args.no_cuda and torch.cuda.is_available() # args.cuda 是可以临时定义的,不过需要定义在 parser.parse_args() 后面
    # 也出现在 https://colab.research.google.com/github/facebookresearch/moco/blob/colab-notebook/colab/moco_cifar10_demo.ipynb#scrollTo=dvuxcmejkKt8
    # 来自 https://github.com/PatrickHua/SimSiam/blob/main/arguments.py 的高级用法   
    vars(args)['aug_kwargs'] = { # 增加一个参数值对象,非常高级的用法 'name':args.model.name, 'image_size': args.dataset.image_size }

    6. 使用shell 变量来设置所使用的显卡, 便于利用shell 脚本进行程序的串行,从而挂起来跑。或者多开几个 screen 进行同一张卡上多个程序并行跑,充分利用显卡的内存。

        命令行中使用如下语句,或者把语句写在 shell 脚本中 # 不要忘了 export

    export CUDA_VISIBLE_DEVICES=1  #设置当前可用显卡为编号为1的显卡(从 0 开始编号),即不在 0 号上跑
    export CUDA_VISIBlE_DEVICES=0,1 # 设置当前可用显卡为 0,1 显卡,当 0 用满后,就会自动使用 1 显卡

    一般经验,即使多个程序并行跑时,即使显存完全足够,单个程序的速度也会变慢,这可能是由于还有 cpu 和内存的限制。 

    这里显存占用不是阻碍,应该主要看GPU 利用率(也就是计算单元的使用,如果达到了 99% 就说明程序过多了。)

    使用 watch nvidia-smi 来监测每个程序当前是否在正常跑。

    7. 使用 python 时间戳来保存并进行区别不同的 result 文件

    • 参照自己很早之前写的 co-training 的代码
    • 参考 MoCoV2 的代码 
      if args.results_dir == '':
          args.results_dir = './cache-' + datetime.now().strftime("%Y-%m-%d-%H-%M-%S-moco")

    8. 把训练时命令行窗口的 print 输出全部保存到一个 log 文件:(参照 DIEN 代码)

    mkdir dnn_save_path
    mkdir dnn_best_model
    CUDA_VISIBLE_DEVICES=0  
    /usr/bin/python2.7 script/train.py train DIEN >train_dien2.log 2>&1 & # 最后一个 & 似乎是让进程在后台运行,参见《Linux命令行大全》第10章

    这里的 2>&1 表示把 标准错误 导出到 标准输出 中, 与前面的 > train_dien2.log 组合起来则相当于把 python 程序的标准输出和标准错误都导入到文件 train_dien2.log 中。

    并且使用如下命令  | tee  命令则可以同时保存到文件并且写到命令行输出:

    python  script/train.py train DIEN  | tee train_dein2.log 

     PS:hanzy 最早就是用此来记录实验结果,我可能在实习之前就学会了此,应该是在之前的某个代码库里有此。跑类别不平衡代码就已经会此。

    9. git clone 可以用来下载 github 上的代码,更快。(由 DIEN 的下载)

    git clone https://github.com/mouna99/dien.git 使用这个命令可以下载 github 上的代码库

    10. (来自 DIEN ) 对于命令行参数不一定要使用 argparser 来读取,也可以直接使用 sys.argv 读取,不过这样的话,就无法指定关键字参数,只能使用位置参数。 

      

     1 ### run.sh ###
     2 CUDA_VISIBLE_DEVICES=0  /usr/bin/python2.7  script/train.py train DIEN  >train_dein2.log 2>&1 &
     3 #############
     4 
     5 if __name__ == '__main__':
     6     if len(sys.argv) == 4:
     7         SEED = int(sys.argv[3]) # 0,1,2,3
     8     else:
     9         SEED = 3 
    10     tf.set_random_seed(SEED)
    11     numpy.random.seed(SEED)
    12     random.seed(SEED)
    13     if sys.argv[1] == 'train':
    14         train(model_type=sys.argv[2], seed=SEED)
    15     elif sys.argv[1] == 'test':
    16         test(model_type=sys.argv[2], seed=SEED)
    17     else:
    18         print('do nothing...')

    参考 https://blog.csdn.net/dcrmg/article/details/51987413

    argc 是 argument count 的缩写,表示传入main函数的参数个数;

    argv 是 argument vector 的缩写,表示传入main函数的参数序列或指针,并且第一个参数argv[0]一定是程序的名称,并且包含了程序所在的完整路径,所以确切的说需要我们输入的main函数的参数个数应该是argc - 1个;

    11.代码的一种逻辑:time_point 是一个参数变量,可以有两种方案来处理

    一种直接在外面判断:

    1 #适用于输出变量的个数不同的情况
    2 if time_point:
    3   A, B, C = f1(x, y, time_point=True)
    4 else:
    5   A, B = f1(x, y, time_point=False)
    6 
    7 # 适用于输出变量个数和类型相同的情况
    8 
    9 C, D = f2(x, y, time_point=time_point)

    12. 写一个 shell 脚本文件来进行调节超参数, 来自 [NIPS-20 Grand]

    mkdir cora
    for num in $(seq 0 99) 
    do   python train_grand.py --hidden 32 --lr 0.01 --patience 200 --seed $num --dropnode_rate 0.5 > cora/"$num".txt done

    13. 使用 或者 不使用 cuda 运行结果可能会不一样,有细微差别。

         cuda 也有一个相关的随机数种子的参数,当不使用 cuda 时,这一个随机数种子没有起到作用,因此可能会得到不同的结果。

      来自 NIPS-20 Grand (2020.11.18)的实验结果发现。

     

    14. 在 ipython 交互式界面中是无法使用读取命令行参数的,需要使用一个空字符读取默认值:

    '''  # https://colab.research.google.com/github/facebookresearch/moco/blob/colab-notebook/colab/moco_cifar10_demo.ipynb#scrollTo=dvuxcmejkKt8
    args = parser.parse_args()  # running in command line, 为空也读取默认值
    '''
    args = parser.parse_args('')  # running in ipynb, 全部会使用默认值

    15. 保存模型的所有超参数:来自 MoCo_CIFAR10

    print(args)
    
    # load model if resume
    epoch_start = 1
    if args.resume is not '':
        checkpoint = torch.load(args.resume)
        model.load_state_dict(checkpoint['state_dict'])
        optimizer.load_state_dict(checkpoint['optimizer'])
        epoch_start = checkpoint['epoch'] + 1
        print('Loaded from: {}'.format(args.resume))
        
        
    # logging
    results = {'train_loss': [], 'test_acc@1': []}
    if not os.path.exists(args.results_dir):
        os.mkdir(args.results_dir)
    
    # dump args
    with open(args.results_dir + '/args.json', 'w') as fid:
        json.dump(args.__dict__, fid, indent=2)

     先前的 LDAM 的代码似乎也记录了很多,几乎全部把所有的实验设置全部都记录下来了,后面有空可以看一下。

    16. 利用 tqdm 的 bar, 似乎还有一个 progress 也有类似的功能?

    # MoCo 代码
    from
    tqdm import tqdm def train(): train_bar = tqdm(data_loader) for im_1, im_2 in train_bar: loss = net(im_1, im_2) train_optimizer.zero_grad() loss.backward() train_optimizer.step() total_num += data_loader.batch_size total_loss += loss.item() * data_loader.batch_size train_bar.set_description('Train Epoch: [{}/{}], lr: {:.6f}, Loss: {:.4f}'.format(epoch, args.epochs, optimizer.param_groups[0]['lr'], total_loss / total_num)) return total_loss / total_num

    # SimSiam 代码也用此

    17. 最近看到 SimSiam 的实现也用到 AverageMeter() 函数,所以准备记录一下。google 搜索发现 MixMatch 代码库的作者也用了此,并给出了出处,原来是参考 pytorch 官方训练 ImageNet 的代码,看来这些人都读过非常优秀的 pytorch 官方源代码库,所以能够写出这么简洁有力的代码。

    class AverageMeter(object):
        """Computes and stores the average and current value
           Imported from https://github.com/pytorch/examples/blob/master/imagenet/main.py#L247-L262
        """
        def __init__(self):
            self.reset()
    
        def reset(self):
            self.val = 0
            self.avg = 0
            self.sum = 0
            self.count = 0
    
        def update(self, val, n=1):
            self.val = val
            self.sum += val * n
            self.count += n
            self.avg = self.sum / self.count

    不过一些代码库关于AverageMeter 的用法有一些错误,当前的用法是:

    acc_meter = AverageMeter(name='Accuracy')
    acc_meter.reset()
    for images, labels, indices in testloader:
        with torch.no_grad():
            feature = model(images.to(args.device))
            preds = classifier(feature).argmax(dim=1)
            correct = (preds == labels.to(args.device)).sum().item()
            acc_meter.update(correct/preds.shape[0])  # 这里读入每个 batch 的平均后的 accuracy
    print(f'Accuracy = {acc_meter.avg*100:.2f}')

    注意到,例如设置 batch_size = 128, 但一般测试集的大小无法恰好整除 128, 就会导致最后一个 batch 样本数小于 128,这样直接计算 batch 的平均后的 accuracy, 再进行平均,算出来的值有问题。除非 test_loader 设置了 drop_last=True, 把最后一个大小不为 128 的 batch 直接丢掉,但这样不合理,相当于改变了 test_loader。一般只会在 train_loader 设置 drop_last=True。

    修复方法例如,可以改为如下代码: 

    acc_meter.update(correct, preds.shape[0])  

    The End.

  • 相关阅读:
    微前端的那些事儿
    网络是怎样连接的 作者户根勤 交流论坛
    数据结构与算法学习
    cpu读取指令时读取的长度
    小程序开发
    npm 安装 chromedriver 失败的解决办法
    Git:代码冲突常见解决方法
    Android通过Chrome Inspect调试WebView的H5 App出现空白页面的解决方法(不需要FQ)
    pm2
    多媒体技术及应用
  • 原文地址:https://www.cnblogs.com/Gelthin2017/p/12148011.html
Copyright © 2020-2023  润新知