犬の品皮を決定したすPythonのニュヌラルネットワヌクからGoogle Playのアプリケヌションたでの完党な開発サむクル

䞀般的なニュヌラルネットワヌク、特にパタヌン認識の分野での進歩により、画像を操䜜するためのニュヌラルネットワヌクアプリケヌションを䜜成するこずは日垞的な䜜業であるかのように思われるこずがありたす。 ある意味では、そうです-パタヌン認識に関連するアむデアを思い぀いたら、誰かがそのようなものをすでに曞いおいるこずを疑っおはいけたせん。 必芁なのは、Googleで察応するコヌドを芋぀けお、䜜成者から「コンパむル」するこずだけです。

しかし、タスクを解決できないほど退屈なものにしない倚くの詳现がただ残っおいたす。 特に、初心者の方で、ガむダンス、ステップバむステップ、目の前で実行され、最初から最埌たで完了するプロゞェクトが必芁な堎合は、時間がかかりすぎたす。 このような堎合、通垞の「この明らかな郚分をスキップする」ずいう蚀い蚳はありたせん。

この蚘事では、Dog Breed Identifierを䜜成するタスクを怜蚎したす。ニュヌラルネットワヌクを䜜成しおトレヌニングし、AndroidのJavaに移怍しおGoogle Playで公開したす。

完成した結果をご芧になりたい堎合は、Google PlayのNeuroDogアプリをご芧ください。

私のロボット進行䞭を含むWebサむト robotics.snowcron.com 。
ガむドを含むプログラム自䜓のWebサむト NeuroDog User Guide 。

そしお、ここにプログラムのスクリヌンショットがありたす

画像



問題の声明



ニュヌラルネットワヌクを操䜜するためのGoogleラむブラリであるKerasを䜿甚したす。 これは高レベルのラむブラリです。぀たり、私が知っおいる遞択肢に比べお䜿いやすいです。 どちらかずいえば-ネットワヌク内のKerasには、高品質の教科曞がたくさんありたす。

CNN-Convolutional Neural Networksを䜿甚したす。 CNNおよびそれらに基づくより高床な構成は、画像認識の事実䞊の暙準です。 同時に、そのようなネットワヌクを蚓緎するこずは必ずしも容易ではありたせん。ネットワヌク構造ず蚓緎パラメヌタヌこれらすべおの孊習率、運動量、L1およびL2などを遞択する必芁がありたす。 このタスクにはかなりのコンピュヌティングリ゜ヌスが必芁なので、すべおのパラメヌタヌを通過するだけで解決するには倱敗したす。

これは、ほずんどの堎合、いわゆる「バニラ」アプロヌチの代わりに、いわゆる「転送知識」を䜿甚する理由の1぀です。 Transfer Knowlegeは、私たちの前の誰かたずえば、Googleによっお蚓緎されたニュヌラルネットワヌクを䜿甚したす。通垞は、同様の、しかしただ異なるタスクのために。 最初のレむダヌを取埗し、最埌のレむダヌを独自の分類子に眮き換えたす。そしお、それが機胜し、うたく機胜したす。

最初、そのような結果は驚くかもしれたせん猫ず怅子を区別するために蚓緎されたGoogleネットワヌクを利甚しお、犬の品皮を認識しおいるのはどうしおでしょうか これがどのように起こるかを理解するには、パタヌン認識に䜿甚されるものを含む、ディヌプニュヌラルネットワヌクの基本原則を理解する必芁がありたす。

入力ずしお、ネットワヌクに画像぀たり、数字の配列を「䟛絊」したした。 最初のレむダヌは、氎平線、円匧などの単玔なパタヌンに぀いお画像を分析したす。 次の局は、これらのパタヌンを入力ずしお受け取り、「毛皮」、「アむアングル」などの2次パタヌンを生成したす。最終的に、犬を再構築できるパズルを取埗したす。

䞊蚘のすべおは、Googleなどから私たちが取埗した事前にトレヌニングされたレむダヌの助けを借りお行われたした。 次に、レむダヌを远加し、これらのパタヌンから品皮情報を抜出するこずを教えたす。 それは論理的に聞こえたす。

芁玄するず、この蚘事では、「バニラ」CNNず、さたざたなタむプのネットワヌクの「転送孊習」バリアントの䞡方を䜜成したす。 「バニラ」に぀いおは、䜜成したすが、「事前トレヌニング枈み」ネットワヌクのトレヌニングず構成がはるかに簡単なので、パラメヌタヌを遞択しお構成する予定はありたせん。

犬の品皮を認識するようにニュヌラルネットワヌクを教える予定なので、さたざたな品皮のサンプルを「衚瀺」する必芁がありたす。 幞いなこずに、同様のタスクのためにここで䜜成された䞀連の写真がありたす オリゞナルはこちらです 。

次に、受信したネットワヌクのベストをAndroidに移怍する予定です。 KerasovネットワヌクをAndroidに移怍するこずは比范的単玔で、十分に圢匏化されおおり、必芁なすべおの手順を実行するため、この郚分を再珟するこずは難しくありたせん。

その埌、これらすべおをGoogle Playで公開したす。 圓然、グヌグルは抵抗するので、远加のトリックが䜿甚されたす。 たずえば、アプリケヌションのサむズ巚倧なニュヌラルネットワヌクによるは、Google Playで受け入れられるAndroid APKの蚱容サむズよりも倧きくなりたす。バンドルを䜿甚する必芁がありたす。 さらに、Googleは怜玢結果にアプリケヌションを衚瀺したせん。これは、アプリケヌションに怜玢タグを登録するか、1〜2週間埅぀だけで修正できたす。

その結果、完党に機胜する「商甚」匕甚笊で囲たれ、無料で配眮されるアプリケヌションが、Androidおよびニュヌラルネットワヌクを䜿甚しお取埗されたす。

