Было бы здорово, если бы, когда вы идете в зоомагазин, вы знали, что получаете лучший корм для кошек для своего пушистого друга?

В этой статье мы воспользуемся облачными функциями Firebase для размещения модели TensorFlow.js и откроем ее для прогнозов с помощью вызовов API из Android ReactNativeПриложение — все по цене $0.

Начните с Firebase

Зарегистрируйте бесплатную учетную запись Firebase здесь: https://console.firebase.google.com/. Вам нужно будет создать проект, запомните его название.

На вашем компьютере установите firebase глобально:

npm install -g firebase-tools

Войдите в созданный вами аккаунт:

firebase login

Инициализируйте свою firebase, выбрав существующий созданный вами проект с возможностями облачных функций и эмулятором. Используйте следующую команду:

firebase init

Вы будете следовать инструкциям и активировать службы, упомянутые выше. В конце всего процесса у вас будет файл firebase.json, похожий на этот:

{
  "database": {
    "rules": "database.rules.json"
  },
  "functions": [
    {
      "source": "functions",
      "codebase": "default",
      "ignore": [
        "node_modules",
        ".git",
        "firebase-debug.log",
        "firebase-debug.*.log"
      ]
    }
  ],
  "emulators": {
    "functions": {
      "port": 5001
    },
    "ui": {
      "enabled": true
    },
    "singleProjectMode": true
  }
}

Создайте облачную функцию NodeJs

Инструмент firebase cli должен был создать папку functions, перейдите в эту папку и отредактируйте index.js, указав приведенные ниже сведения, чтобы создать сценарий входа:

const { onRequest } = require("firebase-functions/v2/https");
const logger = require("firebase-functions/logger");
const admin = require('firebase-admin');

admin.initializeApp();
const database = admin.database();

let GLOBAL_COUNT = 0;

exports.helloWorld = onRequest(async (request, response) => {
    logger.info("Hello logs!", { structuredData: true });

    // Save telemetry to Firebase Realtime Database
    await database.ref('telemetry').push({
        msg: `Hello #${GLOBAL_COUNT++}`,
        timestamp: Date.now(),
    });

    response.send("Hello from Firebase!");
});

установить все импортированные библиотеки:

npm install express firebase-admin

Эмулятор для проверки функции

Эмуляторы — это лучший способ попробовать сервис и API, не привязываясь к ценам Firebase, и чтобы проверить наш код перед переходом в облако, мы должны протестировать его на эмуляторе:

firebase emulators:start

он напечатает URL-адрес пользовательского интерфейса эмулятора, который вы можете использовать для перехода к:

Перейдите на вкладку функций и получите доступ к указанному URL-адресу. Вы должны увидеть вывод «hello world», и журналы должны начать отображаться в пользовательском интерфейсе:

После того, как все это проверено, давайте развернем на фактическую службу Firebase.

Привет Firebase

Чтобы развернуть наш код, мы вводим следующие команды в командной строке:

firebase deploy

Не беспокойтесь, если вас попросят перейти на тарифный план blaze (в основном из-за облачных функций), с вас ничего не будет взиматься за то, что мы сделаем в этой статье.
Если развертывание прошло успешно, вы должны увидеть это в своем приглашении:

Облачная функция должна быть видна из firebase:

Если вы свернете или используете Postman для показа этого URL-адреса в firebase, вы получите hello world:

Покажите нам несколько моделей!

Настало время использовать науку о данных и дать нашим питомцам преимущество при покупках.

Создадим папку, где будем обрабатывать данные и тестировать модель, мы это делаем для проверки нашего режима перед встраиванием в облачную функцию. CD в ​​папку и запустите npm init для инициализации простой установки пакета.

Отсюда установите все необходимые зависимости TensorFlowJS:

npm install @tensorflow/tfjs @tensorflow/tfjs-node nodeplotlib

В этом эксперименте у нас нет наборов данных, поэтому давайте их синтезируем. Нам нужны данные, описывающие свойства корма для кошек, и мы будем использовать их, чтобы найти лучшую цену для покупки.

