Я гурман и энтузиаст компьютерного зрения. Имеет смысл только то, что я в конечном итоге сделаю что-то подобное. После поиска в Kaggle интересных наборов данных для реализации того, что я узнал из deeplearning.ai's Tensorflow: Data and Deployment on Coursera, я наткнулся на набор данных Food-101 и вуаля!

Набор данных Food-101, как следует из названия, предполагает набор данных, содержащий 1000 изображений каждое из колоссального 101 блюда от яблочного пирога до вафель (и мне немного стыдно сказать, что я не слышал довольно много из них, несмотря на мою склонность к еде) . Учитывая, что вряд ли я наткнусь на фуа-гра или ранчо huevos, набор данных был сокращен с 101 класса до 20 для простоты.

chicken curry	    fried rice	     samosa
chicken wings	    garlic bread     sandwich
chocolate cake	    hamburger        soup
cup cake	    hot-dog	     spring rolls
donuts	            ice-cream	     sushi
dumplings	    omelette         waffles
french fries	    pizza

С 1000 изображений в каждой из вышеперечисленных категорий данных было слишком мало для обучения точной модели с нуля. На очереди Transfer Learning с использованием слоев из MobileNetV1, предварительно обученных в наборе данных ImageNet.

conv_base = applications.MobileNet(weights = "imagenet", include_top=False, input_shape = (256, 256, 3))

После добавления слоя GlobalMaxPooling2D и Dense архитектура модели выглядела так:

Model: "sequential_8" _________________________________________________________________ Layer (type)                 Output Shape              Param #    ================================================================= mobilenet_1.00_224 (Model)   (None, 8, 8, 1024)        3228864    _________________________________________________________________ global_average_pooling2d_6 ( (None, 1024)              0          _________________________________________________________________ dense_13 (Dense)             (None, 1024)              1049600    _________________________________________________________________ dropout_25 (Dropout)         (None, 1024)              0          _________________________________________________________________ dense_14 (Dense)             (None, 20)                20500      ================================================================= Total params: 4,298,964 Trainable params: 4,277,076 Non-trainable params: 21,888

Чтобы избежать переобучения, к Data Augmentation были применены методы увеличения, включая поворот изображения, линейные сдвиги, масштабирование, изменение освещения и переворачивание.

from keras.preprocessing.image import ImageDataGenerator
train_datagen = ImageDataGenerator(rescale = 1./255, rotation_range=360,width_shift_range=0.2,height_shift_range=0.2, shear_range = 0.2,zoom_range = [0.5, 1.0],brightness_range = [0.2, 1.0],horizontal_flip = True,vertical_flip =False,zca_whitening=True, zca_epsilon=1e-06)
training_set=train_datagen.flow_from_directory(path_training,target_size =(256,256),batch_size=64,class_mode='categorical',shuffle=True)

Для начальных эпох перенесенные слои были заморожены.

for layer in conv_base.layers:
  layer.trainable=False

Первоначальное обучение проводилось с помощью Adam Optimizer со снижающейся скоростью обучения, за которым последовало переключение на SGD в более поздние эпохи. После выхода на плато с потерей валидации перенесенные слои были разморожены, и обучение было возобновлено для дальнейшей точной настройки модели. Была получена точность обучения 91,34% и точность проверки 85,51%.

Тестирование

imn = '/content/test/testimage.jpg' #test image path
img = load_img(imn, target_size=(256, 256))
img = np.asarray(img)
img = img.astype('float32')
img = img/255
img = np.expand_dims(img, axis=0)
img = img.reshape(1,256,256,3)
res = model2.predict(img)
ord = np.argsort(res)
ind = np.argmax(res)
li = ['chicken curry', 'chicken wings', 'ch cake', 'cup cake', 'donuts', 'dumplings', 'fries','fried rice', 'garlic bread', 'hamburger', 'hot-dog', 'ice-cream', 'omelette', 'pizza', 'samosa', 'sandwich','soup','spring rolls', 'sushi', 'waffle']
lis = []
for i in range(0, 5):
  lis.append(li[(ord[0][19 - i])])
print(lis) # Top-5 predictions

Теперь можно сохранить модель keras .h5. Модель занимает около 32,9 МБ места.

Преобразование в TensorFlow lite

image_shape = (256, 256, 3)
def representative_dataset_gen():
  num_calibration_images = 10
  for i in range(num_calibration_images):
    image = tf.random.normal([1] + list(image_shape))
    yield [image]
converter=lite.TFLiteConverter.from_keras_model_file('model.h5')
converter.default_ranges_stats=[0,255]
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset_gen
converter.target_spec.supported_ops =[tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.uint8
converter.inference_output_type = tf.uint8
model = converter.convert()
file = open( 'model.tflite' , 'wb' )
file.write(model)

Полученная модель tflite имеет размер 16,29 МБ, что почти вдвое меньше размера модели h5.

Тестирование с tflite

imn = '/content/test/testimage.jpg'
img = load_img(imn, target_size=(256, 256))
img = np.asarray(img)
img = img.astype('float32')
img = img/255
img = np.expand_dims(img, axis=0)
img = img.reshape(1,256,256,3)
interpreter = lite.Interpreter(model_path="model.tflite")
interpreter.allocate_tensors()
# Get input and output tensors.
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
interpreter.set_tensor(input_details[0]['index'], img)
interpreter.invoke()
output_data = interpreter.get_tensor(output_details[0]['index'])
ord = np.argsort(output_data)
ind = np.argmax(output_data)
lis = []
for i in range(0, 5):
  lis.append(li[(ord[0][19 - i])])
print(lis)

И мы закончили с TFlite!

Android

Клонируйте репозиторий github из здесь.

В каталог ресурсов вашего приложения добавьте модель tflite и файл label.txt с метками. Вот как должен выглядеть текстовый файл:

label 1
label 2
label 3
..
..
..

В файле Kotlin MainAct.kt отредактируйте переменные mInputSize, mModelPath и mLabelPath в соответствии с вашими потребностями. Мы рассматриваем пороговую оценку 0,40, указанную в Classifier.kt, поиграйте для достижения наилучших результатов!

Вот скриншот приложения: