俺人〜OREGIN〜俺、バカだから人工知能に代わりに頑張ってもらうまでのお話

俺って、おバカさんなので、とっても優秀な人工知能を作って代わりに頑張ってもらうことにしました。世界の端っこでおバカな俺が夢の達成に向けてチマチマ頑張る、そんな小さなお話です。現在はG検定に合格し、E資格取得とKaggle参画に向けて、Pythonや機械学習の勉強中です。まだまだ道半ばですが、お時間がありましたら見て行ってください。

ProbSpace浮世絵作者予測コンペ振り返り(10位にランクイン!)

先日、「ProbSpace」というプラットフォームで開催された「浮世絵作者予測」コンペに参戦したので、振り返っていきたいと思います。

私にとって、画像分類のコンペは初めてでしたが、E資格の課題が画像分類でしたので、その時に得た知識をフル動員して取り組みました。

最終順位は参加232チーム中、10位でした。トップ10入りができてとてもうれしいです!!

f:id:kanriyou_h004:20200118191959p:plain

まだまだ、改善の余地はたくさんあると思いますが、これまでの取り組みで結果を残せたので、素直に喜びたいと思います!!

全体像

上位の方の見よう見まねで全体像の図を作ってみました。

描き方とかも理解できていないので、わかりにくかったら申し訳ございません。

f:id:kanriyou_h004:20200118213140p:plain

では、振り返ってまいります。

1.データ拡張

データを読み込んで、E資格の課題のときに大活躍したデータ拡張をいろんなモデルに、それぞれの割合をハイパーパラメータとして最大になる値を探索しました。

データ拡張は、HorizontalFlip,RandomContrast,RandomBrightness,Blur,Cutoutの6種類を使いました。

また、モデルはResNet18,34,101,WideResNet50,DenseNet121,VGG11,13,16の8種類を使いました。

以下は、ResNet18でのデータ拡張の割合を探索する例です。 

import optuna

def objective(trial):
    model = models.resnet18(num_classes=10)
    model.cuda()

    #最適化するパラメータの設定
    lr_par = trial.suggest_loguniform('learning_rate'1e-51e-2)
    HoriFlip_p = trial.suggest_uniform('H_Flip'0.00.5)
    RandCont_p = trial.suggest_uniform('R_Cont'0.00.5)
    RandBrig_p = trial.suggest_uniform('R_Brig'0.00.5)
    Blur_p = trial.suggest_uniform('Bluer'0.00.5)
    Cut_p = trial.suggest_uniform('Cut_out'0.00.5)
    optim_flg = trial.suggest_categorical('optimizer', ['SGD''Adam'])
    
    #データを訓練用とテスト用に分ける
    train_x, valid_x, train_y, valid_y = train_test_split(train_images, train_labels, test_size=0.2, random_state=RANDOM_SEED)
  
    #データをローダーに格納する。
    train_loader = dataset.DataLoader(
      Dataset(train_x, train_y, HoriFlip_p, RandCont_p, RandBrig_p, Blur_p,Cut_p, train=True), batch_size=64, shuffle=True)
    valid_loader = dataset.DataLoader(
      Dataset(valid_x, valid_y, HoriFlip_p, RandCont_p, RandBrig_p, Blur_p, Cut_p,train=False), batch_size=64, shuffle=False)

    if optim_flg == 'Adam':
      optimizer = optim.Adam(model.parameters(), lr=lr_par)
    else:
      optimizer = optim.SGD(model.parameters(), lr=lr_par, weight_decay=0.01, momentum=0.9)

    log = []

    for epoch in range(20):
      model.train()

      with autograd.detect_anomaly():
        train_loss, train_accuracy = perform(model, train_loader, optimizer)

      model.eval()

      with torch.no_grad():
        valid_loss, valid_accuracy = perform(model, valid_loader, None)

      print('[{}] train(loss/accuracy)={:.2f}/{:.2f}, valid(loss/accuracy)={:.2f}/{:.2f}'.format(epoch + 1, train_loss, train_accuracy, valid_loss, valid_accuracy))

      log.append( (epoch + 1, train_loss, train_accuracy, valid_loss, valid_accuracy) )

    return valid_loss

#最適化の実行
study = optuna.create_study()
study.optimize(objective, n_trials=500)
 

 

2.1層目の学習

先ほど探索した割合のデータ拡張を施した入力を使って1層目の学習を実施します。

1層目の学習はStratifiedKFoldを使って、5Foldのクロスバリデーションを行いました。

今回は、各Fold毎に最も精度が良くなるモデルを保存するように工夫してみました。

def Exe_oof(model,origin_statetrain_images,train_labels,test_imagesoptim_flg,epoch_num,str_name,num_acc,num_fold):
  cv = StratifiedKFold(n_splits=Nsplits, shuffle=True, random_state=42)
  oof_pred = np.zeros( (len(train_images),10 ) )
  y_pred_max = np.zeros( (len(test_images),10 ) )
  str_model = str_name +'_0_0000'
  y_pred_flg=0 

  for fold, (train_idx, val_idx) in enumerate(cv.split(train_images, train_labels)):
    print(fold)
    print(num_fold)
    if fold==num_fold:
      print('True')
      max_acc = num_acc
      model.load_state_dict(origin_state)
      #データを訓練用と検証用に分ける
      train_x, valid_x = train_images[train_idx], train_images[val_idx]
      train_y, valid_y = train_labels[train_idx], train_labels[val_idx]
    
      #データをローダーに格納する。
      train_loader = dataset.DataLoader(
          Dataset(train_x, train_y, HoriFlip_p, RandCont_p, RandBrig_p, Blur_p,Cut_p, train=True), batch_size=64, shuffle=True)
      valid_loader = dataset.DataLoader(
          Dataset(valid_x, valid_y, HoriFlip_p, RandCont_p, RandBrig_p, Blur_p, Cut_p,train=False), batch_size=64, shuffle=False)

      if optim_flg == 'Adam':
        optimizer = optim.Adam(model.parameters(), lr=lr_par)
      else:
        optimizer = optim.SGD(model.parameters(), lr=lr_par, weight_decay=0.01, momentum=0.9)

      log = []
      import datetime

      print(datetime.datetime.now())

      for epoch in range(epoch_num):

        model.train()
        with autograd.detect_anomaly():
          train_loss, train_accuracy = perform(model, train_loader, optimizer)
  
        model.eval()
        with torch.no_grad():
          valid_loss, valid_accuracy = perform(model, valid_loader, None)

        print('[{}] train(loss/accuracy)={:.2f}/{:.2f}, valid(loss/accuracy)={:.2f}/{:.2f}'.format(epoch + 1, train_loss, train_accuracy, valid_loss, valid_accuracy))

        if valid_accuracy > max_acc:
          int_model = int(valid_accuracy*10000)
          str_model = str_name +'_'str(fold) +'_'str(int_model)
          DATA_DIR = 'drive/My Drive/ProbSpace/Ukiyoe/'+ str_name + '/' + str_model
          torch.save(model.state_dict(), DATA_DIR)
          #################################
          # 検証用データの予測
          #################################
          model.eval 
          hogehoge_i = 1
          for images, _ in valid_loader:
            images = images.cuda()
            with torch.no_grad():
              if hogehoge_i>=1 :
                preds = model(images)
                hogehoge_i=0
              else:
                preds = torch.cat([preds,model(images)],dim=0)
          dvs=torch.device('cpu'
          preds_cpu = preds.to(dvs)
          preds_np = preds_cpu.clone().numpy()
          oof_pred[val_idx] = preds_np

          #################################
          # テスト用データの予測
          #################################
          dummy_labels = np.zeros( (test_images.shape[0], 1), dtype=np.int64)
          test_loader = dataset.DataLoader(Dataset(test_images, dummy_labels,0.,0.,0.,0.,0.), batch_size=64, shuffle=False)
          model.eval()
          hogehoge_i = 1
          for images, _ in test_loader:
            images = images.cuda()
            with torch.no_grad():
              if hogehoge_i>=1 :
                preds = model(images)
                hogehoge_i=0
              else:
                preds = torch.cat([preds,model(images)],dim=0)
          y_pred_max = preds
          #################################
          max_acc = valid_accuracy

        print(datetime.datetime.now())

        log.append( (epoch + 1, train_loss, train_accuracy, valid_loss, valid_accuracy) )

      if y_pred_flg==0 :
        y_pred = y_pred_max
        y_pred_flg=1
      else:
        y_pred = torch.cat([y_pred,y_pred_max],dim=1)     
      print('Partial score of fold {} is: {}'.format(fold, max_acc))
   

 

3.1層目の出力(2層目の入力を作成)

先ほど学習させた精度の良い各モデルの出力を2層目への入力として作成しました。

def make_input_for_layer2(model,train_images,train_labels,test_images,str_name,num_fold):
  cv = StratifiedKFold(n_splits=Nsplits, shuffle=True, random_state=42)
  for fold, (train_idx, val_idx) in enumerate(cv.split(train_images, train_labels)):
    print(fold)
    if fold==num_fold:
      print(True)
      #データを訓練用と検証用に分ける
      train_x, valid_x = train_images[train_idx], train_images[val_idx]
      train_y, valid_y = train_labels[train_idx], train_labels[val_idx]
    
      #データをローダーに格納する。
      valid_loader = dataset.DataLoader(
          Dataset(valid_x, valid_y, 0.00.00.00.00.0,train=False), batch_size=64, shuffle=False)

      import datetime
      print(datetime.datetime.now())

      if True:
          str_model = str_name +'_'str(fold)
          DATA_DIR = 'drive/My Drive/ProbSpace/Ukiyoe/'+ str_name + '/' + str_model
          #################################
          # 検証用データの予測
          #################################
          model.eval 
          hogehoge_i = 1
          for images, _ in valid_loader:
            images = images.cuda()
            with torch.no_grad():
              if hogehoge_i>=1 :
                preds = model(images)
                hogehoge_i=0
              else:
                preds = torch.cat([preds,model(images)],dim=0)
          dvs=torch.device('cpu'
          preds_cpu = preds.to(dvs)
          preds_np = preds_cpu.clone().numpy()
          print(preds_np.shape)
          #################################
          # テスト用データの予測
          #################################
          dummy_labels = np.zeros( (test_images.shape[0], 1), dtype=np.int64)
          test_loader = dataset.DataLoader(Dataset(test_images, dummy_labels,0.,0.,0.,0.,0.), batch_size=64, shuffle=False)
          model.eval()
          hogehoge_i = 1
          for images, _ in test_loader:
            images = images.cuda()
            with torch.no_grad():
              if hogehoge_i>=1 :
                preds = model(images)
                hogehoge_i=0
              else:
                preds = torch.cat([preds,model(images)],dim=0)
          preds_sub  = torch.max(preds, dim=1)[1]
          preds_cpu = preds.to(dvs)
          preds_y = preds_cpu.clone().numpy()
          print(preds_y.shape)
          print(preds.shape)
          #################################
          
          print(datetime.datetime.now())

          with open('drive/My Drive/ProbSpace/Ukiyoe/'+ str_name + '/'+ str_model + '_preds_np.pkl','wb'as f1:
            pickle.dump(preds_np,f1)
          with open('drive/My Drive/ProbSpace/Ukiyoe/'+ str_name + '/'+ str_model + '_val_idx.pkl','wb'as f2:
            pickle.dump(val_idx,f2)
          with open('drive/My Drive/ProbSpace/Ukiyoe/'+ str_name + '/'+ str_model + '_pred_y_torch.pkl','wb'as f3:
            pickle.dump(preds,f3)
          with open('drive/My Drive/ProbSpace/Ukiyoe/'+ str_name + '/'+ str_model + '_pred_y.pkl','wb'as f4:
            pickle.dump(preds_y,f4)
          with open('drive/My Drive/ProbSpace/Ukiyoe/'+ str_name + '/'+ str_model + '_pred_sub.pkl','wb'as f5:
            pickle.dump(preds_sub,f5)
          with open('drive/My Drive/ProbSpace/Ukiyoe/Layer1/_'+ str_model + '_valid_y.pkl','wb'as f6:

            pickle.dump(valid_y,f6)

4.1層目の出力に係数をかけてLightGBMに入力

2層目は、LightGBMを使ってみました。

入力は、1層目の出力に対して、0.0~1.0の係数をかけて、精度が最大となる係数をOptunaにて探索しました。

5.上記までのモデルを3パターン作って平均をとる

最終的には、上記までのモデルを3パターン作って、平均したものを提出しました。

結果

 最終結果としては、提出回数98回、ベストスコア:0.899、順位:10位でフィニッシュできました。

f:id:kanriyou_h004:20200118231350p:plain
E資格の課題で、画像分類を実施していたので、その時の経験をフルに活用でき、ベスト10に入れたので、非常に満足のいく結果でした。

ただ、逆にいうとE資格の課題に取り組んだ際に学んだ知識しかなかったので、上位の方々の解法を参考にして、もっと精進していきたいと思います。

今後もいろいろなコンペに参加して、技術を磨いていきたいと思います。

 

[商品価格に関しましては、リンクが作成された時点と現時点で情報が変更されている場合がございます。]

Kaggleで勝つデータ分析の技術 [ 門脇大輔 ]
価格:3608円(税込、送料無料) (2019/12/30時点)

 【参考記事】E資格、G検定 合格記

2019年8月31日(土)にE資格を受験して、合格しました!

E資格対策として勉強の進め方や、参考書などをまとめました。

これから受験される方がいらっしゃいましたらご参考まで。

oregin-ai.hatenablog.com 

 

 2019年3月9日(土)にG検定を受験し、見事合格できました!

受験の体験記や勉強法などを別のブログにまとめました。

これから受験される方がいらっしゃいましたらご参考まで。

g-kentei.hatenablog.com

 【E資格対策に使った参考書】