Loft_mind 2025. 3. 9. 23:09

2025.03.08

Chapter 7. Autoencoders, Image Augmentation

  • 50. 오토 인코더

 


 

 

인코더와 디코더, 잠재변수

  • 오토인코더는 입력과 출력이 동일하다.
    • 자기 자신을 재생성하는 네트워크
  • Latent Vector : 잠재변수. Encoder : 입력쪽, Decoder : 출력쪽.
  • 인코더는 일종의 특징추출기 같은 역할
  • 디코더는 압축된 데이터를 다시 복원하는 역할

 

 

 

MNIST 데이터

Tensorflow.datasets에 저장되어 있는 MNIST 데이터셋 호출

import tensorflow as tf
import numpy as np

(train_X, train_y), (test_X, test_y) = tf.keras.datasets.mnist.load_data()

train_X = train_X/255.0
test_X = test_X/255.0

print(train_X.shape, train_y.shape)

--------------------------------------------------------------------------
>>(60000, 28, 28) (60000,)

 

 

무슨 데이터인가 다시 확인해 보면..

import matplotlib.pyplot as plt

plt.imshow(train_X[0], cmap='gray')
plt.colorbar()
plt.title(f'label : {train_y[0]}')
plt.show()

 

 

 

 

인코더, 디코더, 잠재변수?

# layer 구성

# 모델 내에서 Flatten() layer 와와 동일한 역할할
train_X = train_X.reshape(-1, 28*28)
test_X = test_X.reshape(-1, 28*28)

model = tf.keras.Sequential([
    tf.keras.layers.Dense(784, activation='relu', input_shape=(784,)),
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.Dense(784, activation='sigmoid')
])

model.compile(
    optimizer = tf.keras.optimizers.Adam(),
    loss = 'mse'
)
model.summary()

 

784 layer에서 64 layer로 갈 때, 64는 임의로 지정한 것.

 

 

 

학습진행

## 학습

model.fit(train_X, train_X, epochs=10, batch_size=256)

 

 

train_X 가 두 번 들어간다. 오토인코더는 자기 자신을 입력과 출력으로 가진다. 결국 잠재 vector는 입력을 대표하는 vector가 된다.

 

 

 

 

예측값 확인

import random

plt.figure(figsize=(4, 8))

for c in range(4):

    plt.subplot(4, 2, c*2 + 1)  # 홀수
    rand_index = random.randint(0, test_X.shape[0]) # test_X.shape[0] = 10000
    plt.imshow(test_X[rand_index].reshape(28, 28), cmap='gray')
    plt.axis('off')

    plt.subplot(4, 2, c*2 + 2) # 짝수
    img = model.predict(np.expand_dims(test_X[rand_index], axis=0)) #batch 사이즈 추가
    plt.imshow(img.reshape(28, 28), cmap='gray')
    plt.axis('off')

    print(f'rand_index : {rand_index}')

plt.show()

 

비슷한게 나오는 것 같다.

 

 

 

CNN으로 다시 진행

train_X = train_X.reshape(-1, 28, 28, 1)
test_X = test_X.reshape(-1, 28, 28, 1)

model = tf.keras.Sequential([
    tf.keras.layers.Conv2D(filters=32, kernel_size=2, strides=(2,2), activation='relu', input_shape=(28, 28,1)),
    tf.keras.layers.Conv2D(filters=64, kernel_size=2, strides=(2,2), activation='relu'),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(64, activation='relu'),     #특성벡터가 되는 자리!!
    tf.keras.layers.Dense(7*7*64, activation='relu'),
    tf.keras.layers.Reshape(target_shape=(7, 7, 64)),
    tf.keras.layers.Conv2DTranspose(filters=32, kernel_size=2, strides=(2,2), padding='same', activation='relu'),
    tf.keras.layers.Conv2DTranspose(filters=1, kernel_size=2, strides=(2,2), padding='same', activation='sigmoid')
])

model.compile(optimizer=tf.optimizers.Adam(), loss='mse')
model.summary()

 

 

 

