TEXT CLASSIFICATION

2024. 7. 11. 21:37카테고리 없음

A. 머신러닝 이용하여 자연어 Classification하기

0. COLAB 한글 깨짐
1. 라이브러리 임포트
2. 파일 읽어오기
3. 영문, 숫자 특수문자 제거
4. 전처리 : Null, 중복 제거
5. Label 분포 확인
6. label 숫자로 인코딩
7. X, Y 분리
8. train set와 test set 분리
9. 머신러닝 모델링
10. 예측해 보기

B. LSTM 모델 이용하여 자연어 Classification하기


1. 라이브러리 임포트
2. 파일 읽어오기
3. 영문, 숫자 특수문자 제거
4. 전처리 : Null, 중복 제거
5. Label 분포 확인
6. label 숫자로 인코딩
7. X, Y 분리
8. train set와 test set 분리
9. 전체 문장에 대해 Tokenizing
10. texts_to_sequences : 문장을 숫자로 나열
11. Padding Sequence
12. LSTM 모델링
13. 예측해 보기

 


 

 

본과정에서는 AI-HUB 감정말뭉치 데이터를 가지고 텍스트 언어 모델을 만들고 감성분류 실습하겠습니다.

첫번째는 감정말뭉치 데이터를 전처리하고, TF-IDF 토큰나이져를 활용하여 토큰화하고 머신러닝으로 모델링 만들고 감성분류 수행합니다.

두번째는 keras 토큰나이져를 활용하여 토큰화하고 일정 크기의 문장으로 맞추기 위해 패딩 및 embedding 레이어를 이용해서 단어를 n차원 밀집벡터를 만들고 train시에 학습되도록 합니다.그리고 나서 LSTM 모델을 이용하여 감성분류를 수행합니다.

 

자연어 처리 순서

전처리 > 토큰나이져(단어를 숫자매핑, 단어사전 만들기) > 문장을 숫자 나열(texts_to_sequences) > 문장 길이 맞게 Padding > Embedding(단어를 의미있는 밀집 Vector 표현) > 모델링

Embedding Explanation with Analogy

  • 게임 속 캐릭터:
    • 여러분이 게임 속 캐릭터를 만들고 있다고 상상해보세요. 이 캐릭터는 다양한 능력을 가지고 있고, 게임을 하면서 점점 더 강해집니다. 이 과정이 캐릭터를 "훈련"시키는 것과 비슷합니다.
  • 단어를 캐릭터로 비유:
    • 이제 단어를 게임 속 캐릭터라고 생각해볼게요. 각 단어는 게임 속 캐릭터처럼 특별한 능력(특성)을 가지고 있습니다. 예를 들어, '고양이'는 점프를 잘하고, '강아지'는 달리기를 잘합니다. 하지만 처음에는 이 능력들이 얼마나 중요한지 잘 모릅니다.

Embedding Process

  1. 초기 상태:
    • 처음에 각 단어는 랜덤한 숫자 벡터로 표현됩니다. 이는 게임 속 캐릭터가 처음에 무작위로 능력치를 가지고 있는 것과 같습니다.
  2. 훈련(학습):
    • 게임을 하면서 캐릭터가 점점 강해지듯이, 단어 임베딩도 학습 과정을 통해 점점 더 의미 있는 벡터로 변합니다. 예를 들어, '고양이'와 '강아지'가 비슷한 문장에서 자주 등장한다면, 학습을 통해 이 두 단어의 벡터가 비슷해집니다.
  3. 피드백:
    • 게임에서 점수를 받으면 캐릭터의 능력이 업데이트되듯이, 임베딩도 문장을 학습하면서 피드백을 받아 업데이트됩니다. 이 피드백은 모델이 얼마나 잘 예측했는지에 따라 다릅니다.
  4. 최종 상태:
    • 훈련이 끝나면, 각 단어는 이제 매우 의미 있는 벡터로 표현됩니다. '고양이'와 '강아지'는 비슷한 벡터를 가지고 있고, '사과'와 '오렌지'도 비슷한 벡터를 가지게 됩니다. 이렇게 하면, 컴퓨터가 단어들 간의 관계를 더 잘 이해하게 됩니다.

 

A. 머신러닝 이용하여 자연어 Classification하기

  • AI-HUB 감정말뭉치 데이터를 가지고
  • RandomForest 모델 학습하여 감정 Classification 모델링해 보겠습니다.

0. COLAB 한글 깨짐

