TensorFlow.js - Pengenalan audio menggunakan pemelajaran transfer

1. Pengantar

Dalam codelab ini, Anda akan membangun jaringan pengenalan audio dan menggunakannya untuk mengontrol penggeser di browser dengan membuat suara. Anda akan menggunakan TensorFlow.js, sebuah library machine learning yang andal dan fleksibel untuk JavaScript.

Pertama, Anda akan memuat dan menjalankan model terlatih yang dapat mengenali 20 perintah ucapan. Kemudian, menggunakan mikrofon, Anda akan membuat dan melatih jaringan neural sederhana yang mengenali suara Anda dan membuat penggeser bergerak ke kiri atau kanan.

Codelab ini tidak akan membahas teori di balik model pengenalan audio. Jika Anda penasaran tentang hal tersebut, lihat tutorial ini.

Kami juga telah membuat glosarium istilah machine learning yang dapat Anda temukan dalam codelab ini.

Yang akan Anda pelajari

  • Cara memuat model pengenalan perintah ucapan terlatih
  • Cara membuat prediksi real-time menggunakan mikrofon
  • Cara melatih dan menggunakan model pengenalan audio kustom menggunakan mikrofon browser

Mari kita mulai.

2. Persyaratan

Untuk menyelesaikan codelab ini, Anda memerlukan:

  1. Chrome versi terbaru atau browser modern lainnya.
  2. Editor teks, yang dijalankan secara lokal di komputer Anda atau di web melalui sesuatu seperti Codepen atau Glitch.
  3. Pengetahuan tentang HTML, CSS, JavaScript, dan Chrome DevTools (atau devtool browser pilihan Anda).
  4. Pemahaman konseptual tingkat tinggi tentang Jaringan Neural. Jika Anda memerlukan informasi pengantar atau penyegaran terkait materi yang akan dipelajari, pertimbangkan untuk menonton video dari 3blue1brown atau video Deep Learning in JavaScript dari Ashi Krishnan ini.

3. Memuat TensorFlow.js dan model Audio

Buka index.html di editor dan tambahkan konten ini:

<html>
  <head>
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/speech-commands"></script>
  </head>
  <body>
    <div id="console"></div>
    <script src="index.js"></script>
  </body>
</html>

Tag <script> pertama mengimpor library TensorFlow.js, dan <script> kedua mengimpor model Perintah Suara yang telah dilatih sebelumnya. Tag <div id="console"> akan digunakan untuk menampilkan output model.

4. Memprediksi secara real time

Selanjutnya, buka/buat file index.js di editor kode, dan sertakan kode berikut:

let recognizer;

function predictWord() {
 // Array of words that the recognizer is trained to recognize.
 const words = recognizer.wordLabels();
 recognizer.listen(({scores}) => {
   // Turn scores into a list of (score,word) pairs.
   scores = Array.from(scores).map((s, i) => ({score: s, word: words[i]}));
   // Find the most probable word.
   scores.sort((s1, s2) => s2.score - s1.score);
   document.querySelector('#console').textContent = scores[0].word;
 }, {probabilityThreshold: 0.75});
}

async function app() {
 recognizer = speechCommands.create('BROWSER_FFT');
 await recognizer.ensureModelLoaded();
 predictWord();
}

app();

5. Menguji prediksi

Pastikan perangkat Anda memiliki mikrofon. Perlu diingat bahwa cara ini juga akan berfungsi di ponsel. Untuk menjalankan halaman web, buka index.html di browser. Jika Anda bekerja dari file lokal, untuk mengakses mikrofon, Anda harus memulai server web dan menggunakan http://localhost:port/.

Untuk memulai server web sederhana di port 8000:

python -m SimpleHTTPServer

Mungkin perlu waktu beberapa saat untuk mendownload model, jadi harap bersabar. Segera setelah model dimuat, Anda akan melihat sebuah kata di bagian atas halaman. Model ini dilatih untuk mengenali angka 0 hingga 9 dan beberapa perintah tambahan seperti "kiri", "kanan", "ya", "tidak", dll.

Ucapkan salah satu kata tersebut. Apakah kata Anda sudah benar? Mainkan probabilityThreshold yang mengontrol seberapa sering model diaktifkan – 0,75 berarti model akan diaktifkan jika lebih dari 75% yakin bahwa model mendengar kata tertentu.

Untuk mempelajari lebih lanjut model Perintah Ucapan dan API-nya, lihat README.md di GitHub.

6. Mengumpulkan data

Agar lebih seru, gunakan suara pendek, bukan seluruh kata, untuk mengontrol penggeser.

