또르르's 개발 Story

[Stage 1 - 05] Remote Server에서 NNI (Auto ML) 사용하기 본문

[P Stage 1] Image Classification/프로젝트

[Stage 1 - 05] Remote Server에서 NNI (Auto ML) 사용하기

또르르21 2021. 4. 4. 18:55

 1️⃣ Goal

 

  • [BaseLine 작성] (추가 : 3/29, 기간 : 3/29 ~ 3/29)

  • [Data Processing]

    - Face Recognition (추가 : 3/29, 기간 : 3/30 ~ 3/31)


    - Cross-validation 사용 (추가 : 3/29)

    -데이터 불균형 해소 / imbalanced Sampler, Focal Loss, OverSampling, Weighted loss (추가 : 3/30, 기간 : 4/1 ~ )

    - Data Augumentation (추가 : 3/30)

    -
    Generator의 초당 Batch 처리량 측정 및 향상 (추가 : 3/30, 기간 : 3/31 ~ 3/31)

    - Cutmix 시도 (추가 : 4/1)

    - Repeated Agumentation (추가 : 4/1)

    - 
    validation data 사용 (추가 : 4/1, 기간 : 4/1 ~ 4/1)

  • [Model]

    - ResNet 152층 시도 (추가 : 3/29)

    - Efficient Net 시도 (추가 : 3/29, 기간 : 4/1 ~ 4/2)

    - YOLO 시도 (추가 : 3/31)

    - Pre-trained 모델에 Fine-tuning 하기 (추가 : 3/29, 기간 : 4/1 ~ 4/1)

    - Model의 초당 Batch 처리량 측정 및 향상 (추가 : 3/30)

    - dm_nfnet 시도 (추가 : 4/1)

  • [Training]

    - 앙상블 시도 (추가 : 3/29)

    - Hyperparameter 변경 (추가 : 3/29, 기간 : 3/29 ~)

    - Learning Schedular 사용 (추가 : 3/29, 기간 : 4/1 ~ )

    - Model의 초당 Batch 처리량 측정 (추가 : 3/30)

    - 좋은 위치에서 Checkpoint 만들기 (Adam으로 모든 minimum 찾고, SGD로 극소점 찾기) (추가 : 4/1, 새로운 baseline code)

    - Sex 분류 (1번 모델)  -> Age 분류 (2번 모델) -> Mask 분류 (3번 모델) // 모델 나누기 (추가 : 4/1)

    - Crop image (mask 분류)와 일반 image(age, sex 등 분류) 둘 다 사용 (추가 : 4/1)

    - batch size 작게 쓰면서, SGD 사용 (추가 : 4/2)

    - NNI (Auto ML) 사용 (추가 : 4/2, 기간 : 4/2 ~ 4/4)

  • [Deploy]

    - Python 모듈화 (추가 : 3/30, 새로운 baseline code)

 

 

 

2️⃣ Learning

없음.

 

3️⃣ Main Task

1) Remote Server에서 NNI 사용하기

저는 Pycharm Pro에서 제공하는 ssh를 사용해서 연결했습니다.

 

1-1) NNI linux 버전 설치

대부분의 remote server는 linux나 ubuntu 버전으로 되어있습니다.

nni 공식 docs에 잘 설명 되어있으니 아래 링크를 참조해 주세요.

 

 

Install on Linux & Mac — An open source AutoML toolkit for neural architecture search, model compression and hyper-parameter t

© Copyright 2021, Microsoft Revision aea98dd6.

nni.readthedocs.io

 

설치 방법은 다음과 같습니다.

 

Terminal에서 pip을 이용해서 nni 최신 버전을 설치합니다.

# Terminal

pip install --upgrade nni

 

다음은 git 소스코드를 clone해야 합니다.

원하는 폴더에 위치하신 후 git clone을 사용합니다.

# Terminal

git clone -b v2.1 https://github.com/Microsoft/nni.git

 

설치하신 nni 폴더로 들어가 setup.py를 실행합니다.

setup.py를 설치하면 관련 모듈들이 package로 설치되며, terminal에서 nni 관련 명령어 nnictl을 사용할 수 있습니다.

# Terminal

cd nni

pip install --upgrade pip setuptools		# setuptools를 설치해야 setup.py 실행 가능

python3 setup.py develop

 

설치가 된 후 nni 폴더는 다음과 같습니다.

 

 

 

1-2) config.yml 파일 수정하기

config.yml 파일은 Autom ML의 서버 정보나 실행시킬 .py 파일 등을 저장한 파일입니다.

 

config.yml의 예시 파일은 nni/examples 에 있습니다.

 

 

하지만 저기에는 채워져 있는 내용이 거의 없어서 저는 nni/examples/trials/mnist-tfv2/config_remote.yml 파일을 가지고 오겠습니다. 그리고 거기에 search_space.json 파일도 같이 가지고 옵니다.

 

이 파일을 현재 작업하고 있는 프로젝트로 가지고 옵니다.

 

 

config_remote.yml는 다음과 같이 구성되어 있습니다.

 

 

  1. experimentName : 실험 제목

  2. maxExecDuration : 실험 최대 수행 시간

    최대 시간이 끝나면 실험이 종료됩니다.

  3. maxTrialNum : 실험 최대 수행 횟수

    마찬가지로 최대 수행 횟수에 도달하면 실험이 종료됩니다.

  4. trainingServicePlatform : 실험 할 platform

    여기서 remote로 설정해야합니다.

  5. searchSpacePath : 실험할 변수들을 가지고 있는 파일

    search_space.json 파일을 가지고 옵니다.

  6. command : 실행할 명령

    terminal 형식으로 실행되기 때문에 python [PYTHON_NAME].py로 해주세요.

  7. gpuNum : 실행할 gpu 개수

    일단 gpu를 사용하기 떄문에 1개라고 표시해 놓긴 했는데 의미가 있는지는 잘 모르겠습니다..

  8. machineList : remote server 정보

    remote server를 사용할 것이라면 가장 중요한 정보입니다.

    (1) ip : remote server ip를 입력

    (2) port : remote server에 연결된 port 번호

    (3) username : 대부분 root (따로 지정한 username이 있다면 username 작성)

    (4) sshKeyPath : ssh key를 사용하고 있다면 ssh key가 저장되어 있는 path를 적습니다.

    저는 key를 server에 upload하고 server의 path를 적었습니다. (local path도 아마? 될꺼에요..)

    만약 password를 사용하고 있다면 sshKeyPath 대신 passwd를 적어줍니다.

    (5) PythonPath : python interpreter가 있는 path 

 

config_remote.yml 파일입니다.

# config_remote.yml

authorName: default
experimentName: example_mnist_pytorch
trialConcurrency: 1
maxExecDuration: 12h
maxTrialNum: 50
#choice: local, remote, pai
trainingServicePlatform: remote
searchSpacePath: search_space.json
#choice: true, false
useAnnotation: false
tuner:
  #choice: TPE, Random, Anneal, Evolution, BatchTuner, MetisTuner
  #SMAC (SMAC should be installed through nnictl)
  builtinTunerName: TPE
  classArgs:
    #choice: maximize, minimize
    optimize_mode: maximize
trial:
  command: python3 train.py
  codeDir: .
  gpuNum: 1
#machineList can be empty if the platform is local
machineList:
  - ip: 101.101.216.**
    port : 2222
    username: root
    sshKeyPath: /opt/ml/key
    # Below are examples of specifying python environment.
    # pythonPath: /opt/python3.7/bin
    # pythonPath: C:/Python37
    # Below is an example of specifying python environment for windows anaconda user. Multiple paths separated by ';'. 
    # pythonPath: C:/Users/yourname/.conda/envs/myenv;C:/Users/yourname/.conda/envs/myenv/Scripts;C:/Users/yourname/.conda/envs/myenv/Library/bin
    pythonPath: /opt/conda/bin/python

 

