2D CNN для классификации данных МРТ в 3D оттенках серого, возможная проблема с маркировкой данных

#python #tensorflow #conv-neural-network #mri #medical-imaging

#python #tensorflow #conv-нейронная сеть #мрт #медицинская визуализация

Вопрос:

Я пытаюсь выполнить двоичную классификацию 3D-черно-белых данных МРТ. Я использую 2D свертки из-за отсутствия каналов, присущих черно-белым данным. Я добавил измерение, чтобы выровнять размерность, и, по сути, глубина этих данных действует как измерение пакета. Я использую подвыборку данных, 20 файлов, каждый размером 189 на 233 на 197. Просто в качестве краткого фона.

У меня есть CSV-файл с кучей информации, включая данные метки для каждого файла, который я пытался извлечь, согласно приведенному ниже коду.

 import numpy as np
import glob
import os
import tensorflow as tf
import pandas as pd
import glob

import SimpleITK as sitk

from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from keras.preprocessing.image import ImageDataGenerator


from keras.utils import plot_model
from keras.utils import to_categorical
from keras.utils import np_utils

from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Dropout

from google.colab import drive
drive.mount('/content/gdrive')

datapath = ('/content/gdrive/My Drive/DirectoryTest/All Data/')
patients = os.listdir(datapath)
labels_df = pd.read_csv('/content/Data_Index.csv', index_col = 0 )

labelset = []

for i in patients:
  label = labels_df.loc[i, 'Group']
  if label is 'AD':
    np.char.replace(label, ['AD'], [0])
  if label is 'CN':
    np.char.replace(label, ['CN'], [1])
  labelset.append(label)

label_encoder = LabelEncoder()
labelset = label_encoder.fit_transform(labelset)

labelset = np_utils.to_categorical(labelset, num_classes= 2)

FullDataSet = []

for i in patients:
  a = sitk.ReadImage(datapath   i)
  b = sitk.GetArrayFromImage(a)
  c = np.reshape(b, (189,233,197, 1))
  FullDataSet.append(c)

training_data, testing_data, training_labels, testing_labels = train_test_split(FullDataSet, labelset, train_size=0.70,test_size=0.30)

dataset_train = tf.data.Dataset.from_tensor_slices((training_data, training_labels))
dataset_test = tf.data.Dataset.from_tensor_slices((testing_data, testing_labels))

CNN_model = tf.keras.Sequential(
  [
      #tf.keras.layers.Input(shape=(189, 233, 197, 1), batch_size=2),
      #tf.keras.layers.Reshape((197, 233, 189, 1)),   
                              
      tf.keras.layers.Conv2D(kernel_size=(7, 7), data_format='channels_last', filters=64, activation='relu',
                             padding='same', strides=( 3, 3), input_shape=( 233, 197, 1)),
      #tf.keras.layers.BatchNormalization(center=True, scale=False),
      tf.keras.layers.MaxPool2D(pool_size=(3, 3), padding='same'),
      tf.keras.layers.Dropout(0.20),
      
      tf.keras.layers.Conv2D(kernel_size=( 7, 7), filters=128, activation='relu', padding='same', strides=( 3, 3)),
      #tf.keras.layers.BatchNormalization(center=True, scale=False),
      tf.keras.layers.MaxPool2D(pool_size=(3, 3), padding='same'),
      tf.keras.layers.Dropout(0.20),      

      tf.keras.layers.Conv2D(kernel_size=( 7, 7), filters=256, activation='relu', padding='same', strides=( 3, 3)),
      #tf.keras.layers.BatchNormalization(center=True, scale=False),
      tf.keras.layers.MaxPool2D(pool_size=(3, 3), padding = 'same'),
      tf.keras.layers.Dropout(0.20), 

      # last activation could be either sigmoid or softmax, need to look into this more. Sig for binary output, Soft for multi output 
      tf.keras.layers.Flatten(),
      tf.keras.layers.Dense(256, activation='relu'),   
      tf.keras.layers.Dense(64, activation='relu'),
      tf.keras.layers.Dropout(0.20),
      tf.keras.layers.Dense(2, activation='softmax')

  ])
