Skip to main content
Version: 1.3

fairing 으로 학습시키기

시작하면서#

이전 장에서 Fashion mnist 데이터셋을 가지고 NN(Neural Network) 모델을 만들어 보았습니다.
이번에는 자신의 노트북이 가지고 있는 자원만이 아닌, CAP의 자원을 활용하여 학습을 진행하는 방법(fairing)을 소개하겠습니다.

info

이번 과정은 개발을 위한 준비 챕터에서 만든 handson 노트북서버에서 진행됩니다.
Fashion mnist로 모델 개발하기에서 만든 모델을 함수화 하여 예제에 사용할 것입니다.
사용되는 코드는 아래 링크에서 보실 수 있습니다.
02.fairing-dockerhub.ipynb
02.fairing-harbor.ipynb
03.fairing_fashion_mnist.ipynb
04.fairing_outputmap.ipynb


Goal#

  • fairing으로 모델을 함수화하고 CAP의 자원으로 학습시키기

사전작업 - harbor 프로젝트 생성하기 (선택사항)#

선택사항

CAP 에 있는 harbor를 이미지 저장소(private registry)로 이용하고자 하는경우 이 과정을 진행합니다.
dockerhub 계정으로 이미지 저장소(레지스트리)를 사용해도 무방하기에 번거롭다면 건너뛰어도 됩니다.

  • CAP의 Registry 메뉴로 들어가서 로그인을 진행하여 {아이디}-handson이라는 프로젝트를 생성합니다.
  • Access Level은 public으로 설정합니다.

Access Level 을 public으로 설정하게 되면 컨테이너 이미지의 이름과 태그를 아는 사람은 누구든지 해당 저장소에서 이미지를 다운받을 수 있습니다.

create-registry-project.png

  • 생성이 완료되면 Projects 메뉴에서 생성된 프로젝트를 확인할 수 있습니다.

list-registry-project.png


fairing을 사용하기 위한 준비#

  • fairing은 사용자가 작성한 노트북의 내용을 이미지로 빌드하여 Kubernetes Job으로 실행시킵니다.
  • 컨테이너 이미지 빌드에는 여러 방법이 있지만, CAP은 노트북에서 이미지 빌드를 진행하는 Append Builder
    클러스터에서 빌드를 진행하는 Cluster Builder 2가지를 제공합니다.
  • 이미지 빌드가 완료되면 이미지를 저장소(Registry)에 업로드(push)하여 클러스터에서 사용할 수 있게 해야 합니다.
    그러기 위해서 CAP은 앞서 설명한 Private Registry (harbor)를 제공합니다.
  • 물론 도커허브와 같은 이미지 저장소(Registry)도 사용가능하니, 튜토리얼에서는 편한 Registry를 사용하시면 됩니다.
  • 이미지 저장소에 이미지를 업로드(push) 하기 위해서는 로그인 과정이 필요한데, 이는 설정파일로 정의가 가능합니다.
fairing_job?

fairing을 사용하여 생성되는 컨테이너는 쿠버네티스의 Job 이란 오브젝트로 생성이 됩니다.
이에 fairing 의 요청에 의해 생성되는 컨테이너를 fairing job 이라고 부르기도 합니다.


Registry 환경설정하기#

노트북 Launcher에서 Terminal을 열고 아래의 내용을 붙여넣고 본인의 환경에 맡게 수정하여 실행합니다.

notebook_terminal_dockerhub
# docker hub를 사용하는 경우export DOCKER_REGISTRY=https://index.docker.io/v1/export REGISTRY_CONFIG=$(echo -n '도커허브 아이디:도커허브 비밀번호' | base64)mkdir -p /home/jovyan/.docker/cat << EOF > /home/jovyan/.docker/config.json{        "auths": {                "$DOCKER_REGISTRY": {                        "auth": "$REGISTRY_CONFIG"                }        }}EOF
# Create configmap for dockerconfigkubectl  create configmap docker-config --from-file=/home/jovyan/.docker/config.json# create imagePullSecrets for sakubectl create secret generic regcred --from-file=.dockerconfigjson=/home/jovyan/.docker/config.json --type=kubernetes.io/dockerconfigjsonkubectl patch serviceaccount default -p '{"imagePullSecrets": [{"name": "regcred"}]}'kubectl patch serviceaccount default-editor -p '{"imagePullSecrets": [{"name": "regcred"}]}'

