Developing an App to interpret Chest X-rays to support the diagnosis of respiratory pathology with Artificial Intelligence
Andrew Elkins Felipe F. Freitas Veronica Sanz
 
Secure Client and Server Geolocation Over the Internet
    AbdelRahman Abdou
Carleton University, Ottawa, Canada

ETH Zürich, Switzerland

abdoua@inf.ethz.ch
Paul C. van Oorschot
School of Computer Science

Carleton University, Ottawa, Canada

paulv@scs.carleton.ca

    December 30, 2023
===================================================================================================================================================================================================================







§ INTRODUCTION



Medical images, including results from X-rays, are an integral part of medical diagnosis. Their interpretation requires an experienced radiologist, a human whose skills are scarce and who can commit mistakes when tired. But at face value, the X-ray diagnostic uses simple features from the image, such as black and white intensity, contours and shapes, all properties which  an Artificial Intelligence (AI) can handle well. 

And indeed, in recent years, the idea of using AI as a means of assisting diagnostic (classification) has taken hold, thanks to the increase of computational speed (e.g. with GPUs), the availability of large and well-documented datasets, and the development of deep learning techniques, in particular Convolutional Neural Networks (CNN). The results of a number of studies training CNNs to diagnose diseases using 2D and 3D images are rather impressive, reaching near 100% accuracy in such diverse pathologies as lung nodules or Alzheimers, see Ref. <cit.> for a recent overview. Moreover, in some cases the performance of the AI exceeds that of radiologists, see e.g. the study of CheXNet <cit.>.  


 One of the main problems faced by people in developing countries is access to timely medical diagnosis. Lack of investment in health care infrastructure, geographical isolation and shortage of trained specialists are common obstacles to providing adequate health care in many areas of the world. 
 
 Yet the use of AI is still limited for various reasons, technical and sociological. For example, images are only one aspect of the patient history which can be supplemented with clinical signs, e.g. whether the patient has fever. Currently, the availability of databases with additional clinical information is scarce, but databases with better labelling could substantially improve the AI training in the near future. A more important issue is related to the natural variability of a large training dataset, and the need to remove modelling when interpreting the image. This has led to the development of new techniques beyond CNNs (supervised learning) to incorporate a more bottom-up approach: unsupervised machine learning. Nevertheless, all these studies rely on a large number of neuron layers, typically dozens, and good quality images. The trained neural network  can then be used in a powerful computer station to help diagnosis.
 
 Given our interest in portability, we need to develop a different strategy. We cannot deploy machine learning algorithms which depend on too many layers, and we cannot rely on the acquisition of precise images. Instead, we have to strike a balance between the desired outcomes (portability and reliability) and the real-life situations a clinician may encounter on the field.
 
 This note presents our results to help the early diagnosis of a number of life-threatening conditions in remote areas with the use of new methodologies (based on Machine Learning) and their capability to be deployed into mobile devices.  We use the latest developments in AI environments which are portable and fast, in particular  <cit.>, to develop an Artificial Neural Network  to be deployed as a smartphone app, or in one of Google's AI development kits, or in Rasberry PI, as a tool to assist physicians in their diagnostic. A beta version of the app can be tested in the heroku environment <cit.> and all the code developed during this project can be obtained in GitHub <cit.>.







§ ANALYSIS SET-UP






 §.§ The dataset and analysis framework

We use the ChestX-ray14 dataset <cit.> which contains 112,120 frontal-view X-ray images of 30,805 unique patients. The images are frontal view of chest X-rays PNG images in 1024×1024 resolution. Meta-data for all images contains: Image Index, Finding Labels, Patient ID, Patient Age, Patient Gender, View Position, Original Image Size and Original Image Pixel Spacing. Each image contains the annotations up to 14 different thoracic pathology labels. The labels were assigned using automatic extraction methods on radiology reports.The database from NIH is available online through the link: <https://nihcc.app.box.com/v/ChestXray-NIHCC/folder/36938765345>. 

