1. 程式人生 > 程式設計 >Pytorch實驗常用程式碼段彙總

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 codetxtfile = 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 itdef 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,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,)) #,indicate tuple unpackage
prec1,prec5 = accuracy(logits,5))

4. 善於利用 logger 檔案來記錄每一個 epoch 的實驗值

#fromPytorch_MixMatch codeclass 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('\t')
        self.numbers = {}
        for _,name in enumerate(self.names):
          self.numbers[name] = []

        for numbers in self.file:
          numbers = numbers.rstrip().split('\t')
          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('\t')
      self.numbers[name] = []
    self.file.write('\n')
    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('\t')
      self.numbers[self.names[index]].append(num)
    self.file.write('\n')
    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()

5. 利用 argparser 命令列工具來進行程式碼重構,使用不同引數適配不同資料集,不同優化方式,不同setting, 避免多個高度冗餘的重複程式碼

# argparser 命令列工具有一個坑的地方是,無法設定 bool 變數, flag=FALSE,然後會解釋為 字串,仍然當做 True

發現可以使用如下命令來進行修補,來自 ICML-19-SGC github 上程式碼

parser.add_argument('--test',action='store_true',default=False,help='inductive training.')

當命令列出現 test 字樣時,則為 args.test = true

若未出現 test 字樣,則為 args.test = false

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 的程式碼

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_dein2.log 2>&1 &

並且使用如下命令 | tee 命令則可以同時儲存到檔案並且寫到命令列輸出:

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

9. git clone 可以用來下載 github 上的程式碼,更快。(由 DIEN 的下載)

git clonehttps://github.com/mouna99/dien.git 使用這個命令可以下載 github 上的程式碼庫

10. (來自 DIEN ) 對於命令列引數不一定要使用 argparser 來讀取,也可以直接使用 sys.argv 讀取,不過這樣的話,就無法指定關鍵字引數,只能使用位置引數。

### run.sh ###
CUDA_VISIBLE_DEVICES=0 /usr/bin/python2.7 script/train.py train DIEN >train_dein2.log 2>&1 &
#############

if __name__ == '__main__':
  if len(sys.argv) == 4:
    SEED = int(sys.argv[3]) # 0,2,3
  else:
    SEED = 3
  tf.set_random_seed(SEED)
  numpy.random.seed(SEED)
  random.seed(SEED)
  if sys.argv[1] == 'train':
    train(model_type=sys.argv[2],seed=SEED)
  elif sys.argv[1] == 'test':
    test(model_type=sys.argv[2],seed=SEED)
  else:
    print('do nothing...')

11.程式碼的一種邏輯:time_point 是一個引數變數,可以有兩種方案來處理

一種直接在外面判斷:

#適用於輸出變數的個數不同的情況
if time_point:
  A,B,C = f1(x,y,time_point=True)
else:
  A,B = f1(x,time_point=False)
# 適用於輸出變數個數和型別相同的情況
C,D = f2(x,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)的實驗結果發現。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。