혹시라도 다른 config들을 알고 싶다면 아래 링크 참조해주세요.

 

nni.readthedocs.io/en/latest/Tutorial/ExperimentConfig.html

 

Experiment Config Reference — An open source AutoML toolkit for neural architecture search, model compression and hyper-parame

tuner Required. Specifies the tuner algorithm in the experiment, there are two kinds of ways to set tuner. One way is to use tuner provided by NNI sdk (built-in tuners), in which case you need to set builtinTunerName and classArgs. Another way is to use us

nni.readthedocs.io

 

1-3) nnictl 사용하기

yml 파일이 설정되었다면 config_remote.yml을 가지고 nni를 실행합니다.

nni는 remote terminal에서 아래 명령어로 실행합니다.

 

---> 주의점 : --config는 절대경로로 실행해주는 것이 편합니다. 상대경로로 작성하면 현 위치마다 다르게 작성해야해서요.

---> 주의점2 : port는 남아있는 port로 지정해주면 됩니다. nni docs에는 port 번호 1000이상을 지향한다고 합니다.

---> 주의점3 : 만약 이미 nnictl을 실행 중이시면 "nnictl stop" 명령어 후에 다시 실행해주세요.

# Terminal

nnictl create --config /opt/ml/code/baseline/config_remote.yml --port 5000
INFO: Starting restful server...
INFO: Successfully started Restful server!
INFO: Setting local config...
INFO: Successfully set local config!
INFO: Starting experiment...
INFO: Successfully started experiment!
-----------------------------------------------------------------------
The experiment id is egchD4qy
The Web UI urls are: http://192.172.0.1:5000   http://127.0.0.1:5000
-----------------------------------------------------------------------

You can use these commands to get more information about the experiment
-----------------------------------------------------------------------
         commands                       description
1. nnictl experiment show        show the information of experiments
2. nnictl trial ls               list all of trial jobs
3. nnictl top                    monitor the status of running experiments
4. nnictl log stderr             show stderr log content
5. nnictl log stdout             show stdout log content
6. nnictl stop                   stop an experiment
7. nnictl trial kill             kill a trial job by id
8. nnictl --help                 get help information about nnictl
-----------------------------------------------------------------------

 

1-4) Web UI 사용하기

개인적으로 제일 애먹은 부분입니다. 몇 시간동안 뻘짓을 해가지고...

remote server에서 돌리는 것이기 때문에 저희는 localhost를 통해 접속할 수 없습니다.

따라서 remote server에서 localhost:port를 monitoring 할 수 있는 새로운 모듈(소프트웨어)이 필요합니다.

 

제가 시도해 본 방법들입니다.

 

  • 127.0.0.1:5000 chrome으로 접속하기

    이건 당연히 안되는 거라서...

  • remote server에서 terminal을 통해 google-chrome 열기

    remote server에 연결된 terminal로 브라우저를 실행하는 방법을 사용했는데 server 자체가 graphic interface가 아니라서 열 수 없는 것 같습니다...

  • config_remote.yml에 "nniManagerIp" line 추가하기

    config_remote.yml에 nniMagerIp : [YOUR_SERVERIP] 형태로 작성하면 terminal의 Web UI urls에 해당 IP와 port가 나타나게 됩니다.

    근데 방화벽 때문에 접속이 안됩니다. (아시는 분은 댓글 부탁드립니다...)

 

https://nni.readthedocs.io/en/latest/Tutorial/ExperimentConfig.html

 

 

 

  • frp 사용하기 (test 안해봄)

    위 nni docs에 있는 내용 중 다른 방법 중 하나입니다.

    하지만 frp는 아직 개발 중이라는 소문이 있어서... ngrok을 사용했습니다.

    ref) github.com/fatedier/frp

 

ngrok 사용하기 방법은 아래와 같습니다.

 

ngrok linux 버전을 wget으로 다운로드 받습니다.