# 1. 실행
!sudo apt-get install -y fonts-nanum
!sudo fc-cache -fv
!rm ~/.cache/matplotlib -rf
import matplotlib.pyplot as plt

plt.rc('font', family='NanumBarunGothic')

# 코랩(Colab)의 런타임을 재시작 합니다.

1. 라이브러리 임포트

import numpy as np
import pandas as pd

2. 파일 읽어오기

# AI-HUB 감성 대화 말뭉치 활용하여 만든 데이터 읽어오기
final_data = pd.read_csv('https://github.com/ohgzone/file1/raw/main/aihub_coupus.csv' )

# 데이터 확인하기
final_data.head()

 

# 총 51,630건
final_data.info()

 

3. 영문, 숫자, 특수문자 제거

# '문장' 컬럼의 내용중에 영문, 특수문자 있는지 확인 : 영문과 특수문자 존재 확인
final_data[final_data['문장'].str.contains('[^가-힣 ]')].values[:10]
# '문장' 컬럼의 내용에서 숫자, 영문자, 특수문자등의 글자는 삭제처리
final_data['문장'] = final_data['문장'].str.replace('[^가-힣 ]','')
# '문장' 컬럼의 내용에서 영문, 특수문자 없음 확인
final_data['문장'][final_data['문장'].str.contains('[^가-힣 ]')].sum()
# 숫자, 영문자, 특수문자 등 제거후 데이터 확인하기.
final_data.head()

 

4. 전처리: Null, 중복 제거

# final_data 어떤 컬럼과 내용으로 되어 있는지 다시 확인
final_data.tail()
# '문장' 컬럼의 내용을 양끝의 빈공간 삭제
final_data['문장'] = final_data['문장'].str.strip()
# 데이터 다시 확인
final_data.tail()
# Null 있는지 확인 : 없음
final_data.isnull().sum()
# 중복 데이터 있는지 확인 : 56건 중복 존재 확인
final_data['문장'].duplicated().sum()
# 중복 데이터 제거
final_data.drop_duplicates(subset=['문장'], inplace=True)
# 기존 51,630건 --> 이후 51,574건 : 56건 중복 삭제 확인
final_data.info()

 

5. Label 분포 확인

# label '감정' 분포 확인 : 총 6개이며, 고루게 분포 확인. 단 기쁨이 약간 부족해 보임
final_data['감정'].value_counts()
# plot Bar차트 그리기
final_data['감정'].value_counts().plot(kind='bar')

 

6. label 숫자로 인코딩

# 라벨와 클래스을 매핑 작업
from sklearn.preprocessing import LabelEncoder

encoder = LabelEncoder()
final_data['감정'] = encoder.fit_transform(final_data['감정'])
encoder.classes_

## Output: array(['기쁨', '당황', '분노', '불안', '상처', '슬픔'], dtype=object)
final_data.tail()

 

7. X, Y 분리

# X, Y 분리
features = final_data['문장'].values
labels = final_data['감정'].values
features.shape, labels.shape

## Output: ((51574,), (51574,))
# features 내용 3개 출력
features[:3]
print('이벤트 문자열 최대 길이 :{}'.format(max(len(l) for l in features)))
print('이벤트 문자열 평균 길이 :{}'.format(sum(map(len, features))/len(features)))

## Output: 이벤트 문자열 최대 길이 :152
## Output: 이벤트 문자열 평균 길이 :33.91709000659247
# 히스토그램을 보면 30~40 부근에 많이 몰려 있음 알수 있다.
plt.hist([len(s) for s in features], bins=50)
plt.xlabel('length of samples')
plt.ylabel('number of samples')
plt.show()

 

8. train set와 test set 분리

from sklearn.model_selection import train_test_split

x_train, x_test, y_train, y_test = train_test_split(features, labels , test_size=0.2, stratify=labels, random_state=41)
x_train.shape, x_test.shape, y_train.shape, y_test.shape
# 샘플확인 , 라벨 확인
# {0: '불안', 1: '분노', 2: '상처', 3: '슬픔', 4: '당황', 5: '기쁨'}

x_train[:2], y_train[:2]
# 말뭉치를 TF-IDF로 변환하기
from sklearn.feature_extraction.text import TfidfVectorizer

