레이블이 Modeling인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Modeling인 게시물을 표시합니다. 모든 게시물 표시

20200712

HuggingFace Transformers BERT 파인튜닝

HuggingFace transformers 3.0 나왔다. 기존 2.x 쓸 때 trainer API가 애매해서 직접 루프 짜곤 했는데, 3.0부터 Trainer가 꽤 쓸만해졌음. 한국어 분류 파인튜닝 기록.

데이터

사내 상품 카테고리 분류. 한글 텍스트 3만건, 14 클래스. 클래스 불균형 심함(상위 3개가 60%).

모델

KoBERT는 직접 업데이트가 뜸해서 이번엔 bert-base-multilingual-cased로 갔다. 사이즈 이유로 실운영은 distilbert 다국어 버전도 병행.

from transformers import (
    AutoTokenizer, AutoModelForSequenceClassification,
    Trainer, TrainingArguments
)

tok = AutoTokenizer.from_pretrained("bert-base-multilingual-cased")
model = AutoModelForSequenceClassification.from_pretrained(
    "bert-base-multilingual-cased", num_labels=14
)

args = TrainingArguments(
    output_dir="out",
    per_device_train_batch_size=16,
    num_train_epochs=3,
    learning_rate=2e-5,
    warmup_ratio=0.1,
    weight_decay=0.01,
    evaluation_strategy="epoch",
    fp16=True,
)
Trainer(model=model, args=args, train_dataset=train_ds, eval_dataset=val_ds).train()

결과

  • 베이스라인(TFIDF + LR): macro F1 0.58
  • mBERT fine-tune: macro F1 0.73
  • Distil-mBERT: 0.70 (훨씬 가벼움, 서빙 용)

소수 클래스는 여전히 약함. focal loss 시도해봤는데 효과는 미미. 데이터 리샘플링(상위 클래스 undersampling)이 더 효과 있었다. macro F1 0.73 → 0.76.

서빙은 onnx 변환 후 CPU 추론. 한 건당 35ms 정도. 실시간 API에 붙이기엔 아슬아슬해서 비동기 큐 사용.

20190705

TensorFlow 2.0 beta 마이그레이션

TF 2.0 beta로 사내 모델 두 개 옮겼다. 절반은 수월, 절반은 개고생. 기록 겸.

바뀐 것 핵심

  • eager execution 기본
  • session.run, placeholder, global_variables 이런 거 안 씀
  • tf.contrib 사라짐. slim 썼던 코드는 전부 리팩터
  • @tf.function으로 감싼 함수는 graph로 컴파일됨

tf_upgrade_v2 돌려보기

tf_upgrade_v2 --infile model.py --outfile model_v2.py

이 스크립트가 있는 건 다행. 변수 rename 같은 건 잘 바꾸는데, tf.contrib.slim 의존 코드는 "이건 니가 고쳐라" 주석만 박아둠. 결국 keras로 다시 쓰는 게 빠름.

@tf.function 주의

@tf.function
def train_step(x, y):
    with tf.GradientTape() as tape:
        pred = model(x, training=True)
        loss = loss_fn(y, pred)
    grads = tape.gradient(loss, model.trainable_variables)
    optimizer.apply_gradients(zip(grads, model.trainable_variables))
    return loss

파이썬 side effect (print, list.append 등)는 graph 만들 때만 실행되고 이후엔 무시된다. 디버깅할 땐 tf.print 써야 함. 이거 모르고 한참 헤맴.

결론

2.0 안 가도 되면 1.14에 머무는 게 당분간은 안전. 다만 eager 기반으로 코드 짜는 게 진짜 편하긴 하다. 프로덕션 모델은 9월 GA 되면 옮기기로.

20190225

Keras tf.keras로 통일

keras-team에서 tf.keras로 통일하는 방향을 사실상 확정한 분위기. 2.2.x 이후 multi-backend keras는 maintenance 모드로 들어가고, 앞으로는 TensorFlow 2.0에 포함되는 tf.keras에 힘을 싣겠다는 게 Chollet 본인 발표. 지난주 실무 마이그하면서 느낀 점 정리.

개인적으로 multi-backend 시절의 자유도가 아쉽긴 하다. 근데 실질적으로 Theano는 2017년에 개발 종료, CNTK도 최근 커밋이 끊겼고 PlaidML 정도가 남았는데 그것도 작은 커뮤니티. 결국 TF와 PyTorch 양강 구도에서 keras는 TF 위로 올라가는 게 자연스러운 흐름.

마이그레이션 — 실제로 달라지는 것

# before — multi-backend keras 2.2.4
from keras.models import Sequential
from keras.layers import Dense, Conv2D
from keras.callbacks import EarlyStopping
from keras import backend as K

# after — tf.keras (TF 1.13)
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Conv2D
from tensorflow.keras.callbacks import EarlyStopping
import tensorflow as tf
# backend 호출은 tf.keras.backend 또는 직접 tf 사용