Мы будем генерировать синтетические данные с помощью кода ниже:

/**
 * Tensorflow JS Analysis and Model Building.
 */

import * as tf from '@tensorflow/tfjs-node'
import { plot } from 'nodeplotlib';
import Plot from 'nodeplotlib';
const { tidy, tensor2d } = tf;

// Constants
const BRANDS = ['Whiskers', 'Royal Feline', 'Meowarf', 'Unbranded'];
const STORES = ['Fresh Pet', 'Expensive Cats', 'Overpriced Pets', 'Jungle of Money', 'Mom & Pop Petshop'];
const MAX_DS_X = 1000;
const EPOCHS = 30;

/**
 * Generates random cat food data, either as normal or uniform data.
 * 
 * @param numRows The size of the dataset in X
 * @returns 2darray of features.
 */
function generateData(numRows,
    wieghtRangeGrams = { min: 1000.0, max: 10000.0 },
    brands = BRANDS,
    stores = STORES) {

    const brandIndices = tf.randomUniform([numRows], 0, brands.length, 'int32');
    const brandLabels = brandIndices.arraySync().map(index => brands[index]);
    const locationIndices = tf.randomUniform([numRows], 0, stores.length, 'int32');
    const locationLabels = locationIndices.arraySync().map(index => stores[index]);

    const bestBeforeDates = tf.randomUniform([numRows], 0, 365 * 5, 'int32');
    const baseDate = new Date();
    const bestBeforeDatesFormatted = bestBeforeDates.arraySync().map(days => {
        const date = new Date(baseDate);
        date.setDate(baseDate.getDate() + days);
        return date.toISOString().split('T')[0];
    });

    // Generate price values based on weights (with minor variance)
    const weights = tf.randomUniform([numRows], wieghtRangeGrams.min, wieghtRangeGrams.max, 'float32');

    const pricesTemp = weights.div(120);
    const priceMean = tf.mean(pricesTemp).arraySync(); // Mean weight
    const priceStd = tf.moments(pricesTemp).variance.sqrt().arraySync();
    const priceNoise = tf.randomNormal([numRows], priceMean, priceStd, 'float32');
    let prices = tf.tensor1d(pricesTemp.add(priceMean).add(priceNoise).arraySync());

    // Apply logic and transform each number
    prices = tf.tensor1d(prices.dataSync().map((value, index) => {
        const brandLabel = brandLabels[index];
        let newPrice = value;
        switch (brandLabel) {
            case 'Unbranded':
                newPrice *= 0.82;
                break;

            case 'Royal Feline':
                newPrice *= 1.12;
                newPrice += 10;
                break;

            case 'Whiskers and Paws':
                newPrice *= 1.45;
                newPrice += 25;
                break;

            case 'Meowarf':
                newPrice *= 1.60;
                newPrice += 50;
                break;

            default:
                throw new Error(brandLabel);
        }
        return newPrice;
    }));


    const data = {
        weight: weights.arraySync(),
        brand: brandLabels,
        storeLocation: locationLabels,
        bestBeforeDate: bestBeforeDatesFormatted,
        priceUSD: prices.arraySync(),
    };

    return data;
};
...

console.log('Generating Synth Data');
        const catFoodDataset = await generateData(MAX_DS_X);

Используя функцию нормального и равномерного распределения tensorflow, мы добавляем к нашим данным случайность, но нам нужен элемент корреляции между функциями, поэтому мы можем попытаться привязать цену к весу.

После того, как данные созданы, мы можем выполнить некоторые базовые EDA на основе javascript:

/**
 * Does some EDA on the given data.
 * 
 * @param {*} {
 *       weight: aray of floats,
 *       brand: array of label strings,
 *       storeLocation: array of label strings,
 *       bestBeforeDate: array of iso dates,
 *       priceUSD: aray of floats,
 *   }; 
 */
