TensorFlow.js - Aktarım öğrenmeyi kullanarak ses tanıma

1. Giriş

Bu codelab'de bir ses tanıma ağı oluşturacak ve bu ağı kullanarak ses çıkartarak tarayıcıdaki bir kaydırma çubuğunu kontrol edeceksiniz. JavaScript için güçlü ve esnek bir makine öğrenimi kitaplığı olan TensorFlow.js'yi kullanacaksınız.

Öncelikle, 20 konuşma komutunu tanıyabilen önceden eğitilmiş bir model yükleyip çalıştırırsınız. Ardından mikrofonunuzu kullanarak seslerinizi tanıyan ve kaydırma çubuğunu sola ya da sağa giden basit bir nöral ağ oluşturup eğiteceksiniz.

Bu codelab'de, ses tanıma modellerinin arkasındaki teori kapsamında verilmeyecektir. Bu konuda bilgi edinmek istiyorsanız bu eğitime göz atın.

Ayrıca, bu codelab'de bulabileceğiniz makine öğrenimi terimlerinin yer aldığı bir sözlük de oluşturduk.

Neler öğreneceksiniz?

  • Önceden eğitilmiş bir konuşma komutu tanıma modeli yükleme
  • Mikrofonu kullanarak gerçek zamanlı tahminlerde bulunma
  • Tarayıcı mikrofonunu kullanarak özel bir ses tanıma modelini eğitme ve kullanma

Artık başlayabiliriz.

2. Şartlar

Bu codelab'i tamamlamak için şunlar gerekir:

  1. Chrome'un son sürümü veya başka bir modern tarayıcı.
  2. Makinenizde yerel olarak veya Codepen ya da Glitch gibi bir araçla web'de çalışan bir metin düzenleyici.
  3. HTML, CSS, JavaScript ve Chrome Geliştirici Araçları (veya tercih ettiğiniz tarayıcı geliştirme araçları) hakkında bilgi sahibi olmanız gerekir.
  4. Nöral Ağlarla ilgili üst düzey kavramsal bir anlayış. Giriş veya bilgilerinizi tazelemek isterseniz 3blue1brown adlı bu videoyu veya Ashi Krishnan'ın JavaScript'te Derin Öğrenme konulu bu videosunu izleyebilirsiniz.

3. TensorFlow.js ve Ses modelini yükleme

index.html dosyasını bir düzenleyicide açıp şu içeriği ekleyin:

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

İlk <script> etiketi TensorFlow.js kitaplığını, ikinci <script> etiketi ise önceden eğitilmiş Speech Commands modelini içe aktarır. <div id="console"> etiketi, modelin çıkışını görüntülemek için kullanılır.

4. Gerçek zamanlı tahminde bulunma

Daha sonra, index.js dosyasını bir kod düzenleyicide açın/oluşturun ve aşağıdaki kodu ekleyin:

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. Tahmini test etme

Cihazınızın mikrofonundan emin olun. Bunun cep telefonlarında da çalışacağını belirtmekte fayda var! Web sayfasını çalıştırmak için index.html dosyasını bir tarayıcıda açın. Yerel bir dosyadan çalışıyorsanız mikrofona erişmek için bir web sunucusu başlatmanız ve http://localhost:port/ ürününü kullanmanız gerekir.

8000 numaralı bağlantı noktasında basit bir web sunucusu başlatmak için:

python -m SimpleHTTPServer

Modelin indirilmesi biraz zaman alabilir, bu nedenle lütfen sabırlı olun. Model yüklenir yüklenmez sayfanın üst kısmında bir kelime görürsünüz. Model, 0 ile 9 arasındaki sayıları ve "left" (sol), "right" (sağ), "yes" (evet), "no" (hayır) gibi birkaç ek komutu tanıyacak şekilde eğitildi.

Bu kelimelerden birini söyleyin. Kelimenizi doğru aktarıyor mu? Modelin ne sıklıkta etkinleşeceğini kontrol eden probabilityThreshold ile oynayın.0,75, modelin belirli bir kelimeyi duyduğundan% 75 daha fazla emin olduğunda etkinleşeceği anlamına gelir.

Speech Commands modeli ve API'si hakkında daha fazla bilgi için GitHub'daki README.md dosyasını inceleyin.

6. Veri toplama

Eğlenceli hale getirmek için kaydırma çubuğunu kontrol etmek üzere tam kelimeler yerine kısa sesler kullanalım.

