Machine Learning

[CV 5] AutoEncoder

bomishot 2023. 5. 7. 19:23

이미지를 효과적으로 압축해줄 수 있는 AutoEncoder에 대해 알아보자. 이것은 데이터 시각화를 위한 차원 축소에도 사용이 가능하다.

🌀 AutoEncoder

Encoder에서 입력 데이터를 저차원 벡터로 압축한 뒤, Decoder에서 원래 크기의 데이터로 복원하는 신경망

 

 

 

Latent Vector

잠재 벡터. 가운데 code표시되어있는 가장 저차원의 벡터

원본 데이터보다 차원이 작으면서도, 원본 데이터의 특징을 잘 보존하고 있는 벡터

AutoEncoder는 궁극적으로 데이터의 중요 특징인, Latent vector를 잘 얻기 위한 방법이다.

Latent vector를 바탕으로 다시 원본 데이터로 복원할 때에 발생하는 오류, 즉 복원 오류(Reconstruction Error)를 최소화하도록 훈련한다. 원본 데이터의 특징을 최대한 보존한다.

Encoder를 잘 만드는 것이 주된 목적이다. (VAE, GAN에서는 decoder를 잘만들어야함.)

 

 

AutoEncoder 쓰임새

  1. 차원 축소, 데이터 압축
  2. DAE : 데이터 노이즈 제거 (Denoising AutoEncoder)
  3. Anomaly Detection : 이상치 탐지
    • 정상 데이터만 사용해 모델 학습 후, 복원 했을 때의 오차가 임계값을 초과하는 경우, 해당 데이터를 비정상으로 분류

현업시) AutoEncoder에서 정규화가 중요하다고 말씀함.

 

 

 

 

 

1. 기본적인 AutoEncoder

원본 데이터, AutoEncoder된 데이터가 어떻게 다른지 보자.

먼저, latent vector 차원 수 정의한다. 다른 신경망과 달리, 이것을 정의 한 뒤에 다른 은닉층 노드 수를 정한다.

 

Fashion MNIST 이미지 데이터셋 이용

LATENT_DIM = 64 # latent dim은 784보다 작으면 되겠지.

class AutoEncoder(Model):
	def __init__(self, latent_dim):
		super(AutoEncoder, self).__init__()
		self.latent_dim = latent_dim
		self.encoder = tf.keras.Sequential([
				layers.Flatten(), # 이미지 데이터니까. (28,28)->784
				layers.Dense(latent_dim, acitvation='relu')]) # 잠재 벡터 차원수만큼 줄임.
		
		self.decoder = tf.keras.Sequential([
				layers.Dense(784, activation='sigmoid'), # 입력노드와 맞춰주려고 다시 784차원으로 늘림
				layers.Reshape((28,28))]) # 다시 이미지의 형태로 만들기 위해.

	def call(self, x):
		encoded = self.encoder(x)
		decoded = self.decoder(encoded)
		return decoded

model = AutoEncoder(LATENT_DIM)
model.compile(optimizer='adam', loss='mse')
model.fit(x_train, x_train, epochs=10, shuffle=True, validation_data=(x_test,x_test))
# 원본 x_train, autoencoder된 x_train을 넣어줌.
# shuffle=True : 데이터 순서 셔플해줌.

# encoder, decoder이미지를 numpy array로 변환
encoded_imgs = model.encoder(x_test).numpy()
decoded_imgs = model.decoder(enocded_imgs).numpy()

# 원본이미지, autoencoder에서 복원된 이미지를 비교해 보자.
n = 10
plt.figure(figsize=(20,4))

for i in range(n):
	# 원본 이미지 출력
	ax = plt.subplot(2, n, i+1) # 행의수, 열의수, 인덱스
	plt.imshow(x_test[i]) # 28x28 
	plt.title('Original')
	plt.gray()

	# x,y열의 axis 보이지 않게 하기
	ax.get_xaxis().set_visible(False)
	ax.get_yaxis().set_visible(False)

	# autoencoder에서 나온 이미지
	ax = plt.subplot(2, n, i+n+1) # 11~
	plt.imshow(decoded_imgs[i])
	plt.title('Reconstructed')
	plt.gray()
	ax.get_xaxis().set_visible(False)
	ax.get_yaxis().set_visible(False)

plt.show() # 이거 해야 성능상 이점있다고하네.

위는 원본 이미지, 밑은 autoencoder사용해 복원된 이미지다.

원본 이미지보단, 해상도가 떨어지지만 , 어느정도 형태는 보존되는 것을 볼 수 있다.

 

 

 

 

 

2. DAE (Denoising AutoEncoder)

이번 코딩은 위와 다르게 Dense로 구성된 것이 아니라, Convolution 층이 사용된 autoencoder를 해보자 (Convolutional AutoEncoder) → 이것을 하기 위해선, 데이터에 임의의 채널을 더하는 과정 필요

 

random noise

원본 이미지에 노이즈 추가해줌. noisy를 어떻게 주느냐가 중요하다.

noise데이터가 뭐냐에 따라 다른 형태로 해줘야함.(비 noisef,,a..)

