Professional Field

SQL(ORACLE) | AI | BI | 통계분석

Portfolio

[ML/DL] NLP 재정정보 AI 검색 알고리즘 (실패..)

K_CY 2024. 7. 30. 16:52
반응형

DACON에서 주최한 재정정보 AI  검색 알고리즘 경진대회에 올라온 데이터를 가지고 자연어 분석을 시도해보려고한다.
 
먼저, 데이터 셋은 다음과 같다.

 

Question 변수가  X, Answer 변수가 Y로 모델진행을 계획하고, 먼저 X변수의 토크나이징을 진행하겠다. 
추가로 특수문자나 필요없는 단어들을 삭제해주려고 한다.
 
 
Okt를 이용한 토크나이징과 형태소분석은 다음과 같다.

명사는 잘 추출되지만 재정정보는 연도에 따라 변경되기 때문에 연도가 꼭 필요한 변수라고 생각을 했다.
 
이번엔 코모란을 이용해서 토크나이징을 진행해준다.

#코모란을 이용한 형태소 분석
from konlpy.tag import Komoran
komoran = Komoran()

# 연도 전처리 함수
def preprocess_year(text):
    pattern = re.compile(r'\b(19[0-9]{2}|20[0-9]{2})\b년')
    return pattern.sub(lambda x: x.group(0)[:-1], text)

# 텍스트 전처리
df['Preprocessed_Question'] = df['Question'].apply(preprocess_year)

# 텍스트 전처리
df['morphs'] = df['Preprocessed_Question'].apply(komoran.morphs)
df['nouns'] = df['Preprocessed_Question'].apply(komoran.nouns)

 
komoran을 사용하여 형태소분석을 해주었는데 연도가 분리가 되는 바람에 합쳐줄 수 있는 코드를 추가하였다.
morphs와 nouns를 사용하였는데, morphs에는 토크나이징을 처리하고 nouns 에는 품사가 명사인 것만 추출하기 때문에 연도와 같은 내용이 명사로 인식을 하지 못한다. 

왼쪽부터 komoran.morphs / komoran.nouns / komoran.morphs-nouns / komoran.morphs-okt.nouns

 
그렇기 때문에 그냥 코모란 morphs을 이용해 nouns를 사용한 세번째 변수를 적용해야 할 것 같다. 추후에 연도를 따로 빼는 방법을 추가 포스팅 하겠다. 
 
토크나이징한 텍스트를 이제 임베딩를 진행한다.
 
임베딩에는 BERT를 사용했다. 

from sentence_transformers import SentenceTransformer
model = SentenceTransformer('xlm-r-bert-base-nli-stsb-mean-tokens')

# 각 리스트를 문자열로 결합하여 문장 형태로 변환
df['sentence'] = df['komoran'].apply(lambda x: ' '.join(x))

# 문장들을 BERT 임베딩으로 변환
embeddings = model.encode(df['sentence'].tolist())

# 임베딩 결과를 데이터프레임에 추가
df['embedding'] = list(embeddings)

# 각 문장 및 임베딩 출력
for i, row in df.iterrows():
    print(f"문장: {row['sentence']}\n임베딩: {row['embedding'][:5]}... (차원 수: {len(row['embedding'])})\n")
문장: 2024 년 중앙 정부 재정 체계 게 구성 되
임베딩: [ 0.38069245 -0.41127887  0.8416608   0.03666429  0.39096087]... (차원 수: 768)

문장: 2024 년 중앙 정부 의 예산 지출 은 게 구성 되
임베딩: [ 0.464537   -0.50975853  0.7512195   0.1461296   0.32927892]... (차원 수: 768)

문장: 기금 예산 과 점 은
임베딩: [ 0.2406919  -0.295846    0.36307684  0.3847104   0.28916717]... (차원 수: 768)

문장: 일반 회계 특별회계 기금 간 의 차이점 은
임베딩: [ 0.05531106 -0.1735029  -0.03326645  0.25009072  0.20768152]... (차원 수: 768)

문장: 2024 년 총수입 은 얼마 예산 수입 과 기금 수입 은 원
임베딩: [ 0.1997647  -0.03951213  0.5037688   0.06779161  0.22616374]... (차원 수: 768)

문장: 2024 년대 예산 수입 은 게 구성 되
임베딩: [ 0.44214138 -0.37328902  1.0014652  -0.03269047  0.28826803]... (차원 수: 768)

문장: 년 의 기금 수입 은 게 구성 되
임베딩: [ 0.04217908 -0.13964976  0.66097635  0.14274059  0.24572419]... (차원 수: 768)

BERT를 사용한 이유는 문장이기 때문에 앞 뒤에 양방향 인코더로 맥락을 모델에 훈련킬 수 있다.
 
 
임베딩 벡터를 예측할 때는 회귀 문제로 접근하는 것이 적합하다. 
이를 확인하기 위해, 다중클래스분류인 softmax와 CategoricalCrossentropy를 사용해보았다.
결과는 loss가 음수가 나오고 정확도가 현저하게 낮았다.

 
그 이유는 임베딩은 양방향 인코더로 연속된 실를 이루고 있어, 이중분류 문제가 아니라면 특정 클래스를 예측하는 것이 아니므로 회귀가 적합하다.
손실함수는 예측 값과 실제 값 간의 차이를 제곱하여 평균을 내는 MSE로 정리하고, 활성화함수는 linear 선형함수를 사용해보자.

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.losses import MeanSquaredError