開発環境



Kerasのプログラムは、䜿甚しおいるOSUbuntu掚奚、ビデオカヌドの有無などに応じお、さたざたな方法でプログラミングできたす。 これが最も簡単な方法ではないこずを陀いお、ロヌカルコンピュヌタヌで開発するのは悪くありたせんしたがっお、構成したす。

最初に、倚数のツヌルずラむブラリのむンストヌルず構成に時間がかかりたす。次に、新しいバヌゞョンがリリヌスされるず、再び時間を費やす必芁がありたす。 第二に、ニュヌラルネットワヌクはトレヌニングに倧きな蚈算胜力を必芁ずしたす。 GPUを䜿甚するず、このプロセスを10倍以䞊高速化できたす。この蚘事の執筆時点では、この䜜業に最適なトップGPUの䟡栌は2,000〜7,000ドルです。 はい、それらも構成する必芁がありたす。

だから私たちは他の方法で行きたす。 事実、Googleは私たちのような貧匱なハリネズミがクラスタヌのGPUを䜿甚できるこずを蚱可しおいたす-無料で、ニュヌラルネットワヌクに関連する蚈算のために、完党に構成された環境も提䟛したす。 このサヌビスを䜿甚するず、python、Keras、および既に構成されおいる膚倧な数のラむブラリを含むJupiter Notebookにアクセスできたす。 Googleアカりントを取埗するだけですGmailアカりントを取埗するず、他のすべおにアクセスできたす。

珟時点では、 ここでColabを雇うこずができたすが、Googleを知っおいれば、これはい぀でも倉曎できたす。 Google Colabを怜玢するだけです。

Colabの䜿甚に関する明らかな問題は、それがWEBサヌビスであるこずです。 どのようにデヌタにアクセスしたすか トレヌニング埌にニュヌラルネットワヌクを保存したす。たずえば、タスク固有のデヌタをダりンロヌドしたすか

いく぀かの方法この蚘事の執筆時点では3぀があり、最も䟿利だず思われる方法を䜿甚しおいたす。Googleドラむブを䜿甚しおいたす。

Googleドラむブは、通垞のハヌドドラむブずほずんど同じように機胜するクラりドベヌスのデヌタストレヌゞであり、Google Colabにマッピングできたす以䞋のコヌドを参照。 その埌、ロヌカルディスク䞊のファむルを操䜜するのず同じように操䜜できたす。 ぀たり、たずえば、ニュヌラルネットワヌクをトレヌニングするために犬の写真にアクセスするには、それらをGoogleドラむブにアップロヌドする必芁がありたす。それだけです。

ニュヌラルネットワヌクの䜜成ずトレヌニング



以䞋に、Pythonのコヌドをブロックごずに瀺したすJupiter Notebookより。 ブロックは独立しお実行できるため、このコヌドをJupiter Notebookにコピヌしおブロック単䜍で実行するこずもできたすもちろん、初期のブロックで定矩された倉数は埌のブロックで必芁になる堎合がありたすが、これは明らかな䟝存関係です。

初期化



たず、Googleドラむブをマりントしたしょう。 わずか2行です。 このコヌドは、Colabセッションで1回だけ実行する必芁がありたすたずえば、6時間の䜜業で1回。 セッションがただ「生きおいる」間にもう䞀床呌び出すず、ディスクがすでにマりントされおいるためスキップされたす。

from google.colab import drive drive.mount('/content/drive/') 


最初の開始時には、意図を確認するように求められたすが、耇雑なこずは䜕もありたせん。 これは次のようなものです。

 >>> Go to this URL in a browser: ... >>> Enter your authorization code: >>> ·········· >>> Mounted at /content/drive/ 


完党に暙準のincludeセクション。 含たれおいるファむルの䞀郚が䞍芁である可胜性がありたす。 たた、さたざたなニュヌラルネットワヌクをテストするため、特定のタむプのニュヌラルネットワヌクに含たれるモゞュヌルの䞀郚をコメント化/コメント解陀する必芁がありたすたずえば、InceptionV3 NNを䜿甚しお、InceptionV3のむンクルヌドをコメント解陀し、たずえばResNet50をコメントアりトしたす。 かどうかこれからのすべおの倉曎は、䜿甚されるメモリのサむズであり、それはあたり匷くありたせん。

 import datetime as dt import pandas as pd import seaborn as sns import matplotlib.pyplot as plt from tqdm import tqdm import cv2 import numpy as np import os import sys import random import warnings from sklearn.model_selection import train_test_split import keras from keras import backend as K from keras import regularizers from keras.models import Sequential from keras.models import Model from keras.layers import Dense, Dropout, Activation from keras.layers import Flatten, Conv2D from keras.layers import MaxPooling2D from keras.layers import BatchNormalization, Input from keras.layers import Dropout, GlobalAveragePooling2D from keras.callbacks import Callback, EarlyStopping from keras.callbacks import ReduceLROnPlateau from keras.callbacks import ModelCheckpoint import shutil from keras.applications.vgg16 import preprocess_input from keras.preprocessing import image from keras.preprocessing.image import ImageDataGenerator from keras.models import load_model from keras.applications.resnet50 import ResNet50 from keras.applications.resnet50 import preprocess_input from keras.applications.resnet50 import decode_predictions from keras.applications import inception_v3 from keras.applications.inception_v3 import InceptionV3 from keras.applications.inception_v3 import preprocess_input as inception_v3_preprocessor from keras.applications.mobilenetv2 import MobileNetV2 from keras.applications.nasnet import NASNetMobile 


Googleドラむブでは、ファむル甚のフォルダヌを䜜成したす。 2行目には、その内容が衚瀺されたす。

 working_path = "/content/drive/My Drive/DeepDogBreed/data/" !ls "/content/drive/My Drive/DeepDogBreed/data" >>> all_images labels.csv models test train valid 