Anda akan melatih model untuk mengenali 3 perintah yang berbeda: "Kiri", "Kanan", dan "Bising" yang akan membuat penggeser bergerak ke kiri atau kanan. Mengenali "Kebisingan" (tidak perlu tindakan) sangat penting dalam deteksi ucapan karena kita ingin penggeser bereaksi hanya saat kita menghasilkan suara yang tepat, dan bukan saat kita berbicara dan bergerak di sekitar.

  1. Pertama, kita perlu mengumpulkan data. Tambahkan UI sederhana ke aplikasi dengan menambahkan ini di dalam tag <body> sebelum <div id="console">:
<button id="left" onmousedown="collect(0)" onmouseup="collect(null)">Left</button>
<button id="right" onmousedown="collect(1)" onmouseup="collect(null)">Right</button>
<button id="noise" onmousedown="collect(2)" onmouseup="collect(null)">Noise</button>
  1. Tambahkan kode ini ke index.js:
// One frame is ~23ms of audio.
const NUM_FRAMES = 3;
let examples = [];

function collect(label) {
 if (recognizer.isListening()) {
   return recognizer.stopListening();
 }
 if (label == null) {
   return;
 }
 recognizer.listen(async ({spectrogram: {frameSize, data}}) => {
   let vals = normalize(data.subarray(-frameSize * NUM_FRAMES));
   examples.push({vals, label});
   document.querySelector('#console').textContent =
       `${examples.length} examples collected`;
 }, {
   overlapFactor: 0.999,
   includeSpectrogram: true,
   invokeCallbackOnNoiseAndUnknown: true
 });
}

function normalize(x) {
 const mean = -100;
 const std = 10;
 return x.map(x => (x - mean) / std);
}
  1. Menghapus predictWord() dari app():
async function app() {
 recognizer = speechCommands.create('BROWSER_FFT');
 await recognizer.ensureModelLoaded();
 // predictWord() no longer called.
}

Perinciannya

Kode ini mungkin terasa berat pada awalnya, jadi mari kita perinci.

Kita telah menambahkan tiga tombol ke UI yang diberi label "Kiri", "Kanan", dan "Noise", yang sesuai dengan tiga perintah yang ingin kita kenali oleh model. Menekan tombol ini akan memanggil fungsi collect() yang baru ditambahkan, yang membuat contoh pelatihan untuk model kita.

collect() mengaitkan label dengan output recognizer.listen(). Karena includeSpectrogram benar, recognizer.listen() memberikan spektrogram mentah (data frekuensi) untuk audio 1 detik, yang dibagi menjadi 43 frame, sehingga setiap frame adalah audio ~23 md:

recognizer.listen(async ({spectrogram: {frameSize, data}}) => {
...
}, {includeSpectrogram: true});

Karena kita ingin menggunakan suara pendek, bukan kata-kata, untuk mengontrol penggeser, kita hanya mempertimbangkan 3 frame terakhir (~70 md):

let vals = normalize(data.subarray(-frameSize * NUM_FRAMES));

Untuk menghindari masalah numerik, kita menormalisasi data agar memiliki rata-rata 0 dan simpangan baku 1. Dalam hal ini, nilai spektrogram biasanya berupa angka negatif besar sekitar -100 dan deviasi 10:

const mean = -100;
const std = 10;
return x.map(x => (x - mean) / std);

Terakhir, setiap contoh pelatihan akan memiliki 2 kolom:

  • label****: 0, 1, dan 2 untuk "Kiri", "Kanan", dan "Bising".
  • vals****: 696 angka yang menyimpan informasi frekuensi (spektrogram)

dan kita menyimpan semua data dalam variabel examples:

examples.push({vals, label});

7. Pengumpulan data pengujian

Buka index.html di browser, dan Anda akan melihat 3 tombol yang sesuai dengan 3 perintah. Jika Anda bekerja dari file lokal, untuk mengakses mikrofon, Anda harus memulai server web dan menggunakan http://localhost:port/.

Untuk memulai server web sederhana di port 8000:

python -m SimpleHTTPServer

Untuk mengumpulkan contoh setiap perintah, buat suara yang konsisten berulang kali (atau terus-menerus) sambil menekan dan menahan setiap tombol selama 3-4 detik. Anda harus mengumpulkan sekitar 150 contoh untuk setiap label. Misalnya, kita dapat menjentikkan jari untuk "Kiri", bersiul untuk "Kanan", dan bergantian antara diam dan berbicara untuk "Kebisingan".

Saat Anda mengumpulkan lebih banyak contoh, penghitung yang ditampilkan di halaman akan bertambah. Anda juga dapat memeriksa data dengan memanggil console.log() pada variabel examples di konsol. Pada tahap ini, tujuannya adalah untuk menguji proses pengumpulan data. Nanti Anda akan mengumpulkan ulang data saat menguji seluruh aplikasi.

8. Melatih model

  1. Tambahkan tombol "Train" tepat setelah tombol "Noise" di bagian isi index.html:
<br/><br/>
<button id="train" onclick="train()">Train</button>
  1. Tambahkan kode berikut ke kode yang ada di index.js:
const INPUT_SHAPE = [NUM_FRAMES, 232, 1];
let model;

async function train() {
 toggleButtons(false);
 const ys = tf.oneHot(examples.map(e => e.label), 3);
 const xsShape = [examples.length, ...INPUT_SHAPE];
 const xs = tf.tensor(flatten(examples.map(e => e.vals)), xsShape);

 await model.fit(xs, ys, {
   batchSize: 16,
   epochs: 10,
   callbacks: {
     onEpochEnd: (epoch, logs) => {
       document.querySelector('#console').textContent =
           `Accuracy: ${(logs.acc * 100).toFixed(1)}% Epoch: ${epoch + 1}`;
     }
   }
 });
 tf.dispose([xs, ys]);
 toggleButtons(true);
}

function buildModel() {
 model = tf.sequential();
 model.add(tf.layers.depthwiseConv2d({
   depthMultiplier: 8,
   kernelSize: [NUM_FRAMES, 3],
   activation: 'relu',
   inputShape: INPUT_SHAPE
 }));
 model.add(tf.layers.maxPooling2d({poolSize: [1, 2], strides: [2, 2]}));
 model.add(tf.layers.flatten());
 model.add(tf.layers.dense({units: 3, activation: 'softmax'}));
 const optimizer = tf.train.adam(0.01);
 model.compile({
   optimizer,
   loss: 'categoricalCrossentropy',
   metrics: ['accuracy']
 });
}

function toggleButtons(enable) {
 document.querySelectorAll('button').forEach(b => b.disabled = !enable);
}

function flatten(tensors) {
 const size = tensors[0].length;
 const result = new Float32Array(tensors.length * size);
 tensors.forEach((arr, i) => result.set(arr, i * size));
 return result;
}
  1. Panggil buildModel() saat aplikasi dimuat:
async function app() {
 recognizer = speechCommands.create('BROWSER_FFT');
 await recognizer.ensureModelLoaded();
 // Add this line.
 buildModel();
}

Pada tahap ini, jika Anda memuat ulang aplikasi, Anda akan melihat tombol "Train" baru. Anda dapat menguji pelatihan dengan mengumpulkan ulang data dan mengklik "Latih", atau Anda dapat menunggu hingga langkah 10 untuk menguji pelatihan bersama dengan prediksi.

Menguraikannya

Secara umum, kita melakukan dua hal: buildModel() menentukan arsitektur model dan train() melatih model menggunakan data yang dikumpulkan.

Arsitektur model

Model ini memiliki 4 lapisan: lapisan konvolusional yang memproses data audio (direpresentasikan sebagai spektrogram), lapisan max pool, lapisan flatten, dan lapisan padat yang dipetakan ke 3 tindakan:

model = tf.sequential();
 model.add(tf.layers.depthwiseConv2d({
   depthMultiplier: 8,
   kernelSize: [NUM_FRAMES, 3],
   activation: 'relu',
   inputShape: INPUT_SHAPE
 }));
 model.add(tf.layers.maxPooling2d({poolSize: [1, 2], strides: [2, 2]}));
 model.add(tf.layers.flatten());
 model.add(tf.layers.dense({units: 3, activation: 'softmax'}));

Bentuk input model adalah [NUM_FRAMES, 232, 1] dengan setiap frame adalah audio 23 md yang berisi 232 angka yang sesuai dengan frekuensi yang berbeda (232 dipilih karena merupakan jumlah bucket frekuensi yang diperlukan untuk merekam suara manusia). Dalam codelab ini, kita menggunakan sampel yang panjangnya 3 frame (~sampel 70 md) karena kita membuat suara, bukan mengucapkan seluruh kata untuk mengontrol penggeser.

Kita mengompilasi model untuk mempersiapkannya menjalani pelatihan:

const optimizer = tf.train.adam(0.01);
 model.compile({
   optimizer,
   loss: 'categoricalCrossentropy',
   metrics: ['accuracy']
 });

Kita menggunakan pengoptimal Adam, pengoptimal umum yang digunakan dalam deep learning, dan categoricalCrossEntropy untuk kerugian, fungsi kerugian standar yang digunakan untuk klasifikasi. Singkatnya, metrik ini mengukur seberapa jauh probabilitas yang diprediksi (satu probabilitas per class) dari probabilitas 100% di class yang benar, dan probabilitas 0% untuk semua class lainnya. Kami juga menyediakan accuracy sebagai metrik yang harus dipantau, yang akan memberi kita persentase contoh yang diklasifikasikan dengan benar oleh model setelah setiap iterasi pelatihan.

Pelatihan

