diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..9568d48
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,17 @@
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "daily"
+ open-pull-requests-limit: 7
+ commit-message:
+ prefix: "[deps-gh]: "
+
+ - package-ecosystem: "docker"
+ directory: "/"
+ schedule:
+ interval: "daily"
+ open-pull-requests-limit: 7
+ commit-message:
+ prefix: "[deps-docker]: "
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
new file mode 100644
index 0000000..81a6a0a
--- /dev/null
+++ b/.github/workflows/docker.yml
@@ -0,0 +1,37 @@
+name: Publish Docker Image
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - "main"
+ tags:
+ - "v*"
+ pull_request:
+ branches:
+ - "main"
+
+jobs:
+ docker:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Docker meta
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: putuwaw/mammates-food-recommendation
+ - name: Login to DockerHub
+ if: github.event_name != 'pull_request'
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ - name: Build and push
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ push: ${{ github.event_name != 'pull_request' }}
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..a45e401
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,8 @@
+FROM tensorflow/serving:2.14.0
+
+ENV MODEL_NAME=food_rec
+ENV TF_CPP_VMODULE=http_server=1
+
+COPY /model /models/${MODEL_NAME}/1/
+
+CMD ["tensorflow_model_server", "--rest_api_port=8504", "--model_name=food_rec", "--model_base_path=/models/food_rec"]
diff --git a/MamMates_Food_Recommendation.ipynb b/MamMates_Food_Recommendation.ipynb
new file mode 100644
index 0000000..544917b
--- /dev/null
+++ b/MamMates_Food_Recommendation.ipynb
@@ -0,0 +1,1279 @@
+{
+ "nbformat": 4,
+ "nbformat_minor": 0,
+ "metadata": {
+ "colab": {
+ "provenance": [],
+ "authorship_tag": "ABX9TyP0JCX49ckXYwZmiLthlXQ1",
+ "include_colab_link": true
+ },
+ "kernelspec": {
+ "name": "python3",
+ "display_name": "Python 3"
+ },
+ "language_info": {
+ "name": "python"
+ }
+ },
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "view-in-github",
+ "colab_type": "text"
+ },
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "YFM6rA9WjU7T"
+ },
+ "outputs": [],
+ "source": [
+ "!pip install -q tensorflow-recommenders"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "import pandas as pd\n",
+ "import numpy as np\n",
+ "import tensorflow as tf\n",
+ "import tensorflow_recommenders as tfrs"
+ ],
+ "metadata": {
+ "id": "C9BSA2Z2jjiy"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "dataset_link = \"https://docs.google.com/spreadsheets/d/1o0f663wcmMfta_PAILOJzkvn09_Mbjw5zBxgcDk1Au0\"\n",
+ "\n",
+ "df_dataset = pd.read_csv(f'{dataset_link}/export?gid=0&format=csv')\n",
+ "df_dataset"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 424
+ },
+ "id": "7kknJDXajsut",
+ "outputId": "7a078d83-b752-48bd-e3f4-85dec11e5333"
+ },
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ " id_user id_food rating\n",
+ "0 63 15 1\n",
+ "1 66 20 2\n",
+ "2 37 1 2\n",
+ "3 39 13 3\n",
+ "4 52 18 1\n",
+ ".. ... ... ...\n",
+ "995 35 17 3\n",
+ "996 79 20 1\n",
+ "997 92 1 1\n",
+ "998 1 14 3\n",
+ "999 29 15 2\n",
+ "\n",
+ "[1000 rows x 3 columns]"
+ ],
+ "text/html": [
+ "\n",
+ "
\n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " id_user | \n",
+ " id_food | \n",
+ " rating | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 | \n",
+ " 63 | \n",
+ " 15 | \n",
+ " 1 | \n",
+ "
\n",
+ " \n",
+ " 1 | \n",
+ " 66 | \n",
+ " 20 | \n",
+ " 2 | \n",
+ "
\n",
+ " \n",
+ " 2 | \n",
+ " 37 | \n",
+ " 1 | \n",
+ " 2 | \n",
+ "
\n",
+ " \n",
+ " 3 | \n",
+ " 39 | \n",
+ " 13 | \n",
+ " 3 | \n",
+ "
\n",
+ " \n",
+ " 4 | \n",
+ " 52 | \n",
+ " 18 | \n",
+ " 1 | \n",
+ "
\n",
+ " \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ "
\n",
+ " \n",
+ " 995 | \n",
+ " 35 | \n",
+ " 17 | \n",
+ " 3 | \n",
+ "
\n",
+ " \n",
+ " 996 | \n",
+ " 79 | \n",
+ " 20 | \n",
+ " 1 | \n",
+ "
\n",
+ " \n",
+ " 997 | \n",
+ " 92 | \n",
+ " 1 | \n",
+ " 1 | \n",
+ "
\n",
+ " \n",
+ " 998 | \n",
+ " 1 | \n",
+ " 14 | \n",
+ " 3 | \n",
+ "
\n",
+ " \n",
+ " 999 | \n",
+ " 29 | \n",
+ " 15 | \n",
+ " 2 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
1000 rows × 3 columns
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 3
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "df_food_info = pd.read_csv(f'{dataset_link}/export?gid=1905501804&format=csv')\n",
+ "df_food_info.head()"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 206
+ },
+ "id": "qbOMD77hsuHF",
+ "outputId": "23bc3cdd-8ffa-4ce7-ae82-f614a82fdb50"
+ },
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ " id_food food_name\n",
+ "0 1 Donat Ubi Mawar\n",
+ "1 2 Donat Ubi Mawar\n",
+ "2 3 Kue Cubit Maniez\n",
+ "3 4 Kue Cubit Maniez\n",
+ "4 5 Kue Lapis Legit"
+ ],
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " id_food | \n",
+ " food_name | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 | \n",
+ " 1 | \n",
+ " Donat Ubi Mawar | \n",
+ "
\n",
+ " \n",
+ " 1 | \n",
+ " 2 | \n",
+ " Donat Ubi Mawar | \n",
+ "
\n",
+ " \n",
+ " 2 | \n",
+ " 3 | \n",
+ " Kue Cubit Maniez | \n",
+ "
\n",
+ " \n",
+ " 3 | \n",
+ " 4 | \n",
+ " Kue Cubit Maniez | \n",
+ "
\n",
+ " \n",
+ " 4 | \n",
+ " 5 | \n",
+ " Kue Lapis Legit | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 4
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "len(df_dataset.id_user.unique())"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "KX6diJUZqX1U",
+ "outputId": "bc0d8e21-8bc4-4ed1-a5ca-c63f15165db8"
+ },
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "100"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 5
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "len(df_dataset.id_food.unique())"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "_kMQeQv4qeXV",
+ "outputId": "e115df20-5317-4344-ae5e-6fb9c5ff7fe7"
+ },
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "20"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 6
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "ratings = tf.data.Dataset.from_tensor_slices(\n",
+ " {\"user_id\": df_dataset.id_user.astype(str),\n",
+ " \"food_id\": df_dataset.id_food.astype(str)}\n",
+ ")"
+ ],
+ "metadata": {
+ "id": "8xw6PBsCp0Ka"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "for x in ratings.take(2).as_numpy_iterator():\n",
+ " print(x)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "g_RTUa3It83W",
+ "outputId": "925b2f7a-d7c8-43c9-8322-64d3f1282fc2"
+ },
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "{'user_id': b'63', 'food_id': b'15'}\n",
+ "{'user_id': b'66', 'food_id': b'20'}\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "foods = tf.data.Dataset.from_tensor_slices(\n",
+ " df_food_info.id_food.astype(str)\n",
+ ")"
+ ],
+ "metadata": {
+ "id": "W9Itp9hsuDT4"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "for x in foods.take(2).as_numpy_iterator():\n",
+ " print(x)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "hGAjf97yuJl2",
+ "outputId": "e95d0796-7740-4cf8-b752-854cdfa560ea"
+ },
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "b'1'\n",
+ "b'2'\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "tf.random.set_seed(42)\n",
+ "shuffled = ratings.shuffle(1000, seed=42, reshuffle_each_iteration=False)\n",
+ "train = shuffled.take(800)\n",
+ "test = shuffled.skip(800).take(200)"
+ ],
+ "metadata": {
+ "id": "Xl6EzRnludse"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "food_ids = foods.batch(32)\n",
+ "user_ids = ratings.batch(32).map(lambda x: x[\"user_id\"])\n",
+ "\n",
+ "unique_food_ids = np.unique(np.concatenate(list(food_ids)))\n",
+ "unique_user_ids = np.unique(np.concatenate(list(user_ids)))\n",
+ "\n",
+ "unique_food_ids[:10]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "dLBIUlGSumyR",
+ "outputId": "8f5cc627-b4cc-4708-927b-765e5b694c1e"
+ },
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "array([b'1', b'10', b'11', b'12', b'13', b'14', b'15', b'16', b'17',\n",
+ " b'18'], dtype=object)"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 12
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "embedding_dimension = 32"
+ ],
+ "metadata": {
+ "id": "wllc3TqVlLRQ"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "user_model = tf.keras.Sequential([\n",
+ " tf.keras.layers.StringLookup(\n",
+ " vocabulary=unique_user_ids, mask_token=None),\n",
+ " tf.keras.layers.Embedding(len(unique_user_ids) + 1, embedding_dimension)\n",
+ "])"
+ ],
+ "metadata": {
+ "id": "oP1EClIRlzaE"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "food_model = tf.keras.Sequential([\n",
+ " tf.keras.layers.StringLookup(\n",
+ " vocabulary=unique_food_ids, mask_token=None),\n",
+ " tf.keras.layers.Embedding(len(unique_food_ids) + 1, embedding_dimension)\n",
+ "])"
+ ],
+ "metadata": {
+ "id": "cMOy7PcSl1mx"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "metrics = tfrs.metrics.FactorizedTopK(\n",
+ " candidates=foods.batch(32).map(food_model)\n",
+ ")"
+ ],
+ "metadata": {
+ "id": "pP5WvcaLl6Z7"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "task = tfrs.tasks.Retrieval(\n",
+ " metrics=metrics\n",
+ ")"
+ ],
+ "metadata": {
+ "id": "86P6Q0oZl78d"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "from typing import Dict, Text\n",
+ "\n",
+ "class MamMatesModel(tfrs.Model):\n",
+ "\n",
+ " def __init__(self, user_model, food_model):\n",
+ " super().__init__()\n",
+ " self.food_model: tf.keras.Model = food_model\n",
+ " self.user_model: tf.keras.Model = user_model\n",
+ " self.task: tf.keras.layers.Layer = task\n",
+ "\n",
+ " def compute_loss(self, features: Dict[Text, tf.Tensor], training=False) -> tf.Tensor:\n",
+ " user_embeddings = self.user_model(features[\"user_id\"])\n",
+ " positive_food_embeddings = self.food_model(features[\"food_id\"])\n",
+ "\n",
+ " return self.task(user_embeddings, positive_food_embeddings)"
+ ],
+ "metadata": {
+ "id": "eyvpoKQKvZib"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "class NoBaseClassMammatesModel(tf.keras.Model):\n",
+ "\n",
+ " def __init__(self, user_model, food_model):\n",
+ " super().__init__()\n",
+ " self.food_model: tf.keras.Model = food_model\n",
+ " self.user_model: tf.keras.Model = user_model\n",
+ " self.task: tf.keras.layers.Layer = task\n",
+ "\n",
+ " def train_step(self, features: Dict[Text, tf.Tensor]) -> tf.Tensor:\n",
+ "\n",
+ " with tf.GradientTape() as tape:\n",
+ "\n",
+ " user_embeddings = self.user_model(features[\"user_id\"])\n",
+ " positive_food_embeddings = self.food_model(features[\"food_id\"])\n",
+ " loss = self.task(user_embeddings, positive_food_embeddings)\n",
+ "\n",
+ " regularization_loss = sum(self.losses)\n",
+ "\n",
+ " total_loss = loss + regularization_loss\n",
+ "\n",
+ " gradients = tape.gradient(total_loss, self.trainable_variables)\n",
+ " self.optimizer.apply_gradients(zip(gradients, self.trainable_variables))\n",
+ "\n",
+ " metrics = {metric.name: metric.result() for metric in self.metrics}\n",
+ " metrics[\"loss\"] = loss\n",
+ " metrics[\"regularization_loss\"] = regularization_loss\n",
+ " metrics[\"total_loss\"] = total_loss\n",
+ "\n",
+ " return metrics\n",
+ "\n",
+ " def test_step(self, features: Dict[Text, tf.Tensor]) -> tf.Tensor:\n",
+ "\n",
+ " user_embeddings = self.user_model(features[\"user_id\"])\n",
+ " positive_food_embeddings = self.food_model(features[\"food_id\"])\n",
+ " loss = self.task(user_embeddings, positive_food_embeddings)\n",
+ "\n",
+ " regularization_loss = sum(self.losses)\n",
+ "\n",
+ " total_loss = loss + regularization_loss\n",
+ "\n",
+ " metrics = {metric.name: metric.result() for metric in self.metrics}\n",
+ " metrics[\"loss\"] = loss\n",
+ " metrics[\"regularization_loss\"] = regularization_loss\n",
+ " metrics[\"total_loss\"] = total_loss\n",
+ "\n",
+ " return metrics"
+ ],
+ "metadata": {
+ "id": "0VojqHclvu1K"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "model = MamMatesModel(user_model, food_model)\n",
+ "model.compile(optimizer=tf.keras.optimizers.Adagrad(learning_rate=0.1))"
+ ],
+ "metadata": {
+ "id": "FfU1Jjgmv6kG"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "cached_train = train.shuffle(1000).batch(32).cache()\n",
+ "cached_test = test.batch(32).cache()"
+ ],
+ "metadata": {
+ "id": "a8QxhPIJwNP3"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "model.fit(cached_train, epochs=3)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "NBy9ts6NwQJx",
+ "outputId": "ead9bd3c-1cae-491e-f206-3bba7de320b1"
+ },
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "Epoch 1/3\n",
+ "25/25 [==============================] - 4s 49ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0000e+00 - factorized_top_k/top_5_categorical_accuracy: 0.2025 - factorized_top_k/top_10_categorical_accuracy: 0.4787 - factorized_top_k/top_50_categorical_accuracy: 1.0000 - factorized_top_k/top_100_categorical_accuracy: 1.0000 - loss: 110.9281 - regularization_loss: 0.0000e+00 - total_loss: 110.9281\n",
+ "Epoch 2/3\n",
+ "25/25 [==============================] - 3s 102ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0012 - factorized_top_k/top_5_categorical_accuracy: 0.3237 - factorized_top_k/top_10_categorical_accuracy: 0.6488 - factorized_top_k/top_50_categorical_accuracy: 1.0000 - factorized_top_k/top_100_categorical_accuracy: 1.0000 - loss: 109.5470 - regularization_loss: 0.0000e+00 - total_loss: 109.5470\n",
+ "Epoch 3/3\n",
+ "25/25 [==============================] - 4s 149ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0088 - factorized_top_k/top_5_categorical_accuracy: 0.4137 - factorized_top_k/top_10_categorical_accuracy: 0.7500 - factorized_top_k/top_50_categorical_accuracy: 1.0000 - factorized_top_k/top_100_categorical_accuracy: 1.0000 - loss: 105.0930 - regularization_loss: 0.0000e+00 - total_loss: 105.0930\n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "execution_count": 22
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "model.evaluate(cached_test, return_dict=True)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "3k7U2ERXwQ_q",
+ "outputId": "ee35229a-5ed3-41c4-9516-40bdbdc7ade8"
+ },
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "7/7 [==============================] - 1s 94ms/step - factorized_top_k/top_1_categorical_accuracy: 0.0250 - factorized_top_k/top_5_categorical_accuracy: 0.2400 - factorized_top_k/top_10_categorical_accuracy: 0.4800 - factorized_top_k/top_50_categorical_accuracy: 1.0000 - factorized_top_k/top_100_categorical_accuracy: 1.0000 - loss: 90.3494 - regularization_loss: 0.0000e+00 - total_loss: 90.3494\n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "{'factorized_top_k/top_1_categorical_accuracy': 0.02500000037252903,\n",
+ " 'factorized_top_k/top_5_categorical_accuracy': 0.23999999463558197,\n",
+ " 'factorized_top_k/top_10_categorical_accuracy': 0.47999998927116394,\n",
+ " 'factorized_top_k/top_50_categorical_accuracy': 1.0,\n",
+ " 'factorized_top_k/top_100_categorical_accuracy': 1.0,\n",
+ " 'loss': 16.14521026611328,\n",
+ " 'regularization_loss': 0,\n",
+ " 'total_loss': 16.14521026611328}"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 23
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "index = tfrs.layers.factorized_top_k.BruteForce(model.user_model)\n",
+ "index.index_from_dataset(\n",
+ " tf.data.Dataset.zip((foods.batch(32), foods.batch(32).map(model.food_model)))\n",
+ ")\n",
+ "\n",
+ "_, titles = index(tf.constant([\"14\"]))\n",
+ "print(f\"Recommendations for user with ID 14: {titles[0, :3]}\")"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "6F3hGUB9wUvW",
+ "outputId": "9e0ba438-b825-4b5b-acb4-455447008c92"
+ },
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "Recommendations for user with ID 14: [b'13' b'14' b'12']\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "list_titles = titles.numpy().astype(int).tolist()\n",
+ "list_titles[0]"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "XXxgMglnwiMR",
+ "outputId": "50ac6086-4244-4c69-d9d7-6cce9bcaaa9e"
+ },
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "[13, 14, 12, 2, 18, 20, 11, 10, 7, 9]"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 25
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "filtered_foods = df_food_info[df_food_info['id_food'].isin(list_titles[0])]\n",
+ "\n",
+ "id_to_food = dict(zip(filtered_foods['id_food'], filtered_foods['food_name']))\n",
+ "\n",
+ "food_names = [id_to_food.get(id) for id in list_titles[0]]\n",
+ "print(f\"Recommendations for user with ID 14: {food_names[:3]}\")"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "-4UbSuDbw9Jz",
+ "outputId": "0bf494a4-ad22-42c8-d32a-e0fd73fd340f"
+ },
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "Recommendations for user with ID 14: ['Roti Bakar Cokelat Keju', 'Roti Bakar Niqmat', 'Donat Ubi Rasa Cinta']\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "import tempfile\n",
+ "import os\n",
+ "\n",
+ "MODEL_DIR = tempfile.gettempdir()\n",
+ "version = 1\n",
+ "export_path = os.path.join(MODEL_DIR, str(version))\n",
+ "print('export_path = {}\\n'.format(export_path))\n",
+ "\n",
+ "tf.saved_model.save(index, export_path)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "pHQo33U_xTHh",
+ "outputId": "c77e8768-b53c-4e57-bbf2-1d9fa7337e0c"
+ },
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "export_path = /tmp/1\n",
+ "\n"
+ ]
+ },
+ {
+ "output_type": "stream",
+ "name": "stderr",
+ "text": [
+ "WARNING:tensorflow:Model's `__init__()` arguments contain non-serializable objects. Please implement a `get_config()` method in the subclassed Model for proper saving and loading. Defaulting to empty config.\n",
+ "WARNING:tensorflow:Model's `__init__()` arguments contain non-serializable objects. Please implement a `get_config()` method in the subclassed Model for proper saving and loading. Defaulting to empty config.\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "!zip -r model.zip /tmp/1/"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "A4Am38VZzEYW",
+ "outputId": "8292736f-8e9e-4422-b46a-5d63cc20c391"
+ },
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "updating: tmp/1/ (stored 0%)\n",
+ "updating: tmp/1/fingerprint.pb (stored 0%)\n",
+ "updating: tmp/1/variables/ (stored 0%)\n",
+ "updating: tmp/1/variables/variables.index (deflated 34%)\n",
+ "updating: tmp/1/variables/variables.data-00000-of-00001 (deflated 15%)\n",
+ "updating: tmp/1/assets/ (stored 0%)\n",
+ "updating: tmp/1/saved_model.pb (deflated 84%)\n"
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/model/fingerprint.pb b/model/fingerprint.pb
new file mode 100644
index 0000000..07b1255
--- /dev/null
+++ b/model/fingerprint.pb
@@ -0,0 +1 @@
+ΊY߭ЊS ߾(ǔܶ2
\ No newline at end of file
diff --git a/model/saved_model.pb b/model/saved_model.pb
new file mode 100644
index 0000000..919373b
Binary files /dev/null and b/model/saved_model.pb differ
diff --git a/model/variables/variables.data-00000-of-00001 b/model/variables/variables.data-00000-of-00001
new file mode 100644
index 0000000..df56942
Binary files /dev/null and b/model/variables/variables.data-00000-of-00001 differ
diff --git a/model/variables/variables.index b/model/variables/variables.index
new file mode 100644
index 0000000..341379c
Binary files /dev/null and b/model/variables/variables.index differ