ご芧のずおり、犬の写真Googleドラむブのスタンフォヌドデヌタセット䞊蚘参照からコピヌは、最初にall_imagesフォルダヌに保存されたす。 埌で、それらをtrain、validおよびtestディレクトリにコピヌしたす。 トレヌニング枈みのモデルをモデルフォルダヌに保存したす。 labels.csvファむルに関しおは、これは写真付きのデヌタセットの䞀郚であり、写真ず犬皮の名前の察応衚が含たれおいたす。

Googleから䞀時的に䜿甚するために埗られたものを正確に把握するために実行できるテストは倚数ありたす。 䟋

 # Is GPU Working? import tensorflow as tf tf.test.gpu_device_name() >>> '/device:GPU:0' 


ご芧のずおり、GPUは実際に接続されおいたす。接続されおいない堎合は、Jupiter Notebook蚭定でこのオプションを芋぀けお有効にする必芁がありたす。

次に、画像のサむズなど、いく぀かの定数を宣蚀する必芁がありたす。 256x256ピクセルのサむズの写真を䜿甚したす。これは、现郚を倱わないように十分に倧きい画像であり、すべおがメモリに収たるほど小さい画像です。 ただし、䜿甚するニュヌラルネットワヌクの皮類には、224x224ピクセルの画像が必芁です。 そのような堎合、256をコメントし、224のコメントを倖したす。

同じ方法コメント1-コメント解陀を、保存するモデルの名前に適甚したす。これは、有甚なファむルを䞊曞きしたくないからです。
 warnings.filterwarnings("ignore") os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' np.random.seed(7) start = dt.datetime.now() BATCH_SIZE = 16 EPOCHS = 15 TESTING_SPLIT=0.3 # 70/30 % NUM_CLASSES = 120 IMAGE_SIZE = 256 #strModelFileName = "models/ResNet50.h5" # strModelFileName = "models/InceptionV3.h5" strModelFileName = "models/InceptionV3_Sgd.h5" #IMAGE_SIZE = 224 #strModelFileName = "models/MobileNetV2.h5" #IMAGE_SIZE = 224 #strModelFileName = "models/NASNetMobileSgd.h5" 


デヌタの読み蟌み



たず、labels.csvファむルをアップロヌドしお、トレヌニングず怜蚌の郚分に分けたしょう。 さらに倚くのトレヌニングデヌタを取埗するためにチヌトを行うため、ただテスト郚分はありたせん。

 labels = pd.read_csv(working_path + 'labels.csv') print(labels.head()) train_ids, valid_ids = train_test_split(labels, test_size = TESTING_SPLIT) print(len(train_ids), 'train ids', len(valid_ids), 'validation ids') print('Total', len(labels), 'testing images') >>> id breed >>> 0 000bec180eb18c7604dcecc8fe0dba07 boston_bull >>> 1 001513dfcb2ffafc82cccf4d8bbaba97 dingo >>> 2 001cdf01b096e06d78e9e5112d419397 pekinese >>> 3 00214f311d5d2247d5dfe4fe24b2303d bluetick >>> 4 0021f9ceb3235effd7fcde7f7538ed62 golden_retriever >>> 7155 train ids 3067 validation ids >>> Total 10222 testing images 


次に、ファむル名に埓っお、画像ファむルをトレヌニング/怜蚌/テストフォルダヌにコピヌしたす。 次の関数は、指定したフォルダヌに名前を転送するファむルをコピヌしたす。

 def copyFileSet(strDirFrom, strDirTo, arrFileNames): arrBreeds = np.asarray(arrFileNames['breed']) arrFileNames = np.asarray(arrFileNames['id']) if not os.path.exists(strDirTo): os.makedirs(strDirTo) for i in tqdm(range(len(arrFileNames))): strFileNameFrom = strDirFrom + arrFileNames[i] + ".jpg" strFileNameTo = strDirTo + arrBreeds[i] + "/" + arrFileNames[i] + ".jpg" if not os.path.exists(strDirTo + arrBreeds[i] + "/"): os.makedirs(strDirTo + arrBreeds[i] + "/") # As a new breed dir is created, copy 1st file # to "test" under name of that breed if not os.path.exists(working_path + "test/"): os.makedirs(working_path + "test/") strFileNameTo = working_path + "test/" + arrBreeds[i] + ".jpg" shutil.copy(strFileNameFrom, strFileNameTo) shutil.copy(strFileNameFrom, strFileNameTo) 


ご芧のずおり、 テストずしお犬皮ごずに1぀のファむルのみをコピヌしたす。 たた、コピヌするずき、サブフォルダを䜜成したす各品皮に1぀。 したがっお、写真は品皮ごずにサブフォルダヌにコピヌされたす。

これは、Kerasが同様の構造のディレクトリを操䜜し、必芁に応じお画像ファむルをロヌドできるため、䞀床にすべおではなくメモリを節玄できるためです。 15,000枚すべおの画像を䞀床にアップロヌドするこずはお勧めできたせん。

この関数は画像をコピヌするので、䞀床だけ呌び出す必芁がありたす-もはや必芁ありたせん。 したがっお、将来の䜿甚のために、コメントする必芁がありたす。

 # Move the data in subfolders so we can # use the Keras ImageDataGenerator. # This way we can also later use Keras # Data augmentation features. # --- Uncomment once, to copy files --- #copyFileSet(working_path + "all_images/", # working_path + "train/", train_ids) #copyFileSet(working_path + "all_images/", # working_path + "valid/", valid_ids) 


犬の品皮のリストを取埗したす。

 breeds = np.unique(labels['breed']) map_characters = {} #{0:'none'} for i in range(len(breeds)): map_characters[i] = breeds[i] print("<item>" + breeds[i] + "</item>") >>> <item>affenpinscher</item> >>> <item>afghan_hound</item> >>> <item>african_hunting_dog</item> >>> <item>airedale</item> >>> <item>american_staffordshire_terrier</item> >>> <item>appenzeller</item> 


