├── .gitattributes
├── README.md
├── code
├── 00 - Hello Tensorflow.ipynb
├── 01 - Introduction to Data, Tensorflow and Deep Learning.ipynb
├── 02 - Training a Classifier.ipynb
├── 03 - Inference with a Classifier.ipynb
├── 04 - Training a U-Net.ipynb
├── 05 - Inference with a U-Net.ipynb
├── README.md
├── data.py
├── environment.yml
├── net.py
├── unet.png
└── utils.py
├── data
├── README.md
├── brats_2013_pat0001_1
│ ├── dat.npy
│ └── lbl.npy
├── brats_2013_pat0002_1
│ ├── dat.npy
│ └── lbl.npy
├── brats_2013_pat0004_1
│ ├── dat.npy
│ └── lbl.npy
├── brats_2013_pat0005_0001
│ ├── dat.npy
│ └── lbl.npy
├── brats_2013_pat0006_0001
│ ├── dat.npy
│ └── lbl.npy
├── data.pickle
├── data.png
└── label.png
├── prerequisites
└── README.md
├── pretrained
├── classifier
│ ├── checkpoint
│ ├── model.ckpy.data-00000-of-00001
│ ├── model.ckpy.index
│ └── model.ckpy.meta
└── unet
│ ├── checkpoint
│ ├── model.ckpy.data-00000-of-00001
│ ├── model.ckpy.index
│ └── model.ckpy.meta
├── print_address.py
├── run.sh
├── screenshots
├── aws-ec2new00.png
├── aws-ec2new01.png
├── aws-ec2new02.png
├── aws-ec2new03.png
├── aws-ec2new04.png
├── aws-ec2new05.png
├── aws-ec2new06.png
├── aws-ec2new07.png
├── aws-ec2new08.png
├── aws-jupyter00.png
├── aws-jupyter01.png
├── aws-jupyter02.png
├── aws-jupyter03.png
├── aws-jupyter04.png
├── aws-jupyter05.png
├── aws-limit00.png
├── aws-limit01.png
├── aws-login00.png
├── aws-login01.png
├── aws-login02.png
├── aws-login03.png
├── aws-signup00.png
├── aws-ssh00.png
├── aws-ssh01.png
└── aws-start00.png
└── start_jupyter.sh
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.png binary
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Overview
2 |
3 | This repository contains the code examples and prerequisite materials for the hands-on Deep Learning Tutorial at the ISMRM Machine Learning Workshop 2018, as well as instructions for creating a new AWS EC2 GPU instance pre-configured with all the required Python dependencies, CUDA/cuDNN libraries and training data for following along the course.
4 |
5 | If you wish to follow along on Amazon, proceed with the instructions here to create your own personal EC2 instance which you will use to train neural networks. The cost for a minimum single GPU `p2.xlarge` instance is approximately $0.90/hour. If you wish to follow along locally, feel free to clone this repository onto your own computer. A toy dataset is provided in `dl_tutorial/data` so that the code provided here can be executed. Alternatively, the Jupyter notebooks (`*.ipynb`) can be browsed directly on Github and can be found in the `dl_tutorial/code` directory of this repository.
6 |
7 | ### Steps
8 |
9 | 1. How to create a new AWS EC2 instance.
10 | 2. How to access the AWS EC2 instance.
11 | 3. How to run Jupyter notebook.
12 |
13 | **IMPORTANT NOTE:** You should try to complete steps (1) and (2) above *before* the session as a new AWS user will by default need to complete a separate request for GPU-enable instances (requires up to 24 hours for approval).
14 |
15 | ### Template AMI
16 |
17 | The template Amazon Machine Image (AMI) provided as part of this tutorial contains the following preinstalled dependencies:
18 |
19 | * Python 3.5
20 | * NVIDIA driver version 390
21 | * CUDA 9.0 toolbox
22 | * cudNN 7.0 libraries
23 | * Tensorflow 1.5
24 | * Keras 2.1.4
25 | * Utility packages: jupyter, pandas, scipy, scikit-learn, cv2, matplotlib, ipdb
26 |
27 | # Create AWS Instance
28 |
29 | ### Sign up for AWS account
30 |
31 | New users to Amazon AWS have access to a free tier of services for the initial 12 months of subscription. Note that the GPU EC2 instance required for deep learning is not part of the free tier of services ($0.90/hour) however an Amazon AWS account is still required.
32 |
33 | To sign up, begin by visiting: https://portal.aws.amazon.com/billing/signup#/start
34 |
35 | 
36 |
37 | Note that your chosen `AWS account name` cannot contain spaces or non-alphanumeric characters (`{}[]()/\'"~,;:.<>`). Follow the prompts on the remaining screens. A valid credit card will be required.
38 |
39 | ### Login to EC2 console (dashboard)
40 |
41 | Use the following link to log into your new AWS account: https://console.aws.amazon.com/console/home
42 |
43 | On the following page, enter the email address of your AWS account and click `Next`:
44 |
45 | 
46 |
47 | On the following page, enter the password of your AWS account and click `Sign In`:
48 |
49 | 
50 |
51 | After logging in you will arrive at a launch page of various AWS services. We want to specifically manage EC2 instances. To navigate to the EC2 dashboard, click on the `Services` dropdown menu in the top left hand corner of the banner. You should now have a screen that looks like this:
52 |
53 | 
54 |
55 | Click on the `EC2` link under the first `Compute` header within the first column. You have now arrived at the EC2 console (dashboard):
56 |
57 | 
58 |
59 | Here you can manage the servers in your AWS cloud, including creating, terminating, starting and stopping individual EC2 instances. For more general information about EC2 services and the console, see Amazon documentation here: https://aws.amazon.com/ec2/getting-started/
60 |
61 | ### Update AWS EC2 region
62 |
63 | The EC2 instance we will create will be generated from a preconfigured Amazon Machine Image (AMI). To ensure that this AMI is visible to your AWS account, make sure you are in the `US West (Oregon)` region of service by changing the context in the top right hand corner of the banner as needed:
64 |
65 | 
66 |
67 | ### Request a GPU instance limit increase
68 |
69 | By default Amazon does not allow a user to create a new GPU-based instance to prevent accidental incurrence of charges. To request that AWS increase your GPU limit from 0 to 1, click on the `Limits` link on the EC2 console. Scroll down until you see the `p2.xlarge` selection and click on the corresponding link for `Request limit increase`.
70 |
71 | 
72 |
73 | Complete the following request with the settings shown below. Ensure that the correct region and instance type are selected:
74 |
75 | 
76 |
77 | ### Create a new EC2 instance
78 |
79 | After recieving notification of successful limit increase, log into the EC2 console (see instructions above) to begin creating a new EC2 instance. Click the `Instances` link on the left hand toolbar and subsequently click on the blue `Launch Instance` button.
80 |
81 | 
82 |
83 | For the first step, choose `Community AMIs` on the left hand toolbar and type in `ami-c61d8cbe` into the `Search community AMIs` query field. Click on the blue `Select` button to choose this template image. This step configures the baseline software for the new EC2 instance. The remaining steps configure the baseline hardware and network protocol settings.
84 |
85 | 
86 |
87 | For the second step, we need to choose the EC2 instance type. Scroll down the page until you get to `p2.xlarge` in the Instance Type column. This is the baseline single GPU instance.
88 |
89 | 
90 |
91 | On the top set of links, click on `Add Storage` to configure the storage settings for the EC2 instance. Free tier users recieve up to 30 GiB of storage without charge, so we will configure this instance with 30 GiB of SSD storage.
92 |
93 | 
94 |
95 | On the top set of links, click on `Add Tags` to name your new EC2 instance (for your own personal benefit in case you may have multiple EC2 instances to keep track of). In the middle of the screen click the link for `click to add a Name tag` and complete with an appropriate name:
96 |
97 | 
98 |
99 | On the top set of links, click on `Configure Security Group` to set up port firewall settings. First we will create an arbitrary name for this profile of settings by changing `Security group name` and `Description` to `deep-learning`. By default port 22 (for SSH) is allowed. In addition we must open the default Jupyter notebook port 8888 to allow you to connect to the EC2 instance and edit code through a web browser. To do so, click `Add Rule` and fill in the following settings:
100 |
101 | 
102 |
103 | On the top set of links, click on `Review` to see a summary of the EC2 settings. Click on the bottom right hand `Launch` button.
104 |
105 | 
106 |
107 | The final step is to create an SSH key pair to remotely connect to your EC2 instance. To do so type in a key pair name (`default`) and click `Download Key Pair`. It is important to remember the name and location of this downloaded key. If you lose this key you will be unable to access the EC2 instance. A recommended strategy to store AWS key pairs is to place it in a hidden folder easily accessible from your home folder (`~/.aws`). After downloading and saving the SSH key, click `Launch Instance` to complete the EC2 creation process.
108 |
109 | 
110 |
111 | Before continuing, open a terminal and navigate to the location of your saved key. If you did not create a `~/.aws` folder to place your key and would like to do so now, use the following commands:
112 | ```
113 | mkdir ~/.aws
114 | mv name_of_your_key.pem ~/.aws/default.pem
115 | ```
116 |
117 | Note that some OS's may automatically append a `*.txt` to the end of your `*.pem` file when downloading. If so the `mv` command above wil rename your file appropriately. At this time, also go ahead and change the permissions on the SSH key to not be publically viewable (otherwise SSH client will not accept the key):
118 | ```
119 | chmod 400 ~/.aws/default.pem
120 | ```
121 |
122 | # Accessing AWS Instance
123 |
124 | ### Launch EC2 instance
125 |
126 | Upon completing the EC2 instance creation steps described above, the new EC2 instance will be automatically be started. On each subsequent future session, you will have to start (e.g. boot) your EC2 instance before you can connect and use it. To do so, select the `Instances` link on the left hand toolbar, select the instance you want to boot (blue check box to the left of the instance name), select `Actions` > `Instance State` > `Start`. Your EC2 instance will be ready to connect in about 30-60 seconds.
127 |
128 | 
129 |
130 | ### SSH into EC2 instance
131 |
132 | There are two options to start a remote connection to your EC2 instance. Instructions can be found by selecting a particular EC2 instance (blue check box to the left of the instance name) and clicking the `Connect` button.
133 |
134 | 
135 |
136 | The recommended option is to connect through a standalone SSH client (from your local machine) of your choice. For Mac OS X users, the default SSH client located in the Terminal appliation (Applications > Utilities) is recommended. After opening a terminal session, type in the following command:
137 | ```
138 | ssh -i "/path/to/your/pem/file" ubuntu@[ec2-public-dns]
139 | ```
140 | Note the you should replace [ec2-public-dns] with your EC2 instances public DNS. In the above screenshot this would be `c2-35-160-231-250.us-west-2.compute.amazonaws.com`. Assuming that your SSH key is located at `~/.aws/default.pem` (if you followed the instructions per EC2 creation above) then the full command would be:
141 | ```
142 | ssh -i "~/.aws/default.pem" ubuntu@c2-35-160-231-250.us-west-2.compute.amazonaws.com
143 | ```
144 |
145 | You should now be successfully logged into your remote SSH session.
146 |
147 | 
148 |
149 | ### Stop EC2 instance
150 |
151 | AWS charges a fixed cost per hour of EC2 instance usage, prorated to the second. To keep your charges low, it is advised to stop (e.g. turn off) your EC2 instance whenever your are finished. To do so, select the `Instances` link on the left hand toolbar, select the instance you want to stop, select `Actions` > `Instance State` > `Stop`.
152 |
153 | # Starting Jupyter Notebook
154 |
155 | During the tutorial session, all code written by the participants will be completed using the Jupyter notebook, an iPython kernel / server that will be running from your own personal AWS instance and accessed through a web-browser. Through this web-based interface one will be able to write, edit and run code in an easy way without needing to use the Linux command line. For more advanced users, this entire Github repository is available for access from the command line at `~/dl_tutorial`. See below for more information.
156 |
157 | ### Starting Jupyter server
158 |
159 | A preconfigured Jupyter server has been set up on the EC2 instance broadcasting on port 8888. To launch this server simply run the following bash script after connecting to your EC2 instance (located in your home folder):
160 | ```
161 | ./start_jupyter.sh
162 | ```
163 |
164 | This bash script will:
165 |
166 | * update to the latest version of this repository from Github
167 | * activate the `dl_aws` conda environment
168 | * launch a Jupyter server listening of port 8888
169 |
170 | Upon execution, two URLs are shown:
171 |
172 | 
173 |
174 | The first URL is prefixed with `0.0.0.0`, the default template provided by the Jupyter notebook server, and the second URL is created by an auto-IP detection script, filling the address with your public IP address. If this does not appear, see **Finding your public IP address** section below to locate this manually.
175 |
176 | To connect to this Jupyter server, open a web-browser (e.g. Google Chrome) enter the URL into the address bar.
177 |
178 | 
179 |
180 | Press `[enter]` to navigate to the web page. You now have access to a web-based iPython kernel though the Jupyter notebook. You also have access to the files on your AWS EC2 instance. By default you will login into a clone of this Github code repository pre-configured into your EC2 instance:
181 |
182 | 
183 |
184 | Click on the `Code` folder to open and access the series of Jupyter notebook lectures (`*.ipnyb`) and template code provided for you as part of this tutorial. If you are unfamiliar with Jupyter you can launch the `00 - Hello Tensorflow.ipynb` notebook to walk through a basic working example by simply clicking on it:
185 |
186 | 
187 |
188 | You should now have access to our first example Jupyter notebook. Feel free to walk through this template, execute the code (which will be run on your EC2 instance) and edit as you see fit.
189 |
190 | 
191 |
192 | ### Finding your public IP address
193 |
194 | If the auto-IP detection feature does not work on your EC2 instance, you will need to manually find your IP address in the AWS dashboard. The public IP address of your instance can be found in the `Instances` menu of the EC2 dashboard:
195 |
196 | 
197 |
198 | # Advanced Users
199 |
200 | For more advanced users wishing to follow along directly through the EC2 command line instead of the Jupyter notebook, these are instructions for basic access. In the EC2 instance, all required dependencies have been installed in a separate Conda virtual enivornment named `dl_aws`. To activate simply run:
201 | ```
202 | source activate dl_aws
203 | ```
204 |
205 | From here simply access code and materials from this cloned Github repository at `~/dl_tutorial`. You may use your favorite editor. Note that `vim` has been preconfigured with syntax highlighting, Vundle and several useful plugins for Python development (see `~/.vimrc` for further details). Code may be executed with either `python` or the `ipython` kernel.
206 |
--------------------------------------------------------------------------------
/code/00 - Hello Tensorflow.ipynb:
--------------------------------------------------------------------------------
1 | {"nbformat":4,"nbformat_minor":0,"metadata":{"colab":{"name":"00 - Hello Tensorflow.ipynb","version":"0.3.2","views":{},"default_view":{},"provenance":[]},"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"accelerator":"GPU"},"cells":[{"metadata":{"id":"7FkE6YFBw8Wi","colab_type":"text"},"cell_type":"markdown","source":["# Overview\n","\n","In this example Jupyter notebook we demonstrate basic usage of the notebook itself as a method to run Python code interactively with intermixed `Markdown` documentation. Successful execution of this script will also serve as confirmation of successful Python module dependency installation."]},{"metadata":{"id":"56d3oMiMw8Wm","colab_type":"text"},"cell_type":"markdown","source":["### Jupyter Notebook Usage\n","\n","A Jupyter notebook is composed of blocks of `Markdown` documentation or code referenced as cells. Each cell can be individually selected by a simple click. As you progress through this notebook, simply select a code-containing cell and click the `Run` button on the top toolbar (or alternatively `shift` + `[Enter]`) to execute that particular line or block of code. The `In [ ]` header to the left of each cell will change status to `In [*]` while a line or block of code is executing and then to a number indicating a line or block of executed code if successful."]},{"metadata":{"id":"yNrVTCOhw8Wo","colab_type":"text"},"cell_type":"markdown","source":["### Hello Tensorflow Example\n","\n","To begin this example, import the Tensorflow module by running the code cell below by selecting the cell and clicking the `Run` button on the top toolbar."]},{"metadata":{"id":"1MZQecvYw8Wq","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["import tensorflow as tf"],"execution_count":0,"outputs":[]},{"metadata":{"id":"MhlkojiQw8Wy","colab_type":"text"},"cell_type":"markdown","source":["The variable tf now contains the imported tensorflow module. To confirm success execution of code, use the blank code cell below to display a list of available methods by tab-completing:\n","\n"," tf. [hit tab after typing the period to see list]"]},{"metadata":{"id":"KI31WevJw8W0","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":[""],"execution_count":0,"outputs":[]},{"metadata":{"id":"7d0kRS5Mw8W4","colab_type":"text"},"cell_type":"markdown","source":["In the following next few lines code we will create a constant operation node in the Tensorflow graph that will contain the `Hello, Tensorflow!` string."]},{"metadata":{"id":"pJUuYKLnw8W6","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["hello = tf.constant('Hello, Tensorflow!')"],"execution_count":0,"outputs":[]},{"metadata":{"id":"VVRqCydqw8XA","colab_type":"text"},"cell_type":"markdown","source":["Next we need to create a Tensorflow session to execute the our graph containing our single operation node."]},{"metadata":{"id":"knS4orEHw8XA","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["sess = tf.Session()"],"execution_count":0,"outputs":[]},{"metadata":{"id":"R2-qLgpow8XE","colab_type":"text"},"cell_type":"markdown","source":["Finally, we use the created session to run our graph and print the result."]},{"metadata":{"id":"vl5b7MCaw8XI","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["print(sess.run(hello).decode('utf-8'))"],"execution_count":0,"outputs":[]},{"metadata":{"id":"JLELvPbZw8XM","colab_type":"text"},"cell_type":"markdown","source":["That's it! Now that you are familiar with the basic format of running code in a Jupyter notebook you are ready for the in-person workshop. Feel to close this notebook (`File` > `Close and Halt`). If you would like to preview the lecture series, you may do so by clicking on the files to open in the dl_tutorial/code directory. If you are finished for now, remember to turn off any AWS instance(s) that you may currently have open in the "]}]}
--------------------------------------------------------------------------------
/code/02 - Training a Classifier.ipynb:
--------------------------------------------------------------------------------
1 | {"nbformat":4,"nbformat_minor":0,"metadata":{"colab":{"name":"02 - Training a Classifier.ipynb","version":"0.3.2","views":{},"default_view":{},"provenance":[]},"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"accelerator":"GPU"},"cells":[{"metadata":{"id":"gqHhkbF1ooP4","colab_type":"text"},"cell_type":"markdown","source":["# Overview\n","\n","In this notebook we will cover the basic concepts for training convolutional neural networks in Tensorflow. We will be specifically building a network to detect presence or absence of brain tumors from multimodal MR images. The data that we will be using in this tutorial comes from the MICCAI Brain Tumor Segmentation Challenge (BRaTS). More information about he BRaTS Challenge can be found here: http://braintumorsegmentation.org/\n","\n","For basics of Tensorflow operation and neural networks, consider reviewing the first part of this series **01 - Introduction to Data, Tensorflow and Deep Learning**."]},{"metadata":{"id":"TknhdafV1yBm","colab_type":"text"},"cell_type":"markdown","source":["### Importing modules\n","\n","To train our simple classifer implementation, we will require three open-source libraries (`tensorflow`, `numpy` and `os`) as well as our custom modules created for this tutorial (`net`, `data`). The additional `Markdown` library is used to generate dynamic links to Terminal and TensorBoard below."]},{"metadata":{"id":"8fK4dGnc1yBo","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["import os, sys\n","import tensorflow as tf, numpy as np\n","\n","sys.path.append('%s/dl_tutorial/code' % os.environ['HOME'])\n","import net, data\n","\n","os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' \n","from IPython.display import Markdown as md\n","ip = open('../public-ipv4').read() if os.path.exists('../public-ipv4') else '0.0.0.0'"],"execution_count":0,"outputs":[]},{"metadata":{"id":"6l76yVg91yBy","colab_type":"text"},"cell_type":"markdown","source":["### Hyperparameter variables\n","\n","Hyperparameters are parameters whose values are set before the learning process begins and which in turn influence and direct the training process. These will be the three most important hyperparameter variables to vary in this experiment. We will cover these in more detail as they are encountered in the code below."]},{"metadata":{"id":"DEjsOVTM1yBy","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["iterations = 200\n","batch_size = 16\n","learning_rate = 1e-3"],"execution_count":0,"outputs":[]},{"metadata":{"id":"PtGaXTqA1yB4","colab_type":"text"},"cell_type":"markdown","source":["### Preparation\n","\n","Here we perform some basic preparatory steps including: \n","\n","* setting the data root directory \n","\n","> If **AWS** or **Google**: `/data/brats/npy`\n","\n","> If **local**: `../data` (or whichever local directory data is stored in)\n","\n","* making the output directory for saving training checkpoints and logs\n","* defining an `ops` dictionary to save operations\n","* reseting any existing graph that may exist (`tf.reset_default_graph()`)"]},{"metadata":{"id":"kFkAeBQM1yB6","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["root = '/data/brats/npy'\n","output_dir = '%s/dl_tutorial/exp_classifier' % os.environ['HOME']\n","os.makedirs(output_dir, exist_ok=True)\n","ops = {}\n","tf.reset_default_graph()"],"execution_count":0,"outputs":[]},{"metadata":{"id":"EoHLXiIA1yCA","colab_type":"text"},"cell_type":"markdown","source":["### Data batch\n","\n","A data **mini-batch** is used to describe the collection of image and label pairs used to perform one update of our network parameters. The more number of images and labels we use for each update, the more likely that update is to reflect the underlying population data. However, the trade-off is that computationally each network update will require more time. A good initial starting point for images matrices of our dataset may be 16 or 32. \n","\n","To implement batching, we will use a prepared template method `net.init_batch()` to load a number of slices simulatenously:"]},{"metadata":{"id":"bM0gdn1P1yCA","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["batch = net.init_batch(batch_size, root=root, one_hot=False)"],"execution_count":0,"outputs":[]},{"metadata":{"id":"vH7-w4Ng1yCE","colab_type":"text"},"cell_type":"markdown","source":["### Placeholders\n","\n","A tensorflow **placeholder** is an entry point for us to feed actual data values into the model. We must define this **placeholder** and all subsequent downstream operations performed on this **placeholder** before ever passing data into the model. \n","\n","The placeholder `X` will serve as the method for introduction image data into the graph. The placeholder `y` will serve as the method for introducing the correct target label representing presence (1) or absence (0) of tumor. The placeholder `mode` will serve as a method for introducing whether or not the graph is being executed for training or for validation."]},{"metadata":{"id":"TPe1tcSh1yCI","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["X = tf.placeholder(tf.float32, shape=[None, 240, 240, 4], name='X')\n","y = tf.placeholder(tf.int32, shape=[None], name='y')\n","mode = tf.placeholder(tf.bool, name='mode')"],"execution_count":0,"outputs":[]},{"metadata":{"id":"qg3y4KVa1yCM","colab_type":"text"},"cell_type":"markdown","source":["### Network\n","\n","In this example we will be using the architecture defined by `net.create_classifier()`. This classifer alternates a series of convolutions, ReLU non-linearities and max-pooling to collapse the `240 x 240 x 1` input into a single 2-channel out, one number representing likelihood of no tumor and the other representing likelihood of tumor.\n","\n","A simplified diagram of the architecture is as follows:\n","```\n","DEFINITION\n","----------\n","BLOCK = [ CONV --> RELU --> CONV --> RELU --> POOL ]\n","\n","LAYER NAME | OUTPUT SHAPE\n","------------------------------\n","BLOCK-01 | 120 x 120 x 8\n","BLOCK-02 | 060 x 060 x 16 \n","BLOCK-03 | 030 x 030 x 32\n","BLOCK-04 | 015 x 015 x 64\n","BLOCK-05 | 007 x 007 x 96\n","BLOCK-06 | 003 x 003 x 128\n","FLATTEN | 001 x 001 x 1152\n","FC | 001 x 001 x 2\n","```\n","\n","Note that this diagram is simplified. This particular algorithm also implements minor additions such as batch normalization and L2 regularization which are byeond the scope of this tutorial. See source code for further information. \n","\n","To implement this architecture, simply call the `net.create_classifier()` method:"]},{"metadata":{"id":"snQtml5u1yCO","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["pred = net.create_classifier(X, training=mode)"],"execution_count":0,"outputs":[]},{"metadata":{"id":"bUPgKLc41yCS","colab_type":"text"},"cell_type":"markdown","source":["### Loss and error\n","\n","Next, based on these prediction logits, we need to give the algorithm feedback whether or not the network is correct. To do so, we will use the softmax function, a formula that computes the exponential (e-power) of the given input value divided by the sum of exponential values of all the values in the inputs. For a classification model these values on the range of (0, 1) together represent the probability distribution of the different label classes.\n","\n","To implement a softmax function, we will use the a prepared template function `net.loss_sce()` that serve as a wrapper to the underlying `tf.nn.sparse_softmax_cross_entropy_with_logits()` method described in the previous tutorial.\n","\n","In addition to the loss function, we want to gauge how accurate (%) the predictions are in a human-interpretable way. To do, we will keep track of the `top-k` accuracy of our model, which in our simple two-class prediction simplifies to a `top-1` score (e.g. `k=1`)."]},{"metadata":{"id":"Tc-mVpIw1yCU","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["losses = {}\n","losses['sce'] = net.loss_sce(pred, y)\n","losses['topk'] = net.error_topk(pred, y)"],"execution_count":0,"outputs":[]},{"metadata":{"id":"btw7h7hq1yCY","colab_type":"text"},"cell_type":"markdown","source":["### Optimizer\n","\n","An optimizer is a strategy used to update the network parameters through backprogration by taking into account the quantitative loss function. We will be using the Adam optimizer for our tutorials, an algorithm for first-order gradient-based optimization of stochastic objective functions, based on adaptive estimates of lower-order moments. For further reading, see the following link for the original paper: https://arxiv.org/abs/1412.6980\n","\n","A key hyperparameter here is the optimizer **learning rate**. The learning rate describes the absolute magnitude of update for each parameter for one iteration. A higher learning rate will result in a correspondingly larger, more aggresive \"step\" towards the global minimum of a function, however a learning rate that is too high may cause the network to overshoot the true function minimum and even worse, may lead to network instability. A good initial learning rate to use in most experiments, without other guiding heuristics, is `1e-3` which is what we will set our initial `learning_rate` hyperparameter to.\n","\n","Note that the `tf.control_dependencies()` method here ensures that any other pending graph operations must be complete before the optimizer node is executed."]},{"metadata":{"id":"-t0BnrMI1yCa","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)\n","\n","with tf.control_dependencies(update_ops):\n"," global_step = tf.train.get_or_create_global_step()\n"," optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)\n"," ops['train'] = optimizer.minimize(losses['sce'], global_step=global_step)"],"execution_count":0,"outputs":[]},{"metadata":{"id":"77plaRDV1yCi","colab_type":"text"},"cell_type":"markdown","source":["### Collections\n","\n","After creating the placeholders and predictions, we will add them to named Graph collections for easy retrieval after training is complete during inference."]},{"metadata":{"id":"vDIy5wmu1yCi","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["tf.add_to_collection(\"inputs\", X)\n","tf.add_to_collection(\"inputs\", mode)\n","tf.add_to_collection(\"outputs\", pred)"],"execution_count":0,"outputs":[]},{"metadata":{"id":"NUyY-6jw1yCo","colab_type":"text"},"cell_type":"markdown","source":["### TensorBoard\n","\n","TensorBoard is useful utility that can be used to track various statistics during the network training process. Here we set up operations to create log files that can be loaded using the TensorBoard interface"]},{"metadata":{"id":"Rwi2xTdD1yCq","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["tf.summary.histogram('logits', pred)\n","tf.summary.scalar('sce', losses['sce'])\n","tf.summary.scalar('topk', losses['topk'])\n","ops['summary'] = tf.summary.merge_all()"],"execution_count":0,"outputs":[]},{"metadata":{"id":"sbBxycin1yCw","colab_type":"text"},"cell_type":"markdown","source":["# Network training\n","\n","Now that graph, loss function and optimizer have been configured, it is time to run the training algorithm. To begin we define a new `tf.Session` class and initialize our basic objects to enable saving intermediate checkpoints and writing log data. In addition we initialize `coord` and `thread` objects to handle asynchronous loading of input data into batches:\n","```\n","sess, saver, writer_train, writer_valid = net.init_session(sess, output_dir)\n","```\n","\n","To perform actual training, we will construct a loop to repeat parameter updates a total of `iteration` times. For each update, we will start by loading the data into batches `X_batch` and `y_batch`:\n","```\n","X_batch, y_batch = sess.run([batch['train']['X'], batch['train']['y']])\n","```\n","\n","We will then collapse the label masks into either 0 or 1 based on whether or not any mask is present in the entire label:\n","```\n","y_batch = np.max(y_batch > 0, axis=(1,2)).astype('float32')\n","```\n","\n","At last we call `sess.run()` to run one iteration of the training process. Specifically we wil request the network to output the `error` (accuracy %), `summary` (used for creating logs) and `step` (global step reflecting total number of iterations). Note that the `ops['train']` operation corresponding to the optimizer node is also called, but there is no output for this function and hence no (`_,`) return variable.\n","```\n"," _, metric, summary, step = sess.run(\n"," [ops['train'], losses, ops['summary'], global_step],\n"," feed_dict={\n"," X: X_batch, \n"," y: y_batch, \n"," mode: True})\n","```\n","\n","Finally, for every 10 updates, will ask the network to run against a separate validation cohort (e.g. completely separate from the training dataset) to track the overall generalization of the algorithm's learned representation:\n","```\n","if not i % 10:\n"," ...\n","```\n","\n","This entire training process can be executed by running the following cell:"]},{"metadata":{"id":"Inz2pmcD1yCw","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["with tf.Session() as sess:\n"," \n"," sess, saver, writer_train, writer_valid = net.init_session(sess, output_dir)\n"," print('\\n\\nTraining Statistics:\\n')\n","\n"," try:\n"," coord = tf.train.Coordinator()\n"," threads = tf.train.start_queue_runners(coord=coord)\n"," metrics = net.init_metrics(losses)\n","\n"," for i in range(iterations):\n"," \n"," # --- Run a single iteration of training\n"," X_batch, y_batch = sess.run([batch['train']['X'], batch['train']['y']])\n"," y_batch = np.max(y_batch > 0, axis=(1,2)).astype('float32')\n"," _, metric, summary, step = sess.run(\n"," [ops['train'], losses, ops['summary'], global_step],\n"," feed_dict={\n"," X: X_batch, \n"," y: y_batch, \n"," mode: True})\n","\n"," writer_train.add_summary(summary, step)\n"," metrics = net.update_ema(metrics, metric, mode='train', iteration=i)\n"," net.print_status(metrics, step, metric_names=['sce', 'topk'])\n","\n"," # --- Every 10th iteration run a single validation batch\n"," if not i % 10:\n","\n"," X_batch, y_batch = sess.run([batch['valid']['X'], batch['valid']['y']])\n"," y_batch = np.max(y_batch > 0, axis=(1,2)).astype('float32')\n"," metric, summary = sess.run(\n"," [losses, ops['summary']],\n"," feed_dict={\n"," X: X_batch, \n"," y: y_batch, \n"," mode: False})\n","\n"," writer_valid.add_summary(summary, step)\n"," metrics = net.update_ema(metrics, metric, mode='valid', iteration=i)\n"," net.print_status(metrics, step, metric_names=['sce', 'topk'])\n","\n"," saver.save(sess, '%s/checkpoint/model.ckpy' % output_dir)\n","\n"," finally:\n"," coord.request_stop()\n"," coord.join(threads)\n"," saver.save(sess, '%s/checkpoint/model.ckpy' % output_dir)"],"execution_count":0,"outputs":[]},{"metadata":{"id":"eQ35dAIm1yC4","colab_type":"text"},"cell_type":"markdown","source":["In the above space you will see updates of algorithm training status including number of iterations and errors on both the training and validation set data."]},{"metadata":{"id":"c6RtivH21yC6","colab_type":"text"},"cell_type":"markdown","source":["# TensorBoard \n","\n","### Overview\n","\n","TensorBoard is a suite of web applications for inspecting and understanding your TensorFlow runs and graphs. To use Tensorboard, you must embed specialized `tf.summary.*` operations into your graph which produce serialized protobufs that track various training statistics over time. The supported summary ops include:\n","\n","* tf.summary.scalar\n","* tf.summary.image\n","* tf.summary.audio\n","* tf.summary.text\n","* tf.summary.histogram\n","\n","During the training process, a specialized `summary.FileWriters()` class is used to take summary data created by `tf.summary.*` operations and write them to a specified directory, known as the `logdir`. This was implemented in following line of code above:\n","```\n","writer_valid.add_summary(summary, step)\n","```\n","\n","### Launching TensorBoard\n","\n","To launch TensorBoard, run the following command:"]},{"metadata":{"id":"hE3FCsHY1yC8","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["!pkill -9 tensorboard\n","get_ipython().system_raw(\n"," 'tensorboard --logdir {} --host 0.0.0.0 --port 6006 &'\n"," .format(output_dir)\n",")"],"execution_count":0,"outputs":[]},{"metadata":{"id":"tUwNuiMk1yDA","colab_type":"text"},"cell_type":"markdown","source":["\n","Note, this is equivalent to the following Terminal command:\n","```\n","tensorboard --logdir={output_dir} --host 0.0.0.0 --port 6006\n","```"]},{"metadata":{"id":"DVuHM7Bg9ihI","colab_type":"text"},"cell_type":"markdown","source":["To launch the TensorBoard session, open up a new tab in your browser and type in the following address pattern:\n","```\n","[IP-address]:6006\n","```\n","\n","Where `[IP-address]` is the same address of form `xxx.xxx.xxx.xxx` that represents the IP address of your instance. If you are on AWS (or a local copy) It should be the same prefix as your Jupyter notebook in the address bar currently at the top of your screen. For example, if the IP address is `34.215.158.68`, then the URL entered into the web browser is `34.215.158.68:6006`.\n","\n","Alternatively, the following lines of code can be used for convience:"]},{"metadata":{"id":"U2FmLazf-Ku2","colab_type":"text"},"cell_type":"markdown","source":["For **AWS instance** use the following Markdown text:"]},{"metadata":{"id":"Nw5tEZN31yDC","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["md('**Tensorboard URL** (right-click > open in new tab): [http://%s:6006](http://%s:6006)' % (ip, ip))"],"execution_count":0,"outputs":[]},{"metadata":{"id":"_iKTRwfb-PNw","colab_type":"text"},"cell_type":"markdown","source":["For **Google Colaboratory** use the following ngrok commands:"]},{"metadata":{"id":"_UQTQ4rj-dmQ","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["!pkill -9 ngrok\n","get_ipython().system_raw('./ngrok http 6006 &')\n","!curl -s http://localhost:4040/api/tunnels | python3 -c \\\n"," \"import sys, json; print(json.load(sys.stdin)['tunnels'][0]['public_url'])\""],"execution_count":0,"outputs":[]},{"metadata":{"id":"ruMsbKMz1yDM","colab_type":"text"},"cell_type":"markdown","source":["\n","For more information about TensorBoard usage, see link: https://github.com/tensorflow/tensorboard."]},{"metadata":{"id":"NuewZtde1yDO","colab_type":"text"},"cell_type":"markdown","source":["### Final thoughts\n","\n","Feel free to continue training the algorithm until convergence at reasonable accuracy. Once complete, turn off the kernel (top menu > `Kernel` > `Shutdown`; you can keep this tab open in your browser to retrain later) so that it's resources can be used in the next notebook. You are now ready to move on the **03 - Inference with a Classifier** to use the newly trained network on data."]}]}
--------------------------------------------------------------------------------
/code/03 - Inference with a Classifier.ipynb:
--------------------------------------------------------------------------------
1 | {"nbformat":4,"nbformat_minor":0,"metadata":{"colab":{"name":"03 - Inference with a Classifier.ipynb","version":"0.3.2","views":{},"default_view":{},"provenance":[]},"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"accelerator":"GPU"},"cells":[{"metadata":{"id":"RefVcr2WEOi4","colab_type":"text"},"cell_type":"markdown","source":["# Overview\n","\n","In this notebook we will cover the basic concepts of inference on a trained convolutional neural network in Tensorflow. We will be specifically loading a network trained to detect presence or absence of brain tumors from multimodal MR images. The data that we will be using in this tutorial comes from the MICCAI Brain Tumor Segmentation Challenge (BRaTS). More information about he BRaTS Challenge can be found here: http://braintumorsegmentation.org/\n","\n","For basics of Tensorflow operation, neural networks and training, consider reviewing the preceding lectures in this series:\n","\n"," **01 - Introduction to Data, Tensorflow and Deep Learning**
\n"," **02 - Training a Classifier**\n","\n","**Note:** Before running this notebook, be sure turn off the kernel used for training. You can keep the corresponding tab open in your browser to retrain later, just shut down the kernel from the top menu now so that it's resources can be used here (`Kernel` > `Shutdown`)."]},{"metadata":{"id":"us67eytDEOi8","colab_type":"text"},"cell_type":"markdown","source":["### Importing modules\n","\n","To visualize inference for our simple classifer implementation, we will require two open-source libraries (`tensorflow`, `numpy`) as well as our custom modules created for this tutorial (`utils`, `data`). "]},{"metadata":{"id":"0V_652YcEOi8","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["import tensorflow as tf, numpy as np\n","import sys, os\n","sys.path.append('%s/dl_tutorial/code' % os.environ['HOME'])\n","import data\n","from utils import imshow"],"execution_count":0,"outputs":[]},{"metadata":{"id":"0AfrvLJsEOjG","colab_type":"text"},"cell_type":"markdown","source":["### Loading trained model\n","\n","Our trained model has been saved in the `../exp_classifier/checkpoint` direction. Alternatively, you can also choose instead to load a pretrained model located in `../pretrained/classifier`. Based on your preference, set the model dir into the `model_dir` variable. Then to restore this model we create a new `tf.Session` class and the `tf.train.import_meta_graph()` method to load the graph and parameters into memory."]},{"metadata":{"id":"YuCAjFx3EOjI","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["model_dir = '%s/dl_tutorial/exp_classifer/checkpoint' % os.environ['HOME']\n","model_dir = '%s/dl_tutorial/pretrained/classifier' % os.environ['HOME']\n","\n","tf.reset_default_graph()\n","sess = tf.InteractiveSession()\n","saver = tf.train.import_meta_graph(\"%s/model.ckpy.meta\" % model_dir)\n","saver.restore(sess, \"%s/model.ckpy\" % model_dir)"],"execution_count":0,"outputs":[]},{"metadata":{"id":"2Wu_FHgUEOjO","colab_type":"text"},"cell_type":"markdown","source":["Using the named Graph collections specified during the training process, we can quickly reload handles to the key placeholders of our graph:"]},{"metadata":{"id":"B3TB8YjlEOjQ","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["X, mode = tf.get_collection(\"inputs\")\n","pred = tf.get_collection(\"outputs\")[0]"],"execution_count":0,"outputs":[]},{"metadata":{"id":"EvbEUHmKEOjU","colab_type":"text"},"cell_type":"markdown","source":["### Running inference\n","\n","Now that the graph structure is restored, we simply use the same `sess.run()` method to pass new data into their respective placeholders. As before, we will use the `data.load()` method to load new instances of validation data. The following cell is nearly identical to the several lines of code used to test our graph in the first tutorial (**01 - Introduction to Data, Tensorflow and Deep Learning**). Let us try it out again now after the parameters have been trained. As before, feel free to re-run the following cell repeatedly (ctrl + Enter) to see the network in action:"]},{"metadata":{"id":"2aCppe4GEOjW","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["dat, lbl = data.load(mode='train', n=1)\n","lbl = np.max(lbl > 0, axis=(1,2,3)).astype('int32')\n","logits = sess.run(pred, {X: dat, mode: False})\n","\n","print('REAL (tumor, y/n?): %s' % ('y' if lbl else 'n'))\n","print('PRED (tumor, y/n?): %s' % ('y' if logits[0, 1] > logits[0, 0] else 'n'))\n","\n","imshow(dat[..., 1], title='FLAIR')"],"execution_count":0,"outputs":[]}]}
--------------------------------------------------------------------------------
/code/04 - Training a U-Net.ipynb:
--------------------------------------------------------------------------------
1 | {"nbformat":4,"nbformat_minor":0,"metadata":{"colab":{"name":"04 - Training a U-Net.ipynb","version":"0.3.2","views":{},"default_view":{},"provenance":[]},"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"accelerator":"GPU"},"cells":[{"metadata":{"id":"e0RhTbC7BHvC","colab_type":"text"},"cell_type":"markdown","source":["# Overview\n","\n","In this notebook we will cover the basic concepts for training a specific convolutional neural network architecture known as a U-net in TensorFlow. Using this architecture we are able to estimate boundaries of brain tumor tissue types (e.g. segmentation) from multimodal MR images. The data that we will be using in this tutorial comes from the MICCAI Brain Tumor Segmentation Challenge (BRaTS). More information about he BRaTS Challenge can be found here: http://braintumorsegmentation.org/\n","\n","For basics of Tensorflow operation, neural networks and training, consider reviewing the preceding lectures in this series:\n","\n"," **01 - Introduction to Data, Tensorflow and Deep Learning**
\n"," **02 - Training a Classifier**
\n"," **03 - Inference with a Classifier**\n","\n","### U-net\n","\n","The U-net is a popular network design under the family of encoder-decoder architectures. In this type of architecture, an encoding (collapsing) arm gradually reduces the spatial dimension with pooling layers (or strided convolutions) while a decoding (expanding) arm gradually recovers the object details and spatial dimension. There are usually shortcut connections from encoder to decoder to help decoder recover the object details better. For further reading, see link for original paper (https://arxiv.org/abs/1505.04597) as well as a blog here about different segmentation techniques (http://blog.qure.ai/notes/semantic-segmentation-deep-learning-review)."]},{"metadata":{"id":"nqYGaJgaBHvE","colab_type":"text"},"cell_type":"markdown","source":["### Importing modules\n","\n","To train our simple classifer implementation, we will require three open-source libraries (`tensorflow`, `numpy` and `os`) as well as our custom modules created for this tutorial (`net`, `data`). "]},{"metadata":{"id":"gyjnWwrZBHvG","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["import os, sys\n","import tensorflow as tf, numpy as np\n","from IPython.display import Image\n","\n","sys.path.append('%s/dl_tutorial/code' % os.environ['HOME'])\n","import net, data\n","\n","os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' \n","from IPython.display import Markdown as md\n","ip = open('../public-ipv4').read() if os.path.exists('../public-ipv4') else '0.0.0.0'"],"execution_count":0,"outputs":[]},{"metadata":{"id":"vX5wW6dEBHvO","colab_type":"text"},"cell_type":"markdown","source":["### Hyperparameter variables\n","\n","Hyperparameters are parameters whose values are set before the learning process begins and which in turn influence and direct the training process. These will be the three most important hyperparameter variables to vary in this experiment. We will cover these in more detail as they are encountered in the code below."]},{"metadata":{"id":"YWJspVd6BHvQ","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["iterations = 200\n","batch_size = 16\n","learning_rate = 1e-3"],"execution_count":0,"outputs":[]},{"metadata":{"id":"unkoIbZ6BHvU","colab_type":"text"},"cell_type":"markdown","source":["### Preparation\n","\n","Here we perform some basic preparatory steps including: \n","\n","* setting the data root directory \n","\n","> If **AWS** or **Google**: `/data/brats/npy`\n","\n","> If **local**: `../data` (or whichever local directory data is stored in)\n","\n","* making the output directory for saving training checkpoints and logs\n","* defining an `ops` dictionary to save operations\n","* reseting any existing graph that may exist (`tf.reset_default_graph()`)"]},{"metadata":{"id":"w-g3zfpbBHvY","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["root = '/data/brats/npy'\n","output_dir = '%s/dl_tutorial/exp_unet' % os.environ['HOME']\n","os.makedirs(output_dir, exist_ok=True)\n","ops = {}\n","tf.reset_default_graph()"],"execution_count":0,"outputs":[]},{"metadata":{"id":"iF4zE_s4BHve","colab_type":"text"},"cell_type":"markdown","source":["### Data batch\n","\n","A data **mini-batch** is used to describe the collection of image and label pairs used to perform one update of our network parameters. The more number of images and labels we use for each update, the more likely that update is to reflect the underlying population data. However, the trade-off is that computationally each network update will require more time. A good initial starting point for images matrices of our dataset may be 16 or 32. \n","\n","To implement batching, we will use a prepared template method `net.init_batch()` to load a number of slices simulatenously:"]},{"metadata":{"id":"mk80bf01BHve","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["batch = net.init_batch(batch_size, root=root)"],"execution_count":0,"outputs":[]},{"metadata":{"id":"8uZXrABoBHvi","colab_type":"text"},"cell_type":"markdown","source":["### Placeholders\n","\n","A tensorflow **placeholder** is an entry point for us to feed actual data values into the model. We must define this **placeholder** and all subsequent downstream operations performed on this **placeholder** before ever passing data into the model. \n","\n","The placeholder `X` will serve as the method for introduction image data into the graph. The placeholder `y` will serve as the method for introducing the correct target label at each voxel location:\n","```\n","0 = background (no tumor)\n","1 = non-enhancing tumor\n","2 = edema\n","3 = necrosis\n","4 = enhancing tumor\n","```\n","\n","The placeholder `mask` will serve as a binary mask representing voxels containing non-background data; the utility of masking the loss function will be discussed further below. The placeholder `mode` will serve as a method for introducing whether or not the graph is being executed for training or for validation."]},{"metadata":{"id":"ESx0IIhaBHvk","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["X = tf.placeholder(tf.float32, shape=[None, 240, 240, 4], name='X')\n","y = tf.placeholder(tf.float32, shape=[None, 240, 240, 5], name='y')\n","mask = tf.placeholder(tf.float32, shape=[None, 240, 240, 1], name='mask')\n","mode = tf.placeholder(tf.bool, name='mode')"],"execution_count":0,"outputs":[]},{"metadata":{"id":"jM1_A9P3BHvu","colab_type":"text"},"cell_type":"markdown","source":["### Network\n","\n","In this example we will be using a template U-net created by the `net.create_unet()` method. The encoder arm of the architecture is similar to the classifer from earlier in this tutorial series, implemented by alternating series of convolutions, ReLU non-linearities and max-pooling. In addition a symmetric decoder arm of the architecture uses convolutional-transpose operations to gradually upsample the feature maps and recover high-frequency object details. See below for simplified diagram of network architecture:\n"]},{"metadata":{"id":"XcL3XY74C302","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0},"base_uri":"https://localhost:8080/","height":482},"outputId":"8a9a5081-0178-497c-f915-c2b44c52eb53","executionInfo":{"status":"ok","timestamp":1526345358064,"user_tz":420,"elapsed":1262,"user":{"displayName":"Peter Chang","photoUrl":"https://lh3.googleusercontent.com/a/default-user=s128","userId":"108798638501675293926"}}},"cell_type":"code","source":["Image('%s/dl_tutorial/code/unet.png' % os.environ['HOME'])"],"execution_count":6,"outputs":[{"output_type":"execute_result","data":{"image/png":"iVBORw0KGgoAAAANSUhEUgAAApkAAAHRCAYAAAAhTKLOAAABfGlDQ1BJQ0MgUHJvZmlsZQAAKJFj\nYGAqSSwoyGFhYGDIzSspCnJ3UoiIjFJgv8PAzcDDIMRgxSCemFxc4BgQ4MOAE3y7xsAIoi/rgsxK\n8/x506a1fP4WNq+ZclYlOrj1gQF3SmpxMgMDIweQnZxSnJwLZOcA2TrJBUUlQPYMIFu3vKQAxD4B\nZIsUAR0IZN8BsdMh7A8gdhKYzcQCVhMS5AxkSwDZAkkQtgaInQ5hW4DYyRmJKUC2B8guiBvAgNPD\nRcHcwFLXkYC7SQa5OaUwO0ChxZOaFxoMcgcQyzB4MLgwKDCYMxgwWDLoMjiWpFaUgBQ65xdUFmWm\nZ5QoOAJDNlXBOT+3oLQktUhHwTMvWU9HwcjA0ACkDhRnEKM/B4FNZxQ7jxDLX8jAYKnMwMDcgxBL\nmsbAsH0PA4PEKYSYyjwGBn5rBoZt5woSixLhDmf8xkKIX5xmbARh8zgxMLDe+///sxoDA/skBoa/\nE////73o//+/i4H2A+PsQA4AJHdp4IxrEg8AAAGdaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8\neDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA1LjQu\nMCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1y\nZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAg\nICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAg\nICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjY2NTwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAg\nICAgIDxleGlmOlBpeGVsWURpbWVuc2lvbj40NjU8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAg\nICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KV4z5MwAAQABJ\nREFUeAHsnQd4VMUWx/+kk4QQQpNOKKGGXkRqFFAeRXwKCCoK+AABBaVYEAEVVASFJyAC0lQQsNCU\n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\n","text/plain":[""]},"metadata":{"tags":[]},"execution_count":6}]},{"metadata":{"id":"5AkQtjxxC0BI","colab_type":"text"},"cell_type":"markdown","source":["\n","To implement this architecture, simply call the `net.create_unet()` method:"]},{"metadata":{"id":"cH_NDridBHvw","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["pred = net.create_unet(X, training=mode)"],"execution_count":0,"outputs":[]},{"metadata":{"id":"zTqTKaiqBHv4","colab_type":"text"},"cell_type":"markdown","source":["### Masking\n","\n","Masking the loss function is a technique by which non-important voxels in the output label can be ignored by the algorithm during training. For example, in our data, the background voxels from our brain-extracted images (signal intensity == 0) will never contain tumor. By setting these voxels to zero here in the prediction tensor, they will never contribute to the loss function and thus have a derivative of zero during backprogation. By focusing algorithm learning in this way, overall speed of training can be increased. "]},{"metadata":{"id":"qUQ1a9iwBHv6","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["masked_pred = pred * mask\n","masked_y = y * mask"],"execution_count":0,"outputs":[]},{"metadata":{"id":"3srHGMBsBHwA","colab_type":"text"},"cell_type":"markdown","source":["### Loss and error\n","\n","Next, based on these prediction logits, we need to give the algorithm feedback whether or not the network is correct. To do so, we will use a modified **Dice** score loss function. Comparision between the true formal definition and our modified approximation are shown here:\n","```\n","Dice (formal) = 2 x (y_pred UNION y_true) \n"," -------------------------\n"," | y_pred | + | y_true | \n","\n","Dice (approx) = 2 x (y_pred * y_true) + d \n"," -------------------------\n"," | y_pred | + | y_true | + d \n","```\n","\n","Here *d* is small delta == 1e-7 added both to numerator/denominator to prevent division by zero. Note that the approximation is necessary because the true formal Dice score definition is non-differentiable.\n","\n","To implement a Dice score loss function, we will use the a prepared template function `net.loss_dice()`."]},{"metadata":{"id":"koK6mmfcBHwC","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["losses = net.loss_dice(masked_pred, masked_y)"],"execution_count":0,"outputs":[]},{"metadata":{"id":"Er3iM9YpBHwI","colab_type":"text"},"cell_type":"markdown","source":["### Optimizer\n","\n","An optimizer is a strategy used to update the network parameters through backprogration by taking into account the quantitative loss function. We will be using the Adam optimizer for our tutorials, an algorithm for first-order gradient-based optimization of stochastic objective functions, based on adaptive estimates of lower-order moments. For further reading, see the following link for the original paper: https://arxiv.org/abs/1412.6980\n","\n","A key hyperparameter here is the optimizer **learning rate**. The learning rate describes the absolute magnitude of update for each parameter for one iteration. A higher learning rate will result in a correspondingly larger, more aggresive \"step\" towards the global minimum of a function, however a learning rate that is too high may cause the network to overshoot the true function minimum and even worse, may lead to network instability. A good initial learning rate to use in most experiments, without other guiding heuristics, is `1e-3` which is what we will set our initial `learning_rate` hyperparameter to.\n","\n","Note that the `tf.control_dependencies()` method here ensures that any other pending graph operations must be complete before the optimizer node is executed."]},{"metadata":{"id":"kV_5WCZPBHwK","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)\n","\n","with tf.control_dependencies(update_ops):\n"," global_step = tf.train.get_or_create_global_step()\n"," optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)\n"," ops['train'] = optimizer.minimize(-losses['dice'], global_step=global_step)"],"execution_count":0,"outputs":[]},{"metadata":{"id":"SVg3LbB9BHwO","colab_type":"text"},"cell_type":"markdown","source":["### Collections\n","\n","After creating the placeholders and predictions, we will add them to named Graph collections for easy retrieval after training is complete during inference."]},{"metadata":{"id":"7BIvPkvmBHwQ","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["tf.add_to_collection(\"inputs\", X)\n","tf.add_to_collection(\"inputs\", mode)\n","tf.add_to_collection(\"inputs\", mask)\n","tf.add_to_collection(\"outputs\", pred)"],"execution_count":0,"outputs":[]},{"metadata":{"id":"XjmRDYj9BHwW","colab_type":"text"},"cell_type":"markdown","source":["### TensorBoard\n","\n","TensorBoard is useful utility that can be used to track various statistics during the network training process. Here we set up operations to create log files that can be loaded using the TensorBoard interface"]},{"metadata":{"id":"k5AhUmP3BHwY","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["tf.summary.histogram('logits', pred)\n","tf.summary.scalar('dice-all', losses['dice'])\n","tf.summary.scalar('dice-background', losses[0])\n","tf.summary.scalar('dice-nET', losses[1])\n","tf.summary.scalar('dice-edema', losses[2])\n","tf.summary.scalar('dice-necrosis', losses[3])\n","tf.summary.scalar('dice-enhancement', losses[4])\n","ops['summary'] = tf.summary.merge_all()"],"execution_count":0,"outputs":[]},{"metadata":{"id":"ErpXV_utBHwg","colab_type":"text"},"cell_type":"markdown","source":["# Network training\n","\n","Now that graph, loss function and optimizer have been configured, it is time to run the training algorithm. To begin we define a new `tf.Session` class and initialize our basic objects to enable saving intermediate checkpoints and writing log data. In addition we initialize `coord` and `thread` objects to handle asynchronous loading of input data into batches:\n","```\n","sess, saver, writer_train, writer_valid = net.init_session(sess, output_dir)\n","```\n","\n","To perform actual training, we will construct a loop to repeat parameter updates a total of `iteration` times. For each update, we will start by loading the data into batches `X_batch`, `y_batch` and `mask_batch`:\n","```\n","X_batch, y_batch, mask_batch = sess.run([batch['train']['X'], batch['train']['y'], batch['train']['mask']])\n","```\n","\n","Then we call `sess.run()` to run one iteration of the training process. Specifically we wil request the network to output the tensor variables `losses`, `summary` (used for creating logs) and `step` (global step reflecting total number of iterations). Note that the `ops['train']` operation corresponding to the optimizer node is also called, but there is no output for this function and hence no (`_,`) return variable.\n","```\n"," _, metric, summary, step = sess.run(\n"," [ops['train'], losses, ops['summary'], global_step],\n"," feed_dict={\n"," X: X_batch, \n"," y: y_batch, \n"," mask: mask_batch,\n"," mode: True})\n","```\n","\n","Finally, for every 10 updates, will ask the network to run against a separate validation cohort (e.g. completely separate from the training dataset) to track the overall generalization of the algorithm's learned representation:\n","```\n","if not i % 10:\n"," ...\n","```\n","\n","\n","This entire training process can be executed by running the following cell:"]},{"metadata":{"id":"qhPIK61ABHwi","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0},"base_uri":"https://localhost:8080/","height":86},"outputId":"d2e3ce0f-d57a-4b4b-9001-5194dab19692"},"cell_type":"code","source":["with tf.Session() as sess:\n","\n"," sess, saver, writer_train, writer_valid = net.init_session(sess, output_dir)\n"," print('\\n\\nTraining Statistics:\\n')\n","\n"," try:\n"," coord = tf.train.Coordinator()\n"," threads = tf.train.start_queue_runners(coord=coord)\n"," metrics = net.init_metrics(losses)\n","\n"," for i in range(iterations):\n","\n"," X_batch, y_batch, mask_batch = sess.run([batch['train']['X'], batch['train']['y'], batch['train']['mask']])\n"," _, metric, summary, step = sess.run(\n"," [ops['train'], losses, ops['summary'], global_step],\n"," feed_dict={\n"," X: X_batch, \n"," y: y_batch, \n"," mask: mask_batch,\n"," mode: True})\n","\n"," writer_train.add_summary(summary, step)\n"," metrics = net.update_ema(metrics, metric, mode='train', iteration=i)\n"," net.print_status(metrics, step, metric_names=[2, 4])\n","\n"," # --- Every 10th iteration run a single validation batch\n"," if not i % 10:\n","\n"," X_batch, y_batch, mask_batch = sess.run([batch['valid']['X'], batch['valid']['y'], batch['valid']['mask']])\n"," metric, summary = sess.run(\n"," [losses, ops['summary']],\n"," feed_dict={\n"," X: X_batch, \n"," y: y_batch, \n"," mask: mask_batch,\n"," mode: False})\n","\n"," writer_valid.add_summary(summary, step)\n"," metrics = net.update_ema(metrics, metric, mode='valid', iteration=i)\n"," net.print_status(metrics, step, metric_names=[2, 4])\n","\n"," saver.save(sess, '%s/checkpoint/model.ckpy' % output_dir)\n","\n"," finally:\n"," coord.request_stop()\n"," coord.join(threads)\n"," saver.save(sess, '%s/checkpoint/model.ckpy' % output_dir)"],"execution_count":0,"outputs":[{"output_type":"stream","text":["\n","\n","Training Statistics:\n","\n"],"name":"stdout"}]},{"metadata":{"id":"EnK4FvO0BHwk","colab_type":"text"},"cell_type":"markdown","source":["In the above space you will see updates of algorithm training status including number of iterations and errors on both the training and validation set data."]},{"metadata":{"id":"Cq8HcJwDBHwm","colab_type":"text"},"cell_type":"markdown","source":["# TensorBoard \n","\n","### Overview\n","\n","TensorBoard is a suite of web applications for inspecting and understanding your TensorFlow runs and graphs. To use Tensorboard, you must embed specialized `tf.summary.*` operations into your graph which produce serialized protobufs that track various training statistics over time. The supported summary ops include:\n","\n","* tf.summary.scalar\n","* tf.summary.image\n","* tf.summary.audio\n","* tf.summary.text\n","* tf.summary.histogram\n","\n","During the training process, a specialized `summary.FileWriters()` class is used to take summary data created by `tf.summary.*` operations and write them to a specified directory, known as the `logdir`. This was implemented in following line of code above:\n","```\n","writer_valid.add_summary(summary, step)\n","```"]},{"metadata":{"id":"ozPOhuxXBHwm","colab_type":"text"},"cell_type":"markdown","source":["### Launching TensorBoard\n","\n","To launch TensorBoard, run the following command:"]},{"metadata":{"id":"TIjx6JynBHwm","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["!pkill -9 tensorboard\n","get_ipython().system_raw(\n"," 'tensorboard --logdir {} --host 0.0.0.0 --port 6006 &'\n"," .format(output_dir)\n",")"],"execution_count":0,"outputs":[]},{"metadata":{"id":"t0IhS_jSBHws","colab_type":"text"},"cell_type":"markdown","source":["\n","Note, this is equivalent to the following Terminal command:\n","```\n","tensorboard --logdir={output_dir} --host 0.0.0.0 --port 6006\n","```\n","\n","To launch the TensorBoard session, open up a new tab in your browser and type in the following address pattern:\n","```\n","[IP-address]:6006\n","```\n","\n","Where `[IP-address]` is the same address of form `xxx.xxx.xxx.xxx` that represents the IP address of your AWS instance. It should be the same prefix as your Jupyter notebook in the address bar currently at the top of your screen. For example, if the IP address is `34.215.158.68`, then the URL entered into the web browser is `34.215.158.68:6006`.\n","\n","Alternatively, the following lines of code can be used for convience:"]},{"metadata":{"id":"6ULvbJ6MDpGw","colab_type":"text"},"cell_type":"markdown","source":["For **AWS instance** use the following Markdown text:"]},{"metadata":{"id":"LRrPkhSqBHwu","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["md('**Tensorboard URL** (right-click > open in new tab): [http://%s:6006](http://%s:6006)' % (ip, ip))"],"execution_count":0,"outputs":[]},{"metadata":{"id":"294E1pQVDrvG","colab_type":"text"},"cell_type":"markdown","source":["For **Google Colaboratory** use the following ngrok commands:"]},{"metadata":{"id":"Kt7W6UDkDu5u","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["!pkill -9 ngrok\n","get_ipython().system_raw('./ngrok http 6006 &')\n","!curl -s http://localhost:4040/api/tunnels | python3 -c \\\n"," \"import sys, json; print(json.load(sys.stdin)['tunnels'][0]['public_url'])\""],"execution_count":0,"outputs":[]},{"metadata":{"id":"rC_wh3cLBHww","colab_type":"text"},"cell_type":"markdown","source":["\n","For more information about TensorBoard usage, see link: https://github.com/tensorflow/tensorboard."]},{"metadata":{"id":"ONn13O8GBHwy","colab_type":"text"},"cell_type":"markdown","source":["### Final thoughts\n","\n","Feel free to continue training the algorithm until convergence at reasonable accuracy. Once complete, turn off the kernel (top menu > `Kernel` > `Shutdown`; you can keep this tab open in your browser to retrain later) so that it's resources can be used in the next notebook. You are now ready to move on the **05 - Inference with a U-net** to use the newly trained network on data."]}]}
--------------------------------------------------------------------------------
/code/05 - Inference with a U-Net.ipynb:
--------------------------------------------------------------------------------
1 | {"nbformat":4,"nbformat_minor":0,"metadata":{"colab":{"name":"05 - Inference with a U-Net.ipynb","version":"0.3.2","views":{},"default_view":{},"provenance":[]},"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"}},"cells":[{"metadata":{"id":"xdst6j41JPhS","colab_type":"text"},"cell_type":"markdown","source":["# Overview\n","\n","In this notebook we will cover the basic concepts of inference on a trained U-net in Tensorflow. We will be specifically loading a network trained to perform segmentation of brain tumors from multimodal MR images. The data that we will be using in this tutorial comes from the MICCAI Brain Tumor Segmentation Challenge (BRaTS). More information about he BRaTS Challenge can be found here: http://braintumorsegmentation.org/\n","\n","For basics of Tensorflow operation, neural networks and training, consider reviewing the preceding lectures in this series:\n","\n"," **01 - Introduction to Data, Tensorflow and Deep Learning**
\n"," **02 - Training a Classifier**
\n"," **03 - Inference with a Classifier**
\n"," **04 - Training a U-Net**\n","\n","**Note:** Before running this notebook, be sure turn off the kernel used for training. You can keep the corresponding tab open in your browser to retrain later, just shut down the kernel from the top menu now so that it's resources can be used here (`Kernel` > `Shutdown`)."]},{"metadata":{"id":"hJkRwZn-JPhU","colab_type":"text"},"cell_type":"markdown","source":["### Importing modules\n","\n","To visualize inference for our simple classifer implementation, we will require two open-source libraries (`tensorflow`, `numpy`) as well as our custom modules created for this tutorial (`utils`, `data`). "]},{"metadata":{"id":"pjXnXBseJPhU","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["import tensorflow as tf, numpy as np\n","import sys, os\n","sys.path.append('%s/dl_tutorial/code' % os.environ['HOME'])\n","import data\n","from utils import imshow"],"execution_count":0,"outputs":[]},{"metadata":{"id":"Ay4Hca8dJPhe","colab_type":"text"},"cell_type":"markdown","source":["### Loading trained model\n","\n","Our trained model has been saved in the `../exp_unet/checkpoint` direction. Alternatively, you can also choose instead to load a pretrained model located in `../pretrained/unet`. Based on your preference, set the model dir into the `model_dir` variable. Then to restore this model we create a new `tf.Session` class and the `tf.train.import_meta_graph()` method to load the graph and parameters into memory."]},{"metadata":{"id":"sMBIdDV6JPhg","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["model_dir = '%s/dl_tutorial/exp_unet/checkpoint' % os.environ['HOME']\n","model_dir = '%s/dl_tutorial/pretrained/unet' % os.environ['HOME']\n","\n","tf.reset_default_graph()\n","sess = tf.InteractiveSession()\n","saver = tf.train.import_meta_graph(\"%s/model.ckpy.meta\" % model_dir)\n","saver.restore(sess, \"%s/model.ckpy\" % model_dir)"],"execution_count":0,"outputs":[]},{"metadata":{"id":"jzJpkTHOJPhk","colab_type":"text"},"cell_type":"markdown","source":["Using the named Graph collections specified during the training process, we can quickly reload handles to the key placeholders of our graph:"]},{"metadata":{"id":"KbRczp9YJPhm","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["X, mode, mask = tf.get_collection(\"inputs\")\n","pred = tf.get_collection(\"outputs\")[0]"],"execution_count":0,"outputs":[]},{"metadata":{"id":"hzRfld5qJPhs","colab_type":"text"},"cell_type":"markdown","source":["### Running inference\n","\n","Now that the graph structure is restored, we simply use the same `sess.run()` method to pass new data into their respective placeholders. As before, we will use the `data.load()` method to load new instances of validation data. Feel free to re-run the following cell repeatedly (ctrl + Enter) to see the network in action:"]},{"metadata":{"id":"r4fz6e78JPhs","colab_type":"code","colab":{"autoexec":{"startup":false,"wait_interval":0}}},"cell_type":"code","source":["dat, lbl, msk = data.load(mode='train', n=1, return_mask=True)\n","argmax = sess.run(pred, {X: dat, mode: False})\n","argmax = np.argmax(argmax, axis=3)\n","argmax[msk[..., 0] == 0] = 0\n","\n","imshow(dat[..., 1], lbl=lbl if lbl.any() else None, title='FLAIR (ground-truth)')\n","imshow(dat[..., 1], lbl=argmax if argmax.any() else None, title='FLAIR (prediction)')"],"execution_count":0,"outputs":[]}]}
--------------------------------------------------------------------------------
/code/README.md:
--------------------------------------------------------------------------------
1 | # Overview
2 |
3 | During the tutorial session, all code written by the participants will be completed using the Jupyter notebook, an iPython kernel / server that will be running from your own personal AWS instance and accessed through a web-browser. Through this web-based interface one will be able to write, edit and run code in an easy way without needing to use the Linux command line. For more advanced users, this entire Github repository is available for access from the command line at `~/dl_tutorial`.
4 |
5 | To setup and connect to the Jupyter see instructions on home page: https://github.com/peterchang77/dl_tutorial
6 |
7 | ### Outline
8 |
9 | The workshop session will be organized into a five-part series. The majority of background information is present in the first lecture. The remaining four notebooks cover implementation of training and inference of a CNN. For best results, consider advancing through the following topics in order:
10 |
11 | **01 - Introduction to Data, Tensorflow and Deep Learning**
12 | **02 - Training a Classifier**
13 | **03 - Inference with a Classifier**
14 | **04 - Training a U-Net**
15 | **05 - Inference with a U-Net**
16 |
17 | Without an active AWS instance it is possible to simply launch the `*.ipynb` files directly here in Github to preview content.
18 |
19 | # Data I/O
20 |
21 | The `data.py` module abracts a pipeline for loading random slices of preprocessed data. The syntax is as follows:
22 | ```
23 | import data
24 |
25 | dat, lbl = data.load(mode='train', n=16)
26 |
27 | ```
28 |
29 | From the corresponding docstring within the method:
30 | ```
31 | def load(mode='train', n=1, sid=None, z=None, return_mask=False):
32 | """
33 | Method to open n random slices of data and corresponding labels. Note that this
34 | method will load data in a stratified manner such that approximately 50% of all
35 | returned data will contain tumor.
36 |
37 | :params
38 |
39 | (str) mode : 'train' or 'valid'
40 | (int) n : number of examples to open
41 | (str) sid : if provided, will load specific study ID
42 | (int) z : if provided, will load specifc slice
43 | (bool) return_mask : if True, will also return mask containing brain parenchyma
44 |
45 | :return
46 |
47 | (np.array) dat : N x I x J x 4 input (dtype = 'float32')
48 | (np.array) lbl : N x I x J x 1 label (dtype = 'uint8')
49 | (np.array) msk : N x I x J x 1 lmask (dtype = 'float32'), (optional)
50 |
51 | """
52 | ```
53 |
54 | ### Data source directory
55 |
56 | By default, upon import, the `data.py` module will search for a directory on your local machine located at `/data/brats/npy`; this is the location of the full dataset if you clone the AWS AMI provided as part of this tutorial and are following along currently on an EC2 instance. If found, this directory will be set as the root for loading data. If absent, then the toy dataset present as part of this repository located at `../data` will be used.
57 |
58 | Note that the data source directory can be manually set any time after module import with the `data.set_root()` method.
59 |
--------------------------------------------------------------------------------
/code/data.py:
--------------------------------------------------------------------------------
1 | import os, glob, pickle
2 | import numpy as np
3 |
4 | # ======================================================================
5 | # METHODS FOR MANAGING and LOADING DATA
6 | # ======================================================================
7 | #
8 | # This script contains methods for:
9 | #
10 | # (1) Saving data summary into a data.pickle file which contains a
11 | # dictionary containing study IDs, pp statistics and whether or not
12 | # that study is in the training or validation cohort. This dict
13 | # should be updated every time new data is added, prior to using
14 | # the data.load() method.
15 | #
16 | # (2) Load random slice(s) of data from random studies in either training
17 | # or validation cohort.
18 | #
19 | # ======================================================================
20 |
21 | def create_summary(root='../data', valid_ratio=0.2):
22 | """
23 | Method to create a summary *.pickle file in the current directory
24 | containing summary dictionary for the dataset of form:
25 |
26 | summary = {
27 | 'train': [
28 | {'studyid': 'study_00', 'mean': 0, 'sd': 0},
29 | {'studyid': 'study_01', 'mean': 0, 'sd': 0}, ...
30 | ],
31 | 'valid': [
32 | {'studyid': 'study_02', 'mean': 0, 'sd': 0},
33 | {'studyid': 'study_03', 'mean': 0, 'sd': 0}, ...
34 | ]
35 | }
36 |
37 | """
38 | dfiles = glob.glob('%s/*/dat.npy' % root)
39 | summary = {'train': [], 'valid': []}
40 |
41 | for n, dfile in enumerate(dfiles):
42 | print('Saving summary %03i: %s' % (n + 1, dfile))
43 | dat = np.memmap(dfile, dtype='int16', mode='r')
44 |
45 | sid = os.path.basename(os.path.dirname(dfile))
46 | group = 'train' if np.random.rand() > valid_ratio else 'valid'
47 | summary[group].append({
48 | 'studyid': sid,
49 | 'mean': np.mean(dat[dat > 0]),
50 | 'sd': np.std(dat[dat > 0])})
51 |
52 | pickle.dump(summary, open('%s/data.pickle' % root, 'wb'))
53 |
54 | def load(mode='train', n=1, sid=None, z=None, return_mask=False):
55 | """
56 | Method to open n random slices of data and corresponding labels. Note that this
57 | method will load data in a stratified manner such that approximately 50% of all
58 | returned data will contain tumor.
59 |
60 | :params
61 |
62 | (str) mode : 'train' or 'valid'
63 | (int) n : number of examples to open
64 | (str) sid : if provided, will load specific study ID
65 | (int) z : if provided, will load specifc slice
66 | (bool) return_mask : if True, will also return mask containing brain parenchyma
67 |
68 | :return
69 |
70 | (np.array) dat : N x I x J x 4 input (dtype = 'float32')
71 | (np.array) lbl : N x I x J x 1 label (dtype = 'uint8')
72 | (np.array) msk : N x I x J x 1 lmask (dtype = 'float32'), (optional)
73 |
74 | """
75 | global root, summary
76 | random = True
77 |
78 | # --- Load specific slice
79 | if sid is not None and z is not None:
80 | for mode in ['train', 'valid']:
81 | indices = [n for n, stats in enumerate(summary[mode]) if stats['studyid'] == sid]
82 | if len(indices) > 0:
83 | indices = [indices[0]]
84 | random = False
85 | break
86 |
87 | # --- Load ranom n-slices
88 | if random:
89 | indices = np.random.randint(0, len(summary[mode]), n)
90 |
91 | dats = []
92 | lbls = []
93 | msks = []
94 |
95 | for ind in indices:
96 | stats = summary[mode][ind]
97 |
98 | # --- Load data and labels
99 | fname = '%s/%s/dat.npy' % (root, stats['studyid'])
100 | dat = np.memmap(fname, dtype='int16', mode='r')
101 | dat = dat.reshape(-1, 240, 240, 4)
102 |
103 | fname = '%s/%s/lbl.npy' % (root, stats['studyid'])
104 | lbl = np.memmap(fname, dtype='uint8', mode='r')
105 | lbl = lbl.reshape(-1, 240, 240, 1)
106 |
107 | # --- Determine slice
108 | if random:
109 | reduce_sum = np.sum(lbl, axis=(1,2,3))
110 | z = np.nonzero(reduce_sum > 0)[0] if np.random.rand() > 0.5 else np.nonzero(reduce_sum == 0)[0]
111 | np.random.shuffle(z)
112 | z = z[0]
113 |
114 | msks.append((dat[z, ..., :1] > 0))
115 | dats.append((dat[z] - stats['mean']) / stats['sd'])
116 | lbls.append(lbl[z])
117 |
118 | dats = np.stack(dats, axis=0)
119 | lbls = np.stack(lbls, axis=0)
120 | msks = np.stack(msks, axis=0).astype('float32')
121 |
122 | if return_mask:
123 | return dats, lbls, msks
124 |
125 | else:
126 | return dats, lbls
127 |
128 | def set_root(loc=None):
129 | """
130 | Method to set root directory location of data
131 |
132 | """
133 | global root, summary
134 |
135 | if loc is None:
136 | paths = [
137 | '/data/brats/npy', # AWS
138 | '../data', # local repo
139 | 'brats/npy' # Google CoLaboratory
140 | ]
141 | for path in paths:
142 | if os.path.exists(path):
143 | loc = path
144 | break
145 |
146 | root = loc
147 | summary_file = '%s/data.pickle' % root
148 | if not os.path.exists(summary_file):
149 | create_summary(root=root)
150 |
151 | summary = pickle.load(open(summary_file, 'rb'))
152 |
153 | # --- Set root and summary
154 | global root, summary
155 | set_root()
156 |
157 | if __name__ == '__main__':
158 |
159 | # create_summary()
160 | pass
161 |
--------------------------------------------------------------------------------
/code/environment.yml:
--------------------------------------------------------------------------------
1 | name: dl_aws
2 | channels:
3 | - conda-forge
4 | - defaults
5 | dependencies:
6 | - backports=1.0=py35_1
7 | - backports.functools_lru_cache=1.5=py35_0
8 | - ca-certificates=2018.1.18=0
9 | - certifi=2018.1.18=py35_0
10 | - cycler=0.10.0=py35_0
11 | - dbus=1.10.22=0
12 | - expat=2.2.5=0
13 | - fontconfig=2.12.1=6
14 | - freetype=2.7=2
15 | - gettext=0.19.8.1=0
16 | - glib=2.55.0=0
17 | - gst-plugins-base=1.8.0=0
18 | - gstreamer=1.8.0=1
19 | - ipdb=0.10.1=py35_0
20 | - jpeg=9b=2
21 | - libffi=3.2.1=3
22 | - libpng=1.6.28=2
23 | - libxcb=1.12=1
24 | - matplotlib=2.1.1=py35_0
25 | - openssl=1.0.2n=0
26 | - pcre=8.39=0
27 | - pyparsing=2.2.0=py35_0
28 | - pyqt=5.6.0=py35_4
29 | - qt=5.6.2=6
30 | - xorg-libxau=1.0.8=3
31 | - xorg-libxdmcp=1.1.2=3
32 | - bleach=2.1.2=py35_0
33 | - decorator=4.1.2=py35_0
34 | - entrypoints=0.2.3=py35h48174a2_2
35 | - gmp=6.1.2=h6c8ec71_1
36 | - html5lib=1.0.1=py35h2f9c1c0_0
37 | - icu=58.2=h9c2bf20_1
38 | - ipykernel=4.8.2=py35_0
39 | - ipython=5.0.0=py35_0
40 | - ipython_genutils=0.2.0=py35_0
41 | - ipywidgets=7.1.2=py35_0
42 | - jinja2=2.10=py35h480ab6d_0
43 | - jsonschema=2.6.0=py35h4395190_0
44 | - jupyter=1.0.0=py35_4
45 | - jupyter_client=5.2.2=py35_0
46 | - jupyter_console=5.2.0=py35h4044a63_1
47 | - jupyter_core=4.4.0=py35ha89e94b_0
48 | - libgcc-ng=7.2.0=h7cc24e2_2
49 | - libgfortran=3.0.0=1
50 | - libiconv=1.15=h63c8f33_5
51 | - libsodium=1.0.15=hf101ebd_0
52 | - libstdcxx-ng=7.2.0=h7a57d05_2
53 | - libxml2=2.9.4=0
54 | - markupsafe=1.0=py35h4f4fcf6_1
55 | - mistune=0.8.3=py35_0
56 | - mkl=2017.0.3=0
57 | - nbconvert=5.3.1=py35hc5194e3_0
58 | - nbformat=4.4.0=py35h12e6e07_0
59 | - notebook=5.4.0=py35_0
60 | - numpy=1.13.1=py35_0
61 | - pandas=0.20.3=py35_0
62 | - pandoc=1.19.2.1=hea2e7c5_1
63 | - pandocfilters=1.4.2=py35h1565a15_1
64 | - path.py=10.3.1=py35_0
65 | - pexpect=4.2.1=py35_0
66 | - pickleshare=0.7.4=py35_0
67 | - pip=9.0.1=py35_1
68 | - prompt_toolkit=1.0.15=py35_0
69 | - ptyprocess=0.5.2=py35_0
70 | - pygments=2.2.0=py35_0
71 | - python=3.5.0=1
72 | - python-dateutil=2.6.1=py35_0
73 | - pytz=2017.2=py35_0
74 | - pyzmq=16.0.3=py35ha889422_0
75 | - qtconsole=4.3.1=py35h4626a06_0
76 | - readline=6.2=2
77 | - scikit-learn=0.19.0=np113py35_0
78 | - scipy=0.19.1=np113py35_0
79 | - send2trash=1.5.0=py35_0
80 | - setuptools=36.4.0=py35_1
81 | - simplegeneric=0.8.1=py35_1
82 | - sip=4.18=py35_0
83 | - six=1.10.0=py35_0
84 | - sqlite=3.13.0=0
85 | - terminado=0.8.1=py35_1
86 | - testpath=0.3.1=py35had42eaf_0
87 | - tk=8.5.18=0
88 | - tornado=4.5.3=py35_0
89 | - traitlets=4.3.2=py35_0
90 | - wcwidth=0.1.7=py35_0
91 | - webencodings=0.5.1=py35hb6cf162_1
92 | - wheel=0.29.0=py35_0
93 | - widgetsnbextension=3.1.4=py35_0
94 | - xz=5.0.5=1
95 | - zeromq=4.2.2=hbedb6e5_2
96 | - zlib=1.2.11=0
97 | - pip:
98 | - absl-py==0.1.10
99 | - keras==2.1.4
100 | - markdown==2.6.11
101 | - opencv-python==3.4.0.12
102 | - protobuf==3.5.1
103 | - pyyaml==3.12
104 | - tensorflow-gpu==1.5.0
105 | - tensorflow-tensorboard==1.5.1
106 | - werkzeug==0.14.1
107 | prefix: /home/ubuntu/miniconda3/envs/dl_aws
108 |
109 |
--------------------------------------------------------------------------------
/code/net.py:
--------------------------------------------------------------------------------
1 | import tensorflow as tf, os, sys
2 | import data
3 |
4 | def conv_block(layer, fsize, training, name, pool=True):
5 | """
6 | Method to perform basic CNN convolution block pattern
7 |
8 | [ CONV --> BN --> RELU ] x2 --> POOL (optional)
9 |
10 | :params
11 |
12 | (tf.Tensor) layer : input layer
13 | (int) fsize : output filter size
14 | (tf.Tensor) training : boolean value regarding train/valid cohort
15 | (str) name : name of block
16 | (bool) pool : if True, pooling is performed
17 |
18 | :return
19 |
20 | (tf.Tensor) layer : output layer
21 |
22 | """
23 | with tf.variable_scope(name):
24 |
25 | for i in range(1, 3):
26 |
27 | layer = tf.layers.conv2d(layer, filters=fsize, kernel_size=(3, 3), padding='same',
28 | kernel_regularizer=l2_reg(1e-1), name='conv-%i' % i)
29 | layer = tf.layers.batch_normalization(layer, training=training, name='norm-%s' % i)
30 | layer = tf.nn.relu(layer, name='relu-%i' % i)
31 |
32 | if pool:
33 | pool = tf.layers.max_pooling2d(layer, pool_size=(2, 2), strides=(2, 2), name='pool-%i' % i)
34 |
35 | return layer, pool
36 |
37 | def convt_block(layer, concat, fsize, name):
38 | """
39 | Method to perform basic CNN convolutional-transpose block pattern
40 |
41 | CONVT (applied to `layer`) --> CONCAT (with `concat`)
42 |
43 | :params
44 |
45 | (tf.Tensor) layer : input layer
46 | (tf.Tensor) concat : tensor to be concatenated
47 | (str) name : name of block
48 |
49 | :return
50 |
51 | (tf.Tensor) layer : output layer
52 |
53 | """
54 | with tf.variable_scope(name):
55 |
56 | layer = tf.layers.conv2d_transpose(layer, filters=fsize, kernel_size=2, strides=2,
57 | kernel_regularizer=l2_reg(1e-1), name='convt')
58 | layer = tf.concat([layer, concat], axis=-1, name='concat')
59 |
60 | return layer
61 |
62 | def l2_reg(scale):
63 |
64 | return tf.contrib.layers.l2_regularizer(scale)
65 |
66 | def create_classifier(X, training):
67 | """
68 | Method to implement simple classifier
69 |
70 | :params
71 |
72 | (tf.Tensor) X : input tensor
73 | (tf.Tensor) training : boolean value regarding train/valid cohort
74 |
75 | :return
76 |
77 | (tf.Tensor) layer : output layer
78 |
79 | """
80 | block1, pool1 = conv_block(X, 8, training, name='block01')
81 | block2, pool2 = conv_block(pool1, 16, training, name='block02')
82 | block3, pool3 = conv_block(pool2, 32, training, name='block03')
83 | block4, pool4 = conv_block(pool3, 64, training, name='block04')
84 | block5, pool5 = conv_block(pool4, 96, training, name='block05')
85 | block6, pool6 = conv_block(pool5, 128, training, name='block06')
86 |
87 | pool6 = tf.reshape(pool6, shape=[-1, 1, 1, 1152])
88 | pred = tf.layers.conv2d(pool6, 2, (1, 1), name='pred', padding='same')
89 | pred = tf.contrib.layers.flatten(pred)
90 |
91 | return pred
92 |
93 | def loss_sce(y_pred, y_true):
94 | """
95 | Method to implement simple softmax cross-entropy loss
96 |
97 | """
98 | loss = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y_true, logits=y_pred)
99 |
100 | return tf.reduce_mean(loss)
101 |
102 | def error_topk(y_pred, y_true, k=1):
103 | """
104 | Method to calculate top-k error
105 |
106 | """
107 | error = tf.nn.in_top_k(predictions=y_pred, targets=y_true, k=k)
108 |
109 | return tf.reduce_mean(tf.cast(error, dtype=tf.float32))
110 |
111 | def create_unet(X, training):
112 | """
113 | Method to implement U-net
114 |
115 | :params
116 |
117 | (tf.Tensor) X : input tensor
118 | (tf.Tensor) training : boolean value regarding train/valid cohort
119 |
120 | :return
121 |
122 | (tf.Tensor) layer : output layer
123 |
124 | Original Paper: https://arxiv.org/abs/1505.04597
125 |
126 | """
127 | # --- Contracting arm
128 | block1, pool1 = conv_block(X, 8, training, name='block01')
129 | block2, pool2 = conv_block(pool1, 16, training, name='block02')
130 | block3, pool3 = conv_block(pool2, 32, training, name='block03')
131 | block4, pool4 = conv_block(pool3, 64, training, name='block04')
132 | block5, pool5 = conv_block(pool4, 96, training, name='block05', pool=False)
133 |
134 | # --- Expanding arm
135 | block6 = convt_block(block5, block4, 64, name='block06')
136 | block7, _ = conv_block(block6, 64, training, name='block07', pool=False)
137 |
138 | block8 = convt_block(block7, block3, 32, name='block08')
139 | block9, _ = conv_block(block8, 32, training, name='block09', pool=False)
140 |
141 | block10 = convt_block(block9, block2, 16, name='block10')
142 | block11, _ = conv_block(block10, 16, training, name='block11', pool=False)
143 |
144 | block12 = convt_block(block11, block1, 8, name='block12')
145 | block13, _ = conv_block(block12, 8, training, name='block13', pool=False)
146 |
147 | # --- Collapse to number of classes
148 | pred = tf.layers.conv2d(block13, 5, (1, 1), name='final', activation=tf.nn.softmax, padding='same')
149 |
150 | return pred
151 |
152 | def loss_dice(y_pred, y_true):
153 | """
154 | Method to approximate Dice score loss function
155 |
156 | Dice (formal) = 2 x (y_pred UNION y_true)
157 | -------------------------
158 | | y_pred | + | y_true |
159 |
160 | Dice (approx) = 2 x (y_pred * y_true) + d
161 | -------------------------
162 | | y_pred | + | y_true | + d
163 |
164 | where d is small delta == 1e-7 added both to numerator/denominator to
165 | prevent division by zero.
166 |
167 | :params
168 |
169 | (tf.Tensor) y_pred : predictions
170 | (tf.Tensor) y_true : ground-truth
171 |
172 | :return
173 |
174 | (dict) scores : {
175 | 'final': final weighted Dice score,
176 | 0: score for class 1,
177 | 1: score for class 2, ...
178 | }
179 |
180 | """
181 | # --- Loop over the channels
182 | channels = y_pred.shape.as_list()[-1]
183 | losses = {}
184 | for ch in range(channels):
185 | num = 2 * tf.reduce_sum(y_pred[..., ch] * y_true[..., ch])
186 | den = tf.reduce_sum(y_pred[..., ch]) + tf.reduce_sum(y_true[..., ch])
187 | losses[ch] = (num + 1e-7) / (den + 1e-7)
188 |
189 | # --- Calculate weighted dice score loss function
190 | weight = lambda ch : (tf.reduce_sum(y_true[..., ch]) + 1e-7) / (tf.reduce_sum(y_true) + 1e-7)
191 | losses['dice'] = tf.reduce_sum([losses[ch] * weight(ch) for ch in range(channels)])
192 |
193 | return losses
194 |
195 | def init_metrics(losses):
196 | """
197 | Method to initialize a metrics dictionary based on keys in losses
198 |
199 | """
200 | metrics = {'train': {}, 'valid': {}}
201 | for mode, d in metrics.items():
202 | metrics[mode] = dict([(k, 0) for k in losses])
203 |
204 | return metrics
205 |
206 | def update_ema(metrics, metric, mode, iteration):
207 | """
208 | Method to update the metrics dict with exponential moving average of metric.
209 |
210 | :params
211 |
212 | (dict) errors : dictionary with errors
213 | (float) error : current error value
214 | (str) mode : 'train' or 'valid'
215 | (int) iteration : update iteration (to determine EMA vs average)
216 |
217 | """
218 | decay = 0.99 if mode == 'train' else 0.9
219 | d = decay if iteration > 10 else 0.5
220 |
221 | for key, value in metric.items():
222 | metrics[mode][key] = metrics[mode][key] * d + value * (1 - d)
223 |
224 | return metrics
225 |
226 | def print_status(metrics, step, metric_names=[], end='\r'):
227 | """
228 | Method to print iteration and metrics for train/valid
229 |
230 | :params
231 |
232 | (dict) metrics
233 | (int) step
234 | (list) metric_names : list of keys within metrics to print
235 |
236 | """
237 | printf = {'train': '', 'valid': ''}
238 | values = {'train': [], 'valid': []}
239 |
240 | for name in metric_names:
241 | for mode in ['train', 'valid']:
242 | printf[mode] += '- %s : %s ' % (name, '%0.4f')
243 | values[mode].append(metrics[mode][name])
244 |
245 | printf = '%s | TRAIN %s | VALID %s' % ('%07i', printf['train'], printf['valid'])
246 | values = [step] + values['train'] + values['valid']
247 |
248 | print(printf % tuple(values), end=end)
249 | sys.stdout.flush()
250 |
251 | def init_session(sess, output_dir):
252 | """
253 | Method to initialize generic Tensorflow objects
254 |
255 | :params
256 |
257 | (tf.Session) sess
258 | (str) output_dir
259 |
260 | """
261 | writer_train = tf.summary.FileWriter('%s/logs/train' % output_dir, sess.graph)
262 | writer_valid = tf.summary.FileWriter('%s/logs/valid' % output_dir)
263 |
264 | init = tf.global_variables_initializer()
265 | sess.run(init)
266 | saver = tf.train.Saver()
267 |
268 | # --- Restore checkpoints if available
269 | latest_check_point = tf.train.latest_checkpoint('%s/checkpoint' % output_dir)
270 | if latest_check_point is not None:
271 | saver.restore(sess, latest_check_point)
272 | else:
273 | os.makedirs('%s/checkpoint' % output_dir, exist_ok=True)
274 |
275 | return sess, saver, writer_train, writer_valid
276 |
277 | def init_batch(batch_size, one_hot=True, root=None):
278 | """
279 | Method to return batched data
280 |
281 | """
282 | if root is not None:
283 | data.set_root(root)
284 |
285 | def generator_train():
286 | while True:
287 | dat, lbl, msk = data.load(mode='train', return_mask=True)
288 | yield (dat[0], lbl[0, ..., 0], msk[0])
289 |
290 | def generator_valid():
291 | while True:
292 | dat, lbl, msk = data.load(mode='valid', return_mask=True)
293 | yield (dat[0], lbl[0, ..., 0], msk[0])
294 |
295 | batch = {}
296 | for mode, generator in zip(['train', 'valid'], [generator_train, generator_valid]):
297 |
298 | ds = tf.data.Dataset.from_generator(generator,
299 | output_types=(tf.float32, tf.uint8, tf.float32),
300 | output_shapes=([240, 240, 4], [240, 240], [240, 240, 1]))
301 | ds = ds.batch(batch_size)
302 | ds = ds.prefetch(batch_size * 5)
303 | its = ds.make_one_shot_iterator()
304 | it = its.get_next()
305 | y = tf.one_hot(it[1], depth=5) if one_hot else it[1]
306 |
307 | batch[mode] = {'X': it[0], 'y': y, 'mask': it[2]}
308 |
309 | return batch
310 |
--------------------------------------------------------------------------------
/code/unet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/code/unet.png
--------------------------------------------------------------------------------
/code/utils.py:
--------------------------------------------------------------------------------
1 | import matplotlib.pyplot as plt
2 | import numpy as np, cv2
3 |
4 | def imshow(dat, lbl=None, index=0, radius=1, vm=None, title=None, figsize=(7, 7)):
5 | """
6 | Method to display dat with lbl overlay if provided.
7 |
8 | :params
9 |
10 | (int) index : index of channel to display; set to None if RGB image
11 | (int) radius : thickness of outline for overlays
12 |
13 | """
14 | im = np.squeeze(dat)
15 | if im.ndim == 3:
16 | im = im[..., index]
17 |
18 | if im.ndim > 2:
19 | print('Error input dat is not H x W x N in dimensions')
20 | return
21 |
22 | # --- Overlay if lbl also provided
23 | if lbl is not None:
24 |
25 | m = np.squeeze(lbl)
26 | if m.ndim > 2:
27 | print('Error input lbl is not H x W x N in dimension')
28 | return
29 |
30 | masks = []
31 | for ch in range(np.max(m)):
32 | masks.append(perim_2d(m == ch, radius=radius))
33 | im = imoverlay(im, np.stack(masks, axis=2))
34 |
35 | # --- Display image
36 | cmap = None if im.ndim == 3 or im.dtype == 'uint8' else plt.cm.gist_gray
37 | plt.figure(figsize=figsize)
38 |
39 | if vm is not None:
40 | plt.imshow(im, cmap=cmap, vmin=vm[0], vmax=vm[1])
41 | else:
42 | plt.imshow(im, cmap=cmap)
43 |
44 | plt.axis('off')
45 | if title is not None:
46 | plt.title(title)
47 | plt.show()
48 |
49 | def imoverlay(dat, lbl, vm=None):
50 | """
51 | Method to superimpose lbls on 2D image
52 |
53 | :params
54 |
55 | (np.array) dat : 2D image of format H x W or H x W x C
56 |
57 | if C is empty (grayscale), image will be converted to 3-channel grayscale
58 | if C == 1 (grayscale), image will be squeezed then converted to 3-channel grayscale
59 | if C == 3 (rgb), image will not be converted
60 |
61 | (np.array) lbl : 2D lbl(s) of format H x W or H x W x N
62 |
63 | """
64 | # --- Adjust shapes of dat
65 | if dat.ndim == 3 and dat.shape[-1] == 1:
66 | dat = np.squeeze(dat)
67 |
68 | if dat.ndim == 2:
69 | dat = gray2rgb(dat, vm=vm)
70 |
71 | # --- Adjust shapes of lbl
72 | if lbl.ndim == 2:
73 | lbl = np.expand_dims(lbl, axis=2)
74 | lbl = lbl.astype('bool')
75 |
76 | # --- Overlay lbl(s)
77 | if dat.ndim == 3 and lbl.ndim == 3 and dat.shape[2] == 3:
78 |
79 | rgb = [[1, 0, 0], [0, 1, 0], [0, 0, 1], [0, 1, 1], [1, 0, 1], [1, 1, 0]]
80 | overlay = []
81 |
82 | for channel in range(3):
83 | layer = dat[..., channel]
84 | for i in range(lbl.shape[2]):
85 | layer[lbl[..., i]] = rgb[i % 6][channel]
86 | overlay.append(layer)
87 |
88 | return np.stack(overlay, axis=2)
89 |
90 | def gray2rgb(dat, maximum_val=1, percentile=0, vm=None):
91 | """
92 | Method to convert H x W grayscale tensor to H x W x 3 RGB grayscale
93 |
94 | :params
95 |
96 | (np.array) dat : input H x W tensor
97 | (int) maximum_val : maximum value in output
98 | if maximum_val == 1, output is assumed to be float32
99 | if maximum_val == 255, output is assumed to be uint8 (standard 256 x 256 x 256 RGB image)
100 | (int) percentile : lower bound to set to 0
101 |
102 | """
103 | if vm is None:
104 | dat_min, dat_max = np.percentile(dat, percentile), np.percentile(dat, 100 - percentile)
105 | else:
106 | dat_min, dat_max = vm[0], vm[1]
107 |
108 | den = dat_max - dat_min
109 | den = 1 if den == 0 else den
110 | dat = (dat - dat_min) / den
111 | dat[dat > 1] = 1
112 | dat[dat < 0] = 0
113 | dat = dat * maximum_val
114 | dat = np.expand_dims(dat, 2)
115 | dat = np.tile(dat, [1, 1, 3])
116 |
117 | dtype = 'float32' if maximum_val == 1 else 'uint8'
118 |
119 | return dat.astype(dtype)
120 |
121 | def perim_2d(lbl, radius=1):
122 | """
123 | Method to create perimeter of 2D binary mask through morphologic erosion.
124 |
125 | """
126 | kernel = cv2.getStructuringElement(cv2.MORPH_RECT, ksize=(radius * 2 + 1, radius * 2 + 1))
127 | m = lbl > 0
128 |
129 | return m ^ cv2.erode(src=m.astype('uint8'), kernel=kernel, iterations=1)
130 |
131 |
--------------------------------------------------------------------------------
/data/README.md:
--------------------------------------------------------------------------------
1 | # Overview
2 |
3 | The data from this tutorial comes from the 2016 MICCAI Brain Tumor Segmentation Challenge (BRaTS). Using this data, the goal of the challenge is to generate segmentation masks demarcating various brain tumor components from 3D MR imaging volumes.
4 |
5 | ### Data inputs
6 |
7 | Inputs into the CNN consist of matrices of size 1 x 256 x 256 x 4, where the four separate channels represent the four different MRI modalities common in neuroimaging applications:
8 | ```
9 | dat[..., 0] = T2
10 | dat[..., 1] = FLAIR
11 | dat[..., 2] = T1 precontrast
12 | dat[..., 3] = T1 postcontrast
13 | ```
14 |
15 | 
16 |
17 | ### Label outputs
18 |
19 | Output labels from the CNN consist of matrices of size 1 x 256 x 256 x 1. At each voxel location, there are one of 5 possible labels:
20 | ```
21 | 0 = background (no tumor)
22 | 1 = non-enhancing tumor
23 | 2 = edema
24 | 3 = necrosis
25 | 4 = enhancing tumor
26 | ```
27 |
28 | 
29 |
30 | Note that in the above, yellow == non-enhancing tumor, pink == edema, green == necrosis, blue == enhancing tumor.
31 |
32 | ### Data format
33 |
34 | For this tutorial, all the original data and annotations are converted from original raw `*.mha` format into memory-mapped Numpy arrays, `*.npy`. This format can be easily loaded without external dependencies (only Numpy library required), and by memory-mapping the data in slice-contiguous format, random individual slices of data can be loaded efficiently into memory.
35 |
36 | The full dataset (all ~200 patients) is preloaded in the AWS EC2 instance (see https://github.com/peterchang77/dl_tutorial for more information). A small subset of 5 patients is provided here for demonstration purposes.
37 |
38 | ### Data I/O
39 |
40 | The data.py module in the `dl_tutorial/code` directory contains a method that abstracts a pipeline for loading random slices of preprocessed data in either training or validation cohorts.
41 |
42 | ```
43 | import data
44 |
45 | dat, lbl = data.load(mode='train', n=16)
46 |
47 | ```
48 |
--------------------------------------------------------------------------------
/data/brats_2013_pat0001_1/dat.npy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/data/brats_2013_pat0001_1/dat.npy
--------------------------------------------------------------------------------
/data/brats_2013_pat0002_1/dat.npy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/data/brats_2013_pat0002_1/dat.npy
--------------------------------------------------------------------------------
/data/brats_2013_pat0004_1/dat.npy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/data/brats_2013_pat0004_1/dat.npy
--------------------------------------------------------------------------------
/data/brats_2013_pat0005_0001/dat.npy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/data/brats_2013_pat0005_0001/dat.npy
--------------------------------------------------------------------------------
/data/brats_2013_pat0006_0001/dat.npy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/data/brats_2013_pat0006_0001/dat.npy
--------------------------------------------------------------------------------
/data/data.pickle:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/data/data.pickle
--------------------------------------------------------------------------------
/data/data.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/data/data.png
--------------------------------------------------------------------------------
/data/label.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/data/label.png
--------------------------------------------------------------------------------
/prerequisites/README.md:
--------------------------------------------------------------------------------
1 | # Prerequisites and Resources
2 |
3 | Below you will find a list topics and links for achieve basic proficiency prior to starting the deep learning tutorial.
4 |
5 | ### Python
6 |
7 | Learning Python The Hard Way: https://learnpythonthehardway.org/book/
8 | A Byte of Python: https://python.swaroopch.com/
9 | TutsPlus: https://code.tutsplus.com/articles/the-best-way-to-learn-python--net-26288
10 |
11 | ### Linear algebra
12 |
13 | Deep Learning E-Book: http://www.deeplearningbook.org/contents/linear_algebra.html
14 |
15 | ### General deep learning
16 |
17 | Stanford CS231n: http://cs231n.stanford.edu
18 | Neural Networks and Deep Learning: http://neuralnetworksanddeeplearning.com
19 |
20 |
--------------------------------------------------------------------------------
/pretrained/classifier/checkpoint:
--------------------------------------------------------------------------------
1 | model_checkpoint_path: "model.ckpy"
2 | all_model_checkpoint_paths: "model.ckpy"
3 |
--------------------------------------------------------------------------------
/pretrained/classifier/model.ckpy.data-00000-of-00001:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/pretrained/classifier/model.ckpy.data-00000-of-00001
--------------------------------------------------------------------------------
/pretrained/classifier/model.ckpy.index:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/pretrained/classifier/model.ckpy.index
--------------------------------------------------------------------------------
/pretrained/classifier/model.ckpy.meta:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/pretrained/classifier/model.ckpy.meta
--------------------------------------------------------------------------------
/pretrained/unet/checkpoint:
--------------------------------------------------------------------------------
1 | model_checkpoint_path: "model.ckpy"
2 | all_model_checkpoint_paths: "model.ckpy"
3 |
--------------------------------------------------------------------------------
/pretrained/unet/model.ckpy.data-00000-of-00001:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/pretrained/unet/model.ckpy.data-00000-of-00001
--------------------------------------------------------------------------------
/pretrained/unet/model.ckpy.index:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/pretrained/unet/model.ckpy.index
--------------------------------------------------------------------------------
/pretrained/unet/model.ckpy.meta:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/pretrained/unet/model.ckpy.meta
--------------------------------------------------------------------------------
/print_address.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | if os.path.exists('./public-ipv4') and os.path.exists('./token'):
4 |
5 | ip = open('./public-ipv4').read()
6 | token = open('./token').read()
7 | token = token.split(':')[3]
8 |
9 | print('\n Based on auto-detection of your EC2 public IP, use the following URL:\n\n http://%s:%s' % (ip, token))
10 |
11 | print('\n To shut down Jupyter notebook use the command: pkill jupyter')
12 |
--------------------------------------------------------------------------------
/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | wget http://169.254.169.254/latest/meta-data/public-ipv4 -O public-ipv4
4 | source activate dl_aws
5 | jupyter notebook --no-browser &
6 | sleep 3
7 | jupyter notebook list > token
8 | python ./print_address.py
9 |
--------------------------------------------------------------------------------
/screenshots/aws-ec2new00.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-ec2new00.png
--------------------------------------------------------------------------------
/screenshots/aws-ec2new01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-ec2new01.png
--------------------------------------------------------------------------------
/screenshots/aws-ec2new02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-ec2new02.png
--------------------------------------------------------------------------------
/screenshots/aws-ec2new03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-ec2new03.png
--------------------------------------------------------------------------------
/screenshots/aws-ec2new04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-ec2new04.png
--------------------------------------------------------------------------------
/screenshots/aws-ec2new05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-ec2new05.png
--------------------------------------------------------------------------------
/screenshots/aws-ec2new06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-ec2new06.png
--------------------------------------------------------------------------------
/screenshots/aws-ec2new07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-ec2new07.png
--------------------------------------------------------------------------------
/screenshots/aws-ec2new08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-ec2new08.png
--------------------------------------------------------------------------------
/screenshots/aws-jupyter00.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-jupyter00.png
--------------------------------------------------------------------------------
/screenshots/aws-jupyter01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-jupyter01.png
--------------------------------------------------------------------------------
/screenshots/aws-jupyter02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-jupyter02.png
--------------------------------------------------------------------------------
/screenshots/aws-jupyter03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-jupyter03.png
--------------------------------------------------------------------------------
/screenshots/aws-jupyter04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-jupyter04.png
--------------------------------------------------------------------------------
/screenshots/aws-jupyter05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-jupyter05.png
--------------------------------------------------------------------------------
/screenshots/aws-limit00.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-limit00.png
--------------------------------------------------------------------------------
/screenshots/aws-limit01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-limit01.png
--------------------------------------------------------------------------------
/screenshots/aws-login00.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-login00.png
--------------------------------------------------------------------------------
/screenshots/aws-login01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-login01.png
--------------------------------------------------------------------------------
/screenshots/aws-login02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-login02.png
--------------------------------------------------------------------------------
/screenshots/aws-login03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-login03.png
--------------------------------------------------------------------------------
/screenshots/aws-signup00.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-signup00.png
--------------------------------------------------------------------------------
/screenshots/aws-ssh00.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-ssh00.png
--------------------------------------------------------------------------------
/screenshots/aws-ssh01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-ssh01.png
--------------------------------------------------------------------------------
/screenshots/aws-start00.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peterchang77/dl_tutorial/bb3ffb1e5466cb3e5e9b9456b05a0280cddad62d/screenshots/aws-start00.png
--------------------------------------------------------------------------------
/start_jupyter.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # ==========================================================
4 | # TO RUN SCRIPT:
5 | # >> source ./start_jupyter.sh
6 | # ==========================================================
7 |
8 | cd ~/dl_tutorial
9 | git pull origin master
10 | bash ./run.sh
11 |
--------------------------------------------------------------------------------