# terminal

wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip # download ngrok and unzip it

 

다운받은 ngrok을 unzip합니다.

# terminal

unzip ngrok-stable-linux-amd64.zip

 

여기 에서 ngrok 계정을 등록한 다음 authtoken을 사용하여 계정에 연결하세요.

# terminal

./ngrok authtoken <your-authtoken>

 

이제 ngrok으로 nnictl을 열었던 port를 ngrok으로 엽니다.

(여기서 "./ngrok"은 상대경로이므로 해당 위치 경로에 주의해주세요.)

# terminal

./ngrok http 5000

 

할당받은 http://683583925dbd.ngrok.io (할당 받은 경로는 모두 다름)을 chrome에서 여시면 다음과 같은 화면을 보실 수 있습니다.

 

 

 

1-5) baseline code 수정

baseline code를 nni에서 사용하시려면 약간의 수정이 필요합니다.

* 주의 : baseline code는 parser로 수행되는 python code 입니다.

 

nni.get_next_parameter()를 사용해서 search_space.json에서 choice한 parameter를 받아옵니다.

vars는 NAMESPACE로 구성된 params (객체 형태)를 dictionary 형태로 변경해줍니다.

따라서 args를 사용할 때 args.NAME으로 사용할 수 있었던 것들을 모두 args['NAME']으로 변경해야합니다.

# train.py

if __name__ == '__main__':

    try:
    
        parser = argparse.ArgumentParser()
       

        tuner_params = nni.get_next_parameter()		# nni로 다음 next parameter를 받아옴
        
        args = vars(merge_parameter(get_params(parser), tuner_params))		# parser로 받은 값과 설정된 tuner parameter와 비교
        

        data_dir = args['data_dir']
        
        model_dir = args['model_dir']
        

        train(data_dir, model_dir, args)
        

    except Exception as exception:
    

        raise

 

train.py의 get_params 함수 부분입니다.

get_params 함수에서는 parser를 사용해 model의 argument param들을 받아온 후 default params와 비교해서 사용합니다.

# train.py

def get_params(parser):

    # Data and model checkpoints directories
    
    parser.add_argument('--seed', type=int, default=42, help='random seed (default: 42)')
    
    parser.add_argument('--epochs', type=int, default=25, help='number of epochs to train (default: 30)')
   
    parser.add_argument('--dataset', type=str, default='MaskSplitByProfileDataset',
    
                        help='dataset augmentation type (default: MaskBaseDataset)')
   
   
    # ... 생략 ...
   
   
   
    # Container environment
    
    parser.add_argument('--data_dir', type=str,
    
                        default=os.environ.get('SM_CHANNEL_TRAIN', '/opt/ml/input/data/train/crop_images'))
                        
    parser.add_argument('--model_dir', type=str, default=os.environ.get('SM_MODEL_DIR', '/opt/ml/code/baseline/model'))
    

    args, _ = parser.parse_known_args() # parse_args와 유사하게 동작하지만 여분의 인자에 대해 error를 발생시키지 않음


    return args

 

train.py의 실제 train 부분입니다.

저는 validation set에서 nni.report를 사용해서 nni의 결과를 표시할 수 있게 만들었습니다.

nni.report_intermediate_result()를 통해 중간 report를 nni로 보내고,

nni.report_final_result()를 통해 결과 report에 nni를 보냅니다.

# train.py

for epoch in range(args['epochs']):
        
        # train loop
        
        model.train()
        
        for idx, train_batch in enumerate(train_loader):
        
            # ... 생략 ...
            
            outs = model(inputs)
            
            preds = torch.argmax(outs, dim=-1)
            
            loss = criterion(outs, labels)

            loss.backward()
            
            optimizer.step()


        # val loop
        
        with torch.no_grad():
        
            model.eval()

            for val_batch in val_loader:
            
                # ... 생략 ...

                outs = model(inputs)
                
                
            val_loss = np.sum(val_loss_items) / len(val_loader)
            
            val_acc = np.sum(val_acc_items) / len(val_set)


            # 중간 report를 nni로 보냅니다.
            
            nni.report_intermediate_result(val_acc)
            
            best_val_loss = min(best_val_loss, val_loss)
            
            
        # 최종 report를 nni로 보냅니다.
        
        nni.report_final_result(best_val_acc)

 

