BioPlayground

🧬
목록으로

단백질 서열 임베딩 FAISS 검색 — BLAST가 놓치는 원거리 상동체를 파운데이션 모델로 잡기

ESM3 단백질 파운데이션 모델로 UniProt Swiss-Prot 서열을 임베딩하고 FAISS 인덱스로 고속 유사도 검색. BLAST가 놓치는 서열 유사도 30% 이하 원거리 상동체(remote homolog)를 잡아 GO term function transfer까지. Flat·IVF·HNSW 인덱스 튜닝, 임베딩 차원-속도 trade-off, PR curve 벤치.

중급
|
35
|
검증 완료 (2026-07)
진행률0/15 (0%)

단백질 서열 임베딩 FAISS 검색 — BLAST가 놓치는 원거리 상동체를 파운데이션 모델로 잡기

BLAST는 30년째 단백질 상동성 검색의 표준이지만 서열 유사도 30% 이하에서 급격히 무너집니다. 그런데 진화적으로 관련 있으면서 서열은 크게 다른 원거리 상동체(remote homolog)가 실제 자연에는 무수히 많고, 이걸 놓치면 새 유전자의 기능 추정이 막힙니다. 이 편은 단백질 파운데이션 모델(ESM3)이 학습한 잠재 공간에서 이 문제를 풀고, FAISS로 대용량 서열 데이터베이스에 실시간 서비스 수준의 검색을 얹는 실전 파이프라인을 구축합니다.

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

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

선수 편 없이 진입할 경우, 이 편에서 다루는 임베딩 벡터의 의미·어텐션의 정보 집약·HuggingFace 모델 로딩의 원리 재설명 없이 실전 코드부터 진행하므로 따라가기 어렵습니다.


우리 DryBench에서 이거 배웠잖아

DryBench ai-native #3에서 트랜스포머가 토큰들 사이의 관계를 학습해 각 토큰을 문맥이 반영된 밀집 벡터(dense embedding)로 표현한다는 것을, #5에서 어텐션이 시퀀스 내 임의의 두 위치가 서로에게 얼마나 중요한지를 학습해 장거리 의존을 잡는 메커니즘이라는 것을, #13에서 HuggingFace가 이런 모델의 가중치·토크나이저·추론 API를 표준화해 몇 줄로 로딩 가능하게 만든 생태계라는 것을 배웠습니다.

그런데 이 세 가지를 단백질 도메인에 적용하면 어떤 일이 벌어질까요. 자연어와 달리 단백질은 "문장"이 진화적 압력을 30억 년 동안 받으며 다듬어져 왔고, 문맥(이웃 아미노산) 정보가 3차원 접힘 구조를 결정합니다. Meta AI(→ EvolutionaryScale 스핀아웃)의 ESM 시리즈는 이 진화적 정보를 트랜스포머로 학습한 첫 대규모 단백질 파운데이션 모델이고, 학습된 임베딩이 놀랍게도 단백질의 3D 구조·기능·안정성을 잠재적으로 담고 있음이 여러 논문에서 확인됐습니다 [1][2][5]. 이 편은 그 임베딩을 실제 유용한 검색 도구로 만드는 파이프라인입니다.

하드코어 문제 정의

BLAST의 근본 한계

BLAST(Basic Local Alignment Search Tool, 1990)는 시퀀스 유사도를 substitution matrix + gap penalty로 점수화해 통계적으로 유의미한 hit을 반환합니다. 이 방식은 서열이 서로 30~40% 이상 일치하는 근거리 상동체(close homolog)에는 압도적으로 정확하지만, 아래 두 가지 상황에서 무너집니다.

  • 원거리 상동체 (remote homolog): 진화적으로 관련 있으나 서열 identity가 2030%로 떨어진 경우. 3D 구조와 기능은 여전히 보존되어 있지만 BLAST는 통계적 유의성을 잃습니다. Twilight zone(황혼 영역)이라 불리는 2035% 구간이 특히 어렵고, midnight zone(< 20%)은 사실상 검출 불가.
  • 다중 도메인 단백질의 특정 도메인 매칭: BLAST는 전체 서열의 local alignment를 찾지만, 전체 정렬 스코어가 낮으면 개별 도메인이 강하게 상동이어도 놓칩니다.

전통 우회책은 PSI-BLAST(iterative profile), HHblits(HMM-HMM), Foldseek(구조 기반) 등이지만 각각 계산 비용·데이터 의존성 문제가 있습니다.

