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.
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.
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.