# 정규분포에서 20%만큼의 노이즈를 추가
noise_factor=0.2
x_train_noisy = x_train + noise_factor*tf.random.normal(shape=x_train.shape)
x_test_noisy = x_test + noise_factor*tf.random.normal(shape=x_test.shape)

# 텐서 값을 지정된 0~1 최소최대로 자른다.
x_train_noisy = tf.clip_by_value(x_train_noisy, clip_value_min=0, clip_value_max=1)
x_test_noisy = tf.clip_by_value(x_test_noisy, clip_value_min=0, clip_value_max=1)
class Denoise(Model):
	def __init__(self):
		super(Denoise, self).__init__()
		self.encoder = tf.keras.Sequential([
				layers.Input(shape=(28,28,1)), 
				# Downsampling
				layers.Conv2D(16,(3,3), acivation='relu', padding='same', strides=2),
				layers.Conv2D(8,(3,3), activation='relu', padding='same', strides=2)])
		
		self.decoder = tf.keras.Sequential([
				# Upsampling
				layers.Conv2DTranspose(8, kernel_size=3, strides=2, activation='relu', padding='same'),
				layers.Conv2DTranspose(16, kernel_size=3, strides=2, activation='relu', padding='same'),
				layers.Conv2D(1, kernel_size=(3,3), activation='sigmoid', padding='same')])
				# input_shape=(28,28,1)이니까 채널 1을 맞춰주기위해 1로함.
	def call(self, x):
		encoded = self.encoder(x)
		decoded = self.decoder(encoded)
		return decoded

model = Denoise()
model.compile(optimizer='adam', loss='mse')
model.fit(x_train_noisy, x_train, epochs=10, shuffle=True, validation_data=(x_test_noisy, x_test))
# ouput은 noise가 없는 것으로 나오게끔 훈련.

위는 x_test_noisy이고, 밑은 decoded_img(x_test)한것.

입력 데이터는 Noise가 있는 데이터, target 데이터는 원본 이미지로.

결과 ) noise가 없는 데이터로 잘 복원됨.

 

 

 

 

 

 

 

Anomaly Detection (이상치 탐지)

비정상적인 데이터를 탐지하기 위해 AutoEncoder 사용

AutoEncoder는 잠재 벡터를 바탕으로, 다시 원본 데이터로 복원할 때 발생하는 오류, 즉 복원 오류를 최소화하도록 훈련된다.

정상 데이터로만 훈련한 뒤, 비정상 데이터셋을 복원한다면 복원 오류가 커질 것이다. 복원 오류가 특정한 임계값 초과 시, 비정상으로 판단

physoiNet : 의학데이터 자유롭게 이용할 수 있다.

심전도 측정 데이터셋인 ECG5000 데이터셋

label : 0: 비정상 / 1:정상

# 데이터 받아오기

# MinMaxScaling
min_val = tf.reduct_min(train_data) # 텐서 차원 전체에서 요소의 최소값 계산
max_val = tf.reduct_max(train_data)

train_data = (train_data-min_val) / (max_val-min_val)
test_data = (test_data-min_val) / (max_val-min_val)

# 타입 변환
train_data = tf.cast(train_data, tf.float32) 
test_data = tf.cast(test_data, tf.float32)

# label
# 인덱스로 사용하기 위해 bool형태로 변환
train_label = train_label.astype(bool)
test_label = test_label.astype(bool)

# true인 값만 가져오기
normal_train_data = train_data[train_label]
normal_test_data = teset_data[test_label]

# false인 값만 가져오기
anomalous_train_data = train_data[~train_label]
anomalous_test_data = test_data[~test_label]
class AnomalyDetector(Model):
	def __init__(self):
		super(AnomalyDetector, self).__init__()
		self.encoder = tf.keras.Sequential([
				layers.Dense(32, activation='relu'),
				layers.Dense(16, activation='relu'),
				layers.Dense(8, activation='relu')])

		self.dcoder = tf.keras.Sequential([
				layers.Dense(16, activation='relu'),
				layers.Dense(32, actiavtion='relu'),
				layers.Dense(140, activation='sigmoid')])
				# 마지막 층은 feature수 140개로 맞춰줌.

	def call(self, x):
		encoded = self.encoder(x)
		decoded = self.decoder(encoded)
		return decoded

model = AnomalyDetector()
model.compile(optimizer='adam', loss='mae')
history = model.fit(normal_train_data, normal_train_data,
										epochs=20, batch_size=512,
										validation_data=(test_data, test_data), shuflle=True)

 

복원 오류 시각화

정상데이터, 복원 오류 결과 시각화해보자.

encoded_imgs = model.encoder(normal_test_data).numpy()
decoded_imgs = model.decoder(encoded_imgs).numpy()

plt.plot(normal_test_data[0], 'b')
plt.plot(decoded_imgs[0], 'r')
plt.fill_between(np.arange(140), decoded_imgs[0], normal_test_data[0], color='lightcoral')
plt.legend(labels=['Input', 'Reconstruction', 'Error'])
plt.show()

차이가 면적으로 보이지.

 

비정상데이터와 복원 오류 시각화

 

 

훈련 오차를 시각화해보고 임계값 정하기

reconstructions = model.predict(normal_train_data)
train_loss = tf.keras.losses.mae(reconstructions, normal_train_data)

# 임계값 지정
threshold = np.mean(train_loss)+np.std(train_loss)
threshold
>> 0.03258

고정된 임계값보다 클 경우 이상치로 판단

def predict(model, data, threshold):
	reconstructions = model(data)
	loss = tf.keras.losses.mae(reconstructions, data)
	return tf.math.less(loss, threshold) # 각각의 위치값보고 <면 반환

임계값 넘어가는 애들은 제거하고 예측

 

 

 

 

 

 

 

 

 

지금까지 AutoEncoder에 대해 알아보았다. AE의 가장 큰 목적은 차원 축소이다. 그런데 왜 차원 축소를 해야할까? 이 이유는 Manifold learning을 이해하면 알 수 있다.

Manifold Learning

차원축소는 Manifold Learning과 같은 의미이다. 따라서 AE의 궁극적인 목적은 Manifold라고도 할 수 있다.

AutoEncoder는 데이터가 퍼져있는 공간에서 데이터의 Manifold를 찾아내어 학습하는 과정이라고 할 수도 있다.

Manifold Learning : 고차원의 공간에서 데이터가 이루어지는 저차원의 공간이 매니폴드이며, 매니폴드 공간을 찾는 것이다.

위 그림은 매니폴드의 대표적인 예시로 언급되는 Swiss-roll이다. 왼쪽처럼 데이터가 분포되어 있다할때, 적절한 manifold를 찾으면 오른쪽처럼 2차원으로 차원축소가 가능하다.

 

 

고차원 데이터 공간에 samples들을 잘 아우르는 subspace가 있을 것이라는 가정 하에, 학습을 진행하여 manifold를 통해 데이터의 차원을 축소하는 것이다.

 

 

Manifold 학습 절차

  1. 데이터를 고차원 공간에 뿌린다.
  2. 해당 데이터를 오류 없이 잘 아우르는 subspace(=manifold)를 구한다.
  3. 데이터를 manifold에 projection하면 데이터 차원 축소된다.

 

 

Manifold 목적

  1. 데이터 압축 : 데이터 압축후, latent vector로 디코딩 시 원본 데이터 복원이 잘 된다면 압축이 잘 된것이지!
  2. 데이터 시각화
  3. 차원의 저주 피하기 : 데이터 차원이 늘어날수록 공간의 부피는 기하급수적으로 증가하여 밀도는 희박해진다. 밀도가 희박해지면 데이터 활용력이 떨어지므로 가능한 차원은 줄일수록 좋다.
  4. 유용한 특징 추출 : manifold를 구하면 중요한 feature가 추출된다. 축을 따라가면 size, rotation 변화 등 특징을 발견할 수 있다.
  5. 고차원의 데이터를 잘 표현한다 = 데이터의 중요한 feature 잘 찾는다. 중요한 특징들을 찾았다면 이 특징을 공유하는 sample들도 잘 찾을 수 있어, 의미적으로 가까운 데이터들을 찾을 수 있다.
  6. 고차원의 데이터를 Euclidean 거리상에서 중앙값을 구한다면 비정상적인 값이 나온다. manifold를 벗어낫기 때문이다. 따라서 정상적인 중앙값을 구하려면 manifold안에서 구해야한다.

 

 

 

PCA와 AutoEncoder의 차이점

PCA는 linear 차원축소방법이고, AE는 nonlinear 차원축소방법이다.

아무래도 데이터를 더 효과적으로 아우르는 subspace를 찾을 수 있으니, linear 차원 축소보다 nonlinear차원축소가 더 효과적일 것이다.

 

 

 

 

 

또다른 AutoEncoder에 대해 알아보자.

VAE (Varaiational AutoEncoder)

VAE와 AE는 다르다!!!!!!!!!!!!!!!!!

  • AE : Manifold Learning
  • VAE : Generative Model

수학적으로 AutoEncoder와는 별로 관련이 없으며, 목적도 다르지만, 네트워크 구조가 비슷하기 때문에 VAE라는 이름이 붙었다.

AE의 목적은 Manifold learning으로 Encoder부분이 중요하다고 배웠다. Decoder부분은 압축이 잘 됐는지 검증을 위해 붙은 것뿐이다. 이 부분에서 VAE는 정반대이다. 데이터를 만들어내는 것이 목적인 Generative model이다. (다음에 등장할 GAN과 연결되는 내용) 그러므로, VAE는 뒷단이 중요하다! 뒷단을 학습시켜버렸더니 앞단이 붙었는데, 공교롭게 구조가 AE와 같아진 것 뿐이다.!!

 

VAE의 목적

고양이 샘플이 있다면, 학습을 통해 기존의 고양이와 유사한 새로운 고양이 샘플을 생성해내는 것이다.

https://deepinsight.tistory.com/127

더 공부