function dataEDA(data) {
    function _countUniqueLabels(labels) {
        return labels.reduce((counts, label) => {
            counts[label] = (counts[label] || 0) + 1;
            return counts;
        }, {});
    }

    const { weight, brand, storeLocation, bestBeforeDate, priceUSD } = data;

    // Summary statistics
    const weightMean = tf.mean(weight);
    const weightStd = tf.moments(weight).variance.sqrt().arraySync();
    const priceMean = tf.mean(priceUSD);
    const priceStd = tf.moments(priceUSD).variance.sqrt().arraySync();

    console.log('Weight Summary:');
    console.log(`Mean: ${weightMean.dataSync()[0].toFixed(2)}`);
    console.log(`Standard Deviation: ${weightStd}`);
    console.log('\nPrice Summary:');
    console.log(`Mean: ${priceMean.dataSync()[0].toFixed(2)}`);
    console.log(`Standard Deviation: ${priceStd}`);

    // Histogram of weights
    const weightData = [{ x: weight, type: 'histogram' }];
    const weightLayout = { title: 'Weight Distribution' };
    plot(weightData, weightLayout);

    // Scatter plot of weight vs. price
    const scatterData = [
        { x: weight, y: priceUSD, mode: 'markers', type: 'scatter' },
    ];
    const scatterLayout = { title: 'Weight vs. Price', xaxis: { title: 'Weight' }, yaxis: { title: 'Price' } };
    plot(scatterData, scatterLayout);

    // Box plot of price
    const priceData = [{ y: priceUSD, type: 'box' }];
    const priceLayout = { title: 'Price Distribution' };
    plot(priceData, priceLayout);

    // Bar chart of a categorical feature
    const brandCounts = _countUniqueLabels(brand);
    const locCounts = _countUniqueLabels(storeLocation);

    const brandLabels = Object.keys(brandCounts);
    const locLabels = Object.keys(locCounts);

    const brandData = brandLabels.map(label => brandCounts[label]);
    const locData = locLabels.map(label => locCounts[label]);

    const brandBar = [{ x: brandLabels, y: brandData, type: 'bar' }];
    const locBar = [{ x: locLabels, y: locData, type: 'bar' }];

    const brandLayout = { title: 'Brand Distribution' };
    const locLayout = { title: 'Location Distribution' };

    plot(locBar, brandLayout);
    plot(brandBar, locLayout);

    // Line chart of price over time (Best before date)
    const priceOverTime = bestBeforeDate.map((date, index) => ({ x: date, y: priceUSD[index] }));
    priceOverTime.sort((a, b) => a.x - b.x); // Sort by date in ascending order
    const lineData = [{ x: priceOverTime.map(entry => entry.x), y: priceOverTime.map(entry => entry.y), type: 'scatter' }];
    const lineLayout = { title: 'Price Over Time', xaxis: { type: 'date' }, yaxis: { title: 'Price' } };
    plot(lineData, lineLayout);
}
...
 await dataEDA(catFoodDataset); // For EDA only.

Эта библиотека nodeplotlib была создана для запуска сервера и визуализации данных, как если бы мы были на ноутбуке:

На приведенных выше графиках срок годности и местоположение магазина не имеют значения и должны быть исключены из функций. Цена, бренд и вес имеют корреляцию.

Создадим тренировочные сплиты:

/**
 * Cleans, nromalizes and drops irrelavant data. Then splits the data into train, validate, test sets.
 * 
 * @param {*} data 
 * @param {*} trainRatio 
 * @param {*} testRatio 
 * @param {*} valRatio 
 * @returns {Object} of: {
 *      trainData: {Tensor},
 *      testData: {Tensor},
 *      validationData: {Tensor}
 *   }
 */
