Введение в машинное обучение прямо в браузере

[Пост также доступен на quaintitative.com]

Мне немного надоело возиться с Tensorflow и Keras в Python, и я решил попробовать применить машинное обучение с помощью tensorflow.js прямо в браузере. Это оказалось немного сложнее, чем я ожидал, в основном из-за необходимости адаптироваться к асинхронному характеру Javascript при загрузке данных. Но это было весело.

Данные и процесс оптимизации, визуализированные в D3 (мой собственный маленький TensorBoard, ура) можно увидеть здесь.

Это не анимация D3, мы фактически тренируем модель прямо в браузере на вашем компьютере! Мы обучаем простую модель линейной регрессии, используя пакетный градиентный спуск, оптимизатор Адама и среднеквадратичную ошибку в качестве функции потерь.

В файле data.js мы -

  • Удаленная загрузка набора данных радужной оболочки (из сути)
  • Разбивка их на заранее определенные размеры пакетов

А в файле regression2.js мы —

  • Настройка необходимых переменных и констант тензорного потока
  • Определение прогноза и функции потерь
  • Обучение модели линейной регрессии с использованием загруженных данных и функций потерь/прогноза
  • Построение графика рассеяния набора данных радужной оболочки
  • График потерь при обучении по мере обучения модели прямо в вашем браузере!
  • После завершения обучения сделайте ряд прогнозов и используйте эти точки для построения линии регрессии.

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

Позвольте мне выделить два ключевых блока кода.

Первый — это код, используемый в data.js для удаленной загрузки данных из gist.

async function fetchData(){
    let response = await fetch(datasource);
    let text = await response.text();

    // Parse the text data into json with d3's function
    let data = await d3.csvParse(text);

    let categoricalTargets = ['setosa', 'versicolor', 'virginica'];

    await data.forEach(d=>{

        // Or do it with a separate categoricalTargets lookup
        categoricalTargets.forEach((c,i)=>{
            if (d.species==c){d.speciesNum=i};
        })
        x.push([+d.sepal_length]);
        y.push(+d.speciesNum/1.0);

    });

    return {data:data, x:x, y:y};
}

Мы используем асинхронность и ожидаем здесь по очень простой причине. Это позволяет нам использовать новую функциональность Promise в Javascript ES6. Что здесь происходит, так это то, что каждая строка здесь с добавленным ожиданием фактически будет ждать завершения строки над ней, прежде чем она запустится.

Это в основном заменяет синтаксис типа function1().then(function2()), к которому мы привыкли, и его намного легче читать и писать.

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

// Setting up the variables for machine learning
const learning_rate = 0.05;
const batch_size = 25;

const A = tf.variable(tf.randomNormal(shape=[1,1]));
const b = tf.variable(tf.randomNormal(shape=[1,1]));

// Alternative way of initialising the two variables
// const A = tf.variable(tf.scalar(0.1));
// const b = tf.variable(tf.scalar(0.1));

const optimizer = tf.train.adam(learning_rate);

let lossArray = [];

function predict(xs){
    return tf.tidy(()=>{
        const ys = xs.mul(A).add(b);
        return ys;
    });
}

function loss(predict, actual){
    return tf.tidy(()=>{
        return predict.sub(tf.cast(actual, 'float32')).square().mean();
    });
}

async function train(numIterations, done){
    
    const d = await fetchData();

    for (let i=0; i<numIterations; i++){
        let cost;
        const [xs,ys] = await batchData(d.x,d.y,50);
        cost = tf.tidy(()=>{
            cost = optimizer.minimize(()=>{
                const pred = predict(xs);
                const predLoss = loss(pred, ys);

                return predLoss;
                
            }, true);
            return cost;
        })

        cost.data().then((data)=>lossArray.push({i:i, error:data[0]}));
        
        if (i%100==0){
            ploterrors(lossArray.slice(i-100,i));
            await cost.data().then((data)=>console.log(i,data));
        }

        await tf.nextFrame();
    }
    done();
    // If we want to check the values of A and b
    // await A.data().then((data)=>console.log(data[0]));
    // await b.data().then((data)=>console.log(data[0]));
}

Если вы знакомы с Tensorflow в Python, это должно быть довольно легко понять. Если нет, взгляните на документацию Tensorflow. Ключевое отличие здесь заключается в том, что мы не используем сеансы, а скорее объявляем прогнозирование, потери и т. д. как функции, которые затем вызываются позже в поезде с данными, которые будут использоваться для обучения.

Мы также добавляем await tf.nextFrame(); здесь. Это в основном гарантирует, что экран браузера продолжает отображаться даже во время обучения.

Также важно понимать, для чего нужен tf.tidy(). Всякий раз, когда мы используем тензор, он занимает место в нашем GPU. Мы можем вызывать dispose после каждого шага, чтобы очистить место, но это было бы очень утомительно. Что делает tf.tidy(), так это то, что помогает нам очистить все промежуточные тензоры.

Есть еще кое-что, что меня озадачивает. В tensorflow.js мы можем выполнять операции с помощью tf.add(a,b) или a.add(b). Но почему-то в рамках функции optimizer.minimize работает только последний. Если кто знает почему, подскажите?

Полный код здесь.

playgrd.com || facebook.com/playgrdstar || instagram.com/playgrdstar/