The 14 common thorax disease categories are: Atelectasis,  Cardiomegaly, Effusion,  Infiltration, Mass,  Nodule,  Pneumonia, Pneumothorax,  Consolidation, Oedema,  Emphysema,  Fibrosis,  Pleural Thickening, and Hernia.


Regarding the analysis of the dataset, in this project we use AI techniques in the framework of  <cit.>,  a library which simplifies accurate and fast training of NNs using modern best practices. It includes out-of-the-box support for vision, text, tabular, and collaborative filtering models. Another main advantage of is the simplification of the data processing made by the  API, and the implementation of the fit one cycle method <cit.> and mixed precision training <cit.>. The  fit one cycle method allows us to drastically reduce the training time by allowing changes during the training in the learning rates and momentum (hyper-parameters). This method can reduce the time to train big NN models, but also stabilise it by helping the optimisation algorithm to prevent falling into saddle points. The  mixed precision training uses half-precision floating point numbers, without losing model accuracy or having  to modify hyper-parameters. This nearly halves memory requirements and, on recent GPUs, speeds up arithmetic calculations.




 §.§ Selecting and processing the data



Since we are interested in distinguishing different types of diseases, we will focus on the dataset with labels of at least one disease. In the dataset there are 51759 images with at least one disease (non- healthy). On this dataset, we will perform two types of analyses: 1.)  one vs all and 2.) multi-label classification. 

In the first type of analysis, we will train an algorithm to identify patients with a specific disease, which we choose to be Pneumothorax for illustrative purposes, versus any other type of disease in the sample.  
In the second type of analysis, we will tackle a more difficult but realistic situation: often diseases do not occur in isolation, e.g.  Atelectasis tends to appear in conjunction with Cardiomegaly or Consolidation, or both. This more complex situation is called co-occurrence, which in terms of the data analysis corresponds to a problem of multi-label classification.

Below we describe how we select and process the data in these two situations:





  §.§.§  One vs All problem [i.e. Pneumothorax vs All]




We separate the dataset in two categories, one with the label Pneumothorax the other with all the images with no Pneumothorax label (Atelectasis, Pneumonia, ...).  Specifically, we first read from the metadata information available in the file  the images names and findings, selecting the entries with the Pneumothorax  label to one folder and all the others to a different folder. After this selection one ends up with the following set of samples:


  * Pneumothorax: 5302 images, resolution 1024x1024 8-bit gray scale

  * No-Pneumothorax: 46457 images, resolution 1024x1024 8-bit gray scale


Next, we prepare the data for testing and validation using  the  API, which automatically loads the data from their respective folders, assign their labels according to the name of the folders and split the full data into training (80%) and validation (20%) datasets. The  API ensure the use of correct labels, the correct train/validation (or test) split, the correct normalisation of the images and all the augmentations transformation we later apply [Note that the pixel values can vary from 0 to 255 for each of the three channels in a RGB image, or one single channel for Gray-Scale, and we re-scale this range to -1 to 1 to input the values in a CNN.].
After this selection and transformations we end up with:


  * Train size (80%): 41408 items, items dimension: 224x224 3 channels, transformation applied.

  * Validation size (20%): 10351 items, items dimension: 224x224 3 channels, no transformations.

  * Batches which are set to 64 items per batch, so they can fit on a regular GPU memory.



We select the following transformations to be applied to the train dataset:


  * random rotation (clockwise or counter-clockwise) in an angle range between 0 and 30 degrees.

  * 50% chance of an image to be zoomed by a 1.3 scale.

  * 100% chance of a image be selected to randomly modify brightness and contrast within a range between 0 and 0.4.

  * normalize the items according to the stats of each batch, i.e. take the mean and the standard deviation of each channel and normalize the image using them[Note that in pytorch when we normalize images we have to provide the mean and the std for each channel, in tensorflow we can use  sklearn.preprocessing.StandardScaler to do the same.].


