Spacy를 활용한 나만의 한국어 NER 모델 만들기 (2)
by Cori지난 시간에 만든 NER 모델의 경우, 학습 과정에서의 성능 평가 결과는 좋았으나, 실 사용 결과, 좋지 못한 성능을
보였다. 해당 모델을 개선하기 위해, 데이터세트 구축부터 다시 시작해보려 한다.
Step 1. 기존 데이터세트 문제점 분석
발견한 문제점 첫번째. 학습 데이터세트의 불균형
기존 데이터세트의 경우 증권 종목이 포함되어 있는 텍스트로만 구성되어 있었다.
정확히는, 구축해둔 증권 종목 사전을 GPT에 전달 후, 각 종목들을 포함하고 있는 텍스트 쌍을 생성하도록 시켰다.
이 경우, 증권 종목이 포함되어 있지 않은 데이터와 여러 증권 종목이 들어간 텍스트는 얻을 수 없게 된다.
이를 개선하기 위해, 인공지능 챗봇과 사용자의 대화 로그를 학습 데이터세트 구축에 활용하였다.
발견한 문제점 두번째. 증권 종목 사전의 품질성
기존 증권 종목 사전은 국내 증권 종목 + 해외 증권 종목 + 사용자 대화 로그에서 추출한 증권 종목으로 구성된다.
국내 증권 종목의 경우 yfinance, 해외 증권 종목의 경우 nasdaq 관련 사이트에서 가져왔다. 추가적으로,
사용자 대화 로그에서도 증권 종목을 추출하면 삼성전자와 같은 공식 용어를 비롯한 삼전과 같은 약어까지
얻을 수 있을 것 같아 GPT를 사용해 추출한 증권 종목도 사용했다.
얼핏 보면 꽤 괜찮은 것 같지만, 문제는 GPT를 사용해 추출한 증권 종목들에서 발생하였다.
GPT4-o를 사용해서 증권 종목들을 추출했음에도 불구하고, AI주, 방산주, 테마주와 같이 증권 종목 이름이 아닌
애들을 추출하고, TIGER 나스닥 200 ETF 에서 나스닥 200 ETF는 빼먹고 TIGER 만 추출하더라..
또 "죄송합니다. 입력 받은 문장에서 증권 종목을 추출할 수 없습니다" 같은 함정 데이터도 종종 섞여 있었다.
이를 개선하기 위해, 새롭게 구축한 데이터세트는 전처리 과정을 좀 신경써서 해 주었다.
테마주, 배당주, AI주, 배터리주를 비롯한 관련 없는 애들을 전부 제외시키고, 증권 종목을 추출하지 못한 경우도
룰 베이스 필터링을 진행하고, 프롬프트도 약간의 수정을 거쳤다.
self.system_role = """ 기존 프롬프트
너는 금융권에서 종사하는 전문가야. 입력받은 질문에서 증권 종목을 추출해줘. \
삼성전자 저평가야 ? 같은 질문이 들어오면 삼성전자만 반환하면 돼. 그 외 답변은 하지마.
"""
self.system_role = """ 개선한 프롬프트
너는 금융권에서 종사하는 전문가야. 입력받은 질문에서 증권 종목을 추출해줘. \
삼성전자 저평가야 ? 같은 질문이 들어오면 삼성전자만 반환하면 돼. 그 외 답변은 하지마.
방산주, 수해주, 장비주, 테마주와 같은 애들은 증권 종목이 아니야.
TIGER ETF 200 같은 문장이 들어오면, TIGER만 추출하는게 아니라 TIGER ETF 200 전체를 추출해줘.
발견한 문제점 세번째. 정확한 레이블링 x
이 문제는 좀 복잡해서 골머리를 좀 앓고 있는 문제인데, 정확한 레이블링이 어렵다는 점이다.
위와 같이 증권 종목 사전에 '마소'라는 토큰이 있다고 해보자.
'마소는 요즘 하향가야 ?' 라는 문장이 들어 왔을 때, ['마소', 0, 2, 'TICKLE']과 같은 형태로 레이블링 되어야 한다.
반면, '싸우지들 좀 마소'와 '마소킴'이라는 문장이 들어왔을 때에는 [None] 형태로 레이블링 되어야 한다.
뿐만 아니라 'IS'라는 토큰이 있을 때, 'ISA 계좌 알려줘'라는 문장이 들어와도 [None] 형태로 레이블링 되어야 한다.
기존에는 이러한 예외 상황을 고려하지 못하고, 증권 종목 사전에 있는 토큰들이 사용자 입력 문장으로 들어오는 경우,
레이블링 하도록 코드를 작성했었다. 이러니 성능이 이상할 수 밖에 ..
결국에는 1차적으로 코드를 통해 레이블링을 진행한 후, 레이블링 결과를 보며 사람이 추가 주석 작업할 데이터를
따로 추렸다. 아주 다행인건, NER 태깅을 위한 사이트가 무료로 공개되어 있다는 것이었다 !
위 사이트에 가서 Tag를 추가한 후, 업로드한 파일을 주석할 수 있다.
Step 2. 데이터세트 재구축
앞서 언급한 문제들을 보완함으로써, NER 학습을 위한 준비는 모두 마쳤다.
일단 가지고 있는 데이터를 DocBin 클래스로 감싸주자.
def get_spacy_doc(file, data):
nlp = spacy.blank('ko')
db = DocBin()
for text, annot in tqdm(data):
doc = nlp.make_doc(text)
# Extract entities from the annotations
ents = []; entity_indices = []
for start, end, label in annot['entities']:
skip_entity = False
for idx in range(start, end):
if idx in entity_indices:
skip_entity = True
break
if skip_entity:
continue
entity_indices = entity_indices + list(range(start, end))
try:
span = doc.char_span(start, end, label=label, alignment_mode='strict')
except:
continue
if span is None:
err_data = str([start, end]) + " " + str(text) + "\n"
file.write(err_data)
else:
ents.append(span)
try:
doc.ents = ents
db.add(doc)
except:
pass
return db
레이블링이 잘못된 파일은 file에 기록되는데, 살펴보니 '삼성전자 하향가야 ?'라는 텍스트가 있을 때
(0, 4, 'TICKLE')이 아닌 (0, 5, 'TICKLE') 같은 형태로 공백까지 잘못 레이블링 된 데이터들이 주를 이루고 있었다.
해당 파일에 기록된 데이터들은 모델 학습에 사용되지 않으니, 수정 후 추가해주거나 버리면 된다.
Step 3. Spacy NER 모델 학습 및 테스트
Spacy 모델 학습을 위한 config 파일을 다시 만들어주고
$ python -m spacy init config base_config.cfg --lang ko --pipeline ner
$ python -m spacy init fill-config base_config.cfg config.cfg
모델을 학습시켜보자.
$ python -m spacy train config.cfg --output ./ --paths.train ./train.spacy --paths.dev ./train.spacy
학습 로그를 살펴보면 NER Loss 값이 생각보다 크게 찍혀서, 이 부분은 좀 더 살펴볼 예정이다.
ENTS_F (F1 Score 값)은 그래도 86~88 정도의 성능을 보이니, 학습이 이상하게 된 건 아닌 것 같다.
드디어 대망의 테스트 시간. 이전 포스트에서 결과가 이상하게 나오던 텍스트들을 먼저 실험해보았다.
우려했던 것과는 다르게, 결과는 생각보다 준수하게 나왔다 !!
일상 대화에서 종목 정보를 추출하는 오류도, 접미사를 같이 추출하는 오류도 이전 모델보다 훨씬 개선되었다.
다만, 여전히 오류는 존재했으니 ..
학습 데이터에 존재하는 'lg화학, 더본코리아'를 추출하지 못하고, 범위를 잘못 설정('삼성 비교')해서 추출하더라..
이 부분은 학습 모델을 변경하거나, 데이터를 증강하는 등의 개선 방안을 연구해 볼 생각이다.
개선이 완료되는대로, 여기에 이어서 공유할 예정이다.
'AI > Natural Language Processing' 카테고리의 다른 글
Spacy를 활용한 나만의 한국어 NER 모델 만들기 (1) (7) | 2024.11.06 |
---|---|
FastAPI를 이용한 추론 모델 배포 (feat.docker) (4) | 2024.09.20 |
Self-RAG (실습) (1) | 2024.07.03 |
Exploring RAG for Tables (실습) (0) | 2024.06.28 |
Exploring Query Rewriting (실습) (0) | 2024.06.28 |
블로그의 정보
코딩하는 오리
Cori