seujung / KoBART-summarization

Summarization module based on KoBART
MIT License
196 stars 89 forks source link

안녕하세요 혹시 모델 평가 부분 질문드려도 될까요? #26

Open kkihyeon opened 2 years ago

kkihyeon commented 2 years ago

학습한 모델을 평가할때 어떻게 진행하는지에 대해서 궁금합니다. rouge척도를 이용해서 평가를 하신것 같은데, 혹시 코드와 함께 방법 알려줄 수 있으실까요?

kkihyeon commented 2 years ago

에.. 일단 제가 조금 분석한 내용이라도 공유해드리려 합니다.(오류가 있는 부분은 지적 부탁드립니다...)

학습한 모델에 대하여 평가를 진행할 때 rouge_metric.py를 이용하시게 될 것 같습니다. rouge_metric.py내 소스 코드를 보면 전체적으로 Rouge클래스가 있고 클래스 내 여러 함수로 이루어져 있는 것을 확인할 수 있습니다. 소스코드의 Rouge클래스는 기본적으로 사용할 수 있는 metric으로 rouge-n, rouge-l, rouge-w가 있고, 클래스 객체를 선언하는 과정에서 metric을 선택하여 옵션을 넣어줄 수 있습니다. ex)


rouge = Rouge(metrics=['rouge-n', 'rouge-l', 'rouge-w'])
#예시의 경우에는 모든 metric을 옵션으로 준 것입니다.

객체를 선언하실 때 metric옵션을 주셨으면 이제 실제 평가를 해서 결과값을 도출해내는 함수를 이용하셔야 하는데, 제 생각에는 클래스 내 여러 함수 중 get_scores()가 중점적인 역할을 하는 것으로 보입니다. 해당 함수를 기점으로 클래스 내 타 함수가 서로 엮여서 동작하는 것 같더라구요.

get_scores()는 인자를 (인공지능의 요약 데이터, 라벨데이터) 이렇게 두 가지를 받고 내부적으로 tokenizing하여 앞서 준 metric 옵션대로 연산 후 평가 값을 도출해내는 함수인 것 같습니다. ex)


result = rouge.get_scores(summarize, label)
#summarize는 kobart 인공지능 모델의 요약 데이터(string)
#label은 비교의 기준이 될만한 라벨 데이터(string)

get_scores()를 사용하여 평가하면 총 세가지 버전으로 평가 값을 얻을 수 있습니다. f1, precision, recall 이렇게요. 보통 rouge척도에서 두 데이터 간 비교를 할 때 해당 세가지 연산이 주로 이용되는 것 같습니다. recall은 라벨데이터에 대해 요약데이터가 얼마나 겹치는지, precision은 요약데이터에 대해 라벨데이터가 얼마나 겹치는지, f1은 앞선 두 방법을 종합적으로 고려한 연산. 이렇게 일단은 파악했습니다. 그래서 recall과 precision은 어느 한 쪽에 편파적인 값이 나올 수 있어서 주로 f1이 중점적으로 활용되는 것으로 이해했습니다.

그래서 앞선 get_scores()의 결과값으로 {'rouge-l': f:~, ,p:~, r:~~~} 이런식으로 결과값이 나오게 될 것입니다. 결과 타입은 딕셔너리입니다. value는 float형태이니 소수점을 적당히 자르셔서 쓰시기 바랍니다. 제가 get_scores()를 실행시킨 결과로 0.4xx나 0.5xx이렇게 나오더군요.

지금까지 설명드린 코드는 한 문장에 대해서 입력하고 출력값은 얻는 것을 보인 겁니다. 여러분이 여러 문장을 평가하고 이를 평균내어 종합 결과를 얻고 싶으면 반복문으로 구성하셔서 이용하시면 됩니다.

혹시 몰라 제 코드도 같이 넣어놓겠습니다.(별로 좋은 코드는 아니어서 참고만 하시기 바랍니다^^)


rouge = Rouge(metrics=['rouge-n','rouge-l','rouge-w'])
s_file = 'trainTT.json' #평가할 데이터셋(요약본과 라벨 둘 다 들어있음)
rouge_l = [0,0,0]   #각각 f,p,r
rouge_w = [0,0,0]   #각각 f,p,r

with open(s_file, 'r', encoding="UTF-8") as f:
    st_python = json.load(f)

#tqdm()은 단지 코드 수행 과정을 가시적으로 보기 위함이라 신경쓰지 않으셔도 됩니다.
for i in tqdm(range(len(st_python)),desc="(평가중...)",ascii=True):
    sumR = json.dumps(st_python[i][0],ensure_ascii=False)
    labelR = json.dumps(st_python[i][1],ensure_ascii=False)
    result = rouge.get_scores(sumR,labelR)

    rouge_l[0] += Tresult['rouge-l']['f']
    rouge_l[1] += Tresult['rouge-l']['p']
    rouge_l[2] += Tresult['rouge-l']['r']
    rouge_w[0] += Tresult['rouge-w']['f']
    rouge_w[1] += Tresult['rouge-w']['p']
    rouge_w[2] += Tresult['rouge-w']['r']

    for j in range(len(rouge_l)):
        rouge_l[j] /= (i+1)
        rouge_w[j] /= (i+1)

print(rouge_l, rouge_w)

+추가적으로 원래 rouge_metric.py에서는 문장을 tokenizing 하는 과정에서 mecab라이브러리를 이용한 것 같습니다. 에...하지만 제 컴퓨터에서는 mecab 환경 구성이 잘 안되더군요ㅠ 그래서 이미 있는 kobart_tokenizer를 이용했습니다. 둘 다 한국어 문장을 토큰화 시켜주는 역할을 하는 것 같아서 대체해서 썼습니다. 일단은 필요하신 분들을 위해 원래 rouge_metric.py코드에서 어떤 부분을 수정했는지 적겠습니다.

일단은 모듈 import 부분을 보시면


from konlpy.tag import Mecab

이렇게 되어있으실 건데 저는 대신에 kobart tokenizer를 이용했기 때문에


from kobart import get_kobart_tokenizer

를 넣어주었습니다.

그리고 init에서 tokenizer를 선정하는 코드가 있습니다.


self.use_tokenizer = use_tokenizer
if use_tokenizer:
    self.tokenizer = Mecab()

이 부분에서 Mecab()을 get_kobart_tokenizer()로 수정했습니다.

또 수정할 부분이 하나 더 남았는데 문장을 받아 실제 토큰화 하는 함수입니다. 아까와 같은 init를 보시면 tokenize_text부분이 있는데,


def tokenize_text(self, text):
    if self.use_tokenizer:
        return self.tokenizer.morphs(text)
    else:
        return text

이 중 return self.tokenizer.morphs(text)를 수정해야하는데요 morphs()같은 경우 mecab에서 쓰는 함수로 이해했습니다. 해당 코드를 return self.tokenizer.tokenize(text)로 수정했습니다.

이렇게 수정하시면 mecab 대신에 kobart_tokenizer를 이용할 수 있으실 겁니다.

제가 혼자서 분석한 것이기 때문에 오개념이 포함될 가능성이 있습니다. 잘못된 부분이 있으면 지적 부탁드립니다. 감사합니다.