Masque R-CNN pour la détection et la segmentation des navires

L’une des principales applications passionnantes de l’apprentissage approfondi est la capacité des machines à connaître les images. Fei-Fei Li a mentionné que cela donnait aux machines la “capacité de voir”. Il existe quatre grandes catégories de problèmes de détection et de segmentation, comme le décrit l’image (a) ci-dessous.

https://miro.medium.com/max/1717/1*8Nwk_IdGpe235Nsfewpucg.png

Masque R-CNN

Le masque R-CNN est une extension du R-CNN plus rapide. Faster R-CNN prédit les boîtes englobantes et Mask R-CNN ajoute essentiellement une autre branche pour prédire un masque d’objet en parallèle.

https://miro.medium.com/max/1485/1*zfUPBhMG9L_XlSM8C1PqdQ.pnghttps://miro.medium.com/max/1674/1*JzFsV3nJPhTpDm05KaCrxQ.png

Je ne vais pas entrer dans les détails du fonctionnement de Mask R-CNN, mais voici les étapes générales de l’approche :

Modèle de base : un réseau neuronal convolutionnel typique qui est un extracteur de caractéristiques. Par exemple, il transforme une image 1024x1024x3 en une carte de caractéristiques 32x32x2048 qui est entrée pour les couches suivantes.

Réseau de proposition de région (RPN) : En utilisant des régions définies avec des boîtes d’ancrage de 200K, le RPN scanne chaque région et prédit si un objet est présent ou non. L’un des avantages du RPN est qu’il ne scanne pas l’image particulière, le réseau scanne la carte des caractéristiques, ce qui le rend beaucoup plus rapide.

Classification des régions d’intérêt et boîte englobante : au cours de cette étape, l’algorithme prend les régions d’intérêt proposées par le RPN comme entrées et produit une classification (softmax) et une boîte englobante (régresseur).

Masques de segmentation : au cours de l’étape finale, l’algorithme prend les régions d’intérêt positives comme entrées et génère des masques de 28×28 pixels avec des valeurs flottantes comme sorties pour les objets. Pendant l’inférence, ces masques sont mis à l’échelle.

Formation et inférence avec le masque R-CNN

Au lieu de répliquer l’algorithme complet soutenu par le document de recherche, nous utiliserons l’impressionnante bibliothèque Mask R-CNN que Matterport a construite. Nous devrons A) générer nos ensembles d’entraînement et de développement, B) faire quelques manipulations pour les charger dans la bibliothèque, C) configurer notre environnement d’entraînement dans AWS pour l’entraînement, D) utiliser l’apprentissage par transfert pour commencer l’entraînement à partir des poids coco pré-entraînés, et E) régler notre modèle pour obtenir de bons résultats.

Etape 1 : Télécharger les données de Kaggle et générer des fractionnements d’entraînement et de développement

L’ensemble de données fourni par Kaggle comprend plusieurs milliers d’images, le plus simple est donc de les télécharger sur l’appareil AWS où nous allons faire notre formation. Une fois que nous les avons téléchargées, nous devons les diviser en deux groupes, l’un pour la formation et l’autre pour le développement, ce qui peut être fait de manière aléatoire grâce à un script python.

Je recommande fortement d’utiliser une instance de spot pour télécharger les informations de Kaggle en utilisant l’API de Kaggle et de télécharger ces données zippées dans un seau S3. Vous téléchargerez plus tard ces données depuis S3 et les dézipperez au moment de la formation.

Kaggle fournit un fichier csv appelé train_ship_segmentations.csv avec deux colonnes : ImageId et EncodedPixels (format d’encodage de la longueur de course). En supposant que nous avons téléchargé les images dans le chemin ./datasets/train_val/, nous allons diviser et déplacer les images dans les dossiers train et dev set avec ce code : train_ship_segmentations_df = pd.read_csv(os.path.join(“./datasets/train_val/train_ship_segmentations.csv”))

msk = np.random.rand(len(train_ship_segmentations_df)) < 0.8

train = train_ship_segmentations_df [msk]

test = train_ship_segmentations_df [~msk]

# Déplacer le train

pour index, row in train.iterrows() :

image_id = row[“ImageId”]

old_path = “./datasets/train_val/{}”.format(image_id)

new_path = “./datasets/train/{}”.format(image_id)

si os.path.isfile(old_path) :

os.rename(old_path, new_path)

# Move dev set