tfidf = TfidfVectorizer()
x_train_v = tfidf.fit_transform(x_train)
x_test_v = tfidf.transform(x_test)

 

  • TF (Term Frequency):
    • 특정 문서에서 특정 단어가 얼마나 자주 등장하는지를 나타냅니다.
    • 계산 방법: 단어 빈도 / 문서의 전체 단어 수
  • IDF (Inverse Document Frequency):
    • 특정 단어가 전체 문서 집합에서 얼마나 흔하지 않은지를 나타냅니다.
    • 계산 방법: 로그(전체 문서 수 / 해당 단어가 등장한 문서 수)
  • TF-IDF:
    • TF와 IDF를 곱한 값으로, 단어의 중요도를 계산합니다.
    • 특정 문서에서 자주 등장하면서, 전체 문서 집합에서는 드물게 등장하는 단어에 높은 가중치를 부여합니다.
    • 공식: TF-IDF = TF * IDF

 

# 각 라인의 각 단어에 대한 TF-IDF 값 표현
print(x_train_v)

(a, b) c의 구성 요소

  1. a:
    • 설명: a는 해당 단어가 속한 문서의 인덱스를 나타냅니다.
    • 예시: (0, 42487) 0.34419359480238104에서 0은 첫 번째 문서를 의미합니다.
  2. b:
    • 설명: b는 해당 단어의 인덱스를 나타냅니다. 이 인덱스는 TF-IDF Vectorizer가 단어 사전을 만들 때 각 단어에 부여한 고유한 번호입니다.
    • 예시: (0, 42487) 0.34419359480238104에서 42487은 단어 사전에서 특정 단어의 인덱스입니다.
  3. c:
    • 설명: c는 해당 단어의 TF-IDF 값을 나타냅니다. 이 값은 해당 문서 내에서 해당 단어의 중요도를 나타냅니다.
    • 예시: (0, 42487) 0.34419359480238104에서 0.34419359480238104는 첫 번째 문서에서 인덱스 42487에 해당하는 단어의 TF-IDF 값입니다.

* 한 단어의 TF-IDF 값은 문서마다 다를 수 있음!!

 

9. 머신러닝 모델링

# 학습하는데 Colab에서 4분 소요
from sklearn.ensemble import RandomForestClassifier

rfc = RandomForestClassifier() # 모델 정의
rfc.fit(x_train_v, y_train) # 모델 학습

rfc.score(x_test_v, y_test)

 

10. 예측해 보기

# RandomForest 모델로 예측하기
predict = rfc.predict(x_test_v[:1])
predict, encoder.inverse_transform(predict)

## Output: (array([4]), array(['상처'], dtype=object))

 

 

B. LTSM 모델 이용하여 Classification하기

1. 라이브러리 임포트

import numpy as np
import pandas as pd

2. 파일 읽어오기

# AI-HUB 감성 대화 말뭉치 활용하여 만든 데이터 읽어오기
final_data = pd.read_csv('https://github.com/ohgzone/file1/raw/main/aihub_coupus.csv' )
# 데이터 확인하기
final_data.head()
# 총 51,630건
final_data.info()

 

3. 영문, 숫자 특수문자 제거

# '문장' 컬럼의 내용중에 영문, 특수문자 있는지 확인 : 영문과 특수문자 존재 확인
final_data[final_data['문장'].str.contains('[^가-힣 ]')].values[:10]
# '문장' 컬럼의 내용에서 숫자, 영문자, 특수문자등의 글자는 삭제처리
final_data['문장'] = final_data['문장'].str.replace('[^가-힣 ]','')
# '문장' 컬럼의 내용에서 영문, 특수문자 없음 확인
final_data['문장'][final_data['문장'].str.contains('[^가-힣 ]')].sum()
# 숫자, 영문자, 특수문자 등 제거후 데이터 확인하기.
final_data.head()

 

 

4. 전처리 : Null, 중복 제거

# '문장' 컬럼의 내용을 양끝의 빈공간 삭제
final_data['문장'] = final_data['문장'].str.strip()
# Null 있는지 확인 : 없음
final_data.isnull().sum()

# final_data.dropna(inplace = True)
# 중복 데이터 있는지 확인 : 56건 중복 존재 확인
final_data['문장'].duplicated().sum()
# 중복 데이터 제거
final_data.drop_duplicates(subset=['문장'], inplace=True)
# 기존 51,630건 --> 이후 51,574건 : 56건 중복 삭제 확인
final_data.info()

 

5. Label 분포 확인

# label '감정' 분포 확인 : 총 6개이며, 고루게 분포 확인. 단 기쁨이 약간 부족해 보임
final_data['감정'].value_counts()

 

6. label 숫자로 인코딩

# 감정 리스트 만듬
list1 = final_data['감정'].value_counts().index.values
list1
# 라벨와 클래스을 매핑 작업
label2class = {}
class2label = {}
for cl, la in enumerate(list1):
  # print(i, j)
  label2class[la] = cl
  class2label[cl] = la

print(label2class)
print(class2label)

## Output: {'불안': 0, '분노': 1, '상처': 2, '슬픔': 3, '당황': 4, '기쁨': 5}
## Output: {0: '불안', 1: '분노', 2: '상처', 3: '슬픔', 4: '당황', 5: '기쁨'}
# '감정' 라벨링 수행
final_data['label'] = final_data['감정'].map(label2class)
final_data.tail()

 

7. X, Y 분리

# X, Y 분리
features = final_data['문장'].values
labels = final_data['label'].values
# 넘파이 배열로 반환
features.shape, labels.shape
## Output: ((51574,), (51574,))
# features 내용 3개 출력
features[:3]
print('이벤트 문자열 최대 길이 :{}'.format(max(len(l) for l in features)))
print('이벤트 문자열 평균 길이 :{}'.format(sum(map(len, features))/len(features)))

## Output: 이벤트 문자열 최대 길이 :152
## Output: 이벤트 문자열 평균 길이 :33.91709000659247
# 히스토그램을 보면 30~40 부근에 많이 몰려 있음 알수 있다.
plt.hist([len(s) for s in features], bins=50)
plt.xlabel('length of samples')
plt.ylabel('number of samples')
plt.show()

 

8. train set와 test set 분리 

from sklearn.model_selection import train_test_split

x_train, x_test, y_train, y_test =  train_test_split(features, labels , test_size=0.2, stratify=labels, random_state=41)
x_train.shape, x_test.shape, y_train.shape, y_test.shape

## Output: (array(['아르바이트만 하다가 취업하려니 거부감 들어', 
##		'혼자가 편하다고 한 게 후회돼'], dtype=object), ([1, 4])

9. 전체 문장에 대해 Tokenizing

  • 컴퓨터가 이해하기 위해 모든 단어를 숫자로 변환해야 함.
  • 단어 빈도수 따지지 않고 무조건 모든 단어 수용해서 진행
import tensorflow as tf
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences

왜 딥러닝에서 Tokenizing을 더 많이 사용하는가?

  1. 임베딩 레이어의 효과:
    • 딥러닝 모델에서는 임베딩 레이어를 통해 단어를 고차원 벡터로 변환합니다. 이 벡터는 학습 과정에서 업데이트되며, 단어 간의 의미적 유사성을 학습할 수 있습니다.
    • 예를 들어, 임베딩 레이어는 "고양이"와 "강아지"가 의미적으로 유사한 단어임을 학습할 수 있습니다.
  2. 문맥 정보 반영:
    • 딥러닝 모델, 특히 RNN, LSTM, GRU, Transformer 등은 단어의 순서와 문맥 정보를 반영할 수 있습니다. 이를 통해 단어의 의미를 더 잘 이해할 수 있습니다.
    • TF-IDF는 단어의 빈도와 희귀성을 반영하지만, 단어의 순서와 문맥 정보를 반영하기에는 한계가 있습니다.
  3. 대규모 데이터셋 처리:
    • 딥러닝 모델은 대규모 데이터셋에서 잘 작동하며, 많은 단어를 학습하여 일반화된 표현을 얻을 수 있습니다.
    • TF-IDF는 고정된 크기의 벡터를 생성하므로, 매우 큰 단어 집합에서는 벡터의 크기가 비효율적으로 커질 수 있습니다.
  • 요약: 어짜피 임베딩하기 때문에 단어를 숫자로 바꾸는 간단한 Tokenizing만 하면 된다.
# Tokenizer 구현 : 단어 사전 만들기(fit_on_texts)
tokenizer = Tokenizer()
tokenizer.fit_on_texts(x_train)
# 단어에 대한 숫자 매핑
print(tokenizer.word_index)
## Output: {'너무': 1, '내가': 2, '내': 3, '것': 4, '나': 5, '같아': 6, '안': 7, ...}

# 반대로 숫자로 단어 매핑
print(tokenizer.index_word)
## Output: {1: '너무', 2: '내가', 3: '내', 4: '것', 5: '나', 6: '같아', 7: '안', ...}

# 단어별 빈도수 확인
print(tokenizer.word_counts)
## Output: OrderedDict([('아르바이트만', 6), ('하다가', 76), ('취업하려니', 1), ...}

# 총 단어 갯수 : 47,646
max_words = len(tokenizer.index_word)
print(max_words)

 

10. texts_to_sequences : 문장을 숫자로 나열

  • 빈도수 적은 단어 제외하는것 없이 모든 단어 포함해서 진행
  • 그리고, 예를 들어 1번 등장하는 단어는 삭제하는 작업은 필요시 수행!!
# 문장을 숫자로 나열
x_train_seq = tokenizer.texts_to_sequences(x_train)
x_test_seq = tokenizer.texts_to_sequences(x_test)
# 문장을 숫자로 변경후 갯수 확인
# x_train.shape, x_test.shape, y_train.shape, y_test.shape : ((41259,), (10315,), (41259,), (10315,))
print(len(x_train_seq), len(x_test_seq))
## Output: 41259 10315
print(x_train[1:3])
print(x_train_seq[1:3])

## Output: ['혼자가 편하다고 한 게 후회돼' '나 부모님께 너무 죄송한 마음이 들어']
## Output: [[1664, 8292, 40, 15, 195], [5, 232, 1, 3601, 59, 24]]

 

11. Padding Sequence

# 문장의 최대 길이 파악 : 제일 긴 문장 seq 길이는 38개로 구성됨.
max(len(line) for line in x_train_seq)
# 모든 문장을 최대 문장 Seq 길이 38에 맞춘다.
x_train_pad = pad_sequences(x_train_seq, maxlen=38)
x_test_pad = pad_sequences(x_test_seq, maxlen=38)
# 문장 Seq 내용을 보니 잘 패딩되어 있음 확인
x_train_pad[:1]
# 문장 Seq 패딩의 shape 확인
x_train_pad.shape, x_test_pad.shape

## Output: ((41259, 38), (10315, 38))

 

 

12. LSTM 모델링

from tensorflow.keras.layers import Dense, Flatten, Conv1D, MaxPool2D
from tensorflow.keras.layers import Embedding, Bidirectional, LSTM, SimpleRNN, GRU
from tensorflow.keras.models import Sequential
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
# 하이퍼 파라미터

max_words = 47646 + 1    # 총 단어 갯수 + padding 0 번호
max_len = 38             # 최대 문장 길이
embedding_dim = 32      # embedding 차원

 

  • 작은 데이터셋/간단한 문제:
    • embedding_dim = 32, 50, 100
  • 큰 데이터셋/복잡한 문제:
    • embedding_dim = 200, 300, 500

 

 

# 모델 설정
model = Sequential([
    Embedding(max_words, embedding_dim, input_length=max_len),
    LSTM(16, return_sequences=True),
    LSTM(16, return_sequences=True),
    Flatten(),
    Dense(128, activation='swish'),
    Dense(32, activation='swish'),
    Dense(6, activation='softmax')
])

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

# 모델 요약 출력
model.summary()
# 조기종료 콜백함수 정의(EarlyStopping)
es = EarlyStopping(monitor='val_loss', patience=10, verbose=1)

# 체크포인트 저장(ModelCheckpoint)
checkpoint_path = 'tmp_checkpoint.ckpt'
cp = ModelCheckpoint(checkpoint_path, monitor='val_loss', verbose=1, save_best_only=True)

 

# 모델 학습(fit)
history = model.fit(x_train_pad, y_train, epochs=50, batch_size=512,
                      validation_split=0.2, verbose =1, callbacks=[es, cp])

 

  • 작은 배치 크기:
    • batch_size = 32, 64, 128
    • 안정적이고 세밀한 업데이트가 필요할 때 사용.
  • 큰 배치 크기:
    • batch_size = 256, 512, 1024
    • 학습 속도를 높이고 싶을 때 사용.

 

 

성과 그래프

epochs = range(1, len(history.history['accuracy']) + 1)
plt.plot(epochs, history.history['accuracy'])
plt.plot(epochs, history.history['val_accuracy'])
plt.title('model accuracy')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train', 'valid'], )
plt.show()
model.evaluate(x_test_pad, y_test)

13. 예측해 보기

print(f'문자열 : {x_test[0]}')
print(f'Sequence : {x_test_pad[0]}')
# 모델 예측하기(predict)
predict = model.predict(x_test_pad[:1])
print(f'True : {class2label[y_test[0]]}')
print(f'Predict : {class2label[np.argmax(predict)]}')

## Output: True : 기쁨
## Output: Predict : 분노