본문으로 건너뛰기

Granite Guardian으로 OpenChat 유해성 검토 자동화

Granite Guardian 3.1 2B와 후보 토큰 확률로 OpenChat 자동 검토 범위를 넓혔습니다.

이 요약은 AI가 원문을 분석해 생성했습니다. 정확한 내용은 원문 기준으로 확인하세요.

TL;DR

LINE은 OpenChat 방 이름과 설명의 유해성을 판정하는 실시간 모니터링 모델을 Granite Guardian 3.1 2B 기반으로 개발했습니다. 수동 검토 데이터의 라벨 불일치를 severity와 reason 빈도 규칙으로 정제하고, penalty code와 reason을 고정 후보 토큰의 생성 확률로 순차 예측하도록 학습했으며 LoRA와 KV caching으로 학습·추론 비용을 줄였습니다. 세 국가의 평가에서 정상·유해 클래스 F1이 이전 모델보다 모두 개선됐고, threshold로 정밀도를 높여 자동 검토 범위를 확장했습니다. 다만 threshold 적용으로 재현율이 낮아졌으며, 향후 데이터 품질별 학습 가중치와 대형 모델을 활용한 cascading을 검토할 계획입니다.

섹션별 상세

01
LINE의 OpenChat에서는 방 이름과 설명이 운영 정책을 위반하는지 사람이 검토해야 하므로, 생성·수정량이 많은 국가에서는 자동 검토 모델이 필요합니다. 기존 모델은 일부 국가에서 수동 검토를 줄였지만 더 세밀한 기준이 필요한 지역에는 적용하기 어려웠습니다. 이번 개발의 목표는 유해 콘텐츠 판별 품질을 높여 자동 검토 범위를 넓히고, 이미 자동 검토를 쓰는 국가에서도 정확도를 개선하는 데 있었습니다.
02
학습 데이터는 현재 운영 지침이 적용된 기간의 수동 검토 기록만 추려 구성했습니다. 같은 이름과 설명에 서로 다른 penalty가 붙은 경우에는 가장 심한 penalty가 두 번 이상 나타나면 이를 선택하고, 한 번만 나타나면 두 번째로 심한 penalty를 사용했으며, reason이 동률이면 전체 데이터에서 더 드문 reason을 골랐습니다. 이 정제 과정은 라벨 noise를 줄여 모델이 입력 내용과 penalty level 및 reason 사이의 관계를 더 일관되게 학습하도록 만들었습니다.
text
# Pseudocode for data cleansing
FOR EACH group IN GROUP_BY(records, key = (name, description)):
    codes = SORT_DESC(group.penalty_codes, key = severity_rank)
    top_code = FIRST(codes)
    # Use the most severe penalty if it appears at least twice
    IF COUNT(codes, top_code) >= 2:
        final_code = top_code
    # Otherwise, use the second-most severe penalty
    ELSE:
        final_code = SECOND_HIGHEST(codes, key = severity_rank)
    reason_count = COUNT_BY_VALUE(group.penalty_reasons)
    candidates = ARGMAX_ALL(reason_count)
    # Use the most frequent reason
    IF SIZE(candidates) = 1:
        final_reason = FIRST(candidates)
    # Break ties by choosing the globally rarer reason
    ELSE:
        final_reason = ARGMIN(candidates, key = global_reason_frequency)
    EMIT(group.name, group.description, final_code, final_reason)

동일한 OpenChat 이름과 설명에 서로 다른 penalty code와 reason이 부여된 기록을 하나의 학습 라벨로 정제합니다.