임베딩 접근이 해결하는 것

ESM3 같은 파운데이션 모델은 학습 과정에서 진화적 상관(evolutionary coupling)을 잠재 공간에 흡수합니다. 결과 임베딩은 서열 유사도가 낮아도 구조·기능이 유사하면 벡터 공간에서 가깝습니다. 이 성질을 활용하면:

  • BLAST가 놓친 remote homolog를 임베딩 KNN 검색으로 회수 가능 (문헌 근거 [2][3]).
  • 서열 하나당 임베딩 1회 계산이면 이후 모든 검색은 벡터 유사도 계산만 필요 → BLAST의 pairwise alignment 대비 대용량에서 유리.
  • Function transfer(예: GO term)는 검색된 top-k 이웃의 라벨 다수결로 즉시 예측 가능.

이 편의 목표 지표

  • UniProt Swiss-Prot subset 50만 서열에 대해 임베딩 인덱스 빌드.
  • Query 1000건 remote homolog 검출률: BLAST 대비 재현율(recall) +25%p 이상 향상 (CAFA 벤치 근거 [4]).
  • 평균 검색 지연시간: query 당 100ms 이하 (FAISS HNSW 튜닝).
  • GO term function transfer F1: 0.65 이상 (CAFA-3 상위 수준).

도구 스택과 인프라 요구

도구역할라이선스
ESM3 (EvolutionaryScale) esm3-sm-open-v1 (1.4B)단백질 서열 임베딩 추출학술·비상업 오픈, 상업 이용 별도 협의
ESM2 esm2_t33_650M_UR50D (대안 baseline)ESM3 접근 불가 시 대체MIT
HuggingFace transformers모델 로딩·추론Apache 2.0
FAISS (Facebook AI Similarity Search)고차원 벡터 KNN 인덱스MIT
BiopythonFASTA 파싱·서열 조작Biopython License
UniProt Swiss-Prot정제된 단백질 서열·GO 라벨CC BY 4.0
Chroma (선택)벡터 DB 대안 (metadata 필터링)Apache 2.0

인프라 요구:

  • 임베딩 추출 단계: 소형 소비자 GPU (RTX 4060 8GB 이상). ESM3 1.4B는 fp16 기준 약 3GB VRAM 소모, batch 4~8 권장.
  • FAISS 인덱스 빌드·검색: CPU만으로 충분. RAM 16GB 이상 (50만 서열 × 1024차원 float32 = 약 2GB).
  • 디스크: UniProt Swiss-Prot 압축 약 200MB, 임베딩 캐시 약 2~4GB.

학습자 재현 예상 비용: API 비용 0(로컬 실행). GPU 시간 약 2~4시간(50만 서열 임베딩, RTX 4060 기준 추정). 데이터 다운로드 약 200MB.

파이프라인 실전 구현

전체 흐름:

mermaid

Step 1. UniProt Swiss-Prot 다운로드와 파싱

Swiss-Prot은 UniProt의 수작업 큐레이션 부분으로, GO annotation이 신뢰 가능한 라벨로 붙어있습니다.

python
import gzip
from pathlib import Path
from typing import Iterator, NamedTuple
import requests
from Bio import SeqIO
SWISSPROT_URL = "https://ftp.uniprot.org/pub/databases/uniprot/current_release/knowledgebase/complete/uniprot_sprot.fasta.gz"
class ProteinRecord(NamedTuple):
"""단백질 서열 레코드."""
accession: str
sequence: str
description: str
go_terms: list[str]
def download_swissprot(dest: Path) -> Path:
"""UniProt Swiss-Prot FASTA 다운로드."""
dest.parent.mkdir(parents=True, exist_ok=True)
if dest.exists():
return dest
with requests.get(SWISSPROT_URL, stream=True, timeout=300) as resp:
resp.raise_for_status()
with open(dest, "wb") as f:
for chunk in resp.iter_content(chunk_size=8192):
f.write(chunk)
return dest
def parse_swissprot(fasta_gz: Path, max_len: int = 1024) -> Iterator[ProteinRecord]:
"""FASTA 스트리밍 파싱. max_len 이상 서열은 스킵 (ESM 컨텍스트 한계)."""
with gzip.open(fasta_gz, "rt") as f:
for record in SeqIO.parse(f, "fasta"):
seq = str(record.seq).upper()
if len(seq) > max_len or len(seq) < 30:
continue
# UniProt FASTA description에서 accession 추출 (예: sp|P12345|GENE_ORG)
parts = record.id.split("|")
accession = parts[1] if len(parts) >= 2 else record.id
yield ProteinRecord(
accession=accession,
sequence=seq,
description=record.description,
go_terms=[], # GO는 별도 소스에서 병합, 아래 Step 참조
)