다시 학습 진행 (FIT)
model.fit(train_X, train_X, epochs=20, batch_size=256)

 

아까와 같이 예측한 후, 다시 사진으로 확인해 보면

 

plt.figure(figsize=(4, 8))

for c in range(4):

    plt.subplot(4, 2, c*2 + 1)  # 홀수
    rand_index = random.randint(0, test_X.shape[0]) # test_X.shape[0] = 10000
    plt.imshow(test_X[rand_index].reshape(28, 28), cmap='gray')
    plt.axis('off')

    plt.subplot(4, 2, c*2 + 2) # 짝수
    img = model.predict(np.expand_dims(test_X[rand_index], axis=0)) #batch 사이즈 추가
    plt.imshow(img.reshape(28, 28), cmap='gray')
    plt.axis('off')

    print(f'rand_index : {rand_index}')

plt.show()

비슷한 거 같다.. 사실

 

 

ReLu 말고 ELU 적용

 

ReLU : 음수 이하 값은 모두 0이다. 계산이 빠르나 음수 구간에서 뉴런이 죽는 문제(Vanishing Gradient) 발생 가능

ELU : 음수 값도 -1 이내의 범위에서 존재한다. ReLU 비해 계산이 느리나, 음수 구간에서도 뉴런이 죽지 않는다.

import math

x = np.arange(-5, 5, 0.01)

relu = [0 if z < 0 else z for z in x]
elu = [1.0 * (np.exp(z) - 1) if z < 0 else z for z in x]

plt.axvline(0, color='gray')
plt.plot(x, relu, 'r--', label='relu')
plt.plot(x, elu, 'g-', label='elu')
plt.legend()
plt.show()

 

 

 

모델(CNN)에 활성화 함수를 ReLU 대신 ELU 적용. 학습 및 예측 재진행

train_X = train_X.reshape(-1, 28, 28, 1)
test_X = test_X.reshape(-1, 28, 28, 1)

model = tf.keras.Sequential([
    tf.keras.layers.Conv2D(filters=32, kernel_size=2, strides=(2,2), activation='elu', input_shape=(28, 28,1)),
    tf.keras.layers.Conv2D(filters=64, kernel_size=2, strides=(2,2), activation='elu'),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(64, activation='relu'),     #특성벡터가 되는 자리!!
    tf.keras.layers.Dense(7*7*64, activation='relu'),
    tf.keras.layers.Reshape(target_shape=(7, 7, 64)),
    tf.keras.layers.Conv2DTranspose(filters=32, kernel_size=2, strides=(2,2), padding='same', activation='elu'),
    tf.keras.layers.Conv2DTranspose(filters=1, kernel_size=2, strides=(2,2), padding='same', activation='sigmoid')
])

model.compile(optimizer=tf.optimizers.Adam(), loss='mse')

model.fit(train_X, train_X, epochs=20, batch_size=256)
plt.figure(figsize=(4, 8))

for c in range(4):

    plt.subplot(4, 2, c*2 + 1)  # 홀수
    rand_index = random.randint(0, test_X.shape[0]) # test_X.shape[0] = 10000
    plt.imshow(test_X[rand_index].reshape(28, 28), cmap='gray')
    plt.axis('off')

    plt.subplot(4, 2, c*2 + 2) # 짝수
    img = model.predict(np.expand_dims(test_X[rand_index], axis=0)) #batch 사이즈 추가
    plt.imshow(img.reshape(28, 28), cmap='gray')
    plt.axis('off')

    print(f'rand_index : {rand_index}')

plt.show()

 

뭐 큰 차이는 안 보인다. 조금 더 동일하게 보인다고 해야 하나?..

 

 

 

 

잠재변수 벡터 확보

latent_vector_model = tf.keras.Model(inputs=model.input, outputs=model.layers[3].output)
latent_vector = latent_vector_model.predict(train_X)

print(latent_vector.shape)
print(latent_vector[0])

 

from sklearn.cluster import KMeans

kmeans = KMeans(n_clusters=10, n_init=10, random_state=42)
kmeans.fit(latent_vector)

plt.figure(figsize=(12, 12))

for i in range(10):
    images = train_X[kmeans.labels_ == i]
    for c in range(10):
        plt.subplot(10, 10, i*10+c+1)
        plt.imshow(images[c].reshape(28,28), cmap='gray')
        plt.axis('off')

plt.show()

 

 


 

t-SNE

 

  • 고차원 벡터저차원으로 옮겨서 시각화에 도움을 주는 방법
  • t - Stochastic Nearest Neighbor
  • k-Means가 각 클러스터를 계산하기 위한 단위로 중심과 각 데이터의 거리를 측정한다면,
  • t-SNE는 각 데이터의 유사도를 정의하고, 원래 공간에서의 유사도와 저차원 공간에서의 유사도가 비슷해지도록 학습
  • 여기서 유사도가 수학적으로 확률로 표현됨
## t- SNE 수행


from sklearn.manifold import TSNE

tsne = TSNE(n_components=2, learning_rate=100, perplexity=15, random_state=0)
tsne_vector = tsne.fit_transform(latent_vector[:5000])

cmap = plt.get_cmap('rainbow', 10)

fig = plt.scatter(tsne_vector[:, 0], tsne_vector[:, 1], marker='.', c=train_y[:5000], cmap=cmap)
cb = plt.colorbar(fig, ticks=range(10))

n_clusters=10
tick_locs = (np.arange(n_clusters) + 0.5) * (n_clusters-1) / n_clusters

cb.set_ticks(tick_locs)
cb.set_ticklabels(range(10))

plt.show()

 

 

 

  • manifold 모듈은 데이터의 비선형 구조를 보존하는 차원 축소 기법들을 제공. (여기선 64 채널 → 2 채널 축소)
  • TSNE(n_components=2, ...)64차원 특징 벡터(latent_vector)를 2차원으로 축소
  • n_components=2 → 최종적으로 2차원으로 변환하여 시각화
  • learning_rate = 100 → t-SNE가 좌표를 조정하는 속도
  • perplexity = 15 → 데이터 샘플 개수와 관련된 거리 기준 설정
    • 값이 크면 전역적(전체적인) 구조를 고려
    • 값이 작으면 국소적(근처 이웃 관계) 구조를 고려
  • fit_transform() → latent_vector(64차원 벡터)2차원으로 변환
  • latent_vector[:5000]메모리 부담을 줄이기 위해 처음 5000개 샘플만 사용
  • 결과 tsne_vector의 크기는 (5000, 2)
    • 즉, 5000개의 샘플이 각각 2차원 좌표로 변환됨
  • plt.get_cmap('rainbow', 10)
    • rainbow → 무지개 색상을 사용하여 0~9까지의 라벨을 구별할 수 있도록 설정
    • 10 → 총 10개의 클래스를 표현하기 위한 색상 개수 지정
  • plt.scatter(x, y, marker='.', c=라벨, cmap=색상맵)
    • x=tsne_vector[:, 0] → 2차원으로 변환된 첫 번째 축 값
    • y=tsne_vector[:, 1] → 2차원으로 변환된 두 번째 축 값
    • marker='.' → 점 형태로 표시
    • c=train_y[:5000] → 각 데이터의 실제 라벨 (0~9)을 색상으로 구분
    • cmap=cmap → 미리 지정한 rainbow 색상 맵 사용
  • plt.colorbar(fig, ticks=range(10))
    • fig → scatter 그래프에 대해 색상 막대를 추가
    • ticks=range(10) → 0~9까지의 숫자를 눈금(tick)으로 추가
  • n_clusters=10 → 총 10개의 클러스터 (0~9 숫자)
  • tick_locs → 눈금 위치를 균등하게 분포하도록 설정
  • cb.set_ticks(tick_locs) → 색상 막대의 눈금 위치 조정
  • cb.set_ticklabels(range(10)) → 눈금에 0~9까지의 숫자 표시

 

 

제일 큰 특징은 label 값을 사용하지 않았다.