function cleanTrainSpitData(data, trainRatio = 0.7, testRatio = 0.1, valRatio = 0.2) {

    /**
     * local function to noramlize a range, will save the mins and maxs to a global cache to be used in a prediction.
     * 
     * @see MINIMUMS
     * @returns {Array[*]} The normalized range.
     */
    function _normalizeFeature(feature, featureName, metaData = DATASETS_METADATA) {
        const min = tf.min(feature);
        const max = tf.max(feature);
        const normalizedFeature = tf.div(tf.sub(feature, min), tf.sub(max, min));

        // We will need to normalize input data with the same constants.
        metaData[featureName] = { min: min, max: max };

        return normalizedFeature;
    }

    // Remove irrelevant features (date in this case) and NaNs
    const cleanedAndNormalizedData = { weight: [], brandOHE: [], storeOHE: [], priceUSD: [] };

    for (let i = 0; i < data.weight.length; i++) {
        // Handle missing values if needed
        if (!isNaN(data.weight[i]) && !isNaN(data.priceUSD[i]) && (data.brand[i])) {
            cleanedAndNormalizedData.weight.push(data.weight[i]);
            cleanedAndNormalizedData.brandOHE.push(data.brand[i]);
            cleanedAndNormalizedData.priceUSD.push(data.priceUSD[i]);
        }
    }

    // Normalize the Data
    cleanedAndNormalizedData.weight = _normalizeFeature(cleanedAndNormalizedData.weight, 'weight');
    cleanedAndNormalizedData.brandOHE = oneHotEncode(cleanedAndNormalizedData.brandOHE);
    cleanedAndNormalizedData.priceUSD = _normalizeFeature(cleanedAndNormalizedData.priceUSD, 'priceUSD');

    const { weight, brandOHE, storeOHE, priceUSD } = cleanedAndNormalizedData;
    const totalSize = weight.shape[0];
    const trainIndex = Math.floor(trainRatio * totalSize);
    const valSize = Math.floor(valRatio * totalSize);
    const testIndex = trainIndex + valSize;

    const trainData = {
        weight: weight.slice([0], [trainIndex]),
        brandOHE: brandOHE.slice([0], [trainIndex]),
        priceUSD: priceUSD.slice([0], [trainIndex])
    };
    const validationData = {
        weight: weight.slice([trainIndex], [valSize]),
        brandOHE: brandOHE.slice([trainIndex], [valSize]),
        priceUSD: priceUSD.slice([trainIndex], [valSize])
    };
    const testData = {
        weight: weight.slice([testIndex]),
        brandOHE: brandOHE.slice([testIndex]),
        priceUSD: priceUSD.slice([testIndex])
    };

    return {
        trainData: trainData,
        testData: testData,
        validationData: validationData
    };
}
...
console.log('Clean and Split Data');
const datasets = await cleanTrainSpitData(catFoodDataset);

И построить модель:

/**
 * 
 * @param {*} trainData 
 * @param {*} validationData 
 * @param {*} testData 
 * @param {*} numEpochs 
 */
async function buildLinearRegressionModel(trainData, validationData, testData, epochs) {
    const { weight, brandOHE, storeOHE, priceUSD } = trainData;
    const trainX = tf.tensor2d(
        tf.concat([
            tf.tensor2d(weight.arraySync(), [weight.arraySync().length, 1]),
            tf.tensor2d(brandOHE.arraySync())], 1)
            .arraySync());
    const trainY = tf.tensor1d(priceUSD.arraySync());

    console.log('trainX shape:', trainX.shape);
    console.log('trainY shape:', trainY.shape);

    const model = tf.sequential();
    model.add(tf.layers.dense({
        units: trainX.shape[0],
        activation: 'sigmoid',
        inputShape: [trainX.shape[1]]
    }));
    model.add(tf.layers.dense({ units: trainX.shape[0] / 2, activation: 'sigmoid' }));
    model.add(tf.layers.dense({ units: 1, activation: 'linear' }));
    model.compile({
        optimizer: 'adam',
        loss: 'meanSquaredError',
        metrics: ['accuracy']
    });

    const history = await model.fit(trainX, trainY, { validationData: validationData, epochs: epochs });

    console.log("Model trained and fitted!")

    const { weight: testWeight, brandOHE: testBrandOHE, storeOHE: testStoreOHE, priceUSD: testPriceUSD } = testData;

    const testX = tf.tensor2d(
        tf.concat([
            tf.tensor2d(testWeight.arraySync(), [testWeight.arraySync().length, 1]),
            tf.tensor2d(testBrandOHE.arraySync())], 1)
            .arraySync());
    const testY = tf.tensor1d(testPriceUSD.arraySync());

    console.log('testX shape:', testX.shape);
    console.log('testY shape:', testY.shape);

    const testPredictions = await model.predict(testX);

    return {
        model: model,
        predictions: testPredictions,
        trueValues: testY,
        history: history.history
    };
}
...
console.log('Build Model');
const modelMetaData = await buildLinearRegressionModel(datasets.trainData, datasets.validationData, datasets.trainData, EPOCHS);