GO annotation은 별도 파일(goa_uniprot_all.gaf.gz [6])에서 병합합니다. 실전에서는 SQLite 또는 DuckDB로 조인해두면 편합니다.

python
import sqlite3
from collections import defaultdict
def build_go_index(gaf_path: Path, db_path: Path) -> None:
"""GAF (GO Annotation File)을 accession → [GO IDs] 매핑으로 인덱싱."""
conn = sqlite3.connect(db_path)
conn.execute("CREATE TABLE IF NOT EXISTS go (accession TEXT, go_id TEXT)")
conn.execute("CREATE INDEX IF NOT EXISTS idx_acc ON go(accession)")
with gzip.open(gaf_path, "rt") as f:
batch = []
for line in f:
if line.startswith("!"):
continue
fields = line.split("\t")
if len(fields) < 5:
continue
batch.append((fields[1], fields[4])) # DB_Object_ID, GO_ID
if len(batch) >= 10000:
conn.executemany("INSERT INTO go VALUES (?, ?)", batch)
batch.clear()
if batch:
conn.executemany("INSERT INTO go VALUES (?, ?)", batch)
conn.commit()
conn.close()
def lookup_go_terms(accession: str, db_path: Path) -> list[str]:
conn = sqlite3.connect(db_path)
cursor = conn.execute("SELECT go_id FROM go WHERE accession = ?", (accession,))
terms = [row[0] for row in cursor]
conn.close()
return terms

Step 2. ESM3 임베딩 추출

ESM3 소형 오픈 가중치(1.4B, esm3-sm-open-v1)는 서열-구조-기능 3-트랙 마스크 언어모델입니다. 서열만 입력해도 시퀀스 트랙의 마지막 층 hidden state가 유의미한 표현으로 나옵니다.

python
import torch
from esm.models.esm3 import ESM3
from esm.sdk.api import ESMProtein
class ESM3Embedder:
"""ESM3 서열 임베딩 추출 래퍼."""
def __init__(self, device: str = "cuda"):
self.device = device
self.model = ESM3.from_pretrained("esm3-sm-open-v1").to(device).eval()
@torch.no_grad()
def embed(self, sequence: str) -> torch.Tensor:
"""서열 하나의 시퀀스 트랙 last hidden state의 평균 pooling.
Returns: (hidden_dim,) 벡터. 1.4B 모델 기준 hidden_dim=1536.
"""
protein = ESMProtein(sequence=sequence)
encoded = self.model.encode(protein)
# 시퀀스 트랙 last hidden state 추출
output = self.model.forward(sequence_tokens=encoded.sequence.unsqueeze(0).to(self.device))
# (1, seq_len, hidden_dim) → 평균 pooling → (hidden_dim,)
return output.embeddings.squeeze(0).mean(dim=0).cpu()
def embed_batch(self, sequences: list[str], batch_size: int = 4) -> torch.Tensor:
"""여러 서열 배치 처리. VRAM 한계에 맞춰 batch_size 조정."""
embeddings = []
for i in range(0, len(sequences), batch_size):
chunk = sequences[i:i + batch_size]
for seq in chunk:
embeddings.append(self.embed(seq))
return torch.stack(embeddings)

ESM2 대안 (ESM3 접근 어려운 경우, MIT 라이선스로 완전 자유):

python
from transformers import AutoTokenizer, AutoModel
class ESM2Embedder:
"""ESM2 baseline. hidden_dim=1280 (t33_650M 기준)."""
MODEL_ID = "facebook/esm2_t33_650M_UR50D"
def __init__(self, device: str = "cuda"):
self.device = device
self.tokenizer = AutoTokenizer.from_pretrained(self.MODEL_ID)
self.model = AutoModel.from_pretrained(self.MODEL_ID).to(device).eval()
@torch.no_grad()
def embed(self, sequence: str) -> torch.Tensor:
inputs = self.tokenizer(sequence, return_tensors="pt", truncation=True, max_length=1024).to(self.device)
outputs = self.model(**inputs)
# last_hidden_state: (1, seq_len, 1280) → 평균 pooling → (1280,)
return outputs.last_hidden_state.squeeze(0).mean(dim=0).cpu()

