Using Object Detection for Complex Image Classification Scenarios Part 3: | by Aaron (Ari) Bornstein | Towards Data Science

TLDR; This series is based on the work detecting complex policies in the following real life code story. Code for the series can be found here.
In the previous tutorials we outlined our policy classification challenge and showed how we can approach it using the Custom Vision Cognitive Service. This tutorial introduces deep transfer learning as a means to leverage multiple data sources to overcome data scarcity problem.
Before we try to build a classifier for our complex policy let’s first look at the MNIST dataset to better understand key image classification concepts such as One Hot Encoding, Linear Modeling, Multi Layer Perception, Masking and Convolutions then we will put these concepts together and apply them to our own dataset.
To train a classification model on MNIST we first need a way to represent our images and labels . There are many ways to represent images as either Tensors, Matrices or Vectors.
For our first model we will use a vector representation. To do this we will first flatten the images to a long vector, in a manner similar to how one might unravel the thread of a cloth.
When we apply this process to the image of a “3” below with dimensions of 28 x 28 image pixels, it will result in a flattened array of length 784 pixels.
Now even though it is easy for us to look at this image and know it is a “3”, computers do not innately know this, we need to train a model to learn the how recognize that there is a “3” in the image. To do so we first need a way of representing the fact that the picture above contains an image of a “3”.
To accomplish we associate each of our image with a one 1-hot encoded label, where the first index corresponds to digit 0 and the last one corresponds to digit 9.
When we train a model we use this value as our target. The Keras code below loads the MNIST data
from keras.datasets import mnist from keras.utils import np_utils output_dim = nb_classes = 10 batch_size = 128 nb_epoch = 5# the data, shuffled and split between train and test sets (x_train, y_train), (x_test, y_test) = mnist.load_data()input_dim = 784 #28*28 X_train = x_train.reshape(60000, input_dim) X_test = x_test.reshape(10000, input_dim) X_train = X_train.astype('float32') X_test = X_test.astype('float32') X_train /= 255 X_test /= 255Y_train = np_utils.to_categorical(y_train, nb_classes) Y_test = np_utils.to_categorical(y_test, nb_classes)
Now that we have processed the MNIST images and their labels let’s train our first image classification model using Keras.
Logistic Regression (LR) is a fundamental machine learning technique that uses a linear weighted combination of features and generates probability-based predictions of different classes.
To train the LR model above on MNIST, we apply the following steps:
Initialize a random weight vector of 784 valuesTake the first 784bit MNIST training image vector like the “3” above and multiply it by our weight vector.Take the result of our multiplication and sum each of our 784 values until we get one numberPass the number into an function which takes our sum and fits it into a distribution between 0-9 and one hot encode the output. For the first example this number will most likely be incorrect, since we multiplied by random valuesCompare the output vector to the image label vector and calculate how close our prediction was using a loss function. The output of the loss function is called a loss.Apply an optimization such as SGD with respect to the value of the loss to update each value in the weight vector.
Initialize a random weight vector of 784 values
Take the first 784bit MNIST training image vector like the “3” above and multiply it by our weight vector.
Take the result of our multiplication and sum each of our 784 values until we get one number
Pass the number into an function which takes our sum and fits it into a distribution between 0-9 and one hot encode the output. For the first example this number will most likely be incorrect, since we multiplied by random values
Compare the output vector to the image label vector and calculate how close our prediction was using a loss function. The output of the loss function is called a loss.
Apply an optimization such as SGD with respect to the value of the loss to update each value in the weight vector.
medium.com
We then repeat this process on every image in our MNIST training set. For each image the weight values are updated so that they can better transform our input MNIST vectors into a value that matches it’s label.
When we finish running the steps above on our training set that is called an Epoch. After the first Epoch the values are still likely to be poor but after shuffling the dataset and repeating the process for a couple more Epochs the linear model learns linear weights that they converge on the decent representation of our data.
The Keras code below show the the results of this process.
from keras.models import Sequential from keras.layers import Dense, Activation model = Sequential() model.add(Dense(output_dim, input_dim=input_dim, activation='softmax')) model.compile(optimizer='sgd', loss='categorical_crossentropy', metrics=['accuracy']) history = model.fit(X_train, Y_train, batch_size=batch_size, nb_epoch=nb_epoch,verbose=1, validation_data=(X_test, Y_test)) score = model.evaluate(X_test, Y_test, verbose=0) print('Test Loss:', score[0]) print('Test accuracy:', score[1])
Train on 60000 samples, validate on 10000 samplesEpoch 1/560000/60000 [==============================] - 1s 16us/step - loss: 1.2899 - acc: 0.6898 - val_loss: 0.8185 - val_acc: 0.8255Epoch 2/560000/60000 [==============================] - 1s 17us/step - loss: 0.7228 - acc: 0.8374 - val_loss: 0.6113 - val_acc: 0.8588Epoch 3/560000/60000 [==============================] - 1s 11us/step - loss: 0.5912 - acc: 0.8575 - val_loss: 0.5281 - val_acc: 0.8724Epoch 4/560000/60000 [==============================] - 1s 11us/step - loss: 0.5280 - acc: 0.8681 - val_loss: 0.4821 - val_acc: 0.8800Epoch 5/560000/60000 [==============================] - 1s 13us/step - loss: 0.4897 - acc: 0.8749 - val_loss: 0.4514 - val_acc: 0.8858Test Loss: 0.4514175675392151Test accuracy: 0.8858
One can imagine, that just incrementing and summing weight vector values based on one output is sub-optimal and in some cases unproductive.
After all, not all data is linear.
Take the following example for two image classes called spam and not-spam. No matter how we update our weights there is no linear weights we can learn to differentiate between these classes.
But what if we had a way of combining multiple linear models for better representation power? We could then train a model that could differentiate between the two image classes.
We can do this with a feed forward neural network such as the Multi Layer Perception.
For MLPs to work we need a non linear activation function such as RELU for the sake of brevity we will treat this as a black box for more on this topic see the following post.
medium.com
The keras code below shows how to train a multi-layer perceptron on MNIST to get even better results than provided by our linear model.
from keras.models import Sequential from keras.layers import Dense, Activation output_dim = nb_classes = 10 batch_size = 128 nb_epoch = 5model = Sequential() model.add(Dense(input_dim, input_dim=input_dim, activation='relu')) model.add(Dense(input_dim, input_dim=input_dim, activation='relu'))model.add(Dense(output_dim, input_dim=input_dim, activation='softmax')) model.compile(optimizer='sgd', loss='categorical_crossentropy', metrics=['accuracy']) history = model.fit(X_train, Y_train, batch_size=batch_size, nb_epoch=nb_epoch,verbose=1, validation_data=(X_test, Y_test)) score = model.evaluate(X_test, Y_test, verbose=0) print('Test Loss:', score[0]) print('Test accuracy:', score[1])
Train on 60000 samples, validate on 10000 samplesEpoch 1/560000/60000 [==============================] - 9s 150us/step - loss: 1.0790 - acc: 0.7676 - val_loss: 0.5100 - val_acc: 0.8773Epoch 2/560000/60000 [==============================] - 9s 143us/step - loss: 0.4401 - acc: 0.8866 - val_loss: 0.3650 - val_acc: 0.9011Epoch 3/560000/60000 [==============================] - 12s 194us/step - loss: 0.3530 - acc: 0.9032 - val_loss: 0.3136 - val_acc: 0.9127Epoch 4/560000/60000 [==============================] - 16s 272us/step - loss: 0.3129 - acc: 0.9124 - val_loss: 0.2868 - val_acc: 0.9188Epoch 5/560000/60000 [==============================] - 12s 203us/step - loss: 0.2875 - acc: 0.9194 - val_loss: 0.2659 - val_acc: 0.9246Test Loss: 0.2659078140795231Test accuracy: 0.9246
Notice how much more accurate, yet slower the MLP model was than our simple linear model. When we have images greater than 500Kb to 1Mb it gets increasingly more computationally expensive to process our image as a sequence additionally it becomes much more challenging to detect complex self referential and hierarchical patterns in our sequence data.
This curse of dimensionality, was one of the key reasons the computer vision field was stalled until the advent of AlexNet in 2012.
What if instead of passing our full image as vector representation we represented our image as a matrix (28x28) and instead extracted representative features for making a classification decision? That is how computer vision worked until recently. Lets take a deeper look at traditional image feature extraction by trying to use edges as a feature for a model.
To do this we first we take an image such as the one below.
Then we take a predefined image mask in this case a sobel matrix that is used to extract edges
We apply the sobel matrix mask to our image in strides like a filter
When we visualize the resulting image we get the following edges which we can use as features
Creating masks such as the Sobel mask by hand is hard work and brittle what if we could learn masks? That is the key insight behind the convolutional neural network or CNN.
A CNN is a deep neural network comprised of a bunch of layers in such a way that the output of one layer is fed to the next layer (There are more complex architecture that skip layers with dropout we will take this as a given for now). Usually, CNN’s start with alternating between convolution layer and pooling layer (down sample), then end up with fully connected layer for the classification part.
A convolution layer is a set of filters. Each filter is defined by a weight (W) matrix, and bias (b).
Once we apply our mask we use pooling to reduce the dimensionality of the previous layer, which speeds up the network.
There are many different pooling methods max and average pooling are the most common.
Here an example of max and average pooling with a stride of 2:
In most CNNs we stack a set of convolutional and pooling layers until we have a representational set of features that we can flatten and use for class predictions.
The code below shows how to train a CNN on the MNIST images from above.
from keras.layers import Dropout, Flattenfrom keras.layers import Conv2D, MaxPooling2Dmodel = Sequential()model.add(Conv2D(32, kernel_size=(3, 3),                 activation='relu',                 input_shape=input_shape))model.add(Conv2D(64, (3, 3), activation='relu'))model.add(MaxPooling2D(pool_size=(2, 2)))model.add(Dropout(0.25))model.add(Flatten())model.add(Dense(128, activation='relu'))model.add(Dropout(0.5))model.add(Dense(nb_classes, activation='softmax'))model.compile(loss=keras.losses.categorical_crossentropy,              optimizer=keras.optimizers.Adadelta(),              metrics=['accuracy'])model.fit(x_train, y_train,          batch_size=batch_size,          epochs=nb_epoch,          verbose=1,          validation_data=(x_test, y_test))score = model.evaluate(x_test, y_test, verbose=0)print('Test loss:', score[0])print('Test accuracy:', score[1])
Train on 60000 samples, validate on 10000 samplesEpoch 1/560000/60000 [==============================] - 177s 3ms/step - loss: 0.2638 - acc: 0.9204 - val_loss: 0.0662 - val_acc: 0.9790Epoch 2/560000/60000 [==============================] - 173s 3ms/step - loss: 0.0882 - acc: 0.9732 - val_loss: 0.0404 - val_acc: 0.9865Epoch 3/560000/60000 [==============================] - 166s 3ms/step - loss: 0.0651 - acc: 0.9806 - val_loss: 0.0350 - val_acc: 0.9883Epoch 4/560000/60000 [==============================] - 163s 3ms/step - loss: 0.0549 - acc: 0.9836 - val_loss: 0.0334 - val_acc: 0.9887Epoch 5/560000/60000 [==============================] - 159s 3ms/step - loss: 0.0472 - acc: 0.9859 - val_loss: 0.0322 - val_acc: 0.9899Test loss: 0.03221080291894468Test accuracy: 0.9899
In the MNIST dataset we had tens of thousands of training examples what if we have less data like in our policy task? That is where we can use transfer learning. Training a Deep Neural Network from scratch requires tens of thousands of images, but training one that has already learned features in the domain you are adapting it to requires far fewer.
Transfer Learning, uses a pre-trained model and adapts it to our own problem. In transfer learning we leverage the features and concepts that were learned during the training of the base model. The input to the old and the new prediction layer is the same as the base model, we simply reuse the trained features. Then we train this modified network, either only the new weights of the new prediction layer or all weights of the entire network.
This can be used, for instance, when we have a small set of images that are in a similar domain to an existing trained model. In our case, this means adapting a network trained on ImageNet images to the task of policy classification.
The repo and post by Aditya Ananthram was used for inspiration for this section I strongly suggest you check it out.
For this task, we have chosen to use pretrained MobileNet model as our base model. While there are many classification architectures we’ll use MobileNet since it runs fast on a CPU and provides strong results.
from keras.layers import Dense,GlobalAveragePooling2Dfrom keras.applications import MobileNetfrom keras.preprocessing import imagefrom keras.applications.mobilenet import preprocess_inputfrom keras.preprocessing.image import ImageDataGeneratorfrom keras.models import Modelfrom keras.optimizers import Adambase_model=MobileNet(weights='imagenet',include_top=False) #imports the mobilenet model and discards the last 1000 neuron layer.x=base_model.outputx=GlobalAveragePooling2D()(x)x=Dense(1024,activation='relu')(x) #we add dense layers so that the model can learn more complex functions and classify for better results.x=Dense(1024,activation='relu')(x) #dense layer 2x=Dense(512,activation='relu')(x) #dense layer 3preds=Dense(2,activation='softmax')(x) #final layer with softmax activationmodel=Model(inputs=base_model.input,outputs=preds)
for layer in model.layers[:20]:    layer.trainable=Falsefor layer in model.layers[20:]:    layer.trainable=TrueDownloading data from https://github.com/fchollet/deep-learning-models/releases/download/v0.6/mobilenet_1_0_224_tf_no_top.h517227776/17225924 [==============================] - 13s 1us/step
model.summary()_________________________________________________________________Layer (type)                 Output Shape              Param #   =================================================================input_1 (InputLayer)         (None, None, None, 3)     0         _________________________________________________________________conv1_pad (ZeroPadding2D)    (None, None, None, 3)     0         _________________________________________________________________conv1 (Conv2D)               (None, None, None, 32)    864       _________________________________________________________________conv1_bn (BatchNormalization (None, None, None, 32)    128       _________________________________________________________________conv1_relu (ReLU)            (None, None, None, 32)    0         _________________________________________________________________conv_dw_1 (DepthwiseConv2D)  (None, None, None, 32)    288       _________________________________________________________________conv_dw_1_bn (BatchNormaliza (None, None, None, 32)    128       _________________________________________________________________conv_dw_1_relu (ReLU)        (None, None, None, 32)    0         _________________________________________________________________conv_pw_1 (Conv2D)           (None, None, None, 64)    2048      _________________________________________________________________conv_pw_1_bn (BatchNormaliza (None, None, None, 64)    256       _________________________________________________________________conv_pw_1_relu (ReLU)        (None, None, None, 64)    0         _________________________________________________________________conv_pad_2 (ZeroPadding2D)   (None, None, None, 64)    0         _________________________________________________________________conv_dw_2 (DepthwiseConv2D)  (None, None, None, 64)    576       _________________________________________________________________conv_dw_2_bn (BatchNormaliza (None, None, None, 64)    256       _________________________________________________________________conv_dw_2_relu (ReLU)        (None, None, None, 64)    0         _________________________________________________________________conv_pw_2 (Conv2D)           (None, None, None, 128)   8192      _________________________________________________________________conv_pw_2_bn (BatchNormaliza (None, None, None, 128)   512       _________________________________________________________________conv_pw_2_relu (ReLU)        (None, None, None, 128)   0         _________________________________________________________________conv_dw_3 (DepthwiseConv2D)  (None, None, None, 128)   1152      _________________________________________________________________conv_dw_3_bn (BatchNormaliza (None, None, None, 128)   512       _________________________________________________________________conv_dw_3_relu (ReLU)        (None, None, None, 128)   0         _________________________________________________________________conv_pw_3 (Conv2D)           (None, None, None, 128)   16384     _________________________________________________________________conv_pw_3_bn (BatchNormaliza (None, None, None, 128)   512       _________________________________________________________________conv_pw_3_relu (ReLU)        (None, None, None, 128)   0         _________________________________________________________________conv_pad_4 (ZeroPadding2D)   (None, None, None, 128)   0         _________________________________________________________________conv_dw_4 (DepthwiseConv2D)  (None, None, None, 128)   1152      _________________________________________________________________conv_dw_4_bn (BatchNormaliza (None, None, None, 128)   512       _________________________________________________________________conv_dw_4_relu (ReLU)        (None, None, None, 128)   0         _________________________________________________________________conv_pw_4 (Conv2D)           (None, None, None, 256)   32768     _________________________________________________________________conv_pw_4_bn (BatchNormaliza (None, None, None, 256)   1024      _________________________________________________________________conv_pw_4_relu (ReLU)        (None, None, None, 256)   0         _________________________________________________________________conv_dw_5 (DepthwiseConv2D)  (None, None, None, 256)   2304      _________________________________________________________________conv_dw_5_bn (BatchNormaliza (None, None, None, 256)   1024      _________________________________________________________________conv_dw_5_relu (ReLU)        (None, None, None, 256)   0         _________________________________________________________________conv_pw_5 (Conv2D)           (None, None, None, 256)   65536     _________________________________________________________________conv_pw_5_bn (BatchNormaliza (None, None, None, 256)   1024      _________________________________________________________________conv_pw_5_relu (ReLU)        (None, None, None, 256)   0         _________________________________________________________________conv_pad_6 (ZeroPadding2D)   (None, None, None, 256)   0         _________________________________________________________________conv_dw_6 (DepthwiseConv2D)  (None, None, None, 256)   2304      _________________________________________________________________conv_dw_6_bn (BatchNormaliza (None, None, None, 256)   1024      _________________________________________________________________conv_dw_6_relu (ReLU)        (None, None, None, 256)   0         _________________________________________________________________conv_pw_6 (Conv2D)           (None, None, None, 512)   131072    _________________________________________________________________conv_pw_6_bn (BatchNormaliza (None, None, None, 512)   2048      _________________________________________________________________conv_pw_6_relu (ReLU)        (None, None, None, 512)   0         _________________________________________________________________conv_dw_7 (DepthwiseConv2D)  (None, None, None, 512)   4608      _________________________________________________________________conv_dw_7_bn (BatchNormaliza (None, None, None, 512)   2048      _________________________________________________________________conv_dw_7_relu (ReLU)        (None, None, None, 512)   0         _________________________________________________________________conv_pw_7 (Conv2D)           (None, None, None, 512)   262144    _________________________________________________________________conv_pw_7_bn (BatchNormaliza (None, None, None, 512)   2048      _________________________________________________________________conv_pw_7_relu (ReLU)        (None, None, None, 512)   0         _________________________________________________________________conv_dw_8 (DepthwiseConv2D)  (None, None, None, 512)   4608      _________________________________________________________________conv_dw_8_bn (BatchNormaliza (None, None, None, 512)   2048      _________________________________________________________________conv_dw_8_relu (ReLU)        (None, None, None, 512)   0         _________________________________________________________________conv_pw_8 (Conv2D)           (None, None, None, 512)   262144    _________________________________________________________________conv_pw_8_bn (BatchNormaliza (None, None, None, 512)   2048      _________________________________________________________________conv_pw_8_relu (ReLU)        (None, None, None, 512)   0         _________________________________________________________________conv_dw_9 (DepthwiseConv2D)  (None, None, None, 512)   4608      _________________________________________________________________conv_dw_9_bn (BatchNormaliza (None, None, None, 512)   2048      _________________________________________________________________conv_dw_9_relu (ReLU)        (None, None, None, 512)   0         _________________________________________________________________conv_pw_9 (Conv2D)           (None, None, None, 512)   262144    _________________________________________________________________conv_pw_9_bn (BatchNormaliza (None, None, None, 512)   2048      _________________________________________________________________conv_pw_9_relu (ReLU)        (None, None, None, 512)   0         _________________________________________________________________conv_dw_10 (DepthwiseConv2D) (None, None, None, 512)   4608      _________________________________________________________________conv_dw_10_bn (BatchNormaliz (None, None, None, 512)   2048      _________________________________________________________________conv_dw_10_relu (ReLU)       (None, None, None, 512)   0         _________________________________________________________________conv_pw_10 (Conv2D)          (None, None, None, 512)   262144    _________________________________________________________________conv_pw_10_bn (BatchNormaliz (None, None, None, 512)   2048      _________________________________________________________________conv_pw_10_relu (ReLU)       (None, None, None, 512)   0         _________________________________________________________________conv_dw_11 (DepthwiseConv2D) (None, None, None, 512)   4608      _________________________________________________________________conv_dw_11_bn (BatchNormaliz (None, None, None, 512)   2048      _________________________________________________________________conv_dw_11_relu (ReLU)       (None, None, None, 512)   0         _________________________________________________________________conv_pw_11 (Conv2D)          (None, None, None, 512)   262144    _________________________________________________________________conv_pw_11_bn (BatchNormaliz (None, None, None, 512)   2048      _________________________________________________________________conv_pw_11_relu (ReLU)       (None, None, None, 512)   0         _________________________________________________________________conv_pad_12 (ZeroPadding2D)  (None, None, None, 512)   0         _________________________________________________________________conv_dw_12 (DepthwiseConv2D) (None, None, None, 512)   4608      _________________________________________________________________conv_dw_12_bn (BatchNormaliz (None, None, None, 512)   2048      _________________________________________________________________conv_dw_12_relu (ReLU)       (None, None, None, 512)   0         _________________________________________________________________conv_pw_12 (Conv2D)          (None, None, None, 1024)  524288    _________________________________________________________________conv_pw_12_bn (BatchNormaliz (None, None, None, 1024)  4096      _________________________________________________________________conv_pw_12_relu (ReLU)       (None, None, None, 1024)  0         _________________________________________________________________conv_dw_13 (DepthwiseConv2D) (None, None, None, 1024)  9216      _________________________________________________________________conv_dw_13_bn (BatchNormaliz (None, None, None, 1024)  4096      _________________________________________________________________conv_dw_13_relu (ReLU)       (None, None, None, 1024)  0         _________________________________________________________________conv_pw_13 (Conv2D)          (None, None, None, 1024)  1048576   _________________________________________________________________conv_pw_13_bn (BatchNormaliz (None, None, None, 1024)  4096      _________________________________________________________________conv_pw_13_relu (ReLU)       (None, None, None, 1024)  0         _________________________________________________________________global_average_pooling2d_1 ( (None, 1024)              0         _________________________________________________________________dense_7 (Dense)              (None, 1024)              1049600   _________________________________________________________________dense_8 (Dense)              (None, 1024)              1049600   _________________________________________________________________dense_9 (Dense)              (None, 512)               524800    _________________________________________________________________dense_10 (Dense)             (None, 2)                 1026      =================================================================Total params: 5,853,890Trainable params: 5,817,986Non-trainable params: 35,904_________________________________________________________________
The code below shows how to train a custom MobileNet model on our custom policy using Keras.
train_datagen=ImageDataGenerator(preprocessing_function=preprocess_input) #included in our dependenciestrain_generator=train_datagen.flow_from_directory('/data/dataset/Beverages/Train/',target_size=(224,224),color_mode='rgb',batch_size=32,class_mode='categorical',shuffle=True)test_datagen = ImageDataGenerator(preprocessing_function=preprocess_input)test_generator = test_datagen.flow_from_directory(    directory=r"/data/dataset/Beverages/Test/",    target_size=(224, 224),    color_mode="rgb",    batch_size=1,    class_mode='categorical',    shuffle=False,    seed=42)
Found 180 images belonging to 2 classes.Found 60 images belonging to 2 classes.
i = 0for data in test_generator:    if i > 3: break     else: i+=1    img, cls = data    print(np.argmax(cls))    plt.imshow(img[0])    plt.show()
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).0
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).0
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).0
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).0
model.compile(optimizer='Adam',loss='categorical_crossentropy',metrics=['accuracy'])# Adam optimizer# loss function will be categorical cross entropy# evaluation metric will be accuracystep_size_train=train_generator.n//train_generator.batch_sizemodel.fit_generator(generator=train_generator,                   steps_per_epoch=step_size_train,                   epochs=5)
Epoch 1/55/5 [==============================] - 96s 19s/step - loss: 0.8017 - acc: 0.7313Epoch 2/55/5 [==============================] - 77s 15s/step - loss: 0.0101 - acc: 1.0000Epoch 3/55/5 [==============================] - 79s 16s/step - loss: 0.0289 - acc: 0.9937Epoch 4/55/5 [==============================] - 111s 22s/step - loss: 0.0023 - acc: 1.0000Epoch 5/55/5 [==============================] - 87s 17s/step - loss: 0.0025 - acc: 1.0000
As we can see below the MobileNet is a really strong model for learning and representing our toy policy.
from utils import classification_reporty_true = np.concatenate([np.argmax(test_generator[i][1], axis=1) for i in range(test_generator.n)])y_pred =  np.argmax(model.predict_generator(test_generator, steps=test_generator.n), axis=1)classification_report(y_true, y_pred)
precision    recall  f1-score   support           0       1.00      1.00      1.00        30           1       1.00      1.00      1.00        30   micro avg       1.00      1.00      1.00        60   macro avg       1.00      1.00      1.00        60weighted avg       1.00      1.00      1.00        60Confusion matrix, without normalization[[30  0] [ 0 30]]Normalized confusion matrix[[1. 0.] [0. 1.]]
However if our policy was more complex it might have been challenging for us to model this way. In the next post we will dive into how to use object detection for complex image classification scenarios.
medium.com
github.com
azure.microsoft.com
towardsdatascience.com
towardsdatascience.com
In the next post we will dive into how to use object detection for complex image classification scenarios. Future posts will cover.
Training and Computer Vision Models on the Cloud using Azure ML Service
Train a Computer Vision Model on a Remote Cluster with Azure Machine Learning
If you have any questions, comments, or topics you would like me to discuss feel free to follow me on Twitter if there is a milestone you feel I missed please let me know.
Aaron (Ari) Bornstein is an avid AI enthusiast with a passion for history, engaging with new technologies and computational medicine. As an Open Source Engineer at Microsoft’s Cloud Developer Advocacy team, he collaborates with Israeli Hi-Tech Community, to solve real world problems with game changing technologies that are then documented, open sourced, and shared with the rest of the world.