대부분의 고수준 API(Model, Layer, callbacks, losses, metrics)는 시그니처가 거의 동일해서 import 경로만 바꾸면 돌아간다. 걸리는 건 다음 몇 가지.

  • 옵티마이저 객체. TF 1.x는 tf.train.AdamOptimizerkeras.optimizers.Adam이 따로 존재. tf.keras 안에서는 원래 multi-backend keras의 옵티마이저와 유사하지만 내부 wrapper가 다름. 저장된 모델의 옵티마이저 상태를 다른 구현으로 로드하면 학습 이어붙일 때 loss가 튄다. 한 프로젝트 안에서 반드시 하나로 통일
  • 커스텀 레이어 내부의 backend. K.function, K.gradients, K.learning_phase 같은 함수 호출은 tf.keras에서 대응 API가 미묘하게 다르거나, TF 2.0에서는 eager/graph 모드 구분 때문에 재설계 필요
  • Sequence, DataGenerator. 대체로 호환인데 multiprocessing 워커 수, worker_init_fn 처리가 TF 기반으로 통일되며 살짝 바뀜. use_multiprocessing=True는 OS 플랫폼에 따라 여전히 까다로움
  • Learning phase 자동 전환. multi-backend keras는 BatchNorm/Dropout이 학습/추론 모드를 전역 플래그로 관리. tf.keras + eager에서는 모델 호출 시 training=True/False 인자로 명시적. 커스텀 레이어 call(self, x, training=None)에서 training을 받아서 내부 BN에 넘기는 패턴을 꼭 써야 함

TF 2.0 alpha

며칠 전에 2.0 alpha 공개됨. 가장 큰 변화는 eager execution이 기본이라는 점. 1.x 시대의 "그래프 먼저 그리고 session 돌리기"가 사라지고, 파이썬 코드 그대로 실행되는 정의-실행 동시 모델. PyTorch에서 건너오는 사람들에게 자연스러운 방식.

그리고 tf.function 데코레이터. 성능이 필요한 구간만 그래프 컴파일. 내부적으로 AutoGraph가 파이썬 제어문(for, while, if)을 그래프 오퍼레이션으로 변환. 이 조합으로 "디버깅은 PyTorch처럼, 성능은 TF 1.x처럼"을 노린다.

import tensorflow as tf

@tf.function
def train_step(x, y, model, loss_fn, optimizer):
    with tf.GradientTape() as tape:
        logits = model(x, training=True)
        loss = loss_fn(y, logits)
    grads = tape.gradient(loss, model.trainable_variables)
    optimizer.apply_gradients(zip(grads, model.trainable_variables))
    return loss

loop을 이렇게 직접 쓰는 게 어색할 수도 있는데, 실전에서 loss에 auxiliary term 붙이거나 gradient clipping/accumulation을 커스텀하기 훨씬 쉽다. model.fit의 편의성이 필요한 건 그대로 쓰고, 실험적인 학습 로직은 custom loop. 하이브리드 구성.

할 일

  • 현재 서비스 3개 모델 import 경로 → tf.keras
  • 옵티마이저와 체크포인트 포맷 통일(SavedModel로)
  • 이번 분기 중 TF 2.0 beta 나오면 소규모 모델부터 2.0 마이그
  • 커스텀 레이어의 backend 호출 제거, tf.function 적용 구간 선별

아쉬운 점: Theano 시절 theano.gradient.jacobian 같은 걸 잘 썼는데 tf.keras + TF는 JAX 나오기 전까지 고차 미분 쪽 편의성이 떨어짐. JAX가 안정화되면 그쪽으로 연구용은 옮길지도. 운영용은 당분간 tf.keras로 정착.

20180103

TensorFlow 1.5 CNN 이미지 분류 실험

연말에 시간 남아서 TF 1.5 rc 올라온 김에 CNN 돌려봤다. 데이터는 사내 제품 이미지 분류 토이 셋(8 클래스, 각 300장 정도). 별 거 아니지만 기록용.

모델은 그냥 평범하게.

import tensorflow as tf
from tensorflow.contrib import slim

def build(x, is_training):
    net = slim.conv2d(x, 32, [3,3], scope='conv1')
    net = slim.max_pool2d(net, [2,2], scope='pool1')
    net = slim.conv2d(net, 64, [3,3], scope='conv2')
    net = slim.max_pool2d(net, [2,2], scope='pool2')
    net = slim.flatten(net)
    net = slim.dropout(net, 0.5, is_training=is_training)
    return slim.fully_connected(net, 8, activation_fn=None)

tf.contrib 언제 없어진다는 얘기 계속 나오는데 아직까지는 slim 편함. 1.5에서 eager execution도 써보려다가 학습 속도 차이 너무 나서 접음. 아직은 graph 기반이 맘편하다.

결과는 val acc 0.81 정도. 데이터 augmentation(좌우반전, 살짝 zoom) 했을 때 0.86까지 올라감. 배치 사이즈는 32. GPU는 1080ti 한 장.

메모 — 다음에 할 것:
- pretrained resnet_v2_50 feature로 바꿔보기
- tf.data API 써서 큐 없애기 (1.4부터 정식)
- TensorBoard에 confusion matrix 넣는 방법