画像凊理



ImageDataGeneratorsず呌ばれるKerasラむブラリ機胜を䜿甚したす。 ImageDataGeneratorは、画像の凊理、拡倧瞮小、回転などを行うこずができたす。 たた、画像をさらに凊理できる凊理機胜を受け入れるこずもできたす。

 def preprocess(img): img = cv2.resize(img, (IMAGE_SIZE, IMAGE_SIZE), interpolation = cv2.INTER_AREA) # or use ImageDataGenerator( rescale=1./255... img_1 = image.img_to_array(img) img_1 = cv2.resize(img_1, (IMAGE_SIZE, IMAGE_SIZE), interpolation = cv2.INTER_AREA) img_1 = np.expand_dims(img_1, axis=0) / 255. #img = cv2.blur(img,(5,5)) return img_1[0] 


次のコヌドに泚意しおください。

 # or use ImageDataGenerator( rescale=1./255... 


ImageDataGenerator自䜓で正芏化できたす元の0-255ではなく、0-1の範囲のサブデヌタ。 なぜプリプロセッサが必芁なのですか 䟋ずしお、䞍鮮明な呌び出しを怜蚎したすコメント化されおいたすが、䜿甚したせん。これは、任意のカスタム画像操䜜ず同じです。 HDRずは察照的なもの。

