VAE 기반 신약 분자 생성 #1 VAE와 KL 발산 #
#2026-02-28
#1
약을 만드는 과정의 첫 번째 관문은 “어떤 분자를 약으로 쓸 것인가"를 정하는 것이다. 자연에 존재하는 분자 중에서 고르는 것도 있지만, 완전히 새로운 분자를 설계하는 것이 현대 신약 개발의 핵심이다.
그런데 여기서 문제의 규모를 실감해야 한다. 약이 될 수 있는 분자의 종류가 대략 10의 60제곱 개 이상이라고 추정된다. 이게 얼마나 큰 수인지 감을 잡기 위해 비교하면, 우주에 존재하는 원자의 수가 약 10의 80제곱 개다. 가능한 약물 분자의 수는 우주의 원자 수에 견줄 만큼 거대하다. 이 광활한 공간을 하나하나 탐색하는 건 당연히 불가능하다.
그렇다면 영리한 방법이 필요하다. 이미 약으로 알려진 분자들의 패턴을 학습해서, 그 패턴에 부합하는 새로운 분자를 자동으로 만들어내면 어떨까? 이것이 바로 이 챕터에서 하려는 일이다. 기존 약물 분자들을 딥러닝 모델에 학습시키고, 그 모델이 학습한 패턴을 바탕으로 아직 세상에 없었던 새로운 분자를 생성하는 것이다.
| 단계 | 내용 |
|---|---|
| 데이터 | MUV 데이터셋에서 SMILES 문자열 로드 |
| 전처리 | 문자 토큰화 + 최대 길이 계산 |
| 모델 | VAE (Variational Autoencoder): SMILES → 잠재 벡터 → SMILES |
| 학습 | 시퀀스-투-시퀀스 방식 |
| 생성 | 랜덤 잠재 벡터 → 새 분자 디코딩 → RDKit 유효성 검증 |
#
#2 SMILES: 분자를 글자로 적는 법
딥러닝 모델에 분자를 넣으려면 분자를 컴퓨터가 다룰 수 있는 형태로 표현해야 한다. 분자는 원래 3차원 공간에서 원자들이 결합으로 연결된 구조인데, 이걸 어떻게 텍스트로 바꿀 수 있을까?
SMILES라는 표기법이 바로 그 해결책이다. 분자 구조를 아스키 문자열로 변환하는 규칙이다. 예를 들어 에탄올(술의 주성분)은 탄소 두 개에 산소 하나가 붙어 있는 간단한 분자인데, SMILES로 쓰면 CCO라는 세 글자가 된다. C는 탄소, O는 산소다. 아스피린은 CC(=O)Oc1ccccc1C(=O)O라는 좀 더 복잡한 문자열이 된다. 괄호는 분자의 가지(곁사슬)를 나타내고, 소문자 c는 방향족 탄소(벤젠 고리 같은 구조)를 나타내고, 등호는 이중결합을 나타내고, 숫자는 고리가 닫히는 위치를 표시한다.
아스피린: CC(=O)Oc1ccccc1C(=O)O
에탄올: CCO
카페인: Cn1cnc2c1c(=O)n(c(=O)n2C)C
포도당: OC[C@H]1OC(O)[C@H](O)[C@@H](O)[C@@H]1O
이 표기법의 천재적인 점은 분자라는 2차원/3차원 구조를 1차원 문자열로 변환한다는 것이다. 문자열이 되는 순간, 자연어 처리(NLP)에서 발전해온 모든 기법을 그대로 갖다 쓸 수 있다. DNA 서열을 Conv1D로 분석했던 것과 마찬가지로, SMILES 문자열도 시퀀스 모델로 다룰 수 있다. 분자 설계 문제가 텍스트 생성 문제로 바뀌는 것이다.
SMILES 규칙:
C → 탄소 원자
O, N, S → 산소, 질소, 황
c → 방향족 탄소 (소문자 = 방향족)
(...) → 가지(branch)
=, # → 이중결합, 삼중결합
1...1 → 고리(ring) 닫기
[C@@H] → 입체화학 정보
#
#3 오토인코더와 VAE
VAE를 이해하려면 먼저 오토인코더라는 더 단순한 구조부터 이해해야 한다. 오토인코더의 아이디어는 놀라울 정도로 간단하다. 입력을 아주 작은 벡터로 압축한 다음, 그 작은 벡터에서 다시 원래 입력을 복원하도록 학습하는 것이다. 마치 긴 소설을 한 문단 요약으로 압축했다가, 그 요약만 보고 원래 소설을 다시 쓰는 것과 비슷하다.
입력 x ──→ 인코더 ──→ z (잠재 벡터) ──→ 디코더 ──→ 복원 x'
(낮은 차원)
목표: x ≈ x' (입력을 최대한 그대로 복원)
구체적으로 보면 인코더와 디코더 두 부분으로 나뉜다. 인코더는 SMILES 문자열 같은 고차원 입력을 받아서 z라는 저차원 벡터(잠재 벡터, latent vector)로 압축한다. 디코더는 이 z를 받아서 원래 입력을 복원한다. 학습 목표는 “입력과 복원된 출력이 최대한 비슷해야 한다"는 것이다. 이 과정에서 잠재 벡터 z는 입력의 핵심 정보만 담고 있어야 한다. 입력이 100차원인데 z가 10차원이라면, 100개의 정보를 10개로 압축해야 하니까 정말 중요한 특징만 남길 수밖에 없다. 이 z가 입력 데이터의 본질적인 “요약"이 되는 것이다. 분자의 맥락에서 말하면, 수십 글자의 SMILES 문자열이 수십 차원의 잠재 벡터로 압축된다. 이 벡터의 각 차원이 분자의 어떤 추상적인 특성(크기, 소수성, 고리 구조의 복잡도 등)을 포착하게 된다.
오토인코더로 분자를 압축하고 복원하는 건 잘 된다. 하지만 우리의 진짜 목표는 “새로운 분자를 생성하는 것"이다. 기존 분자를 복원하는 게 아니라, 아직 존재하지 않는 분자를 만들어내고 싶다. 자연스러운 생각은 이거다. 잠재 공간에서 아무 점이나 찍고, 디코더에 넣으면 새로운 분자가 나오지 않을까? 여기서 일반 오토인코더의 근본적인 문제가 드러난다. 오토인코더의 잠재 공간은 불규칙하다. 학습 데이터의 각 분자가 잠재 공간의 특정 점에 매핑되는데, 이 점들이 고르게 분포하지 않는다. 어떤 영역에는 점들이 빽빽하게 몰려 있고, 어떤 영역은 텅 비어 있다.
문제: 잠재 공간(latent space)이 불규칙하다.
AE의 잠재 공간:
z₁
z₂ z₃
z₄
z₅
(빈 공간이 있음 → 빈 곳에서 샘플링하면 의미 없는 출력)
비어 있는 영역에서 점을 찍어서 디코더에 넣으면 어떻게 될까? 디코더는 그 영역에서 한 번도 학습한 적이 없으니, 무의미한 쓰레기를 내놓는다. 유효한 분자가 아닌 문자열의 난잡한 조합이 나오는 것이다. 잠재 공간의 빈 곳은 “디코더가 모르는 미지의 영역"인 셈이다. 즉 일반 오토인코더는 훌륭한 압축기이지만 형편없는 생성기다.
변분 오토인코더(VAE)는 잠재 벡터를 하나의 고정된 점이 아니라 확률 분포로 표현함으로써 이를 해결한다.
VAE는 잠재 벡터를 **확률 분포**로 표현한다.
입력 x ──→ 인코더 ──→ μ (평균), σ² (분산)
↓
z ~ N(μ, σ²) ← 정규분포에서 샘플링
↓
디코더 ──→ 복원 x'
일반 오토인코더에서 인코더는 입력을 받아서 “이 분자의 잠재 벡터는 [1.3, -0.7, 2.1]이다"라고 하나의 점을 찍었다. VAE의 인코더는 대신 “이 분자의 잠재 벡터는 평균이 [1.3, -0.7, 2.1]이고 분산이 [0.2, 0.5, 0.1]인 정규분포를 따른다"라고 분포를 내놓는다. 즉 인코더가 μ(평균)와 σ²(분산) 두 벡터를 출력하는 것이다.
그 다음, 이 분포에서 실제 잠재 벡터 z를 하나 샘플링한다. z는 매번 약간씩 다른 값이 나온다. 같은 분자를 넣어도 z가 조금씩 흔들리는 것이다. 디코더는 이 흔들리는 z를 받아서도 원래 분자를 복원해야 한다.
이것만으로도 잠재 공간이 더 부드러워진다. z가 매번 정확히 같은 점이 아니라 주변의 약간 다른 점에서도 올 수 있으니, 디코더는 특정 점 하나에만 적응하는 게 아니라 그 주변 영역 전체에서 올바른 출력을 내놓도록 학습된다. 잠재 공간에 빈틈이 줄어드는 것이다.
#
#4 KL 발산
하지만 여기서 한 가지가 더 필요하다. 각 분자가 만들어내는 분포가 제각각이면, 여전히 잠재 공간에 빈 영역이 생길 수 있다. 어떤 분자의 분포는 잠재 공간의 한쪽 구석에 있고, 다른 분자의 분포는 반대쪽 구석에 있으면, 그 사이는 여전히 비어 있다. 이걸 해결하는 것이 KL 발산(Kullback-Leibler divergence)이라는 항이다. VAE의 손실 함수는 두 부분으로 이루어진다. 재구성 손실과 KL 발산이다.
학습 목표:
Loss = 재구성 손실 + KL 발산
재구성 손실: x와 x'가 얼마나 다른가 (분류 cross-entropy)
KL 발산: z의 분포 N(μ,σ²)가 표준 정규분포 N(0,1)에서 얼마나 벗어났는가
재구성 손실은 익숙하다. 입력과 복원된 출력이 얼마나 다른가를 재는 것이다. 이전 챕터들에서 봤던 cross-entropy와 본질적으로 같다. KL 발산은 새로 등장하는 것인데, “인코더가 내놓는 분포 N(μ, σ²)가 표준 정규분포 N(0, 1)에서 얼마나 벗어나 있는가"를 재는 것이다. 이 항이 손실에 포함되면 모델은 인코더의 출력 분포를 표준 정규분포에 가깝게 만들라는 압력을 받는다.
구체적으로 무슨 일이 벌어지는지 보자. μ가 0에서 멀어지면 KL 발산이 커진다. 그러니 모델은 μ를 0 근처로 모으려 한다. σ가 1에서 벗어나면(너무 크거나 너무 작으면) 역시 KL 발산이 커진다. 그러니 σ를 1 근처로 유지하려 한다.
KL 발산 직관:
KL(N(μ,σ²) || N(0,1)) = 1/2 × (μ² + σ² - 1 - log σ²)
이 항이 있으면:
- μ → 0 에 가까워지도록 강제 (중심에 모임)
- σ → 1 에 가까워지도록 강제 (퍼짐이 일정)
→ 잠재 공간이 N(0,1)처럼 균일하고 연속적
결과적으로 모든 분자의 잠재 분포가 원점 근처에 모이고, 퍼짐이 비슷해진다. 잠재 공간 전체가 표준 정규분포처럼 균일하고 연속적으로 채워지는 것이다. 이제 잠재 공간의 어느 점을 찍어도 그 근처에 의미 있는 분자가 있다. 빈 공간이 사라진 것이다.
결과: VAE의 잠재 공간
··················
·················· ← 빈 공간 없이 균일하게 채워짐
·················· → 어디서 샘플링해도 의미 있는 분자 생성 가능
물론 재구성 손실과 KL 발산 사이에는 긴장이 있다. 재구성 손실만 줄이려면 각 분자에 고유한 위치를 부여해서 디코더가 헷갈리지 않게 하는 게 좋다. KL 발산만 줄이려면 모든 분자의 분포를 똑같이 N(0,1)로 만들어야 하는데, 그러면 디코더가 아무것도 구분할 수 없다. VAE는 이 두 힘 사이에서 균형을 찾아야 한다. “충분히 잘 복원하면서도 잠재 공간은 충분히 규칙적인” 최적의 지점을 찾는 것이다.
#
#5 재파라미터화 트릭 (Reparameterization Trick)
여기서 기술적으로 중요한 문제가 하나 있다. 인코더가 μ와 σ를 내놓고, 거기서 z를 샘플링한다고 했다. 그런데 “샘플링"이라는 연산은 랜덤이다. 주사위를 굴리는 것과 같다. 신경망을 학습시키려면 역전파를 해야 하는데, 역전파는 출력에서 입력 방향으로 그래디언트를 계산하는 과정이다. 랜덤 연산은 미분할 수가 없다. “주사위 결과를 μ에 대해 미분하라"는 건 수학적으로 의미가 없다.
재파라미터화 트릭은 z = μ + ε × σ로 쓰는 것이다. 여기서 ε은 표준 정규분포 N(0,1)에서 샘플링한 값이다. 이 두 식이 수학적으로 동일하다는 걸 확인해보자. z가 N(μ, σ²)를 따른다는 건 z = μ + σ × (어떤 표준 정규 변수)라는 뜻이다. 그 표준 정규 변수를 ε이라고 부른 것뿐이다. 결과는 완전히 같다. 하지만 그래디언트 계산 관점에서는 완전히 달라진다. 원래 식에서 z는 μ와 σ에 의존하는 랜덤 변수였다. 랜덤성과 파라미터 의존성이 뒤섞여 있어서 미분이 안 됐다. 재파라미터화 후에는 ε이 모든 랜덤성을 담당하고, ε은 모델의 파라미터와 아무 관계가 없다. z = μ + ε × σ에서 ε은 그냥 상수처럼 취급할 수 있다. 그러면 z를 μ에 대해 미분하면 1이고, σ에 대해 미분하면 ε이다. 깔끔하게 미분이 된다.
문제: `z ~ N(μ, σ²)` 샘플링 연산은 미분 불가 → 역전파 불가
해결:
z = μ + ε × σ (ε ~ N(0,1))
- ε는 모델 파라미터와 무관하게 샘플링
- μ, σ는 파라미터에 의존하므로 미분 가능
비유하자면 이렇다. 랜덤성이라는 불확실한 요소를 네트워크 밖으로 빼내서 “외부에서 주입되는 잡음"으로 만든 것이다. 네트워크 내부의 연산은 모두 결정적(deterministic)이고 미분 가능하다. 랜덤 잡음 ε은 밖에서 던져주는 주사위일 뿐이고, 네트워크는 그 주사위 결과에 μ와 σ로 변환을 가하는 것이다. 이 간단한 수학적 트릭 하나가 VAE의 학습을 가능하게 만드는 결정적인 열쇠다.
재파라미터화 트릭 직관:
랜덤성을 ε이라는 외부 잡음으로 분리하고,
학습 가능한 부분(μ, σ)만 네트워크가 담당한다.
모델이 학습을 마치면 생성 단계는 간단하다. 표준 정규분포 N(0,1)에서 잠재 벡터 z를 하나 랜덤으로 뽑는다. 이 z를 디코더에 넣으면 SMILES 문자열이 한 글자씩 생성된다. KL 발산 덕분에 잠재 공간이 표준 정규분포처럼 균일하게 채워져 있으므로, 아무 데서나 뽑은 z도 의미 있는 분자에 대응할 가능성이 높다. 생성된 SMILES가 실제로 유효한 분자인지는 RDKit이라는 화학 라이브러리로 검증한다. SMILES 규칙을 만족하는 문자열이라도 화학적으로 불가능한 구조(원자가가 맞지 않는다든지)일 수 있기 때문이다. 유효성 검증을 통과한 분자만이 실제 신약 후보로 고려될 수 있다.
#
#6 정리
VAE 기반 신약 분자 생성 파이프라인은 다음과 같다. MUV 데이터셋에서 기존 약물 분자들의 SMILES를 가져온다. 이 문자열들을 VAE에 학습시킨다. 인코더가 각 분자를 확률 분포로 압축하고, 거기서 샘플링한 잠재 벡터를 디코더가 다시 원래 분자로 복원하도록 학습한다. 재구성 손실이 복원 품질을 보장하고, KL 발산이 잠재 공간의 규칙성을 보장한다. 학습이 끝나면 잠재 공간에서 랜덤하게 점을 찍고 디코더로 새 분자를 만들어낸다.
이전 챕터들에서는 딥러닝으로 “예측"을 했다. DNA 서열을 보고 결합 여부를 예측하고, 망막 사진을 보고 등급을 예측했다. 이 챕터에서는 딥러닝으로 “생성"을 한다. 기존 데이터의 패턴을 학습해서 완전히 새로운 것을 만들어내는 것이다.