Хабрахабр

[Из песочницы] Определяем спелость арбуза с помощью Keras: полный цикл, от идеи до программы на Google Play

С чего все началось

Все началось с Эппл Маркета — я обнаружил, что у них есть программа, позволяющая определить спелость арбуза. Программа… странная. Чего стоит, хотя бы, предложение постучать по арбузу не костяшками пальцев, а… телефоном! Тем не менее, мне захотелось повторить это достижение на более привычной платформе Андроид.

Выбор инструментов

Задача наша решается несколькими способами, и если честно, мне пришлось приложить немалые усилия, чтобы не пойти «простым» путем. То есть, взять преобразования Фурье, вейвлеты и редактор сигналов. Однако, я хотел получить опыт работы с нейросетями, так что пусть сети и занимаются анализом данных.

Вообще, если вы только начинаете работу с сетями глубокого обучения, лучше инструмента вам не найти. В качестве библиотеки для создания и обучения нейросетей был выбран Керас — гугловская надстройка над TensorFlow и Theano. С другой — всё, что можно «спрятать» от пользователя, там спрятано, так что вам не придется ломать голову над стыковкой слоев нейросети, например. С одной стороны, Керас — мощный инструмент, оптимизированный по скорости, памяти и железу (да, он умеет работать на видеокартах и их кластерах). Очень удобно.

Короче, без Питона в современный Deep Learning соваться не стоит. Как Керас, так и нейросети вообще, требуют знания Питона — этот язык, подобно змее обвил… извините, наболело. К счастью, Питон можно изучить за две недели, в крайнем случае — за месяц.

Потребуется знакомство (весьма поверхностное) с NumPy, PyPlot и возможно, еще с парой библиотек, из которых мы возьмем буквально по паре функций. К Питону вам потребуются еще некоторые библиотеки, но это уже мелочи — я имею в виду, если уж вы справились с самим Питоном. Правда. Не сложно.

Ну и в завершение замечу, что упоминавшиеся выше кластеры видеокарт нам не потребуются — наша задача нормально решается с помощью компьютерного CPU — медленно, но не критично медленно.

План работы

Сначала нужно создать нейросеть — на Питоне и Керасе, под Убунту. Можно — на эмуляторе Убунты. Можно — под Виндоуз, но потраченного дополнительно времени вам хватит, чтобы упомянутую Убунту изучить, и далее работать под ней.

Я планирую сделать это на Java под Андроид. Следующий шаг — написание программы. Это будет прототип программы, в том смысле, что пользовательский интерфейс у нее будет, а вот нейросети пока что нет.

А вот в чем: любая задача, связанная с анализом данных, рано или поздно упирается в поиск данных — для обучения нашей программы. В чем же смысл написания «пустышки», спросите вы. Сотню? В самом деле, сколько арбузов надо обстучать и попробовать на вкус, чтобы нейросеть смогла на этих данных построить достоверную модель? Больше?

Здесь нам и поможет наша программа: заливаем ее на Google Play, раздаем (ладно, навязываем, выкручивая руки) всем друзьям, которым не повезло иметь телефон с Андроидом, и данные, тонюсеньким ручейком, начинают стекаться… а кстати, куда?

Правда, эта серверная программа очень проста, я все закончил минут за двадцать. Следующий шаг — написание серверной программы, принимающей данные от нашего андроид клиента. Но, тем не менее, это отдельный этап.

Обучаем нейросеть. Наконец, данных достаточно.

Портируем нейросеть на Java и выпускаем обновление нашей программы.

Хотя нет. Профит. Только опыт и набитые шишки. Программа была бесплатной.

Создание нейросети

Работа с аудио, каковым, безусловно, является постукивание по арбузу, это либо рекуррентные нейросети, либо так называемая одномерная конволюционная сеть. Причем, в последнее время конволюционные сети однозначно лидируют, вытесняя рекуррентные. Идея конволюционной сети заключается в том, что по массиву данных — графику «интерсивность звука — время» — скользит окошко, и вместо анализа сотен тысяч сэмплов, мы работаем только с тем, что в окошко попадает. Следующие слои объединяют и анализируют результаты работы этого слоя.

Вы сканируете картину — «окошко» вашего внимания движется вдоль воображаемых строк и столбцов, в поисках белой галочки. Чтобы было понятнее, представьте, что вам надо найти на фото морского пейзажа чайку. Именно так работает конволюционная 2D сеть, одномерная же сканирует вдоль одной координаты — оптимальный выбор, если мы имеем дело со звуковым сигналом.

В качестве упражнения, я построил график звука и анализировал полученный битмап как картинку — с помощью 2d конволюционной сети. Отмечу, однако, что зацикливаться на 1D сетях не обязательно. К моему удивлению, результат получился не хуже, чем при анализе «сырых одномерных» данных.

Используемая сеть имела следующую структуру:

model = Sequential()
model.add(Conv1D(filters=32, kernel_size=512, strides=3, padding='valid', use_bias=False, input_shape=(nSampleSize, 1), name='c1d', activation='relu'))
model.add(Activation('relu', input_shape=(nSampleSize, 1)))
model.add(MaxPooling1D(pool_size=(2))) model.add(Conv1D(32, (3)))
model.add(Activation('relu'))
model.add(MaxPooling1D(pool_size=(2))) model.add(Conv1D(64, (3)))
model.add(Activation('relu'))
model.add(MaxPooling1D(pool_size=(2))) model.add(Flatten())
model.add(Dense(64))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(nNumOfOutputs)) #1))
model.add(Activation('sigmoid')) model.compile(loss='mean_squared_error', optimizer='adam', metrics=['accuracy'])