Pelatihan dilakukan 10 kali (epoch) pada data menggunakan ukuran batch 16 (memproses 16 contoh sekaligus) dan menampilkan akurasi saat ini di UI:

await model.fit(xs, ys, {
   batchSize: 16,
   epochs: 10,
   callbacks: {
     onEpochEnd: (epoch, logs) => {
       document.querySelector('#console').textContent =
           `Accuracy: ${(logs.acc * 100).toFixed(1)}% Epoch: ${epoch + 1}`;
     }
   }
 });

9. Memperbarui penggeser secara real-time

Setelah dapat melatih model, mari tambahkan kode untuk membuat prediksi secara real-time dan memindahkan penggeser. Tambahkan tepat setelah tombol "Train" di index.html:

<br/><br/>
<button id="listen" onclick="listen()">Listen</button>
<input type="range" id="output" min="0" max="10" step="0.1">

Dan kode berikut di index.js:

async function moveSlider(labelTensor) {
 const label = (await labelTensor.data())[0];
 document.getElementById('console').textContent = label;
 if (label == 2) {
   return;
 }
 let delta = 0.1;
 const prevValue = +document.getElementById('output').value;
 document.getElementById('output').value =
     prevValue + (label === 0 ? -delta : delta);
}

function listen() {
 if (recognizer.isListening()) {
   recognizer.stopListening();
   toggleButtons(true);
   document.getElementById('listen').textContent = 'Listen';
   return;
 }
 toggleButtons(false);
 document.getElementById('listen').textContent = 'Stop';
 document.getElementById('listen').disabled = false;

 recognizer.listen(async ({spectrogram: {frameSize, data}}) => {
   const vals = normalize(data.subarray(-frameSize * NUM_FRAMES));
   const input = tf.tensor(vals, [1, ...INPUT_SHAPE]);
   const probs = model.predict(input);
   const predLabel = probs.argMax(1);
   await moveSlider(predLabel);
   tf.dispose([input, probs, predLabel]);
 }, {
   overlapFactor: 0.999,
   includeSpectrogram: true,
   invokeCallbackOnNoiseAndUnknown: true
 });
}

Menguraikannya

Prediksi real-time

listen() mendengarkan mikrofon dan membuat prediksi real time. Kodenya sangat mirip dengan metode collect(), yang menormalisasi spektrogram mentah dan menghilangkan semua frame kecuali NUM_FRAMES frame terakhir. Satu-satunya perbedaan adalah kita juga memanggil model terlatih untuk mendapatkan prediksi:

const probs = model.predict(input);
const predLabel = probs.argMax(1);
await moveSlider(predLabel);

Output model.predict(input) adalah Tensor berbentuk [1, numClasses] yang merepresentasikan distribusi probabilitas di seluruh jumlah kelas. Lebih sederhananya, ini hanyalah serangkaian keyakinan untuk setiap kemungkinan kelas output yang berjumlah 1. Tensor memiliki dimensi luar 1 karena itulah ukuran batch (satu contoh).

Untuk mengonversi distribusi probabilitas menjadi bilangan bulat tunggal yang merepresentasikan class yang paling mungkin, kita memanggil probs.argMax(1) yang menampilkan indeks class dengan probabilitas tertinggi. Kita meneruskan "1" sebagai parameter sumbu karena ingin menghitung argMax di dimensi terakhir, numClasses.

Memperbarui penggeser

moveSlider() mengurangi nilai penggeser jika labelnya 0 ("Kiri"), menambahkannya jika labelnya 1 ("Kanan"), dan mengabaikannya jika labelnya 2 ("Derau").

Melepaskan tensor

Untuk membersihkan memori GPU, kita harus memanggil tf.dispose() secara manual pada Tensor output. Alternatif untuk tf.dispose() manual adalah membungkus panggilan fungsi dalam tf.tidy(), tetapi ini tidak dapat digunakan dengan fungsi asinkron.

   tf.dispose([input, probs, predLabel]);

10. Menguji aplikasi akhir

Buka index.html di browser Anda dan kumpulkan data seperti yang Anda lakukan di bagian sebelumnya dengan 3 tombol yang sesuai dengan 3 perintah. Ingatlah untuk menekan dan menahan setiap tombol selama 3-4 detik saat mengumpulkan data.

Setelah Anda mengumpulkan contoh, tekan tombol "Latih". Tindakan ini akan memulai pelatihan model dan Anda akan melihat akurasi model di atas 90%. Jika Anda tidak mendapatkan performa model yang baik, coba kumpulkan lebih banyak data.

Setelah pelatihan selesai, tekan tombol "Dengarkan" untuk membuat prediksi dari mikrofon dan mengontrol penggeser.

Lihat tutorial lainnya di http://js.tensorflow.org/.