Завершение с оценкой:

/**
 * 
 * @param {*} model 
 * @param {*} testData 
 */
async function modelMetrics(modelMetaData) {
    const accuracy = tf.metrics.binaryAccuracy(modelMetaData.trueValues, modelMetaData.predictions);
    const error = tf.metrics.meanAbsoluteError(modelMetaData.trueValues, modelMetaData.predictions);

    console.log(`Accuracy: ${accuracy.arraySync()[accuracy.arraySync().length - 1] * 100}%`);
    console.log(`Error: ${error.arraySync()[error.arraySync().length - 1] * 100}%`);

    console.log(`Loss: ${[modelMetaData.history.loss.length - 1]}%`);
}
...
console.log('Get Model Metrics');
await modelMetrics(modelMetaData, datasets.trainData);

Что дает нам следующие результаты:

Ой! не самые лучшие результаты. Тем не менее, мы обучали эту модель на нереалистичных данных. Мусор-в-мусор-выход.

Разогрейте этот API

Когда у вас есть модель на бессерверной установке, всегда полезно прогреть ее, чтобы кэшировать веса слоев.

Мы сделаем это с помощью функции loadModel:

/**
 * Loads meta data and model.
 * 
 * Once loaded, warm up model with sample prediciton.
 */
function loadModel() {
    fs.readFile(`${FUNCTION_MODEL_PATH}/meta.json`, (err, data) => {
        if (err) throw err;

        logger.info(`Model metadata loaded ${data}`);

        DATASETS_METADATA = JSON.parse(data);

        const brand = oneHotEncode([BRANDS[1]], BRANDS, 'brand');
        const wieghtInGrams = tf.tensor1d([5000]);
        const wieght = normalizeFeature(wieghtInGrams, 'weight');

        tf.loadLayersModel(tfn.io.fileSystem(`${FUNCTION_MODEL_PATH}/model.json`))
            .then((loadedModel) => {
                logger.info(`Model loaded ${loadedModel}, predicting sample: `);

                const x = tf.tensor2d(
                    tf.concat([
                        tf.tensor2d(wieght.arraySync(), [wieght.arraySync().length, 1]),
                        tf.tensor2d(brand.arraySync())], 1)
                        .arraySync());
                MODEL = loadedModel;

                return MODEL.predict(x);
            }).then((prediction) => {
                logger.info(`Predicted: '$${prediction}' for a brand: '${BRANDS[1]}' and weight: '${wieghtInGrams}g'`);
            });
    });

}

loadModel();

Мы не должны забыть взять с собой служебные функции, используемые в тестовом скрипте nodeJs для нормализации данных и выполнения одного oneHotEncoding, вместе со всеми метаданными из обученной модели: данные OHE и минимальные/максимальные веса.

