09_DL(Deep_Learning)

28_문답데이터_감정분류

chuuvelop 2025. 5. 8. 22:35
728x90
문답데이터_감정분류

 

# 챗봇
# Q -> 분류 -> DB검색 -> 개체명 인식(한국어의 패턴 인식) -> A

 

챗봇 문답 데이터 감정 분류 모델

  • 문장을 감정 클래스별로 분류하는 CNN 모델 구현
  • 텍스트 데이터의 임베딩 품질만 괜찮다면 자연어 분류에도 CNN이 좋은 성능을 낼 수 있음
  • 컴퓨터는 임베딩된 벡터로 표현 가능한 대상이라면 특징을 추출하도록 CNN 모델을 학습할 수 있음
  • 데이터셋 구조
    • Q(질문)
    • A(답변)
    • label(감정)
      • 0 : 일상다반사
      • 1 : 이별(부정)
      • 2 : 사랑(긍정)

 

import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow.keras import preprocessing
from tensorflow.keras.models import Model
from tensorflow.keras.layers import (Input, Embedding, Dense, Dropout, Conv1D, GlobalMaxPool1D, concatenate)

 

데이터 확인

data = pd.read_csv("./data/ChatbotData.csv", delimiter = ",")
data.head()

 

 

# 코사인 유사도로 유사한 질문의 답변이 나오는 구 방식

 

data.shape
(11823, 3)

 

 

data.isna().sum()
Q        0
A        0
label    0
dtype: int64

 

 

data[data["label"] == 1]

 

 

 

data[data["label"] == 2]

 

 

data["label"].value_counts()
label
0    5290
1    3570
2    2963
Name: count, dtype: int64

 

 

데이터 전처리

features = data["Q"].tolist()
labels = data["label"].tolist()

 

# 단어 인덱스 시퀀스 벡터
corpus = [preprocessing.text.text_to_word_sequence(text) for text in features]

 

features[0]
'12시 땡!'

 

corpus[0]
['12시', '땡']

 

tokenizer = preprocessing.text.Tokenizer()
tokenizer.fit_on_texts(corpus)
sequences = tokenizer.texts_to_sequences(corpus)

 

sequences[0]
[4646, 4647]

 