04.hub_setting

정상적으로 실행이 되었다면, 노트북에서 이미지 저장소를 사용할 준비가 되었습니다.



간단한 fairing job 실행해보기#

간단한 fairing job을 만들면서 fairing에 대한 개념을 익혀보겠습니다.

  • 먼저 fairing.ipynb 이라는 이름의 노트북을 하나 생성합니다.
    그리고 아래와 같이 호스트네임을 출력해주는 show_hostname()이라는 함수를 작성합니다.
    여기서 출력값 handson-0은 우리가 생성한 노트북의 이름이기도 합니다. 😁
fairing.ipynb
# fairing.ipynbimport osdef show_hostname():    print(os.environ['HOSTNAME'])show_hostname()

04.show_hostname

show_hostname() 이라는 함수를 fairing 을 이용하여 노트북에서 실행하고 CAP의 자원을 이용해서 생성한 컨테이너에서도 실행해보겠습니다. 셀을 하나 더 추가해서 아래의 코드를 붙어 넣고 실행해봅시다.

fairing_dockerhub.ipynb
# fairing_dockerhub.ipynbimport osfrom kubeflow import fairing
def show_hostname():    print(os.environ['HOSTNAME'])
DOCKER_REGISTRY = 'YOURID' # 도커허브 아이디fairing.config.set_builder(    'append',    image_name='fairing-job',    registry=DOCKER_REGISTRY,    base_image='dudaji/cap-jupyterlab:tf2.0-cpu',    push=True)
fairing.config.set_deployer('job',                            cleanup=True) # job 실행 완료 후 삭제할지 여부를 결정
if __name__ == '__main__':    print('local show_hostname()')    show_hostname()    print('remote show_hostname()')    remote_show_hostname = fairing.config.fn(show_hostname)    remote_show_hostname()

04.simple_fairing_01 04.simple_fairing_02

실행이 되면,

  1. 노트북에서 show_hostname()이 실행되면서 handson-0이란 값을 출력하고,
  2. show_hostname() 함수가 fairing에 감싸져서 remote_show_hostname() 으로 바뀌어 실행되면서
    베이스 이미지가 dudaji/cap-jupyterlab:tf2.0-cpu이면서 [도커허브 아이디 또는 harbor이미지주소]/fairing-job:[랜덤값] 라는 이미지를 생성합니다.

    remote_show_hostname = fairing.config.fn(show_hostname)

  3. 그리고 그 이미지를 도커허브에 저장(푸쉬)한 후에
  4. CAP에서 fairing job을 실행하도록 요청합니다.
  5. fairing job이 실행이 되면 저장된 이미지를 다운로드 한 후
  6. show_hostname()를 실행하여 fairing-job-9hp29-48tg4 라는 값을 출력하는 것을 확인할 수 있습니다.
    즉, fairing-job-9hp29-48tg4은 fairing job 컨테이너의 호스트 네임입니다.

    fairing-job-[컨테이너 호스트 네임](랜덤한 값)

중요

위 예제 show_hostname 함수를 이용하여 알 수 있는 것은, 노트북 내부에서 작성한 함수를 fairing으로 감싸주면 CAP의 자원을 사용하여 어딘가에 별도로 생성된 컨테이너(job)가 이 함수를 실행시켜 준다는 것입니다.


fairing work flow#