В CLI введите emulators:run и перейдите к URL-адресу функции (должен быть что-то вроде: http://127.0.0.1:5001/cloudfunctions-f2309/us-central1/catFoodPredictor).

Предполагая, что эмулятор в порту 4000, если вы заходите на http://127.0.0.1:4000/logs, вы должны увидеть прогноз прогрева:

Теперь мы обслуживаем модель с помощью API:

/**
 * POST only, predicts the price of the catfood item.
 */
exports.catFoodPredictor = onRequest(async (req, res) => {
    if (req.method !== 'POST') {
        return res.status(400).json({ error: 'Invalid request method. Only POST requests are allowed.' });
    }

    const data = req.body;
    logger.info(`Received this: ${JSON.stringify(data)}`);


    await database.ref('telemetry').push({
        data: JSON.stringify(data),
        timestamp: Date.now(),
    });

    logger.info(`Received this: ${data.brand} and ${data.weight}`);

    const brand = oneHotEncode([data.brand], BRANDS, 'brand');
    const weightInGrams = tf.tensor1d([data.weight]);
    const weight = normalizeFeature(weightInGrams, 'weight');

    const x = tf.tensor2d(
        tf.concat([
            tf.tensor2d(weight.arraySync(), [weight.arraySync().length, 1]),
            tf.tensor2d(brand.arraySync())], 1)
            .arraySync());

    try {
        const prediciton = MODEL.predict(x).arraySync()[0];
        res.status(200).json({ prediciton: prediciton });

        logger.info(`Predicted this: ${JSON.stringify(prediciton)}`);
    }
    catch (err) {
        console.error('Error adding data:', error);
        res.status(500).json({ error: 'Something went wrong. Please try again later.' });
    }
});

Используя POSTMAN, протестируйте эмулируемую функцию, и вы должны увидеть следующий результат:

Наконец-то мы можем запустить Firebase! Разверните функции и базу данных аналитики, которую мы настроили, с помощью команды:

firebase deploy

Если мы перейдем к нашей панели инструментов Google Cloud и найдем вкладку журналов, мы увидим, что функция загружена, а модель прогрета:

Теперь последний и настоящий тест почтальона:

Предсказания в кармане

После успешного развертывания модели в firebase нам нужна портативность, чтобы мы могли использовать эту информацию в зоомагазине.

Здесь мы создадим приложение React Native. Мы рекомендуем прочитать нашу предыдущую статью о том, как создавать приложения для Android здесь, в ней описаны различные шаги по установке SDK устройств, настройке Android Studio и повторной регистрации виртуальных устройств, на которых мы можем работать.

Инициализируйте проект:

npx react-native@latest init fairCatApp

CD в ​​только что созданную папку и протестируйте приложение с помощью npm run android (или npm start и выберите Android в Metro), чтобы увидеть страницу приветствия на виртуальном устройстве.

Обратите внимание, что YARN упоминается в другой литературе или в файлах проекта — пряжа похожа на npm.

Установите дополнительные библиотеки для форм ввода и POST-запроса: npm install axios react-native-dropdown-picker.

В приложении мы создадим простую форму с выбором брендов кормов для кошек и числовым счетчиком их веса в граммах. Замените страницу приветствия в App.tsx следующим кодом:

import React, { useState, FC } from 'react';
import {
  View,
  StyleSheet,
  TextInput,
  Text,
  Button,
  Image,
  Alert,
} from 'react-native';
import axios from 'axios';
import DropDownPicker from 'react-native-dropdown-picker';

const App: FC = () => {
  const DEFAULT_WEIGHT = DEFAULT_WEIGHT;
  const BRANDS: Array<Object> = [
    { label: 'Unbranded', value: 'Unbranded' },
    { label: 'Whiskers and Paws', value: 'Whiskers and Paws' },
    { label: 'Royal Feline', value: 'Royal Feline' },
    { label: 'Meowarf', value: 'Meowarf' },
  ];
  const [weight, setWeight] = useState('0');
  const [open, setOpen] = useState(false);
  const [brand, setBrand] = useState(BRANDS[0].value);
  const [brands, setBrands] = useState(BRANDS);
  const [prediction, setPrediction] = useState([]);

  /**
   * Submit Weight and Brand to firebase for a prediction.
   */
  const handleSubmit = () => {
    const data = {
      brand: brand,
      weight: weight,
    };

    axios
      .post(
        'https://catfoodpredictor-2526dyxuva-uc.a.run.app/catFoodPredictor',
        data,
        {
          headers: {
            'Access-Control-Allow-Origin': '*',
            'Content-Type': 'application/json',
          },
        },
      )
      .then(response => response.data)
      .then(result => {
        setPrediction(result);
        Alert.alert(
          `Predicted: $${Number.parseFloat(result?.prediciton).toFixed(2)}`,
        );
      })
      .catch(err => {
        Alert.alert(`Error: ${err}`);
      });
  };
  return (
    <View style={styles.container}>
      <Image style={styles.image} source={require('./img/freeCatLogo.jpg')} />
      <Text style={styles.title}>fair Cat!</Text>

      <View style={styles.container}>
        <Text>Weight in Grams: </Text>
        <TextInput
          style={styles.input}
          label="Weight in Grams"
          value={weight}
          onChangeText={setWeight}
          keyboardType="numeric"
          inputMode="numeric"
        />
        <Text>Select Brand from dropdown: </Text>
        <DropDownPicker
          open={open}
          value={brand}
          items={brands}
          setOpen={setOpen}
          setValue={setBrand}
          setItems={setBrands}
        />
        {prediction?.length > 0 && (
          <Alert
            title="Prediction"
            message={prediction.join(', ')}
            onPress={() => {
              setPrediction([]);
            }}
          />
        )}
      </View>
      <View style={styles.button}>
        <Text>
          Selected Brand: [{brand}] and Quantity: [{weight}]g.
        </Text>
        <View style={{ padding: 10 }} />
        <Text>Submit for Prediction: </Text>
        <Button title="Predict Price" onPress={handleSubmit} />
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16,
    width: 350,
    alignSelf: 'center',
  },
  button: {
    alignSelf: 'center',
  },
  input: {
    width: 320,
    borderColor: '#000',
    borderWidth: 1,
    borderRadius: 10,
    alignSelf: 'center',
    backgroundColor: '#fff',
    color: '#000',
  },
  image: {
    width: 100,
    height: 100,
    alignSelf: 'center',
    marginTop: 16,
  },
  title: {
    fontSize: 20,
    textAlign: 'center',
    marginTop: 8,
  },
});

