import os
'drive/MyDrive/2024-cnu-lecture') os.chdir(
7 Deep Learning
7.1 Convolutional Neual Network
- 딥러닝 기반 DNA 서열 분석 가이드
- http://www.btnews.or.kr/bbs/board.php?bo_table=bt_news&wr_id=342
7.1.1 목표
- 임의의 활성을 갖는 서열을 분류하는 CNN 모형 개발 (시뮬레이션)
- 예시) 임의의 전사인자가 결합하는 특정 DNA 모티프 찾는 모형 개발. 즉, 모형 개발 후 임의의 DNA 서열을 모형에 넣었을 때 해당 전사인자가 입력 DNA 서열에 붙으면 1 붙지 않으면 0 이라고 예측하는 모형 개발
7.1.2 데이터
딥러닝을 위해서는 라벨링 데이터가 필요함 (최근 self-supervised learning에서는 필수는 아님). 서열분석의 경우에는 DNA 서열과 함께 해당 서열의 표현형이 라벨이 될 수 있음 (Genotype-phenotype 짝 데이터). 예를 들어 특정 전사인자가 결합하는 DNA 서열을 예측하는 딥러닝 모형을 학습하고자 할 경우 전사인자의 서열 데이터와 해당 전사인자가 DNA에 실제로 붙는지를 나타내는 True 또는 False 라벨이 붙은 데이터가 필요함.
일반적으로 통계적 분석을 위한 데이터는 샘플의 개수와 (행) 변수의 개수로 (열) 구분되어 2차원 배열 형태로 표현. 딥러닝에서도 같은 방식으로 데이터를 표현하며 필요한 샘플의 수는 학습할 모형의 복잡도에 따라서 달라질 수 있지만 최소 수천 개 이상이 필요하며 수 만개 이상의 가능한 많은 데이터를 사용 권장.
딥러닝을 위해서 수집된 데이터 세트는 모형 학습을 위한 Training 데이터와 Test 데이터로 나누어 사용되며 Training 데이터는 또다시 Training 데이터와 Validation 데이터로 나누어 구분.
7.1.3 One-hot encoding
딥러닝을 위해서 데이터는 숫자(기계가 인식 가능한)로 표현 필요. One-hot encoding은 딥러닝에서 가장 널리 사용되는 방법 중 하나이며 4 종류의 염기를 갖는 DNA의 경우 “A”는 [1,0,0,0], “T”는 [0,0,0,1], “G”는 [0,0,1,0], 그리고 “C”는 [0,1,0,0] 으로 인코딩 할 수 있음
import numpy as np
="ATACAA"
my_string=np.array(list(my_string))
my_arrayprint(my_array)
['A' 'T' 'A' 'C' 'A' 'A']
list(my_string)
['A', 'T', 'A', 'C', 'A', 'A']
- Numpy Dimension 관련 내용은 앞서 강의 numpy 부분 참고
5))
display(np.zeros(7,5))) display(np.zeros((
array([0., 0., 0., 0., 0.])
array([[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.]])
= np.zeros((3, 7, 5))
box type(box)
numpy.ndarray
= np.zeros((len(my_array),4), dtype=int)
onehot_encode = {"A":0, "C":1, "G":2, "T":3}
base_dict for i in range(len(my_array)):
= 1
onehot_encode[i, base_dict[my_array[i]]]
print(onehot_encode)
print(onehot_encode.shape)
[[1 0 0 0]
[0 0 0 1]
[1 0 0 0]
[0 1 0 0]
[1 0 0 0]
[1 0 0 0]]
(6, 4)
- one-hot 방식으로 변환한 서열 데이터
7.1.4 모티프 설정
PFM (Position Frequency Matrix)와 PWM (Position Weight Matrix) 개념 이해 필요. Alignment가 수행된 몇 개의 서열들을 가정하면 PFM은 이 서열들의 특정 위치에 A, T, G, C 각 염기들의 빈도수를 나타내며 PWM은 각 염기의 비율을 나타냄.
from Bio import motifs
from Bio.Seq import Seq
= [Seq("TACAA"), Seq("TACGA"), Seq("TACAA")]
instances = motifs.create(instances)
m = m.counts
pfm print(pfm)
= m.counts.normalize(pseudocounts=0.5)
pwm print (pwm)
0 1 2 3 4
A: 0.00 3.00 0.00 2.00 3.00
C: 0.00 0.00 3.00 0.00 0.00
G: 0.00 0.00 0.00 1.00 0.00
T: 3.00 0.00 0.00 0.00 0.00
0 1 2 3 4
A: 0.10 0.70 0.10 0.50 0.70
C: 0.10 0.10 0.70 0.10 0.10
G: 0.10 0.10 0.10 0.30 0.10
T: 0.70 0.10 0.10 0.10 0.10
pseudocounts는 계산시 NULL이나 0으로 나누어지는 경우 방지. 특정 서열 모티프의 PWM은 새로운 서열이 One-hot encoding 방식으로 주어져 있을 경우 서열 처음 위치부터 마지막까지 Sliding window 방식으로 해당 모티프가 있는 위치를 탐색할 수 있음. 다음은 위 주어진 pwm모티프를 0, 1로만 가정하여 해당 모티프의 존재 유무를 계산하는 방법을 보여줌.
위와 같이 길이3 배열에 1이 있을 경우 타깃 서열에 모티프와 같은 서열이 있음을 알 수 있음
앞서 “ATACAA” 서열에서 위 PWM 모티프가 존재하는지 탐색. “ATACAA” 는 길이 5인 슬라이딩 윈도우를 사용하면 “ATACA”와 “TACAA” 두 개의 서열로 나눌 수 있음. 이 두 서열을 One-hot encoding으로 전환 후 위 PWM과 원소들끼리 곱하면 One-hot encoding에서 0이 아닌 위치와 동일 위치의 PWM 값들만 남게 되므로 0이 아닌 값들을 모두 곱한 후 log를 취해 주면 해당 서열이 모티프와 얼마나 비슷한지를 나타내는 스칼라 값이 구해짐. 이론적으로 이 값이 0이면 동일한 서열임.
= np.array(list(pwm.values())).transpose()
pwm_arr print(pwm_arr.shape)
print(onehot_encode.shape)
print(onehot_encode[0:5,].shape)
print(onehot_encode[1:6,].shape)
= np.multiply(onehot_encode[0:5,], pwm_arr)
s1 = np.multiply(onehot_encode[1:6,], pwm_arr)
s2 print(s1)
print(s2)
print(np.sum(s1, axis=1))
print(np.prod(np.sum(s1, axis=1)))
print(np.log(np.prod(np.sum(s1, axis=1)))) #s1 score
print(np.log(np.prod(np.sum(s2, axis=1)))) #s2 score
(5, 4)
(6, 4)
(5, 4)
(5, 4)
[[0.1 0. 0. 0. ]
[0. 0. 0. 0.1]
[0.1 0. 0. 0. ]
[0. 0.1 0. 0. ]
[0.7 0. 0. 0. ]]
[[0. 0. 0. 0.7]
[0.7 0. 0. 0. ]
[0. 0.7 0. 0. ]
[0.5 0. 0. 0. ]
[0.7 0. 0. 0. ]]
[0.1 0.1 0.1 0.1 0.7]
7.000000000000002e-05
-9.567015315914915
-2.119846956314875
- 딥러닝 스타일로 배열을 가시화 할 경우 다음과 같이 표현 가능
7.1.5 모의 서열 데이터 생성
서열 중간 motif를 넣어서 임의의 시뮬레이션 positive 데이터를 1000개 생성하고 랜덤한 서열을 넣어 negative 데이터를 1000개 생성함
import numpy as np
= 20
seq_length = 1000
num_sample #motif CCGGAA
= np.array([[10.41, 22.86, 1.92, 1.55, 98.60, 86.66],
motif_pwm 68.20, 65.25, 0.50, 0.35, 0.25, 2.57],
[17.27, 8.30, 94.77, 97.32, 0.87, 0.00],
[4.13, 3.59, 2.81, 0.78, 0.28, 10.77]])
[= np.hstack([np.ones((4, 7)), motif_pwm, np.ones((4, 7))])
pwm = np.array([np.random.choice( ['A', 'C', 'G', 'T'], num_sample,
pos =pwm[:,i]/sum(pwm[:,i])) for i in range(seq_length)]).transpose()
p= np.array([np.random.choice( ['A', 'C', 'G', 'T'], num_sample,
neg =np.array([1,1,1,1])/4) for i in range(seq_length)]).transpose()
p
print(pos.shape)
''.join(x) for x in pos[1:5,]])
display([print()
''.join(x) for x in neg[1:5,]]) display([
(1000, 20)
['AGCGTAGGCGGAACATAATA',
'GGATCGTCAGGATCACCGCC',
'CCGAGTCACGGAATTAACTG',
'AACATCAGCGGAAGCTTTGT']
['TTCCAATTACCGACCTGGAT',
'ATTGATTTCCTGCCAAGATC',
'AGTGAGCTGCTTTAGGTCCC',
'TGTGAGGGCTTAGATGAATG']
7.1.6 DNA 서열 데이터 전처리
= {'A':0, 'C':1, 'G':2, 'T':3}
base_dict
# response variable for pos
= np.zeros((num_sample, seq_length, 4))
onehot_encode_pos = np.zeros((num_sample, 2), dtype=int)
onehot_encode_pos_label 0] = 1
onehot_encode_pos_label[:,# print(onehot_encode_pos_label)
# response variable for pos
= np.zeros((num_sample, seq_length, 4))
onehot_encode_neg = np.zeros((num_sample, 2), dtype=int)
onehot_encode_neg_label 1] = 1
onehot_encode_neg_label[:,# print(onehot_encode_neg_label)
# convert sequence to onehot
for i in range(num_sample):
for j in range(seq_length):
= 1
onehot_encode_pos[i,j,base_dict[pos[i,j]]] = 1
onehot_encode_neg[i,j,base_dict[neg[i,j]]]
# concatenation
= np.vstack((onehot_encode_pos, onehot_encode_neg))
X = np.vstack((onehot_encode_pos_label, onehot_encode_neg_label))
y
print(X.shape, y.shape)
# (2000, 20, 4) (2000, 2)
(2000, 20, 4) (2000, 2)
- PyTorch Conv1d는 입력 데이터가 [batch_size, channels, length]의 형식이므로 transpose(1,2) 적용
import torch
from torch.utils.data import TensorDataset, DataLoader
from sklearn.model_selection import train_test_split
# 데이터를 훈련 세트와 테스트 세트로 나눔
= train_test_split(X, y, test_size=0.2, random_state=125)
X_train, X_test, y_train, y_test print(X_train.shape, y_train.shape)
# NumPy 배열을 PyTorch 텐서로 변환
= torch.tensor(X_train, dtype=torch.float32).transpose(1,2)
X_train = torch.tensor(X_test, dtype=torch.float32).transpose(1,2)
X_test = torch.tensor(y_train, dtype=torch.float32)
y_train = torch.tensor(y_test, dtype=torch.float32)
y_test print(y_test.dtype)
# DataLoader 설정
= TensorDataset(X_train, y_train)
train_dataset = DataLoader(train_dataset, batch_size=64, shuffle=True)
train_loader print(train_loader.dataset.tensors[0].shape)
print(train_loader.dataset.tensors[1].shape)
= TensorDataset(X_test, y_test)
test_dataset = DataLoader(test_dataset, batch_size=64, shuffle=False) test_loader
(1600, 20, 4) (1600, 2)
torch.float32
torch.Size([1600, 4, 20])
torch.Size([1600, 2])
import torch
= torch.tensor(X_train, dtype=torch.float32)
X_torch print(X_torch.shape)
torch.Size([1600, 4, 20])
/tmp/ipykernel_341094/3124571761.py:3: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).
X_torch = torch.tensor(X_train, dtype=torch.float32)
7.1.7 모델 정의
import torch.nn as nn
import torch.optim as optim
import torch
import torch.nn as nn
import torch.optim as optim
class DNA_CNN(nn.Module):
def __init__(self):
super(DNA_CNN, self).__init__()
self.conv1 = nn.Conv1d(in_channels=4, out_channels=16, kernel_size=3, padding=1)
self.relu = nn.ReLU()
self.maxpool = nn.MaxPool1d(kernel_size=2)
self.flatten = nn.Flatten()
self.fc1 = nn.Linear(160, 64) # Adjust the input features according to your pooling and conv1d output
self.fc2 = nn.Linear(64, 2) # Adjust according to your problem's needs (e.g., number of classes)
#self.softmax = nn.Softmax(dim=1)
def forward(self, x):
= self.conv1(x)
x = self.relu(x)
x = self.maxpool(x)
x = self.flatten(x)
x = self.fc1(x)
x = self.fc2(x)
x #x = self.softmax(x)
return x
= DNA_CNN()
model if torch.cuda.is_available():
model.cuda()
# Loss and optimizer
= nn.CrossEntropyLoss()
criterion = optim.Adam(model.parameters(), lr=0.001)
optimizer
from torchsummary import summary
=(4, 20)) # (Channels, Length) summary(model, input_size
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv1d-1 [-1, 16, 20] 208
ReLU-2 [-1, 16, 20] 0
MaxPool1d-3 [-1, 16, 10] 0
Flatten-4 [-1, 160] 0
Linear-5 [-1, 64] 10,304
Linear-6 [-1, 2] 130
================================================================
Total params: 10,642
Trainable params: 10,642
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.01
Params size (MB): 0.04
Estimated Total Size (MB): 0.05
----------------------------------------------------------------
7.1.8 훈련
# 훈련 루프
= 20
num_epochs for epoch in range(num_epochs):
for inputs, labels in train_loader:
if torch.cuda.is_available():
= inputs.cuda(), labels.cuda()
inputs, labels
# Forward pass
= model(inputs)
outputs = criterion(outputs, labels)
loss
# Backward and optimize
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
Epoch [1/20], Loss: 0.4758
Epoch [2/20], Loss: 0.1928
Epoch [3/20], Loss: 0.0787
Epoch [4/20], Loss: 0.0622
Epoch [5/20], Loss: 0.0541
Epoch [6/20], Loss: 0.0364
Epoch [7/20], Loss: 0.0975
Epoch [8/20], Loss: 0.0732
Epoch [9/20], Loss: 0.0431
Epoch [10/20], Loss: 0.0285
Epoch [11/20], Loss: 0.0258
Epoch [12/20], Loss: 0.1933
Epoch [13/20], Loss: 0.0316
Epoch [14/20], Loss: 0.0399
Epoch [15/20], Loss: 0.0546
Epoch [16/20], Loss: 0.0661
Epoch [17/20], Loss: 0.0291
Epoch [18/20], Loss: 0.0105
Epoch [19/20], Loss: 0.0390
Epoch [20/20], Loss: 0.0536
# 모델 평가
eval()
model.with torch.no_grad():
= 0
correct = 0
total for inputs, labels in test_loader:
if torch.cuda.is_available():
= inputs.cuda(), labels.cuda()
inputs, labels = model(inputs)
outputs #print(outputs.data)
= torch.max(outputs.data, 1)
_, predicted #print(predicted)
+= labels.size(0)
total = torch.max(labels, 1)[1]
labels_max #print(labels_max)
+= (predicted == labels_max).sum().item()
correct
print(f'Accuracy of the model on the test images: {100 * correct / total} %')
Accuracy of the model on the test images: 97.75 %
- 검증을 위한 데이터 저장, 훈련, 예측 동시 수행
import matplotlib.pyplot as plt
# 데이터 저장을 위한 리스트 초기화
= []
train_losses = []
val_accuracies
= 200
num_epochs for epoch in range(num_epochs):
model.train()= 0.0
running_loss for inputs, labels in train_loader:
if torch.cuda.is_available():
= inputs.cuda(), labels.cuda()
inputs, labels
# Forward pass
= model(inputs)
outputs = criterion(outputs, labels)
loss
# Backward and optimize
optimizer.zero_grad()
loss.backward()
optimizer.step()
+= loss.item() * inputs.size(0)
running_loss
= running_loss / len(train_loader.dataset)
epoch_loss
train_losses.append(epoch_loss)
# 모델 평가
eval()
model.= 0
correct = 0
total with torch.no_grad():
for inputs, labels in test_loader:
if torch.cuda.is_available():
= inputs.cuda(), labels.cuda()
inputs, labels = model(inputs)
outputs = torch.max(outputs.data, 1)
_, predicted += labels.size(0)
total = torch.max(labels, 1)[1]
labels_max += (predicted == labels_max).sum().item()
correct
= 100 * correct / total
epoch_accuracy
val_accuracies.append(epoch_accuracy)
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {epoch_loss:.4f}, Accuracy: {epoch_accuracy:.2f}%')
# 그래프 그리기
=(12, 5))
plt.figure(figsize1, 2, 1)
plt.subplot(='Training Loss')
plt.plot(train_losses, label'Training Loss')
plt.title('Epoch')
plt.xlabel('Loss')
plt.ylabel(
plt.legend()
1, 2, 2)
plt.subplot(='Validation Accuracy')
plt.plot(val_accuracies, label'Validation Accuracy')
plt.title('Epoch')
plt.xlabel('Accuracy (%)')
plt.ylabel(
plt.legend()
plt.show()