# Compile the model
CNN_model.compile(optimizer=tf.keras.optimizers.Adam(lr=0.00001), loss='binary_crossentropy', metrics=['accuracy'])

# print model layers
CNN_model.summary()

CNN_history = CNN_model.fit(dataset_train, epochs=10, validation_data=dataset_test)
  

Когда я подхожу к подгонке модели, я получаю следующую ошибку:

 Epoch 1/10
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-35-a8b210ec2e72> in <module>()
      1 #running of the model
      2 #CNN_history = CNN_model.fit(dataset_train, epochs=100, validation_data =dataset_test, validation_steps=1)
----> 3 CNN_history = CNN_model.fit(dataset_train, epochs=10, validation_data=dataset_test)
      4 
      5 

10 frames
/usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/func_graph.py in wrapper(*args, **kwargs)
    971           except Exception as e:  # pylint:disable=broad-except
    972             if hasattr(e, "ag_error_metadata"):
--> 973               raise e.ag_error_metadata.to_exception(e)
    974             else:
    975               raise

ValueError: in user code:

    /usr/local/lib/python3.6/dist-packages/tensorflow/python/keras/engine/training.py:806 train_function  *
        return step_function(self, iterator)
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/keras/engine/training.py:796 step_function  **
        outputs = model.distribute_strategy.run(run_step, args=(data,))
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/distribute/distribute_lib.py:1211 run
        return self._extended.call_for_each_replica(fn, args=args, kwargs=kwargs)
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/distribute/distribute_lib.py:2585 call_for_each_replica
        return self._call_for_each_replica(fn, args, kwargs)
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/distribute/distribute_lib.py:2945 _call_for_each_replica
        return fn(*args, **kwargs)
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/keras/engine/training.py:789 run_step  **
        outputs = model.train_step(data)
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/keras/engine/training.py:749 train_step
        y, y_pred, sample_weight, regularization_losses=self.losses)
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/keras/engine/compile_utils.py:204 __call__
        loss_value = loss_obj(y_t, y_p, sample_weight=sw)
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/keras/losses.py:149 __call__
        losses = ag_call(y_true, y_pred)
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/keras/losses.py:253 call  **
        return ag_fn(y_true, y_pred, **self._fn_kwargs)
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/util/dispatch.py:201 wrapper
        return target(*args, **kwargs)
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/keras/losses.py:1605 binary_crossentropy
        K.binary_crossentropy(y_true, y_pred, from_logits=from_logits), axis=-1)
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/util/dispatch.py:201 wrapper
        return target(*args, **kwargs)
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/keras/backend.py:4829 binary_crossentropy
        bce = target * math_ops.log(output   epsilon())
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/ops/math_ops.py:1141 binary_op_wrapper
        raise e
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/ops/math_ops.py:1125 binary_op_wrapper
        return func(x, y, name=name)
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/ops/math_ops.py:1457 _mul_dispatch
        return multiply(x, y, name=name)
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/util/dispatch.py:201 wrapper
        return target(*args, **kwargs)
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/ops/math_ops.py:509 multiply
        return gen_math_ops.mul(x, y, name)
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/ops/gen_math_ops.py:6176 mul
        "Mul", x=x, y=y, name=name)
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/op_def_library.py:744 _apply_op_helper
        attrs=attr_protos, op_def=op_def)
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/func_graph.py:593 _create_op_internal
        compute_device)
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/ops.py:3485 _create_op_internal
        op_def=op_def)
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/ops.py:1975 __init__
        control_input_ops, op_def)
    /usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/ops.py:1815 _create_c_op
        raise ValueError(str(e))

    ValueError: Dimensions must be equal, but are 2 and 189 for '{{node binary_crossentropy/mul}} = Mul[T=DT_FLOAT](ExpandDims, binary_crossentropy/Log)' with input shapes: [2,1], [189,2].
  

