본문 바로가기

이것저것

역함수와 역행렬로 딥러닝의 결과로부터 입력 복원하기

읽으시기 전에…

수학, AI에 대한 지식이 많이 부족한 상태에서 작성한 글이고, 사실관계와는 상관없이 생각을 정리한 글이기 때문에 틀린 내용이 있을 수 있습니다. 틀린 내용이 많더라도 너그럽게 봐주시고, 귀여운 개발자의 글로 생각해주시면 감사하겠습니다. 댓글로 내용에 대한 지적, 피드백 해주셔도 좋습니다🙂


딥러닝 모델에서 출력으로 입력 복원이 가능할까?

처음 생각은 LLM와 이미지 생성 모델로부터 였다. LLM와 이미지 생성 모델의 결과를 잘 뽑기 위해서는 좋은 프롬프트를 넣는 것이 중요하다고 한다. 그러면 결과로부터 프롬프트를 복원할 수 있다면 어떤 프롬프트를 넣어야 원하는 결과가 나오는 지 이해가 가능하다고 생각했다. 조금 더 확장하여 딥러닝 모델에서 결과로부터 입력을 추출할 수 있다면 black box를 그나마 이해할 수 있지 않을까 싶다.

역행렬과 역함수

그럼 어떻게 해야 결과로부터 입력을 뽑아낼 수 있을까? 이 때 역행렬과 역함수에 대한 생각이 들었다. 일단 복잡한 layer(convolution, transformer 등등)말고 간단한 fully connected layer에서만 생각해보자.

$$
AW = B \rightarrow A = BW^{-1}
$$

위처럼 행렬 A와 W의 행렬곱을 취했을 때 B라는 결과가 나온다고 가정하면 A를 얻기 위해서는 B와 W의 역행렬을 구해주면 된다.

하지만 역행렬을 구하기 위한 W의 조건은 아래와 같다.

  1. 정사각 행렬이어야한다.
  2. 행렬식이 0이 아니어야한다.
  3. 모든 행과 열이 선형적으로 독립적이어야한다.

하지만 대다수의 경우 정사각 행렬이라는 조건을 만족하지 못한다. 입력과 출력의 크기가 다른 경우가 대부분이기 때문이다. 그러면 정사각 행렬이 아닌 경우에는 역행렬이 없는 것일까?

유사역행렬

정사각 행렬이 아닌 다른 모양의 행렬에서는 역행렬 대신 유사 역행렬을 정의할 수 있다. 유사 역행렬을 구하는 방법은 아래의 포스트에 잘 정리되어 있다.


유사 역행렬 (Pseudo Inverse Matrix)


결론적으로는 유사역행렬을 사용하면 출력에 대해서 fully connected layer를 거치기 전의 값을 얻을 수 있는 것이다.

Activation function

보통 fully connected layer이후에는 비선형성을 위한 activation function을 계산하게 된다. 여기서는 역함수가 존재하는 activation function을 사용하면 역 변환이 가능할 것이라는 생각이든다. 그럼 다양한 activation function과 이것에 대한 역함수가 존재하는지 살펴보자.

활성화 함수 역함수 존재 여부 역함수 식
Identity $$ f(x) = x $$ $$ f^{-1}(y) = y $$
Sigmoid $$ f(x) = \frac{1}{1 + e^{-x}} $$ $$ f^{-1}(y) = \ln\left(\frac{y}{1-y}\right) $$
Tanh $$ f(x) = \tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}} $$ $$ f^{-1}(y) = \tanh^{-1}(y) = \frac{1}{2}\ln\left(\frac{1+y}{1-y}\right) $$
ReLU $$ f(x) = \max(0, x) $$ 아니요 존재하지 않음
Leaky ReLU $$ f(x) = \max(0.01x, x) $$ 아니요 존재하지 않음
Softmax $$ f(x_i) = \frac{e^{x_i}}{\sum_{j} e^{x_j}} $$ 아니요 존재하지 않음
Swish $$ f(x) = x \cdot \sigma(x) = \frac{x}{1 + e^{-x}} $$ 아니요 존재하지 않음
GELU $$ f(x) = x \cdot \Phi(x) $$ (where $$ \Phi $$ is the CDF of the normal distribution) 아니요 존재하지 않음

여기서 Identity function, Sigmoid, Tanh의 경우에는 역함수가 존재하기 때문에 이 activation function들에 대해서는 출력으로부터 입력을 복원할 수 있다.


Autoencoder

그럼 가설에 대해 실험하기위해 여기서는 MNIST 데이터셋에 대해 autoencoder를 사용하기로 했다. Autoencoder는 Encoder와 Decoder으로 구성되어 있다. Encoder는 입력을 latent vector로 압축하는데 사용하고, Decoder는 압축된 latent vector를 이미지로 재구성하는데 사용된다.

원했던 것은 학습할 때 Encoder와 Decoder 구조가 한 번에 학습된다. 이 과정에서 사용한 Encoder와 Decoder 구조는 아래와 같다. 모든 layer는 역행렬이 존재하는 fully connected layer를 사용했고, 모든 activation function은 역함수가 존재하는 Tanh와 Sigmoid 함수를 사용했다.