즉, 정리하면 baseline code에서

  • nni.get_next_parameter() : 다음 parser 함수들을 받아옴
  • nni.report_intermediate_result() : 중간 result들을 nni로 보냅니다.
  • nni.report_final_result() : 최종 result을 nni로 보냅니다.

이렇게 3가지 함수를 baseline code에 작성하면 됩니다.

 

1-6) search_space.json으로 parameter 설정하기

search_space.json으로 원하는 hyperparameter를 설정할 수 있습니다.

# search_space.json

{
    "batch_size": {"_type":"choice", "_value": [8, 16, 32, 64, 128]},
    "lr":{"_type":"choice","_value":[0.001, 0.01, 0.1]},
    "model": {"_type" : "choice", "_value" : ["Efficientnet_b6", "Efficientnet_b0", "Efficientnet_b3"]},
    "criterion": {"_type": "choice", "_value" : ["focal", "label_smoothing", "cross_entropy", "f1"]},
    "augmentation": {"_type": "choice", "_value" : ["BaseAugmentation", "AffineAugmentation", "GaussianAugmentation"]}
}

 

이 hyperparameter들을 random으로 선택해서 nni가 train.py에 dictionary 형태로 전달하며, 전달된 hyperparameter를 기반으로 train을 시작합니다.

 

 

1-7) nni 실행

위에서와 같이 nnictl을 실행하고 ngrok을 통해 monitoring하면 다음과 같이 실행됩니다.

 

4️⃣ Sub Task

1) 모든 결과에서 FAILED가 뜨는 이유

대부분의 경우는 코드 작성이 잘못된 경우가 많습니다.

nni에 code를 올리기 전에 terminal에서 한 번씩 hyperparameter를 넣고 test하고 올려주세요.

 

baseline의 코드를 수정해 train.py에서 param을 어떻게 넣고 돌려야하는지 고민되시는 분들은 params를 dict() 형태로 넣어서 test할 수 있습니다.

 

 

 

 

5️⃣ Evaluation

 

Data processing Model Training Time Accuracy F1
3/29   -
ResNet-50
-
-
Hyperparmeter
설정
-
7h 32m 61.87% 0.52%
3/31 -
Face Recognition
-
    36m 65.05% 0.56%
4/1   -
(pre-trained)
Efficient-Net-b6
-
-
epoch : 20,
batch size : 128
-
1h 35m 73.67% 0.66%
4/1   -
(pre-trained)
Efficient-Net-b7
-
-
epoch : 20,
batch size : 100
-
1h 31m 73.25% 0.66%
4/1     -
learning scheduler : 
CosineAnnealingLR
-
epoch : 21
-
1h 41m 68.49% 0.60%
4/2 -
 imbalanced-dataset-sampler
-
  -
learning scheduler X
-
epoch : 26
-
1h 39m 72.38% 0.64%
4/2     -
loss :
Focal loss 
($\gamma$ = 2)
-
epoch : 16
-
1h 29m 74.43% 0.68%

 

* F1 Score


1) Remote Server에서 NNI 사용하기

 

 

현재 nni를 실행한 것 자체로 기쁩니다 ㅎㅎㅎ

중간 중간에 FAILED가 뜬 이유는 F1 criterion을 제대로 설정하지 않아서 뜬 이유입니다.

 

가장 높은 model들을 ensemble한 결과를 가지고 score를 높이는 것이 목표입니다.

 

 

3) 차후 목표

  • 데이터 불균형 해소 (over sampling)

  • AutoML을 사용해서 최적의 hytperparameter 찾기
Comments