Chapter 7: 현미경 영상을 위한 딥러닝

이 장에서는 현미경으로 포착한 생물학적 이미지를 딥러닝으로 분석하는 기술을 다룹니다. 특히 연구 효율을 획기적으로 높여주는 세포 계수(Cell Counting)와 세포 세분화(Segmentation) 기법에 주목합니다.

세포 계수 (Cell Counting) 자동화

수많은 세포 이미지를 일일이 눈으로 확인하며 숫자를 세는 일은 매우 고된 작업입니다. 이번 예제에서는 딥러닝을 활용해 세포 수를 자동으로 정확하게 계산하는 과정을 구현해 봅니다.

import deepchem as dc
import tensorflow as tf
import tensorflow.keras.layers as layers
import numpy as np
import os
import re

RETRAIN = False

# 데이터셋을 로드합니다.
image_dir = "BBBC005_v1_images"
files = []
labels = []
for f in os.listdir(image_dir):
    if f.endswith(".TIF"):
        files.append(os.path.join(image_dir, f))
        labels.append(int(re.findall("_C(.*?)_", f)[0]))
dataset = dc.data.ImageDataset(files, np.array(labels))
splitter = dc.splits.RandomSplitter()
train_dataset, valid_dataset, test_dataset = splitter.train_valid_test_split(
    dataset, seed=123
)

# 모델을 생성합니다.
features = tf.keras.Input(shape=(520, 696, 1))
prev_layer = features
for num_outputs in [16, 32, 64, 128, 256]:
    prev_layer = layers.Conv2D(
        num_outputs, kernel_size=5, strides=2, activation=tf.nn.relu
    )(prev_layer)
output = layers.Dense(1)(layers.Flatten()(prev_layer))
keras_model = tf.keras.Model(inputs=features, outputs=output)
learning_rate = dc.models.optimizers.ExponentialDecay(0.001, 0.9, 250)
model = dc.models.KerasModel(
    keras_model,
    loss=dc.models.losses.L2Loss(),
    learning_rate=learning_rate,
    model_dir="models/model",
)

if not os.path.exists("./models"):
    os.mkdir("models")
if not os.path.exists("./models/model"):
    os.mkdir("models/model")

if not RETRAIN:
    model.restore()

# 모델을 훈련하고 테스트 세트에서 성능을 평가합니다.
if RETRAIN:
    print("50 에포크 동안 모델 학습을 시작합니다.")
    model.fit(train_dataset, nb_epoch=50)

y_pred = model.predict(test_dataset).flatten()
rmse = np.sqrt(np.mean((y_pred - test_dataset.y) ** 2))
print(f"테스트 세트 RMSE(평균 제곱근 오차): {rmse}")

세포 세분화 (Cell Segmentation)

세포 세분화는 이미지 속에서 각 세포의 경계를 명확히 식별하고 분리해내는 정교한 작업입니다. 딥러닝 모델이 세포의 형태를 어떻게 파악하는지 확인해 보세요.

import deepchem as dc
import tensorflow as tf
import tensorflow.keras.layers as layers
import numpy as np
import os

# import re

RETRAIN = False

# 데이터셋을 로드합니다.
image_dir = "BBBC005_v1_images"
label_dir = "BBBC005_v1_ground_truth"
rows = ("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P")
blurs = (1, 4, 7, 10, 14, 17, 20, 23, 26, 29, 32, 35, 39, 42, 45, 48)
files = []
labels = []

for f in os.listdir(label_dir):
    if f.endswith(".TIF"):
        for row, blur in zip(rows, blurs):
            fname = f.replace("_F1", "_F%d" % blur).replace("_A", "_%s" % row)
            files.append(os.path.join(image_dir, fname))
            labels.append(os.path.join(label_dir, f))

dataset = dc.data.ImageDataset(files, labels)
splitter = dc.splits.RandomSplitter()
train_dataset, valid_dataset, test_dataset = splitter.train_valid_test_split(
    dataset, seed=123
)

# 모델을 생성합니다.
features = tf.keras.Input(shape=(520, 696, 1))

# 다운샘플링(Downsample)을 세 번 수행합니다.
conv1 = layers.Conv2D(
    16,
    kernel_size=5,
    strides=2,
    activation=tf.nn.relu,
    padding="same",
)(features / 255.0)
conv2 = layers.Conv2D(
    32,
    kernel_size=5,
    strides=2,
    activation=tf.nn.relu,
    padding="same",
)(conv1)
conv3 = layers.Conv2D(
    64,
    kernel_size=5,
    strides=2,
    activation=tf.nn.relu,
    padding="same",
)(conv2)
# 1x1 합성곱을 수행합니다.
conv4 = layers.Conv2D(
    64,
    kernel_size=1,
    strides=1,
)(conv3)

# 업샘플링(Upsample)을 세 번 수행합니다.
concat1 = layers.Concatenate(axis=3)([conv3, conv4])
deconv1 = layers.Conv2DTranspose(
    32,
    kernel_size=5,
    strides=2,
    activation=tf.nn.relu,
    padding="same",
)(concat1)
concat2 = layers.Concatenate(axis=3)([conv2, deconv1])
deconv2 = layers.Conv2DTranspose(
    16,
    kernel_size=5,
    strides=2,
    activation=tf.nn.relu,
    padding="same",
)(concat2)
concat3 = layers.Concatenate(axis=3)([conv1, deconv2])
deconv3 = layers.Conv2DTranspose(
    1,
    kernel_size=5,
    strides=2,
    activation=tf.nn.relu,
    padding="same",
)(concat3)

# 최종 출력을 계산합니다.
concat4 = layers.Concatenate(axis=3)([features, deconv3])
logits = layers.Conv2D(1, kernel_size=5, strides=1, padding="same")(concat4)
output = layers.Activation(tf.math.sigmoid)(logits)
keras_model = tf.keras.Model(inputs=features, outputs=[output, logits])
learning_rate = dc.models.optimizers.ExponentialDecay(0.01, 0.9, 250)
model = dc.models.KerasModel(
    keras_model,
    loss=dc.models.losses.SigmoidCrossEntropy(),
    output_types=["prediction", "loss"],
    learning_rate=learning_rate,
    model_dir="models/segmentation",
)

if not os.path.exists("./models"):
    os.mkdir("models")
if not os.path.exists("./models/segmentation"):
    os.mkdir("models/segmentation")

if not RETRAIN:
    model.restore()

# 모델을 훈련하고 테스트 세트에서 성능을 평가합니다.
if RETRAIN:
    print("50 에포크 동안 모델 학습을 시작합니다.")
    model.fit(train_dataset, nb_epoch=50, checkpoint_interval=100)

scores = []
for x, y, w, id in test_dataset.itersamples():
    y_pred = model.predict_on_batch([x]).squeeze()
    scores.append(np.mean((y > 0) == (y_pred > 0.5)))

print(f"테스트 세트 평균 정확도(Mean Accuracy): {np.mean(scores)}")