본문 바로가기
[파이썬]/딥러닝

[딥러닝] 자연어 처리를 위한 말뭉치와 이를 활용한 단어 사전 구축

by sung min_Kim 2024. 1. 9.


 이번 글에서는 "자연어 처리(NLP)의 핵심 개념인 말뭉치와 이를 활용한 단어 사전 구축"에 대한 내용들을 살펴보려 한다.
차례와 사용 툴 및 라이브러리는 아래와 같다.

 [차례]
 첫 번째, 자연어 처리(Natural Language Processing, NLP) 개요
 두 번째, 말뭉의 이해와 활용
 세 번째, 단어 사전 구축
 
 [사용 툴]

- Jupyter notebook(웹 기반 대화형 코딩 환경

 [사용 라이브러리 및 모듈]

  • 딥러닝 라이브러리 및 모델: TensorFlow
    - tensorflow.keras.preprocessing.text.Tokenizer: 토큰화 및 숫자 인덱스로 변환
    - tensorflow.keras.preprocessing.sequence.pad_sequences: 시퀀스 패딩

자연어 처리(Natural Language Processing, NLP) 개요

 

 

출처(https://www.freepik.com)


 자연처 처리(NLP)컴퓨터가 사람의 언어를 이해하고 처리하는 기술이다. 컴퓨터가 사람의 언어를 이해하게 만드는 것은 매우 중요하다. 왜냐하면, 이를 통해 컴퓨터는 사람들의 문장을 이해하고, 대화를 나누거나, 텍스트를 분석하고, 의미 있는 정보를 추출하는 등의 작업을 할 수 있게 되기 때문이다.

 예를 들어, 고객 서비스를 담당하는 챗봇이 있다. 이 챗봇이 사람의 질문을 이해하고, 적절한 답변을 할 수 있다면, 고객 서비스의 효율이 크게 향상될 것이다. 이렇게 NLP는 우리의 일상생활과 비즈니스에 많은 영향을 미친다.

 


말뭉치를 활용한 단어 사전

 

 

(출처:국립국어원)

 

말뭉치(Corpus)


 말뭉치는 텍스트 데이터의 집합을 의미하며, 이는 컴퓨터에게 '사람의 언어를 이해하는 방법'을 가르쳐주는 학습 데이터로 사용된다.

 예를 들어, 영화 리뷰 말뭉치를 사용하면 컴퓨터는 각 리뷰가 긍정적인지 부정적인지를 판단하는 방법을 배울 수 있다.

단어 사전(Vocabulary)


 단어 사전은 말뭉치 안에 있는 모든 고유한 단어와 해당 단어에 부여된 고유한 숫자 ID집합을 의미한다. 이 사전은 컴퓨터가 텍스트 데이터를 이해하고 처리할 수 있게 도와준다. 왜냐하면 컴퓨터는 텍스트보다는 숫자를 더 잘 이해하기 때문이다.

 따라서 "이 영화는 재밌었었다"와 같은 문장을 컴퓨터가 이해할 수 있는 형태, 예를 들어 [0, 1, 2, 3]과 같은 숫자 배열로 변환해 주는 작업이 필요한데, 이때 사용되는 것이 단어 사전이다. 이렇게 변환된 데이터는 순환신경망(RNN) 등의 머신러닝 모델에 입력으로 사용될 수 있다.

 결국, 말뭉치와 단어 사전은 자연어 처리(NLP)에서 핵심적인 역할을 한다. 이 두 가지 요소를 이해하는 것은 컴퓨터가 사람의 언어를 이해하는 방법을 배우는 첫걸음이라고 할 수 있다.

 


단어 사전 구축

 

 

 말뭉치는 자연어 처리에 사용되는 큰 텍스트 데이터 집합을 의미한다. 반면, 단어 사전은 말뭉치에 등장하는 모든 고유한 단어들과 그에 해당하는 숫자 ID의 집합을 말한다. 단어 사전은 말뭉치에서 추출되며, 이는 자연어 처리 과정에서 텍스트 데이터를 컴퓨터가 이해할 수 있는 형태(숫자)로 변환하는 데 사용된다.

0. 라이브러리 정의 및 데이터 불러오기


 단어 사전을 만들기 위해 케라스에서 제공하는 'Tokenizer', 'pad_sequences' 함수를 호출한다.

  • Tokenizer
    - 텍스트 데이터를 토큰화하는 과정을 수행한다. 이는 텍스트를 머신러닝 모델이 이해할 수 있는 형태로 변환하는 첫 단계이다. 토큰화 과정에서는 문장을 개별 단어 또는 토큰으로 나눈다. 이러한 토큰들은 각각 고유한 숫자 ID에 매핑되며, 이 ID들은 나중에 텍스트 데이터를 숫자 형태로 변환하는 데 사용된다.

  • pad_sequences
    - 모든 시퀀스의 길이를 동일하게 맞추는 과정을 수행한다. 다양한 길이의 문장을 입력으로 받아 처리하는 머신러닝 모델에서는, 모든 입력 데이터가 동일한 길이를 가져야 한다. 따라서 길이가 짧은 문장에는 패딩을 추가하고, 길이가 긴 문장은 잘라내어 모든 문장의 길이를 동일하게 맞춰준다.

 사용데이터는 챗봇에 적용할 수 있는 질문과 답변으로 이루어진 규칙기반 데이터이다.

import tensorflow as tf
from tensorflow.keras.preprocessing.text import Tokenizer         # 토큰화
from tensorflow.keras.preprocessing.sequence import pad_sequences # 정규화(시퀀스 길이 동일)

questions = ["질문들"]

answers = ["답변들"]

len(questions), len(answers)

 

1. 데이터 전처리
1.1 토큰화


 데이터 전처리의 첫 단계로, 토큰화를 수행한다. 토큰화는 텍스트 데이터를 개별 단어나 표현으로 분리하는 과정이다. 이 과정은 자연어 처리에서 매우 중요한데, 이를 통해 컴퓨터가 텍스트 데이터를 이해하고 분석할 수 있게 된다.

 텐서 플로우의 Tokenizer 클래스를 사용하여 토큰화를 수행한다. 이 클래스는 말뭉치의 모든 텍스트를 공백, 마침표, 쉼표 등을 기준으로 토큰화하는 기능을 제공한다.

 또한, 각 토큰에 고유한 숫자 ID를 부여하며, 이 ID를 활용하여 텍스트 데이터를 숫자 형태로 변환할 수 있다.

tokenizer = Tokenizer()
tokenizer


 이렇게 생성한 토크나이저 객체는 훈련 데이터를 기반으로 토큰화를 수행하며, 이후에는 새로운 텍스트 데이터를 동일한 방식으로 토큰화하는 데 사용될 수 있다. 이를 통해 일관된 방식으로 데이터를 처리하고, 머신러닝 모델을 학습 및 테스트할 수 있다.

1.2 단어 사전 구축


 토크나이저 객체를 생성한 후에는 질문과 답변 데이터를 합쳐 전체 텍스트 데이터를 구성한다. 이 데이터를 기반으로 단어 사전을 구축하게 된다. 단어 사전이란 텍스트 데이터에 등장하는 모든 고유한 단어와 그에 해당하는 숫자 ID를 매핑한 것을 의미한다.

 토크나이저는 기본적으로 띄어쓰기를 기준으로 단어를 인식한다. 즉, 띄어쓰기가 없이 연결된 텍스트는 하나의 단어로 인식하게 된다. 예를 들어, "아버지가방에들어가신다"는 하나의 단어로 인식되며, 이에 하나의 고유한 ID가 부여된다.

 'fit_on_texts' 함수는 텍스트 데이터에서 단어를 추출하고 각 단어에 순차적인 인덱스를 부여하는 역할을 한다. 이 함수를 호출하면, 토크나이저 객체는 입력된 텍스트 데이터를 기반으로 단어 사전을 구축한다.

tokenizer.fit_on_texts(questions + answers)

 

  1.3 단어 사전 확인


 토크나이저 객체의 'word_index' 속성을 통해 단어 사전을 확인할 수 있다. 이 속성은 단어를 키(key)로, 해당 단어에 부여된 인덱스를 값(value)으로 가지는 딕셔너리이다. 이를 통해 각 단어에 어떤 인덱스가 부여되었는지 확인할 수 있다.

 또한 len 함수를 사용하여 단어 사전에 등록된 단어의 개수를 확인할 수 있다. 이는 토크나이저가 텍스트 데이터에서 몇 개의 고유한 단어를 인식하였는지를 나타낸다.

print("단어 갯수 : ", len(tokenizer.word_index))

tokenizer.word_index

 

1.4 단어 사이즈 설정


 신경망에서 사용할 단어 사이즈(vocab_size)를 설정한다. 이 단어 사이즈는 토크나이저가 구축한 단어 사전의 크기보다 하나 더 크게 설정한다. 이는 인덱스 0을 사용하지 않고, 인덱스 '1'부터 단어 인덱싱을 시작하기 때문이다.

 이 'vocab_size'는 신경망이 처리할 수 있는 고유한 단어의 개수를 나타내며, 이는 신경망의 입력 차원과 임베딩 벡터의 크기를 결정하는 데 사용된다.

vocab_size = len(tokenizer.word_index) + 1
vocab_size

 

1.5 텍스트를 숫자 시퀀스로 변환


 토크나이저 객체의 'texts_to_sequences' 함수를 사용하면, 입력에 대한 텍스트 데이터를 단어 사전의 인덱스 번호로 변환할 수 있다. 이 함수는 주어진 텍스트 데이터를 토크나이저가 구축한 단어 사전을 기반으로 숫자 시퀀스로 변환한다. 이렇게 변환된 숫자 시퀀스는 신경망의 입력으로 사용된다.

questions_sequences = tokenizer.texts_to_sequences(questions)

print(len(questions_sequences))
questions_sequences

변환된 시퀀스의 갯수 &시퀀스

 

1.6 시퀀스 패딩


 텍스트 데이터를 숫자 시퀀스로 변환한 후에는, 모든 시퀀스가 동일한 길이를 가지도록 패딩을 추가한다. 이 과정을 '패딩'이라고 부르며, 이는 신경망에 동일한 길이의 시퀀스를 입력하기 위한 전처리 과정이다.

 텐서 플로우의 'pad_sequences' 함수를 사용하여 질문 시퀀스에 패딩을 추가한다. 이 함수는 주어진 시퀀스의 길이를 동일하게 맞추기 위해 필요한 만큼 '0'을 추가한다. padding의 매개변수를 "post"로 설정하면 패딩이 시퀀스의 뒷부분에, "pre(기본값)"로 설정하면 시퀀스의 앞부분에 추가된다.

 이렇게 패딩된 시퀀스는 이후 신경망의 입력 데이터로 사용된다. 패딩은 모든 입력 데이터가 동일한 길이를 가지도록 하여, 신경망이 일관된 형태의 입력을 받을 수 있게 해 준다.

questions_padded = pad_sequences(questions_sequences, padding="post")

print(len(questions_padded))
questions_padded

시퀀스의 단어 길이 통일


 답변에 대한 데이터도 동일하게 1.5(시퀀스를 숫자로 변환) ~ 1.6(시퀀스 패딩) 전처리 단계를 수행한다.

2. 모델 구축
2.1 신경망 생성
model = tf.keras.Sequential()
model

 

2.2 계층 추가 및 확인


 신경망은 여러 개의 계층(layer)을 쌓아서 구성된다. 각 계층은 입력 데이터를 받아서 처리한 후, 그 결과를 다음 계층에 전달한다. 이런 방식으로 신경망은 입력 데이터를 최종 출력까지 반환한다.

 이 계층은 단어 임베딩 계층, SimpleRNN 계층, RepeatVector 계층, 다시 SimpleRNN 계층, 그리고 TimeDistributed 계층을 순서대로 추가하며 신경망을 구성한다.

  • Embedding 계층
    - 이 계층은 단어를 고정된 크기의 벡터로 변환하는 역할을 한다. 이 벡터는 단어 간의 의미적인 관계를 반영하며, 신경망이 이해할 수 있는 형태로 단어를 표현한다.

  • 첫 번째 SimpleRNN 계층
    - 이 계층은 순환 신경망(RNN)을 구성한다. RNN은 시퀀스의 각 요소를 순차적으로 처리하며, 각 단계에서 이전 단계의 정보를 기억한다.

  • RepeatVector 계층
    - 이 계층은 이전 계층의 결과를 특정 횟수만큼 반복하여 다음 계층에 전달하는 역할을 수행한다. 이 예에서는 이전 계층의 출력 결과를 answers_padded.shape[1]'만큼, 즉 입력 데이터의 특성(열)의 수만큼 반복하여 다음 계층에 입력으로 제공한다.

  • 두 번째 SimpleRNN 계층
    - 이 계층에서는 'return_sequences=True'를 설정하여, 시퀀스의 각 요소를 처리할 때마다 중간 결과를 출력한다. 즉, 시퀀스의 각 단어를 처리한 후의 상태를 모두 반환한다. 이는 다음 계층이 시퀀스의 전체 정보를 활용할 수 있도록 해 준다. 반대로 'return_sequences=False'로 설정하면, RNN 계층은 시퀀스의 마지막 요소를 처리한 후의 상태만을 반환한다

  • TimeDistributed 계층
    - 이 계층은 시퀀스의 각 요소에 독립적으로 작동하는 Dense 계층을 적용한다. 이는 시퀀스 데이터를 처리할 때, 각 데이터 요소를 개별적으로 다룰 수 있게 해 준다. 이 계층은 동일한 Dense 계층을 시퀀스의 모든 단계에 걸쳐 적용함으로써, 각 단계에서 동일한 가중치를 사용한다. 이렇게 해서, 모델은 전체 시퀀스에 대한 정보를 고려하면서도 각 단계를 개별적으로 처리할 수 있다. TimeDistributed 계층은 주로 퀀스 데이터의 예측을 담당하며, 모델의 최종 출력을 감싸는 역할을 한다.

 

model.add(tf.keras.layers.Embedding(vocab_size, 64, input_length=questions_padded.shape[1]))

model.add(tf.keras.layers.SimpleRNN(128, activation="relu"))

model.add(tf.keras.layers.RepeatVector(answers_padded.shape[1]))

model.add(tf.keras.layers.SimpleRNN(128, activation="relu", return_sequences=True))

model.add(tf.keras.layers.TimeDistributed(tf.keras.layers.Dense(vocab_size, 
                                                                activation="softmax")))
                                                                
model.summary()

 


 이렇게 구성된 신경망은 텍스트 데이터를 입력받아서 처리하고, 그 결과를 단어 사전의 인덱스로 출력한다. 이렇게 구성된 신경망은 텍스트 데이터를 처리하는 데 적합하며, 특히 자연어 처리나 시퀀스 예측 같은 문제를 해결하는 데 사용된다.

 참고로 '각 시간 단계'라는 표현은 시퀀스 데이터의 각 요소를 의미한다. 시퀀스 데이터는 순차적인 정보를 담고 있으며, 이러한 정보의 각 단계를 '시간 단계'라고 부른다. 예를 들어, 단어로 이루어진 문장이나, 일련의 시계열 데이터 등이 있다.


2.3 모델 설정


 이 단계에서는 모델의 학습 방법을 설정한다.

  • 옵티마이저(optimizer)
    - 가중치 업데이트는 'Adam' 옵티마이저를 사용하여 경사하강법에 따라 진행된다.

  • 손실 함수(loss)
    - 손실 함수로는 'sparse_categorical_crossentropy'을 선택하였는데, 이는 정수형 데이터의 다중 분류 문제 처리에 적합하기 때문이다. 이 함수는 모델의 예측이 실제 값과 얼마나 잘 일치하는지를 측정하며, 학습 과정에서 이 손실 값을 최소화하는 것이 목표이다.

  • 성능 평가 지표(metrics)
    - 성능 평가 지표로는 '정확도(accuracy)'를 선택하였으며, 이는 모델이 올바르게 분류한 샘플의 비율을 나타낸다.

 이러한 설정을 마치면 모델의 학습(훈련)이 가능한 상태가 된다.

model.compile(optimizer="adam", 
              loss="sparse_categorical_crossentropy",
              metrics=["accuracy"])

 

3. 모델 훈련(학습)

 
 모델은 정규화된 훈련 데이터셋을 활용하여 총 100회(epoch)의 훈련(학습)을 수행한다. 'batch_size=64'는 한 번의 배치 과정에서 학습할 데이터의 수를 64로 지정한 것을 의미한다. 즉, 이 설정에 따라 모델은 한 번에 64개의 데이터 샘플을 사용하여 학습을 진행하게 된다.

model.fit(questions_padded, answers_padded, epochs=100, 
          batch_size=64, verbose=1)


 출력 결과, 이 모델은 0.18대의 낮은 손실률과 0.9 이상의 높은 정확도를 가진 모델로 보인다.

4. 답변 함수 만들기


 학습된 모델을 기반으로, 사용자의 새로운 질문에 대해 응답을 생성하는 함수를 생성하였다.

 이 코드는 다음과 같은 순서로 작동한다:

  1. 먼저, 사용자의 질문을 토크나이저를 통해 단어 사전의 인덱스로 변환한다.

  2. 그 후, 변환된 질문의 길이에 패딩을 추가함으로써 훈련 데이터와 동일하게 맞춰준다.

  3. 이렇게 정규화된 질문에 대해, 모델을 이용해 응답을 예측한다.

  4. 예측 결과 중에서 가장 확률이 높은 값을 'argmax' 함수를 이용하여 선택하고,

  5. 이 인덱스를 다시 토크나이저를 사용하여 실제 텍스트로 변환한다.

  6. 마지막으로, 변환된 텍스트 즉, 모델이 생성한 응답을 반환한다.

 결국 이 함수는 사용자의 질문을 받아, 모델을 통해 응답을 생성하고, 이를 실제 텍스트로 변환해 반환하는 역할을 수행한다.

def get_Generate_Response(model, tokenizer, user_input) : 

    # 전처리1 - 텍스트를 숫자 인덱스로 변환
    input_seq = tokenizer.texts_to_sequences([user_input])
    
    # 전처리2 - 시퀀스 패딩
    padded_seq = pad_sequences(input_seq, padding="post", 
                               maxlen=questions_padded.shape[1])
    
    # 예측
    pred = model.predict(padded_seq)
    
    # 답변으로 가장 적절한 인덱스 선택
    pred_index = tf.argmax(pred, axis=-1).numpy()[0]
    
    # 선택된 인덱스를 텍스트로 변환
    response = tokenizer.sequences_to_texts([pred_index])[0]
 
    # 텍스트(예측값) 반환
    return response

 

5. 질문에 대한 답변 출력


 사용자의 입력이 발생하는 경우, 앞서 생성한 답변 함수를 호출하여 사용자의 질문에 대한 답변을 제공한다.

 이 코드는 다음과 같은 순서로 작동한다:

  1. 사용자로부터 새로운 질문을 입력받는다.

  2. 사용자가 "종료"라고 입력하면, 채팅을 종료하며 루프를 빠져나온다.

  3. 'get_Generate_Response' 함수를 호출하여 사용자의 질문에 대한 답변을 생성한다.

  4. 생성된 답변을 출력한다.

이 과정이 무한히 반복되므로, 사용자는 원하는 만큼 질문을 계속해서 입력할 수 있다.

while True :

    # 질문 입력
    user_input = input("사용자 : ")

    # 채팅 종료
    if user_input == "종료" :
        print("채팅을 종료합니다. ------------------")
        break

    # 질문에 대한 답변 생성
    response = get_Generate_Response(model, tokenizer, user_input)

    # 답변 출력
    print("챗봇 : ", response)