from tensorflow.keras.losses import CategoricalCrossentropy
# 데이터 형상 확인
print(f'train_x shape: {train_x.shape}')  # (496, 768)
print(f'train_y shape: {train_y.shape}')  # (496, 768)

# 모델 정의
model = Sequential()
model.add(Dense(512, input_shape=(train_x.shape[1],)))  # 입력 레이어
model.add(Dense(256))  # 중간 레이어
model.add(Dense(train_y.shape[1], activation='linear'))  # 출력 레이어

# 모델 컴파일
model.compile(loss='MeanSquaredError',
              optimizer='adam',
              metrics=['accuracy'])


# 모델 훈련
model.fit(train_x, train_y, epochs=100, batch_size=16, validation_split=0.2)

 
이후 test_x 도 위와 같은 방법으로 임베딩 진행 

test_df['Preprocessed_Question']=test_df['Question'].apply(preprocess_year)

test_df['morphs'] = test_df['Preprocessed_Question'].apply(komoran.morphs)

test_df['komoran'] = test_df['morphs'].apply(extract_nouns_and_numbers_komoran)

from sentence_transformers import SentenceTransformer
model_ST = SentenceTransformer('xlm-r-bert-base-nli-stsb-mean-tokens')

# 각 리스트를 문자열로 결합하여 문장 형태로 변환
test_df['sentence'] = test_df['komoran'].apply(lambda x: ' '.join(x))

# 문장들을 BERT 임베딩으로 변환
embeddings = model_ST.encode(test_df['sentence'].tolist())

# 임베딩 결과를 데이터프레임에 추가
test_df['embedding'] = list(embeddings)

test_x=test_df['embedding']
test_x= np.array(test_x.tolist())

 
예측

predictions = model.predict(test_x)

 
결과

from sklearn.cluster import KMeans

# 훈련 데이터의 임베딩을 클러스터링
kmeans = KMeans(n_clusters=100)
kmeans.fit(train_y.astype(np.float64))

# 각 클러스터의 중심을 텍스트로 매핑
cluster_centers = kmeans.cluster_centers_
text_mapping = {}

# 각 클러스터에 가장 가까운 텍스트를 찾음
for i, center in enumerate(cluster_centers):
    min_dist = float('inf')
    closest_text = None
    for text, embedding in zip(df['Answer'], train_y):
        dist = np.linalg.norm(center - embedding)
        if dist < min_dist:
            min_dist = dist
            closest_text = text
    text_mapping[i] = closest_text

# 예측 임베딩을 텍스트로 변환
predicted_texts = []
for embedding in predictions.astype(np.float64):
    cluster = kmeans.predict([embedding])[0]
    predicted_texts.append(text_mapping[cluster])

# 예측 결과를 데이터프레임에 추가
test_df['predicted_text'] = predicted_texts

# 예측 결과 출력
for i, row in test_df.iterrows():
    print(f"질문: {row['Question']}\n예측 답변: {row['predicted_text']}\n")

 
값은 나오는데 질문과 답변이 잘 맞지 않아서 모델
에 들어가는 토큰화를 다시 진행해야 할 것 같다. 
24년 8월에 모델훈련을 다시 맞춰야 할 듯 하다.....

질문: 핵심재정사업 성과관리제도를 안착시키기 위해 필요한 노력과 성과 정보를 학습의 도구로 활용하는 방안은 무엇인가?
예측 답변: 국가재정법에 따라 성과계획서를 작성해야 하는 대상기관은 국가재정법(제4장의2)에 따라 예산요구서를 제출하는 모든 중앙관서의 장과 기금관리주체입니다.

질문: 사회보험 사각지대 발생의 주요 원인과 이로 인해 발생하는 문제는 무엇인가요?
예측 답변: 재정융자사업은 정부가 자금을 민간의 사적 경제주체에 대해 대출해 주고 회수하는 활동을 말하며, 민간 금융의 역할이 미약하거나 부족한 부분에 직접 자금을 공급할 수 있는 장점이 있습니다.

질문: 수직적 재정조정제도와 수평적 재정조정제도의 차이는 무엇인가요?
예측 답변: 관리재정수지는 통합재정수지에서 사회보장성기금수지를 차감하여 산출된 재정 흑자 또는 적자를 의미합니다.

질문: 중앙-지방 간 재정조정제도에서 어떤 재원을 이전하여 수직적 재정 불균형을 해소하는가?
예측 답변: 구조적 재정수지는 경기조정재정수지에서 일시적인 수입·지출 증감 요인을 제거하여 재정건전성을 판단하는 지표로 활용된다.

질문: 중앙정부의 예산편성은 어떤 재원 배분 문제를 결정하며, 중앙-지방 간 재정조정제도를 통해 어떤 재원을 이전하고, 이의 목적은 무엇인가?
예측 답변: 예산 확정 후 전년도로부터의 이월액 반영, 추경 편성, 기금운용계획 변경, 이용・전용, 예비비 증액 등을 반영한 것으로, 실제 지출할 수 있는 한도액을 의미한다.

 
 
 
 
https://seokii.tistory.com/28

NLP 기초 - KoNLPy를 활용한 한국어 형태소 토크나이징(Komoran, Kkma, Okt)

토크나이징(tokenizing) 일반적으로, 자연어 처리를 하기 위해서는 문장을 일정 의미를 지닌 작은 단어들로 나누어야 합니다. 가장 기본이 되는 단어를 토큰(token)이라 합니다. 말뭉치(혹은 문장)가

seokii.tistory.com

 

반응형