pour index, ligne dans test.iterrows() :

image_id = row [“ImageId”]

old_path = “./datasets/train_val/{}”.format(image_id)

new_path = “./datasets/val/{}”.format(image_id)

si os.path.isfile(old_path) :

os.rename(old_path, new_path)

Étape 2 : Chargement des données dans la bibliothèque

Il existe une convention choisie que la bibliothèque Mask R-CNN suit pour le chargement des ensembles de données. Nous aimerions créer une catégorie ShipDataset qui mettra en œuvre la plupart des fonctions requises :

ship.py

classe ShipDataset(utils.Dataset) :

def load_ship(self, dataset_dir, subset) :

def load_mask(self, image_id) :

def image_reference(self, image_id) :

Pour convertir un Run Length Encoded Mask en un masque d’image (tenseur booléen), nous utilisons cette fonction sous rle_decode. Cela permet souvent de générer les masques de vérité de fond que nous chargeons dans la bibliothèque pour la formation dans notre classe ShipDataset.

ship.py

classe ShipDataset(utils.Dataset) :

def load_ship(self, dataset_dir, subset) :

def load_mask(self, image_id) :

def image_reference(self, image_id) :

Étape 3 : Formation à la configuration avec les instances P3 Spot et le lot AWS

Compte tenu de l’énorme quantité de données que nous aimerions utiliser, nous devrons utiliser les instances GPU d’AWS pour solliciter de bonnes pistes pendant une partie pratique de votre temps. Les instances P3 sont assez coûteuses, mais si vous utilisez les instances Spot, vous obtiendrez une instance P3.2xlarge pour environ 0,9 $ / h, ce qui représente une économie d’environ 70 %. La clé ici est d’être efficace et d’automatiser au maximum car nous ne perdrons pas de temps ni d’argent dans des tâches non liées à la formation comme la correction des informations, etc. Pour y parvenir, nous utiliserons des scripts shell et des conteneurs docker, puis nous utiliserons le formidable service AWS Batch pour programmer notre formation.

La première chose que j’ai faite est de créer un AMI d’apprentissage approfondi configuré pour AWS Batch qui utilise nvidia-docker en suivant ce guide AWS. L’ID AMI est ami-073682d8e65240b76 et il est accueillant pour la communauté. Cela pourrait nous permettre d’utiliser des conteneurs docker avec des GPU.

Ensuite, il faut créer un fichier docker qui a toutes les dépendances que nous aimerions aussi parce que les scripts shell qui vont surveiller le téléchargement des informations et exécuter la formation : FROM nvidia/cuda:9.0-cudnn7-devel-ubuntu16.04

MAINTAIN Gabriel Garza <garzagabriel@gmail.com>

# Essentiels : outils de développement, outils de construction, OpenBLAS

RUN apt-get update && apt-get install -y –no-install-recommends \

apt-utils git curl vim unzip openssh-client wget \

construction-essentiel cmake \

libopenblas-dev

#

# Python 3.5

#

# Pour plus de commodité, alias (mais pas de lien symbolique) python & pip à python3 & pip3 comme recommandé dans :

# http://askubuntu.com/questions/351318/changing-symlink-python-to-python3-causes-problems

RUN apt-get install -y –no-install-recommends python3.5 python3.5-dev python3-pip python3-tk && \

pip3 install pip==9.0.3 –upgrade && \

pip3 install –no-cache-dir –upgrade setuptools && \

echo “alias python=’python3′” >> /root/.bash_aliases && \

echo “alias pip=’pip3′” >> /root/.bash_aliases

# L’oreiller et ses dépendances

RUN apt-get install -y –no-install-recommends libjpeg-dev zlib1g-dev && \

pip3 –no-cache-dir installent Oreiller

# Bibliothèques scientifiques et autres ensembles communs

RUN pip3 –no-cache-dir install \

numpy scipy sklearn scikit-image==0.13.1 pandas matplotlib Cython demande pandas imgaug

# Installer AWS CLI

RUN pip3 –no-cache-dir install awscli –upgrade

#

# Carnet Jupyter

#

# Autoriser l’accès depuis l’extérieur du conteneur et ne pas essayer d’ouvrir un navigateur.

# NOTE : désactivez le jeton d’authentification pour plus de commodité. NE FAITES PAS CELA SUR UN SERVEUR PUBLIC.

RUN pip3 –no-cache-dir install jupyter && \

mkdir /root/.jupyter && \

