오늘은 지난주 했었던 머신러닝을 활용한 강아지 견종 데이터 분류에 대한 추가 학습과 신경망에 대해 학습하였다.

 

견종 데이터를 추가할 때 '증강'을 통해 데이터를 추가하는 과정이나 신경망이라는 낯선 개념이 많이 등장하였다.

 

한번에 이해하지 못했지만 테크노트를 작성하며 다시 한번 이해하려고 노력해보자.

 

 


 

 

견종 분류 예제 - 증강을 통한 데이터 추가

 

교수님의 깜짝 선물이 도착했다.

 

20일차에서 해결했던 견종분류모델의 데이터가 부족한 문제를 해결하기 위해 데이터를 추가하는 과제이다.

 

데이터를 추가할 때 반드시 기준을 선정하여 해당 기준에 맞는 데이터를 추가해야 한다.

 

그렇기에 우리는 평균값을 기준으로 데이터를 추가하기로 했다.

# 수정사항
dachshund_length_mean = np.mean(dachshund_length)
dachshund_height_mean = np.mean(dachshund_height)

samoyed_length_mean = np.mean(samoyed_length)
samoyed_height_mean = np.mean(samoyed_height)

 

각 견종 별 길이와 키의 평균치를 구해 해당 변수에 저장하였다.

 

 

 

정규분포를 사용하여 평균과 표준편차가 주어진 새로운 데이터를 생성한다.

# 통계적 기반으로 데이터 증강시키기
# 편차를 키울 수록 오차가 높아진다.
new_normal_dachshund_length_data = np.random.normal(dachshund_length_mean, 5.5, 200)
new_normal_dachshund_height_data = np.random.normal(dachshund_height_mean, 5.5, 200)

new_normal_samoyed_length_data = np.random.normal(samoyed_length_mean, 5.5, 200)
new_normal_samoyed_height_data = np.random.normal(samoyed_height_mean, 5.5, 200)

 

위 방식과 같이 통계적 기반으로 데이터를 증강시켰다.

 

뒤 숫자로 편차와 증강시킬 데이터 수를 설정할 수 있다.

 

편차를 키울 수록 오차가 높아지므로 가장 효율적인 오차를 찾아 설정하도록 해야한다.

 

 

 

증강시킨 데이터를 시각화 해보도록 하자.

plt.scatter(x=new_normal_dachshund_length_data, y=new_normal_dachshund_height_data, c='b', marker='.')
plt.scatter(x=new_normal_samoyed_length_data, y=new_normal_samoyed_height_data, c='m', marker='*')
plt.show()

 

증강 후 데이터 시각화

 

중간중간 분류하기 애매할 것 같은 데이터들이 섞여있지만 이정도는 괜찮은 정답률을 가질 수 있기에 넘어가도록 하자.

 

증강 후 데이터들이 정상적으로 시각화 되었음을 확인할 수 있다.

 

 

 

이제 훈련 전 데이터와 레이블을 합성해야 한다.

new_dachshund_data = np.column_stack((new_normal_dachshund_length_data, new_normal_dachshund_height_data))
new_samoyed_data = np.column_stack((new_normal_samoyed_length_data, new_normal_samoyed_height_data))

new_dachshund_label = np.zeros(len(new_dachshund_data)) # 200 [0, 0, 0, ..., 0]
new_samoyed_label = np.ones(len(new_samoyed_data)) # 200 [1, 1, 1, ..., 1]

new_dogs = np.concatenate((new_dachshund_data, new_samoyed_data), axis=0)
new_labels = np.concatenate((new_dachshund_label, new_samoyed_label), axis=0)

 

 

 

이제 데이터와 레이블을 합성하였으면, 훈련 데이터와 테스트 데이터로 분할해야 한다.

from sklearn.model_selection import train_test_split
(X_train, X_test, y_train, y_test) = train_test_split(new_dogs, new_labels, test_size=0.2, random_state=0)

