Solving with AI — Colorectal Histology Tissue Classification

“AI is only as good as what we make out of it. With it, you can solve poverty forever, or with it, you can bring chaos.”

AI is going to have a huge impact on everything around us, more than we can possible envision. I feel constantly motivated to think of problems around me and imagine how AI can help solve them. In my last post, I explained my journey in getting started with ML & AI. With this post, I’m kicking off a new series — “Solving with AI”, where I will try to solve problems with ML, DL and many other exciting technologies. Being the first of many to come, I will start with a simple classification problem and work on top of an existing dataset, but eventually I will look into building my own dataset or maybe even train a model on with unstructured data from the web.

We will be working with this dataset — Collection of textures in colorectal cancer histology — which consists of a collection of textures in histological images of human colorectal cancer. As simple as it sounds, there are 5000 images, each labeled to one of eight tissue types, and our mission is to build a model, that, given a new image, will infer (categorize) its tissue type.

Dataset — https://zenodo.org/record/53169 (258.1 MB)

You will also find this dataset on Kaggle, where all the data is neatly pre-processed and ready to use, but for the purpose of learning we shall work from scratch.


Preprocessing — Convert RGB Image to numbers

Neural networks do one thing — crunch through lots of numbers (and numbers only) and almost magically spit out meaningful insights (could be prediction, inference, generated content, etc). So the first step in any image classification problem is to convert this image into a 3 dimensional array of pixel values. It’s as easy as said with python, where you will find inbuilt functions to do the same.

First things first, let's import all the required modules. We will be working with keras — a wrapper for Tensorflow that lets us easily and efficiently build CNN models.

from __future__ import print_function
import keras
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D, Activation
from keras import backend as K
from sklearn.preprocessing import LabelEncoder
from sklearn.utils import shuffle
import numpy as np
import sys
from scipy import misc
import os

You can change batch size depending on the size of your memory.

Don't forget to change the base path to the location of your extracted dataset.

We will be working with 150px x 150px images.

batch_size = 200
num_classes = 8
epochs = 10
BASE_PATH = "./dataset/Kather_texture_2016_image_tiles_5000/"
img_rows, img_cols = 150, 150

List ‘x’ will hold 3 dimensional value read from the image (3 layers of 150x150 pixel values for each of RGB), and ‘y’ will hold their labels.

x = []
y = []
for category in os.listdir(BASE_PATH):
for image in os.listdir(BASE_PATH + category):
if image.endswith(".tif"):
print("found " + image)
arr = misc.imread(BASE_PATH + category + '/' + image)
x.append(arr)
y.append(category)

By default, ‘y’ will hold labels as string, and we need to convert it into a numerical representation. Here, we are one-hot encoding the labels so that each category will be represented as a binary list.

For example,

01_TUMOR = [1,0,0,0,0,0,0,0]

02_STROMA = [0,1,0,0,0,0,0,0]

03_COMPLEX = [0,0,1,0,0,0,0,0] and so on…

Keras provides the to_categorical() method to do the same.

input_shape = (img_rows, img_cols, 3)
encoder = LabelEncoder()
encoder.fit(y)
encoded_y = encoder.transform(y)
y = keras.utils.to_categorical(encoded_y, num_classes)

As with any ML model, shuffling the dataset is extremely important, especially when processing it in batches.

x, y = shuffle(x, y, random_state=1)

We will split the dataset into train (80%) and test (20%)

x_train = x[:int(len(x)*0.8)]
y_train = y[:int(len(x)*0.8)]
x_test = x[int(len(x)*0.8):]
y_test = y[int(len(x)*0.8):]

We shall also normalise the pixel values so as to get values between 0–1. It is observed that doing so helps the model generalise better with images of different contrast ratios.

x_train = np.array(x_train, dtype=np.float32)
x_test = np.array(x_test, dtype=np.float32)
x_train /= 255
x_test /= 255
print('x_train shape:', x_train.shape)
print(x_train.shape[0], 'train samples')
print(x_test.shape[0], 'test samples')

And finally, the model itself. We will first create a “Sequential” model, where each layer we add will be sequentially follow one another. You can also try to experiment with different parameters in the model and even try using a graph model.

model = Sequential()
model.add(Conv2D(32, 3, 3, input_shape=input_shape))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(32, 3, 3))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(64, 3, 3))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dense(64))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(8))
model.add(Activation('sigmoid'))
model.compile(loss='binary_crossentropy',
optimizer='rmsprop',
metrics=['accuracy'])
model.fit(x_train, y_train,
batch_size=batch_size,
epochs=epochs,
verbose=1,
validation_data=(x_test, y_test))
score = model.evaluate(x_test, y_test, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])

You can now make inferences on new images.

path_to_image = <your image path>
image = misc.imread(path_to_image)
image = image.reshape(1, img_rows, img_cols, 3)
prediction = model.predict(image)

Congrats! You just built your own Image classification model using Keras from scratch. You can try this code with other interesting datasets by tuning the parameters of the model.

Look forward to seeing you build great things!