echo “c.NotebookApp.ip = ‘*'””. \

“\nc.NotebookApp.open_browser = Faux” \

“\nc.NotebookApp.token = ”” \

> /root/.jupyter/jupyter_notebook_config.py

EXPOSE 8888

#

# Tensorflow 1.6.0 – GPU

#

# Installer TensorFlow

RUN pip3 –no-cache-dir install tensorflow-gpu

# Exposer le port pour le TensorBoard

EXPOSE 6006

#

# OpenCV 3.4.1

#

# Dépendances

RUN apt-get install -y –no-install-recommends \

libjpeg8-dev libtiff5-dev libjasper-dev libpng12-dev \

libavcodec-dev libavformat-dev libswscale-dev libv4l-dev libgtk2.0-dev \

liblapacke-dev checkinstall

RUN pip3 installer opencv-python

#

# Keras 2.1.5

#

RUN pip3 install –no-cache-dir –upgrade h5py pydot_ng keras

#

# PyCocoTools

#

# En utilisant une fourchette de l’original qui a un correctif pour Python 3.

# J’ai soumis un PR au repo original (https://github.com/cocodataset/cocoapi/pull/50)

# mais il ne semble plus être actif.

RUN pip3 install –no-cache-dir git+https://github.com/waleedka/coco.git#subdirectory=PythonAPI

COPY setup_project_and_data.sh /home

COPY train.sh /home

COPY predict.sh /home

WORKDIR “/home”

setup_project_and_data.sh -> clone notre masque R-CNN repo, télécharge et décompresse nos données depuis S3, divise l’information en trains et ensembles de développement, télécharge les derniers poids que nous avons enregistrés dans S3

train.sh -> charge les derniers poids, lance la commande train python3 ./ship.py train –dataset=./datasets –weights=last, télécharge les poids formés sur S3 après la fin de l’entraînement

predict.sh -> télécharge l’ensemble des données du test Kaggle Challenge (qui est utilisé pour soumettre votre participation au défi), génère des prédictions pour chacune des images, convertit les masques en codage de longueur d’exécution, et télécharge le fichier CSV des prédictions en S3.

Étape 3 : Entraînement du modèle à l’aide de l’AWS Batch

La beauté d’AWS Batch est que vous pouvez simplement créer un environnement de calcul qui utilise un Spot Instance et il exécutera l’emploi en utilisant votre conteneur de docker, puis terminera votre Spot Instance dès que votre emploi se terminera.

Je n’entrerai pas dans les détails ici (je pourrais en faire un autre post), mais essentiellement vous construisez votre image, vous la téléchargez dans AWS ECR, puis dans AWS Batch vous programmez votre formation ou votre travail d’inférence à exécuter avec la commande bash predict.sh ou bash train.sh et vous attendez la fin (vous pouvez suivre la progression en regardant les logs dans AWS Watch). Les fichiers résultants (poids entraînés ou prédictions csv) sont téléchargés sur S3 par notre script.

La première fois que nous nous entraînons, nous passons dans l’argument coco (dans train.sh) afin d’utiliser l’apprentissage par transfert et d’entraîner notre modèle en plus de l’ensemble de données coco déjà formé :

python3 ./ship.py train –dataset=./datasets –weights=coco

Une fois que nous avons terminé notre entraînement initial, nous passons le dernier argument au commandement du train pour commencer l’entraînement là où nous nous sommes arrêtés :

python3 ./ship.py train –dataset=./datasets –weights=last

Nous pouvons régler notre modèle en utilisant la classe ShipConfig et en écrasant les paramètres par défaut. Il était important de régler la suppression des non-Max à 0 pour éviter de prédire des masques de navire qui se chevauchent (ce que le défi Kaggle ne permet pas). classe ShipConfig(Config) :

“””Configuration pour la formation sur l’ensemble de données du jouet.

Dérive de la classe Config de base et remplace certaines valeurs.

“””

# Donner un nom reconnaissable à la configuration

NOM = “navire

# Nous utilisons un GPU avec une mémoire de 12 Go, qui peut contenir deux images.

# Ajustez vers le bas si vous utilisez un GPU plus petit.

IMAGES_PER_GPU = 1

# Nombre de classes (y compris les antécédents)

NUM_CLASSES = 1 + 1 # Fond + navire

# Nombre d’étapes de formation par époque

STEPS_PER_EPOCH = 500