Note that the images fed into the CNN are set to be 224× 224 with three channels due to hardware limitations and the size of the batch (64 images) to fit into the GPU memory, as well as coding implementation – the CNN architecture we are using (DenseNet) was originally designed to work with 3 channels (RGB), so the input layer is designed to take inputs (tensors) with the dimension (batch size,C=3,x size, y size) where C is the number of channels from the image. 



In Fig. <ref> we show the effects of the transformations we apply in the train dataset items. They are designed to emulate some of the issues which different situations could lead to. For example, brightness variance can occur when the technician exposes the patient to a more energetic X-ray beam (higher keV X-rays) or long exposure time, which greatly increases the contrast of the image, but also increases the radiation dose received by the patient; or in the case of film X-ray, the exposure time to chemicals can drastically reduce contrast of the image, leading to erroneous feature differentiability.
   






  §.§.§ Multi-label classification




For the multi-label case we prepare a new csv file which contains the name of the image (in our input dataset the names are formatted as ) and the label of the diseases found in the respective images. This file will be used by the  API to correctly label the images. The file  contains all the metadata with the name of each image together with the findings and other information that can be used for future applications. 

To create the multi-class label file we use a python script with two main functions: one to extract the information from the  metadata and another function to write the new csv file with the columns contain the path to the images, their respective findings and a new column with a one-hot encode[In digital circuits and machine learning, one-hot is a group of bits among which the legal combinations of values are only those with a single high (1) bit and all the others low (0).] to represent the presence or not of each disease (class) in a given image. All the scripts will be available in GitHub so anyone can reproduce our findings. 



The API contains a very convenient method to prepare and load the images into our CNN model called , which and contains a series of useful functions which one can use for AI purposes. For example, for the multi-label classification one can use the , which automatically identifies  the images and  multi-class labels we are loading into the CNN, e.g.



This set of commands will automatically load the images in batches of 64, re-size the images form 1024×1024 to 224×224, load the labels from  and normalise according to the pytorch standard, and split the dataset into 80% test images and 20% for validation.

A very important  aspect in our analysis is how often one disease appears together with others, i.e. the rate of co-occurrences. We can check the co-occurrences of a disease using scikit-learning feature extraction library, and in Fig. <ref> we show the matrix of number of co-occurrences in the dataset. This information is key to introduce the proper weights used in the loss function and lead to the positive identification of a given class. These values can help us to better calibrate the weights for each class in order to avoid situations like a  model who can only predict the class where one has the higher number of targets.






 §.§ The model architecture and training


Our architecture will be based on the DenseNet-121 model with the last layers (header) modified for our purposes. Specifically, our convolutional neural network (CNN) model consists on the following parts:


  * The body of the model is the same as the denseNet-121, described in table <ref>  (third column) (DenseNet-121).

  * For the classification layer, we remove the last layer from denseNet-121 and replace it with the following layers:


  * AdaptiveConcatPool2d layer

  * Flatten layer

  * A block with [nn.BatchNorm1d, nn.Dropout, nn.Linear, nn.ReLU] layers.








In our loss function we use the weights from each class calculated according to the scikit-learn class weight method, defined as:

    n_samples/(n_classes * [n_1,n_2,...])

Where n_samples is the total number of images in the training sample, n_classes is the number of classes (2 in the case of One vs All), and n_1,n_2,... are the number of images for each class. This function from sklearn returns the weight for each class in our training sample, information we pass onto the weights in the loss function, which mitigates the problem of having more images from one class than another class, i.e. imbalanced classes datasets.

The network is trained end-to-end using Adam as an optimizer <cit.> with standard parameters (β_1 = 0.9 and β_1 = 0.999). We trained the model using the fit one cycle method <cit.>, which consist in the following steps: initialises the training with a given learning rate and momentum; in the middle of the training phase the learning rate is increased up to a given maximum value while the momentum is reduced; and close to the end of the training the learning rate is reduced again and the momentum increased, as shown in figure <ref>. This method gives more stable results by avoiding the optimization process to fall into saddle points and requires less epochs to fully train our model.






