텐서플로를 사용하여 다항 분류 모형을 만듭니다.
데이터를 불러옵니다.
import joblib
x_train, x_test, y_train, y_test, tfidf = joblib.load('newsgroup.pkl')
텐서플로를 이용해 다항 분류 모형을 만듭니다. 다항 분류란 3개 이상, 여러 개로 분류한다는 뜻입니다.
import tensorflow as tf
먼저 모형을 만듭니다.
model = tf.keras.models.Sequential()
tf.keras.layers.Dense()
로 층(완전 연결층)을 추가합니다. 세부 옵션은 다음과 같습니다.units
: 최종 출력의 개수를 유닛(unit)이라 합니다. 본 예시의 목표는 3가지 다항 분류이므로 최종 출력의 개수가 3 이 됩니다.input_shape
: 입력데이터의 크기 설정. 앞서 max_feature
개수를 1000으로 지정하였으므로 (1000,) 과 같이 튜플 형태로 입력합니다.kernel_regularizer
: 단어들의 가중치가 지나치게 커지지 않도록 정규화항을 추가해줍니다. l1_l2
는 L1과 L2, 2가지 정규화를 모두 해줍니다. L1은 가중치의 절대값을 손실함수에 추가합니다. L2는 가중치의 제곱을 손실함수에 추가합니다. 어느 쪽이든 가중치가 커지면 손실이 커지므로, 가중치가 지나치게 커지는 것을 막아줍니다. l1_l2(0.003, 0.005)
는 손실함수에서 L1항에 0.003을 곱하고 L2항 0.005를 곱하라는 뜻입니다. 여기서 0.003, 0.005와 같은 수치는 다양하게 바꿔보면서 성능이 가장 잘 나오는 수치를 선택합니다.activation
: 활성화 함수로 소프트맥스 함수를 사용합니다.model.add(tf.keras.layers.Dense(
units=3,
input_shape=(1000,),
kernel_regularizer=tf.keras.regularizers.l1_l2(0.003, 0.005),
activation='softmax'))
모형을 model.summary()
로 확인합니다.
model.summary()
Model: "sequential_1" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense_1 (Dense) (None, 3) 3003 ================================================================= Total params: 3,003 Trainable params: 3,003 Non-trainable params: 0 _________________________________________________________________
다항 분류를 해결하기 위해 최적화 알고리즘을 사용합니다.
model.compile
:loss
: 모형의 손실함수는 sparse_categorical_crossentropy
를 사용optimizer
: 여기에서 최적화 함수는 Adam (Adaptive Moment Estimation: 적응적 모멘트 추정) 을 사용하였습니다.metrics
: 모형 훈련 과정에서 기록할 요소를 정합니다. 여기에서는 정확도'accuracy'를 사용하였습니다.model.compile(
loss='sparse_categorical_crossentropy',
optimizer=tf.keras.optimizers.Adam(),
metrics=['accuracy'])
모형이 오차로부터 파라미터(매개변수)를 업데이트 시키는 과정을 학습/훈련/적합(fit) 한다고 일컫습니다.
model.fit
으로 모형을 학습합니다.x_train.toarray()
: 훈련 데이터y_train
: 지도 학습(supervised learning)에서 카테고리(label) 데이터epochs
: 에포크 횟수. 30일 경우 전체 데이터를 30번 학습합니다.validation_split
: 훈련(train)과 검증(validate) 데이터셋을 분리하는 비율. 여기서는 0.3 으로 설정하였고, 이는 1795개 중 30% 인 539개(반올림)를 검증용 데이터로 나눈다는 의미입니다.callbacks
: tf.keras.callbacks.EarlyStopping()
옵션은 모형의 validation loss가 3개 에포크 동안 낮아지지 않을 경우 학습을 멈추게 됩니다.verbose
: 모형 학습 중 출력되는 텍스트를 조절하는 옵션(verbosity). 0, 1, 2의 3개가 있으며verbose=0
일 경우, 아무런 문구도 출력되지 않습니다. verbose=2
일 경우, 진행상태를 나타내는 상태바(====)가 표시되지 않습니다.history = model.fit(x_train.toarray(), y_train,
epochs=30, callbacks=[tf.keras.callbacks.EarlyStopping()],
validation_split=.3, verbose=0)
history
변수에는 30개 에포크 간의 학습 정확도(acc), 검증 정확도(val_acc), 학습 손실(loss), 검증 손실(val_loss)가 저장됩니다.
모형의 학습(손실과 정확도) 를 알아보기 위해 시각화를 합니다. matplotlib
라이브러리를 사용합니다.
import matplotlib.pyplot as plt
모형이름.history['loss']
: 학습한 모형의 손실모형이름.history['val_loss']
: 학습한 모형의 검증 손실값(validation loss).xlabel
: x축 제목.ylabel
: y축 제목.legend
: 각 그래프에 대한 설명 범례.title
: 전체 제목plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train_loss', 'val_loss'])
plt.title('LOSS')
plt.show()
손실 그래프는 에포크가 증가함에 따라 일정한 추세로 감소하는 모습을 보입니다.
모형이름.history['acc']
: 학습한 모형의 정확도모형이름.history['val_acc']
: 학습한 모형의 검증 정확도(validation accuracy).xlabel
: x축 제목.ylabel
: y축 제목.legend
: 각 그래프에 대한 설명 범례.title
: 전체 제목plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train_accuracy', 'val_accuracy'])
plt.title('ACCURACY')
plt.show()
정확도 그래프는 증가하다 어느 에포크 이상에서는 증가세가 감소하는 모습을 보입니다.
model.evaluate
를 사용하여 모형의 손실값과 정확도를 출력합니다.
model.evaluate(x_test.toarray(), y_test, verbose=2)
1194/1 - 0s - loss: 1.0646 - accuracy: 0.7915
[1.0758204781629732, 0.7914573]
위의 모형 평가 결과를 소숫점 셋째자리에서 반올림하면 [0.44, 0.93] 이 됩니다.
즉, 모형의 손실은 1.07 이고 정확도는 약 79%입니다.
단어별 가중치를 표로 정리합니다.
import pandas as pd
w, _ = model.weights
weights = pd.DataFrame(w.numpy())
결과해석을 위해 다항 분류를 [0, 1, 2] 의 숫자가 아닌 본래 카테고리(라벨) 이름으로 설정합니다.
weights.columns = ['motorcycle', 'baseball', 'hockey']
weights
변수의 word
열에 원 단어(feature)를 저장합니다.
weights['word'] = tfidf.get_feature_names()
첫째, motorcycle
을 기준으로 내림차순 정렬을 하고, 해당 카테고리에 어떤 단어들이 많이 등장하는지 확인합니다.
weights.sort_values('motorcycle', ascending=False).head()
motorcycle | baseball | hockey | word | |
---|---|---|---|---|
181 | 0.745936 | -0.374010 | -0.343098 | bike |
256 | 0.568886 | -0.094269 | -0.459109 | com |
318 | 0.541011 | -0.214346 | -0.200502 | dod |
767 | 0.269240 | -0.000391 | -0.002543 | ride |
930 | 0.252685 | -0.013626 | -0.003667 | uk |
둘째, baseball
을 기준으로 내림차순 정렬을 하고, 어떤 단어들이 많이 등장하는지 확인합니다.
weights.sort_values('baseball', ascending=False).head()
motorcycle | baseball | hockey | word | |
---|---|---|---|---|
340 | -0.124038 | 0.427687 | -0.120957 | edu |
166 | -0.161280 | 0.357024 | -0.000412 | baseball |
786 | -0.005161 | 0.257479 | -0.053414 | runs |
992 | -0.213979 | 0.212253 | 0.000080 | year |
501 | -0.004552 | 0.212245 | -0.004203 | jewish |
마지막으로 세번째 분류인 hockey
을 기준으로 내림차순 정렬을 하고, 어떤 단어들이 많이 등장하는지 확인합니다.
weights.sort_values('hockey', ascending=False).head()
motorcycle | baseball | hockey | word | |
---|---|---|---|---|
460 | -0.202504 | -0.212338 | 0.496042 | hockey |
629 | -0.067200 | -0.083500 | 0.398874 | nhl |
888 | -0.373876 | -0.000127 | 0.251696 | team |
396 | -0.377219 | 0.000425 | 0.230549 | game |
209 | -0.000016 | -0.190912 | 0.202833 | ca |