BioPlayground

🧬
목록으로

멀티모달 파운데이션 자율 단백질 디자인 — Serine Protease 활성 부위 유지 신규 서열 100개 자율 생성

ESM3 (EvolutionaryScale 2024)의 서열·구조·기능 3-트랙 마스크 언어모델로 특정 catalytic triad (Ser-His-Asp) 활성 부위를 고정하고 나머지 서열을 자율 설계. 신규 serine protease 100개 생성, AF2/Boltz로 구조 검증, self-consistency · novelty 정량, RFdiffusion+ProteinMPNN 비교·앙상블까지 완결 파이프라인.

심화
|
50
|
검증 완료 (2026-07)
진행률0/15 (0%)

멀티모달 파운데이션 자율 단백질 디자인 — Serine Protease 활성 부위 유지 신규 서열 100개 자율 생성

편 02(단백질 임베딩)와 편 11(구조·친화도 예측)에서 우리는 파운데이션 모델이 자연의 단백질을 얼마나 잘 이해하는지 봤습니다. 그런데 진짜 흥미로운 질문은 이거입니다. 이 모델이 자연에 없는 새 단백질을 처음부터 코딩할 수 있는가? EvolutionaryScale의 ESM3 (2024)는 서열(Sequence) · 구조(Structure) · 기능(Function) 세 모달리티를 하나의 트랜스포머로 학습해 임의 모달리티 조건부 생성이 가능한 첫 대규모 단백질 파운데이션 모델입니다. 이 편은 그 능력으로 serine protease의 catalytic triad (Ser195-His57-Asp102, chymotrypsin numbering) 활성 부위를 고정하고 나머지 서열을 신규로 자율 설계하는 실전 파이프라인을 구축합니다. 자연 서열과 유사도 30% 이하의 진짜 novel serine protease 후보 100개를 생성하고 AF2/Boltz로 3D 구조 검증까지 완결합니다.

📚 선수 편 권고 (강력 권고)

이 편은 AI×바이오 하드코어 심화 편의 정점입니다. 진입 전에 DryBench의 다음 편들을 먼저 듣고·보시길 강력히 권고드립니다.

선수 편 없이 진입할 경우, 이 편에서 다루는 다중 모달리티 트랜스포머 · 마스크 언어모델 학습 · PyTorch 대용량 모델 관리 원리에 대한 재설명 없이 실전 코드부터 진행하므로 따라가기 어렵습니다.


우리 DryBench에서 이거 배웠잖아

DryBench ai-native #3에서 트랜스포머의 임베딩이 도메인·모달리티에 무관하게 확장 가능하다는 것을, #12에서 PyTorch로 대용량 tensor · GPU 메모리 · mixed precision 최적화 기본기를, #13에서 HuggingFace가 pre-trained 모델의 표준 로딩·추론 API를 제공한다는 것을 배웠습니다.

ESM3는 이 세 원리가 단백질 도메인에서 정점에 도달한 사례입니다. 서열 토큰 · 구조 discrete token(3Di 알파벳 유사) · 기능 도메인 토큰이 하나의 통합 vocabulary로 들어가고, 트랜스포머가 마스크 언어모델 방식으로 임의 위치·임의 모달리티의 토큰을 예측하도록 학습됐습니다. 결과는 놀랍습니다. 특정 활성 부위 구조를 고정하고 나머지 서열을 생성하거나, 원하는 기능(예: 형광 · 촉매 · 결합)을 조건으로 신규 서열을 자율 코딩할 수 있습니다. 이 편은 그 능력을 실전 enzyme design 파이프라인으로 만듭니다.

하드코어 문제 정의

실전 시나리오: 신규 Serine Protease 자율 디자인

Serine protease는 단백질을 분해하는 효소로 chymotrypsin · trypsin · elastase 등이 대표적입니다. 공통 catalytic mechanism: Ser195의 hydroxyl이 nucleophile, His57가 general base로 proton 이동, Asp102가 His57 stabilize. 이 삼각형이 완벽한 3D 배치를 유지해야 활성.

우리 목표:

  • 자연 serine protease와 서열 유사도 30% 이하 (진짜 novel).
  • Ser-His-Asp catalytic triad 3D 좌표 유지 (AF2 예측 RMSD ≤ 2 Å).
  • 100개 후보 자율 생성 (약 30~60분).
  • self-consistency (ESM3 생성 → AF2 예측 → 활성 부위 재현) 40%+ 성공률.
  • 상위 5개를 실측 활성 예측 (Rosetta · MD)으로 재검증 (선택적).

De Novo 단백질 디자인의 스펙트럼

  • Rosetta (2003 이후): 물리학 기반 단백질 디자인 · 오래된 표준.
  • RFdiffusion (Baker Lab 2023): 확산 모델로 backbone 생성. 후속 서열 설계는 ProteinMPNN [1].
  • ProteinMPNN (Baker Lab 2022): backbone → 서열 재설계 표준 [2].
  • RFdiffusion + ProteinMPNN + AF2: 현대 표준 pipeline (backbone → 서열 → 검증).
  • Chroma (2024, Baker Lab): diffusion 기반 all-atom [3].
  • ESM3 (2024): 첫 대규모 멀티모달 파운데이션. 조건부 생성 통합 [4].
  • Boltz-2 (편 11): 예측 특화, 디자인은 제한적.
  • Ligand-conditioned design: RFdiffusion-Motif · Chroma-Ligand.

이 편은 ESM3 조건부 생성 (본편) + AF2/Boltz 검증 + RFdiffusion+ProteinMPNN 앙상블 비교 (확장).

이 편의 목표 지표

  • Chymotrypsin PDB (1AB9 · 4CHA 등)에서 catalytic triad 3D 좌표 추출.
  • ESM3 조건부 생성 100 서열 (target_length 200~250 residues).
  • 다양성 필터 (pairwise identity ≤ 70%).
  • 자연 서열 유사도 검사 (UniProt Swiss-Prot BLAST or ESM 임베딩) → 30% 이하만 유지.
  • AF2/Boltz 구조 예측 → 활성 부위 RMSD ≤ 2 Å 재현 비율 40%+.
  • 평균 pLDDT ≥ 70 (구조 신뢰도).

도구 스택과 인프라 요구

도구역할라이선스
ESM3 (esm3-sm-open-v1 1.4B)3-트랙 조건부 생성학술 오픈, 상업 별도 협의
ESM3 large (open weights, non-commercial)대안 대형 모델EvolutionaryScale license
RFdiffusion + ProteinMPNN앙상블 비교학술 오픈
Boltz-2 (편 11)생성 서열 구조 예측 검증MIT
AlphaFold2 (or ColabFold)대안 구조 검증Apache 2.0
Biopython · PyMOL서열·구조 조작·시각화Biopython License · LGPL
BLAST · Foldseek자연 유사 서열·구조 검색학술 오픈
PyTorch백엔드BSD

인프라 요구:

  • ESM3 1.4B 소형 오픈 가중치: 16-24GB VRAM 서버 GPU 이상. fp16 기준 약 4GB VRAM.
  • ESM3 대형 (98B, 비공개): 학술 API 접근만.
  • RFdiffusion: 24-48GB VRAM 데이터센터 GPU.
  • AF2/Boltz 검증: 편 11 참조 (상급 소비자 GPU 또는 24-48GB VRAM 데이터센터 GPU).
  • 대규모 배치 생성 · 검증: 80GB+ VRAM 데이터센터 워크스테이션 (일반 접근 불가, 클라우드 온디맨드 권장).
  • RAM 32GB 이상.

학습자 재현 예상 비용: 로컬 GPU 사용 시 API 비용 0. 100개 서열 생성 + AF2/Boltz 검증 약 4~8시간 (24-48GB VRAM GPU 기준).

파이프라인 실전 구현

전체 흐름:

mermaid

Step 1. 활성 부위 정의 · ESM3 조건 준비

Chymotrypsin (PDB 4CHA, 5CHA, 1AB9 등)에서 catalytic triad residue의 3D 좌표 추출.

