Search
moon
sun
🏷️

safety_train + evaluable 에 대해 희수님과 생각을 정리하고 방향을 잡는다.

프로젝트
진행기간
2024/05/28 → 2024/06/08
데드라인
상위티켓
선행티켓
하위티켓
진행상태
완료
타입
4 more properties

배경

개발 초기부터 evaluable 의 복잡성과 사용성에 대한 의구심을 가지고 있었다. 명연님이 고쳐달라고 부탁하셨던 기능들이 모두 복구됐지만, 내가(장후) 보기에는 실사용 가능성이 불투명해 보인다. 이것이 실제로 사용이 되어야 의미가 있을 것 같다는 생각이 들었다.
종종 팀원분들을 만날 때마다 evaluable 사용 경험이 있는지 여쭤보았을 때, 다들 ‘사용한 적 없다. 완성되도 사용할지 의문이다’ 라는 말씀을 주셨다. 근본적인 사용성 개선이 필요하다는 생각이 들었다.
내가 구조를 가장 잘 파악하고 있는 저장소는 safety_model_train 이다. 만약 여기서 학습된 모델이 어렵지 않게 evaluable 과 연동된다면 활용성이 높아지겠다는 생각이 들었다. 하지만 safety_model_train 은 그 자체만으로도 꽤나 복잡한 스캐폴드를 가지고 있어 evaluable 이나 model_zoo 가 요구하는 형태를 맞추기가 번거롭다는 생각이 들었다.
이것은 단순히 evaluable 만의 문제는 아니다. evaluation 을 어떻게 더 유연하게 할 수 있을까? 좋은 인터페이스가 설계된다면 새롭게 생겨나는 다른 프로젝트들에서도 별다른 고민 없이 evaluable 이나 evaluation 의 유틸을 그대로 사용할 수 있을 것이다. 이 고민의 시작점이 safety_model_train 저장소가 될 수 있다. 희수님도 safety_model_train 을 만들면서 model_zooevaluation 등의 관계를 고민하셨을 것 같은데 어떤 생각을 하고 계신지 이야기를 나누고 evaluable 활용에 도움이 될 수 있는 방향으로 리소스를 쓰는 것이 좋지 않을까.

목표

‘이렇게 설계되면 evaluable 이 실제로 사용될 수 있겠다’ 라는 그림을 그린다.

기록

@5/31/2024 장후

기존 저장소들에 대해서 제가 파악하고 있는 내용

model_zoo: 미니멀한 모델 정의가 업로드되어 있는 저장소이다.
evaluation: 데이터 전처리, 데이터 후처리, metric 계산 통제하여 모델을 실행하고 성능을 평가하는 저장소이다.
vision-pipeline: model_zoo 로부터 모델을 불러와서 실행시키는 저장소이다.

evaluable 에 대해서 공유드릴 내용

evaluable: 모델 정의를 주면 evaluation 에게 평가를 위임한 뒤, 반환받은 결과를 리더보드에 등록한다.
실행 방법
1.
model_zoo 스캐폴드상 가장 고수준 파일인 {pytorch, onnx, ...}/net.py 과 가중치 파일을 지정해서 evaluable CLI로 업로드한다.
2.
evaluable 이 요구하는 함수를 가지고 있는 net.py 파일 (net.py 가 임포트하는 모든 파일들을 net.py 와 동일한 레벨의 디렉토리로 이동 필요) 가중치 파일을 압축해서 evaluable 웹 UI로 업로드한다.
실행 전제는 evaluation 에서 모델 객체를 어떻게 사용해서 어떻게 평가를 할 것인지에 대한 정의가 있다는 것입니다. 즉, evaluation 없이 도는 것은 불가능합니다. 아래 주석 확인해 보시면 이해가 쉬우실 겁니다.
# 예를 들어, `lmk` 태스크에 대한 사용자의 평가 요청이 있는 상황이라고 생각해 보겠습니다. def evaluate_landmark( task_ticker: str, model_name: str, dataset_name: str, weight_path: str, ) -> Dict[str, float]: assert task_ticker == "lmk" # 모델 소스코드 로드: 사용자가 업로드한 `net.py` 파일을 불러오는 영역 net_module = load_net_module(task_ticker, model_name) # 모델 빌드: 사용자가 `net.py` 파일에 정의해야 하는 세 개의 함수를 호출 # build_model() # set_model_from_weight_path() # eval() try: model = net_module.build_model(phase="forward_only") except TypeError: model = net_module.build_model() model.set_model_from_weight_path(weight_path) model.eval() # 평가 실행: 적절한 `evaluation` 모듈을 가져와서 run() 함수를 실행 if dataset_name == "wflw": nme = import_module(f"{evaluator_root}.evaluator").run( name=model_name, net=model, gpus=[0], dataset_name=dataset_name, ) result = {"testset": float(nme)} # ... 다른 데이터셋들 return result
Python
복사