Bir modeli 3 farklı komutu tanıyacak şekilde eğiteceksiniz: "Sol", "Sağ" ve "Gürültü" kaydırma çubuğu sola veya sağa hareket eder. "Gürültü" Tanıma (bir işlem yapmanıza gerek yoktur) kaydırma çubuğu, genel olarak konuşurken ve etrafta gezinirken değil, yalnızca doğru sesi çıkardığımızda tepki vermesini istediğimiz için konuşma algılamada kritik öneme sahiptir.

  1. Öncelikle veri toplamamız gerekiyor. <body> etiketinin içine <div id="console"> etiketinden önce şunu ekleyerek uygulamaya basit bir kullanıcı arayüzü ekleyin:
<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. Bunu index.js öğesine ekleyin:
// 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. predictWord(), app() sitesinden kaldırılacak:
async function app() {
 recognizer = speechCommands.create('BROWSER_FFT');
 await recognizer.ensureModelLoaded();
 // predictWord() no longer called.
}

Ayrıntılı inceleme

Bu kod ilk başta göz korkutucu olabilir; bu nedenle bir daha açalım.

Kullanıcı arayüzümüze, modelimizin tanımasını istediğimiz üç komuta karşılık gelen "Sol", "Sağ" ve "Gürültü" etiketli üç düğme ekledik. Bu düğmelere bastığınızda, modelimiz için eğitim örnekleri oluşturan yeni eklenen collect() işlevimiz çağrılır.

collect(), bir label öğesini recognizer.listen() çıkışıyla ilişkilendirir. includeSpectrogram doğru olduğundan, recognizer.listen(), 1 saniyelik sesin ham spektrogramını (frekans verisi) 43 kareye bölünmüş olarak verir. Dolayısıyla her karenin ses değeri yaklaşık 23 ms olur:

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

Kaydırma çubuğunu kontrol etmek için kelimeler yerine kısa sesler kullanmak istediğimizden yalnızca son 3 kareyi (~70 ms) dikkate alırız:

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

Sayısal sorunlardan kaçınmak için de verileri ortalama 0, standart sapma 1 olacak şekilde normalleştiririz. Bu durumda, spektrogram değerleri genellikle -100 civarındaki büyük negatif sayılar ve 10'un sapması şeklindedir:

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

Son olarak, her eğitim örneğinde 2 alan bulunur:

  • label****: "Sol", "Sağ" için 0, 1 ve 2 ve "Gürültü" tıklayın.
  • vals****: Frekans bilgisini içeren 696 sayı (spektrogram)

ve tüm veriler examples değişkeninde depolanır:

examples.push({vals, label});

7. Test verisi toplama

index.html dosyasını bir tarayıcıda açın; 3 komuta karşılık gelen 3 düğme göreceksiniz. Yerel bir dosyadan çalışıyorsanız mikrofona erişmek için bir web sunucusu başlatmanız ve http://localhost:port/ kullanmanız gerekir.

8000 numaralı bağlantı noktasında basit bir web sunucusu başlatmak için:

python -m SimpleHTTPServer

Her komutla ilgili örnek toplamak için her düğmeyi 3-4 saniye basılı tutarak tekrar tekrar (veya sürekli) tutarlı bir ses çıkarın. Her etiket için yaklaşık 150 örnek toplamanız gerekir. Örneğin, "Sol" için parmaklarınızı şıklatabilir, "Sağ" için ıslık çalabilir ve sessiz ile "Gürültü" arasında geçiş yapabiliriz.

Daha fazla örnek topladıkça sayfada gösterilen sayaç yukarı gitmelidir. Dilerseniz konsoldaki examples değişkeninde console.log() yöntemini çağırarak verileri inceleyebilirsiniz. Bu noktada hedef veri toplama sürecini test etmektir. Daha sonra, uygulamanın tamamını test ederken verileri yeniden toplarsınız.

8. Model eğitin

  1. "Tren" ekleyin "Gürültü" düğmesinden hemen sonraki index.html: dosyasındaki gövdede bulunan düğme
<br/><br/>
<button id="train" onclick="train()">Train</button>
  1. Aşağıdakini index.js dosyasındaki mevcut koda ekleyin:
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. Uygulama yüklendiğinde buildModel() komutunu çağır:
async function app() {
 recognizer = speechCommands.create('BROWSER_FFT');
 await recognizer.ensureModelLoaded();
 // Add this line.
 buildModel();
}

Bu noktada uygulamayı yenilerseniz yeni bir "Tren" simgesi görürsünüz. düğmesini tıklayın. Verileri yeniden toplayıp "Eğit"i tıklayarak eğitimi test edebilir veya eğitimi tahminle birlikte test etmek için 10. adıma kadar bekleyebilirsiniz.

Ayrıntılı inceleme

Genel olarak iki şey yapıyoruz: buildModel(), model mimarisini tanımlar ve train(), modeli toplanan verileri kullanarak eğitir.

Model mimari

Modelin 4 katmanı vardır: ses verilerini işleyen konvolüsyonel katman (spektrogram olarak temsil edilir), maksimum havuz katmanı, düzleştirilen katman ve 3 işlemle eşlenen yoğun katman:

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

