Trénujeme AI: Tensorflow a konvolučné neurónové siete v praxi a appke

tensorflow konvoluční neuronové sítě

Umelá inteligencia má takmer neobmedzené možnosti a využíva sa čím ďalej tým viac. Preto je dôležité sa s ňou zoznámiť. Ukážeme si, ako pomocou konvolučnej neurónovej siete AI vytrénovať. V mobilnej aplikácii nám potom rozpozná eMan kačičku. Tak poďme na to!

Doba nám ukazuje, že umelá inteligencia ponúka mnoho príležitostí takmer v každej oblasti. Asi sa zhodneme na tom, že nám AI zatiaľ primárne pomáha zlepšovať kvalitu života a v mnohých prípadoch aj robiť prácu lepšie. Na dobu, kedy sa umelá inteligencia zmocní ľudstva, si ešte musíme pár rokov počkať, obavy zatiaľ odložme bokom.

V praxi sa umelá inteligencia hodí na väčšinu problémov, ktoré si dokážeme predstaviť. No zvyčajne nachádza využitie tam, kde je veľmi zložité naprogramovať bežný algoritmus, ktorý by daný problém riešil. Napríklad je efektívnejšie vytrénovať model, ktorý bude vyhodnocovať možné podvody v bankových transakciách, ako to explicitne naprogramovať.

Veľké výzvy umelá inteligencia predstavuje napríklad v healthcare oblasti, pre podporu diagnostiky pacientov, systému rozpoznávania snímkov CT, röntgenu a následné vyhodnocovanie diagnózy pacientov. Ale to je iba malé zrnko, je toho omnoho viac. Široké uplatnenie nachádza aj v oblasti automotive, finančných služieb, logistiky, energetiky, výroby atď.

 

Tensorflow

Machine learning framework Tensorflow od Googlu si po vydaní v roku 2015 rýchlo získal veľkú obľubu. Odvtedy sa stal jedným z najznámejších frameworkov v tejto oblasti. Dnes už toho pokrýva fakt veľa, takže si môžeme nie len navrhnúť a vytrénovať model v Python frameworku, ale aj pomocou Tensorflow Lite použiť model v mobilnej aplikácii a IOT, prípadne cez Tensorflow.js púšťať model vo webovom prehliadači.

 

Konvolučná neurónová sieť

Nebudeme zachádzať úplne do detailov, počítam s tým, že vieš niečo o neurónových sieťach a konvolučných neurónových sieťach. Ak nie, tento free kurz ťa vnesie do problematiky. Alebo nám napíš do komentárov a pripravím o tom článok.

V tomto praktickom blog poste si navrhneme jednoduchú konvolučnú neurónovu sieť pomocou frameworku Tensorflow. Ukážeme si, ako zmraziť model, a potom ho použijeme v Android appke pomocou Tensorflow Lite. Konvolučnú neurónovu sieť si vytrénujeme na vlastnom datasete, ten si môžeš upraviť podľa seba. Odkaz na repository nájdeš na konci článku. Pripravili sme si dataset 1.000 obrázkov kačičky a 1.000 obrázkov, kde kačička nie je. Úlohou modelu bude nájsť našu stratenú kačičku!

 

kachna eman neuronové sítě

konvoluční neuronové sítě eman

 

Poznámka: Tensorflow má aj high level API, kde nemusíme ručne definovať matice váh a bi asov. My si však spravíme návrh pomocou low level API, aby sme mali všetko pekne pod kontrolou. V repository nájdeš pre porovnanie aj kompletnú high level implementáciu Vytváranie vrstiev je pomocou high level API veľmi jednoduché:

 

# Convolution layer
conv1 = tf.layers.conv2d(
   inputs=input_layer,
   filters=32,
   kernel_size=[5, 5],
   padding="SAME",
   activation=tf.nn.relu)
# Pooling layer
pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2)

 

Architektúra

 

konvoluční neuronové sítě_eman kachna

 

Pre návrh použijeme tri bežne používané typy vrstiev: konvolučná vrstva, pooling vrstva a plne prepojená vrstva. Vrstvy budeme skladať za seba, tak ako to máme na obrázku. Úlohou prvej časti (séria konvolučných a pooling vrstiev) je extrahovať vlastnosti z obrázka. Druhá časť (klasická neurónová sieť) sa potom na týchto vlastnostiach naučí rozlišovať. Benefit konvolučnej neurónovej siete je, že už od návrhu počítame s tým, že vstupom je obraz. Narozdiel od klasickej plne prepojenej neurónovej siete, kde pracujeme s vektormi.

Vstupný obraz má rozlíšenie 32×32, pričom použijeme iba jeden kanál (obrázok v odtieňoch šedej). V prvej vrstve na vstup aplikujeme 32 konvolučných filtrov o veľkosti 5×5 so striedou 1. Ako aktivačnú funkciu používame naprieč sieťou ReLu, ktorá nám pridáva do modelu nelinearitu, a vďaka svojej jednoduchosti nie je veľmi výpočetne náročná. Ďalšiu pridáme pooling vrstvu, kde downsamplujeme obraz filtrom 2×2 so striedou 2. To nám zredukuje šírku a výšku obrazov 2-krát, ale počet kanálov ostane rovnaký. Takže z tenzoru 32x32x32 sa dostaneme na 16x16x32.

 

input_layer = tf.placeholder(tf.float32, shape=[None, image_size * image_size], name=input_layer_name)
input_image = tf.reshape(input_layer, shape=[-1, image_size, image_size, 1])

# 1 Convolution layer
conv1_w = weight_variable([5, 5, 1, 32])
conv1_b = bias_variable([32])
conv1 = tf.nn.relu(conv2d(input_image, conv1_w) + conv1_b)
pool1 = max_pool_2x2(conv1)

 

Znova pridáme ďalšiu konvolučnú vrstvu so 64 filtrami o veľkosti 5×5 a znova downsamplujeme pooling vrstvou. Tým sa už extrakcia vlastností končí a máme tenzor o veľkosti 8x8x64. Predtým, ako tento tenzor privedieme na vstup plne prepojenej neurónovej vrstvy, ho musime sploštiť do vektoru pomocou operácie reshape.

 

# 2 Convolution layer
conv2_w = weight_variable([5, 5, 32, 64])
conv2_b = bias_variable([64])
conv2 = tf.nn.relu(conv2d(pool1, conv2_w) + conv2_b)
pool2 = max_pool_2x2(conv2)

# Flatten
pool2_flat = tf.reshape(pool2, [-1, 8 * 8 * 64])

 

Na záver už iba pridáme plne prepojenú vrstvu s 1.024 neuronmi a ešte jednu výstupnú vrstvu s dvoma neurónmi, keďže rozlišujeme práve dve classy. Na výstup aplikujeme softmax, čo nám zaistí výstup v rozmedzi <0;1>, takže dostaneme akúsi pravdepodobnosť, že práve vidíme kačiatko alebo niečo iné.

 

# 3 Fully connected layer
full_layer1_w = weight_variable([8 * 8 * 64, 1024])
full_layer1_b = bias_variable([1024])
full_layer1 = tf.nn.relu(tf.matmul(pool2_flat, full_layer1_w) + full_layer1_b)

# 4 Fully connected layer
full_layer2_w = weight_variable([1024, classes])
full_layer2_b = bias_variable([classes])
full_layer2 = tf.matmul(full_layer1, full_layer2_w) + full_layer2_b

# Output
output = tf.nn.softmax(full_layer2, name=output_layer_name)  # softmax output
pred = tf.argmax(output, axis=1)  # predictions

 

Na výstupe siete dostaneme po predložení obrázku triedu, ktorú “vidí”. Avšak bez tréningu budú výsledky úplne náhodné. Preto do grafu pridáme ešte placeholder, ktorý budeme napĺňať počas tréningu očakávaným výsledkom. Túto premennú použijeme do loss funkcie, kde budeme vyjadrovať akúsi “vzdialenosť” predpovede od skutočnosti. A počas tréningu budeme túto loss funkciu minimalizovať pomocou gradient descent algoritmu.

 

# Placeholders used for training
output_true = tf.placeholder(tf.float32, shape=[None, classes])
pred_true = tf.argmax(output_true, axis=1)

# Calculate loss function
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(labels=output_true, logits=full_layer2))
# Configure training operation
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss)

 

Tréning

V prvej časti sme navrhli graf modelu, kde definujeme operácie, ktoré má náš model obsahovať. V tréningu budeme používať tensorflow session, ktorá nám dovolí graf naplniť a vypočítať jeho jednotlivé časti. Implementácia nie je vôbec zložitá, na začiatku máme zopár riadkov boilerplate tensorflow, no potom sa už dostávame k samotnému tréningu. Náš prípad je typický príklad učenia s učiteľom, kedy sieti predkladáme dáta (obraz) a taktiež aj výsledok, ktorý od nej očakávame. Snažíme sa vytvoriť model, ktorý bude našu funkciu čo najvernejšie aproximovať. Tréning ale nebude prebiehat s jedným obrázkom, budeme používať náhodne vybraný batch z datasetu o veľkosti 32. Touto technikou dokážeme optimálnejšie využiť gradient descent optimizér pre minimalizovanie loss funkcie.

 

# Initialize variables (assign default values..)
init = tf.global_variables_initializer()
# Initialize saver
saver = tf.train.Saver()

with tf.Session() as session:
   session.run(init)
   summary_writer = tf.summary.FileWriter(train_dir, graph=tf.get_default_graph())

   for step in range(train_steps+1):
       # Get random batch
       idx = np.random.randint(train_length, size=batch_size)
       batchX = train_data[idx, :]
       batchY = train_labels[idx, :]

       # Run the optimizer
       _, train_loss, train_accuracy = session.run(
           [optimizer, loss, accuracy],
           feed_dict={input_layer: batchX,
                      output_true: batchY}
       )
       # Test training
       if step % logging_step == 0:
           test_loss, test_accuracy = session.run(
               [loss, accuracy],
               feed_dict={input_layer: eval_data,
                          output_true: eval_labels}
           )
           print("Step {0:d}: Loss = {1:.4f}, Accuracy = {2:.3f}".format(step, test_loss, test_accuracy))
       # Save checkpoint
       if step % checkpoint_step == 0:
           saver.save(session, path_current() + "/tmp/model.ckpt", global_step=step)

 

Po spustení celého skriptu by sme mali ideálne vidieť ako sa náš model učí a zvyšuje presnosť svojho odhadu. Počas trénovania parametrov siete si ukladáme checkpointy, tie sa obzvlášť hodia pri riešení zložitých problémov, kedy tréning trvá niekoľko hodín. Kedykoľvek môžeme session z checkpointov obnoviť a pokračovať v tréningu.

Keď budeme s tréningom spokojní, prichádza na rad zmrazenie grafu a vyexportovanie .tflite modelu, ktorý môžeme použiť v produkcii. Načítame si teda session znova z checkpointu a nájdeme v grafe vstup a výstup. V produkčnom modeli nás totiž zaujíma iba cesta zo vstupu na vystup, všetky ostatné operácie budú zahodené. V produkcii tiež budeme vyhodnocovať samostatné obrázky, už žiadne batche, takže tak nastavíme rozmery tenzorov.

 

def freeze(checkpoint_path):
   with tf.Session() as sess:
       # First let's load meta graph and restore weights
       saver = tf.train.import_meta_graph(checkpoint_path + '.meta')
       saver.restore(sess, checkpoint_path)

       # Get the input and output tensors needed for toco
       input_tensor = sess.graph.get_tensor_by_name("input_tensor:0")
       input_tensor.set_shape([1, 1024])
       out_tensor = sess.graph.get_tensor_by_name("softmax_tensor:0")
       out_tensor.set_shape([1, 2])

       # Pass the output tensor and freeze graph
       frozen_graph_def = tf.graph_util.convert_variables_to_constants(
           sess, sess.graph_def, output_node_names=["softmax_tensor"])

   tflite_model = tf.contrib.lite.toco_convert(frozen_graph_def, [input_tensor], [out_tensor])
   open("model.tflite", "wb").write(tflite_model)
   print("Frozen model saved")

 