현재 현상에 대한 문제정의

피상적으로 드러나는 문제
1.
이때 vision-pipelineevaluation 둘 모두는 공통적으로 모델을 실행하는 일을 한다(executor 역할). 하지만 vision-pipelineevaluation 은 호환이 잘 안 된다는 문제가 있다.
2.
model_zoo 규약을 따라야 하는 번거로움: 현재 표준 executor 은 vision-pipeline 이다. 하지만 vision-pipeline 에서 돌리려면 model_zoo 규약에 맞추어야 한다는 문제가 있다. 이 model_zoo 규약에 맞추는 것 자체가 번거롭다.
network architecture code는 모두 model_zoo에서 load됩니다. 새 model architecture를 추가할 경우 model_zoo에 먼저 추가해주세요. 자세한 사항은 model_zoo의 메뉴얼을 따라주세요. (ref: vision-pipeline README.md)
3.
evaluation 표준 준수의 번거로움: evaluation 이 학습을 담당하는 저장소들과 별도의 저장소에서 분리되어 관리되다 보니, 모델 입출력을 둘러싼 전처리와 후처리 처리 로직과 분리된다. 이미 잘 돌아가고 있는 특정 태스크용 evaluation 로직이 있는데 (예를 들어 xeye_test.py) 이것을 사용하지 못하고 또 구현해야 한다는 불편함이 있다. 학습-서빙 왜곡이라고도 바라볼수도 있다.
진짜 문제
1.
evaluationmodel_zoo 규약 준수를 요구하지만, 사실 evaluation 이 내부적으로 사용하는 model_zoo 규약이란 set_model_from_weight_path()forward() 에 불과하다.
2.
겉으로 드러나지는 않지만 model_zoo 규약을 준수하는 것은 사실 model_zoo 의 인터페이스 클래스를 구현하는 것이 아니라 model_zoo 의 스캐폴드(디렉토리 구조)를 지키는 것이다. 이것이 model_zoo 에 있는 베이스 클래스를 단순히 인터페이스 클래스로 볼 수 없는 이유다. 예를 들어 model_zoo 를 사용하는 evaluationmodel_zoo 의 특정 디렉토리에 net.py 파일이 존재할 것을 기대한다. 이러한 디자인은 소스코드를 잘 정리하여 model_zoo 에 업로드해야 하는 과정이 요구되므로 model_zoo 를 깔끔하게 유지하는 일에 도움이 되지만, evaluation 이 유용하게 활용되지 못하게 방해한다.
net = importlib.import_module("{}.{}.{}.{}.net".format(args.model_zoo_name, TASK_NAME, args.name, args.mode))
Python
복사
3.
evaluable 같은 경우에도 처음에 설계될 때 model_zoo 의 스캐폴드에 의존적으로 만들어졌기 때문에 model_zoo 에 있는 부모 클래스가 자동으로 참조되기도 하는 이상한 상황이 발생하고, 사용자가 원하는 스캐폴드로 테스트를 할 수 없다는 상황도 발생한다. 이렇게 model_zoo 경로가 전염되는 현상이 발생하고 있다.

각 저장소가 자신의 역할이라고 주장하는 것들

