ML 연구자를 위한 분자생물학 기초

먼저 읽을 장 1.2 AI for Science의 지형

생명과학 데이터셋은 표 하나, 서열 파일 하나로 내려받을 수 있다. 그러나 그 숫자와 문자는 세포 안의 분자, 그 분자를 꺼내고 측정하는 실험, 그리고 결과를 정리한 데이터베이스를 차례로 거쳐 만들어졌다. 각 단계는 데이터에 고유한 구조와 잡음을 남긴다. 같은 유전체를 공유하는 세포들, 진화로 이어진 단백질 가족, 같은 날 같은 시약으로 처리한 시료 묶음이 그런 구조다. 이 구조를 모르면 모델은 생물학 대신 실험 설계를 학습하고, 연구자는 그것을 발견으로 보고한다.

이 장은 생물학 교과서를 대신하지 않는다. ML 연구자가 생명과학 데이터를 받았을 때 "이 값은 어떤 분자를, 어떤 실험으로, 어떤 잡음과 함께 잰 것인가"를 물을 수 있도록 최소한의 지도를 준다. 서열 모델(29.2장), 구조 예측(29.3장), 단일세포 데이터(29.4장), 평가(29.5장)는 모두 이 지도 위에서 진행된다.

학습 목표

  • 중심 원리로 서열·구조·발현 데이터의 관계를 설명한다
  • 생명과학 데이터가 만들어지는 과정과 주요 데이터 종류를 구분한다
  • 측정 잡음과 배치 효과가 데이터에 남기는 흔적을 식별한다

생명의 분자

왜 필요한가. 생명과학 데이터의 대부분은 세 종류의 분자, DNA·RNA·단백질 중 하나를 잰 것이다. 셋의 관계를 알아야 "DNA 서열로 단백질 기능을 예측한다", "발현량으로 세포 상태를 분류한다" 같은 과제가 어떤 정보를 입력으로 쓰고 어떤 정보를 빠뜨리는지 판단할 수 있다.

세 분자.

분자 단위(알파벳) 역할 ML에서의 모습
DNA 염기 A·C·G·T (4종) 유전 정보의 저장. 두 가닥이 A–T, G–C로 짝지어 이중나선을 이룬다 길이가 매우 긴 문자열
RNA 염기 A·C·G·U (4종) DNA의 일부를 복사한 사본. mRNA는 단백질의 설계도를 운반한다 문자열, 또는 유전자별 개수(발현량)
단백질 아미노산 20종(표준) 효소·구조·신호 전달 등 세포 기능의 대부분을 수행한다 문자열, 3차원 좌표

중심 원리. 유전 정보는 DNA → RNA → 단백질 방향으로 흐른다. DNA의 한 구간(유전자)을 RNA로 복사하는 과정을 전사(transcription), mRNA의 염기 세 개(코돈, codon)를 아미노산 하나로 읽어 단백질 사슬을 만드는 과정을 번역(translation)이라 한다. Crick은 이를 서열 정보가 어디서 어디로 전달될 수 있는지에 대한 원리로 정리했다. 핵심은 단백질에서 핵산(DNA·RNA)으로, 또는 단백질에서 단백질로는 서열 정보가 전달되지 않는다는 것이다(Crick, 1970). RNA에서 DNA로 가는 역전사(reverse transcription)는 이 원리와 모순되지 않는다.

코돈은 염기 세 개이므로 43=644^3 = 64종이다. 그중 61개가 아미노산 20종을 지정하고 3개는 번역을 멈추는 정지 코돈(stop codon)이다. 아미노산보다 코돈이 많으므로 여러 코돈이 같은 아미노산을 지정한다. 이 중복성(degeneracy) 때문에 DNA의 한 자리가 바뀌어도 단백질이 그대로일 수 있다(동의 변이, synonymous variant). 표준 유전 부호로 짧은 유전자를 번역해 확인한다.

import numpy as np

BASES = "TCAG"
AA = "FFLLSSSSYY**CC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG"   # 표준 유전 부호
CODON = {a + b + c: AA[16 * i + 4 * j + k]
         for i, a in enumerate(BASES) for j, b in enumerate(BASES) for k, c in enumerate(BASES)}

