59doit

다중분류 신경망(2) 본문

인공지능

다중분류 신경망(2)

yul_S2 2023. 1. 12. 15:52
반응형

 텐서플로 - 패션 MNIST 데이터활용 

"""

keras.datasets.fashion_mnist 모듈 아래 load_data()함수 사용
데이터 세트는 훈련데이터와 테스트 데이터로 나누어져 있다.
"""

 

# 텐서플로 설치 및 임포트

import tensorflow as tf
tf.__version__ #'2.11.0'

▶ predict결과와 타킷 y의 클래스를 비교한다. 
    배열 y의 행을 따라 가장 큰 값의 인덱스를 구해 사용한다.

 

 

# 데이터 불러오기

(x_train_all, y_train_all), (x_test, y_test) = tf.keras.datasets.fashion_mnist.load_data()

▶load_data()는 입력과 타깃을 튜플로 묶어 훈련세트와 테스트세트로 반환한다.
x_train_all,y_train_all,x_test,y_test 변수에 담는다.

 

 

# 훈련세트 크기 확인

print(x_train_all.shape, y_train_all.shape)
# (60000, 28, 28) (60000,)

▶ 28*28 크기의 흑백이미지 6만개가 쌓여있음
y_train_all : 이미지를 분류한 타킷의 값이 들어있다.1차원배열

 

 

 

# imshow함수로 샘플 이미지 확인

import matplotlib.pyplot as plt
plt.imshow(x_train_all[0], cmap='gray')
plt.show()

▶ imshow 는 넘파이 배열을 입력받아 이미지를 그린다.
데이터는 넘파이 배열이 2차원 배열이고 배열의 원소는 색을 표현하는 값으로 구성되어있다.
matplotlib패키지는 컬러맵을 사용하여 이미지를 그리고 cmap매개변수로 설정할수있다.
cmap의 디폴트는 픽셀을 짙은 녹색에서 밝은 노란색 사이로 표현 : viridis이다
데이터는 흑백이미지 이므로 'gray'로 지정하였다.
배열의 원소값이 0에 가까울수록 이미지가 검게 그려진다

 

 

 

 

 

 

# 타깃의 내용과 의미 확인

print(y_train_all[:10])
class_names = ['티셔츠/윗도리', '바지', '스웨터', '드레스', '코트',
               '샌들', '셔츠', '스니커즈', '가방', '앵클부츠']
 
class_names[y_train_all[0]]
# '앵클부츠'

 

 

 

# 타깃 분포 확인

import numpy as np
np.bincount(y_train_all)
# array([6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000],
      dtype=int64)

▶ 각 레이블당 6000개 데이터가 들어 있음

 

 

 

 

# 훈련세트와 검증세트 나누기

from sklearn.model_selection import train_test_split
x_train, x_val, y_train, y_val = train_test_split(
    x_train_all, y_train_all, stratify=y_train_all, test_size=0.2, random_state=42)
"""
클래스 균형 맞는지 확인
"""
np.bincount(y_train)
np.bincount(y_val)
array([4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800, 4800],
      dtype=int64)

array([1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200],
      dtype=int64)

▶ 클래스 균형 맞는지 확인

 

 

 

 

# 데이터 정규화하기

x_train = x_train / 255 # 훈련세트
x_val = x_val / 255     # 검증세트

▶  각 색상은 0에서 255사의 정수로 표시된다. 이 수치는 각 색생의 강도를 나타낸다
따라서 각 데이터를 255로 나누어서 0~1값으로 만든다

 

 

 

 

# 훈련세트와 검증세트의 차원 변경하기

x_train = x_train.reshape(-1,784)
x_val = x_val.reshape(-1,784)
print(x_train.shape,x_val.shape)
# (48000, 784) (12000, 784)

▶ 훈련세트와 검증세트의 두번째 세번째 차원을 합치기
즉, 이미지의 픽셀을 한줄로 이어붙이기
reshape 함수 사용

 

 

 

 

# 다중신경망 훈련

▶ MNIST 데이터 10개의 클래스로 되어 있어 출력 뉴런의 개수도 10개가 되어야한다.
y_train, y_val 은 0~9 사이의 정수값으로 10개의 출력 뉴런에 대응되지 않는다
따라서 출력 뉴런의 개수에 맞게 변형해야한다.
-> 원-핫 인코딩 진행
타킷의 정수값에 해당하는 원소 : 1
그 외 원소 : 0

 

 

# to_categorical 함수 사용