Я знаю, что 2 в [189,2] связано с конечным слоем softmax, но я не знаю, что делать с этой информацией или куда идти отсюда.
Любая помощь была бы оценена, спасибо!

Комментарии:

1. Добро пожаловать в StackOverflow! 🙂

Ответ №1:

Вот несколько комментариев относительно вашего кода, надеюсь, полезных.

Используйте Conv3D и MaxPool3D

Если вы имеете дело с 3D-изображениями, то вам почти наверняка следует использовать Conv3D вместо Conv2D MaxPool3D и MaxPool2D вместо,,. Вот пример (с использованием случайных данных), который я только что протестировал, и, похоже, он работает нормально:

 import numpy as np
import tensorflow as tf
from tensorflow import keras

train_size = 20
val_size = 5

X_train = np.random.random([train_size, 189, 233, 197]).astype(np.float32)
X_valid = np.random.random([val_size, 189, 233, 197]).astype(np.float32)
y_train = np.random.randint(2, size=train_size).astype(np.float32)
y_valid = np.random.randint(2, size=val_size).astype(np.float32)

CNN_model = keras.Sequential([
      keras.layers.Reshape([189, 233, 197, 1], input_shape=[189, 233, 197]),
      keras.layers.Conv3D(kernel_size=(7, 7, 7), filters=32, activation='relu',
                          padding='same', strides=(3, 3, 3)),
      #keras.layers.BatchNormalization(),
      keras.layers.MaxPool3D(pool_size=(3, 3, 3), padding='same'),
      keras.layers.Dropout(0.20),
      
      keras.layers.Conv3D(kernel_size=(5, 5, 5), filters=64, activation='relu',
                          padding='same', strides=(3, 3, 3)),
      #keras.layers.BatchNormalization(),
      keras.layers.MaxPool3D(pool_size=(2, 2, 2), padding='same'),
      keras.layers.Dropout(0.20),

      keras.layers.Conv3D(kernel_size=(3, 3, 3), filters=128, activation='relu',
                          padding='same', strides=(1, 1, 1)),
      #keras.layers.BatchNormalization(),
      keras.layers.MaxPool3D(pool_size=(2, 2, 2), padding='same'),
      keras.layers.Dropout(0.20),

      keras.layers.Flatten(),
      keras.layers.Dense(256, activation='relu'),   
      keras.layers.Dense(64, activation='relu'),
      keras.layers.Dropout(0.20),
      keras.layers.Dense(1, activation='sigmoid')
  ])

# Compile the model
CNN_model.compile(optimizer=keras.optimizers.Adam(lr=0.00001),
                  loss='binary_crossentropy',
                  metrics=['accuracy'])

# print model layers
CNN_model.summary()

CNN_history = CNN_model.fit(X_train, y_train, epochs=10,
                            validation_data=[X_valid, y_valid])
  

Не изменяйте форму для перестановки размеров

Что касается этих двух закомментированных строк:

       #tf.keras.layers.Input(shape=(189, 233, 197, 1), batch_size=2),
      #tf.keras.layers.Reshape((197, 233, 189, 1)),   
  

Изменение размера изображения 189x233x197x1 на 197x233x189x1 не будет работать так, как вы ожидаете. Это полностью перетасует пиксели, что значительно усложнит задачу. Это похоже на преобразование изображения 2×3 в изображение 3×2:

 >>> img = np.array([[1,2,3],[4,5,6]])
>>> np.reshape(img, [3, 2])
array([[1, 2],
       [3, 4],
       [5, 6]])
  

Обратите внимание, что это не то же самое, что поворот изображения: пиксели полностью перепутаны.

Вместо этого вы хотите использовать tf.keras.layers.Permute() вот так:

 CNN_model = tf.keras.Sequential([
      tf.keras.layers.Permute((3, 2, 1, 4), input_shape=(189, 233, 197, 1)),
      ...
])
  

Поскольку эти закомментированные строки были неправильными, я подозреваю, что следующая строка также может быть неправильной:

 c = np.reshape(b, (189,233,197, 1))
  