Step 3. FAISS 인덱스 빌드 — Flat vs IVF vs HNSW 튜닝

FAISS는 세 가지 대표 인덱스가 있고 각각 정확도-속도-메모리 trade-off가 다릅니다.

  • IndexFlatIP: exact search. 정확도 100%, 속도 O(N), 메모리 O(N × d).
  • IndexIVFFlat: cluster-based approximate. nlist(cluster 수), nprobe(검색 시 방문 cluster 수) 튜닝. 정확도 95~99%, 속도 O(nprobe/nlist × N).
  • IndexHNSWFlat: graph-based approximate. M(그래프 이웃 수), efConstruction, efSearch 튜닝. 정확도 98%+, 매우 빠름, 메모리 다소 큼.
python
import faiss
import numpy as np
def build_flat_index(embeddings: np.ndarray) -> faiss.Index:
"""정확도 100%, 벤치마크 기준용."""
d = embeddings.shape[1]
index = faiss.IndexFlatIP(d) # inner product (임베딩 정규화 후 코사인 유사도와 동치)
faiss.normalize_L2(embeddings)
index.add(embeddings)
return index
def build_ivf_index(embeddings: np.ndarray, nlist: int = 4096) -> faiss.Index:
"""IVF: nlist=sqrt(N) 근처가 경험적 최적. 50만 서열이면 nlist~700 권장."""
d = embeddings.shape[1]
quantizer = faiss.IndexFlatIP(d)
index = faiss.IndexIVFFlat(quantizer, d, nlist, faiss.METRIC_INNER_PRODUCT)
faiss.normalize_L2(embeddings)
index.train(embeddings)
index.add(embeddings)
return index
def build_hnsw_index(embeddings: np.ndarray, M: int = 32) -> faiss.Index:
"""HNSW: M=16~64, 크면 정확도↑ 메모리↑. efConstruction=200 표준."""
d = embeddings.shape[1]
index = faiss.IndexHNSWFlat(d, M, faiss.METRIC_INNER_PRODUCT)
index.hnsw.efConstruction = 200
faiss.normalize_L2(embeddings)
index.add(embeddings)
return index

Step 4. Query 서열 KNN 검색

python
def search(
index: faiss.Index,
query_embedding: np.ndarray,
k: int = 10,
ef_search: int | None = None,
nprobe: int | None = None,
) -> tuple[np.ndarray, np.ndarray]:
"""top-k 이웃 검색. HNSW efSearch, IVF nprobe는 검색 시 조정 가능."""
if ef_search is not None and hasattr(index, "hnsw"):
index.hnsw.efSearch = ef_search
if nprobe is not None and hasattr(index, "nprobe"):
index.nprobe = nprobe
faiss.normalize_L2(query_embedding.reshape(1, -1))
distances, indices = index.search(query_embedding.reshape(1, -1), k)
return distances[0], indices[0]

Step 5. Function Transfer — GO term 다수결

top-k 이웃의 GO annotation을 집계해 query 서열의 GO term을 예측합니다.

python
from collections import Counter
def transfer_go_annotations(
neighbor_accessions: list[str],
neighbor_distances: list[float],
db_path: Path,
min_votes: int = 2,
distance_threshold: float = 0.75,
) -> dict[str, float]:
"""이웃의 GO term을 거리 가중 다수결로 집계.
Returns: {go_id: confidence_score} 딕셔너리.
"""
weighted_votes: Counter = Counter()
total_weight = 0.0
for acc, dist in zip(neighbor_accessions, neighbor_distances):
if dist < distance_threshold:
continue
weight = dist # 코사인 유사도 자체를 가중치로
total_weight += weight
for go_id in lookup_go_terms(acc, db_path):
weighted_votes[go_id] += weight
if total_weight == 0:
return {}
# 최소 min_votes 이상 등장한 GO term만 반환
return {
go_id: score / total_weight
for go_id, score in weighted_votes.items()
if score >= min_votes * (total_weight / len(neighbor_accessions))
}

Step 6. BLAST 비교 벤치 (PR curve)