▶ 문자열로 된 레이블은 인코딩 하지 못한다.
현재 사용하는 데이터의 타깃 데이터는 정수이기때문에 사용가능

from sklearn.preprocessing import LabelBinarizer
lb = LabelBinarizer()
lb.fit_transform([0, 1, 3, 1])
array([[1, 0, 0],
       [0, 1, 0],
       [0, 0, 1],
       [0, 1, 0]])

 

tf.keras.utils.to_categorical([0, 1, 3])
array([[1., 0., 0., 0.],
       [0., 1., 0., 0.],
       [0., 0., 0., 1.]], dtype=float32)

 

y_train_encoded = tf.keras.utils.to_categorical(y_train)
y_val_encoded = tf.keras.utils.to_categorical(y_val)

 

print(x_train.shape, x_val.shape)
# (48000, 784) (12000, 784)

print(y_train_encoded.shape, y_val_encoded.shape)
# (48000, 10) (12000, 10)

▶2차원 배열로 바꾸었다

 

print(y_train[0], y_train_encoded[0])
# 6 [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]

▶인코딩 잘 되었는지 레이블 출력
레이블6이 일곱번째 원소 위치의 값만 원-핫 인코딩 되어 바뀌었다.

 

 

 

 

 

# MultiClassNetwork클래스로 다중분류 신경망 훈련하기