python
from dataclasses import dataclass
from pathlib import Path
import torch
import numpy as np
from Bio.PDB import PDBParser
@dataclass
class ActiveSiteConstraint:
"""활성 부위 조건 정의."""
site_name: str # e.g. "serine_protease_triad"
residue_positions: list[int] # 생성 서열에서 활성 부위 residue 위치 (0-based)
residue_types: list[str] # 각 위치의 아미노산 (예: ["S", "H", "D"])
coordinates_backbone: np.ndarray # (N_residues, 4, 3): N, CA, C, O 좌표
ideal_distances: dict[tuple[int, int], float] # 활성 부위 residue 간 이상적 거리 (Å)
AA_3TO1 = {
"ALA": "A", "ARG": "R", "ASN": "N", "ASP": "D", "CYS": "C",
"GLN": "Q", "GLU": "E", "GLY": "G", "HIS": "H", "ILE": "I",
"LEU": "L", "LYS": "K", "MET": "M", "PHE": "F", "PRO": "P",
"SER": "S", "THR": "T", "TRP": "W", "TYR": "Y", "VAL": "V",
}
def load_catalytic_triad_from_pdb(
pdb_path: Path,
triad_selection: list[tuple[str, int, str]], # [(chain, resid, expected_aa)]
site_name: str = "serine_protease_triad",
) -> ActiveSiteConstraint:
"""PDB에서 catalytic triad 좌표 추출.
Chymotrypsin (4CHA) 예: [("A", 195, "S"), ("A", 57, "H"), ("A", 102, "D")]
"""
parser = PDBParser(QUIET=True)
structure = parser.get_structure("target", str(pdb_path))
residue_types = []
coordinates = []
positions = []
for i, (chain_id, resid, expected_aa) in enumerate(triad_selection):
try:
chain = structure[0][chain_id]
residue = chain[resid]
except KeyError:
raise ValueError(f"PDB 결측: {chain_id}:{resid}")
aa = AA_3TO1.get(residue.get_resname().upper(), "X")
if aa != expected_aa:
raise ValueError(f"활성 부위 {chain_id}:{resid} 아미노산 mismatch: 기대 {expected_aa}, 실제 {aa}")
residue_types.append(aa)
coords = np.array([
residue["N"].get_coord(),
residue["CA"].get_coord(),
residue["C"].get_coord(),
residue["O"].get_coord(),
])
coordinates.append(coords)
positions.append(i)
coordinates_arr = np.stack(coordinates, axis=0)
# 활성 부위 residue 간 CA-CA 이상적 거리 (chymotrypsin 실측 기반)
ca_positions = coordinates_arr[:, 1, :] # CA만
ideal_dist = {}
for i in range(len(triad_selection)):
for j in range(i + 1, len(triad_selection)):
d = float(np.linalg.norm(ca_positions[i] - ca_positions[j]))
ideal_dist[(i, j)] = d
return ActiveSiteConstraint(
site_name=site_name,
residue_positions=positions,
residue_types=residue_types,
coordinates_backbone=coordinates_arr,
ideal_distances=ideal_dist,
)
# 예시: chymotrypsin catalytic triad
CHYMOTRYPSIN_TRIAD_4CHA = [
("A", 57, "H"), # His57 (histidine base)
("A", 102, "D"), # Asp102 (aspartate stabilizer)
("A", 195, "S"), # Ser195 (serine nucleophile)
]

Step 2. ESM3 조건부 생성

ESM3 SDK는 서열/구조/기능 각 트랙 마스크 지정 후 iteratively unmask.