export default App;

Компонент TextInput предназначен для веса, который будет преобразован в число от 500 до 100 000 граммов, а настраиваемый элемент DropDownPicker — для раскрывающегося списка брендов.
Axios используется для доставки почтового запроса в функцию firebase, и результат будет отображаться в приложении в виде модального диалогового окна с Alert.

Некоторые проблемы, с которыми вы можете столкнуться, будут: утверждение ваших лицензий SDK, запуск Metro, чтобы позволить Android Studio подключить приложение React к вашему устройству, и если вы пытаетесь использовать приложение из виртуальных устройств, библиотека axios может не выполнять никаких запросов.

Если все пойдет хорошо, вот что вы увидите:

Заключение

Мы полностью изучили стек мобильных приложений на базе науки о данных (только для Android) с помощью tensorflowJS.

Мы построили простую нейронную сеть для прогнозирования непрерывного значения, которое является ценой. Это было протестировано на синтетических данных и обучено с помощью простого скрипта nodejs перед развертыванием в нашей базе данных.
Затем мы загрузили облегченное приложение ReactNative, которое вызывает нашу функцию базы данных — и для некоторых из вас — установили приложение на свой компьютер. настоящее устройство.

Теперь у вас есть приложение, которое дает вам справедливую цену на кошачий корм, который вы покупаете. Ваш кошелек и ваша кошка будут намного счастливее в этом году.

Рекомендации

Гитхаб

Статья и исходный код здесь доступны на Github

СМИ

Все используемые медиафайлы (в виде кода или изображений) либо принадлежат исключительно мне, либо приобретены по лицензии, либо являются частью общественного достояния и предоставлены для использования по лицензии Creative Commons.

Лицензирование и использование CC

Эта работа находится под лицензией Creative Commons Attribution-NonCommercial 4.0 International License.

Сделано с ❤ автором Адамом