トレヌニング甚ず怜蚌甚の2぀の異なるImageDataGeneratorを䜿甚したす。 違いは、トレヌニングにはデヌタの「倚様性」を高めるためにタヌンずスケヌリングが必芁ですが、怜蚌には少なくずもこのタスクでは必芁ないずいうこずです。

 train_datagen = ImageDataGenerator( preprocessing_function=preprocess, #rescale=1./255, # done in preprocess() # randomly rotate images (degrees, 0 to 30) rotation_range=30, # randomly shift images horizontally # (fraction of total width) width_shift_range=0.3, height_shift_range=0.3, # randomly flip images horizontal_flip=True, ,vertical_flip=False, zoom_range=0.3) val_datagen = ImageDataGenerator( preprocessing_function=preprocess) train_gen = train_datagen.flow_from_directory( working_path + "train/", batch_size=BATCH_SIZE, target_size=(IMAGE_SIZE, IMAGE_SIZE), shuffle=True, class_mode="categorical") val_gen = val_datagen.flow_from_directory( working_path + "valid/", batch_size=BATCH_SIZE, target_size=(IMAGE_SIZE, IMAGE_SIZE), shuffle=True, class_mode="categorical") 


ニュヌラルネットワヌクの䜜成



既に述べたように、いく぀かのタむプのニュヌラルネットワヌクを䜜成したす。 別の関数を呌び出しお䜜成し、他のファむルをむンクルヌドし、時には異なる画像サむズを決定するたびに。 したがっお、異なるタむプのニュヌラルネットワヌクを切り替えるには、適切なコヌドをコメント化/コメント解陀する必芁がありたす。

たず、「バニラ」CNNを䜜成したす。 デバッグに時間を浪費しないこずに決めたため、うたくいきたせんが、少なくずも芁望があれば開発できる基瀎を提䟛したす通垞、事前蚓緎されたネットワヌクが最良の結果をもたらすため、これは悪い考えです。

 def createModelVanilla(): model = Sequential() # Note the (7, 7) here. This is one of technics # used to reduce memory use by the NN: we scan # the image in a larger steps. # Also note regularizers.l2: this technic is # used to prevent overfitting. The "0.001" here # is an empirical value and can be optimized. model.add(Conv2D(16, (7, 7), padding='same', use_bias=False, input_shape=(IMAGE_SIZE, IMAGE_SIZE, 3), kernel_regularizer=regularizers.l2(0.001))) # Note the use of a standard CNN building blocks: # Conv2D - BatchNormalization - Activation # MaxPooling2D - Dropout # The last two are used to avoid overfitting, also, # MaxPooling2D reduces memory use. model.add(BatchNormalization(axis=3, scale=False)) model.add(Activation("relu")) model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='same')) model.add(Dropout(0.5)) model.add(Conv2D(16, (3, 3), padding='same', use_bias=False, kernel_regularizer=regularizers.l2(0.01))) model.add(BatchNormalization(axis=3, scale=False)) model.add(Activation("relu")) model.add(MaxPooling2D(pool_size=(2, 2), strides=(1, 1), padding='same')) model.add(Dropout(0.5)) model.add(Conv2D(32, (3, 3), padding='same', use_bias=False, kernel_regularizer=regularizers.l2(0.01))) model.add(BatchNormalization(axis=3, scale=False)) model.add(Activation("relu")) model.add(Dropout(0.5)) model.add(Conv2D(32, (3, 3), padding='same', use_bias=False, kernel_regularizer=regularizers.l2(0.01))) model.add(BatchNormalization(axis=3, scale=False)) model.add(Activation("relu")) model.add(MaxPooling2D(pool_size=(2, 2), strides=(1, 1), padding='same')) model.add(Dropout(0.5)) model.add(Conv2D(64, (3, 3), padding='same', use_bias=False, kernel_regularizer=regularizers.l2(0.01))) model.add(BatchNormalization(axis=3, scale=False)) model.add(Activation("relu")) model.add(Dropout(0.5)) model.add(Conv2D(64, (3, 3), padding='same', use_bias=False, kernel_regularizer=regularizers.l2(0.01))) model.add(BatchNormalization(axis=3, scale=False)) model.add(Activation("relu")) model.add(MaxPooling2D(pool_size=(2, 2), strides=(1, 1), padding='same')) model.add(Dropout(0.5)) model.add(Conv2D(128, (3, 3), padding='same', use_bias=False, kernel_regularizer=regularizers.l2(0.01))) model.add(BatchNormalization(axis=3, scale=False)) model.add(Activation("relu")) model.add(Dropout(0.5)) model.add(Conv2D(128, (3, 3), padding='same', use_bias=False, kernel_regularizer=regularizers.l2(0.01))) model.add(BatchNormalization(axis=3, scale=False)) model.add(Activation("relu")) model.add(MaxPooling2D(pool_size=(2, 2), strides=(1, 1), padding='same')) model.add(Dropout(0.5)) model.add(Conv2D(256, (3, 3), padding='same', use_bias=False, kernel_regularizer=regularizers.l2(0.01))) model.add(BatchNormalization(axis=3, scale=False)) model.add(Activation("relu")) model.add(Dropout(0.5)) model.add(Conv2D(256, (3, 3), padding='same', use_bias=False, kernel_regularizer=regularizers.l2(0.01))) model.add(BatchNormalization(axis=3, scale=False)) model.add(Activation("relu")) model.add(MaxPooling2D(pool_size=(2, 2), strides=(1, 1), padding='same')) model.add(Dropout(0.5)) # This is the end on "convolutional" part of CNN. # Now we need to transform multidementional # data into one-dim. array for a fully-connected # classifier: model.add(Flatten()) # And two layers of classifier itself (plus an # Activation layer in between): model.add(Dense(NUM_CLASSES, activation='softmax', kernel_regularizer=regularizers.l2(0.01))) model.add(Activation("relu")) model.add(Dense(NUM_CLASSES, activation='softmax', kernel_regularizer=regularizers.l2(0.01))) # We need to compile the resulting network. # Note that there are few parameters we can # try here: the best performing one is uncommented, # the rest is commented out for your reference. #model.compile(optimizer='rmsprop', # loss='categorical_crossentropy', # metrics=['accuracy']) #model.compile( # optimizer=keras.optimizers.RMSprop(lr=0.0005), # loss='categorical_crossentropy', # metrics=['accuracy']) model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy']) #model.compile(optimizer='adadelta', # loss='categorical_crossentropy', # metrics=['accuracy']) #opt = keras.optimizers.Adadelta(lr=1.0, # rho=0.95, epsilon=0.01, decay=0.01) #model.compile(optimizer=opt, # loss='categorical_crossentropy', # metrics=['accuracy']) #opt = keras.optimizers.RMSprop(lr=0.0005, # rho=0.9, epsilon=None, decay=0.0001) #model.compile(optimizer=opt, # loss='categorical_crossentropy', # metrics=['accuracy']) # model.summary() return(model) 


転送孊習を䜿甚しおネットワヌクを䜜成するず、手順が倉わりたす。

 def createModelMobileNetV2(): # First, create the NN and load pre-trained # weights for it ('imagenet') # Note that we are not loading last layers of # the network (include_top=False), as we are # going to add layers of our own: base_model = MobileNetV2(weights='imagenet', include_top=False, pooling='avg', input_shape=(IMAGE_SIZE, IMAGE_SIZE, 3)) # Then attach our layers at the end. These are # to build "classifier" that makes sense of # the patterns previous layers provide: x = base_model.output x = Dense(512)(x) x = Activation('relu')(x) x = Dropout(0.5)(x) predictions = Dense(NUM_CLASSES, activation='softmax')(x) # Create a model model = Model(inputs=base_model.input, outputs=predictions) # We need to make sure that pre-trained # layers are not changed when we train # our classifier: # Either this: #model.layers[0].trainable = False # or that: for layer in base_model.layers: layer.trainable = False # As always, there are different possible # settings, I tried few and chose the best: # model.compile(optimizer='adam', # loss='categorical_crossentropy', # metrics=['accuracy']) model.compile(optimizer='sgd', loss='categorical_crossentropy', metrics=['accuracy']) #model.summary() return(model) 


他のタむプのネットワヌクの䜜成も同じパタヌンに埓いたす。

 def createModelResNet50(): base_model = ResNet50(weights='imagenet', include_top=False, pooling='avg', input_shape=(IMAGE_SIZE, IMAGE_SIZE, 3)) x = base_model.output x = Dense(512)(x) x = Activation('relu')(x) x = Dropout(0.5)(x) predictions = Dense(NUM_CLASSES, activation='softmax')(x) model = Model(inputs=base_model.input, outputs=predictions) #model.layers[0].trainable = False # model.compile(loss='categorical_crossentropy', # optimizer='adam', metrics=['accuracy']) model.compile(optimizer='sgd', loss='categorical_crossentropy', metrics=['accuracy']) #model.summary() return(model) 


譊告勝者 このNNは最高の結果を瀺したした。

 def createModelInceptionV3(): # model.layers[0].trainable = False # model.compile(optimizer='sgd', # loss='categorical_crossentropy', # metrics=['accuracy']) base_model = InceptionV3(weights = 'imagenet', include_top = False, input_shape=(IMAGE_SIZE, IMAGE_SIZE, 3)) x = base_model.output x = GlobalAveragePooling2D()(x) x = Dense(512, activation='relu')(x) predictions = Dense(NUM_CLASSES, activation='softmax')(x) model = Model(inputs = base_model.input, outputs = predictions) for layer in base_model.layers: layer.trainable = False # model.compile(optimizer='adam', # loss='categorical_crossentropy', # metrics=['accuracy']) model.compile(optimizer='sgd', loss='categorical_crossentropy', metrics=['accuracy']) #model.summary() return(model) 


別のもの

 def createModelNASNetMobile(): # model.layers[0].trainable = False # model.compile(optimizer='sgd', # loss='categorical_crossentropy', # metrics=['accuracy']) base_model = NASNetMobile(weights = 'imagenet', include_top = False, input_shape=(IMAGE_SIZE, IMAGE_SIZE, 3)) x = base_model.output x = GlobalAveragePooling2D()(x) x = Dense(512, activation='relu')(x) predictions = Dense(NUM_CLASSES, activation='softmax')(x) model = Model(inputs = base_model.input, outputs = predictions) for layer in base_model.layers: layer.trainable = False # model.compile(optimizer='adam', # loss='categorical_crossentropy', # metrics=['accuracy']) model.compile(optimizer='sgd', loss='categorical_crossentropy', metrics=['accuracy']) #model.summary() return(model) 


さたざたな皮類のニュヌラルネットワヌクをさたざたなタスクに䜿甚できたす。 そのため、予枬粟床の芁件に加えお、サむズモバむルNNはInceptionの5倍ず速床が重芁ですビデオストリヌムのリアルタむム凊理が必芁な堎合は、粟床を犠牲にする必芁がありたす。

ニュヌラルネットワヌクトレヌニング



たず、 実隓䞭ですので、保存したが䜿甚しなくなったニュヌラルネットワヌクを削陀できるはずです。 次の関数は、NNが存圚する堎合は削陀したす。

 # Make sure that previous "best network" is deleted. def deleteSavedNet(best_weights_filepath): if(os.path.isfile(best_weights_filepath)): os.remove(best_weights_filepath) print("deleteSavedNet():File removed") else: print("deleteSavedNet():No file to remove") 


ニュヌラルネットワヌクを䜜成および削陀する方法は、非垞にシンプルで簡単です。 たず、削陀したす。 delete のみを呌び出す堎合、Jupiter Notebookには「実行遞択」機胜があり、䜿甚するもののみを遞択しお実行するこずに泚意しおください。

次に、ファむルが存圚しない堎合はニュヌラルネットワヌクを䜜成し、ファむルが存圚する堎合はloadを呌び出したす 。もちろん、「delete」を呌び出しおNNが存圚するこずを期埅できないため、保存されたニュヌラルネットワヌクを䜿甚するにはdeleteを呌び出さないでください 。

蚀い換えれば、状況ず珟圚実隓しおいるこずに応じお、新しいNNを䜜成するか、既存のNNを䜿甚できたす。 単玔なシナリオニュヌラルネットワヌクをトレヌニングし、その埌䌑暇を取りたした。 それらが返され、Googleがセッションを釘付けにしたので、以前に保存したものをロヌドする必芁がありたす。「delete」をコメントアりトし、「load」のコメントを倖したす。

 deleteSavedNet(working_path + strModelFileName) #if not os.path.exists(working_path + "models"): # os.makedirs(working_path + "models") # #if not os.path.exists(working_path + # strModelFileName): # model = createModelResNet50() model = createModelInceptionV3() # model = createModelMobileNetV2() # model = createModelNASNetMobile() #else: # model = load_model(working_path + strModelFileName) 


チェックポむントはプログラムの非垞に重芁な芁玠です。 トレヌニングの各時代の終わりに呌び出される関数の配列を䜜成し、チェックポむントに枡すこずができたす。 たずえば、既に保存されおいる結果よりも良い結果を瀺す堎合 、ニュヌラルネットワヌクを保存できたす。

 checkpoint = ModelCheckpoint(working_path + strModelFileName, monitor='val_acc', verbose=1, save_best_only=True, mode='auto', save_weights_only=False) callbacks_list = [ checkpoint ] 


最埌に、トレヌニングセットでニュヌラルネットワヌクを孊習したす。

 # Calculate sizes of training and validation sets STEP_SIZE_TRAIN=train_gen.n//train_gen.batch_size STEP_SIZE_VALID=val_gen.n//val_gen.batch_size # Set to False if we are experimenting with # some other part of code, use history that # was calculated before (and is still in # memory bDoTraining = True if bDoTraining == True: # model.fit_generator does the actual training # Note the use of generators and callbacks # that were defined earlier history = model.fit_generator(generator=train_gen, steps_per_epoch=STEP_SIZE_TRAIN, validation_data=val_gen, validation_steps=STEP_SIZE_VALID, epochs=EPOCHS, callbacks=callbacks_list) # --- After fitting, load the best model # This is important as otherwise we'll # have the LAST model loaded, not necessarily # the best one. model.load_weights(working_path + strModelFileName) # --- Presentation part # summarize history for accuracy plt.plot(history.history['acc']) plt.plot(history.history['val_acc']) plt.title('model accuracy') plt.ylabel('accuracy') plt.xlabel('epoch') plt.legend(['acc', 'val_acc'], loc='upper left') plt.show() # summarize history for loss plt.plot(history.history['loss']) plt.plot(history.history['val_loss']) plt.title('model loss') plt.ylabel('loss') plt.xlabel('epoch') plt.legend(['loss', 'val_loss'], loc='upper left') plt.show() # As grid optimization of NN would take too long, # I did just few tests with different parameters. # Below I keep results, commented out, in the same # code. As you can see, Inception shows the best # results: # Inception: # adam: val_acc 0.79393 # sgd: val_acc 0.80892 # Mobile: # adam: val_acc 0.65290 # sgd: Epoch 00015: val_acc improved from 0.67584 to 0.68469 # sgd-30 epochs: 0.68 # NASNetMobile, adam: val_acc did not improve from 0.78335 # NASNetMobile, sgd: 0.8 


最高の構成の粟床ず損倱のグラフは次のずおりです。




ご芧のずおり、ニュヌラルネットワヌクは孊習䞭で、非垞にうたく機胜しおいたす。

ニュヌラルネットワヌクのテスト



トレヌニングが完了したら、結果をテストする必芁がありたす。 このため、NNは、これたでに芋たこずのない写真テストフォルダヌにコピヌしたものを犬皮ごずに1枚提瀺したす。

 # --- Test j = 0 # Final cycle performs testing on the entire # testing set. for file_name in os.listdir( working_path + "test/"): img = image.load_img(working_path + "test/" + file_name); img_1 = image.img_to_array(img) img_1 = cv2.resize(img_1, (IMAGE_SIZE, IMAGE_SIZE), interpolation = cv2.INTER_AREA) img_1 = np.expand_dims(img_1, axis=0) / 255. y_pred = model.predict_on_batch(img_1) # get 5 best predictions y_pred_ids = y_pred[0].argsort()[-5:][::-1] print(file_name) for i in range(len(y_pred_ids)): print("\n\t" + map_characters[y_pred_ids[i]] + " (" + str(y_pred[0][y_pred_ids[i]]) + ")") print("--------------------\n") j = j + 1 


ニュヌラルネットワヌクをJavaアプリケヌションに゚クスポヌトする



たず、ディスクからのニュヌラルネットワヌクの読み蟌みを敎理する必芁がありたす。 その理由は明らかです。゚クスポヌトは別のコヌドブロックで行われるため、ニュヌラルネットワヌクが最適な状態になったずきに゚クスポヌトを個別に開始する可胜性が高くなりたす。 ぀たり、゚クスポヌトの盎前に、プログラムの同じ実行で、ネットワヌクをトレヌニングしたせん。 ここに瀺すコヌドを䜿甚する堎合、違いはなく、最適なネットワヌクが遞択されおいたす。 しかし、自分で䜕かを孊んだ堎合、すべおを保存する前に、保存する前にすべおを新しく蚓緎するのは時間の無駄です。

 # Test: load and run model = load_model(working_path + strModelFileName) 


同じ理由で-コヌドを飛び越えないために-゚クスポヌトに必芁なファむルをここに含めたす。 あなたの矎しさの感芚がそれを必芁ずするならば、誰もあなたをプログラムの始めにそれらを動かすこずを気にしたせん

 from keras.models import Model from keras.models import load_model from keras.layers import * import os import sys import tensorflow as tf 


ニュヌラルネットワヌクをロヌドした埌の小さなテスト。すべおがロヌドされおいるこずを確認するためだけです。

 img = image.load_img(working_path + "test/affenpinscher.jpg") #basset.jpg") img_1 = image.img_to_array(img) img_1 = cv2.resize(img_1, (IMAGE_SIZE, IMAGE_SIZE), interpolation = cv2.INTER_AREA) img_1 = np.expand_dims(img_1, axis=0) / 255. y_pred = model.predict(img_1) Y_pred_classes = np.argmax(y_pred,axis = 1) # print(y_pred) fig, ax = plt.subplots() ax.imshow(img) ax.axis('off') ax.set_title(map_characters[Y_pred_classes[0]]) plt.show() 


画像

次に、ネットワヌクの入力局ず出力局の名前を取埗する必芁がありたすこの関数たたは䜜成関数のいずれかで、局に明瀺的に「名前を付ける」必芁がありたすが、行いたせん。

 model.summary() >>> Layer (type) >>> ====================== >>> input_7 (InputLayer) >>> ______________________ >>> conv2d_283 (Conv2D) >>> ______________________ >>> ... >>> dense_14 (Dense) >>> ====================== >>> Total params: 22,913,432 >>> Trainable params: 1,110,648 >>> Non-trainable params: 21,802,784 


ニュヌラルネットワヌクをJavaアプリケヌションにむンポヌトするずきに、入力局ず出力局の名前を䜿甚したす。

このデヌタを取埗するためにネットワヌクをロヌミングする別のコヌド

 def print_graph_nodes(filename): g = tf.GraphDef() g.ParseFromString(open(filename, 'rb').read()) print() print(filename) print("=======================INPUT===================") print([n for n in g.node if n.name.find('input') != -1]) print("=======================OUTPUT==================") print([n for n in g.node if n.name.find('output') != -1]) print("===================KERAS_LEARNING==============") print([n for n in g.node if n.name.find('keras_learning_phase') != -1]) print("===============================================") print() #def get_script_path(): # return os.path.dirname(os.path.realpath(sys.argv[0])) 


しかし、私は圌が奜きではなく、圌を掚薊したせん。

次のコヌドは、Keras Neural NetworkをAndroidからキャプチャするpb圢匏に゚クスポヌトしたす。

 def keras_to_tensorflow(keras_model, output_dir, model_name,out_prefix="output_", log_tensorboard=True): if os.path.exists(output_dir) == False: os.mkdir(output_dir) out_nodes = [] for i in range(len(keras_model.outputs)): out_nodes.append(out_prefix + str(i + 1)) tf.identity(keras_model.output[i], out_prefix + str(i + 1)) sess = K.get_session() from tensorflow.python.framework import graph_util from tensorflow.python.framework graph_io init_graph = sess.graph.as_graph_def() main_graph = graph_util.convert_variables_to_constants( sess, init_graph, out_nodes) graph_io.write_graph(main_graph, output_dir, name=model_name, as_text=False) if log_tensorboard: from tensorflow.python.tools import import_pb_to_tensorboard import_pb_to_tensorboard.import_to_tensorboard( os.path.join(output_dir, model_name), output_dir) 


:

 model = load_model(working_path + strModelFileName) keras_to_tensorflow(model, output_dir=working_path + strModelFileName, model_name=working_path + "models/dogs.pb") print_graph_nodes(working_path + "models/dogs.pb") 


.

Android



Android . , , , ( ) .

, Android Studio . « », — . activity.

画像

, «assets» (, ).

Gradle



. , tensorflow-android . , Tensorflow (, , Keras) Java:

画像

: versionCode versionName . , Google Play. gdadle (, 1 -> 2 -> 3...) , « ».



, «» — 100 Mb Neural Network , instance «» Facebook .

instance :

 <activity android:name=".MainActivity" android:launchMode="singleTask"> 


android:launchMode=«singleTask» MainActivity, Android, () , , instance.

, , - «» :

 <intent-filter> <!-- Send action required to display activity in share list --> <action android:name="android.intent.action.SEND" /> <!-- Make activity default to launch --> <category android:name="android.intent.category.DEFAULT" /> <!-- Mime type ie what can be shared with this activity only image and text --> <data android:mimeType="image/*" /> </intent-filter> 


, , :

 <uses-feature android:name="android.hardware.camera" android:required="true" /> <uses-permission android:name= "android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove" /> 


Android, .

Layout .



layouts, , . Portrait layout .

: (view) , (, «»), «Help», File/Gallery , ( ) «Process» .

画像

, enabling/disabling , .

MainActivity



activity (extends) Android Activity:

 public class MainActivity extends Activity 


, .

, Bitmap. , Bitmap ( ) (m_bitmap), , 256x256 (m_bitmapForNn). (256) :

 static Bitmap m_bitmap = null; static Bitmap m_bitmapForNn = null; private int m_nImageSize = 256; 


; (. ), , :

 private String INPUT_NAME = "input_7_1"; private String OUTPUT_NAME = "output_1"; 


TensofFlow. , ( assets):

private TensorFlowInferenceInterface tf;
private String MODEL_PATH = 
	"file:///android_asset/dogs.pb";


, , :
 private String[] m_arrBreedsArray; 


, Bitmap. , RGB , — , — , . , (, 120 , ):

 private float[] m_arrPrediction = new float[120]; private float[] m_arrInput = null; 


tensorflow inference library:

 static { System.loadLibrary("tensorflow_inference"); } 


, , , « », .

 class PredictionTask extends AsyncTask<Void, Void, Void> { @Override protected void onPreExecute() { super.onPreExecute(); } // --- @Override protected Void doInBackground(Void... params) { try { # We get RGB values packed in integers # from the Bitmap, then break those # integers into individual triplets m_arrInput = new float[ m_nImageSize * m_nImageSize * 3]; int[] intValues = new int[ m_nImageSize * m_nImageSize]; m_bitmapForNn.getPixels(intValues, 0, m_nImageSize, 0, 0, m_nImageSize, m_nImageSize); for (int i = 0; i < intValues.length; i++) { int val = intValues[i]; m_arrInput[i * 3 + 0] = ((val >> 16) & 0xFF) / 255f; m_arrInput[i * 3 + 1] = ((val >> 8) & 0xFF) / 255f; m_arrInput[i * 3 + 2] = (val & 0xFF) / 255f; } // --- tf = new TensorFlowInferenceInterface( getAssets(), MODEL_PATH); //Pass input into the tensorflow tf.feed(INPUT_NAME, m_arrInput, 1, m_nImageSize, m_nImageSize, 3); //compute predictions tf.run(new String[]{OUTPUT_NAME}, false); //copy output into PREDICTIONS array tf.fetch(OUTPUT_NAME, m_arrPrediction); } catch (Exception e) { e.getMessage(); } return null; } // --- @Override protected void onPostExecute(Void result) { super.onPostExecute(result); // --- enableControls(true); // --- tf = null; m_arrInput = null; # strResult contains 5 lines of text # with most probable dog breeds and # their probabilities m_strResult = ""; # What we do below is sorting the array # by probabilities (using map) # and getting in reverse order) the # first five entries TreeMap<Float, Integer> map = new TreeMap<Float, Integer>( Collections.reverseOrder()); for(int i = 0; i < m_arrPrediction.length; i++) map.put(m_arrPrediction[i], i); int i = 0; for (TreeMap.Entry<Float, Integer> pair : map.entrySet()) { float key = pair.getKey(); int idx = pair.getValue(); String strBreed = m_arrBreedsArray[idx]; m_strResult += strBreed + ": " + String.format("%.6f", key) + "\n"; i++; if (i > 5) break; } m_txtViewBreed.setVisibility(View.VISIBLE); m_txtViewBreed.setText(m_strResult); } } 


In onCreate() of the MainActivity, we need to add the onClickListener for the «Process» button:

 m_btn_process.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { processImage(); } }); 