evaluation
일관된 방식으로 데이터를 불러오고 평가해야 한다는 생각을 가지고 만들어진 저장소이다. evaluation 은 다음과 같은 요소들을 강제하고 싶어한다.
1.
데이터 로드 방식
2.
메트릭 산출 방식
safety_model_train 과 같은, 특정 태스크를 위한 모델 학습 프레임워크
모델을 학습하고 평가하는 과정에서 어떤 전처리-후처리를 해야 하는지 정보를 가지고 있다.
1.
데이터 전처리
2.
데이터 후처리
3.
모델 추론

개선 방향성 제시

AS-IS: 개선해야 하는 불편
safety_model_train 같은 모델 학습 프레임워크에는 이미 데이터 전처리, 데이터 후처리, 모델 추론을 담당하는 소스코드가 존재한다. 모델을 개발하고 학습할 때 이들 소스코드가 변경되는 일은 잦다. 이런 변경이 있을 때마다 model_zoo 를 업데이트하고, evaluation 을 고려해서 소스코드를 작성하는 것은 불가능하다. 그래서 각 모델 학습 프레임워크들은 자신만의 방식으로 데이터 전처리, 데이터 후처리, 모델 추론에 더해 데이터 로드와 메트릭 산출 스크립트를 다시 작성하는 실정이다. 이때 데이터 로드 스크립트와 메트릭 산출 스크립트에 중복이 생겨난다. 모델 학습 프레임워크 작성자 입장에서는 이미 잘 구조화되어 돌아가고 있는 학습 프레임워크를 두고, evaluation 규약에 맞추기 위해 중복 노동을 한다는 생각으로 작업을 해야 한다.
TO-BE: 만들고자 하는 것
어떤 인터페이스 클래스 (e.g. EvaluationInterface)하나를 상속받아서 구현한다. 이 인터페이스는 model_zoo 에 대해 완전히 독립적이다.
class EvaluationInterface(): @abstractmethod def setup(self, *args, **kwargs): pass @abstractmethod def calculate(self, *args, **kwargs): pass def run(self, *args, **kwargs): # ... class MySafetyModelTrainingBaseClass(pl.LightningModule, EvaluationInterface): # 사용하는 프레임워크가 무엇이 되었든, 플러그인 방식으로 인터페이스만 구현하도록 요구 task_ticker = 'sfty' task = 'safety' datasets = ['mafa', 'widerface'] def setup(self, ... ): # ... def calculate(self, ... ): # ...
Python
복사
이들 규약만 준수하면 모든 데이터셋에 대해서 평가가 완료되고, evaluable 리더보드(non submit)에도 등록되도록 만들어야 한다. 이때 NSML 서버는 evaluable 서버와 네트워크상 단절되어 있기 때문에 파일, 메트릭, 도커 컨테이너 등을 주고받는 것이 불가능하다. 그렇다면 선택지는 세 가지로 좁혀진다.
1.
NSML 대신 evaluable 서버가 위치한 컴퓨터에서 환경을 한 번 세팅하고 EvaluationInterface 하위 클래스를 실행한다. 결과적으로 evaluable 에는 메트릭만 던지고 리더보드로서 사용한다.
2.
git 저장소에 도커파일을 포함시켜서 해당 도커파일로 컨테이너를 매번 만들도록 한다.
evaluable 웹 콘솔에서 git 저장소 주소를 입력하고 저장소 내 도커파일의 위치를 입력한다.
도커파일에는 어떤 파일이 EvaluationInterface 을 상속받은 클래스인지 진입점을 작성한다.
3.
training 프레임워크 전체를 압축파일로 싸서 업로드하도록 한다.
패키지 관리자를 poetry 로 통일하고, 항상 가상환경이나 컨테이너를 생성하여 의존성을 설치하도록 한다.
evaluable 이 사용할 EvaluationInterface 진입점을 명시하는 방식이 있겠다.

@5/31/2024 희수님