위에서 show_hostname()이라는 함수를 fairing을 이용해 클러스터에서 실행해보았습니다.
여기서 fairing이 패키지화(함수화) - 컨테이너 이미지 빌드 - 빌드된 이미지 배포(job container 생성)로 이어지는 3단계의 프로세스로 이루어 진다는 것을 알 수 있습니다. fairing은 이 프로세스를 아래와 같이 정의합니다.

  • preprocessor: python 파일/ 노트북/ 단일 함수를 컨테이너 이미지에 넣을 수 있도록 패키지화
  • builder: 패키지된 파일을 도커 이미지로 빌드
  • deployer: 빌드된 이미지를 쿠버네티스 클러스터에 배포

위에서 사용했던 코드는 이 3가지 과정을 fairing.config라는 하나의 클래스로 묶어서 사용하고 있습니다.
물론 이 과정을 별도로 실행할 수 있는 클래스들도 제공하고 있습니다.


fairing으로 fashion mnist 모델을 감싸기(함수화하기)#

간단한 예제를 통해 fairing이 뭔지 알아보았으니 이제 우리가 만든 모델을 fairing으로 실행시켜봅시다.
그러기 위해서는 일단 우리의 모델을 함수화 하여야 합니다.

  • 먼저 fairing_fashion_mnist.ipynb라는 노트북을 하나 생성하고 아까 만든 모델을 train()이라는 함수안에 넣어보겠습니다.
fairing_fashion_mnist.ipynb
# fairing_fashion_mnist.ipynbfrom kubeflow import fairing
def train():    import datetime, os    import tensorflow as tf
    mnist = tf.keras.datasets.fashion_mnist    (x_train, y_train), (x_test, y_test) = mnist.load_data()
    print("x_train shape:", x_train.shape, "y_train shape:", y_train.shape)    print("x_test shape:", x_test.shape, "y_test shape:", y_test.shape)
    x_train, x_test = x_train / 255.0, x_test / 255.0
    model = tf.keras.models.Sequential([        tf.keras.layers.Flatten(input_shape=(28, 28)),        tf.keras.layers.Dense(128, activation='relu'),        tf.keras.layers.Dropout(0.2),        tf.keras.layers.Dense(10, activation='softmax')    ])
    model.compile(optimizer='sgd',                    loss='sparse_categorical_crossentropy',                    metrics=['acc'])

    log_dir = "log/fit/" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S")    print(f"tensorboard log dir : {log_dir}")    tensorboard_cb = tf.keras.callbacks.TensorBoard(log_dir=log_dir,                                                    histogram_freq=1)    model.fit(x_train, y_train,                verbose=1,                validation_data=(x_test, y_test),                epochs=10,                callbacks=[tensorboard_cb])
  • 이제 fairing 으로 train 함수를 감싸주겠습니다.
fairing.config.set_builder(    'append',    image_name='fairing-job',    registry=DOCKER_REGISTRY,    base_image='dudaji/cap-jupyterlab:tf2.0-cpu',    push=True)
fairing.config.set_deployer('job',                            cleanup=True) # 잡을 실행후 완료시 잡을 삭제할지의 여부를 결정
if __name__ == '__main__':    print('remote train()')    remote_train = fairing.config.fn(train) # 여기서 fairing으로 함수화된 train을 감싸줍니다.    remote_train()


fairing으로 실행한 학습의 텐서보드(tensorboard) 보기#

fairing으로 실행된 학습은 이전에 저희가 만든 handson 노트북과 볼륨을 공유하지 않습니다.
따라서 fairing이 실행한 학습의 tensorboard를 보기 위해선 로그 경로를 바꾸어주어야 합니다.

  • fairing은 컨테이너를 만들때 /app 이라는 경로에 패키징된 파일(함수화된 파일)들을 풀어 놓고 실행을 시킵니다.
  • 따라서 학습이 진행되면 로그는 /app/log/fit 에 저장이 됩니다.
  • 하지만 handson 노트북의 볼륨을 /app 으로 마운트를 하게 되면 fairing이 패키징한 /app 경로에 overwrite 되기 때문에 경로를 변경해야 합니다. 여기선 /notebook/log/fit/로 정하겠습니다.
