1. परिचय
इस कोडलैब में, आपको ऑडियो की पहचान करने वाला एक नेटवर्क बनाना होगा. साथ ही, ब्राउज़र में आवाज़ चलाकर स्लाइडर को कंट्रोल करने के लिए इसका इस्तेमाल किया जा सकेगा. TensorFlow.js का इस्तेमाल किया जाएगा, जो JavaScript के लिए एक बेहतरीन और सुविधाजनक मशीन लर्निंग लाइब्रेरी है.
सबसे पहले, पहले से ट्रेन किए गए मॉडल को लोड करें और चलाएं. यह मॉडल, बोले गए 20 निर्देशों की पहचान कर सकता है. इसके बाद, माइक्रोफ़ोन का इस्तेमाल करके, एक सामान्य न्यूरल नेटवर्क बनाया और ट्रेनिंग दी जाएगी. यह नेटवर्क आपकी आवाज़ों को पहचानकर, स्लाइडर को बाईं या दाईं ओर ले जाएगा.
यह कोडलैब, ऑडियो की पहचान करने वाले मॉडल से जुड़े सिद्धांत का इस्तेमाल नहीं करता. अगर आपको इस बारे में जानना है, तो यह ट्यूटोरियल देखें.
हमने मशीन लर्निंग से जुड़े शब्दों की एक शब्दावली भी बनाई है, जो आपको इस कोडलैब में मिलती है.
आप इन चीज़ों के बारे में जानेंगे
- बोली पहचानने वाले मॉडल को लोड करने का तरीका
- माइक्रोफ़ोन का इस्तेमाल करके रीयल-टाइम में अनुमान लगाने का तरीका
- ब्राउज़र माइक्रोफ़ोन का इस्तेमाल करके, आवाज़ की पहचान करने वाले कस्टम मॉडल को ट्रेनिंग देने और उसे इस्तेमाल करने का तरीका
आइए, शुरू करते हैं.
2. ज़रूरी शर्तें
इस कोडलैब को पूरा करने के लिए, आपको इनकी ज़रूरत होगी:
- Chrome या किसी अन्य मॉडर्न ब्राउज़र का नया वर्शन.
- ऐसा टेक्स्ट एडिटर जो कोडपेन या Glitch जैसे टूल के ज़रिए, आपकी मशीन या वेब पर चल रहा हो.
- एचटीएमएल, सीएसएस, JavaScript, और Chrome DevTools (या आपके पसंदीदा ब्राउज़र Devtools) के बारे में जानकारी.
- न्यूरल नेटवर्क के बारे में कॉन्सेप्ट की अच्छी जानकारी. अगर आपको कोई परिचय या रीफ़्रेशर चाहिए, तो 3blue1brown का यह वीडियो या JavaScript में डीप लर्निंग के बारे में आशी कृष्णन का यह वीडियो देखें.
3. TensorFlow.js और ऑडियो मॉडल लोड करें
index.html
को एडिटर में खोलें और यह कॉन्टेंट जोड़ें:
<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>
पहला <script>
टैग, TensorFlow.js लाइब्रेरी को इंपोर्ट करता है. वहीं, दूसरा <script>
, पहले से ट्रेन किए गए स्पीच कमांड मॉडल को इंपोर्ट करता है. <div id="console">
टैग का इस्तेमाल, मॉडल का आउटपुट दिखाने के लिए किया जाएगा.
4. रीयल-टाइम में अनुमान लगाएं
इसके बाद, कोड एडिटर में index.js फ़ाइल खोलें या बनाएं और नीचे दिया गया कोड शामिल करें:
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. सुझाव की जांच करना
पक्का करें कि आपके डिवाइस में माइक्रोफ़ोन हो. ध्यान दें कि यह सुविधा मोबाइल फ़ोन पर भी काम करती है! वेबपेज चलाने के लिए, किसी ब्राउज़र में index.html खोलें. अगर आप किसी लोकल फ़ाइल से काम कर रहे हैं, तो माइक्रोफ़ोन ऐक्सेस करने के लिए आपको वेब सर्वर शुरू करना होगा और http://localhost:port/
का इस्तेमाल करना होगा.
पोर्ट 8000 पर एक सामान्य वेबसर्वर शुरू करने के लिए:
python -m SimpleHTTPServer
मॉडल को डाउनलोड करने में कुछ समय लग सकता है, इसलिए कृपया थोड़ा इंतज़ार करें. मॉडल लोड होते ही, आपको पेज के सबसे ऊपर कोई शब्द दिखना चाहिए. इस मॉडल को 0 से लेकर 9 तक की संख्याओं को पहचानने के लिए ट्रेनिंग दी गई है. साथ ही, इस मॉडल को कुछ अतिरिक्त कमांड (जैसे, "left", "right", "yes", "no" वगैरह) वगैरह को पहचानने के लिए भी ट्रेनिंग दी गई है.
उनमें से कोई एक शब्द बोलें. क्या यह आपके शब्द का सही तरीके से वर्णन कर रहा है? probabilityThreshold
की मदद से खेलें. इससे यह कंट्रोल किया जाता है कि मॉडल कितनी बार ऐक्टिव होगा. 0.75 का मतलब है कि मॉडल तब ही फ़ायर होगा, जब वह दिए गए शब्द को सुनकर 75% से ज़्यादा भरोसेमंद होगा.
Speech Commands मॉडल और उसके एपीआई के बारे में ज़्यादा जानने के लिए, GitHub पर README.md पढ़ें.
6. डेटा संग्रहित करें
आइए, स्लाइडर को कंट्रोल करने के लिए पूरे शब्दों के बजाय छोटी आवाज़ों का इस्तेमाल करते हैं!
आप मॉडल को तीन अलग-अलग निर्देशों को पहचानने की ट्रेनिंग देने वाले हैं: "लेफ़्ट", "राइट" और "शोर" इससे स्लाइडर बाईं या दाईं ओर खिसक जाएगा. "शोर" की पहचान करना (किसी कार्रवाई की ज़रूरत नहीं है) बोली पहचानने में अहम है, क्योंकि हम चाहते हैं कि स्लाइडर सिर्फ़ तब प्रतिक्रिया करे, जब हम सही आवाज़ निकाल रहे हों, न कि जब हम आम तौर पर बोल रहे हों और इधर-उधर जा रहे हों.
- सबसे पहले हमें डेटा इकट्ठा करना होगा.
<div id="console">
से पहले<body>
टैग में इसे जोड़कर ऐप्लिकेशन में एक आसान यूज़र इंटरफ़ेस (यूआई) जोड़ें:
<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>
- इसे
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);
}
app()
सेpredictWord()
हटाएं:
async function app() {
recognizer = speechCommands.create('BROWSER_FFT');
await recognizer.ensureModelLoaded();
// predictWord() no longer called.
}
विषय बारीकी से समझें
शुरुआत में यह कोड आपको परेशान कर सकता है, तो आइए इसे छोटा कर दें.
हमने अपने यूज़र इंटरफ़ेस (यूआई) में "लेफ़्ट", "राइट", और "शोर" लेबल वाले तीन बटन जोड़े हैं. ये बटन उन तीन कमांड से जुड़े हैं जिन्हें हम अपने मॉडल को पहचानना चाहते हैं. इन बटन को दबाने से, जोड़े गए नए collect()
फ़ंक्शन कॉल हो जाते हैं. इससे हमारे मॉडल के लिए ट्रेनिंग के उदाहरण बन जाते हैं.
collect()
, label
को recognizer.listen()
के आउटपुट से जोड़ता है. includeSpectrogram
सही है,
recognizer.listen()
, एक सेकंड के ऑडियो के लिए रॉ स्पेक्ट्रोग्राम (फ़्रीक्वेंसी डेटा) देता है. इसे 43 फ़्रेम में बांटा जाता है. इसलिए, हर फ़्रेम का ऑडियो करीब 23 मि॰से॰ का होता है:
recognizer.listen(async ({spectrogram: {frameSize, data}}) => {
...
}, {includeSpectrogram: true});
हम स्लाइडर को कंट्रोल करने के लिए शब्दों के बजाय छोटी आवाज़ों का इस्तेमाल करना चाहते हैं, इसलिए हम सिर्फ़ आखिरी तीन फ़्रेम (~70 मि॰से॰) पर ध्यान देते हैं:
let vals = normalize(data.subarray(-frameSize * NUM_FRAMES));
संख्या वाली समस्याओं से बचने के लिए, हम डेटा को नॉर्मलाइज़ कर देते हैं, ताकि उसका औसत 0 और उसका स्टैंडर्ड डीविएशन 1 हो. इस मामले में, स्पेक्ट्रोग्राम की वैल्यू आम तौर पर -100 के आस-पास बड़ी नेगेटिव नंबर और 10 का डीविएशन होती हैं:
const mean = -100;
const std = 10;
return x.map(x => (x - mean) / std);
आखिर में, ट्रेनिंग के हर उदाहरण में दो फ़ील्ड होंगे:
label
****: "लेफ़्ट" और "राइट" के लिए 0, 1, और 2 और "शोर" क्रम से.vals
****: 696 नंबर में फ़्रीक्वेंसी की जानकारी (स्पेक्ट्रोग्राम)
और हम पूरा डेटा examples
वैरिएबल में सेव करते हैं:
examples.push({vals, label});
7. डेटा कलेक्शन की जांच करना
ब्राउज़र में index.html खोलें और आपको तीन निर्देशों से जुड़े तीन बटन दिखेंगे. अगर आप किसी लोकल फ़ाइल से काम कर रहे हैं, तो माइक्रोफ़ोन ऐक्सेस करने के लिए आपको वेबसर्वर शुरू करना होगा और http://localhost:port/
का इस्तेमाल करना होगा.
पोर्ट 8000 पर एक सामान्य वेबसर्वर शुरू करने के लिए:
python -m SimpleHTTPServer
किसी निर्देश के उदाहरण इकट्ठा करने के लिए, हर बटन को तीन से चार सेकंड दबाकर रखें और बार-बार एक जैसी आवाज़ सुनें. आपको हर लेबल के लिए करीब 150 उदाहरण इकट्ठा करने चाहिए. उदाहरण के लिए, "बाएं" के लिए उंगलियां थिरकनी हैं, "दाएं" के लिए सीटी बजाई जा सकती है, और बीच में साइलेंस और "शोर" के लिए बात की जा सकती है.
जैसे-जैसे ज़्यादा उदाहरण इकट्ठा होते जाएंगे, वैसे-वैसे पेज पर दिखने वाला काउंटर बढ़ता जाना चाहिए. कंसोल में examples
वैरिएबल पर console.log() को कॉल करके भी डेटा की जांच की जा सकती है. इस दौरान लक्ष्य का मकसद, डेटा इकट्ठा करने की प्रोसेस को टेस्ट करना है. पूरे ऐप्लिकेशन की जांच करने पर, बाद में डेटा फिर से इकट्ठा किया जाएगा.
8. मॉडल को ट्रेनिंग दें
- "ट्रेन" जोड़ें "शोर" के ठीक बाद वाला बटन index.html: के मुख्य हिस्से में दिया गया बटन:
<br/><br/>
<button id="train" onclick="train()">Train</button>
- 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;
}
- ऐप्लिकेशन लोड होने पर
buildModel()
पर कॉल करें:
async function app() {
recognizer = speechCommands.create('BROWSER_FFT');
await recognizer.ensureModelLoaded();
// Add this line.
buildModel();
}
इस समय, ऐप्लिकेशन को रीफ़्रेश करने पर आपको नई "ट्रेन", बटन. डेटा को फिर से इकट्ठा करके और "ट्रेन" पर क्लिक करके, ट्रेनिंग की जांच की जा सकती है. इसके अलावा, सुझाव देने के साथ-साथ ट्रेनिंग को टेस्ट करने के लिए, 10वें चरण तक इंतज़ार किया जा सकता है.
इन बातों का विश्लेषण करें
हाई लेवल पर हम दो काम कर रहे हैं: buildModel()
मॉडल आर्किटेक्चर तय करता है और train()
इकट्ठा किए गए डेटा का इस्तेमाल करके मॉडल को ट्रेनिंग देता है.
मॉडल आर्किटेक्चर
इस मॉडल में चार लेयर हैं: एक कॉन्वलूशन लेयर जो ऑडियो डेटा को प्रोसेस करती है (इसे स्पेक्ट्रोग्राम के तौर पर दिखाया जाता है), ज़्यादा से ज़्यादा पूल की लेयर, फ़्लैटन लेयर, और एक सघन लेयर जो इन तीन कार्रवाइयों को मैप करती है:
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'}));
इस मॉडल का इनपुट आकार [NUM_FRAMES, 232, 1]
है. यहां हर फ़्रेम 23 मि॰से॰ के ऑडियो का है. इसमें अलग-अलग फ़्रीक्वेंसी से जुड़ी 232 संख्याएं हैं. 232 को इसलिए चुना गया, क्योंकि यह इंसानों की आवाज़ को कैप्चर करने के लिए ज़रूरी फ़्रीक्वेंसी बकेट की मात्रा है. इस कोडलैब में, हम तीन फ़्रेम (~70 मि॰से॰ के सैंपल) वाले सैंपल का इस्तेमाल कर रहे हैं, क्योंकि हम स्लाइडर कंट्रोल करने के लिए पूरे शब्द बोलने की बजाय आवाज़ निकाल रहे हैं.
हम अपने मॉडल को कंपाइल करते हैं, ताकि उसे ट्रेनिंग के लिए तैयार किया जा सके:
const optimizer = tf.train.adam(0.01);
model.compile({
optimizer,
loss: 'categoricalCrossentropy',
metrics: ['accuracy']
});
हम Adam ऑप्टिमाइज़र का इस्तेमाल करते हैं. यह डीप लर्निंग में इस्तेमाल किया जाने वाला एक सामान्य ऑप्टिमाइज़र है. साथ ही, नुकसान पहुंचाने के लिए categoricalCrossEntropy
का इस्तेमाल करते हैं, जो कि कैटगरी तय करने के लिए इस्तेमाल किया जाने वाला स्टैंडर्ड लॉस फ़ंक्शन है. कम शब्दों में, यह मेज़र किया जाता है कि अनुमानित प्रॉबबिलिटी (हर क्लास के लिए एक प्रॉबबिलिटी), सही क्लास में 100% प्रॉबबिलिटी होने से कितनी दूर है. वहीं, अन्य सभी क्लास के लिए प्रॉबबिलिटी 0% है. हम मॉनिटर करने के लिए मेट्रिक के तौर पर accuracy
भी देते हैं. इससे हमें उन उदाहरणों का प्रतिशत पता चलेगा जो ट्रेनिंग के हर epoch का इस्तेमाल करने के बाद, मॉडल सही हो सकता है.
ट्रेनिंग
ट्रेनिंग, 16 के बैच साइज़ (एक बार में 16 उदाहरणों को प्रोसेस किया जाता है) का इस्तेमाल करके, डेटा पर 10 बार (epoch) लगाती है. साथ ही, यह यूज़र इंटरफ़ेस (यूआई) में मौजूदा सटीक जानकारी दिखाती है:
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. स्लाइडर को रीयल टाइम में अपडेट करें
अब जब हम अपने मॉडल को ट्रेनिंग दे सकते हैं, तो रीयल टाइम में अनुमान लगाने के लिए कोड जोड़ें और स्लाइडर को आगे बढ़ाएं. इसे "ट्रेन" के ठीक बाद जोड़ें index.html में दिया गया बटन:
<br/><br/>
<button id="listen" onclick="listen()">Listen</button>
<input type="range" id="output" min="0" max="10" step="0.1">
और 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
});
}
इन बातों का विश्लेषण करें
रीयल-टाइम में अनुमान
listen()
, माइक्रोफ़ोन की आवाज़ सुनता है और रीयल-टाइम में अनुमान लगाता है. यह कोड काफ़ी हद तक collect()
तरीके से मिलता-जुलता है, जो रॉ स्पेक्ट्रोग्राम को नॉर्मलाइज़ करता है. साथ ही, आखिरी NUM_FRAMES
फ़्रेम को छोड़कर बाकी सभी फ़्रेम को ड्रॉप करता है. अंतर सिर्फ़ यह है कि हम प्रशिक्षित मॉडल को सुझाव पाने के लिए कॉल भी करते हैं:
const probs = model.predict(input);
const predLabel = probs.argMax(1);
await moveSlider(predLabel);
model.predict(input)
का आउटपुट, [1, numClasses]
आकार का टेन्सर है. यह प्रॉबबिलिटी डिस्ट्रिब्यूशन के तौर पर क्लास की संख्या दिखाता है. आसान शब्दों में कहें, तो यह हर संभावित आउटपुट क्लास के लिए कॉन्फ़िडेंस का एक सेट है, जिसका योग 1 है. टेन्सर का आउटर डाइमेंशन 1 है, क्योंकि यह बैच का साइज़ है (एक उदाहरण).
प्रॉबबिलिटी डिस्ट्रिब्यूशन को सबसे संभावित क्लास को दर्शाने वाले किसी एक पूर्णांक में बदलने के लिए, हम probs.argMax(1)
को कॉल करते हैं. यह सबसे ज़्यादा प्रॉबबिलिटी वाला क्लास इंडेक्स लौटाता है. हम "1" पास करते हैं को ऐक्सिस पैरामीटर के तौर पर इस्तेमाल किया जा सकता है, क्योंकि हम आखिरी डाइमेंशन numClasses
के मुकाबले argMax
की गिनती करना चाहते हैं.
स्लाइडर को अपडेट करना
अगर लेबल 0 ("बायां") है, तो moveSlider()
स्लाइडर का मान घटा देता है. अगर लेबल 1 ("दायां") है, तो इसे बढ़ा देता है. अगर लेबल 2 ("शोर") है, तो इसे अनदेखा कर देता है.
टेंसर डिस्पोज़ करना
जीपीयू मेमोरी को खाली करने के लिए, यह ज़रूरी है कि हम आउटपुट Tensors पर मैन्युअल तरीके से tf.dispos() को कॉल करें. मैन्युअल tf.dispose()
का विकल्प, फ़ंक्शन कॉल को tf.tidy()
में रैप कर रहा है. हालांकि, इसका इस्तेमाल एक साथ काम नहीं करने वाले फ़ंक्शन के साथ नहीं किया जा सकता.
tf.dispose([input, probs, predLabel]);
10. फ़ाइनल ऐप्लिकेशन की जांच करें
index.html को अपने ब्राउज़र में खोलें और पिछले सेक्शन में किए गए तीन निर्देशों से जुड़े तीन बटन की मदद से डेटा इकट्ठा करें. डेटा इकट्ठा करते समय, हर बटन को तीन से चार सेकंड तक दबाकर रखें.
उदाहरण इकट्ठा करने के बाद, "ट्रेन" बटन दबाएं. ऐसा करने पर, मॉडल की ट्रेनिंग शुरू हो जाएगी. साथ ही, आपको दिखेगा कि मॉडल 90% से ज़्यादा सटीक है. अगर मॉडल की परफ़ॉर्मेंस अच्छी नहीं है, तो ज़्यादा डेटा इकट्ठा करें.
ट्रेनिंग पूरी हो जाने के बाद, माइक्रोफ़ोन की मदद से सुझाव देने और स्लाइडर कंट्रोल करने के लिए, "सुनें" बटन को दबाएं!
http://js.tensorflow.org/ पर ज़्यादा ट्यूटोरियल देखें.