print(X_train.shape)
print(y_train.shape)
print(X_test.shape)
print(y_test.shape)

 

훈련 데이터와 테스트 데이터의 요소 수

 

테스트 데이터가 전체 데이터의 20% 만큼 할당되었음을 확인할 수 있다.

 

 

 

이제 KNN 분류기를 사용하여 학습하고 평가해보자.

from sklearn.neighbors import KNeighborsClassifier

Knn = KNeighborsClassifier(n_neighbors=5)
Knn.fit(X=X_train, y=y_train)
print(f'훈련의 정확도 : {Knn.score(X=X_train, y=y_train)}')

# 예측
y_predict = Knn.predict(X=X_test)
print(y_predict) # 예측값
print(y_test) # 정답

from sklearn.metrics import accuracy_score
print(f'테스트 정확도 : {accuracy_score(y_true=y_test, y_pred=y_predict)}')

 

아래 실행결과에서는 높은 정확도를 보이며 견종을 분류할 수 있었다.

 

 


 

 

신경망

 

신경망이란?

 

신경망이란 생물학적인 신경망에서 영감을 받아 만들어진 컴퓨팅 구조를 말한다.

 

신경망의 구성요소는 뉴런, 층(Layer), 활성화 함수(Activation Function)으로 구성되어 있다.

 

 

먼저 뉴런은 신경망의 기본 단위로 가중치(Weight)와 절편(bias)를 가지고 있다.

또한, 입력을 받아서 처리한 후 출력을 생성한다.

 

 

두번째로, 레이어은 뉴런들이 모여있는 층이다.

일반적으로 입력층, 은닉층, 출력층으로 구성된다.

  • 입력층 : 외부 데이터가 들어오는 층
  • 은닉층 : 입력 데이터를 처리하여 특징을 추출. 하나 이상의 은닉층을 가질 수 있음.
  • 출력층 : 최종 결과를 출력하는 층

층 별 역할은 위와 같다.

 

 

마지막으로 활성화 함수는 뉴런의 출력을 결정하는 함수이다.

만약 데이터들이 선형으로 표현할 수 없는 경우 해당 활성화 함수에서 비선형성을 도입하여 신경망이 복잡한 문제를 해결할 수 있게 한다.

대표적인 활성화 함수는 relu와 sigmoid, tanh 등이 있다.

 

 

그렇다면 신경망의 장점은 무엇이길래 신경망을 사용하는 걸까?

 

크게 두가지의 장점이 있다.

 

1. 데이터만 주어지면 학습할 수 있다.

2. 몇개의 소자가 오작동 하더라도 큰 문제가 발생하지 않는다.

 

 

 

퍼셉트론에 대해

 

퍼셉트론은 1957년 고안된 인공 신경망이다.

 

뉴런에서 입력 신호의 가중치 합이 어떤 임계값을 넘는 경우에만 뉴런이 활성화 되어 1을 출력하며, 그렇지 않을 경우에는 0을 출력한다.

 

sklearn의 라이브러리를 활용하여 Perceptron 클래스를 가져올 수 있다.

from sklearn.linear_model import Perceptron
# 샘플과 레이블이다.
X = [[0,0],[0,1],[1,0],[1,1]]
y = [0, 0, 0, 1]
# 퍼셉트론을 생성한다. tol는 종료 조건이다. random_state는 난수의 시드이다.
clf = Perceptron(tol=1e-3, random_state=0)
# 학습을 수행한다.
clf.fit(X, y)
# 테스트를 수행한다.
print(clf.predict(X))

 

해당 코드로 퍼셉트론 객체를 생성하여 학습시키고 출력을 예측할 수 있다.

학습 후 출력결과 예측

 

 

 

그러나 퍼셉트론에도 한가지 치명적인 단점이 존재하는데 XOR 게이터에 대한 선형 분리가 불가능하다.

 