Another useful parameter for training our CNN is the weight decays[Do not confuse this term with class weights.  The class weights are use to balance the number of images of each class.]. After each epoch, the weights of the NN are multiplied by a smaller factor between 0 and 1. This is one of the various forms of regularisation of the NN training, together with batch size and dropout<cit.>. One can choose a weight decay which allows us to use a bigger initial value for the learning rate and thus reduce the time of training during the fit one cycle.


For the One vs All case we choose the Cross Entropy Loss with weights defined for each class to take into account the high imbalance between the Pneumothorax case vs all the others. Specifically, to apply the weights into our loss function we do as follows:

where the weights are calculated by the equation <ref>, the function  just moves the numbers calculated for the weights to the GPU memory where our CNN is loaded. The weights are passed to the loss function by the argument weight= from the cross entropy loss class.




The Cross Entropy Loss function, in the case of the class weight argument being specified, can be described as:

    loss(x, class) = weight[class] (-x[class] + log(∑_j exp(x[j])))

with x as the image input for the respective class.

Another important aspect for the Cross Entropy Loss is the reduction method, which specifies the reduction to apply to the output. The pyTorch framework gives three options: none,mean and sum, and we used mean in our analysis. For the multi-label classification task we choose the Binary Cros Entropy loss with  . This loss combines a Sigmoid layer and the  in one single layer. This loss is more stable numerically than a plain Sigmoid layer followed by a . By combining the operations into one layer, one takes advantage of the log-sum-exp trick <cit.> for numerical stability.

The  for the multi-label case with class weights is described by:

    ℓ_c(x, y) = L_c = {l_1,c,…,l_N,c}^⊤,
       l_n,c = - w_n,c[ p_c y_n,c·logσ(x_n,c)
            + (1 - y_n,c) ·log (1 - σ(x_n,c)) ]  ,

where c is the class number (c > 1 for multi-label binary classification, c = 1 for single-label binary classification), n is the number of the sample in the batch and p_c is the weight of the positive answer for the class c. Note that p_c > 1 increases the recall, whereas the choices with p_c < 1 increase the precision. Also note that we used the default option mean as reduction method to .


Before training, one has  to define the range of the learning rate to be used in the fit one cycle method. To  compute the best range for the learning rate we used the function , which will perform a test run, starting with a very small learning rate and increasing it after each mini-batch until the loss function starts exploding. Once the loss starts diverging, the  will stop the range test run. In figure <ref> we show the loss values versus the learning rate for each weight decay choice (WD=0,0.1,0.001, 1e^-5). The best initial values for learning rates are the ones giving the steeper gradient towards the minimum loss value <cit.>. In the case of figure <ref> this value is 1.32e^-2 for the left figure and 1.02e^-2 for the right one.

The learning rate change can be described as follows:

    n = number of iterations
        = maximum learning rate
        = lower learning rate (we start the range test from this value)
        = *q^n
       q = (/)^1/n


Note that the learning rate after the i-th mini-batch is given by:

    lr_i= *(/)^i/n









We are using the transfer learning methodology to not just training our CNN faster, but also to increase the accuracy. To execute this task we trained the CNN in two phases. During the first phase, the model is trained from end-to-end, i.e. all the layers are trained, for 30 epochs with a initial learning rate of 1.32e-02 to a maximum 1e-1. This initial value is chosen due to the loss value for this learning rate exhibit the steeper gradient value towards the direction of the minimum, as we can see in figure <ref>. After the 30 epochs, the training is stopped and the weights are saved, leading to a second train phase which uses the weights saved from the previous training, freeze all the layers before the last classification layer to void retraining all the NN, and train only the last classification layers with a different range for the learning rate. This process described above is what we know as transfer learning, and  we evaluate the impact of this methodology on the accuracy of our model between the two phases.











§ RESULTS





In a simple binary classification problem (like in our benchmark Pneumothorax vs All), one can define some measures of how well our algorithm is performing: precision, accuracy and recall. All these are computed by evaluating how often in a sample the results lead to a true-positive (TP), true-negative (TN), false-positive (FP) and false-negative (FP) diagnoses. Based on these numbers one can define three measures of goodness of diagnosis:
  
 Accuracy = TP+TN/TP+FP+FN+TN, the ratio of correct observations versus all observations, e.g. accuracy would tell us how likely is to correctly identify pneumothorax pathologies in sick patients.
  