Tensorflow Lite a Android appka

Vytvoríme jednoduchú Android appku, aby sme si overili, že to, čo sme si navrhli, v praxi aj funguje. Appka bude v reálnom čase brať vstup z kamery a predkladať ho modelu na vyhodnotenie. Kameru sme do appky implementovali pomocou Android camera2 API, tá je mimo rozsah tohto prispevku, ale pozri na ich basic example na githube, ak ťa to zaujíma. Pridáme Tensorflow Lite dependency do build.gradle aplikácie:

 

dependencies {
   implementation 'org.tensorflow:tensorflow-lite:0.1.7'
}

 

Model si pridáme do assetov, no má to jeden háčik. Musíme nastaviť Gradle, aby nám pri buildovaní náš model nekomprimoval. Do build.gradle teda ešte pridáme:

 

android {
   aaptOptions {
       noCompress "tflite"
   }
}

 

Tensorflow Lite ma celkom jednoduché API. Jeho Interpreter vytvoríme takto:

 

val interpreter = Interpreter(loadModelFile())

 

Predtým, než obrázok predložíme Interpretru, môže v závislosti na implementácii byť potrebné ešte predspracovanie dát. My sme si našu sieť navrhli tak, že používa obrázok v odtieňoch šedej, v rozlíšení 32×32, nejakú tú transformáciu teda urobiť musíme. Dáta obrázku po transformacii uložíme do poľa a predložíme Interpretru:

 

private fun runInference(imageData: FloatArray): FloatArray {
   val input = Array(1) { _ -> imageData }
   val output = Array(1) { _ -> FloatArray(numClasses)}

   interpreter?.run(input, output)
   Log.d(TAG, "Inference run: ${Arrays.deepToString(output)}")

   return output[0]
}

 

To je všetko, jednoduché, však? Interpreter preženie dáta obrázku sieťou a vráti nám výstup pravdepodobností. Ako ste si mohli všimnúť, Interpretru nepredkladáme iba jednoduché pole, ale musíme ho vytvoriť dvojrozmerné, inak nám Tensorflow vynadá, že nám nesedia rozmery s tenzormi modelu. Výsledok teda vyzerá takto, našli sme kačičku!

 

konvoluční neuronové sítě eman kachna

 

Android appku sme preleteli trochu z rýchlika, snažil som sa ukázať len to najdôležitejšie a hlavne nezdokumentované chytáky, na ktoré sme u nás pri používaní Tensorflow Lite narazili. Koniec koncov, ak budete Tensorflow Lite implementovať, tak sa pozrite do nášho repository, kde nájdete kompletný zdrojový kód.

 

Záver

Úspešne sme vytvorili konvolučnú neurónovú sieť, ktorá nám rozoznáva predmet z reálneho sveta. Všetky kačičky sme úspešne našli a počas testu nebolo žiadnej z nich ublížené.
Samozrejme, tento konkrétny príklad veľmi veľký prínos okrem zábavy nemá, no s trochou snahy sieť vytrénujeme na iný dataset alebo aj pridáme viacero tried. Môžeme vytvoriť sieť, ktorá napríklad bude triediť výrobky na výrobnej linke, kontrolovať ich kvalitu. Alebo niečo z iného súdku: úspešne vyhodnocovať stupne vývoja embryí v zdravotníctve a ušetriť prácu lekárom. Uplatnení majú neurónové siete veľa, stačí sa poobzerať okolo seba… 🙂

S akými zaujímavými aplikáciami neurónových sietí ste sa stretli v poslednej dobe vy? Dajte nám vedieť do komentárov.

Github demo implementácia modelu

Github demo Android appka

 

Zdroje:

Branislav Štupák
Full Stack Developer

RSS