위 사진과 같이 AND, OR 게이트들은 선형 분리가 가능하다.

 

XOR게이트는 선형 분리가 불가능 함을 확인할 수 있다.

 

그러나 다층 퍼셉트론에서는 XOR에 대해 선형으로 분리할 수 있다.

 

 

 

MLP(Multi Layer Perceptron) : 다층 퍼셉트론

 

다층 퍼셉트론의 구조

 

사진과 같이 다층 퍼셉트론은 위의 신경망의 구성 요소인 입력층, 은닉층, 출력층의 모습을 띄고 있으며, 각 층의 역할도 위와 같다.

 

또한, 민감도가 높고  모델의 학습과 성능에 있어 큰 역할을 하기에 상황에 맞는 활성화 함수를 잘 선택애햐 한다.

 

 

 

활성화 함수(activation function)

 

활성화 함수란 입력의 총합을 받아 출력값을 계산하는 함수이다.

 

위에서 설명했듯 활성화 함수는 주로 3가지 종류를 많이 사용한다.

 

일반적으로는 sigmoid를 가장 많이 사용하며, 양수의 값 중에서 빠른 계산이 필요 시 ReLU를 사용한다.

 

뉴런(Unit)과 활성화 함수(activation)은 같이 사용된다.

 

활성화 함수

 

 

텐서플로우와 케라스

 

텐서플로우는 딥러닝 프레임워크의 일종이며, 다양한 머신러닝과 딥러닝 모델을 구축하고 학습시키는 데 사용한다.

 

이때, tensor라는 기본 데이터 구조를 사용하는데 이는 다차원 배열이다.

 

케라스는 파이썬으로 작성된 고수준 딥러닝 API이다. 

 

둘 중에서 가장 많이 선택되는 백엔드는 텐서플로우이다.

 

 

 


 

 

텐서플로우를 활용한 XOR을 학습하는 MLP 작성

 

# 라이브러리 호출
import keras
import tensorflow as tf

# 모델 구축
model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Dense(units=2, input_shape=(2,), activation='sigmoid', name='INPUT'))
model.add(tf.keras.layers.Dense(units=1, activation='sigmoid', name="OUTPUT"))

 

레이어를 순차적으로 쌓아올리는 Sequential 모델을 사용하였다.

 

XOR을 사용할 때는 최소 두개의 뉴런이 필요하다. 그렇기에 은닉층에 두개의 유닛을 할당하고, input_shape 파라미터를 통해 별도의 입력층을 정의하지 않고 입력 데이터를 받아드렸다.

 

다음으로 추가한 레이어는 출력층으로 출력은 0과 1 하나만 출력하므로 하나의 뉴런을 사용하여 생성한다.

 

두개의 층의 활성화 함수는 모두 시그모이드 곡선을 사용하였다.

 

 

# 모델 컴파일
model.compile(loss='mse', optimizer=keras.optimizers.SGD(learning_rate=0.1))

 

모델을 구축하였으면 이제 컴파일 과정을 수행해야 한다.

 

컴파일 과정은 모델이 학습할 준비를 하는 단계로 손실 함수, 최적화 알고리즘, 평가 지표 등을 정의한다.

 

손실함수는 모델의 예측값과 실제값 사이의 차이를 측정하는 함수이다.

즉 모델이 잘 학습하고 있는지를 평가한다.

 

최적화 알고리즘은 모델의 가중치를 어떻게 조정할지를 결정하는 알고리즘으로 손실 함수를 최소화 하는 방향으로 가중치를 업데이트한다.

 

위 코드에서는 평가 지표는 포함하지 않고, 손실 함수와 최적화 알고리즘만 설정하였다.

 

MSE 방식의 손실 함수를 사용하였고, SGD(확률적 경사 하강법)을 사용하였다.

또한, 학습률을 0.1로 설정하였고, 이는 가중치를 얼마나 크게 업데이트 할 지 결정한다.

 

 

# 데이터 준비
X = tf.constant([[0, 0], [0, 1], [1, 0], [1, 1]])
y = tf.constant([0, 1, 1, 0])

 

이제 각 게이트의 입력값 및 출력값에 대한 데이터를 준비하였다.

 

 

이제 모델의 구조를 출력하고 파라미터를 확인하여, 문제가 없을 시 모델을 학습시켰다.

 

또한 학습 후에는 입력값 X에 대한 예측을 출력한다.

# 모델 구조 출력
print(model.summary())

# 모델 학습
model.fit(X, y, batch_size=1, epochs=2000)

# 예측
print(model.predict(X))

 

summary() 함수는 모델의 구조를 요약하여 출력하는 메서드이다.

레이어의 종류와 형태 파라미터 수를 한눈에 파악할 수 있게 도와준다.

 

fit()은 모델을 학습시키는 함수이다. 

학습 데이터 및 가중치 업데이트 시 사용할 데이터 수(batch_size), 데이터 셋을 학습할 횟수(epochs)를 설정하여 모델이 데이터를 사용해 학습하면서 가중치를 조정하여 예측 정확도를 높여간다.

 

예측결과

 

1번째와 4번째는 XOR 게이트에서 0이며, 2번째와 3번째가 1이 출력되므로 예측결과가 맞았다고 판단할 수 있다.

 

 


 

 

이차방정식 노이즈 생성

 

우리가 흔히 생각하는 이차함수 그래프를 그대로 훈련시킨다면 학습 데이터를 너무 잘 맞추는 과적합 상태가 발생할 수 있다.

 

그렇기에 특정 노이즈를 발생시켜 학습에 약간의 어려움을 주어 더욱 일반적인 방향으로 학습시킬 수 있게 된다.

 

 

데이터 생성 및 전처리

 

라이브러리를 통해 데이터를 불러오고 전처리 과정을 수행해보자.

import numpy as np
import time
from matplotlib import pyplot as plt
from sklearn.model_selection import train_test_split

# 생성할 샘플의 수
SAMPLE_NUMBER = 10,000

# 랜덤 넘버 생성기 초기화
np.random.seed(int(time.time()))

 

우선 라이브러리를 호출하고 생성할 샘플 수를 상수에 저장한다.

 

또한, 현재 시간값을 시드로 설정하여 매번 실행할 때마다 다른 랜덤값을 생성할 수 있게 하였다.

 

 

Xs = np.random.uniform(low=-2.0, high=0.5, size=SAMPLE_NUMBER)
np.random.shuffle(Xs)

 

위 코드 중 첫번째 코드는 -2.0과 0.5 사이의 균일 분포에서 위에서 정의한 상수의 수만큼 샘플을 생성한다.

이때 생성된 랜덤 샘플들은 리스트 형태의 변수에 저장된다.

 

그 이후 랜덤 샘플들의 순서를 무작위로 섞는다.

 

 

2차 함수 생성

 

이제 이차함수를 생성하고 그래프로 표현해보자.

# 이차함수 생성
ys = 2 * np.square(Xs) + 3 * Xs + 5

# 이차함수 시각화
plt.plot(Xs, ys, 'r.')
plt.show()

 

np.square() 함수는 제곱근을 표현한다.

그렇기에 위 코드를 수식으로 표현해보면

 

이제 해당 수식을 시각화 한 그래프를 확인해보자.

시각화 한 이차함수 그래프

 

위 이차함수 그래프가 빨간색 선으로 표시되었다.

 

 

 

노이즈 생성

 

이제 위 이차함수 그래프에 노이즈를 적용해보자.

ys += 0.1 * np.random.randn(SAMPLE_NUMBER)
plt.plot(Xs, ys, 'b.')
plt.show()

 

해당 코드는 정규분포를 따르는 랜덤 값에 0.1을 곱해 y값에 추가한다.

 

위같은 과정을 통해 이차함수에 노이즈를 적용할 수 있다.

 

노이즈를 적용한 이차함수 그래프 시각화

 

 

 

데이터 분할

 

이제 학습을 위해 데이터들을 분할해보자.

# 학습 데이터 분할
(X_train, X_test, y_train, y_test) = train_test_split(Xs, ys, test_size=0.2)

# 데이터 형태 확인
print(X_train.shape, X_test.shape, y_train.shape, y_test.shape)

 

데이터 형태

 

훈련 데이터와 테스트 데이터가 정상적으로 분할됐음을 확인할 수 있다.

 

 

 

훈련 데이터와 테스트 데이터 시각화

 

분할한 두 데이터를 한번 시각화 해보았다.

 

훈련 데이터는 파랑색, 테스트 데이터는 파랑색으로 표시하였다.

plt.plot(X_train, y_train, 'b.', label='Train')
plt.plot(X_test, y_test, 'r.', label='Test')
plt.legend()
plt.show()

 

훈련 데이터와 테스트 데이터 시각화

 

 

 

 

모델 생성

 

이제 학습에 맞는 모델을 생성하고 훈련시켜볼 차례이다.

 

import tensorflow as tf

# Sequential 모델 생성
model = tf.keras.Sequential([], name="Model")

# 입력층 생성
Input_layer = tf.keras.Input(shape=(1,))
model.add(Input_layer)
# 은닉층 생성
model.add(tf.keras.layers.Dense(units=16, activation='relu', name='Layer1'))
model.add(tf.keras.layers.Dense(units=16, activation='relu', name='Layer2'))

# 출력층 생성
model.add(tf.keras.layers.Dense(units=1, name='OUTPUT'))
model.summary()

 

모델은 Sequential 모델을 사용하였으며, 은닉층은 총 두개로 구성되어 있으며 16개의 뉴런과 relu 활성화 함수를 할당하고 있다.

 

또한, 출력층은 한개의 뉴런을 가지고 있다.

 

정리하자면 1개의 입력층, 2개의 은닉층, 1개의 출력층을 가지고 있다.

 

 

summary() 함수를 통해 확인한 모델의 모습이다.

 

 

 

모델 컴파일 및 학습하기

 

모델을 생성하였으니 해당 모델을 컴파일 후 학습시켜야 한다.

# 모델 컴파일
model.compile(optimizer='adam', loss='mse')

# 모델 훈련
history=model.fit(X_train, y_train, epochs=500)

# 손실값 출력
print(history.history['loss'])

 

우선 컴파일 시 최적화 알고리즘은 adam을 사용하였고, 손실함수는 mse를 사용하였다.

 

이제 모델 훈련에 데이터를 집어넣고 에포크에는 500을 넣어 훈련시켜 보았다.

 

또한 손실값을 확인하기 위해 마지막에 손실값을 따로 출력해보았다.

 

훈련중....

 

손실률 출력화면

 

 

 

모델 평가 및 시각화

 

이제 해당 모델을 평가하고 시각화 해보겠다.

# 결과 예측
y_pred = model.predict(X_test)

# 최종 정확도 평가
print(f'최종 정확도 : {model.evaluate(y_test, y_pred)}')

# 결과 시각화
plt.plot(y_test, y_pred, 'r.')
plt.show()
plt.plot(history.history['loss'])
plt.show()

 

 

코드는 정상적이나 딱봐도 부정확해 보이는 정확도가 출력되었다.

 

처음에는 모델의 학습 과정에서의 오류나 전처리 과정에서 오류라고 생각했으나 정확도 예측 결과를 시각화 해본 결과 그래프가 정상적으로 그려진걸 확인한 후에는 정확도를 평가하는 과정에서 문제가 생긴 것 같다.

 

데이터 평가 결과 시각솨

 

위 그래프에서 정상적으로 저런 결과가 나왔음은 학습에는 문제가 없음을 뜻한다.