change_log_dir
# fairing_fashion_mnist.ipynbdef train():  ...  log_dir = "log/fit/" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S")  ...--- 수정 후 ---def train():  ...  log_dir = "/notebook/log/fit/" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S")  ...

노트북에서도 함수를 실행하고, fairing으로도 함수를 실행시켜 텐서보드를 보려면?
  • 노트북환경과 fairing으로 함수를 모두 실행 시켜 두개의 텐서보드를 보고 싶다면 각각이 로그를 저장하는 경로가 달라 별도의 분기가 필요합니다.
  • fairing에서는 fairing으로 빌드된 컨테이너에 환경변수로 FAIRING_RUNTIME란 값을 넣어줍니다.
    아래의 코드는 간단하게 분기를 적용한 예입니다. :::
if_else_log_dir_settings
# fairing_fashion_mnist.ipynbdef train():  ...  date_folder = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")  if os.getenv('FAIRING_RUNTIME', None) is None:      # 노트북에서 실행하는 train() -> log/fit 경로      log_dir = "log/fit/" + date_folder  else:      # fairing으로 실행하는 remote_train()  -> /notebook/log/fit 경로      log_dir = "/notebook/log/fit/" + date_folder  ...

이제 노트북 볼륨을 fairing 으로 실행된 컨테이너에 마운트 해보겠습니다. mounting_pvc 패키지를 사용하여,
fairing deployer에서 pod_spec_mutators를 통해 볼륨 마운트가 진행됩니다.

여기서 볼륨을 마운트 한다는 것은, handson 노트북 저장소(workspace-handson)에 있는 경로(log/fit)를 fairing에 의해 생성되는 새로운 컨테이너에 /notebook 이라는 디렉토리를 만들어 그 안에 그대로 복사 및 공유 한다는 것을 의미합니다.

  • 실제로 물리적으로 복사하는 것은 아니고 참조하는 형태입니다.
    :::
mounting_pvc
# fairing_fashion_mnist.ipynbfrom kubeflow.fairing.kubernetes.utils import mounting_pvc # mounting_pvc 추가
fairing.config.set_builder(...)
notebook_volume = mounting_pvc(pvc_name="workspace-handson",                                pvc_mount_path="/notebook") #마운트 경로 /notebookfairing.config.set_deployer('job',                            pod_spec_mutators=[notebook_volume], #마운트 할 볼륨                            cleanup=True) # 잡을 실행후 완료시 잡을 삭제할지의 여부를 결정

pvc_mount_path="/notebook"로 설정을 하게 되면 fairing 에 의해 함수된 모델 remote_train() 이 실행되고 /notebook/log/fit 경로에 로그가 저장되어 텐서보드를 볼 수 있게 됩니다.

아래는 전체 코드입니다. 편의상 노트북에서 실행되는 train() 은 주석처리 하였습니다.
분기 설정이 되어있지만 실제로는 remote_train() 만 실행합니다.

# fairing_fashion_mnist.ipynb 전체 코드from kubeflow import fairingfrom kubeflow.fairing.kubernetes.utils import mounting_pvc
def train():    import datetime, os    import tensorflow as tf
    mnist = tf.keras.datasets.fashion_mnist    (x_train, y_train), (x_test, y_test) = mnist.load_data()
    print("x_train shape:", x_train.shape, "y_train shape:", y_train.shape)    print("x_test shape:", x_test.shape, "y_test shape:", y_test.shape)
    x_train, x_test = x_train / 255.0, x_test / 255.0
    model = tf.keras.models.Sequential([        tf.keras.layers.Flatten(input_shape=(28, 28)),        tf.keras.layers.Dense(128, activation='relu'),        tf.keras.layers.Dropout(0.2),        tf.keras.layers.Dense(10, activation='softmax')    ])
    model.compile(optimizer='sgd',                    loss='sparse_categorical_crossentropy',                    metrics=['acc'])
    date_folder = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
    # 분기설정    if os.getenv('FAIRING_RUNTIME', None) is None:        log_dir = "log/fit/" + date_folder    else:        log_dir = "/notebook/log/fit/" + date_folder    print(f"tensorboard log dir : {log_dir}")    tensorboard_cb = tf.keras.callbacks.TensorBoard(log_dir=log_dir,                                             리      histogram_freq=1)    model.fit(x_train, y_train,                verbose=1,                validation_data=(x_test, y_test),                epochs=10,                callbacks=[tensorboard_cb])