word_index = tokenizer.word_index
# 단어장
word_index
{'너무': 1,
 '좋아하는': 2,
 '거': 3,
 '싶어': 4,
 '같아': 5,
 '안': 6,
 '나': 7,
 '좀': 8,
 '사람': 9,
 '내가': 10,
 '싶다': 11,
 '어떻게': 12,
 '썸': 13,
 '왜': 14,
 '내': 15,
 '사람이': 16,
 ...

 

 

lengths = np.array([len(i) for i in sequences])

 

lengths.mean(), np.median(lengths), lengths.min(), lengths.max()
(3.590290112492599, 3.0, 1, 15)

 

# 단어 시퀀스 벡터 크기
MAX_SEQ_LEN = 15
padded_seqs = preprocessing.sequence.pad_sequences(sequences, maxlen = MAX_SEQ_LEN,
                                                   padding = "post")
# 그리스 프로크루스테스의 침대

 

padded_seqs[0]
array([4646, 4647,    0,    0,    0,    0,    0,    0,    0,    0,    0,
          0,    0,    0,    0])

 

 

데이터 분할

# 학습용 검증용 테스트용 데이터셋 생성
# 학습 : 검증 : 테스트 = 7 : 2 : 1
ds = tf.data.Dataset.from_tensor_slices((padded_seqs, labels)) # (독립변수, 정답값) 묶어서 텐서로 만들어줌
# ds 섞어줌
ds = ds.shuffle(len(features))
train_size = int(len(padded_seqs) * 0.7)
val_size = int(len(padded_seqs) * 0.2)
test_size = int(len(padded_seqs) * 0.1)
train_ds = ds.take(train_size).batch(20)
val_ds = ds.skip(train_size).take(val_size).batch(20)
test_ds = ds.skip(train_size + val_size).take(test_size).batch(20)

 

 

모델 구성

# 하이퍼 파라미터 설정
dropout_prob = 0.5
EMB_SIZE = 128
EPOCH = 5
VOCAB_SIZE = len(word_index) + 1  #전체 단어 수
# CNN 모델 정의
input_layer = Input(shape = (MAX_SEQ_LEN,))
embedding_layer = Embedding(VOCAB_SIZE, EMB_SIZE)(input_layer)
dropout_emb = Dropout(rate = dropout_prob)(embedding_layer)

conv1 = Conv1D(
    filters = 128, # 유닛수
    kernel_size = 3,
    padding = "valid",
    activation = tf.nn.relu
)(dropout_emb)
pool1 = GlobalMaxPool1D()(conv1)

conv2 = Conv1D(
    filters = 128,
    kernel_size = 4,
    padding = "valid",
    activation = tf.nn.relu
)(dropout_emb)
pool2 = GlobalMaxPool1D()(conv2)

conv3 = Conv1D(
    filters = 128,
    kernel_size = 5,
    padding = "valid",
    activation = tf.nn.relu
)(dropout_emb)
pool3 = GlobalMaxPool1D()(conv3)

# 3, 4, 5-gram 이후 합치기
concat = concatenate([pool1, pool2, pool3])

hidden = Dense(128, activation = tf.nn.relu)(concat)
dropout_hidden = Dropout(rate = dropout_prob)(hidden)
predictions = Dense(3, activation = tf.nn.softmax)(dropout_hidden)

 

  • GlobalMaxPooling
    • 주로 합성곱 신경망에서 사용되는 풀링 기법 중 하나로, 네트워크 차원을 줄이고 중요한 특징을 추출하는 데 도움을 줌
    • 입력 데이터의 전체 공간적 차원에서 최대값을 추출하는 풀링
      • 특정 feature map 에서 가장 강한 활성화 값을 선택하여 다음 층으로 전달
    • 특징
      • 차원 축소 : 입력 특성 맵의 공간적 차원을 완전히 제거하고, 각 특성앱당 하나의 값을 출력
      • 파라미터 없음
      • 과대적합 방지: 차원을 줄임으로써 모델의 복잡성을 감소시켜 과대적합을 방지할 수 있음
    • 장점
      • 전역적인 특징 추출 : 전체 특성 맵에서 가장 중요한 특징을 추출하여 전역적인 정보를 캡처
      • 유연성 : 다양한 입력 크기에 대해 동일한 풀링을 적용할 수 있어 유연한 네트워크 설계가 가능

 

# 모델 생성
model = Model(inputs = input_layer, outputs = predictions)

 

model.summary()

 

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

 

 

모델 학습

es_cb = tf.keras.callbacks.EarlyStopping(patience = 4, restore_best_weights = True)
model.fit(train_ds, validation_data = val_ds, epochs = EPOCH, callbacks = [es_cb])
Epoch 1/5
414/414 ━━━━━━━━━━━━━━━━━━━━ 9s 18ms/step - accuracy: 0.5118 - loss: 0.9821 - val_accuracy: 0.8160 - val_loss: 0.5143
Epoch 2/5
414/414 ━━━━━━━━━━━━━━━━━━━━ 7s 18ms/step - accuracy: 0.7972 - loss: 0.5272 - val_accuracy: 0.9150 - val_loss: 0.2854
Epoch 3/5
414/414 ━━━━━━━━━━━━━━━━━━━━ 7s 18ms/step - accuracy: 0.8908 - loss: 0.3296 - val_accuracy: 0.9577 - val_loss: 0.1561
Epoch 4/5
414/414 ━━━━━━━━━━━━━━━━━━━━ 7s 18ms/step - accuracy: 0.9431 - loss: 0.1845 - val_accuracy: 0.9738 - val_loss: 0.0787
Epoch 5/5
414/414 ━━━━━━━━━━━━━━━━━━━━ 7s 18ms/step - accuracy: 0.9603 - loss: 0.1360 - val_accuracy: 0.9814 - val_loss: 0.0634
<keras.src.callbacks.history.History at 0x1e194ce90d0>

 

 

모델 평가

model.evaluate(test_ds)
60/60 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - accuracy: 0.9751 - loss: 0.0758
[0.08155106008052826, 0.9746192693710327]

 

 

train_size + val_size
10640

 

 

corpus[10650]
['여자친구를', '믿는데', '가끔', '의심이', '가']

 

labels[10650]
2

 

model.predict(padded_seqs[[10650]])
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 129ms/step
array([[0.00201458, 0.00318222, 0.99480313]], dtype=float32)

 

 

corpus[10651]
['여자친구에게', '고민이나', '속사정을', '어디까지', '얘기해']

 

labels[10651]
2

 

model.predict(padded_seqs[[10651]])
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 31ms/step
array([[4.2591505e-06, 4.3001151e-06, 9.9999142e-01]], dtype=float32)
728x90