장후님은 interface class를 정의해서 어떤 구현체던지 이를 상속해서 구현하면 evaluable에 compatible하게 만드는 안으로 해석했는데 맞을까요?
제가 생각한 안은 새로운 evaluation이 dataloader 를 제공하고 이를 외부 패키지에서 받아서 model inference를 한 뒤 score.json을 evaluation에 return하면 서버에서 metric을 계산하는 형태를 생각했습니다.
그렇게되면 model inference 를 evaluable 서버에서 수행하지 않아도 되서 통신의 부담이 적지 않을까 생각했습니다. dataloading에 관련된 데이터는 nubes를 통해 외부 패키지에 전달하고 score file의 submit도 nubes 혹은 web UI를 통해 제출하는 식으로 할 수 있지 않을까 생각했습니다.
dataloading에 관련된 코드 (전처리) 와 데이터는 굳이 evaluable 서버에 있을필요 없이 oss 와 nubes를 통해 전달이 될테니까.. 이슈는 없다고 생각합니다.
대신 latency를 재는 작업은 어떻게 해결해야할지 모르겠네요. random data로 재면되기 떄문에 net.py만 제출해서 재는 식으로 하면 되지 않을까요?

@6/5/2024 장후

클라이언트단의 평가를 위한 분석 (deprecated)

오늘 회의에서 model_zoo 에 등록하는 과정이 복잡하지 않은지에 대해 여쭤보았을 때, 그렇게 복잡하지 않다고 말씀하셨다. 그럼 바꿀 이유가 크게 줄어든다. model_zooevaluation 으로 중복된 코드를 옮기는 과정이 불편할 것이라고 생각했기 때문이다.
현재 safety_model_train 저장소와 safety_demo 저장소의 연결고리는 safety_model_train 에서 callback 에 의해 생성되는 score.json 파일이다. 이 파일에는 classification output 이 작성되어 있고, safety_demo 저장소에서 이 파일을 읽어 metric 을 계산한다.

@6/7/2024 희수님

현재는 evaluable이 evaluation을 통해서만 평가가 진행되도록 종속성을 (executor 종속성) 가지고 있고, 평가도 서버에서만 일어나도록 종속성을 가지고 있다. (machine 종속성)
이 둘의 제한을 풀면 활용성이 더 늘어날 것으로 보인다.
executor 종속성을 해제할 경우 evaluation 과 model_zoo 에 데이터로딩 , 모델의 inference 와 metric 계산 을 구현하지 않아도 된다. 대신 일관된 평가를 보장하지 않는 단점이 생긴다.
이를 보완하기 위해 아래와 같이 데이터로딩과 metric 계산을 패키지로 제공하고 모델 inference 부분을 사용자가 자신의 머신에서 자신의 코드로 수행하게 하는 것을 제안하고 싶다.
latency 계산의 경우 model class를 submit해서 evaluable server에서 재는 방식을 제공할 수 있다.
# evaluation_new != evaluation from evaluation_new import dataloader from evaluation_new import compute_metric from evaluable import submit_score, submit_net # information of local machine machine_info = my_host() # my_model independent to evaluation model = my_model() for data in dataloader:     score = model(data) # compute metric at local for debug and early decision metric = compute_metric(score) # submit score to eValuable with required information submit_score(machine_info, model.info, dataloader.info, score) # submit model definition to measure latency at server submit_net(my_model)
Python
복사
💡
명연님께 의견 들어보면 좋을듯한 내용
사실 evaluable 리더보드는 두 개입니다. 하나는 submit 리더보드고, 두번째는 show-all 버튼을 누르는 경우에만 보이는 리더보드입니다.
중요한 리더보드인 submit 리더보드에는 Web UI나 CLI 를 통해 파일을 제출하고 evaluable 서버에서 직접 평가를 한 경우에만 등록됩니다.
show-all 버튼을 눌렀을 때 나타나는 리더보드에는 외부에서 metric 을 연산한 뒤 결과만 등록하는 것이 가능해졌습니다.
이를 통해 매일매일 사용 가능한 메트릭 메모장 & 비교 표로서도 기능하게 하여 활용도를 높이면서도 중앙평가기로서 신뢰성을 잃지 않기를 기대합니다.