03
모델 후보는 Decoder-only 구조, safety moderation task 조정 여부, 약 2B 규모, Apache license라는 조건으로 좁혔고 Granite Guardian 3.1 2B를 선택했습니다. 이 모델은 유해성 질문에 대응하는 Yes나 No 같은 고정 후보 토큰의 생성 확률을 비교하므로, 긴 설명이나 예측하지 못한 형식 대신 클래스별 확률을 직접 사용할 수 있습니다. OpenChat 모델은 이 방식을 확장해 penalty code와 penalty reason을 함께 예측하고, 약 2B 규모로 실시간 대규모 serving 비용과 응답 지연을 제한하려 했습니다.
04
학습 입력은 safety monitoring agent 역할, OpenChat 이름과 설명, assistant의 Action과 Reason 토큰으로 구성했으며 cross entropy loss는 전체 prompt가 아니라 모델 응답 영역에만 적용했습니다. 원래 가중치를 고정하고 두 개의 작은 행렬로 업데이트를 근사하는 LoRA를 사용해 학습 파라미터와 메모리 요구량을 줄였습니다. 추론에서는 penalty code 후보 토큰의 확률을 먼저 비교하고 가장 높은 code를 넣은 다음 reason 후보 토큰의 확률을 계산해 다단계 판정을 수행했습니다.
05
운영 코드에서는 실제 penalty code와 reason code를 tokenizer가 여러 조각으로 나눌 수 있는 문제를 피하려고 각각 단일 토큰에 대응하는 자연어 토큰으로 매핑했습니다. 첫 단계에서 logits 중 penalty code 토큰만 추려 softmax 확률을 계산하고, 두 번째 단계에서는 첫 단계의 past key-value를 재사용해 같은 입력 prefix의 key와 value를 다시 계산하지 않았습니다. 이 KV caching 구조는 code를 선택한 뒤 reason을 예측하는 연속 추론에서 중복 연산을 줄이는 방식입니다.
python
class OndfPredictor:
    ...
    self.penalty_code_ids = [self.tokenizer.convert_tokens_to_ids(t) for t in PENALTY_CODE_TOKENS]
    self.penalty_reason_ids = [self.tokenizer.convert_tokens_to_ids(t) for t in PENALTY_REASON_TOKENS]
    # exclude
    self.inner_template = self.tokenizer('
Reason:', add_special_tokens=False).input_ids
    ...
    # (1) predict penalty_code
    outputs = self.model(**inputs, use_cache=True)
    logits = outputs.logits
    past_key_values = outputs.past_key_values
    # cache for (2)
    last_token_logits = logits[:, -1, :]
    penalty_code_logits = last_token_logits[:, self.penalty_code_ids]
    penalty_code_probs = torch.softmax(penalty_code_logits, dim=1)
    pred_code_scores, pred_code_ids = torch.max(penalty_code_probs, dim=1)
    # map subset indices back to original token IDs & add '
Reason:' tokens after penalty_code
    pred_code_ids = torch.tensor([[self.penalty_code_ids[i]] + self.inner_template for i in pred_code_ids.cpu().tolist()]).to(self.device)
    # (2) predict penalty_reason
    outputs_step2 = self.model(
        input_ids=pred_code_ids,
        past_key_values=past_key_values,
        use_cache=True
    )
    last_token_logits_2 = outputs_step2.logits[:, -1, :]
    penalty_reason_logits = last_token_logits_2[:, self.penalty_reason_ids]
    penalty_reason_probs = torch.softmax(penalty_reason_logits, dim=1)
    pred_reason_scores, pred_reason_ids = torch.max(penalty_reason_probs, dim=1)
    # map subset indices back to original token IDs
    pred_reason_ids = [self.penalty_reason_ids[i] for i in pred_reason_ids.cpu().tolist()]

penalty code의 후보 토큰 확률을 먼저 계산한 뒤 KV cache를 재사용해 해당 reason의 후보 토큰 확률을 계산합니다.

06
세 국가의 offline 평가에서 국가 A는 약 10일, 국가 B와 C는 각각 약 한 달의 데이터를 사용했고, 평가지표로 정밀도와 재현율의 조화평균인 F1을 적용했습니다. 새 모델은 이전 모델보다 모든 지역에서 정상 클래스 F1과 유해 클래스 F1을 크게 높였으며, 전체 데이터를 대상으로 한 비교에서도 이전 모델을 앞섰습니다. 이후 online 평가에서도 좋은 성능을 보여 자동 검토 범위를 확대할 수 있었습니다.
Baseline Model과 New Model의 국가 A·B·C별 Normal F1 및 NG F1 비교 막대그래프입니다.
Chart세 국가 모두에서 초록색 New Model 막대가 회색 Baseline Model보다 높습니다. 그래프는 정확한 수치를 공개하지 않으면서 정상 클래스와 유해 클래스 F1이 새 모델에서 전반적으로 개선됐다는 본문의 평가를 시각화합니다.
07
후보 토큰 확률 방식은 일반적인 classification head 방식보다 정상 클래스 F1과 유해 클래스 F1을 모두 높였습니다. 정상 토큰의 확률이 가장 높기만 하면 정상으로 분류하는 방식은 자동 처리에 필요한 정밀도를 충족하지 못했지만, 해당 확률이 최고이면서 threshold도 넘도록 제한하자 운영 기준을 만족했습니다. threshold 적용 뒤 재현율은 낮아졌지만, 잘못된 정상 판정이 허용되지 않는 자동 검토에서는 정밀도 확보가 더 중요한 개선으로 판단됐습니다.
Normal F1과 NG F1에서 Classification Head와 Generative Token Probability를 비교한 막대그래프입니다.
Chart두 평가 항목 모두에서 초록색 Generative Token Probability 막대가 회색 Classification Head보다 높습니다. 이는 별도 classification head 대신 후보 토큰 생성 확률을 이용한 분류 방식이 두 클래스의 F1을 모두 높였다는 ablation 비교와 연결됩니다.
Normal Precision과 Normal Recall에서 Highest-probability Prediction과 Threshold-based Prediction을 비교한 그래프입니다.
ChartThreshold-based Prediction은 Normal Precision을 높이는 대신 Normal Recall을 낮춥니다. 정상 토큰 확률이 가장 높다는 조건에 threshold를 추가하면 자동 처리에 필요한 높은 정밀도를 확보할 수 있지만, 정상으로 자동 분류되는 범위가 줄어드는 정밀도와 재현율의 상충 관계가 나타납니다.

용어 해설

F1 점수(F1 score)
정밀도와 재현율의 조화평균으로, 정상 또는 유해 클래스를 얼마나 균형 있게 판별하는지 나타내는 평가 지표입니다. 한쪽 지표만 높아지는 문제를 줄여 자동 처리에 필요한 전체 판별 품질을 함께 평가합니다.
정밀도와 재현율의 상충 관계(Precision-Recall Trade-off)
모델이 정상으로 분류하는 기준을 바꾸면 정밀도와 재현율이 서로 반대 방향으로 움직이는 현상입니다. 자동 처리에서는 잘못된 정상 판정을 줄이기 위해 정밀도를 우선하고, 그 결과 일부 재현율을 감수할 수 있습니다.
KV 캐싱(KV Caching)
Decoder Transformer가 앞선 토큰에서 계산한 key와 value를 저장해 다음 토큰 처리에 재사용하는 추론 최적화 기법입니다. 입력 prefix를 반복 계산하지 않아 penalty code와 reason을 연속 예측할 때 지연과 연산량을 줄입니다.
후보 토큰 확률 분류(Candidate Token Probability)
분류 head가 별도 점수를 내도록 하는 대신, 미리 정한 답변 토큰의 생성 확률을 비교해 클래스를 결정하는 방식입니다. 생성 모델의 확률 출력을 활용하면서 예상하지 못한 긴 문장이나 출력 형식 변동을 피할 수 있습니다.
데이터 정제(Data Cleansing)
같은 OpenChat 이름과 설명에 서로 다른 penalty 결과가 붙은 사례를 규칙으로 통합해 학습 라벨의 불일치를 줄이는 과정입니다. 최고 severity 빈도와 penalty reason의 전역 빈도를 이용해 noise를 완화하고 일관된 학습 데이터를 만듭니다.

기술

  • OpenChat
  • Granite Guardian 3.1 2B
  • LoRA
  • Apache license
  • cross entropy loss
  • KV caching
  • PyTorch
  • tokenizer

활용 사례

  • OpenChat 방 이름과 설명의 유해성 자동 판정
  • penalty level과 penalty reason 예측
  • 정상 콘텐츠의 자동 처리
  • 자동 처리 기준을 충족하지 못한 사례의 수동 검토 라우팅
AI 분석 전체 내용 보기

AI 요약 · 북마크 · 개인 피드 설정 — 무료

출처 · 인용 안내

원문 발행 2026. 08. 25.수집 2026. 08. 25.출처 타입 RSS

인용 시 "요약 출처: AI Trends (aitrends.kr)"를 표기하고, 사실 확인은 원문 보기 기준으로 진행해 주세요. 자세한 기준은 운영 정책을 참고해 주세요.