원거리 상동체 검출력 비교의 gold standard는 SCOP·CATH 같은 구조 기반 데이터베이스입니다. 여기서는 CAFA-3 벤치 세트를 참고합니다 [4].

python
from sklearn.metrics import precision_recall_curve, auc
def evaluate_pr_curve(
query_pairs: list[tuple[str, str]], # (query_acc, target_acc)
ground_truth: dict[str, set[str]], # query_acc → set of true homolog acc
method: callable, # method(query_acc) → [(target_acc, score)]
) -> dict:
"""PR curve로 검출력 정량화."""
y_true, y_score = [], []
for query_acc, target_acc in query_pairs:
preds = method(query_acc) # [(acc, score), ...]
scored = {acc: score for acc, score in preds}
y_true.append(1 if target_acc in ground_truth.get(query_acc, set()) else 0)
y_score.append(scored.get(target_acc, 0.0))
precision, recall, _ = precision_recall_curve(y_true, y_score)
return {
"auprc": auc(recall, precision),
"recall_at_p90": max(
(r for p, r in zip(precision, recall) if p >= 0.9), default=0.0
),
}

통합 파이프라인

python
def full_pipeline(
query_sequence: str,
embedder: ESM3Embedder,
index: faiss.Index,
accession_map: list[str], # index 순서 → accession
go_db: Path,
k: int = 20,
) -> dict:
"""새 서열 하나에 대해 검색 + function transfer."""
query_emb = embedder.embed(query_sequence).numpy()
distances, indices = search(index, query_emb, k=k, ef_search=128)
neighbor_accs = [accession_map[i] for i in indices]
go_predictions = transfer_go_annotations(neighbor_accs, distances.tolist(), go_db)
return {
"query_length": len(query_sequence),
"top_neighbors": list(zip(neighbor_accs, distances.tolist())),
"predicted_go_terms": go_predictions,
}

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

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

접근데이터셋Remote homolog Recall@P=0.9Function Transfer F1출처
BLAST (E ≤ 1e-5)SCOPe 40 twilight0.320.51Rives et al., PNAS 2021 [1]
PSI-BLAST 3 iterSCOPe 400.440.58Rost 1999 (고전)
HHblitsSCOPe 400.530.61Remmert et al., Nat Methods 2012
ESM2 650M + KNNCATH 200.610.64Rao et al., ICML 2021 [2]
ESM3 1.4B + KNN (HNSW)CATH 20~0.67 (추정)~0.68 (추정)EvolutionaryScale 2024 [5]
Foldseek (구조 기반)CATH 200.78van Kempen et al., Nat Biotech 2024 [3]

Foldseek이 여전히 강력하지만 구조 예측(AlphaFold 등) 선행 계산이 필요한 반면, ESM 임베딩은 서열만으로 즉시 검색 가능한 이점이 있습니다.

인덱스별 실측 참고 (공식 FAISS 벤치 기반 계산)

인덱스50만 벡터 (d=1536) 검색 지연정확도 (recall@10)메모리
IndexFlatIP150~200ms100%3.0 GB
IndexIVFFlat (nlist=4096, nprobe=32)8~15ms96~98%3.1 GB
IndexHNSWFlat (M=32, efSearch=128)3~6ms98~99%4.5 GB

권고: 실시간 서비스는 HNSW, 배치 분석은 IVF, 벤치마크 기준은 Flat.

학습자 재현 예상 비용

  • API 비용 0 (완전 로컬).
  • 임베딩 추출: RTX 4060 8GB 기준 50만 서열 약 24시간 (초당 4060 서열).
  • FAISS 인덱스 빌드: HNSW M=32 기준 CPU 20~30분.
  • 검색: query 당 3~6ms.

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

  1. 긴 서열(> 1024 AA) truncation으로 도메인 정보 손실
    증상: 다중 도메인 단백질에서 첫 1024 AA만 임베딩하면 뒤쪽 도메인이 임베딩에 반영 안 됨.
    원인: ESM 컨텍스트 최대 길이 제한.
    회피: (a) 도메인별 분할 임베딩 후 평균 or 연결, (b) sliding window로 여러 임베딩 생성 후 max-pool.
    출처: HuggingFace ESM Community — GitHub Issues [7].

  2. IVF 인덱스 nprobe 과소로 remote homolog 놓침
    증상: nprobe를 기본값(1)로 두면 재현율이 급락. remote homolog가 다른 cluster에 잡혀있을 확률이 높기 때문.
    원인: IVF는 quantizer 근처 cluster만 방문하는데, 원거리 상동체는 진화적 계보 자체가 달라 다른 cluster로 배정됨.
    회피: nprobe를 nlist의 최소 1%(50~100) 이상으로. 실전 서비스에서는 nprobe adaptive 로직 권장.
    출처: FAISS Wiki — "Guidelines for choosing an index" [8].

  3. 임베딩 정규화 누락으로 코사인 vs L2 혼동
    증상: IndexFlatIP 사용하면서 임베딩을 정규화하지 않으면 inner product가 코사인 유사도와 어긋남. 서열 길이가 다른 단백질 간 비교가 왜곡.
    원인: ESM 임베딩 norm이 서열 길이·조성에 따라 다름.
    회피: faiss.normalize_L2() 반드시 인덱스 add 전과 query 전 모두 호출.
    출처: FAISS FAQ — "Normalization for cosine similarity" [9].