def translate(dna):
    protein = ""
    for s in range(0, len(dna) - 2, 3):
        aa = CODON[dna[s:s + 3]]
        if aa == "*":                                  # 정지 코돈
            break
        protein += aa
    return protein

def point(seq, pos, base):                             # 한 자리 치환
    return seq[:pos] + base + seq[pos + 1:]

gene = "ATGGCTAAAGGTTGGTAA"                            # 코딩 가닥 5'→3'
print("mRNA        :", gene.replace("T", "U"))         # 전사: T 대신 U
print("단백질      :", translate(gene))
print("6번째 T→C   :", translate(point(gene, 5, "C")))   # GCT→GCC
print("11번째 G→A  :", translate(point(gene, 10, "A")))  # GGT→GAT
n_stop = sum(v == "*" for v in CODON.values())
print(f"코돈 {len(CODON)}개 = 아미노산 지정 {len(CODON) - n_stop} + 정지 {n_stop}")
mRNA        : AUGGCUAAAGGUUGGUAA
단백질      : MAKGW
6번째 T→C   : MAKGW
11번째 G→A  : MAKDW
코돈 64개 = 아미노산 지정 61 + 정지 3

6번째 염기를 바꾼 변이는 코돈 GCT를 GCC로 바꾸지만 둘 다 알라닌(A)이라 단백질이 같다. 11번째 염기를 바꾼 변이는 GGT(글리신, G)를 GAT(아스파르트산, D)로 바꾼다. DNA 서열에서 한 글자 차이라는 점은 같지만 단백질 수준의 결과는 다르다. 그래서 같은 변이 데이터도 어느 분자 수준에서 표현하느냐에 따라 모델이 보는 정보가 달라진다.

서열에서 구조와 기능으로. 아미노산 사슬은 접혀서 3차원 구조를 이루고, 구조가 기능(어떤 분자와 결합하는가, 어떤 반응을 촉매하는가)을 정한다. 많은 단백질은 서열만으로 접힌 구조가 거의 정해진다. 이것이 서열에서 구조를 예측하는 문제(29.3장)의 출발점이다. 다만 일정한 구조 없이 유연하게 움직이는 영역도 흔하고, 결합 상대에 따라 모양이 바뀌는 단백질도 있다. "서열 → 구조 → 기능"은 한 방향의 결정론적 함수가 아니라 정보가 점점 맥락에 의존하게 되는 사슬이다.

유전자 발현. 한 개체의 거의 모든 세포는 같은 유전체를 가진다. 간세포와 신경세포가 다른 것은 DNA가 달라서가 아니라 어떤 유전자를 얼마나 전사하는가(유전자 발현, gene expression)가 달라서다. 그래서 세포의 종류와 상태를 구분하는 데이터는 유전체 서열이 아니라 발현량이다(29.4장). 한 유전자에서 RNA 조각을 서로 다르게 이어 붙이는 대체 스플라이싱(alternative splicing) 때문에 한 유전자가 여러 단백질을 만들기도 한다. "유전자 하나 = 단백질 하나"라는 가정은 데이터베이스의 식별자를 짝지을 때 자주 깨진다.

데이터의 종류

왜 필요한가. 데이터 종류마다 자연스러운 표현과 모델, 그리고 고유한 누수 경로가 있다. 종류를 먼저 구분하면 "이 과제에 어떤 귀납적 편향(inductive bias)이 맞는가"(18.1장)를 정할 수 있다.

종류 예 자연스러운 표현 대칭·구조 흔한 함정
서열 DNA, 단백질 아미노산 서열 문자열, one-hot, 토큰 진화로 이어진 가족(상동성) 비슷한 서열이 train·test에 나뉨(29.5장)
구조 원자 좌표, 거리 행렬 점구름, 그래프 회전·평행이동 대칭(7.10장) 예측 구조를 실험 구조처럼 사용(29.3장)
발현량 RNA-seq 개수, 단일세포 개수 행렬 유전자 × 시료 행렬 시료 간 측정 깊이 차이 배치 효과(29.4장)
영상 현미경, 조직 슬라이드 이미지 텐서 평행이동·회전 염색·장비 차이, 같은 시료의 여러 조각
상호작용 단백질–단백질, 유전자 조절 그래프 노드 순서 대칭 관측되지 않은 쌍을 음성으로 간주