# Sauter les détections avec un taux de confiance de < 95%.

DÉTECTION_MIN_CONFIANCE = 0,95

# Seuil de suppression non maximal pour la détection

DETECTION_NMS_THRESHOLD = 0,0

IMAGE_MIN_DIM = 768

IMAGE_MAX_DIM = 768

Étape 4 : Prévoir les segmentations des navires

Pour générer nos prédictions, il nous suffit d’essayer de faire tourner notre conteneur dans AWS Batch avec la commande bash predict.sh. Cela peut utiliser le script à l’intérieur de generate_predictions.py, voici un extrait de ce à quoi ressemble l’inférence :

class InferenceConfig(config.__class__) :

# Exécuter la détection sur une image à la fois

GPU_COUNT = 1

IMAGES_PER_GPU = 1

DÉTECTION_MIN_CONFIANCE = 0,95

DÉTECTION_NMS_SEUIL = 0,0

IMAGE_MIN_DIM = 768

IMAGE_MAX_DIM = 768

RPN_ANCHOR_SCALES = (64, 96, 128, 256, 512)

DÉTECTION_MAX_INSTANCES = 20

# Créer un objet modèle en mode inférence.

config = InferenceConfig()

model = modellib.MaskRCNN(mode=”inférence”, model_dir=MODEL_DIR, config=config)

# Installer l’ensemble des données

ensemble de données = ship.ShipDataset()

# Poids de la charge

model.load_weights(os.path.join(ROOT_DIR, SHIP_WEIGHTS_PATH), by_name=True)

class_names = [‘BG’, ‘ship’]

# Détection de course

# Charger les identifiants des images (noms de fichiers) et les pixels codés en longueur de course

images_path = “ensembles de données/test”.

sample_sub_csv = “sample_submission.csv”

# images_path = “datasets/val”

# sample_sub_csv = “val_ship_segmentations.csv”

sample_submission_df = pd.read_csv(os.path.join(images_path,sample_sub_csv))

unique_image_ids = sample_submission_df.ImageId.unique()

out_pred_rows = []

compte = 0

pour image_id dans unique_image_ids :

image_path = os.path.join(images_path, image_id)

si os.path.isfile(image_path) :

compter += 1

imprimer (“Étape : “, compter)

# Commencez à compter le temps de prédiction

tic = time.clock()

image = skimage.io.imread(image_path)

résultats = model.detect([image], verbose=1)

r = résultats [0]

# Première image

re_encoded_to_rle_list = []

for i in np.arange(np.array(r[‘masques’]).shape[-1]) :

boolean_mask = r[‘masques’][ :,:,i]

re_encoded_to_rle = dataset.rle_encode(boolean_mask)

re_encoded_to_rle_list.append(re_encoded_to_rle)

si len(re_encoded_to_rle_list) == 0 :

out_pred_rows += [{‘ImageId’ : image_id, ‘EncodedPixels’ : None}]

print(“Found Ship : “, “NO”)

d’autre part :

pour rle_mask dans re_encoded_to_rle_list :

out_pred_rows += [{‘ImageId’ : image_id, ‘EncodedPixels’ : rle_mask}]

print(“Found Ship : “, rle_mask)

toc = time.clock()

print(“Temps de prédiction : “,toc-tic)

submission_df = pd.DataFrame(out_pred_rows)[[[‘ImageId’, ‘EncodedPixels’]]

nom de fichier = “{}{:%Y%m%dT%H%M}.csv”.format(“./submissions/submission_”, datetime.datetime.now())

submission_df.to_csv(nom de fichier, index=Faux)

J’ai vu plusieurs cas difficiles, comme des vagues et des nuages dans les images, que le modèle pensait initialement être des navires. Pour relever ce défi, j’ai modifié la taille des boîtes d’ancrage du réseau de propositions régionales RPN_ANCHOR_SCALES pour qu’elles soient plus petites, ce qui a considérablement amélioré les résultats car le modèle ne prédisait pas que les petites vagues étaient des navires.

Résultats

Vous pouvez obtenir des résultats décents après environ 30 époques (définies dans ship.py). Je me suis entraîné pour 160 époques et j’étais prêt à atteindre une précision de 80,5% dans ma soumission Kaggle.

J’ai inclus un ordinateur portable Jupyter appelé inspect_shyp_model.ipynb qui vous permet d’exécuter le modèle et de faire des prédictions sur n’importe quelle image localement sur votre ordinateur.