Serverless ile Canlıya Çıkmak

5 minute read

Published:


MLZoomcamp‘te bulunan deployment dersinden yararlanarak AWS üzerinde Lambda ve API Gateway servisleri ile köpek ırklarını tahmin eden bir Deep Learning modelini endpoint haline getirdim. keras kullanarak “transfer learning” ile InceptionResNetV2 modeliyle 24 ırka ait görselleri sınıflandırdım. Amaç modeli canlıya almak olduğu için model başarısını göz ardı ettim. Elde edilen modeli tflite ile dönüştürdüm ve kaydettim. Sonrasında Lambda fonksiyonlarını kullanarak bir Rest API oluşturdum. Bu Rest API’ı da API Gateway kullanarak bir endpoint haline getirdim.

Veri Seti

Çalışmada veri seti olarak Kaggle’da bulunan Dog Breed Identification verisini kullandım. Veride 120 ırka ait 10 bin civarında eğitim görseli bulunuyor. 120 sınıf ile modelleme süresi arttığı için hızlandırmak ve basitleştirme adına %20’sine denk gelen ve veride en çok görülen 24 sınıfı seçtim.

Seçilen Irklar
  • afghan_hound
  • airedale
  • australian_terrier
  • basenji
  • beagle
  • bernese_mountain_dog
  • blenheim_spaniel
  • cairn
  • entlebucher
  • great_pyrenees
  • irish_wolfhound
  • japanese_spaniel
  • lakeland_terrier
  • leonberg
  • maltese_dog
  • miniature_pinscher
  • papillon
  • pomeranian
  • saluki
  • samoyed
  • scottish_deerhound
  • shih-tzu
  • tibetan_terrier
  • whippet


Modelleme

Kendi bilgisayarımda GPU üzerinde (GTX 1660 Ti) keras’tan yararlanarak InceptionResNetV2 ile modeli kurdum. 32’lik batch’ler ile 12 epoch yaklaşık 6 dakikada tamamlandı. Her ne kadar model aşırı öğreniyor gibi görünse de yukarıda da belirttiğim gibi amaç iyi bir model kurmak olmadığı için fine-tuning vb. denemelerde bulunmadım.

tflite

Modelin Kaydedilmesi

Modeli kaydetmek ve biraz daha küçültmek için Tensorflow’un mobil ve edge noktalarda da kullanılabilmesi için geliştridiği tflite‘tan yararlandım. tflite modeli sadece çıkarım/tahmin (inference) yapabileceğiniz şekilde küçültüyor. Modelde sadece metadata ve çıkarım için gerekli bilgiler/fonksiyonlar kalıyor. Her ne kadar küçültüyor demiş olsam da bu küçülme dosya boyutuna pek etki etmedi (214 mb’tan 212’ye düştü 😅). Modeli dönüştürmesi neredeyse 5.5 dakika sürdü. Yalnız dönüştürme sırasında RAM ihtiyacı bir hayli artabiliyor, 16GB RAM’i bitirip swap ile de biraz üstüne ekledi dönüşüm sırasında, transfer learning ile 54 milyon parametreye sahip bir model kullanmanın da etkisi vardır diye tahmin ediyorum.

file_name = "inceptionResNetV2_NoTop_{0}.tflite".format(n_class)
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()

with open(file_name, 'wb') as f_out:
    f_out.write(tflite_model)


Modelin Yüklenmesi

Modeli geri yükleme kısmında ise tflite ile işler biraz değişiyor. keras’taki klasik load_model fonksiyonu yerini başka bir yapıya bırakıyor. tflite ile model çağrılıyor ve girdi (input) tensörü için ram’de alan ayırmanız gerekiyor. Sonrasında da modelin giriş ve çıkış katmanlarının indeksini belirtmeniz gerekiyor. Artık model tahmin yapmaya hazır.

import tflite_runtime.interpreter as tflite

interpreter = tflite.Interpreter(model_path=model_path) 
interpreter.allocate_tensors()

input_index = interpreter.get_input_details()[0]["index"]
output_index = interpreter.get_output_details()[0]["index"]

Modelden Tahmin Alınması

Girdi için gerekli dönüşümler/hesaplamalar yapıldıktan sonra tahmin almak için aşağıda bulunan kod bloğundan yararlanıyoruz.

interpreter.set_tensor(input_index, X)
interpreter.invoke()
preds = interpreter.get_tensor(output_index)

AWS

Dockerize Edilmesi & Lambda

Uygulama Lambda üzerinde çalışacağı için bir Docker imajı oluşturdum. TF kullandığı için şu an imajın boyutu bir hayli büyük, sadece tflite kullanan bir imaja çevirerek yüklenme süresini de kısaltmayı planlıyorum. Aşağıda bulunan Dockerfile ile imajı oluşturdum ve imajı ECR‘a gönderdim. Lambda’yı oluştururken de ECR üzerinde bulunan imajı kullandım. Bahsettiğim gibi imajın biraz büyük olması nedeniyle de Lambda’da tetiklenecek olan fonksiyon için memory’i 1024, timeout süresini de 30 saniye olarak değiştirdim.

FROM public.ecr.aws/lambda/python:3.8

RUN pip install tensorflow==2.2.0 numpy==1.19.5 pillow==8.4.0

COPY inceptionResNetV2_NoTop_24.tflite .
COPY lambda_function.py .

CMD [ "lambda_function.lambda_handler" ]

Lambda

Lambda için lambda_function.py script’ini aşağıdaki gibi oluşturdum. Özetlemek gerekirse görsel için gerekli önişlemeleri yapan fonksiyonlar tanımlanıyor, tflite ile model yükleniyor ve son olarak sınıf tahminlerine ait olasılıklar gösteren bir dict elde ediliyor. Lambda için gerekli olan lambda_handler fonksiyonu içerisine görsele ait URL’i alacak şekilde tüm bu işlemleri yapan predict fonksiyonu yazılıyor ve bu şekilde Lambda fonksiyonu oluşturulmuş oluyor.

# lambda_function.py
import tensorflow.lite as tflite
import requests
from urllib.request import urlopen
import io
from PIL import Image
import numpy as np

image_size = 299
model_path = "model.tflite"

def preprocess_input(x):
    x /= 127.5
    x -= 1.
    return x

def process(image_path):
    # try except ile görsel kaynağı (lokal ya da URL) fark etmeksizin görsel okunabiliyor.
    try:
        with urlopen(image_path) as u:
            f = io.BytesIO(u.read())
        img = Image.open(f).resize((299, 299))

    except ValueError:
        with Image.open(image_path) as img:
            img = img.resize((299, 299), Image.NEAREST)

    x = np.array(img, dtype="float32")
    X = np.array([x])
    X = preprocess_input(X)

    return X

interpreter = tflite.Interpreter(model_path=model_path)
interpreter.allocate_tensors()

input_index = interpreter.get_input_details()[0]["index"]
output_index = interpreter.get_output_details()[0]["index"]


classes = ["afghan_hound", "airedale", ...]


def predict(url):
    X = process(url)

    interpreter.set_tensor(input_index, X)
    interpreter.invoke()
    preds = interpreter.get_tensor(output_index)

    float_predictions = preds[0].tolist()

    return dict(zip(classes, float_predictions))


def lambda_handler(event, context):
    url = event["url"]
    result = predict(url)
    return result

API Gateway

Kısaca Docker, Lambda ya da farklı AWS servislerini kullanarak Rest, HTTP veya Websocket API oluşturmanıza imkan veren bir servis. Daha önce hiç kullanmamıştım, bu bahaneyle de aradan çıkarmış oldum. Temel olarak API’a ait metotları (post, get vb.) yönetmenizi sağlıyor. Oluşturduğum endpoint post metodu ile çalışacağı için bir post metodu oluşturuyorum ve kaynak olarak da lambda fonksiyonun adını veriyorum. Son olarak da deploy edeceğim stage’i belirterek endpoint’i deploy ediyorum. Artık endpoint URL’imizi görebiliyoruz.

Endpoint’in Test Edilmesi

import requests

endpoint = "https://flcv8gl5f8.execute-api.eu-central-1.amazonaws.com/staging/predict"

image = 'https://kopekturleri.org/wp-content/uploads/2019/09/Maltese-Lifespan-long.jpg' # maltese

data = {'url': image}

result = requests.post(endpoint, json=data).json()
result


{‘afghan_hound’: 5.527678013095283e-07,
‘airedale’: 8.179575274880335e-07,
‘australian_terrier’: 7.309374723263318e-06,
‘basenji’: 1.9752662865357706e-06,
‘beagle’: 2.229114443252911e-06,
‘bernese_mountain_dog’: 2.028755034189089e-06,
‘blenheim_spaniel’: 1.5408966191898799e-06,
‘cairn’: 1.6157423488039058e-06,
‘entlebucher’: 1.0025323717854917e-06,
‘great_pyrenees’: 8.633060701868089e-07,
‘irish_wolfhound’: 2.15330842934236e-07,
‘japanese_spaniel’: 1.2726724889944308e-05,
‘lakeland_terrier’: 2.096956905006664e-06,
‘leonberg’: 6.628104074479779e-07,
‘maltese_dog’: 0.9999086856842041,
‘miniature_pinscher’: 3.3455692118877778e-06,
‘papillon’: 4.414156137499958e-06,
‘pomeranian’: 1.2062919267918915e-05,
‘saluki’: 3.89771957998164e-06,
‘samoyed’: 4.500319846556522e-06,
‘scottish_deerhound’: 2.4904377369239228e-06,
‘shih-tzu’: 1.628622158023063e-05,
‘tibetan_terrier’: 7.716933396295644e-06,
‘whippet’: 9.684737278803368e-07}


Endpoint: Link


Siz de modeli test etmek isterseniz aşağıdaki trinked interpreter ile deneyebilirsiniz.

Interaktif Python