한 연구가 여러 종류를 함께 쓰는 일이 많다. 단백질 서열과 구조로 기능을 예측하거나, 발현량과 영상을 짝지어 세포 상태를 해석한다. 이때 종류마다 식별자 체계가 달라서(유전자 이름, 전사체 식별자, 단백질 식별자) 짝짓기 단계에서 오류가 생기기 쉽다.

실험과 측정

왜 필요한가. 모델이 학습하는 값은 분자 그 자체가 아니라 측정값이다. 측정 과정을 모르면 측정이 만든 패턴을 생물학적 패턴으로 오해한다.

시퀀싱. 현재의 고처리량 시퀀싱은 대개 DNA(RNA는 cDNA로 바꾼 뒤)를 짧은 조각으로 자르고, 각 조각의 일부를 읽어 짧은 read를 만든다. 유전체 연구는 read를 이어 붙이거나 참조 유전체에 정렬해 서열·변이를 얻는다. 발현 연구(RNA-seq)는 각 유전자에 정렬된 read 수를 세어 발현량으로 쓴다. 따라서 발현량은 연속적인 농도가 아니라 유한한 표본에서 센 개수다. 시료 하나에서 읽은 read 수(시퀀싱 깊이, depth)가 작으면 적게 발현된 유전자는 실제로 있어도 0으로 관측된다.

분석법. 단백질 기능은 대개 분석법(assay)으로 잰다. 결합 세기, 효소 활성, 형광 신호, 세포 생존율 같은 값이다. 같은 성질이라도 분석법이 다르면 값의 척도와 잡음이 다르고, 측정 한계(검출 하한·상한) 밖의 값은 잘려 기록된다. 서로 다른 분석법의 값을 한 표에 합쳐 회귀 target으로 쓰는 것은 단위가 다른 값을 섞는 것과 같다.

반복과 잡음의 원천. 같은 시료를 두 번 측정하는 기술적 반복(technical replicate)은 측정 과정의 잡음만 보여 준다. 같은 조건의 다른 개체·다른 배양을 측정하는 생물학적 반복(biological replicate)은 여기에 개체 간 차이가 더해진다. 모델이 "새 개체"에 일반화한다고 주장하려면 생물학적 반복의 변동을 견뎌야 한다. 아래 시뮬레이션은 유전자 2,000개의 실제 비율에서 read를 Poisson 표본으로 뽑는다. 생물학적 반복은 유전자별로 약간 다른 비율을 가진 다른 개체다.

rng = np.random.default_rng(0)
G = 2000                                               # 유전자 수
frac = rng.lognormal(0, 1.5, G)
frac /= frac.sum()                                     # (2000,) 시료의 실제 mRNA 비율
bio = frac * rng.lognormal(0, 0.3, G)
bio /= bio.sum()                                       # (2000,) 같은 조건의 다른 개체

def logcorr(a, b):
    return np.corrcoef(np.log1p(a), np.log1p(b))[0, 1]

for depth in [10_000, 1_000_000]:                      # 시료 하나에서 읽은 read 수
    rep1 = rng.poisson(depth * frac)                   # (2000,) 기술 반복 1
    rep2 = rng.poisson(depth * frac)                   # (2000,) 기술 반복 2
    other = rng.poisson(depth * bio)                   # (2000,) 생물학적 반복
    print(f"depth {depth:>9,}: 0인 유전자 {np.mean(rep1 == 0):4.0%}  "
          f"기술 반복 r {logcorr(rep1, rep2):.3f}  생물학적 반복 r {logcorr(rep1, other):.3f}")
depth    10,000: 0인 유전자  29%  기술 반복 r 0.839  생물학적 반복 r 0.827
depth 1,000,000: 0인 유전자   0%  기술 반복 r 0.993  생물학적 반복 r 0.973

두 가지를 읽을 수 있다. 첫째, 깊이가 얕으면 유전자의 상당수가 0으로 관측되고 기술적 반복끼리의 상관도 낮다. 이 0은 "발현되지 않음"이 아니라 "표본에 잡히지 않음"이다(29.4장). 둘째, 깊이를 100배 늘리면 기술적 반복의 상관은 거의 1에 가까워지지만 생물학적 반복의 상관은 그만큼 오르지 않는다. 깊이를 늘려 줄일 수 있는 것은 측정 잡음뿐이고, 개체 간 차이는 남는다.

