멀티모달 파운데이션 자율 단백질 디자인 — 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의 다음 편들을 먼저 듣고·보시길 강력히 권고드립니다.
- DryBench ai-native #3 트랜스포머와 임베딩
- DryBench ai-native #12 PyTorch 기초
- DryBench ai-native #13 HuggingFace와 상용 API
선수 편 없이 진입할 경우, 이 편에서 다루는 다중 모달리티 트랜스포머 · 마스크 언어모델 학습 · 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 기준).
파이프라인 실전 구현
전체 흐름:
Step 1. 활성 부위 정의 · ESM3 조건 준비
Chymotrypsin (PDB 4CHA, 5CHA, 1AB9 등)에서 catalytic triad residue의 3D 좌표 추출.
from dataclasses import dataclassfrom pathlib import Path
import torchimport numpy as npfrom Bio.PDB import PDBParser
@dataclassclass 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 triadCHYMOTRYPSIN_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.
from esm.models.esm3 import ESM3from 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 positionsStep 3. 다양성 필터
생성 서열들 사이 pairwise identity 필터로 다양성 확보.
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 keptStep 4. 자연 서열 유사도 검사
BLAST 또는 편 02의 ESM 임베딩 + FAISS로 자연 서열과의 유사도 확인.
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_idStep 5. AF2/Boltz 구조 검증
생성된 서열을 AF2 또는 Boltz-2 (편 11)로 3D 구조 예측 → 원래 요구했던 활성 부위 좌표와 RMSD 비교.
import subprocessimport 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_plddtStep 6. 통합 · 후보 랭킹
from dataclasses import asdictimport pandas as pd
@dataclassclass 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 표준 파이프라인 실행 후 결과 비교.
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 design | 20~30% | 낮음 | Baker Lab 2003+ |
| ProteinMPNN 단독 (기존 backbone 재설계) | 40~50% | 낮음~중 | Dauparas et al., Science 2022 [2] |
| RFdiffusion + ProteinMPNN + AF2 | 60~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 검증 | 중~높음 | 커뮤니티 벤치 |
실전 관찰: 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건 (커뮤니티·논문 수집형)
-
생성 서열의 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]. -
자연 서열과 지나치게 유사 (진짜 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]. -
표적 기능 (촉매 · 형광) 실측 활성 부재
증상: 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]. -
활성 부위 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. -
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 자율 실험 설계.
참고 문헌
- 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 - 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 - Ingraham J, Baranov M, Costello Z, et al. "Illuminating protein space with a programmable generative model (Chroma)." Nature 2023.
- 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 - EvolutionaryScale ESM GitHub:
https://github.com/evolutionaryscale/esm - RFdiffusion GitHub:
https://github.com/RosettaCommons/RFdiffusion - ProteinMPNN GitHub:
https://github.com/dauparas/ProteinMPNN - AlphaFold2 (DeepMind):
https://github.com/google-deepmind/alphafold - ColabFold:
https://github.com/sokrypton/ColabFold - EvolutionaryScale ESM License:
https://www.evolutionaryscale.ai/· GitHub LICENSE - Chroma (Baker Lab):
https://github.com/RosettaCommons/chroma - Baker Lab 종합:
https://www.bakerlab.org/ - pyrosetta:
https://www.pyrosetta.org/ - PyMOL open-source:
https://github.com/schrodinger/pymol-open-source - Boltz GitHub (편 11 참조):
https://github.com/jwohlwend/boltz - Biopython:
https://biopython.org/ - Foldseek (구조 유사 검색):
https://github.com/steineggerlab/foldseek - UniProt (자연 서열 검색):
https://www.uniprot.org/ - RCSB PDB (chymotrypsin 4CHA 등):
https://www.rcsb.org/ - RosettaCommons enzyme design tutorial:
https://www.rosettacommons.org/docs/latest/application_documentation/design/enzyme-design