Model(
  (encoder_conv): Sequential(
    (0): Linear(in_features=784, out_features=512, bias=False)
    (1): Tanh()
    (2): Linear(in_features=512, out_features=256, bias=False)
    (3): Tanh()
    (4): Flatten(start_dim=1, end_dim=-1)
  )
  (encoder_mean): Sequential(
    (0): Linear(in_features=256, out_features=16, bias=False)
  )
  (encoder_log_var): Sequential(
    (0): Linear(in_features=256, out_features=16, bias=False)
  )
  (decoder): Sequential(
    (0): Linear(in_features=16, out_features=256, bias=False)
    (1): Tanh()
    (2): Linear(in_features=256, out_features=512, bias=False)
    (3): Tanh()
    (4): Linear(in_features=512, out_features=784, bias=False)
    (5): Sigmoid()
  )
)

학습 결과

학습이 잘 되었나 확인하기 위해서 2가지를 확인했다.

  1. 원본 이미지를 넣었을 때 생성된 이미지가 제대로 복원되는가(encoder + decoder 학습 확인)
  2. 원본 이미지를 넣었을 때 숫자별로 latent vector가 잘 뭉쳐있는가(encoder 학습 확인)

아래가 원본 이미지 복원에 대한 이미지이다. 홀수번째 줄에는 원본 이미지, 짝수번 째 줄에는 복원된 이미지가 있다. 전체적으로 약간 희미한 느낌이 있으나 원본 이미지의 특징을 어느정도 반영하여 생성된 모습이다.

아래가 encoder를 거친 latent vector의 분포를 나타내는 그림이다. 잘 분리가 안된 부분도 있으나 전반적으로는 같은 라벨의 숫자들끼리 뭉쳐져있는 모습이다.

Decoder를 Encoder로 변환해보자.

이제 결과로부터 입력을 생성하는 과정을 진행해보자. Decoder의 입력은 latent vector가 입력이고, 출력은 이미지이다. Decoder를 변환하여 Encoder 처럼 입력은 이미지이고, 출력은 latent vector가 되도록 만들것이다. 만약 예상과 같다면 Decoder로 만든 Encoder는 실제 Encoder와 동일한 분포를 띄면서 같은 라벨의 숫자들은 뭉쳐있고, 다른 라벨의 숫자는 분리되어있어야한다.

기존의 decoder 구조는 아래와 같다.

(decoder): Sequential(
    (0): Linear(in_features=16, out_features=256, bias=False)
    (1): Tanh()
    (2): Linear(in_features=256, out_features=512, bias=False)
    (3): Tanh()
    (4): Linear(in_features=512, out_features=784, bias=False)
    (5): Sigmoid()
 )

아래는 Decoder 구조를 Encoder 구조처럼 역으로 돌린것이다. Fully connected layer는 유사 역행렬을 사용했고, sigmoid와 tanh 함수는 역함수를 사용했다.

(inversed decoder): Sequential(
    (0): SigmoidInverse()
    (1): Linear(in_features=784, out_features=512, bias=False)
    (2): TanhInverse()
    (3): Linear(in_features=512, out_features=256, bias=False)
    (4): TanhInverse()
    (5): Linear(in_features=256, out_features=16, bias=False)
 )

결과

아래는 decoder를 돌린 모델로부터 입력으로 이미지를 주고, latent vector를 뽑은 것이다.

예상과는 달리 숫자들이 전혀 분리되지 않은 모습이다. 그럼 왜 이런 결과가 나온것일까?

원인 분석

  1. 입력으로 이미지가 들어갈 때 픽셀값이 0인 경우 inverse sigmoid에서 에러가 발생한다.
    아래 두 그래프는 sigmoid 함수와 그의 역함수인 logit 함수이다. Decoder를 역으로 변환한 모델의 첫 번째 layer가 아래의 logit 함수이다. 이미지 중에서는 픽셀값이 0인 경우가 있다. 이를 logit 함수에 넣으면 값이 -inf로 발산하게 된다. 그래서 이를 처리하기 위해 최솟값을 0.0001로 설정하였으나 이도 정확하지 않고, 0과 가까운 곳에서는 값이 조금만 달라져도 함수값이 크게 바뀌기 때문에 오류가 발생하게 된다.
    sigmoid inverse sigmoid(logit)
  1. Tanh의 역함수의 x 값의 범위가 정해져있다.
    아래 그래프는 tanh 함수와 그의 역함수인 Inverse Hyperbolic Tangent함수이다. Inverse Hyperbolic Tangent 함수를 보면 x값의 범위가 -1에서 1까지이다. 만약 해당 함수에 범위 밖의 x값이 주어진다면 계산이 불가능하다. 하지만 모델은 원하는 출력을 얻기 위해 최적화한 것이기 때문에 역함수를 취해서 계산을 할 경우 -1과 1밖의 값이 나올 수 있다. 그래서 이를 처리하기 위한 최소, 최대 clamp를 사용했으나 이 또한 근사치이기 때문에 실제값과는 큰 차이가 있다. 또한 x값이 -1또는 1에 가까워질수록 함수값이 크게 변하게 되는 데 이것도 출력값에 큰 영향을 주게된다.
    sigmoid inverse sigmoid(logit)

결론

결론적으로 가설처럼 출력으로부터 입력을 복원하는 과정은 성공하지 못했다. Activation function의 경우 역함수를 취했을 때 값의 제한이 있었기 때문이다.

그럼 결과로부터 입력을 복원하는 것은 완전히 불가능한 일일까? Autoencoder를 다시 살펴보면 이것이 출력을 통해 입력을 복원하는 모델임을 확인할 수 있다. Decoder는 latent vector로부터 이미지를 생성하는데 Encoder는 이미지로부터 latent vector를 생성하는 모델이기 때문이다. 이처럼 모델을 역함수와 역행렬을 사용해서 출력, 입력 변환은 어려울 수 있으나 또 다른 딥러닝 모델을 사용해서 출력, 입력 변환은 가능한것이다.