Modelin giriş şekli [NUM_FRAMES, 232, 1] şeklindedir. Burada her kare, farklı frekanslara karşılık gelen 232 sayı içeren 23 ms'lik ses içerir (İnsan sesini yakalamak için gereken frekans paketlerinin miktarı olduğu için 232 seçildi). Bu codelab'de, kaydırma çubuğunu kontrol etmek için kelimelerin tamamını söylemek yerine ses çıkardığımız için 3 kare uzunluğunda (yaklaşık 70 ms. örnekler) örnekler kullanıyoruz.

Eğitime hazır olması için modelimizi derleriz:

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

Derin öğrenmede kullanılan ortak bir optimize edici olan Adam optimize edici'yi ve kayıp için (categoricalCrossEntropy) sınıflandırmada kullanılan standart kayıp işlevi için kullanıyoruz. Kısacası, tahmin edilen olasılıkların (her sınıf için bir olasılık) gerçek sınıfta% 100 olasılığa ve diğer tüm sınıflarda% 0 olasılığa ne kadar uzak olduğunu ölçer. Ayrıca, izlenecek metrik olarak accuracy özelliği de sağlıyoruz. Bu metrik, modelin her eğitim döneminden sonra doğru sonuç verdiğini gösterir.

Eğitim

Eğitim, 16'lık bir grup boyutu kullanılarak veriler üzerinde 10 kez (dönemler) ilerler (bir defada 16 örnek işlenir) ve kullanıcı arayüzünde mevcut doğruluğu gösterir:

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. Kaydırma çubuğunu gerçek zamanlı olarak güncelleme

Artık modelimizi eğitebildiğimize göre gerçek zamanlı tahminlerde bulunabileceğimiz bir kod ekleyelim ve kaydırma çubuğunu hareket ettirelim. Bunu "Tren"in hemen sonrasına ekleyin index.html'deki düğmesini tıklayın:

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

Ve index.js dosyasında aşağıdakiler:

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

Ayrıntılı inceleme

Gerçek zamanlı tahmin

listen(), mikrofonu dinler ve gerçek zamanlı tahminler yapar. Kod, ham spektrogramı normalleştiren ve son NUM_FRAMES kare hariç tümünü bırakan collect() yöntemine çok benzer. Tek fark, eğitilen modeli tahmin almak için de çağırmamızdır:

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

model.predict(input) çıktısı, sınıf sayısı üzerinden olasılık dağılımını temsil eden [1, numClasses] biçiminde bir tensördür. Daha basit şekilde anlatmak gerekirse bu, toplamı 1 olan olası çıkış sınıflarının her biri için bir güven dizisidir. Tensor'un dış boyutu 1, çünkü grubun boyutu budur (tek bir örnek).

Olasılık dağılımını, en olası sınıfı temsil eden tek bir tam sayıya dönüştürmek için, en yüksek olasılığa sahip sınıf dizinini döndüren probs.argMax(1) adını verdiğimizi belirtir. "1" ilerliyoruz. çünkü son boyut (numClasses) üzerinden argMax'yi hesaplamak istediğimizden bunu eksen parametresi olarak ayarladık.

Kaydırma çubuğunu güncelleme

moveSlider(), etiket 0 ("Sol") ise kaydırma çubuğunun değerini azaltır, etiket 1 ("Sağ") ise kaydırma çubuğunu artırır ve etiket 2 ("Gürültü") ise yoksayar.

Tensörleri ayırma

GPU belleğini temizlemek için çıkış Tensor'larında tf.dispose() öğesini manuel olarak çağırmamız önemlidir. Manuel tf.dispose() işlevinin alternatifi, işlev çağrılarını tf.tidy() içinde sarmalamaktır, ancak bu işlev eşzamansız işlevlerle kullanılamaz.

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

10. Uygulamanın son sürümünü test etme

Tarayıcınızda index.html sayfasını açın ve 3 komuta karşılık gelen 3 düğmeyle önceki bölümde yaptığınız gibi verileri toplayın. Verileri toplarken her düğmeyi 3-4 saniye boyunca basılı tutmayı unutmayın.

Örnekleri topladıktan sonra "Eğit" düğmesine basın. Bu işlem, modeli eğitmeye başlar ve modelin doğruluğunun %90'ın üzerine çıktığını görürsünüz. İyi model performansı elde etmezseniz daha fazla veri toplamayı deneyin.

Eğitim tamamlandığında, mikrofondan tahminde bulunmak ve kaydırma çubuğunu kontrol etmek için "Dinle" düğmesine basın.

Diğer eğitimler için http://js.tensorflow.org/ adresini ziyaret edin.