Precision = TP/TP+FP, the ratio of correct predicted positives versus all the classified as positive, e.g. precision would tell us how many patients diagnosed with pneumothorax do actually have pneumothorax.

Recall (or sensitivity) = TP/TP+FN, the ratio of correctly predicted positive versus all the elements with the actual disease, e.g. recall would tell us the probability for correctly diagnosing pneumothorax among all the patients suffering from pneumothorax.

Obviously all these measures are important, and depending on the nature of the disease and focus of the practitioner the algorithm could be trained to improve any of these three, usually at the cost of the other two. A typical average measure of goodness which works well in imbalanced datasets is the F1 score, defined in its simplest form as an average between accuracy and recall 
 

 F1 score= 2 * (precision * recall) / (precision + recall) 
 

 




 §.§ Results for One vs All (Pneumothorax)





We can compute the F1 scores and Precision-Recall of our CNN using our validation dataset. In scikit-learn, F1 scores can be evaluated using different average methods[<https://scikit-learn.org/stable/modules/generated/sklearn.metrics.f1_score.html>], namely:


  binary: Only reports results for the class specified by positive label, i.e. Pneumothorax detected. This method is only applicable in the binary problem (Pneumothorax vs All) but not in the multi-class problem. 

  micro: This method counts the total true positives, false negatives and false positives, to compute a more global metric.

  macro: In this case, the function calculates the metrics for each label and find their unweighted mean, i.e. it does not take label imbalance into account.

  weighted: In this case, the function finds metrics for each label, and their average weighted by the number of true instances for each label. This is an improvement from 'macro' to account for label imbalance.

  samples: In this case, the function calculates metrics for each instance, and find their average. This is only meaningful for multi-label cases. 


Since we are dealing with high imbalanced classes we will be  using the weights for our samples from the validation dataset:

    W_validation = [All others: 0.5566, Pneumothorax:4.902]

with these weights we can build an array with the same size as the ground labels and evaluate all the metrics  taking into account the class imbalance. The results are as follows 


  * F1 score (macro): 0.5822911828419564

  * F1 score (micro): 0.6762802817903739

  * F1 score (weighted): 0.7132615220206845 

  * F1 score (binary): 0.384149629028795

  * F1 score (All others, Pneumothorax): [0.780433 0.38415]

and show the range of different F1 scores one can obtain, even in the binary classification problem.





There are two common representations of the goodness of the algorithm. One is the  precision-recall curve, shown in Fig. <ref>. A high area under the curve represents both high recall and high precision and is an indication of good performance. 


 Another representation is the so-called ROC curve (Receiving Operating Characteristic) which shows the shape of TP as a function of FP. One often says that the algorithm learns when the curve is steeper than a straight line, namely the algorithm is doing better than a 50% chance of identifying the right disease (better than a random pick). In Fig. <ref>, the orange line corresponds to the ROC curve of our algorithm, which is clearly performing better than chance. A related and more global measure of goodness based on the ROC plot is the AUC (Area Under the Curve), and in this figure AUC is 70% for detecting Pneumothorax. 
 




  §.§.§ Layers heatmaps


To visually understand what regions in the image our CNN is picking up on, we look at the area of the image which is  more active when we show an image from a given class. To do this, we built a Grad-CAM following Ref <cit.>. 




The regions displayed in yellow to red colours highlight the spatial location of the features which activate more intensely the last convolutional layer before the classification. As one can see, in the Pneumothorax image the CNN is identifying important features located in the left lung, top of diaphragm and a region below the right lung. Meanwhile, for the non-Pneumthorax image the CNN identify as hot regions on the top of diaphragm, apex of the heart and a hot spot on the left side outside of the body. These images provide valuable information about what are the most important features for our CNN and how to tune the parameters of the model to improve the training phase. One conclusion we can extract from these images is that, although overall we are getting good classification scores, the CNN is struggling to localise good features to better classify the images. We plan to use this method to   improve on the training, by including more random transformation like flip horizontal and vertical, random crop and a different pad method.



 §.§ Results for multi-label


We move onto a much more complex case, where instead of a binary classification problem Pneumothorax vs no-Pneumothorax, we tackle the classification of 14 diseases, including the additional difficulty of co-occurrence of various diseases in the same patient. 

The first thing we need to do is to define new measures of goodness for our method. Precision-Recall or ROC curves like Fig. <ref> are designed to understand a binary classification problem. 

We need to generalise these two notions to a multi-classification case. For example, one ROC curve can be drawn per label, but one can also draw a ROC curve by considering each element of the label indicator matrix as a binary prediction (micro-averaging).
Another evaluation measure for multi-label classification is macro-averaging, which gives equal weight to the classification of each label. 

The 'micro-average', purple dashed curve Fig. <ref>, is calculated as each element of the label indicator matrix, in our case a matrix with the dimensions (number of validations samples, number of classes), as a binary prediction. For the 'macro-average' we first need to aggregate all elements from the false positive rate (fpr) of each class and casting them into a list which we call . From this list we can interpolate each aggregated fpr and the ROC curves of each class we have calculated. By averaging the interpolated points computed previously over the number of classes (14 for our data), we obtain the mean of the true positive rate for all classes (blue dashed curve Fig. <ref>). 




We can also compute the ROC and AUC for each class using the predictions and the truth information for each class. The results are shown in Fig. <ref>, where we display all ROC and AUC for the 14 classes including the micro-average and macro-average.

As discussed in the previous section (Pneumothorax vs All), the Precision-Recall plot is a useful measure of prediction success when the classes are very imbalanced. In Figures <ref> and <ref>, we show the precision-recall curves for the average and for each class, respectively.   

The average precision (AP) summarizes the precision-recall plots as the weighted mean of precisions achieved at each threshold, with the increase in recall from the previous threshold used as the weight:

    AP = ∑_n(R_n - R_n-1)P_n

wheree R_n and P_n are the precision and recall at the n-th threshold.

Finally, in Tables <ref> and <ref> we quote the values of the AUC score and the average precision for each class. 














  §.§.§ Layers activation maps and Guided-Grad-CAM



Here we perform a similar study as in the Pneumothorax vs All case by identifying the regions in the image which activates neurons in the CNN. All the images used in to generate the activations and Grad-CAM maps are from the validation data set, namely these are images the CNN never saw  before. In the next figures (<ref>-<ref>),  we show examples for different pathologies: the input figure, the Grad-CAM heatmap, and the PR curve related to the specific class.
































 §.§ Weighted vs Unweighted (ROC vs PR) for multi-label classification
 







We noticed that for multi-label classification CheXnet use unweighted binary cross entropy losses. This might not be problematic since in their paper the authors compare the AUC from the ROC metrics with the state of the art models for the ChestX-ray14 dataset. ROC curves have an attractive property: they are insensitive to changes in class distribution. If the proportion of positive and negative classes changes in a test set, the ROC curves will not change. To compare with our results we first implemented our loss function without considering the weights for each class. The results are displayed in the figures <ref>, <ref> and <ref> as well in the tables <ref>. However, due to the class imbalance the PR curves tell us a very different history from the ROC curves.


Indeed, by comparing Figs. <ref> , we see that while the ROC curve for unweighted loss seems to indicate a very good classification, one should consider that reality is different:  we are dealing with a very skewed class distribution, e.g. some labels have samples with order 10^4 inputs (Atelactasis) while others only a few hundred samples (Hernia) . In this situation, the ROC does not tell the full history of our classification model. We have to also consider cases where we have few samples more carefully.  To use AI techniques in the real world, one should consider how to design and evaluate the performance of such algorithms. If we are dealing with a disease with a small number of examples but still focus on an algorithm which can lead to a precise diagnostic (i.e. high precision) most of the time (i.e. high recall), then we should 1.) gather more data, which has an intrinsic cost,  and/or 2.)  consider the use of weight class in the loss function, which provides a more realistic measure of accuracy.