DOCKER_REGISTRY = 'YOURID' # 도커허브 아이디# DOCKER_REGISTRY = 'harbor.dudaji.com/프로젝트 이름'
fairing.config.set_builder(    'append',    image_name='fairing-handson',    registry=DOCKER_REGISTRY,    base_image='dudaji/cap-jupyterlab:tf2.0-cpu',    push=True)
notebook_volume = mounting_pvc(pvc_name="workspace-handson",                                pvc_mount_path="/notebook")
fairing.config.set_deployer('job',                            pod_spec_mutators=[notebook_volume],                            cleanup=True) # 잡을 실행후 완료시 잡을 삭제할지의 여부를 결정
if __name__ == '__main__':    # print('local train()')    # train()    print('remote train()')    remote_train = fairing.config.fn(train)    remote_train()

짜잔! 🎉🎉 우리의 모델을 함수화하여 fairing으로 감싸는 작업이 완료되었습니다.
주석을 해제하고 전체 코드를 실행시키면 노트북에서 학습을 진행한 다음 다시 클러스터에서 학습이 진행될 겁니다.

fairing_train_01 fairing_train_02
fairing_train_03
fairing_train_04


중요

fairing을 사용하기 전의 train()과 다른점은 import 문이 train() 안에 들어간 것입니다.
fairing의 preprocessor는 함수를 패키징할 때 pickle을 사용합니다. tensorflow는 패키징 되는 함수안에 포함이 되지 않으면 오류가 발생하기 때문에 함수안에 넣어 준 것입니다.

학습이 완료되면 CAP의 Tensorboard 메뉴로 이동하여 텐서보드를 확인해봅시다.


fairing 으로 생성할 컨테이너의 리소스 설정하기#

fairing으로 생성할 컨테이너의 cpu, memory, gpu 설정은 pod_spec_mutators 를 통해 가능합니다.

pod_spec_mutators=[cpu=2, memory=2]
  • gpu도 gpu=1 형태로 추가가 가능합니다.


fairing 패키지에 python 파일을 추가하기#

info

현업에서는 모델이 하나의 함수로 구성되지 않기 마련입니다.
복잡한 모델을 fairing으로 실행시키는 법을 익히기 위해 python 파일을 패키지화 하는 방법을 배워봅시다.

여기선 노트북에서 만든 모델을 python 파일로 만들고 fairing에 추가해 job을 실행해보겠습니다.
먼저 fairing_outputmap.ipynb 이라는 노트북을 하나 만듭니다.

fairing_outputmap.ipynb
# fairing_outputmap.ipynbimport osimport datetimeimport tensorflow as tf
class MyModel(object):    def train(self):        mnist = tf.keras.datasets.fashion_mnist        (x_train, y_train), (x_test, y_test) = mnist.load_data()
        print("x_train shape:", x_train.shape, "y_train shape:", y_train.shape)        print("x_test shape:", x_test.shape, "y_test shape:", y_test.shape)
        x_train, x_test = x_train / 255.0, x_test / 255.0
        model = tf.keras.models.Sequential([            tf.keras.layers.Flatten(input_shape=(28, 28)),            tf.keras.layers.Dense(128, activation='relu'),            tf.keras.layers.Dropout(0.2),            tf.keras.layers.Dense(10, activation='softmax')        ])
        model.compile(optimizer='sgd',                        loss='sparse_categorical_crossentropy',                        metrics=['acc'])

        date_folder = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")        if os.getenv('FAIRING_RUNTIME', None) is None:            log_dir = "log/fit/" + date_folder        else:            log_dir = "/notebook/log/fit/" + date_folder
        print(f"tensorboard log dir : {log_dir}")
        tensorboard_cb = tf.keras.callbacks.TensorBoard(log_dir=log_dir,                                                        histogram_freq=1)        model.fit(x_train, y_train,                    verbose=1,                    validation_data=(x_test, y_test),                    epochs=10,                    callbacks=[tensorboard_cb])        return model
  • 이제 이 셀에 작성한 모델을 my_model.py라는 이름으로 저장하겠습니다.
  • 노트북에서는 %%writefile 란 명령어롤 이용해서 현재 셀을 파일화 할 수 있습니다.
  • import 문 바로 위에 %%writefile my_model.py를 넣고 실행합니다.