배치 효과. 실험은 한 번에 다 하지 못하고 묶음(batch)으로 나뉜다. 실험 날짜, 시약 lot, 장비, 실험자가 batch마다 다르고, 이 차이는 측정값에 체계적인 편향을 남긴다. 이를 배치 효과(batch effect)라 한다. 배치 효과는 고처리량 측정에서 흔하며, 특히 배치가 연구하려는 결과(조건, 질병 여부)와 함께 움직일 때 잘못된 결론을 만든다(Leek 외, 2010). 조건의 효과가 전혀 없는 데이터를 두 가지 설계로 만들고, 조건을 분류해 본다.

import warnings
from sklearn.model_selection import cross_val_score
from sklearn.neighbors import NearestCentroid
warnings.filterwarnings("ignore", module="threadpoolctl")      # 브라우저 실행 환경의 무해한 경고만 끈다

def make_study(confounded, n=40, genes=200):
    cond = np.repeat([0, 1], n // 2)                   # (40,) 대조군 0, 처리군 1
    batch = cond.copy() if confounded else np.tile([0, 1], n // 2)
    shift = rng.normal(0, 0.5, genes)                  # (200,) 배치 1의 유전자별 측정 편향
    X = rng.normal(0, 1, (n, genes)) + np.outer(batch, shift)   # (40, 200) 조건 효과는 0
    return X, cond

for confounded in [True, False]:
    X, cond = make_study(confounded)
    acc = cross_val_score(NearestCentroid(), X, cond, cv=5).mean()
    print(f"배치와 조건 {'일치' if confounded else '교차'}: 조건 분류 정확도 {acc:.2f}")
배치와 조건 일치: 조건 분류 정확도 1.00
배치와 조건 교차: 조건 분류 정확도 0.60

대조군을 모두 배치 0에서, 처리군을 모두 배치 1에서 측정한 설계에서는 분류 정확도가 1.00이다. 처리 효과는 0인데도 모델은 배치를 보고 조건을 완벽하게 맞힌다. 배치를 조건과 교차시킨 설계에서는 우연 수준(0.5) 근처에 머문다(시료 40개라 0.5에서 조금 벗어난다). 첫 번째 설계에서는 어떤 분석으로도 배치와 조건을 분리할 수 없다. 이 문제는 데이터를 받은 뒤가 아니라 실험을 설계할 때 막아야 한다. 이미 겹친 데이터라면, 그 사실을 한계로 밝히는 것 말고는 할 수 있는 일이 거의 없다.

공개 데이터베이스

왜 필요한가. 생명과학 ML의 데이터는 대부분 공개 데이터베이스에서 온다. 데이터베이스는 여러 실험실의 결과를 모은 것이라 실험 조건이 섞여 있고, 판(release)마다 내용이 바뀌며, 같은 단백질·유전자가 여러 번 등록되어 있기도 하다.

종류 대표 데이터베이스(집필 시점 예시) 담는 것
핵산 서열 GenBank, ENA, DDBJ DNA·RNA 서열과 주석
단백질 서열·기능 UniProt 단백질 서열, 기능 주석, 다른 데이터베이스로의 연결
실험 구조 PDB X선 결정학·cryo-EM·NMR 등으로 결정한 3차원 구조
예측 구조 AlphaFold Protein Structure Database 구조 예측 모델이 만든 구조와 잔기별 신뢰도
발현 GEO, ArrayExpress 발현 측정 데이터와 실험 설명

데이터베이스를 쓸 때 확인할 것은 네 가지다.

  • 판과 내려받은 날짜. 같은 이름의 데이터베이스도 판마다 항목이 추가·삭제·수정된다. 판 번호와 날짜를 기록하지 않으면 결과를 재현할 수 없다(29.5장).
  • 항목의 근거. 기능 주석이 실험으로 확인된 것인지, 비슷한 서열에서 옮겨 온 추정인지 구분하는 표시가 있는지 본다. 추정 주석을 정답 label로 쓰면 모델은 주석 도구를 흉내 낸다.
  • 실험 구조와 예측 구조의 구분. 예측 구조 데이터베이스의 구조는 측정값이 아니라 모델 출력이다(29.3장).
  • 이용 조건. 라이선스, 인용 요구, 인간 유래 데이터의 접근 제한은 데이터베이스마다 다르다. 각 데이터베이스의 이용 약관 페이지에서 확인하고, 사람의 유전 정보처럼 접근이 통제된 데이터는 승인 절차를 따른다.

실습

공개 데이터베이스에서 단백질 하나를 골라 서열·구조·기능 정보를 연결해 정리한다. 웹 화면과 메뉴 이름은 바뀔 수 있으므로 아래 절차는 집필 시점 예시다.

  1. UniProt에서 관심 있는 단백질 하나를 검색해 아미노산 서열(FASTA)과 기능 주석, 기능 주석의 근거 표시를 기록한다.
  2. 같은 항목에서 연결된 실험 구조(PDB 식별자)가 있는지 확인하고, 있으면 구조를 결정한 실험 방법과 해상도를 기록한다. 없으면 예측 구조가 있는지 확인하고 "예측"이라고 표시한다.
  3. 구조 파일의 서열과 UniProt 서열이 같은지 비교한다. 실험 구조는 단백질의 일부 구간만 담는 경우가 많다. 어느 구간인지, 빠진 잔기가 있는지 적는다.
  4. 데이터베이스 이름, 판(또는 내려받은 날짜), 식별자를 표로 남긴다.
  5. 이 단백질의 기능을 예측하는 모델을 만든다고 가정하고, label의 근거와 예상되는 잡음 원천(분석법, 실험실 차이, 추정 주석)을 세 줄로 쓴다.

그다음 번역 함수를 직접 구현한다. DNA의 두 가닥과 세 가지 읽기 틀(reading frame)을 다루는 연습이다. 같은 DNA 구간도 어느 가닥, 어느 틀로 읽느냐에 따라 전혀 다른 단백질 서열이 나온다.

실패 사례

  • 실험 조건과 측정 방식을 모른 채 데이터를 학습에 쓰는 것. 서로 다른 분석법의 값을 하나의 target으로 합치거나, 측정 한계에서 잘린 값을 실제 값처럼 회귀하면 모델은 실험 방법의 차이를 학습한다.
  • 배치와 조건이 겹친 데이터로 분류 성능을 보고하는 것. 정확도가 높을수록 배치 효과일 가능성을 먼저 의심한다. 배치·날짜·장비와 target의 교차표를 확인한다.
  • 0을 "없음"으로 해석하는 것. 개수 데이터의 0은 측정 깊이가 부족해서 생길 수 있다.
  • 추정 주석을 실험으로 확인된 label처럼 쓰는 것. 주석이 서열 유사성으로 옮겨진 것이면, 서열 유사성 모델이 높은 점수를 받는 것은 순환 논리다.
  • 유전자·전사체·단백질 식별자를 일대일로 가정하는 것. 대체 스플라이싱과 데이터베이스 간 식별자 변환에서 짝이 어긋난다.

통과 기준

  • 자신이 쓰려는 생명과학 데이터셋 하나에 대해 측정 분자, 실험 방법, 시퀀싱 깊이나 분석법, 반복의 종류, 배치 정보를 표로 정리하고 주요 잡음 원천 세 가지를 설명한다.
  • 배치(또는 날짜·장비)와 target의 교차표를 만들어 둘이 겹치는지 확인한다.
  • 실습의 단백질 하나에 대해 서열·구조·기능 정보와 데이터베이스 판을 연결한 기록을 남긴다.
  • 연습문제 translate-dna의 테스트를 통과한다.

참고 문헌

  • Crick (1970). Central dogma of molecular biology. Nature, 227, 561–563. — 서열 정보 전달의 허용·금지 방향으로서의 중심 원리
  • Leek 외 (2010). Tackling the widespread and critical impact of batch effects in high-throughput data. Nature Reviews Genetics, 11(10), 733–739. — 고처리량 데이터의 배치 효과와 결과 변수와 겹칠 때의 위험