§ SUMMARY AND OUTLOOK



In this note we present our study of the use of Machine Learning techniques to identify diseases using X-ray information. We have used the new framework of , and imported the resulting model to an app which can be tested by any user. In the app, the user can upload an image and obtain as an output a certain likelihood for pertaining to one of the 14 labelled diseases. This classification could assist diagnosis by medical providers and broaden access to medical services to remote areas.

We have studied two diagnosis situations: first we studied the simpler problem of identifying one disease (pneumothorax) versus any other disease, and then tackled the much more complex case of multiple classification, where we aim to identify individual diseases and the co-occurrence of diseases in the same patient. To emulate realistic situations, we have transformed the high-quality images into lower resolution and applied transformations to them: rotated, cropped and changed the contrast of the images. On those images, we have trained a CNN and applied a number of numerical techniques to speed up computation time, achieving a good accuracy of diagnosis.

We have explored the use of grad-CAM to improve training. We have also studied the different types of measures of how well the algorithm perform. We found that it is particularly important to account for imbalances in both the ROC and Precision-Recall by weighting the samples. For diseases with low numbers of training samples, the issue of weighting may be crucial to get a realistic measure of performance. 

We consider this project as setting the first steps towards a reliable app to help in diagnosing  diseases using images and other sources of information, such as electrocardiogram data. We have provided open source code <cit.> for others to use and improve  our procedure as well as a beta version of the app for testing <cit.> and step-by-step instructions on how to set up the app. We hope this tool can assist clinicians or other health providers in areas where good quality equipment or relevant skills are missing. We welcome any questions and suggestions which can be posted in the GitHub page. 
 