fairing_outputmap.ipynb
%%writefile my_model.py# fairing_outputmap.ipynbimport osimport datetimeimport tensorflow as tf
class MyModel(object):

04.write_file


  • 이제 my_model.py를 포함하는 fairing job을 만들어 보겠습니다.
    파일을 fairing 패키지에 포함하는 설정은 preprocessor에서 진행합니다.
    새로운 셀을 추가하고 아래 전체 코드를 복사해줍니다.
fairing_outputmap.ipynb
# fairing_outputmap.ipynbimport osfrom my_model import MyModelfrom kubeflow import fairingfrom kubeflow.fairing.kubernetes.utils import mounting_pvc
def train():    my_model = MyModel()    my_model.train()

# output_map 에 key[현재경로의 파일이름]:value[컨테이너 안의 파일경로] 형태로 넣어줍니다.output_map =  {    "my_model.py": "/app/my_model.py"}
# preprocessor에서 ouput_map을 넣음으로써 fairing 패키지 안에 my_model.py가 들어가게 됩니다.fairing.config.set_preprocessor("function",                                function_obj=train,                                output_map=output_map)
DOCKER_REGISTRY = 'YOURID' # 도커허브아이디# DOCKER_REGISTRY = 'harbor.dudaji.com/프로젝트 이름'
fairing.config.set_builder(    'append',    image_name='fashion-mnist-job',    base_image='dudaji/cap-jupyterlab:tf2.0-cpu',    registry=DOCKER_REGISTRY,    push=True)
notebook_volume = mounting_pvc(pvc_name="workspace-handson",                                pvc_mount_path="/notebook")
fairing.config.set_deployer('job',                            pod_spec_mutators=[notebook_volume],                            cleanup=True) # 잡을 실행후 완료시 잡을 삭제할지의 여부를 결정
if __name__ == '__main__':    fairing.config.run()

  • 이 코드에서 주의 깊게 봐야할 점은 output_mappreprocessor 를 설정하는 부분입니다.
  • 먼저 outputmap은 파라미터에 **{ "현재경로의 파일" : "fairing 으로 생성된 컨테이너에 넣을 파일경로"}_**
    의 python dictionary 형태로 넣어줍니다.
output_map
output_map = {  "my_model.py": "/app/my_model.py"}

여기서 fairing으로 생성된 컨테이너의 경로가 /app/my_model.py 인 이유는 fairing이 패키징된 파일들을 /app 에 풀어놓고 사용하기 때문입니다.

함수화 예제에서는 fairing.config.fn() 을 이용해 바로 패키징하였지만
여기선 파일을 패키징 할 것이기 때문에 preprocessor를 설정해줍니다.

fairing_config_preprocessor
fairing.config.set_preprocessor("function",                                function_obj=train, # 실행시킬 함수                                output_map=output_map) # 넣을 파일의 딕셔너리

function_obj 에는 실행시킬 함수의 이름을 넣습니다.
앞서 만든 함수의 이름이 train() 이니 train을 넣었습니다.

main 함수에서 fairing.config.run() 함수를 이용해 MyModel 클래스를 호출하여 사용합니다.
실행하면 python으로 만든 파일(my_model.py)을 포함하여 학습이 진행되는 것을 확인할 수 있습니다.

이것으로 fairing 에 대한 튜토리얼을 마치겠습니다.