본문 바로가기

ML/LLM

1. 비속어 탐지 모델 만들기 (with bert)

해당 글은 스마일게이트의 비속어 데이터셋을 바탕으로 비속어 탐지 모델을 개발한 내용입니다.

 

데이터에 대한 자세한 사항은 아래의 링크들을 확인하시면 됩니다.

욕설 모델 + 배포하기 [GitHub - smilegate-ai/korean_unsmile_dataset](https://github.com/smilegate-ai/korean_unsmile_dataset)

 

 

비속어.ipynb
0.02MB

 

 

https://colab.research.google.com/drive/1NKYYVSex__vde-lnYCmsRmyHjJhV6cKt?usp=sharing#scrollTo=Wi1qd6i8__Jl

 

Smilegate-AI UnSmile dataset fine-tuning tutorial.ipynb

Colaboratory notebook

colab.research.google.com

 

 

 

1. 한국어 욕설 탐지 BERT 모델 개발

최근 온라인 공간에서 욕설이 증가하고 있는 가운데, 욕설을 탐지하고 제한하는 기술에 대한 관심이 높아지고 있습니다. 욕설 탐지 기술은 온라인 커뮤니티, SNS, 게임 등 다양한 온라인 플랫폼에서 유해 콘텐츠를 선제적으로 감지하고 제한하는 데 활용될 수 있습니다.

해당 글은 Smilegate AI에서 공개한 한국어 욕설 데이터 세트를 활용하여 욕설을 탐지하는 BERT 모델을 개발합니다. BERT는 Google AI에서 개발한 대규모 언어 모델로, 다양한 자연어 처리 작업에서 우수한 성능을 보여 주고 있습니다.

(게임사에서는 엄청난 욕설 데이터셋를 가지고 있을거 같다...하루에도 몇억건씩 쌓이겠지...)

 

본 프로젝트는 다음과 같은 단계로 진행될 예정입니다.

  1. 한국어 욕설 데이터 세트 수집 및 전처리
  2. BERT 모델 학습
  3. 모델 평가 및 성능 개선

 

 

 

2. 데이터 세트

해당 글에서 사용되는 데이터 세트는 Smilegate AI에서 공개한 한국어 욕설 데이터 세트입니다. 데이터 세트는 약 2만 개의 문장으로 구성되어 있으며, 욕설, 비하, 인신공격 등 다양한 유형의 유해 콘텐츠를 포함하고 있습니다.

허깅페이스에 데이터를 올려주셔서 (https://huggingface.co/datasets/smilegate-ai/kor_unsmile)datasets모듈을 활용하여 쉽게 데이터를 로드할 수 있습니다.

from datasets import load_dataset
dataset = load_dataset('smilegate-ai/kor_unsmile')

 

 

총 10개의 라벨링이 되어 있으며, 각 문장에 대해 멀티라벨링이 되어 있습니다.

data_train = dataset["train"]
data_valid = dataset["valid"]

print(data_train[0])

{'문장': '일안하는 시간은 쉬고싶어서 그런게 아닐까',
 '여성/가족': 0,
 '남성': 0,
 '성소수자': 0,
 '인종/국적': 0,
 '연령': 0,
 '지역': 0,
 '종교': 0,
 '기타 혐오': 0,
 '악플/욕설': 0,
 'clean': 1,
 '개인지칭': 0,
 'labels': [0, 0, 0, 0, 0, 0, 0, 0, 0, 1]}

 

 

 

3. 모델 학습 과정

bert 모델을 통해 멀티라벨링 훈련시킬 때, BertForSequenceClassification 모델은 출력층에서 logits을 반환합니다. 이 logits는 각 클래스(혹은 라벨)에 대한 점수를 나타내는 벡터입니다. Adam 옵티마이저를 사용하여 모델을 최적화하였습니다.

 

해당 과정은 다음과 같이 진행됩니다.

 

  • 토크나이저와 모델 불러오기: BERT 토크나이저와 분류를 위한 BERT 모델을 불러옵니다.
  • 데이터셋 클래스 정의: Dataset 클래스를 상속하여 토크나이저를 이용해 데이터를 처리하는 클래스를 정의합니다.
  • DataLoader 설정: 생성된 데이터셋을 DataLoader로 감싸 배치 처리를 위한 설정을 합니다.
  • 모델 학습: Adam 옵티마이저를 사용해 BERT 모델을 훈련시킵니다.

 

 

BERT는 Transformer 아키텍처를 기반으로 하며, 각 레이어는 Self-Attention과 Feedforward Neural Network로 구성되어 있습니다. 이러한 구조를 통해 문장 내의 단어 간 관계를 이해하고 문맥을 파악하여 효과적으로 문장을 인코딩하고 분류하는 데 사용됩니다.

 

모델학습을 테스트 해보았지만, 저의 경우엔 "beomi/kcbert-base" 모델이 가장 정확도가 높았습니다. (사용하실분들은 model_name만 변경해서 사용하시면 됩니다.)

해당 모델을 BertForSequenceClassification 를 통해서 로드합니다.

model_name = "Huffon/klue-roberta-base-nli"
model_name = 'klue/roberta-base'
model_name = 'klue/roberta-large'
model_name = 'beomi/kcbert-base'


# 토크나이저 및 모델 불러오기
tokenizer = BertTokenizer.from_pretrained(model_name)
model = BertForSequenceClassification.from_pretrained(model_name, num_labels=len(data_train['labels'][0]))


# 모델 학습 준비
device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')
model.to(device)

 

 

BERT 모델은 다음과 같은 구조를 가지고 있습니다.

Input Layer         # 입력문장을 벡터로 변환
Embedding Layer     # 벡터를 transformer 레이어에 입력
Transformer Encoder # 문장 이해 및 추출
Output Layer        # 라벨링값 출력

 

 

주요 레이어 설명

BertModel:  BERT모델은 embeddings, encoder, pooler 레이어로 구성되어 있습니다.
        Embeddings: 입력 토큰을 임베딩하는 부분으로, 단어, 위치, 토큰 타입 임베딩을 합칩니다.
        Encoder: Transformer 인코더 레이어로 구성되어 있으며, 여러 개의 BertLayer로 이루어져 있습니다.

        Pooler: BERT의 출력을 사용하여 시퀀스 레벨의 표현을 얻는 부분입니다.
    Dropout: 모델 내에서 사용되는 드롭아웃 계층으로, 과적합을 방지하기 위해 사용됩니다.
    Classifier: 실제 분류를 수행하는 선형 분류기입니다. out_features=10은 이 모델이 10개의 클래스를 분류할 수 있는 것을 나타냅니다.

BertForSequenceClassification(
  (bert): BertModel(
    (embeddings): BertEmbeddings(
      (word_embeddings): Embedding(32000, 768, padding_idx=1)
      (position_embeddings): Embedding(514, 768)
      (token_type_embeddings): Embedding(1, 768)
      (LayerNorm): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
      (dropout): Dropout(p=0.1, inplace=False)
    )
    (encoder): BertEncoder(
      (layer): ModuleList(
        (0-11): 12 x BertLayer(
          (attention): BertAttention(
            (self): BertSelfAttention(
              (query): Linear(in_features=768, out_features=768, bias=True)
              (key): Linear(in_features=768, out_features=768, bias=True)
              (value): Linear(in_features=768, out_features=768, bias=True)
              (dropout): Dropout(p=0.1, inplace=False)
            )
            (output): BertSelfOutput(
              (dense): Linear(in_features=768, out_features=768, bias=True)
              (LayerNorm): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
              (dropout): Dropout(p=0.1, inplace=False)
            )
          )
          (intermediate): BertIntermediate(
            (dense): Linear(in_features=768, out_features=3072, bias=True)
            (intermediate_act_fn): GELUActivation()
          )
          (output): BertOutput(
            (dense): Linear(in_features=3072, out_features=768, bias=True)
            (LayerNorm): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
      )
    )
    (pooler): BertPooler(
      (dense): Linear(in_features=768, out_features=768, bias=True)
      (activation): Tanh()
    )
  )
  (dropout): Dropout(p=0.1, inplace=False)
  (classifier): Linear(in_features=768, out_features=10, bias=True)
)

 

 

 

해당 클래스는 BERT 모델에 사용될 데이터셋을 로드하고 제공하는 역할을 합니다.

# 데이터셋 클래스 정의
class CustomDataset(Dataset):
    def __init__(self, texts, labels, tokenizer, max_length):
        self.encodings = tokenizer(texts, padding=True, truncation=True, max_length=max_length, return_tensors='pt')
        self.labels = torch.tensor(labels, dtype=torch.float32)

    def __getitem__(self, idx):
        item = {key: val[idx] for key, val in self.encodings.items()}
        item['labels'] = self.labels[idx]
        return item

    def __len__(self):
        return len(self.labels)

 

 

 

torch.optim.Adam를 사용하여 모델의 파라미터를 업데이트하는 옵티마이저를 설정하는 부분입니다.

함수는 Adam을 사용합니다. Adam은 경사 하강법 알고리즘 중 하나로, 모멘텀(momentum)과 제곱 그래디언트의 지수 가중 이동 평균을 사용하여 최적화를 수행합니다.

lr은 학습률(learning rate)을 설정합니다. 이 값은 각 파라미터의 업데이트 속도를 결정합니다

optimizer = torch.optim.Adam(model.parameters(), lr=1e-5)

 

 

 

다음은 train 부분입니다.

    model.train(): 모델을 학습 모드로 설정합니다. 드롭아웃과 배치 정규화 등과 같은 학습 관련 기능을 활성화합니다.
    optimizer.zero_grad(): 이전 배치에서의 그래디언트를 초기화합니다. 새로운 배치에 대한 그래디언트 계산을 위해 사용됩니다.

    model(input_ids, attention_mask=attention_mask, labels=labels): 입력 데이터를 모델에 전달하여 출력을 계산합니다. BERT 모델에 입력 데이터와 라벨을 제공합니다.
    손실 계산: outputs.loss를 통해 손실을 계산합니다. 이 손실은 모델의 출력값과 실제 라벨 간의 차이를 나타냅니다.
    역전파 및 가중치 업데이트: loss.backward()로 손실에 대한 그래디언트를 계산하고, optimizer.step()으로 옵티마이저를 사용하여 가중치를 업데이트합니다. 이 과정은 각 배치마다 수행되며, 모델이 데이터셋의 패턴을 학습하도록 돕습니다.

epochs = 3  # 에폭 수 설정

for epoch in range(epochs):  # 지정한 에폭 수만큼 반복
    model.train()  # 모델을 학습 모드로 설정

    for batch in train_loader:  # 훈련 데이터셋을 배치 단위로 순회
        optimizer.zero_grad()  # 그래디언트 초기화

        # 배치에서 데이터를 GPU로 이동
        input_ids = batch['input_ids'].to(device)
        attention_mask = batch['attention_mask'].to(device)
        labels = batch['labels'].to(device)

        # 모델에 입력 데이터를 전달하여 출력 계산
        outputs = model(input_ids, attention_mask=attention_mask, labels=labels)

        # 손실 계산
        loss = outputs.loss

        # 역전파 수행
        loss.backward()
        optimizer.step()  # 옵티마이저를 이용하여 가중치 업데이트

 

 

 

 

    

학습된 모델을 평가하는 부분입니다.

model.eval(): 모델을 평가 모드로 설정합니다. 평가 모드에서는 드롭아웃과 같은 학습 과정에서 사용되는 기능들이 비활성화됩니다.
    with torch.no_grad():: 그래디언트 계산을 비활성화합니다. 검증 과정에서는 모델을 훈련시키지 않고 예측만 수행합니다.
    검증 데이터를 GPU로 이동하고, 모델에 입력하여 출력을 계산합니다.
    label_ranking_average_precision_score: 검증 데이터셋에서 멀티 라벨 분류의 스코어를 계산하여 scores 리스트에 추가합니다.
    모델의 출력값에 시그모이드 함수를 적용하여 확률값으로 변환한 후, 예측값과 실제 라벨을 각각 all_preds와 all_labels 리스트에 추가합니다.

from sklearn.metrics import label_ranking_average_precision_score

model.eval()  # 모델을 평가 모드로 전환

scores = []  # 검증 데이터셋에서의 스코어를 저장할 리스트
all_preds = []  # 모든 예측값을 저장할 리스트
all_labels = []  # 모든 실제 라벨을 저장할 리스트

for batch in val_loader:  # 검증 데이터셋을 배치 단위로 순회
    with torch.no_grad():  # 그래디언트 계산 비활성화
        input_ids = batch['input_ids'].to(device)
        attention_mask = batch['attention_mask'].to(device)
        labels = batch['labels'].to(device)

        # 모델에 입력 데이터를 전달하여 출력 계산
        outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
        val_loss = outputs.loss

        # 검증 데이터에서의 스코어 계산
        scores.append(label_ranking_average_precision_score(labels.cpu().numpy(), outputs.logits.cpu().numpy()))

        # 예측값과 실제 라벨 저장
        all_preds.extend(torch.sigmoid(outputs.logits).cpu().numpy())  # 시그모이드 함수를 통해 확률값으로 변환
        all_labels.extend(labels.cpu().numpy())

 

 

 

 

4. 모델 성능 평가

모델의 성능은 F1-score를 사용하여 평가합니다. F1-score는 정밀도(precision)와 재현율(recall)의 조화 평균입니다.

classification_report 함수를 통해 생성된 모델의 precision(정밀도) / recall(재현율) / f1-score(F1 점수)이 출력됩니다.

import numpy as np
from sklearn.metrics import classification_report

# 최종 보고서 생성
all_preds = np.array(all_preds) > 0.5  # 임계값을 조정하여 예측 값을 얻음
report = classification_report(np.array(all_labels), all_preds)
print(report)


               precision    recall  f1-score   support

           0       0.82      0.77      0.79      1182
           1       0.88      0.80      0.84      1002
           2       0.88      0.83      0.85       840
           3       0.84      0.76      0.80      1278
           4       0.89      0.82      0.86       438
           5       0.86      0.92      0.89       780
           6       0.85      0.90      0.88       870
           7       0.70      0.25      0.36       402
           8       0.76      0.58      0.66      2358
           9       0.76      0.72      0.74      2805

   micro avg       0.81      0.73      0.77     11955
   macro avg       0.82      0.74      0.77     11955
weighted avg       0.81      0.73      0.76     11955
 samples avg       0.75      0.74      0.74     11955

 

0.80의 정밀도와 0.73의 재현율, 그리고 0.75의 F1-score를 가진다면 해당 클래스에 대한 모델의 성능이 좋다고 평가할 수 있습니다.

 

 

 

 

Next level

제공 데이터는 스마일게이트에서 지원한 데이터만으로 학습했으므로, 데이터 세트의 크기를 늘려 모델의 성능을 향상시켜야 하며, 데이터를 수집 / 모델학습 / 평가하는 작업도 필요합니다.

 

해당 모델을 배포 및 실제 사용에 나온 로그를 통한 재학습 파이프라인 및 MLops가 필요합니다.

 

다음글에는 개발한 모델을 서비스하기 위한 인퍼런스 서버 개발 및 최적화를 통한 onnx를 통한 모델 학습 및 배포에 대해서 글을 써보록 하겠습니다.

 

 

 

참고문서

https://wikidocs.net/166803

https://github.com/smilegate-ai/korean_unsmile_dataset

https://colab.research.google.com/drive/1NKYYVSex__vde-lnYCmsRmyHjJhV6cKt?usp=sharing#scrollTo=Wi1qd6i8__Jl