class MultiClassNetwork:
 
    def __init__(self, units=10, batch_size=32, learning_rate=0.1, l1=0, l2=0):
        self.units = units  # 은닉층의 뉴런 개수
        self.batch_size = batch_size  # 배치 크기
        self.w1 = None  # 은닉층의 가중치
        self.b1 = None  # 은닉층의 절편
        self.w2 = None  # 출력층의 가중치
        self.b2 = None  # 출력층의 절편
        self.a1 = None  # 은닉층의 활성화 출력
        self.losses = []  # 훈련 손실
        self.val_losses = []  # 검증 손실
        self.lr = learning_rate  # 학습률
        self.l1 = l1  # L1 손실 하이퍼파라미터
        self.l2 = l2  # L2 손실 하이퍼파라미터

    def forpass(self, x):
        z1 = np.dot(x, self.w1) + self.b1  # 첫 번째 층의 선형 식을 계산합니다
        self.a1 = self.sigmoid(z1)  # 활성화 함수를 적용합니다
        z2 = np.dot(self.a1, self.w2) + self.b2  # 두 번째 층의 선형 식을 계산합니다.
        return z2
 
    def backprop(self, x, err):
        m = len(x)  # 샘플 개수
        # 출력층의 가중치와 절편에 대한 그래디언트를 계산합니다.
        w2_grad = np.dot(self.a1.T, err) / m
        b2_grad = np.sum(err) / m
        # 시그모이드 함수까지 그래디언트를 계산합니다.
        err_to_hidden = np.dot(err, self.w2.T) * self.a1 * (1 - self.a1)
        # 은닉층의 가중치와 절편에 대한 그래디언트를 계산합니다.
        w1_grad = np.dot(x.T, err_to_hidden) / m
        b1_grad = np.sum(err_to_hidden, axis=0) / m
        return w1_grad, b1_grad, w2_grad, b2_grad

    def sigmoid(self, z):
        z = np.clip(z, -100, None)  # 안전한 np.exp() 계산을 위해
        a = 1 / (1 + np.exp(-z))  # 시그모이드 계산
        return a

    def softmax(self, z):
        # 소프트맥스 함수
        z = np.clip(z, -100, None)  # 안전한 np.exp() 계산을 위해
        exp_z = np.exp(z)
        return exp_z / np.sum(exp_z, axis=1).reshape(-1, 1)
 
 
    def init_weights(self, n_features, n_classes):
        self.w1 = np.random.normal(0, 1, (n_features, self.units))  # (특성 개수, 은닉층의 크기)
        self.b1 = np.zeros(self.units)  # 은닉층의 크기
        self.w2 = np.random.normal(0, 1, (self.units, n_classes))  # (은닉층의 크기, 클래스 개수)
        self.b2 = np.zeros(n_classes)
 
    def fit(self, x, y, epochs=100, x_val=None, y_val=None):
        np.random.seed(42)
        self.init_weights(x.shape[1], y.shape[1])  # 은닉층과 출력층의 가중치를 초기화합니다.
        # epochs만큼 반복합니다.
        for i in range(epochs):
            loss = 0
            print('.', end='')
            # 제너레이터 함수에서 반환한 미니배치를 순환합니다.
            for x_batch, y_batch in self.gen_batch(x, y):
                a = self.training(x_batch, y_batch)
                # 안전한 로그 계산을 위해 클리핑합니다.
                a = np.clip(a, 1e-10, 1 - 1e-10)
                # 로그 손실과 규제 손실을 더하여 리스트에 추가합니다.
                loss += np.sum(-y_batch * np.log(a))
            self.losses.append((loss + self.reg_loss()) / len(x))
            # 검증 세트에 대한 손실을 계산합니다.
            self.update_val_loss(x_val, y_val)
 
    # 미니배치 제너레이터 함수
    def gen_batch(self, x, y):
        length = len(x)
        bins = length // self.batch_size  # 미니배치 횟수
        if length % self.batch_size:
            bins += 1  # 나누어 떨어지지 않을 때
        indexes = np.random.permutation(np.arange(len(x)))  # 인덱스를 섞습니다.
        x = x[indexes]
        y = y[indexes]
        for i in range(bins):
            start = self.batch_size * i
            end = self.batch_size * (i + 1)
            yield x[start:end], y[start:end]  # batch_size만큼 슬라이싱하여 반환합니다.
 
    def training(self, x, y):
        m = len(x)  # 샘플 개수를 저장합니다.
        z = self.forpass(x)  # 정방향 계산을 수행합니다.
        a = self.softmax(z)  # 활성화 함수를 적용합니다.
        err = -(y - a)  # 오차를 계산합니다.
        # 오차를 역전파하여 그래디언트를 계산합니다.
        w1_grad, b1_grad, w2_grad, b2_grad = self.backprop(x, err)
        # 그래디언트에서 페널티 항의 미분 값을 뺍니다
        w1_grad += (self.l1 * np.sign(self.w1) + self.l2 * self.w1) / m
        w2_grad += (self.l1 * np.sign(self.w2) + self.l2 * self.w2) / m
        # 은닉층의 가중치와 절편을 업데이트합니다.
        self.w1 -= self.lr * w1_grad
        self.b1 -= self.lr * b1_grad
        # 출력층의 가중치와 절편을 업데이트합니다.
        self.w2 -= self.lr * w2_grad
        self.b2 -= self.lr * b2_grad
        return a

    def predict(self, x):
        z = self.forpass(x)  # 정방향 계산을 수행합니다.
        return np.argmax(z, axis=1)  # 가장 큰 값의 인덱스를 반환합니다.

    def score(self, x, y):
        # 예측과 타깃 열 벡터를 비교하여 True의 비율을 반환합니다.
        return np.mean(self.predict(x) == np.argmax(y, axis=1))
 
    def reg_loss(self):
        # 은닉층과 출력층의 가중치에 규제를 적용합니다.
        return self.l1 * (np.sum(np.abs(self.w1)) + np.sum(np.abs(self.w2))) + \
               self.l2 / 2 * (np.sum(self.w1 ** 2) + np.sum(self.w2 ** 2))

    def update_val_loss(self, x_val, y_val):
        z = self.forpass(x_val)  # 정방향 계산을 수행합니다.
        a = self.softmax(z)  # 활성화 함수를 적용합니다.
        a = np.clip(a, 1e-10, 1 - 1e-10)  # 출력 값을 클리핑합니다.
        # 크로스 엔트로피 손실과 규제 손실을 더하여 리스트에 추가합니다.
        val_loss = np.sum(-y_val * np.log(a))
        self.val_losses.append((val_loss + self.reg_loss()) / len(y_val))
fc = MultiClassNetwork(units=100, batch_size=256)
fc.fit(x_train, y_train_encoded, x_val=x_val, y_val=y_val_encoded, epochs=40)

▶ 40번의 에포크 동안 훈련

 

 

 

 

# 훈련손실, 검증 손실 그래프 & 훈련 모델 점수 확인

plt.plot(fc.losses)
plt.plot(fc.val_losses)
plt.ylabel('loss')
plt.xlabel('iteration')
plt.legend(['train_loss', 'val_loss'])
plt.show() 

 

 

 

 

# score 확인

fc.score(x_val, y_val_encoded)
# 0.8150833333333334

▶ 오차가 많이 나므로 실전에 사용 x
-> 텐서플로 이용하여 다중분류 모델을 만든다.

 

반응형

'인공지능' 카테고리의 다른 글

합성곱(1)  (1) 2023.01.17
다중분류 신경망(3)  (8) 2023.01.13
다중분류 신경망(1)  (0) 2023.01.12
다층신경망(2) 미니배치  (7) 2023.01.11
다층신경망(1)  (3) 2023.01.11
Comments