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, library machine learning yang canggih dan fleksibel untuk JavaScript.

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

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

Kami juga telah membuat glosarium istilah machine learning yang Anda temukan di 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 akan memerlukan:

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

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 Ucapan yang telah dilatih sebelumnya. Tag <div id="console"> akan digunakan untuk menampilkan output model.

4. Membuat prediksi 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 fitur ini juga akan berfungsi di ponsel. Untuk menjalankan halaman web, buka index.html di browser. Jika Anda bekerja dari file lokal, Anda harus memulai server web dan menggunakan http://localhost:port/ untuk mengakses mikrofon.

Untuk memulai server web sederhana di port 8000:

python -m SimpleHTTPServer

Mungkin perlu beberapa waktu untuk mengunduh model, jadi harap bersabar. Segera setelah model dimuat, Anda akan melihat kata di bagian atas halaman. Model ini dilatih untuk mengenali angka 0 sampai 9 dan beberapa perintah tambahan seperti "left", "right", "yes", "no", dll.

Ucapkan salah satu kata tersebut. Apakah itu mengerti kata Anda dengan benar? Coba gunakan probabilityThreshold yang mengontrol seberapa sering model diaktifkan – 0,75 berarti model akan diaktifkan saat lebih dari 75% yakin bahwa model mendengar kata tertentu.

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

6. Mengumpulkan data

Agar lebih menyenangkan, mari gunakan suara pendek, bukan seluruh kata, untuk mengontrol penggeser!

Anda akan melatih model untuk mengenali 3 perintah berbeda: "Left", "Right" dan "Derau" yang akan membuat {i>slider<i} bergerak ke kiri atau ke kanan. Mengenali "Derau" (tidak diperlukan tindakan) sangat penting dalam deteksi ucapan karena kita ingin penggeser hanya bereaksi saat kita menghasilkan suara yang tepat, bukan saat kita berbicara dan bergerak secara umum.

  1. Pertama, kita perlu mengumpulkan data. Tambahkan UI sederhana ke aplikasi dengan menambahkannya 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 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. Hapus predictWord() dari app():
async function app() {
 recognizer = speechCommands.create('BROWSER_FFT');
 await recognizer.ensureModelLoaded();
 // predictWord() no longer called.
}

Menguraikan

Pada awalnya, kode ini mungkin membingungkan, jadi mari kita perinci.

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

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

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));

Dan untuk menghindari masalah numerik, kami menormalisasi data agar memiliki rata-rata 0 dan deviasi standar 1. Dalam hal ini, nilai spektrogram biasanya berupa angka negatif yang besar sekitar -100 dan penyimpangan 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 "Derau" secara berurutan.
  • vals****: 696 angka yang menyimpan informasi frekuensi (spektrogram)

dan kita menyimpan semua data dalam variabel examples:

examples.push({vals, label});

7. Menguji pengumpulan data

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

Untuk memulai server web sederhana di port 8000:

python -m SimpleHTTPServer

Agar dapat mengumpulkan contoh untuk setiap perintah, buat suara yang konsisten berulang kali (atau secara terus-menerus) sambil menekan dan menahan setiap tombol selama 3-4 detik. Anda harus mengumpulkan ~150 contoh untuk setiap label. Misalnya, kita dapat menjepit jari untuk "Kiri", bersiul untuk "Kanan", dan bergantian antara senyap dan berbicara untuk "Derau".

Begitu Anda mengumpulkan lebih banyak contoh, penghitung yang ditampilkan di halaman akan naik. Jangan ragu untuk memeriksa data juga dengan memanggil console.log() pada variabel examples di konsol. Pada titik ini tujuannya adalah untuk menguji proses pengumpulan data. Nantinya, Anda akan mengumpulkan kembali data saat menguji seluruh aplikasi.

8. Melatih model

  1. Tambahkan "Kereta" tombol tepat setelah "Derau" pada isi di 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 memuat ulang aplikasi, Anda akan melihat "Train" baru tombol. Anda dapat menguji pelatihan dengan mengumpulkan kembali data dan mengklik "Latih", atau Anda dapat menunggu hingga langkah 10 untuk menguji pelatihan beserta prediksi.

Memerinci

Pada tingkat tinggi, 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 kumpulan maksimum, lapisan rata, dan lapisan padat yang memetakan 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 ini adalah [NUM_FRAMES, 232, 1] dengan setiap frame berupa audio 23 md yang berisi 232 angka yang sesuai dengan frekuensi berbeda (232 dipilih karena ini adalah jumlah bucket frekuensi yang diperlukan untuk menangkap suara manusia). Dalam codelab ini, kami menggunakan contoh yang panjangnya 3 frame (~contoh 70 md) karena kita membuat suara, bukan mengucapkan seluruh kata, untuk mengontrol penggeser.

Kita mengompilasi model guna mempersiapkannya untuk pelatihan:

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

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

Pelatihan

Pelatihan ini berjalan 10 kali (epoch) pada data menggunakan ukuran batch 16 (memproses 16 contoh sekaligus) dan menunjukkan 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. Mengupdate penggeser secara real time

Setelah dapat melatih model, mari tambahkan kode untuk membuat prediksi secara real-time dan menggerakkan penggeser. Tambahkan ini tepat setelah "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 berikut ini 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
 });
}

Memerinci

Prediksi real-time

listen() mendengarkan mikrofon dan membuat prediksi secara real time. Kode ini sangat mirip dengan metode collect(), yang menormalkan spektrogram mentah dan menghapus semua kecuali frame NUM_FRAMES 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 bentuk [1, numClasses] yang mewakili distribusi probabilitas terhadap jumlah class. Sederhananya, ini hanyalah serangkaian keyakinan untuk setiap kemungkinan class output, yang berjumlah 1. Tensor memiliki dimensi luar 1 karena itu adalah ukuran tumpukan (satu contoh).

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

Memperbarui penggeser

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

Menghapus tensor

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

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

10. Menguji aplikasi final

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

Setelah mengumpulkan contoh, tekan tombol "Train". Tindakan ini akan memulai pelatihan model dan Anda akan melihat akurasi model di atas 90%. Jika Anda tidak mencapai 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/.