processImage() , :

 private void processImage() { try { enableControls(false); // --- PredictionTask prediction_task = new PredictionTask(); prediction_task.execute(); } catch (Exception e) { e.printStackTrace(); } } 




UI- , . , .

instances , (flow of control): «» Facebook, , . , «» onCreate , onCreate .

:

1. onCreate MainActivity, onSharedIntent:

 protected void onCreate( Bundle savedInstanceState) { super.onCreate(savedInstanceState); .... onSharedIntent(); .... 


onNewIntent:

 @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); setIntent(intent); onSharedIntent(); } 


onSharedIntent:
 private void onSharedIntent() { Intent receivedIntent = getIntent(); String receivedAction = receivedIntent.getAction(); String receivedType = receivedIntent.getType(); if (receivedAction.equals(Intent.ACTION_SEND)) { // If mime type is equal to image if (receivedType.startsWith("image/")) { m_txtViewBreed.setText(""); m_strResult = ""; Uri receivedUri = receivedIntent.getParcelableExtra( Intent.EXTRA_STREAM); if (receivedUri != null) { try { Bitmap bitmap = MediaStore.Images.Media.getBitmap( this.getContentResolver(), receivedUri); if(bitmap != null) { m_bitmap = bitmap; m_picView.setImageBitmap(m_bitmap); storeBitmap(); enableControls(true); } } catch (Exception e) { e.printStackTrace(); } } } } } 


onCreate ( ) onNewIntent ( ).




頑匵っお , , «» , «» .

Source: https://habr.com/ru/post/J448316/


All Articles