Я не знаю форму b , поэтому, пожалуйста, убедитесь, что она абсолютно совместима с этой np.reshape() операцией. Например, если его форма [189, 233, 197] , это нормально. Но если это так [197, 233, 189] , например, тогда вам нужно будет переставить размеры перед изменением формы:

 b_permuted = np.transpose(b, [2, 1, 0]) # permute dims
c = np.reshape(b_permuted, [189, 233, 197, 1]) # then add the channels dim
  

np.transpose() Функция аналогична использованию Permute() , за исключением того, что измерения индексируются с индексом 0 вместо индекса 1.

Это может быть еще сложнее. Например, если 3D-изображения хранятся в виде больших 2D-изображений, содержащих бок о бок более мелкие 2D-фрагменты, то форма b может быть чем-то вроде [189*197, 233] . В этом случае вам нужно будет сделать что-то вроде этого:

 b_reshaped = np.reshape(b, [189, 197, 233, 1])
c = np.transpose(b_reshaped, [0, 2, 1, 3])
  

Я надеюсь, что эти примеры достаточно понятны.

Используйте tf.keras , не keras

Существует несколько реализаций Keras API. Одним из них является keras пакет, представляющий собой «мульти-серверный» Keras (который устанавливается с помощью pip install keras ). Есть еще один tf.keras , и он поставляется с TensorFlow. Ваша программа, похоже, использует оба. Вы должны абсолютно избегать этого, это вызовет странные проблемы.

 from keras.utils import plot_model # this is multibackend Keras
...
CNN_model = tf.keras.Sequential(...) # this is tf.keras
  

Я настоятельно рекомендую вам удалить keras с несколькими обратными связями, чтобы избежать ошибок такого типа: pip uninstall keras . Затем исправьте импорт, добавив префикс tensorflow. , например:

 from tensorflow.keras.models import Sequential
from tensorflow.keras.utils import to_categorical # note: not from np_utils
...
  

Не используйте to_categorical() для двоичной классификации

Для двоичной классификации метки должны быть просто одномерным массивом, содержащим 0 и 1, например np.array([1., 0., 0., 1., 1.]) . Код может быть довольно упрощен:

 labelset = []

for i in patients:
  label = labels_df.loc[i, 'Group']
  if label == 'AD':  # use `==` instead of `is` to compare strings
    labelset.append(0.)
  elif label == 'CN':
    labelset.append(1.)
  else:
      raise "Oops, unknown label" # I recommend testing possible failure cases

labelset = np.array(labelset)
  

Важно отметить, что для двоичной классификации вы должны использовать один нейрон в выходном слое, а также использовать "sigmoid" функцию активации (не "softmax" , которая используется для многоклассовой классификации):

 CNN_model = tf.keras.Sequential([
      ...
      tf.keras.layers.Dense(1, activation='sigmoid')
])
  

Незначительный комментарий

  • Вам не нужно указывать train_size и test_size при вызове train_test_split() одновременно.

Удачи!

Комментарии:

1. Спасибо MiniQuark, очевидно, я новичок в этом, поэтому любые указания приветствуются. Ваша входная форма [train_size, 189, 233, 197], а затем вы изменяете размер, чтобы добавить 1 измерение. Я изменяю данные на основе вашей инструкции, и в результате получается [ 20, 189, 233, 197, 1] моя форма данных метки [20] Когда я подхожу к подгонке модели, я получаю ошибку: ValueError: Мощность данных неоднозначна: размеры x: 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189 y размеры: 15 Пожалуйста, предоставьте данные, которые имеют одно и то же первое измерение.

2. С удовольствием. 🙂 Проблема, вероятно, в том, что вы передаете model.fit() список Python, содержащий 20 массивов numpy shape [189, 233, 197, 1], а не один массив NumPy shape [20, 189, 233, 197, 1]. Это можно исправить, выполнив: FullDataSet = np.stack(FullDataSet) непосредственно перед вызовом train_test_split(). Примечание: если у вас уже есть размер каналов (размером 1), то вам не нужен слой Reshape в моем примере. Надеюсь, это поможет.