У этой сети два выходных значения (она предсказывает две величины): сладость и спелость. Сладость бывает 0 (несладкий), 1 (нормальный) и 2 (превосходный), а спелость, соответственно, 0 — слишком твердый, 1 — то, что надо, и 2 — перезрелый, как вата с песком.

Задача нейросети — предсказать, какую для данного арбуза (по записи постукивания) оценку поставит человек. Оценки для тестовой выборки выставляются человеком, как именно — мы поговорим в разделе, посвященном программе для Андроид.

Написание программы

Я уже упоминал, что программа должна выйти в виде двух версий. Первая, предварительная, честно предупреждает пользователя, что ее предсказания — полный бред. Зато она позволяет пользователю записать стук по арбузу, выставить оценку вкусовых качеств этого арбуза и переслать по Интернет автору программы. То есть, первая версия просто собирает данные.

Вот страничка программы на Google Play, разумеется, программа бесплатна.

Что она делает:

Нажимаем кнопку с микрофоном и начинается запись. 1. Кнопка с арбузом делает «предсказание», и ее мы пока не трогаем. У вас есть пять секунд, чтобы три раза стукнуть по арбузу — тук-тук-тук.

Примечание — если на Гугле старая версия, то запись и предсказание совмещены в кнопке с арбузом, а кнопки с микрофоном нету.

image

Сохраненный файл — временный, и будет перезаписан при следующем нажатии кнопки записи. 2. Это позволяет повторить постукивание, если кто-то говорит под руку (вы не представляете, как трудно заставить окружающих заткнуться на пять секунд!) или просто шумит вода — звенит посуда — сверлит сосед…

Вы принесли его домой, записали звук и разрезали. Но вот арбуз выбран и куплен. Выбираем вкладку Save. Теперь вы готовы дать оценку его вкусовым качествам.

Выставили оценку — нажали Save. На этой вкладке мы видим два комбобокса для выставления оценок — сладость и спелость (sweetness and ripeness, работа над переводом ведется).

Save можно нажать лишь раз. Внимание! По нажатию кнопки, звуковой файл переименовывается, и теперь он не будет стерт при следующей записи. Так что, сначала выставьте оценку.

image

Наконец, записав (и значит, съев) с десяток арбузов, вы вернулись с дачи, где у вас не было Интернета. 3. Открываем вкладку Submit и нажимаем кнопку. Теперь Интернет есть. Пакет (с десятком арбузов) уходит на сервер разработчика.

image

Написание серверной программы

Тут все просто, так что я лучше выложу полный код этого скрипта. Программа «ловит» файлы, дает им уникальные имена и складывает в директории, доступной только владельцу сайта.

<?php if (is_uploaded_file($_FILES['file']['tmp_name'])) else { echo "File not uploaded successfully."; } ?>

Обучение нейросети

Данные делятся на обучающие и тестовые, 70 и 30 процентов, соответственно. Нейросеть — сходится. Здесь нет никаких неожиданностей, однако, для новичков: не забудьте нормировать входные данные, это съэкономит вам массу нервов. Что-то в таком роде:

for file_name in os.listdir(path): nSweetness, nRipeness, arr_loaded = loadData(file_name) arr_data.append(arr_loaded / max(abs(arr_loaded))) # 2 stands for num. of inputs of a combo box - 1 arr_labels.append([nSweetness / 2.0, nRipeness / 2.0])

Портирование нейросети

Есть несколько способов портировать сеть из питоновского окружения в Java. В последнее время, Гугл сделал этот процесс удобнее, так что, будете читать учебники — убедитесь, что они не устарели. Вот как это делал я:

from keras.models import Model
from keras.models import load_model
from keras.layers import *
import os
import sys import tensorflow as tf # ------------------- 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])) # ------------------- 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, 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(get_script_path() + "/models/model.h5")
#keras_to_tensorflow(model, output_dir=get_script_path() + "/models/model.h5",
# model_name=get_script_path() + "/models/converted.pb") print_graph_nodes(get_script_path() + "/models/converted.pb")

Обратите внимание на последнюю строку: в Java коде вам нужно будет указать имена ввода и вывода сети. Этот «print» как раз их и печатает.

здесь, или здесь, а лучше, здесь) библиотеку tensorflowinferenceinterface, и всё. Итак, кладем в директорию assets проекта в Андроид Студио полученный файл concerted.pb, подключаем (см.

Когда я делал это впервые, я ожидал, что будет трудно, но… заработало с первой попытки. Всё.

Вот как выглядит вызов нейросети из Java кода:

protected Void doInBackground(Void... params) { try { //Pass input into the tensorflow tf.feed(INPUT_NAME, m_arrInput, 1, // batch ? m_arrInput.length, 1); // channels ? //compute predictions tf.run(new String[]{OUTPUT_NAME}); //copy the output into the PREDICTIONS array tf.fetch(OUTPUT_NAME, m_arrPrediction); } catch (Exception e) { e.getMessage(); } return null; }

Здесь m_arrInput — массив с двумя элементами, содержащий — та-да! — наше предсказание, нормированное от нуля до единицы.

Заключение

Здесь, вроде, полагается поблагодарить за внимание, и выразить надежду, что было интересно. Вместо этого, замечу, что на Гугле лежит первая версия программы. Вторая полностью готова, но данных — мало. Так что, если вы любите арбузы — поставьте, пожалуйста, прогу на ваш Андроид. Чем больше данных вы пришлете, тем лучше будет работать вторая версия…

Разумеется, она будет бесплатной.

Надеюсь, было интересно. Удачи, и да: спасибо за внимание.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть