├── .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 | ![AWS Sign-up Screen](./screenshots/aws-signup00.png) 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 | ![AWS Login Screen](./screenshots/aws-login00.png) 46 | 47 | On the following page, enter the password of your AWS account and click `Sign In`: 48 | 49 | ![AWS Password Screen](./screenshots/aws-login01.png) 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 | ![AWS Services Toolbar](./screenshots/aws-login02.png) 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 | ![AWS Console](./screenshots/aws-login03.png) 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 | ![AWS Region Selection](./screenshots/aws-ec2new00.png) 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 | ![AWS Limits](./screenshots/aws-limit00.png) 72 | 73 | Complete the following request with the settings shown below. Ensure that the correct region and instance type are selected: 74 | 75 | ![AWS Request](./screenshots/aws-limit01.png) 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 | ![AWS Instace Selection](./screenshots/aws-ec2new01.png) 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 | ![AWS Instance Type Selection](./screenshots/aws-ec2new02.png) 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 | ![AWS Instance Type Selection](./screenshots/aws-ec2new03.png) 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 | ![AWS Storage Selection](./screenshots/aws-ec2new04.png) 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 | ![AWS Tags Selection](./screenshots/aws-ec2new05.png) 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 | ![AWS Security Selection](./screenshots/aws-ec2new06.png) 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 | ![AWS Launch](./screenshots/aws-ec2new07.png) 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 | ![AWS Launch](./screenshots/aws-ec2new08.png) 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 | ![AWS Start](./screenshots/aws-start00.png) 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 | ![AWS Connect](./screenshots/aws-ssh00.png) 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 | ![AWS Remote Session](./screenshots/aws-ssh01.png) 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 | ![AWS Jupyter server](./screenshots/aws-jupyter01.png) 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 | ![AWS Jupyter server](./screenshots/aws-jupyter02.png) 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 | ![AWS Jupyter launch page](./screenshots/aws-jupyter03.png) 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 | ![AWS Jupyter code page](./screenshots/aws-jupyter04.png) 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 | ![AWS Jupyter hello_tensorflow.ipynb](./screenshots/aws-jupyter05.png) 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 | ![AWS IP Address](./screenshots/aws-jupyter00.png) 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 | ![BRATS Input](./data.png) 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 | ![BRATS Input](./label.png) 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 | --------------------------------------------------------------------------------