python
from esm.models.esm3 import ESM3
from esm.sdk.api import ESMProtein, GenerationConfig
class ESM3Designer:
"""ESM3 조건부 단백질 디자인."""
def __init__(self, device: str = "cuda", model_name: str = "esm3-sm-open-v1"):
self.device = device
self.model = ESM3.from_pretrained(model_name).to(device).eval()
self.model_name = model_name
@torch.no_grad()
def design_with_active_site(
self,
constraint: ActiveSiteConstraint,
target_length: int = 245, # chymotrypsin 유사 길이
num_samples: int = 100,
temperature: float = 0.7,
seed: int | None = None,
) -> list[dict]:
"""활성 부위 조건 유지하며 신규 서열 생성.
Returns: [{sequence, seed, active_site_preserved: bool}]
"""
if seed is not None:
torch.manual_seed(seed)
np.random.seed(seed)
generated = []
for i in range(num_samples):
# 초기 단백질: 서열 masked, 활성 부위 residue 위치는 고정
seq_list = ["_"] * target_length
# 활성 부위 위치 (target_length 상 임의 배치, 예: 중반)
triad_positions = self._distribute_triad_positions(
target_length, len(constraint.residue_positions),
)
for pos, aa in zip(triad_positions, constraint.residue_types):
seq_list[pos] = aa
initial_sequence = "".join(seq_list)
protein = ESMProtein(sequence=initial_sequence)
# ESM3 iterative decoding
config = GenerationConfig(
track="sequence",
num_steps=max(target_length // 4, 20),
temperature=temperature,
)
try:
result = self.model.generate(protein, config)
# 활성 부위 residue 실제로 유지됐는지 사후 확인
preserved = all(
result.sequence[pos] == aa
for pos, aa in zip(triad_positions, constraint.residue_types)
)
generated.append({
"sequence": result.sequence,
"seed": (seed or 0) + i,
"triad_positions": triad_positions,
"active_site_preserved": preserved,
"length": len(result.sequence),
})
except Exception as e:
print(f"[{i}] 생성 실패: {e}")
return generated
def _distribute_triad_positions(
self, target_length: int, n_triad: int, spread_ratio: float = 0.4,
) -> list[int]:
"""활성 부위 residue를 target_length 상 균등 분포.
실제 chymotrypsin은 57, 102, 195 위치. 245 residue 상 21%, 42%, 80% 위치.
신규 디자인에서는 유사 상대 위치 유지.
"""
chymo_ref = [57, 102, 195]
chymo_length = 245
positions = [
int(target_length * (p / chymo_length))
for p in chymo_ref[:n_triad]
]
return positions

Step 3. 다양성 필터

생성 서열들 사이 pairwise identity 필터로 다양성 확보.

python
def compute_pairwise_identity(seq_a: str, seq_b: str) -> float:
"""길이가 같으면 position-wise, 다르면 alignment."""
if len(seq_a) == len(seq_b):
matches = sum(1 for a, b in zip(seq_a, seq_b) if a == b)
return matches / len(seq_a)
# 길이 다르면 global alignment
from Bio import pairwise2
aln = pairwise2.align.globalxx(seq_a, seq_b, one_alignment_only=True)[0]
return aln.score / max(len(seq_a), len(seq_b))
def diversity_filter(
sequences: list[str],
max_pairwise_identity: float = 0.7,
) -> list[str]:
"""탐욕적 필터: 이미 채택한 서열과 identity 낮은 것만 유지."""
kept = []
for seq in sequences:
keep = True
for existing in kept:
if compute_pairwise_identity(seq, existing) > max_pairwise_identity:
keep = False
break
if keep:
kept.append(seq)
return kept

Step 4. 자연 서열 유사도 검사

BLAST 또는 편 02의 ESM 임베딩 + FAISS로 자연 서열과의 유사도 확인.

python
def blast_against_swissprot(
query_seq: str,
swissprot_fasta_path: Path,
e_threshold: float = 1e-5,
) -> list[dict]:
"""BLAST로 자연 서열 유사도 검사.
실전은 로컬 BLAST DB (makeblastdb + blastp) 활용.
"""
import subprocess
import tempfile
with tempfile.NamedTemporaryFile(mode="w", suffix=".fasta", delete=False) as query_f:
query_f.write(f">query\n{query_seq}\n")
query_path = query_f.name
try:
result = subprocess.run(
["blastp",
"-query", query_path,
"-db", str(swissprot_fasta_path),
"-outfmt", "6 qseqid sseqid pident evalue bitscore",
"-evalue", str(e_threshold),
"-max_target_seqs", "5"],
capture_output=True, text=True, check=True,
)
hits = []
for line in result.stdout.strip().split("\n"):
if not line:
continue
fields = line.split("\t")
hits.append({
"subject": fields[1],
"identity": float(fields[2]),
"e_value": float(fields[3]),
"bit_score": float(fields[4]),
})
return hits
except (subprocess.CalledProcessError, FileNotFoundError):
return []
def natural_similarity_check(
seq: str,
max_identity: float = 30.0, # %
) -> tuple[bool, float]:
"""seq가 자연 서열과 max_identity 이하로 novel한지.
Returns: (is_novel, max_identity_found)
"""
hits = blast_against_swissprot(seq, Path("/path/to/swissprot.fasta"))
if not hits:
return True, 0.0
max_id = max(h["identity"] for h in hits)
return max_id < max_identity, max_id

Step 5. AF2/Boltz 구조 검증

생성된 서열을 AF2 또는 Boltz-2 (편 11)로 3D 구조 예측 → 원래 요구했던 활성 부위 좌표와 RMSD 비교.

python
import subprocess
import yaml
def predict_structure_boltz(sequence: str, output_dir: Path, seq_id: str) -> Path:
"""편 11의 Boltz-2 파이프라인으로 구조 예측."""
yaml_content = {
"version": 1,
"sequences": [{"protein": {"id": "A", "sequence": sequence}}],
}
output_dir.mkdir(parents=True, exist_ok=True)
yaml_path = output_dir / f"{seq_id}.yaml"
with open(yaml_path, "w") as f:
yaml.safe_dump(yaml_content, f)
try:
subprocess.run([
"boltz", "predict", str(yaml_path),
"--out_dir", str(output_dir),
"--use_msa_server",
], check=True, capture_output=True)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Boltz-2 실패 {seq_id}: {e.stderr.decode()[:500]}")
return output_dir / seq_id / f"{seq_id}_model_0.pdb"
def kabsch_rmsd(coords_a: np.ndarray, coords_b: np.ndarray) -> float:
"""Kabsch 정렬 후 RMSD 계산."""
a = coords_a - coords_a.mean(axis=0)
b = coords_b - coords_b.mean(axis=0)
h = a.T @ b
u, _, vt = np.linalg.svd(h)
d = np.sign(np.linalg.det(vt.T @ u.T))
correction = np.eye(3)
correction[2, 2] = d
r = vt.T @ correction @ u.T
a_aligned = a @ r.T
return float(np.sqrt(np.mean(np.sum((a_aligned - b) ** 2, axis=1))))
def check_active_site_recovery(
predicted_pdb: Path,
triad_positions: list[int],
original_constraint: ActiveSiteConstraint,
rmsd_threshold: float = 2.0,
) -> tuple[bool, float, float]:
"""예측 구조가 활성 부위를 재현하나?
Returns: (recovered, rmsd, mean_plddt)
"""
parser = PDBParser(QUIET=True)
structure = parser.get_structure("predicted", str(predicted_pdb))
residues_list = list(structure.get_residues())
predicted_coords = []
plddt_values = []
for pos in triad_positions:
if pos >= len(residues_list):
return False, float("inf"), 0.0
residue = residues_list[pos]
try:
coords = np.array([
residue["N"].get_coord(),
residue["CA"].get_coord(),
residue["C"].get_coord(),
residue["O"].get_coord(),
])
except KeyError:
return False, float("inf"), 0.0
predicted_coords.append(coords)
# pLDDT는 CA atom의 B-factor에 저장 (AF2/Boltz 관례)
plddt_values.append(float(residue["CA"].get_bfactor()))
predicted_arr = np.stack(predicted_coords, axis=0).reshape(-1, 3)
reference_arr = original_constraint.coordinates_backbone.reshape(-1, 3)
rmsd = kabsch_rmsd(predicted_arr, reference_arr)
mean_plddt = float(np.mean(plddt_values))
return rmsd < rmsd_threshold, rmsd, mean_plddt

Step 6. 통합 · 후보 랭킹

python
from dataclasses import asdict
import pandas as pd
@dataclass
class DesignCandidate:
sequence: str
length: int
seed: int
active_site_preserved_in_seq: bool
triad_positions: list[int]
pdb_path: str | None
active_site_rmsd: float
plddt_active_site: float
natural_identity_max: float
novelty_score: float
composite_score: float
def full_design_pipeline(
catalytic_pdb: Path,
triad_selection: list[tuple[str, int, str]],
target_length: int,
output_dir: Path,
num_samples: int = 100,
device: str = "cuda",
) -> pd.DataFrame:
"""활성 부위 → 신규 서열 생성 → 다양성 · novelty · 검증 → 랭킹."""
output_dir.mkdir(parents=True, exist_ok=True)
print("[1/6] 활성 부위 constraint 로딩")
constraint = load_catalytic_triad_from_pdb(catalytic_pdb, triad_selection)
print(f" triad: {constraint.residue_types}, distances: {constraint.ideal_distances}")
print("[2/6] ESM3 조건부 생성")
designer = ESM3Designer(device=device)
generated = designer.design_with_active_site(
constraint, target_length, num_samples, temperature=0.7, seed=42,
)
preserved = [g for g in generated if g["active_site_preserved"]]
print(f" 생성 {len(generated)}, 활성 부위 유지 {len(preserved)}")
print("[3/6] 다양성 필터")
preserved_seqs = [g["sequence"] for g in preserved]
diverse_seqs = diversity_filter(preserved_seqs, max_pairwise_identity=0.7)
diverse = [g for g in preserved if g["sequence"] in diverse_seqs]
print(f" 다양성 후 {len(diverse)}")
print("[4/6] 자연 서열 novelty 검사")
novel_candidates = []
for g in diverse:
is_novel, max_id = natural_similarity_check(g["sequence"])
g["natural_identity_max"] = max_id
g["novelty_score"] = 1.0 - (max_id / 100.0)
if is_novel:
novel_candidates.append(g)
print(f" novel (identity ≤ 30%) {len(novel_candidates)}")
print(f"[5/6] Boltz-2 구조 검증 ({len(novel_candidates)}개)")
candidates_verified = []
for i, g in enumerate(novel_candidates):
seq_id = f"design_{g['seed']}"
try:
pdb = predict_structure_boltz(g["sequence"], output_dir / "structures", seq_id)
recovered, rmsd, plddt = check_active_site_recovery(
pdb, g["triad_positions"], constraint,
)
candidates_verified.append(DesignCandidate(
sequence=g["sequence"],
length=g["length"],
seed=g["seed"],
active_site_preserved_in_seq=g["active_site_preserved"],
triad_positions=g["triad_positions"],
pdb_path=str(pdb),
active_site_rmsd=rmsd,
plddt_active_site=plddt,
natural_identity_max=g["natural_identity_max"],
novelty_score=g["novelty_score"],
composite_score=0.0, # 하단에서 계산
))
except Exception as e:
print(f" [{i}] {seq_id} 실패: {e}")
print(f"[6/6] Composite 랭킹 · CSV 저장")
if not candidates_verified:
return pd.DataFrame()
df = pd.DataFrame([asdict(c) for c in candidates_verified])
# composite: plddt (40%) + 활성 부위 RMSD (40%) + novelty (20%)
df["composite_score"] = (
df["plddt_active_site"] / 100 * 0.4 +
(1 - df["active_site_rmsd"].clip(0, 5) / 5.0) * 0.4 +
df["novelty_score"] * 0.2
)
df = df.sort_values("composite_score", ascending=False)
df.to_csv(output_dir / "design_candidates.csv", index=False)
print(f" 상위 5 composite: {df.head(5)['composite_score'].tolist()}")
return df
# 실행 예시 (chymotrypsin catalytic triad → 신규 serine protease 100개)
# result = full_design_pipeline(
# catalytic_pdb=Path("./4CHA.pdb"),
# triad_selection=CHYMOTRYPSIN_TRIAD_4CHA,
# target_length=245,
# output_dir=Path("./serine_protease_design"),
# num_samples=100,
# )

Step 7. RFdiffusion + ProteinMPNN 앙상블 비교 (선택 확장)

같은 활성 부위 조건으로 Baker Lab 표준 파이프라인 실행 후 결과 비교.

python
def run_rfdiffusion_motif(
active_site_pdb: Path,
triad_residues: list[int],
output_dir: Path,
num_designs: int = 100,
) -> list[Path]:
"""RFdiffusion motif scaffolding.
catalytic triad를 motif로 고정하고 나머지 backbone 생성.
실전은 RosettaCommons RFdiffusion GitHub 예시 참조.
"""
# 개념 stub: RFdiffusion CLI 호출
# subprocess.run(["python", "run_inference.py", ...])
return [] # 실전 구현 시 완성
def run_proteinmpnn(
backbone_pdb: Path,
output_dir: Path,
num_sequences: int = 10,
) -> list[str]:
"""ProteinMPNN으로 backbone에 서열 배치."""
# subprocess.run(["python", "protein_mpnn_run.py", ...])
return []

성능·비용·알려진 실패 케이스

성능 참고 (공개 벤치 인용)

접근벤치 (self-consistency AF2 RMSD ≤ 2Å)Novelty출처
Rosetta enzyme design20~30%낮음Baker Lab 2003+
ProteinMPNN 단독 (기존 backbone 재설계)40~50%낮음~중Dauparas et al., Science 2022 [2]
RFdiffusion + ProteinMPNN + AF260~70%중~높음Watson et al., Nature 2023 [1]
ESM3 조건부 생성40~55% (추정)중~높음Hayes et al., bioRxiv 2024 [4]
Chroma (Baker Lab all-atom)65~75%중~높음Ingraham et al., Nature 2023 [3]
RFdiffusion + ProteinMPNN + Boltz-2 검증6572% (편 11 앙상블)중~높음커뮤니티 벤치

실전 관찰: RFdiffusion + ProteinMPNN + AF2가 backbone-first 디자인의 gold standard. ESM3는 조건부 유연성 (기능 · 서열 · 구조 임의 조건)에서 강함. 앙상블 접근이 최상.

학습자 재현 예상 비용

  • API 비용 0 (완전 로컬).
  • 100개 서열 생성: ESM3 1.4B 기준 16-24GB VRAM GPU에서 30분~1시간.
  • 100개 Boltz-2 검증: 편 11 pipeline 기준 2~4시간.
  • BLAST swissprot 다운로드 · 로컬 인덱싱: 약 500MB · 1~2시간.

알려진 실패 케이스 5건 (커뮤니티·논문 수집형)

  1. 생성 서열의 pLDDT는 높지만 활성 부위 재현 실패
    증상: AF2/Boltz 예측 pLDDT 80+인데 활성 부위 residue 배치가 어긋남.
    원인: pLDDT는 전역 신뢰도, 특정 잔기 3D 배치와 별개. 특히 loop 지역은 pLDDT 높아도 flexibility.
    회피: (a) 활성 부위 residue의 pLDDT 개별 확인 (본 편 Step 5), (b) predicted structure에서 활성 부위 RMSD 필수 검사, (c) MD 시뮬레이션 (OpenMM · GROMACS)으로 안정성 재검증, (d) 여러 seed로 반복 생성 후 consensus.
    출처: RFdiffusion vs AF2 self-consistency 논의 [1].

  2. 자연 서열과 지나치게 유사 (진짜 novel 아님)
    증상: 생성 서열을 UniProt BLAST 시 40% 이상 identity 히트 다수.
    원인: 파운데이션 모델이 학습한 자연 서열 분포에서 벗어나지 못함.
    회피: (a) diversity filter 엄격히 (본 편 Step 3), (b) UniProt 유사도 필터 사후 적용 (본 편 Step 4), (c) temperature 높여 sampling 다양성 증대 (0.9+), (d) 낮은 유사도 강제 조건부 생성 (auxiliary loss).
    출처: 여러 de novo 디자인 논문 discussion [3][4].

  3. 표적 기능 (촉매 · 형광) 실측 활성 부재
    증상: 3D 구조는 요구 조건 완벽 재현. 실험 발현 · 정제 후 실제 활성 0.
    원인: 활성은 활성 부위 residue 배치만이 아닌 전체 folding 안정성 · dynamics · 미묘한 side chain 배치 모두 필요. Protein folding · expression yield도 별개 문제.
    회피: (a) MD 시뮬레이션 · Rosetta enzyme design으로 활성 예측 강화, (b) 상위 후보를 여러 병렬 실험, (c) high-throughput screening 자동화 (yeast · phage display), (d) 실측 실험 시 stability tag · fluorescent reporter 병용.
    출처: Watson et al. RFdiffusion 논문의 실험 검증 discussion [1].

  4. 활성 부위 residue만 유지되고 orientation 잘못
    증상: Ser195, His57, Asp102가 서열에 있지만 3D 상 catalytic 배치 (Ser hydroxyl이 His에 hydrogen bond)가 안 됨.
    원인: ESM3가 residue identity만 유지하고 side chain rotamer · 3D 상대 위치는 별도 최적화 없음.
    회피: (a) ESM3의 structure track에 backbone + side chain constraint 함께 주입, (b) 사후 Rosetta enzyme design으로 side chain 최적화, (c) 활성 부위 CA-CA 거리 · dihedral 각도 사후 검증 (본 편 Step 1 ideal_distances 확인), (d) MDFF · MDshaping으로 dynamics 검증.
    출처: ESM3 논문 discussion [4]; Rosetta enzyme design tutorial.

  5. ESM3 상업 라이선스 제한
    증상: EvolutionaryScale ESM3 대형 (98B) 모델은 상업 이용 별도 협의. 소형 (1.4B) 오픈이지만 여전히 학술 우선.
    원인: EvolutionaryScale 정책 · Meta 기존 ESM2와 다른 라이선스.
    회피: (a) 학술 프로젝트는 1.4B 오픈 가중치, (b) 상업 프로젝트는 라이선스 문의 or 대안 (RFdiffusion + ProteinMPNN 조합) 사용, (c) 자체 fine-tuning 시 라이선스 조항 재확인.
    출처: EvolutionaryScale ESM 라이선스 [10].

확장 아이디어

  • Complex design: 단일 단백질 아닌 protein-protein 인터페이스 디자인 (편 11 Boltz-2와 연계).
  • Antibody design: CDR loop 조건부 생성으로 항체 디자인 (RFantibody · IgLM 참조).
  • De novo enzyme with substrate: 특정 substrate 결합 사이트를 도킹(편 08) 조건으로 활용.
  • Cyclic peptide: N-C 연결 조건으로 안정적인 cyclic peptide 자율 설계.
  • Multi-objective 최적화: 활성 + 안정성 (thermal stability · aggregation resistance) + 용해도 동시.
  • Fluorescent protein 디자인: GFP chromophore (Ser65-Tyr66-Gly67) triad 유지 신규 fluorescent 단백질.

다음 편

  • 편 11 structure-affinity-boltz: 생성 단백질의 리간드 결합 예측.
  • 편 12 single-cell-perturbation: 생성 단백질을 세포에 표현시켰을 때 반응 in silico.
  • 편 07 drug-target-gnn: 신규 protease의 potential inhibitor GNN 스코어링.
  • 편 14 bio-mcp-agent: 단백질 디자인을 MCP tool로 노출 → agent 자율 실험 설계.

참고 문헌

  1. Watson JL, Juergens D, Bennett NR, et al. "De novo design of protein structure and function with RFdiffusion." Nature 2023. https://www.nature.com/articles/s41586-023-06415-8
  2. Dauparas J, Anishchenko I, Bennett N, et al. "Robust deep learning-based protein sequence design using ProteinMPNN." Science 2022. https://www.science.org/doi/10.1126/science.add2187
  3. Ingraham J, Baranov M, Costello Z, et al. "Illuminating protein space with a programmable generative model (Chroma)." Nature 2023.
  4. Hayes T, Rao R, Akin H, et al. "Simulating 500 million years of evolution with a language model (ESM3)." bioRxiv 2024. https://www.biorxiv.org/content/10.1101/2024.07.01.600583v1
  5. EvolutionaryScale ESM GitHub: https://github.com/evolutionaryscale/esm
  6. RFdiffusion GitHub: https://github.com/RosettaCommons/RFdiffusion
  7. ProteinMPNN GitHub: https://github.com/dauparas/ProteinMPNN
  8. AlphaFold2 (DeepMind): https://github.com/google-deepmind/alphafold
  9. ColabFold: https://github.com/sokrypton/ColabFold
  10. EvolutionaryScale ESM License: https://www.evolutionaryscale.ai/ · GitHub LICENSE
  11. Chroma (Baker Lab): https://github.com/RosettaCommons/chroma
  12. Baker Lab 종합: https://www.bakerlab.org/
  13. pyrosetta: https://www.pyrosetta.org/
  14. PyMOL open-source: https://github.com/schrodinger/pymol-open-source
  15. Boltz GitHub (편 11 참조): https://github.com/jwohlwend/boltz
  16. Biopython: https://biopython.org/
  17. Foldseek (구조 유사 검색): https://github.com/steineggerlab/foldseek
  18. UniProt (자연 서열 검색): https://www.uniprot.org/
  19. RCSB PDB (chymotrypsin 4CHA 등): https://www.rcsb.org/
  20. RosettaCommons enzyme design tutorial: https://www.rosettacommons.org/docs/latest/application_documentation/design/enzyme-design