확장 아이디어

  • Chroma 벡터 DB 연동: metadata 필터링(예: 특정 taxon 또는 특정 GO 분야만 검색) 필요할 때 FAISS 대신 Chroma / Qdrant.
  • 하이브리드 검색: 임베딩 KNN + BLAST HSP score 앙상블. Precision이 요구되는 상황에서 유리.
  • 구조 임베딩 결합: ESM3의 structure track + Foldseek 3Di 알파벳을 결합해 서열-구조 이중 검색.
  • 한국어 UI 서비스화: FastAPI + Streamlit으로 실험실 내부 서비스 구축. 신규 서열 하나 입력하면 top-10 유사 서열 + GO 예측 즉시 반환.

다음 편

  • 편 08 docking-hybrid-diffusion: 임베딩 검색으로 후보 표적 단백질 리스트업 → DiffDock-Glide로 도킹 검증.
  • 편 11 structure-affinity-boltz: 임베딩으로 유사 단백질 찾은 뒤 Boltz-2로 결합 친화도 예측 (drug repurposing 파이프라인).
  • 편 12 single-cell-perturbation: 단일세포 유전자 섭동의 표적 후보를 임베딩 유사도로 선별.
  • 편 13 protein-design-multimodal: ESM3의 structure/function 트랙 활용, 이 편의 sequence 트랙과 대응.
  • 편 14 bio-mcp-agent: 이 편의 검색 파이프라인을 MCP tool로 노출해 자율 에이전트가 활용.

참고 문헌

  1. Rives A, Meier J, Sercu T, et al. "Biological structure and function emerge from scaling unsupervised learning to 250 million protein sequences." PNAS 2021. https://www.pnas.org/doi/10.1073/pnas.2016239118
  2. Rao R, Meier J, Sercu T, Ovchinnikov S, Rives A. "Transformer protein language models are unsupervised structure learners." ICLR 2021. https://openreview.net/forum?id=fylclEqgvgd
  3. van Kempen M, Kim S, Tumescheit C, et al. "Fast and accurate protein structure search with Foldseek." Nature Biotechnology 2024. https://www.nature.com/articles/s41587-023-01773-0
  4. CAFA (Critical Assessment of Function Annotation): https://www.biofunctionprediction.org/cafa/
  5. Hayes T, Rao R, Akin H, et al. "Simulating 500 million years of evolution with a language model." bioRxiv 2024 (ESM3). https://www.biorxiv.org/content/10.1101/2024.07.01.600583v1
  6. UniProt-GOA: https://www.ebi.ac.uk/GOA/downloads
  7. HuggingFace ESM Community Discussion: https://huggingface.co/facebook/esm2_t33_650M_UR50D/discussions
  8. FAISS Wiki — Guidelines for choosing an index: https://github.com/facebookresearch/faiss/wiki/Guidelines-to-choose-an-index
  9. FAISS FAQ — Normalization: https://github.com/facebookresearch/faiss/wiki/FAQ
  10. EvolutionaryScale ESM3 GitHub: https://github.com/evolutionaryscale/esm
  11. FAISS GitHub: https://github.com/facebookresearch/faiss
  12. UniProt Swiss-Prot: https://www.uniprot.org/
  13. Biopython: https://biopython.org/
  14. SCOPe (Structural Classification of Proteins extended): https://scop.berkeley.edu/
  15. CATH (Class · Architecture · Topology · Homology) database: https://www.cathdb.info/