Deep Learning
#
2026-02-27 ⋯ Conv1D 기반 DNA 분석 #5 분석 정리
Conv1D 기반 DNA 분석에서 세 가지 실험을 했는데 전사인자 결합 예측, 크로마틴 접근성을 추가한 결합 예측, 그리고 RNAi 효율 예측을 수행했다. 세부 사항은 다 달랐지만 DNA(또는 RNA) 서열을 숫자로 바꾸고, 1차원 합성곱 필터로 패턴을 찾고, 그 패턴들을 종합해서 하나의 답을 내놓는 공통 로직으로 작동한다. 모델 구조 Conv1D 레이어
- Conv1D를 한 겹만 쓰지 않고 두세 겹을 쌓는다. 첫 번째 레이어가 원본 서열에서 기본 모티프를 찾으면, 두 번째 레이어는 그 모티프들의 조합을 본다. "이 모티프 옆에 저 모티프가 있다"는 식의 더 복잡한 패턴을 학습하는 것이다. 세 번째 레이어가 있으면 한 단계 더 추상적인 패턴까지 잡아낸다.
- 실험마다 레이어 수가 달랐다. 101글자짜리 전사인자 문제에서는 3겹, 21글자짜리 RNAi 문제에서는 2겹을 썼다. 이는 데이터의 복잡도에 맞춘 선택인데 서열이 길고 패턴이 복잡할수록 더 깊은 모델이 필요하고, 서열이 짧고 단순하면 얕은 모델로 충분하다. 모델을 불필요하게 크게 만들면 훈련 데이터의 잡음까지 외워버리는 과적합이 생기고, 너무 작게 만들면 중요한 패턴을 놓치는 과소적합이 생긴다. 그 사이의 적절한 지점을 찾는 것이 모델 설계의 핵심이다. Flatten-Dense
- Conv1D를 다 통과하면 "서열의 어디에 어떤 패턴이 있는가"를 요약한 특징 맵이 나온다. 전사인자 모델에서는 (101, 15), RNAi 모델에서는 (21, 10) 형태다. Flatten이 이 2차원 구조를 1차원 벡터로 쭉 펼치고, Dense(1)이 그 벡터의 모든 원소에 가중치를 곱해 더해서 숫자 하나를 만든다. Sigmoid가 이 숫자를 0~1 사이로 눌러준다.
- 여기서 크로마틴 접근성 실험(실험 2)만 Flatten 뒤에 접근성 스칼라 하나를 Concatenate로 이어붙여서, Dense가 서열 패턴과 크로마틴 상태를 동시에 고려하게 만들었다. 이건 "같은 모델 구조에 외부 정보를 주입하는 가장 간단한 방법"을 보여주는 사례다. 서열 분석은 Conv1D한테 맡기고, 그 결과와 외부 정보를 최종 판단 직전에 합치는 전략이다. Conv1D가 DNA 분석에 적절한것은 3가지 이유가 있다. 첫째는 위치 불변성이다. JUND가 좋아하는 모티프 TGACTCA가 서열의 앞쪽에 있든 중간에 있든 끝쪽에 있든, 그건 같은 모티프다. Conv1D의 필터는 서열 전체를 처음부터 끝까지 미끄러지면서 같은 패턴을 찾으므로, 모티프가 어디에 위치하든 놓치지 않는다. 만약 Conv1D 대신 일반 Dense 레이어만으로 모델을 만들었다면, "3번째 위치의 TGACTCA"와 "50번째 위치의 TGACTCA"를 완전히 다른 것으로 취급했을 거다. 같은 패턴을 위치마다 따로따로 배워야 하니 데이터가 훨씬 많이 필요하고 효율이 떨어진다. 둘째는 파라미터 공유다. 필터 하나가 서열의 모든 위치에 동일하게 적용된다. 101개 위치를 훑더라도 필터의 가중치는 하나뿐이다. 이건 메모리와 계산 효율 면에서 엄청난 이점이다. Dense 레이어로 같은 일을 하려면 각 위치에 별도의 가중치가 필요하므로 파라미터 수가 폭발적으로 늘어난다. 파라미터가 적다는 건 적은 데이터로도 잘 학습할 수 있다는 뜻이기도 하다. 셋째는 지역성이다. DNA 모티프는 대부분 5-15개 염기 길이다. JUND의 핵심 결합 모티프는 7-8글자 정도다. kernel_size=10이면 한 번에 10개 염기를 보는 창이니, 이런 모티프를 한 눈에 포착하기에 딱 맞는 크기다. DNA에서 생물학적으로 의미 있는 신호는 대부분 이웃한 염기들 사이의 국소적 패턴이지, 서열의 양 끝에 있는 글자들 사이의 장거리 관계가 아니다. Conv1D는 바로 이 국소적 패턴을 찾도록 설계된 도구다. 이 세 가지 성질이 합쳐져서, Conv1D는 DNA 서열 분석에 거의 이상적인 도구가 된다. 모티프라는 짧고 반복적인 패턴을 서열 어디에서든 효율적으로 찾아낸다. 손실 함수 선택 기준 모델의 뼈대는 같지만, 문제가 분류냐 회귀냐에 따라 반드시 바꿔야 하는 부품이 손실 함수와 평가 지표이다. 분류 문제(전사인자 결합)에서는 SigmoidCrossEntropy를 손실로 쓴다. 이 함수는 "정답이 1인데 0에 가깝게 예측하면 엄청나게 벌주고, 정답이 0인데 1에 가깝게 예측해도 엄청나게 벌준다"는 식으로 작동한다. 자신감 있게 틀리는 것에 기하급수적인 페널티를 부과해서, 모델이 양성과 음성을 확실히 구분하도록 밀어붙인다. 평가는 ROC-AUC로 한다. "임의의 양성-음성 쌍에서 양성의 점수가 더 높을 확률"이라는 직관적인 의미를 가진 지표다. 회귀 문제(RNAi 효율)에서는 L2Loss를 쓴다. 예측값과 실제값의 차이를 제곱한 것이 전부다. 큰 오차에 불균형적으로 큰 벌점을 줘서 극단적인 실수를 줄이는 성질이 있다. 평가는 피어슨 상관계수로 한다. 예측값과 실제값이 함께 오르내리는 정도, 즉 선형적 동조 정도를 재는 지표다. 이 선택이 틀리면 모델이 제대로 학습하지 못한다. 회귀 문제에 이진 교차 엔트로피를 쓰면 손실 함수가 연속값을 제대로 처리하지 못하고, 분류 문제에 L2Loss를 쓰면 확률 분포를 최적화하는 데 비효율적이다. 도구를 작업에 맞게 고르는 것, 이것이 딥러닝에서 "설계"라고 부르는 행위의 상당 부분을 차지한다. | 문제 유형 | 손실 함수 | 평가 지표 |
|---|---|---|
| 이진 분류 (TF 결합) | SigmoidCrossEntropy | ROC-AUC |
| 회귀 (RNAi 효율) | L2Loss (MSE) | Pearson r | cf 데이터 흐름 원본 데이터는 DiskDataset이라는 형태로 디스크에 저장되어 있다. shard라는 조각 파일들로 나뉘어 있고, 각 shard에는 서열(X), 정답(y), 가중치(w), ID(ids)가 들어 있다. 단일 입력 모델(실험 1, 3)에서는 DeepChem의 표준 파이프라인이 알아서 배치를 만들어 모델에 넣어준다. model.fit()을 호출하면 끝이다.
다중 입력 모델(실험 2)에서는 표준 파이프라인이 접근성 데이터를 끼워넣을 방법이 없으므로, 커스텀 제너레이터를 직접 만들었다. 이 제너레이터가 배치를 하나씩 만들 때마다 ID로 접근성 딕셔너리를 조회해서 서열과 접근성을 함께 묶어서 내놓는다. fit_generator가 이 제너레이터에서 배치를 받아 학습한다. 즉 프레임워크의 표준 기능으로 해결되면 그걸 쓰고, 안 되면 직접 만들면 된다. 제너레이터라는 파이썬의 기본 기능 하나로 데이터 파이프라인 전체를 커스터마이즈할 수 있다는 사실이, 이 생태계의 유연함을 잘 보여준다. cf2 모델링 핵심 요약 서열을 원-핫으로 숫자화하는 건 모든 실험에서 동일하다. 이건 DNA/RNA 서열을 신경망에 넣기 위한 사실상의 표준이다. Conv1D로 모티프를 탐지하는 것도 동일하다. 서열 데이터의 1차원적, 국소적, 위치 독립적 특성에 정확히 맞는 도구이기 때문이다. Flatten과 Dense로 최종 판단을 내리는 구조도 동일하다.
바뀌는 건 세 가지뿐이다. 모델의 크기(레이어 수, 필터 수, Dropout 비율)는 데이터의 복잡도에 맞게 조절한다. 추가 입력이 있으면 Concatenate로 합류시킨다. 손실 함수와 평가 지표는 문제 유형(분류 vs 회귀)에 따라 선택한다. 궁극적으로 목적은 "이 서열 안에 있는 어떤 패턴이 우리가 예측하려는 결과와 관련이 있는가?"이고 Conv1D는 그 패턴을 찾는 돋보기이고, Dense는 찾은 패턴들을 종합해서 답을 내놓는 판사이며, 손실 함수는 판사가 얼마나 틀렸는지를 알려주는 채점관이다. 이 세 부품의 협업이, 서열이라는 문자열 속에 숨겨진 생물학적 신호를 끄집어내는 것이다.
2026-02-27 ⋯ Conv1D 기반 DNA 분석 #4 RNAi 효율 예측
RNA 간섭(RNAi)이란? 세포 안에서 유전자가 단백질을 만드는 과정은 DNA에서 mRNA라는 복사본이 만들어지고, 이 mRNA를 리보솜이라는 기계가 읽어서 단백질을 찍어낸다. 유전자 → mRNA → 단백질, 이 흐름이 생명의 기본 공정이다. 그런데 세포 안에 짧은 RNA 조각을 집어넣으면, 그 RNA가 특정 mRNA를 찾아가서 분해한다. mRNA가 사라지면 리보솜이 읽을 게 없으니 단백질도 안 만들어진다 즉 특정 유전자를 "조용히 시키는" 것이다. 이걸 RNA 간섭, RNAi라고 한다. 이때 집어넣는 짧은 RNA를 siRNA(small interfering RNA)라고 하는데, 길이가 딱 21개 염기다. 21글자짜리 문자열 하나가 특정 유전자를 꺼버리는 스위치 역할을 하는 셈이다. 이게 왜 중요하냐면, 약으로 쓸 수 있기 때문이다. 질병을 일으키는 유전자가 있으면, 그 유전자의 mRNA를 겨냥하는 siRNA를 설계해서 넣으면 된다. 실제로 RNAi 기반 치료제가 이미 시판되고 있다. 문제는 아무 21글자 서열이나 잘 작동하는 게 아니라는 것이다. 같은 유전자를 겨냥하더라도 어떤 siRNA 서열은 mRNA를 90% 넘게 분해하고, 어떤 서열은 10%밖에 분해하지 못하는 등 서열의 구성에 따라 효율이 다르다. 이유는 여러가지가 있는데 siRNA의 특정 위치에 어떤 염기가 오느냐에 따라 RISC라는 단백질 복합체와의 결합 효율이 달라지고, 서열의 열역학적 안정성(얼마나 단단하게 결합하는가)도 영향을 미치고, mRNA의 표적 부위가 접근 가능한 구조인지도 관계된다. 이러한 siRNA를 하나하나 실험으로 테스트하는 건 시간과 돈이 많이 든다. 이에 분석 목적은 서열만 보고 "이 siRNA는 효율이 높을 거야"라고 미리 예측할 수 있는 모델을 만드는것이다. 이전의 전사인자 결합 예측과 겉모습은 비슷한데, DNA(또는 RNA) 서열을 넣으면 뭔가를 예측한다. 하지만 본질적으로 다른 점이 하나 있는데 전사인자 문제에서는 "붙는다" 또는 "안 붙는다", 즉 0 아니면 1인 분류 문제였다. 하지만 RNAi 효율은 0.12일 수도 있고 0.47일 수도 있고 0.93일 수도 있다. 0과 1 사이의 어떤 값이든 될 수 있는 연속적인 숫자이므로 회귀 문제이다. 이에 모델의 손실 함수, 평가 지표가 바뀐다. 모델 구조 이전에는 입력이 길이 101짜리 DNA 조각이었는데, 이번에는 길이 21짜리 siRNA다. 원-핫 인코딩은 동일하게 적용해서 (21, 4) 형태의 행렬이 된다. 101에서 21로 줄었다는 건 단순히 숫자가 작아진 것 이상의 의미가 있다. 서열이 짧다는 건 그 안에 담긴 패턴의 복잡도도 낮다는 뜻이다. 101글자 안에는 여러 모티프가 복잡하게 조합될 수 있지만, 21글자 안에서는 그럴 여지가 훨씬 적다. 그래서 모델도 더 단순해야 한다. Conv1D 레이어가 3개에서 2개로 줄었고 필터 수도 15개에서 10개로 줄었다. 이건 과적합 방지와 직결된다. 데이터가 담고 있는 정보량에 비해 모델이 너무 크면, 모델이 훈련 데이터의 잡음까지 외워버린다. 21글자짜리 서열에 15개 필터 3겹은 과분하다. 10개 필터 2겹이면 21글자 안의 패턴을 충분히 포착할 수 있다. Dropout 비율도 0.5에서 0.3으로 내려갔다. 이전에는 뉴런의 절반을 껐는데, 이번에는 30%만 끈다. 모델 자체가 작아졌으니 너무 많이 끄면 학습할 용량이 부족해지기 때문이다. 큰 팀에서는 절반을 빼도 나머지가 커버할 수 있지만, 작은 팀에서 절반을 빼면 일이 안 돌아가는 것과 같은 원리다. Conv1D의 kernel_size는 여전히 10이다. 21글자 서열에서 10글자씩 보는 창이면 서열의 거의 절반을 한 번에 보는 셈이다. 이 정도면 siRNA 효율에 영향을 미치는 위치별 염기 선호도를 충분히 잡아낼 수 있다. 2겹의 Conv1D를 통과하면 (21, 10) 형태의 특징 맵이 나온다. 21개 위치 각각에 대해 10개 필터가 값을 내놓은 것이다. Flatten이 이걸 210개짜리 1차원 벡터로 펼친다. 이전의 1,515개에 비하면 훨씬 작다. tfbinding 대비 차이점: | | tfbinding | rnai |
|---|---|---|
| 입력 길이 | 101 bp | 21 bp |
| Conv 레이어 수 | 3 | 2 |
| 필터 수 | 15 | 10 |
| Dropout 비율 | 0.5 | 0.3 |
| 출력 | 이진 분류 | 회귀 | 즉 서열이 짧고(21bp) 문제가 단순하므로 더 작은 모델을 사용한다. Dense(units=1, activation=sigmoid)가 210개의 특징을 숫자 하나로 압축하고 sigmoid를 통과시킨다. 이전 모델에서도 sigmoid를 썼고, 이번에도 sigmoid를 썼지만 의미가 다르다. 이전 모델에서 sigmoid의 출력은 "결합할 확률"이었다. 확률이니까 0-1 사이인 게 자연스럽다. 이번 모델에서 sigmoid의 출력은 "유전자 침묵 효율"이다. 이것도 0-1 사이 값이다. 0이면 전혀 효과 없음, 1이면 완벽한 침묵. 출력 범위가 마침 0~1이니까 sigmoid가 잘 맞는 것이다. 만약 예측해야 하는 값의 범위가 0-1이 아니라 임의의 실수였다면 sigmoid를 쓸 수 없었을 거다. 그때는 활성화 함수 없이 raw 출력을 그대로 내보내거나 다른 방법을 써야 한다. 하지만 RNAi 효율은 운 좋게도 0-1 범위에 딱 들어맞으므로 sigmoid가 자연스러운 선택이다. 그리고 한 가지 중요한 구조적 차이가 있다. 이전 분류 모델에서는 출력을 두 개(sigmoid 적용한 확률과 적용 전 logit) 내보냈다. 수치 안정성을 위해 손실 함수에 logit을 직접 넣어야 했기 때문이다. 이번 모델에서는 출력이 하나뿐이다. Dense 레이어에 sigmoid가 바로 붙어 있고, 이 값이 예측에도 쓰이고 손실 계산에도 쓰인다. 이게 가능한 이유는 손실 함수가 바뀌었기 때문이다. 이전 모델은 SigmoidCrossEntropy를 썼다. 그건 "맞다/틀리다"라는 이진 판단에 최적화된 손실 함수다. 이번에는 분류가 아니라 연속값 예측이므로 L2Loss, 즉 평균 제곱 오차(MSE)를 쓴다. 실제값이 0.7인데 모델이 0.8이라고 예측했다면, 손실은 (0.8 - 0.7)² = 0.01이다. 꽤 가까우니 벌점이 작다. 실제값이 0.7인데 0.2라고 예측했다면, 손실은 (0.2 - 0.7)² = 0.25다. 많이 틀렸으니 벌점이 크다. 제곱을 하는 이유가 두 가지 있다. 첫째, 오차의 방향(높게 틀렸든 낮게 틀렸든)을 무시하고 크기만 본다. 0.1 차이든 -0.1 차이든 똑같이 0.01의 손실이다. 둘째, 큰 오차에 불균형적으로 큰 벌점을 준다. 0.1 차이의 손실은 0.01인데, 0.5 차이의 손실은 0.25로 25배나 크다. 이 성질 덕분에 모델은 크게 틀리는 걸 특히 싫어하게 되고, 극단적인 오류를 줄이는 방향으로 학습한다. 왜 이진 교차 엔트로피 대신 L2를 쓰느냐? 이진 교차 엔트로피는 본질적으로 "두 확률 분포가 얼마나 다른가"를 재는 것이다. 정답이 0 아니면 1인 상황에 맞게 설계되어 있다. 정답이 0.43 같은 중간값인 회귀 문제에서는 의미가 없다. L2Loss는 "두 숫자가 얼마나 떨어져 있는가"를 직접 재니까 연속값 예측에 자연스럽다. 분류에서는 ROC-AUC로 평가했다. 회귀에서는 피어슨 상관계수 r을 쓴다. 피어슨 상관계수가 측정하는 건 "모델의 예측값과 실제값이 함께 움직이는 정도"다. 실제 효율이 높은 siRNA에 대해 모델도 높은 값을 예측하고, 실제 효율이 낮은 siRNA에 대해 모델도 낮은 값을 예측한다면, 두 값 사이에 강한 양의 상관관계가 있는 것이다. r이 1이면 완벽한 양의 상관이다. 예측값과 실제값을 그래프에 점으로 찍으면 모든 점이 하나의 직선 위에 올라간다. r이 0이면 아무 관계가 없다. 점들이 사방에 흩어져 있어서 예측이 무의미하다는 뜻이다. r이 -1이면 완벽한 음의 상관, 즉 모델이 체계적으로 반대로 예측하고 있다는 뜻이다. 피어슨 상관계수의 좋은 점은 스케일에 민감하지 않다는 것이다. 모델이 실제값보다 전체적으로 0.1씩 높게 예측하더라도, 높고 낮음의 순서가 맞으면 r은 여전히 높다. 이건 "예측의 절대적 정확도"보다 "상대적 순위를 얼마나 잘 잡는가"에 더 관심이 있을 때 유용하다. siRNA를 설계할 때도 정확한 효율 수치보다는 "어떤 서열이 더 효과적인가"라는 순위가 더 중요한 경우가 많으므로, 이 지표가 실용적으로 의미 있다. 정리 21글자짜리 siRNA 서열을 원-핫 인코딩으로 (21, 4) 행렬로 바꾸고, 두 겹의 Conv1D가 서열을 훑으며 효율에 영향을 미치는 패턴을 추출하고, Flatten과 Dense가 그 패턴들을 종합해서 0~1 사이의 효율 예측값 하나를 내놓는다. L2Loss가 예측과 실제의 차이를 재서 모델을 교정하고, 피어슨 상관계수가 전반적인 예측 품질을 평가한다. 이전 모델들과 뼈대는 같다 Conv1D로 서열 패턴을 찾고, Dense로 최종 판단을 내리는 구조인데, 바뀐 건 문제의 성격(분류에서 회귀로)에 맞춰 모델 크기, 손실 함수, 평가 지표를 조정하였다. 같은 도구를 다른 작업에 맞게 튜닝하는 것이 딥러닝 실무의 핵심이다.
2026-02-27 ⋯ Conv1D 기반 DNA 분석 #3 크로마틴 접근성 추가
이전 모델에서 우리는 DNA 서열 101글자만 보고 JUND가 붙을지 말지를 예측했고 꽤 잘 작동했다. 그런데 생물학의 현실은 좀 더 복잡한데 JUND가 좋아하는 서열 패턴(TGACTCA 같은 모티프)이 거기 있어도, 그 DNA 구간이 물리적으로 접근 불가능한 상태라면 JUND는 절대 결합할 수 없다. 우리 세포 안의 DNA는 그냥 풀어져서 떠다니는 게 아니라 히스톤이라는 작은 단백질 뭉치에 실타래처럼 감겨 있고 이렇게 DNA가 히스톤에 감긴 구조를 크로마틴이라고 부른다. DNA가 히스톤에 빽빽하게 감긴 구간에서는 전사인자가 DNA에 접근할 수 없고, 느슨하게 풀린 구간에서는 전사인자가 자유롭게 달라붙을 수 있다. 생물학자들은 이 "얼마나 풀려 있는가"를 실험으로 측정할 수 있다. ATAC-seq 같은 기술을 쓰면 게놈의 각 구간이 얼마나 열려 있는지를 숫자로 얻을 수 있고 이 숫자가 바로 크로마틴 접근성이다. 숫자가 크면 활짝 열려 있는 거고, 작으면 꽉 닫혀 있는 거다. accessibility.txt 파일을 보면 chr22:20208963-20209064에 대해 0.003902 같은 값이 적혀 있다. 이건 "22번 염색체의 이 구간은 접근성이 0.003902다"라는 뜻이다. 각 DNA 조각마다 이 숫자가 하나씩 딸려 있는 것이다. 모델 구조: 두 개의 입력 이전 모델의 입력은 DNA 서열 하나뿐이었다. 이제는 두 가지 정보를 동시에 모델에 넣어야 하는데 DNA 서열 (101, 4) 행렬과, 크로마틴 접근성이라는 숫자 하나이다. DNA 서열 쪽 처리는 이전과 완전히 동일하다. 세 겹의 Conv1D가 서열을 훑으면서 모티프 패턴을 추출하고, 각 레이어 뒤에 Dropout이 과적합을 방지한다. 여기까지는 바뀐 게 없다. 세 겹을 다 통과하면 (101, 15) 형태의 특징 맵이 나오고, Flatten이 이걸 1,515개짜리 1차원 벡터로 펼친다. 이전 모델에서는 이 1,515개 벡터가 곧바로 Dense(1)로 들어가서 최종 예측을 만들었다. 하지만 이번에는 Concatenate라는 연산이 들어간다. Concatenate는 이름 그대로 "이어붙이기"인데 1,515개짜리 벡터의 끝에 접근성 숫자 1개를 붙인다. 그러면 1,516개짜리 벡터가 된다. 앞쪽 1,515개는 "이 서열에 어떤 패턴들이 있는가"라는 정보이고, 맨 마지막 1개는 "이 구간의 크로마틴이 얼마나 열려 있는가"라는 정보다. 이 1,516개짜리 벡터가 Dense(1) 레이어로 들어간다. Dense 레이어는 1,516개 숫자 각각에 가중치를 곱하고 다 더해서 logit 하나를 만든다. 이때 접근성에 해당하는 가중치도 학습된다. 여기서 Concatenate의 위치가 핵심이다. 접근성 정보를 맨 처음에 서열과 함께 넣어버리는 게 아니라, Conv1D가 서열 분석을 다 끝낸 뒤에 합류시킨다. 왜 이렇게 설계했을까? Conv1D의 역할은 DNA 서열에서 모티프 패턴을 찾는 것이다. 크로마틴 접근성은 서열 패턴과는 전혀 다른 종류의 정보다. 이 둘을 처음부터 섞어버리면 Conv1D 필터가 혼란스러워질 수 있다. "나는 서열 패턴만 찾으면 되는 건데, 이 이상한 숫자는 뭐지?" 하게 되는 거다.
대신 Conv1D한테는 순수하게 서열 분석만 시키고, 그 분석 결과를 다 내놓은 뒤에 접근성 정보와 합친다. 그러면 최종 Dense 레이어가 두 정보를 종합해서 판단할 수 있다. "서열을 보니 JUND가 좋아할 만한 모티프가 확실히 있다. 그런데 접근성이 거의 0이네. 그러면 결합 못 하겠다." 이런 논리를 학습할 수 있는 구조가 된다. 커스텀 배치 생성기 이전 모델에서는 DeepChem의 model.fit(train)을 호출하면 알아서 데이터를 배치로 나눠서 모델에 넣어줬다. 그런데 그 표준 파이프라인은 입력이 하나라고 가정하는데 우리 모델은 이제 입력이 두 개(DNA 서열과 접근성)여서 표준 도구로는 접근성 데이터를 끼워넣을 방법이 없다. 그래서 우리가 직접 배치를 만들어주는 generate_batches 함수를 생성한다. 먼저 접근성 데이터를 파이썬 딕셔너리로 올린다. accessibility.txt 파일을 한 줄씩 읽어서 "chr22:20208963-20209064" 같은 ID를 키(key)로, 0.003902 같은 접근성 값을 값(value)으로 저장한다. 이제 ID만 알면 접근성을 바로 찾을 수 있는 전화번호부가 만들어진 셈이다. generate_batches 함수는 파이썬의 제너레이터다. 제너레이터는 일반 함수와 달리 return 대신 yield를 쓴다. 함수를 호출하면 값을 하나 내놓고 멈추고, 다음에 다시 호출하면 멈춘 데서 이어서 다음 값을 내놓는다. 자판기에 동전을 넣을 때마다 음료 하나씩 나오는 것과 비슷하다. 이 제너레이터가 하는 일을 단계별로 따라가보자. dataset.iterbatches(batch_size=1000)가 훈련 데이터에서 1,000개씩 뽑아서 X(서열 행렬들), y(정답들), w(가중치들), ids(ID 문자열들)를 내놓는다. 이때 ids가 핵심이다. ids에는 "chr22:20208963-20209064" 같은 문자열이 1,000개 들어 있다. 이 ID들로 아까 만든 딕셔너리를 조회하면 해당 배치의 접근성 값 1,000개를 얻을 수 있다. 그러면 yield로 내보내는 건 이런 구조다. 첫 번째 원소가 [X, accessibility_array]로, 두 개의 입력을 리스트로 묶은 것이다. X는 (1000, 101, 4) 형태의 서열 배치이고, accessibility_array는 (1000, 1) 형태의 접근성 배치다. 두 번째 원소가 [y]로 정답 레이블이고, 세 번째가 [w]로 샘플 가중치다. Keras 모델은 이 구조를 받아서 첫 번째 입력(features)에 X를, 두 번째 입력(accessibility)에 접근성 배열을 자동으로 연결한다. 배치를 제너레이터로 만들었으니, 학습과 평가도 제너레이터 전용 함수를 써야 한다. 이전에 model.fit()을 쓰던 자리에 model.fit_generator()를, model.evaluate() 자리에 model.evaluate_generator()를 쓴다. 학습 루프의 구조는 이전과 같다. 바깥 루프 20번, 안쪽 10 에포크씩 총 200 에포크를 돈다. 매 10 에포크마다 generate_batches(train, 1)과 generate_batches(valid, 1)로 각각 한 에포크 분량의 배치를 만들어서 훈련 세트와 검증 세트의 ROC-AUC를 출력한다. generate_batches에 epochs 매개변수가 있는 이유는, fit_generator에 10 에포크를 시키려면 제너레이터가 10 에포크 분량의 배치를 내놓아야 한다. 그래서 generate_batches 내부에서 for epoch in range(epochs) 루프를 돌며 같은 데이터를 여러 번 반복해서 내보낸다. evaluate_generator에는 1 에포크만 필요하니 epochs=1을 넘긴다. 정리 이전 모델은 DNA 서열이라는 단일 증거만으로 판단했다. 이번 모델은 서열 분석 결과에 크로마틴 접근성이라는 물리적 맥락 정보를 한 조각 더해준다. Conv1D 세 겹이 서열에서 1,515개의 패턴 특징을 뽑아내면, 거기에 접근성 숫자 1개를 이어붙여 1,516개짜리 벡터를 만들고, Dense 레이어가 이 모든 정보를 종합해서 최종 결합 확률을 내놓는다. 추가된 정보는 숫자 하나에 불과하지만, 그 하나가 "문이 열려 있는가?"라는 결정적인 질문에 대한 답이기 때문에 예측 성능에 의미 있는 차이를 만들어낸다. 아무리 완벽한 결합 모티프가 있어도 크로마틴이 닫혀 있으면 소용없다는 생물학적 현실을, 모델 구조에 직접 반영한 것이다.
2026-02-27 ⋯ Conv1D 기반 DNA 분석 #2 전사인자 결합 부위 예측
분석 목적 우리 몸의 세포 안에는 DNA라는 아주 긴 문자열이 있고 A, C, G, T 네 글자로 이루어져 있다. DNA는 유전자라는 "레시피"를 담고 있는데, 이 레시피가 항상 켜져 있는 건 아니며 특정 단백질이 DNA의 특정 위치에 물리적으로 달라붙어야 그 근처 유전자가 켜진다. 이렇게 달라붙어서 유전자를 켜고 끄는 단백질을 전사인자라고 부른다. JUND가 전사인자 중 하나인데, 아무 데나 붙는 게 아니라 특정한 글자 패턴이 있는 곳에만 붙는다. 예를 들어 TGACTCA 같은 패턴을 좋아한다고 알려져 있다. 그런데 현실은 이렇게 깔끔하지 않고 정확히 그 패턴이 아니어도 비슷하면 붙기도 하고, 주변 서열의 맥락에 따라 붙고 안 붙고가 달라지기도 한다. 이에 분석 목적은 다음과 같다. DNA에서 101글자짜리 조각을 하나 잘라서 모델에게 보여주면 모델이 "여기엔 JUND가 붙겠다" 또는 "여기엔 안 붙겠다"라는 판단을 하게 만든다 즉 이진 분류 문제이다. 데이터 구조 데이터 파일 구조를 보면 shard라는 말이 나오는데 데이터가 너무 많아서 한 파일에 다 넣으면 메모리가 터지니까 여러 조각으로 나눠 저장한 것이다. 각 shard에는 X(입력 서열), y(정답 레이블 0 또는 1), w(샘플 가중치), ids(이 서열이 게놈의 어디에서 왔는지 주소)가 들어 있다. ID가 chr22:20208963-20209064 같은 형태인데, 이건 "22번 염색체의 20,208,963번째에서 20,209,064번째 위치까지"라는 위치 좌표이다. 모델 구조 설명 입력은 길이 101짜리 DNA 서열이며 원-핫 인코딩되어있다. shape (101, 4)이다. 모델의 핵심은 Conv1D, 1차원 합성곱이다. 사진을 인식하는 신경망에서는 Conv2D를 쓴다. 사진은 2차원(가로, 세로)이니까 작은 정사각형 필터가 이미지 위를 상하좌우로 움직이면서 패턴을 찾는데 고양이 귀의 삼각형 윤곽 같은 걸 찾는 식이다. DNA 서열은 1차원이고 글자가 일렬로 쭉 늘어서 있다. 그래서 필터도 1차원으로 움직인다. 서열의 왼쪽 끝에서 시작해서 오른쪽으로 한 칸씩 미끄러지면서 "지금 내가 보고 있는 구간에 내가 찾는 패턴이 있나?" 하고 확인한다. kernel_size=10이라는 건 필터 하나가 한 번에 10개 염기를 동시에 본다는 뜻이다. 이 필터는 10×4 크기의 숫자 행렬인데(10개 위치 × 4개 가능한 염기), 처음에는 아무 의미 없는 랜덤 숫자로 채워져 있다. 이 필터가 서열 위를 미끄러진다. 1번째-10번째 염기를 보고 숫자 하나를 뱉고, 2번째-11번째를 보고 숫자 하나를 뱉고, 이런 식으로 쭉 간다. padding='same'이 있으니까 출력 길이가 입력 길이와 똑같이 101이 된다. 양쪽 끝에 0을 채워서 길이를 맞추는 것이다. 필터가 뱉는 숫자가 크면 "여기에 내가 찾는 패턴이 있다!"는 뜻이고, 작으면 "여기엔 없다"는 뜻이다. 학습이 진행되면서 이 필터의 숫자들이 조금씩 바뀐다. 처음에는 아무 의미 없는 패턴을 찾다가, 점점 JUND 결합에 실제로 중요한 서열 패턴을 인식하는 방향으로 진화한다. 마치 현미경의 초점을 맞추듯, 학습이 필터를 생물학적으로 의미 있는 모티프 탐지기로 다듬어가는 것이다. filters=15라는 건 이런 필터를 15개 동시에 돌린다는 뜻이다. 왜 여러 개가 필요하냐? JUND가 결합하는지를 판단하려면 하나의 패턴만 봐서는 부족하기 때문이다. 어떤 필터는 TGACTCA 같은 핵심 결합 모티프를 잡고, 어떤 필터는 GC가 많이 반복되는 영역을 감지하고, 또 어떤 필터는 특정 "반(anti)-패턴"을 탐지할 수 있다. 15개의 필터가 각각 다른 관점에서 서열을 들여다보는 셈이다. 이 모델은 Conv1D + Dropout 조합을 3번 반복한다. 왜 한 번이면 안 되고 세 번이나 쌓을까? 첫 번째 레이어를 생각해보자. 첫 번째 레이어의 필터는 원본 DNA 서열을 직접 본다. 한 번에 10개 염기만 보니까, 인식할 수 있는 건 기껏해야 10글자 이내의 짧은 패턴이다. "여기 TGAC가 있네" 정도다.
두 번째 레이어는 첫 번째 레이어의 출력을 입력으로 받는다. 첫 번째 레이어가 "3번 위치에 TGAC 패턴 발견, 15번 위치에 GGC 패턴 발견" 같은 정보를 내놓으면, 두 번째 레이어는 이 정보들 사이의 관계를 본다. "TGAC 패턴 근처에 GGC 패턴이 있네?" 같은 더 복잡한 조합을 인식할 수 있다.
세 번째 레이어는 여기서 한 단계 더 나간다. "TGAC 근처에 GGC가 있고, 그 조합이 서열의 중앙부에 위치해 있다" 같은 훨씬 추상적이고 넓은 범위의 패턴을 잡아낼 수 있다. 즉 첫 번째 레이어는 글자를 인식하는 것이고, 두 번째 레이어는 단어를 인식하는 것이고, 세 번째 레이어는 문장의 의미를 파악하는 것이다. 각 단계가 이전 단계의 결과를 재료 삼아 점점 더 고차원적인 특징을 추출한다. 각 Conv1D 뒤에 Dropout(rate=0.5)가 붙어 있다. 이건 학습할 때 뉴런의 50%를 무작위로 꺼버리는 것이다. 이는 과적합을 막기위한것인데 매번 학습할 때마다 랜덤하게 뉴런 절반을 끄니까, 모델은 특정 뉴런 하나에 모든 것을 걸 수 없게 된다. "3번 필터가 꺼져도, 7번 필터가 꺼져도, 나머지 필터들만으로도 합리적인 판단을 내릴 수 있어야 해"라는 압력이 생긴다. 결과적으로 모델이 더 견고하고 일반화된 패턴을 학습하게 된다. 참고로 실제 예측(테스트)을 할 때는 Dropout이 꺼지고 모든 뉴런이 동원된다. 세 겹의 Conv1D를 통과하고 나면 (101, 15) 형태의 텐서가 나온다. 101개 위치 각각에 대해 15개 필터가 내놓은 값, 총 101×15 = 1,515개의 숫자가 있는 셈이다. 이 숫자들은 "서열의 어디에 어떤 패턴이 있는지"를 요약한 일종의 특징 지도다. 그런데 우리가 원하는 건 "결합한다/안 한다"라는 하나의 대답이다. 1,515개의 숫자를 어떻게 하나로 줄일까? 먼저 Flatten이 (101, 15)라는 2차원 구조를 (1515,)라는 1차원 벡터로 쭉 펼친다. 2차원 표를 한 줄로 늘어놓는 것이다. 구조 정보(어디가 몇 번째 위치였는지)가 사라지는 것 같지만, 사실 Dense 레이어의 가중치가 각 위치를 구분하는 법을 학습하기 때문에 정보 손실은 없다. 그 다음 Dense(units=1)이 1,515개의 숫자에 각각 가중치를 곱하고 다 더해서 숫자 하나를 만든다. 이 숫자를 logit이라고 부른다. logit은 양수일 수도 있고 음수일 수도 있고 크기에 제한이 없다. logit이 큰 양수면 "결합할 가능성이 높다", 큰 음수면 "결합 안 할 가능성이 높다"라는 뜻이다.
마지막으로 Sigmoid 함수가 이 logit을 0과 1 사이의 숫자로 눌러준다. sigmoid는 S자 모양의 곡선인데, 큰 양수를 넣으면 1에 가까운 값이 나오고, 큰 음수를 넣으면 0에 가까운 값이 나오고, 0을 넣으면 정확히 0.5가 나온다. 이렇게 변환된 값을 확률로 해석할 수 있다. "이 서열에 JUND가 결합할 확률이 0.87이다" 같은 식으로. 모델이 output(sigmoid 적용한 확률)과 logits(sigmoid 적용 전 raw 값) 두 개를 동시에 내보내는데 수학적으로는 같은 정보지만 용도가 다르다. 평가할 때는 output을 쓴다. "이 서열의 결합 확률이 0.87이다"라고 보고하려면 0~1 사이 확률이 필요하니까. 손실(loss) 계산할 때는 logit을 쓰는데 이유는 수치 안정성 때문이다. sigmoid 함수를 먼저 적용한 다음에 log를 취하면, 확률이 0이나 1에 아주 가까울 때 log(0)에 근접해서 숫자가 폭발하거나(무한대가 되거나) 정밀도가 뭉개질 수 있다. 그래서 SigmoidCrossEntropy 손실 함수는 내부적으로 logit을 직접 받아서 sigmoid와 log를 한 번에 계산하는 수학적 트릭을 쓴다. 이렇게 하면 극단적인 값에서도 안정적으로 계산할 수 있다. 컴퓨터가 소수점 계산에서 오류를 일으키는 걸 방지하기 위한 엔지니어링 방법이다. 손실 함수는 모델이 얼마나 틀렸는지 재는 도구이다. SigmoidCrossEntropy, 이진 교차 엔트로피의 공식은 이렇다. y는 정답(0 또는 1), p는 모델이 예측한 확률이다. 만약 정답이 "결합한다"(y=1)인데 모델이 p=0.9라고 높은 확률을 예측했다면, loss = -log(0.9) ≈ 0.1이다. 거의 맞췄으니 벌점이 작다. 반대로 p=0.1이라고 낮게 예측했다면, loss = -log(0.1) ≈ 2.3이다. 크게 틀렸으니 벌점이 크다. 정답이 "안 붙는다"(y=0)일 때도 마찬가지다. 모델이 p=0.1로 예측하면 loss = -log(0.9) ≈ 0.1로 벌점이 작고, p=0.9로 잘못 예측하면 loss = -log(0.1) ≈ 2.3으로 벌점이 크다. 핵심은 모델이 자신감 있게 틀리면 벌점이 기하급수적으로 커진다. "확실히 결합한다고 봅니다!"라고 말했는데 실제로는 안 붙는 경우, 모델은 엄청난 페널티를 받는다. 이 압력 덕분에 모델은 점점 더 정확한 예측을 하는 방향으로 학습된다. 모델이 잘 하고 있는지를 재는 척도로 ROC-AUC를 쓴다. 테스트 데이터에서 실제로 JUND가 결합하는 서열(양성) 하나와, 실제로 결합하지 않는 서열(음성) 하나를 무작위로 뽑는다. 모델한테 둘 다 보여주고 예측 확률을 얻는다. 만약 양성 서열에 대한 예측 확률이 음성 서열보다 높다면, 모델이 이 한 쌍에 대해서는 옳은 판단을 한 거다. 이 실험을 가능한 모든 양성-음성 쌍에 대해 반복한다. "양성의 예측값이 음성보다 높은 비율"이 바로 ROC-AUC다. ROC-AUC가 0.5면 동전 던지기 수준이다. 양성이든 음성이든 예측값이 반반으로 섞여 있어서 구분이 안 된다는 뜻이다. ROC-AUC가 1.0이면 완벽하다. 모든 양성 서열이 모든 음성 서열보다 높은 점수를 받았다는 뜻이다. ROC-AUC의 장점은 임계값(threshold) 선택에 영향을 받지 않는다는 것이다. "확률 0.5 이상이면 결합이라고 하자"라는 기준을 정하기 전에, 모델이 양성과 음성을 전반적으로 잘 구분하고 있는지를 보여주는 포괄적인 지표이다. 학습 루프를 보면 바깥 루프가 20번, 안쪽이 10 에포크씩 돌아서 총 200 에포크를 학습한다. 에포크 하나는 전체 훈련 데이터를 한 바퀴 도는 것이다.
매 10 에포크마다 훈련 세트와 검증 세트의 ROC-AUC를 출력한다. 이걸 왜 두 개 다 보느냐? 훈련 성능만 좋아지고 검증 성능이 정체되거나 떨어지면 과적합이 시작된 거다. 두 값을 비교하면서 "이 모델이 진짜로 패턴을 배운 건지, 아니면 답안지를 외운 건지"를 감시할 수 있다.
batch_size=1000은 한 번에 1,000개의 서열을 묶어서 처리한다는 뜻이다. 하나씩 보면 너무 느리고 그래디언트(학습 방향)가 불안정하다. 전체를 한 번에 보면 메모리가 모자란다. 1,000개는 그 사이의 절충이다. 정리 101글자짜리 DNA 조각을 원-핫 인코딩으로 숫자 행렬로 바꾸고, 세 겹의 1차원 합성곱 필터가 서열을 훑으면서 점점 더 복잡한 패턴을 추출하고, 그 패턴들을 하나의 숫자로 압축해서 sigmoid로 확률을 만들고, 이진 교차 엔트로피로 틀린 정도를 재서 필터의 숫자들을 조금씩 고쳐나가는 과정을 200번 반복하면, 결국 JUND가 어디에 붙을지 꽤 잘 맞추는 모델이 만들어진다. 결국 이 모델이 하는 일은, 생물학자가 실험으로 하나하나 확인해야 했던 "이 DNA 조각에 JUND가 붙을까?"라는 질문에 대해, 서열의 패턴만 보고 확률적으로 답하는 것이다. 수십 개의 학습된 필터가 각각 다른 서열 특징을 감지하고, 그 정보를 종합해서 하나의 예/아니오 판단을 내리는 구조다.
2026-02-27 ⋯ Conv1D 기반 DNA 분석 #1 유전체 서열 분석하기
CNN으로 유전체 서열 분석하기 DNA는 A, T, G, C 4개의 문자로 이루어진 긴 문자열이다.
이 문자열 어딘가에는 단백질이 달라붙는 자리(결합 부위)가 있고,
어딘가에는 RNA 간섭(RNAi)을 잘 일으키는 서열이 있다. 눈으로는 도저히 찾을 수 없다. 딥러닝으로 학습시키면? 유전체 서열 분석하는 3가지 실험을 한다: | 실험 | 목표 | 문제 유형 |
|---|---|---|
| 1 | 전사인자 JUND가 결합하는 DNA 서열 예측 | 이진 분류 |
| 2 | 결합 예측 + 크로마틴 접근성 추가 | 이진 분류 (다중 입력) |
| 3 | siRNA 서열의 유전자 침묵 효율 예측 | 회귀 | DNA 서열을 컴퓨터에 입력하는 방법: 원-핫 인코딩 (One-Hot Encoding) DNA는 4개의 염기 A, T, G, C로 이루어져 있다.
이를 숫자로 표현하기 위해 원-핫 인코딩을 사용한다. 길이 101짜리 DNA 서열이라면 → `(101, 4)` 형태의 2D 배열이 된다. 왜 이렇게 하냐면 숫자로 1,2,3,4 로 표현하면 모델이 A와 T가 "가깝다"고 착각하는데 원-핫은 모든 염기를 동등하게 취급한다.