There are a number of improvements in this analysis which one could tackle, including refining the CNN training to improve the diagnosis in the less performing diseases where there is co-occurrence of several diseases, including the no-disease vs disease case, or enlarging the training set with more images from other databases or provided by users, and finally to incorporate more information besides X-ray information.






§ COMPARISON WITH CHEXNET




The state-of-the-art in terms of use of the X-ray database <cit.> is CheXNet <cit.>.  In this work, the authors use the same denseNet-121 basic architecture we employed, but in their model the classification layer had one neuron with softmax activation (in our case we have two neurons, one for each class, with logSoftMax activation) and weighted binary cross-entropy loss (in our case we can use a weighted cross-entropy loss). Moreover, we have performed further transformations to the images in the dataset, such as changing the contrast between the dark and bright areas, to emulate more closely difficult conditions a physician could find in a remote location and low-quality equipment. 
 
In the CheXNet they compared with the diagnoses of four radiologists, who studied  a test set with 420 images and labelled them according to the 14 diseases. This was used to evaluate a radiologist F1 score for the pneumonia detection task and compare to the F1 score obtained by the CheXNet. We had no such benchmarking, and our F1 scores are based on the validation dataset.

Finally, in CheXNet scores were based on unweighted goodness measures which, as we discussed in Sec. <ref>, may not give a realistic view of diseases with small datasets. 




§ HEROKU SETUP



In this appendix we describe the setup of our online tester for the app. In order to setup the online server we used docker and heroku. After we have trained our model, we export it using the function , which exports both the model and weights. The model is saved in a pkl (pickle) format and the weights are saved in pth (pytorch) format, which we store in a folder to upload to the heroku webserver.



  
Docker:

We recommend to install docker, which facilitates the sharing of codes between any machines, without the need to install many dependencies. The installation of docker is quite straightforward <https://docs.docker.com/install/#support>, except when running on linux where you might need to take some extra steps <https://docs.docker.com/install/linux/linux-postinstall/> to setup the proxies properly.

After the installation, one can create a docker image for our model to work and docker allows us to create a virtual machine able to run in any kind of computer as long it has the docker tool.  To create the image one first needs to create a empty file called Dockerfile, the Dockerfile will contain the instructions needed to load the model and run it in our environment.
In the following we give some examples of how to do this:



To call the  pre-installed docker image with python3 and gcc compiler from the docker website we do the following:




here we add the file requirements to docker so it download and install the libraries we are using, this files contains the following instructions:

these are the packages and libraries with their respective versions. The following line will instruct pip to download and install the packages in the Docker image we want to create.

which tells docker the paths of our source files, where we will put the app program and other files we will need to run the app, and the path to our model and weights.

The line:

tells docker which proxy port our app will run, important to our app.

And the following lines tell docker to run our app stored in the folder src:




  
The app:
We wrote the app in a python script file called app.py. The app is written using the flask library which can create web interfaces based in java and php. The main important  features of the web app are the  file and the static folder. The  file defines the url configurations of the web app for the example images and other links, while the folder static contains the css scripts for colours, button sizes, etc.

After we prepare the app and setup the docker file, we now can create the docker image by executing the command:

where the  flag is to indicate the name of the image we create. Now we can start to set up the Heroku webserver.



  
Heroku:
First, one  has to create a Heroku account and then  install the heroku git[You might need to install git, please follow these instructions: <https://git-scm.com/book/en/v2/Getting-Started-Installing-Git>.] app in your machine. 

Next, open a terminal and run the command to login into your heroku account:

which will ask for your login name, usually your email, and password. After login, one has to pull the folder which contains our dockerfile, model and source code of our app:

Now we can give the instruction to heroku to push our docker container, by running the command:

and release the container in the webserver:

Finally, one can now open the app and get some logs by running the command:

Note that after 15 minutes of inactivity the server goes to sleep mode and it might take a little while to wake up again.




99


review
Ker, Justin, et al. Deep learning applications in medical image analysis. IEEE Access 6 (2018): 9375-9389. <https://ieeexplore.ieee.org/abstract/document/8241753/>
chexnet
See <https://stanfordmlgroup.github.io/projects/chexnet/> and the paper in arXiV: <https://arxiv.org/abs/1711.05225>. 
fastai
See <https://docs.fast.ai> and documentation in these webpages.

heroku
See <https://ml-xray.herokuapp.com> for a beta version.

github
See <https://github.com/FFFreitas/X-ray-and-ML>.

Wang
Wang, Xiaosong, Peng, Yifan, Lu, Le, Lu, Zhiyong,
Bagheri, Mohammadhadi, and Summers, Ronald M,
Chestx-ray8: Hospital-scale chest x-ray database
and benchmarks on weakly-supervised classification
and localization of common thorax diseases.
arXiv preprint arXiv:1705.02315, (2017)


Leslie
Leslie N Smith, Cyclical learning rates for training neural networks, In Applications of Computer Vision (WACV), 2017 IEEE Winter Conference IEEE, (2017) 464 472.


Micikevicius
Paulius Micikevicius and Sharan Narang and Jonah Alben and Gregory Frederick Diamos and Erich Elsen and David García and Boris Ginsburg and Michael Houston and Oleksii Kuchaiev and Ganesh Venkatesh and Hao Wu,
Mixed Precision Training,
CoRR,
abs/1710.03740,(2018)
  







Diederik
Diederik P. Kingma, Jimmy Ba,
Adam: A Method for Stochastic Optimization,
3rd ICLR,
(2015)

Nitish
Nitish Srivastava, Geoffrey Hinton, Alex Krizhevsky, Ilya Sutskever, and Ruslan Salakhutdinov,
Dropout: A simple way to prevent neural networks from overfitting,
The Journal of Machine Learning Research,
15(1),(2014) 1929.

Nielsen
Nielsen, Frank; Sun, Ke,
Guaranteed bounds on the Kullback-Leibler divergence of univariate mixtures using piecewise log-sum-exp inequalities,
CoRR,
18(12),(2016) 442.

gradcam
<http://openaccess.thecvf.com/content_ICCV_2017/papers/Selvaraju_Grad-CAM_Visual_Explanations_ICCV_2017_paper.pdf>


