일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- Tableu
- 트위치
- 개체명 인식
- KeyBert
- 클래스 분류
- 데벨챌
- 구글 스토어 리뷰
- 문맥을 반영한 토픽모델링
- 피파온라인 API
- 토픽 모델링
- 자연어 모델
- 데이터리안
- 조축회
- 붕괴 스타레일
- LDA
- Roberta
- 원신
- 블루 아카이브
- SBERT
- 옵티마이저
- 블루아카이브 토픽모델링
- 포아송분포
- 데이터넥스트레벨챌린지
- BERTopic
- geocoding
- NLP
- Optimizer
- CTM
- 코사인 유사도
- 다항분포
- Today
- Total
분석하고싶은코코
NLP - 텍스트 요약(Text Summarization) + BERTsum 논문 톺아보기 본문
NLP에서 주목받는 분야중 하나가 요약(Summarization)과 관련된 분야입니다. 이에 대해서 알아보고 BERT를 기반으로 만들어진 BERTSum 논문에 대해서 살펴보는 포스팅을 진행해보겠습니다.
0) 텍스트 요약 in NLP
텍스트 요약이라하면 긴 원문이 존재하고 그 안에서 핵심 내용만 추려서 원문에 비해 비교적 간소한 문장으로 변환하는 것으로 의미합니다. 이 텍스트 요약은 추출적(extractive) 요약과 추상적(abstractive) 요약으로 나뉩니다. 추출과 추상 두 가지 단어를 있는 그대로 이해하시면 됩니다. 추출적 요약은 원문에 있는 단어들을 활용해 요약을 만들어 내는것이고, 추상적 요약은 원문에 있지는 않은 단어들도 포함하여 요약문을 만들어 내는 것입니다. 흔히 요즘 이야기하는 생성AI라고 생각하시면 될 것 같습니다. 텍스트 요약에 대한 많은 방법들이 존재하지만 이번 포스팅에서는 텍스트랭크를 통한 추출적 요약을 실습해보고 BERTSum 논문에 대해서 다뤄볼 예정입니다.
1) 추출적 요약(extractive summarizaiton) + BERTSum
추출적 요약에 대한 실습을 먼저 진행해보고 이후에 BERT를 활용한 추출적 요약 모델인 BERTSum에 대한 논문을 살펴보겠습니다. 추출적 요약에 대한 실습은 텍스트랭크(TextRank)를 통한 추출적 요약을 간단하게 진행해보겠습니다. 이번 포스팅에서 집중적으로 다룰것은 텍스트에 대한 요약이기에 텍스트랭크에 대해서 자세한 설명은 하지 않고 실습을 바로 진행해보겠습니다. 텍스트랭크에 대한 내용은 링크 포스팅을 참고해주세요.
1-1) TextRank를 통한 추출적 요약
이번 실습에서는 스팀리뷰 리뷰 데이터를 통해서 임베딩 벡터를 만들고 임베딩 벡터를 가지고 최근 출시한 'P의 거짓'의 디스이즈게임의 뉴스 기사를 요약해보는 작업을 해보겠습니다. 여기서 글로 설명하는것보다 코드를 통해서 어떤 작업들인지 설명하면서 진행해보겠습니다.
스팀 리뷰 데이터로 임베딩 만들기
사전 훈련된 임베딩을 가져와 사용할 수 있지만 저는 텍스트랭크를 수행할 데이터가 게임과 관련된 뉴스기사라는 점에서 스팀 리뷰에 데이터를 통해 임베딩을 진행하려합니다. 스팀리뷰를 다운로드하고 그 중 리뷰 텍스트를 가져와서 Okt를 사용해 토큰화를 진행하였습니다. 이후 gensim의 Word2Vec을 사용하여 임베딩 크기가 100인 Word2Vec 모델을 만들어 워드 임베딩을 가져와 steam_dict이라는 사전으로 정의하였습니다.
import pandas as pd
import gensim
import urllib
from konlpy.tag import Okt
from tqdm import tqdm
from sklearn.metrics.pairwise import cosine_similarity
from newspaper import Article
import re
import numpy as np
import networkx as nx
# 스코어 시각화시 임포트
# import matplotlib.pyplot as plt
#스팀 리뷰 다운로드
urllib.request.urlretrieve("https://raw.githubusercontent.com/bab2min/corpus/master/sentiment/steam.txt", filename="steam.txt")
train_data = pd.read_table('steam.txt', names=['label', 'reviews'])
train_data = train_data.dropna(how = 'any') # Null 값이 존재하는 행 제거
train_data['reviews'] = train_data['reviews'].str.replace("[^ㄱ-ㅎㅏ-ㅣ가-힣 ]","")
# 불용어 정의
stopwords = ['의','가','이','은','들','는','좀','잘','걍','과','도','를','으로','자','에','와','한','하다']
# 형태소 분석기 OKT를 사용한 토큰화 작업 (다소 시간 소요)
okt = Okt()
tokenized_data = []
for sentence in tqdm(train_data['reviews']):
tokenized_sentence = okt.morphs(sentence, stem=True) # 토큰화
stopwords_removed_sentence = [word for word in tokenized_sentence if not word in stopwords] # 불용어 제거
tokenized_data.append(stopwords_removed_sentence)
model = gensim.models.Word2Vec(sentences = tokenized_data, vector_size = 100, window = 5, min_count = 5, workers = 4, sg = 0)
word_vectors = model.wv
word_vectors.save('naverWord2Vec.wordvectors')
wv = gensim.models.KeyedVectors.load('naverWord2Vec.wordvectors', mmap='r')
steam_dict = dict()
for word in wv.key_to_index.keys():
steam_dict[word]=wv[word]
데이터 가져오기
newspaper 모듈을 사용하여 뉴스 기사의 텍스트만 가져오는 작업을 진행하였습니다. 줄바꿈 기호가 포함되어 가져왔기 떄문에 제거하는 작업을 진행하였고, 이번 실습에서는 문장 단위로 데이터를 볼 것이기 때문에 온점을 기준으로 문서를 구분지어 문장 데이터를 만들었습니다.
url = 'https://m.thisisgame.com/webzine/nboard/16/?n=176290'
article = Article(url, language='ko')
article.download()
article.parse()
sentence = article.text
sentences = re.sub('[\n]', "", sentence).split('.')
sentences = [s.strip() for s in sentences]
토크나이징
토크나이징을 진행하는 과정에서 전처리를 같이 진행하였습니다. 워드 임베딩을 만들떄의 전처리와 동일한 작업을 진행하였습니다. 문장의 토큰화는 okt모듈을 사용하여 진행하였습니다.
def preprocess_sentence(sentence):
sentence = re.sub('[^ㄱ-ㅎㅏ-ㅣ가-힣 ]' , '' , sentence)
return [word for word in okt.morphs(sentence) if word not in stopwords and word]
def preprocess_sentences(sentences):
return [preprocess_sentence(sentence) for sentence in sentences]
tokenized_sentences = preprocess_sentences(sentences)
기사 문장 임베딩
이 부분에 대해서 이해하실 필요가 있습니다. 여기서는 문장에 대한 문장 벡터를 구할겁니다. 그런데 문장 벡터를 구하는 방법이 아주 다양합니다. 이번에는 가장 간단한 문장의 단어 벡터의 평균으로 문장 벡터를 구하는 과정을 진행했습니다. 그런데 우리가 만든 스팀 리뷰의 문장 임베딩에 없는 단어가 존재할 수 있습니다. 이 경우 OOV문제가 발생하기 때문에 제로벡터를 반환해주게끔 해줄 필요가 있습니다. 그 과정은 아래와 같습니다. 우리가 학습시킨 스팀리뷰의 임베딩을 크기 100으로 설정했기 떄문에 여기서도 100으로 설정하여 기사에 대한 임베딩을 진행해 줍니다.
embedding_dim = 100
zero_vector = np.zeros(embedding_dim)
def calcu_sentence_vector(sentence):
if len(sentence) != 0:
return sum([steam_dict.get(word, zero_vector) for word in sentence])/len(sentence)
else:
return zero_vector
def sentences_to_vetocrs(sentences):
return [calcu_sentence_vector(sentence) for sentence in sentences]
embedding_sentence = sentences_to_vetocrs(tokenized_sentences)
코사인 유사도 + 문장간 관계 시각화
문장 임베딩을 통해 기사를 구성하고 있는 문장들의 벡터를 만들었으니 이제 이를 통해서 문장간 유사도를 구해주는 작업을 진행합니다. 이번 실습의 기사에서 129개의 문장이 사용됐기 때문에 129*129 크기를 같는 문장간 유사도 행렬을 얻습니다. 추가적으로 문장간의 유사도를 통한 시각화를 할 수 있는데 129개의 문장을 시각화하는 것은 알아복 힘들기 때문에 진행하지 않았지만 코드는 같이 첨부합니다. 문장의 수가 10개 이하라면 한 번 진행해보시는걸 추천드립니다.
def similarity_matrix(sentence_embedding):
sim_mat = np.zeros([len(sentence_embedding), len(sentence_embedding)])
for i in range(len(sentence_embedding)):
for j in range(len(sentence_embedding)):
sim_mat[i][j] = cosine_similarity(sentence_embedding[i].reshape(1, embedding_dim),
sentence_embedding[j].reshape(1,embedding_dim))[0,0]
return sim_mat
simMatrix = similarity_matrix(embedding_sentence)
# 129개의 문장의 관계를 비교를 시각화는 복잡해서 알아볼 수가 없어 생략
# def draw_graphs(sim_matrix):
# nx_graph = nx.from_numpy_array(sim_matrix)
# plt.figure(figsize=(10, 10))
# pos = nx.spring_layout(nx_graph)
# nx.draw(nx_graph, with_labels=True, font_weight='bold')
# nx.draw_networkx_edge_labels(nx_graph,pos,font_color='red')
# plt.show()
# draw_graphs(simMatrix)
점수 계산 요약 문장 추출
우리가 구한 문장벡터간 유사도를 통해서 텍스트랭크의 점수를 구합니다. 이후에는 정렬을 통해서 상위 점수를 받은 문장들을 출력하면 되겠습니다.
def calculate_score(sim_matrix):
nx_graph = nx.from_numpy_array(sim_matrix)
scores = nx.pagerank(nx_graph)
return scores
scores = calculate_score(simMatrix)
def ranked_sentences(sentences, scores, n=5):
top_scores = sorted(((scores[i],s) for i,s in enumerate(sentences)), reverse=True)
top_n_sentences = [sentence for score,sentence in top_scores[:n]]
return (top_n_sentences)
summary = ranked_sentences(sentences, scores)
최종 결과 (기사 요약 문장)
결과를 보니 게임 진행하는 방식중 전투방식에 대한 문장들이 핵심 문장들로 추출된 것을 볼 수 있습니다. 실제로 기사 링크를 따라서 들어가보면 'P의 거짓'을 플레이 방법의 설명이 주를 이루고 있는 것을 확인할 수 있습니다.
0 : 게임이 진행될수록 보스의 공격이 강력하기에 조력자가 빠르게 사망하긴 하지만, 조력자의 성능을 강화시키는 아이템을 통해 상쇄할 수 있기도 합니다
1 : 따라서 이상적인 그림은 모든 공격을 퍼펙트 가드로 쳐내고 반격하며 그로기 상태를 유발하는 것이라고 할 수 있습니다
2 : 처음에는 적의 패턴을 파악하고 피하는 데 급급하지만, 자연스럽게 적의 패턴을 익히고 주인공을 강화시키면서 들어오면 공격은 모조리 쳐내고, 막아내기 어려운 공격은 회피를 사용하다가 그로기에 빠트릴 수 있는 기회가 오는 순간 강력한 공격을 꽂아 넣는 한 방을 노리게 됩니다
3 : 어떤 무기 조합과 리전 암을 사용할 것인가어떤 공격을 피하고 막으며 그로기를 만들어낼 것이냐는 플레이어의 선택입니다
4 : 어떤 날과 자루를 조합할 것이냐, 페이블 아츠는 무엇을 활용할 것이냐, 리전 암은 무엇을 사용할 것이냐, 얻은 쿼츠를 바탕으로 주인공의 어떤 능력을 강화할 것이냐, 특정한 적의 어떤 공격을 막고, 어떤 공격을 회피할 것이냐, 무엇을 사용해 그로기 상태를 터트릴 것이냐를 직접 몸으로 익히며 자신만의 방식으로 맵을 탐사하며 적을 쓰러트리는 것이 <P의 거짓>의 재미입니다
1-2) BERTSum 논문 톺아보기
앞서 텍스트랭크를 통해 문서를 대표할 수 있는 문장을 요약하는 추출적 요약을 진행해보았습니다. 이런 추출적 요약 방법안에서도 다양함이 존재합니다. 이번에는 그 중 BERT기반의 요약 모델은 BertSum 논문을 살펴보고 어떻게 구성했는지 알아보겠습니다.
바닐라 BERT모델에 대해서 이해하셨다면 [CLS]토큰에 대한 의미를 아실겁니다. 문장의 분류문제를 풀기 위해 사용하는 토큰이었습니다. 그리고 [SEP]토큰의 하나의 문장이 끝났을때 사용하던 토큰이었습니다. BertSum은 이 토큰을 활용하여 임베딩 과정에서 추가적인 작업을 진행합니다. 'Encoding Multiple Sentences', 'Interval Segment Embeddings' 두 가지 작업을 추가로 진행하게 됩니다.
Encoding Multiple Sentences'은 언급한 두 토큰을 각 문장의 처음과 끝에 넣어주는 방식으로 진행하게 됩니다. BERT에 대해서 설명할때 잠깐 언급한 바가 있었는데 그 부분이 여기서 등장합니다. [SEP]토큰의 경우 문장의 끝에 나온다고 보통 설명을 하는데 BERT모델에 대해서 알아볼때 사실 문장이 아닌 문서라고 보는게 정확하다고 이야기한 부분이 있습니다. BertSum에서는 그 부분을 좀 더 세분화하여 진짜로 문장마다 [CLS]토큰과 [SEP]토큰을 부여하는 방식으로 수정하였습니다.
Interval Segment Embeddings은 문장을 구분하기 위한 작업입니다. 이전에 BERT에서 언급했던 포지셔널 임베딩과 유사한 작업이라고 생각하시면 됩니다. 그런데 포지셔널 임베딩처럼 짝수와 활수 인덱스에 코사인, 사인을 적용하는게 아니라 그냥 두 개의 값을 통해 홀수와 짝수를 구분하게끔 임베딩 작업을 진행합니다. 아래 그림에서는 E_A와 E_B가 그값이 되겠습니다. 이를 통해 BERT모델에 데이터를 입력하게되고 결과를 받습니다. 결과로 나온 T_n은 각 문장에 대한 벡터로 생각하시면 됩니다. 즉 아래 그림에서는 [CLS] sent one [SEP]가 T_1입니다.
이렇게 나온 n개의 T(문장 벡터)를 'Summarization Layers' 층을 통과시켜 요약문장으로 사용할지에 대한 여부를 결정하는 작업을 진행하게 됩니다. 해당 층에서는 손실함수로 BinaryCrossEntropy 함수를 사용합니다. 논문에서는 해당 층을 'Simple Classifier', 'Recurrent Neural Network', 'Inter-sentence Transformer'3가지로 구성하여 결과를 보여주었습니다. 이 중에서 'inter-sentence Trasformer'의 성능이 가장 좋았다고 논문에서는 이야기하고 있습니다. 그럼 이 3가지에 대해서 살펴보겠습니다
Simple Classifier
바닐라 BERT의 기본분류에서처럼 최종층에 하나의 선형층을 구성하고 이를 통해 나온결과를 활성화 함수를Sigmoid를 적용한 결과를 얻는 것입니다. σ는 시그모이드 함수의 약자입니다.
Recurrent Neural Network
RNN의 층을 구성한 것인데 여기서 의아한 분들이 있을 수 있습니다. BERT의 탄생 배경을 생각해보면 RNN의 한계점을 극복하기 위해 어텐션의 개념이 등장하였고 이를 통해 RNN을 대체하자는 방법론에서 Transformer와 BERT가 탄생했습니다. 그런데 BertSum에서는 RNN으로 마지막 요약층을 구성했는데 이는 RNN모델의 장점이 Transformer와 결합했을때도 유지가 되는 부분이 있기 때문이라고 언급합니다.(이 부분은 BertSum 논문이 아닌 다른 논문에서 언급한 부분이니 궁금하신 분은 참고하시면 될것 같습니다.) 이를 바탕으로 BertSum에서는 LSTM을 통해 층을 구성하였고 층의 완정화를 위해 'pergate layer normalization'을 활용하였다고 언급하고 있습니다. 이 부분 역시 다른 논문에서 등장한 방법입니다. 이를 통해 해당 층은 다음과 같은 과정을 거치고 마지막에는 Simple Classifier와 동일하게 Sigmoid를 통해 결과를 얻습니다.
Inter-sentence Transformer
해당 구조에서는 Transformer의 구조를 가져와 사용합니다. 기존의 Transformer에 대해서 생각해보시면 문장을 구성하고 있는 단어벡터들에 대해서 어텐션값을 구하였고 이를 통해 구하였고 이를 통해 단어간의 연관성을 통해 'it'이 어떤 단어를 말하고 있는 것인지에 대해 알 수 있다고 설명한 바 있습니다. 그런데 이 범주를 좀 더 확장 시킨 방법을 여기서 적용합니다. 단어가 아닌 문장에 대한 어텐션을 진행하게 됩니다. 이게 가능한 이유는 앞서 버트 모델을 통해서 얻은 T에 대한 정보는 문장 임베딩이기 때문입니다. h라는 값은 PosEmb(T)라고 논문에서 이야기하는데 이는 문장의 포지션을 나타내는 것이라 생각하시면 됩니다. MHAtt는 멀티헤드어텐션을 뜻합니다. l은 transformer를 구성하는 층이라고 생각하시면 됩니다. (이부분은 Transformer에 대한 이해가 필요한 부분이니 모르시는 분은 Transformer에 대해서 학습해주세요)
그리고 이후에는 다른 층 구성과 같이 시그모이드 함수를 활성화함수로 선형층을 구성하여 결과를 예측합니다. 이 과정에서 L은 최종적으로 transformer를 거쳐서 나온 마지막 층인데 논문에서 1,2,3으로 시도를 해보았고 2에서 가장 좋은 성능이 나왔다고 합니다.
'머신러닝&딥러닝 > NLP' 카테고리의 다른 글
NLP - STS, NLI Downstream 구현(SBERT) (1) | 2023.10.23 |
---|---|
NLP - RoBERTa(Robustly optimized BERT approach) 논문 톺아보기 (0) | 2023.10.17 |
NLP - 텍스트랭크(TextRank) (1) | 2023.10.16 |
모바일 4가지 게임 리뷰 토픽 모델링 분석(6) (0) | 2023.10.12 |
모바일 4가지 게임 리뷰 토픽 모델링 분석(5) (2) | 2023.10.12 |