Note
Go to the end to download the full example code
DS-CNN/KWS inference
This tutorial illustrates the process of developing an Akida-compatible speech recognition model that can identify thirty-two different keywords.
Initially, the model is defined as a CNN in Keras and trained regularly. Next, it undergoes quantization using QuantizeML and finally converted to Akida using CNN2SNN.
This example uses a Keyword Spotting Dataset prepared using TensorFlow audio recognition example utils.
1. Load the preprocessed dataset
The TensorFlow speech_commands dataset is used for training and validation. All keywords except “backward”, “follow” and “forward”, are retrieved. These three words are kept to illustrate the edge learning in this edge example.
The words to recognize have been converted to spectrogram images that allows us to use a model architecture that is typically used for image recognition tasks. The raw audio data have been preprocessed, transforming the audio files into MFCC features, well-suited for CNN networks. A pickle file containing the preprocessed data is available on Brainchip data server.
import pickle
from tensorflow.keras.utils import get_file
# Fetch pre-processed data for 32 keywords
fname = get_file(
fname='kws_preprocessed_all_words_except_backward_follow_forward.pkl',
origin="https://data.brainchip.com/dataset-mirror/kws/kws_preprocessed_all_words_except_backward_follow_forward.pkl",
cache_subdir='datasets/kws')
with open(fname, 'rb') as f:
[_, _, x_valid, y_valid, _, _, word_to_index, _] = pickle.load(f)
# Preprocessed dataset parameters
num_classes = len(word_to_index)
print("Wanted words and labels:\n", word_to_index)
Downloading data from https://data.brainchip.com/dataset-mirror/kws/kws_preprocessed_all_words_except_backward_follow_forward.pkl
8192/62628765 [..............................] - ETA: 0s
212992/62628765 [..............................] - ETA: 15s
663552/62628765 [..............................] - ETA: 9s
1081344/62628765 [..............................] - ETA: 8s
1482752/62628765 [..............................] - ETA: 8s
1884160/62628765 [..............................] - ETA: 8s
2285568/62628765 [>.............................] - ETA: 8s
2695168/62628765 [>.............................] - ETA: 7s
3104768/62628765 [>.............................] - ETA: 7s
3538944/62628765 [>.............................] - ETA: 7s
3981312/62628765 [>.............................] - ETA: 7s
4390912/62628765 [=>............................] - ETA: 7s
4833280/62628765 [=>............................] - ETA: 7s
5283840/62628765 [=>............................] - ETA: 7s
5742592/62628765 [=>............................] - ETA: 7s
6201344/62628765 [=>............................] - ETA: 6s
6676480/62628765 [==>...........................] - ETA: 6s
7135232/62628765 [==>...........................] - ETA: 6s
7610368/62628765 [==>...........................] - ETA: 6s
8101888/62628765 [==>...........................] - ETA: 6s
8601600/62628765 [===>..........................] - ETA: 6s
9076736/62628765 [===>..........................] - ETA: 6s
9601024/62628765 [===>..........................] - ETA: 6s
10108928/62628765 [===>..........................] - ETA: 6s
10616832/62628765 [====>.........................] - ETA: 5s
11141120/62628765 [====>.........................] - ETA: 5s
11665408/62628765 [====>.........................] - ETA: 5s
12189696/62628765 [====>.........................] - ETA: 5s
12713984/62628765 [=====>........................] - ETA: 5s
13254656/62628765 [=====>........................] - ETA: 5s
13795328/62628765 [=====>........................] - ETA: 5s
14336000/62628765 [=====>........................] - ETA: 5s
14893056/62628765 [======>.......................] - ETA: 5s
15450112/62628765 [======>.......................] - ETA: 5s
16015360/62628765 [======>.......................] - ETA: 5s
16564224/62628765 [======>.......................] - ETA: 4s
17121280/62628765 [=======>......................] - ETA: 4s
17694720/62628765 [=======>......................] - ETA: 4s
18251776/62628765 [=======>......................] - ETA: 4s
18808832/62628765 [========>.....................] - ETA: 4s
19382272/62628765 [========>.....................] - ETA: 4s
19955712/62628765 [========>.....................] - ETA: 4s
20512768/62628765 [========>.....................] - ETA: 4s
21069824/62628765 [=========>....................] - ETA: 4s
21643264/62628765 [=========>....................] - ETA: 4s
22216704/62628765 [=========>....................] - ETA: 4s
22790144/62628765 [=========>....................] - ETA: 4s
23347200/62628765 [==========>...................] - ETA: 4s
23920640/62628765 [==========>...................] - ETA: 3s
24494080/62628765 [==========>...................] - ETA: 3s
25051136/62628765 [==========>...................] - ETA: 3s
25624576/62628765 [===========>..................] - ETA: 3s
26181632/62628765 [===========>..................] - ETA: 3s
26755072/62628765 [===========>..................] - ETA: 3s
27312128/62628765 [============>.................] - ETA: 3s
27869184/62628765 [============>.................] - ETA: 3s
28442624/62628765 [============>.................] - ETA: 3s
28983296/62628765 [============>.................] - ETA: 3s
29474816/62628765 [=============>................] - ETA: 3s
29966336/62628765 [=============>................] - ETA: 3s
30457856/62628765 [=============>................] - ETA: 3s
30965760/62628765 [=============>................] - ETA: 3s
31473664/62628765 [==============>...............] - ETA: 3s
31981568/62628765 [==============>...............] - ETA: 3s
32489472/62628765 [==============>...............] - ETA: 3s
33013760/62628765 [==============>...............] - ETA: 2s
33538048/62628765 [===============>..............] - ETA: 2s
34078720/62628765 [===============>..............] - ETA: 2s
34619392/62628765 [===============>..............] - ETA: 2s
35143680/62628765 [===============>..............] - ETA: 2s
35684352/62628765 [================>.............] - ETA: 2s
36241408/62628765 [================>.............] - ETA: 2s
36782080/62628765 [================>.............] - ETA: 2s
37322752/62628765 [================>.............] - ETA: 2s
37879808/62628765 [=================>............] - ETA: 2s
38436864/62628765 [=================>............] - ETA: 2s
38993920/62628765 [=================>............] - ETA: 2s
39550976/62628765 [=================>............] - ETA: 2s
40108032/62628765 [==================>...........] - ETA: 2s
40632320/62628765 [==================>...........] - ETA: 2s
41156608/62628765 [==================>...........] - ETA: 2s
41680896/62628765 [==================>...........] - ETA: 2s
42205184/62628765 [===================>..........] - ETA: 2s
42680320/62628765 [===================>..........] - ETA: 1s
43122688/62628765 [===================>..........] - ETA: 1s
43565056/62628765 [===================>..........] - ETA: 1s
44007424/62628765 [====================>.........] - ETA: 1s
44466176/62628765 [====================>.........] - ETA: 1s
44924928/62628765 [====================>.........] - ETA: 1s
45400064/62628765 [====================>.........] - ETA: 1s
45875200/62628765 [====================>.........] - ETA: 1s
46366720/62628765 [=====================>........] - ETA: 1s
46858240/62628765 [=====================>........] - ETA: 1s
47333376/62628765 [=====================>........] - ETA: 1s
47841280/62628765 [=====================>........] - ETA: 1s
48332800/62628765 [======================>.......] - ETA: 1s
48840704/62628765 [======================>.......] - ETA: 1s
49348608/62628765 [======================>.......] - ETA: 1s
49872896/62628765 [======================>.......] - ETA: 1s
50397184/62628765 [=======================>......] - ETA: 1s
50921472/62628765 [=======================>......] - ETA: 1s
51462144/62628765 [=======================>......] - ETA: 1s
52002816/62628765 [=======================>......] - ETA: 1s
52543488/62628765 [========================>.....] - ETA: 1s
53100544/62628765 [========================>.....] - ETA: 0s
53657600/62628765 [========================>.....] - ETA: 0s
54214656/62628765 [========================>.....] - ETA: 0s
54771712/62628765 [=========================>....] - ETA: 0s
55345152/62628765 [=========================>....] - ETA: 0s
55902208/62628765 [=========================>....] - ETA: 0s
56442880/62628765 [==========================>...] - ETA: 0s
57016320/62628765 [==========================>...] - ETA: 0s
57573376/62628765 [==========================>...] - ETA: 0s
58146816/62628765 [==========================>...] - ETA: 0s
58703872/62628765 [===========================>..] - ETA: 0s
59260928/62628765 [===========================>..] - ETA: 0s
59834368/62628765 [===========================>..] - ETA: 0s
60407808/62628765 [===========================>..] - ETA: 0s
60964864/62628765 [============================>.] - ETA: 0s
61538304/62628765 [============================>.] - ETA: 0s
62111744/62628765 [============================>.] - ETA: 0s
62628765/62628765 [==============================] - 6s 0us/step
Wanted words and labels:
{'six': 23, 'three': 25, 'seven': 21, 'bed': 1, 'eight': 6, 'yes': 31, 'cat': 3, 'on': 18, 'one': 19, 'stop': 24, 'two': 27, 'house': 11, 'five': 7, 'down': 5, 'four': 8, 'go': 9, 'up': 28, 'learn': 12, 'no': 16, 'bird': 2, 'zero': 32, 'nine': 15, 'visual': 29, 'wow': 30, 'sheila': 22, 'marvin': 14, 'off': 17, 'right': 20, 'left': 13, 'happy': 10, 'dog': 4, 'tree': 26, '_silence_': 0}
2. Load a pre-trained native Keras model
The model consists of:
a first convolutional layer accepting dense inputs (images),
several separable convolutional layers preserving spatial dimensions,
a global pooling reducing the spatial dimensions to a single pixel,
a final dense layer to classify words.
All layers are followed by a batch normalization and a ReLU activation.
from tensorflow.keras.models import load_model
# Retrieve the model file from the BrainChip data server
model_file = get_file("ds_cnn_kws.h5",
"https://data.brainchip.com/models/AkidaV2/ds_cnn/ds_cnn_kws.h5",
cache_subdir='models')
# Load the native Keras pre-trained model
model_keras = load_model(model_file)
model_keras.summary()
Downloading data from https://data.brainchip.com/models/AkidaV2/ds_cnn/ds_cnn_kws.h5
8192/172504 [>.............................] - ETA: 0s
98304/172504 [================>.............] - ETA: 0s
172504/172504 [==============================] - 0s 0us/step
Model: "ds_cnn_kws"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input (InputLayer) [(None, 49, 10, 1)] 0
rescaling (Rescaling) (None, 49, 10, 1) 0
conv_0 (Conv2D) (None, 25, 5, 64) 1600
conv_0/BN (BatchNormalizati (None, 25, 5, 64) 256
on)
conv_0/relu (ReLU) (None, 25, 5, 64) 0
dw_separable_1 (DepthwiseCo (None, 25, 5, 64) 576
nv2D)
pw_separable_1 (Conv2D) (None, 25, 5, 64) 4096
pw_separable_1/BN (BatchNor (None, 25, 5, 64) 256
malization)
pw_separable_1/relu (ReLU) (None, 25, 5, 64) 0
dw_separable_2 (DepthwiseCo (None, 25, 5, 64) 576
nv2D)
pw_separable_2 (Conv2D) (None, 25, 5, 64) 4096
pw_separable_2/BN (BatchNor (None, 25, 5, 64) 256
malization)
pw_separable_2/relu (ReLU) (None, 25, 5, 64) 0
dw_separable_3 (DepthwiseCo (None, 25, 5, 64) 576
nv2D)
pw_separable_3 (Conv2D) (None, 25, 5, 64) 4096
pw_separable_3/BN (BatchNor (None, 25, 5, 64) 256
malization)
pw_separable_3/relu (ReLU) (None, 25, 5, 64) 0
dw_separable_4 (DepthwiseCo (None, 25, 5, 64) 576
nv2D)
pw_separable_4 (Conv2D) (None, 25, 5, 64) 4096
pw_separable_4/BN (BatchNor (None, 25, 5, 64) 256
malization)
pw_separable_4/relu (ReLU) (None, 25, 5, 64) 0
pw_separable_4/global_avg ( (None, 64) 0
GlobalAveragePooling2D)
reshape_1 (Reshape) (None, 1, 1, 64) 0
flatten (Flatten) (None, 64) 0
dense_5 (Dense) (None, 33) 2145
act_softmax (Activation) (None, 33) 0
=================================================================
Total params: 23,713
Trainable params: 23,073
Non-trainable params: 640
_________________________________________________________________
import numpy as np
from sklearn.metrics import accuracy_score
# Check Keras Model performance
potentials_keras = model_keras.predict(x_valid)
preds_keras = np.squeeze(np.argmax(potentials_keras, 1))
accuracy = accuracy_score(y_valid, preds_keras)
print("Accuracy: " + "{0:.2f}".format(100 * accuracy) + "%")
1/308 [..............................] - ETA: 38s
51/308 [===>..........................] - ETA: 0s
101/308 [========>.....................] - ETA: 0s
151/308 [=============>................] - ETA: 0s
198/308 [==================>...........] - ETA: 0s
248/308 [=======================>......] - ETA: 0s
299/308 [============================>.] - ETA: 0s
308/308 [==============================] - 0s 1ms/step
Accuracy: 93.26%
3. Load a pre-trained quantized Keras model
The above native Keras model has been quantized to 8-bit. Note that a 4-bit version is also available from the model zoo.
from quantizeml.models import load_model
# Load the pre-trained quantized model
model_file = get_file(
"ds_cnn_kws_i8_w8_a8.h5",
"https://data.brainchip.com/models/AkidaV2/ds_cnn/ds_cnn_kws_i8_w8_a8.h5",
cache_subdir='models')
model_keras_quantized = load_model(model_file)
model_keras_quantized.summary()
# Check Model performance
potentials_keras_q = model_keras_quantized.predict(x_valid)
preds_keras_q = np.squeeze(np.argmax(potentials_keras_q, 1))
accuracy_q = accuracy_score(y_valid, preds_keras_q)
print("Accuracy: " + "{0:.2f}".format(100 * accuracy_q) + "%")
Downloading data from https://data.brainchip.com/models/AkidaV2/ds_cnn/ds_cnn_kws_i8_w8_a8.h5
8192/178480 [>.............................] - ETA: 0s
178480/178480 [==============================] - 0s 0us/step
Model: "ds_cnn_kws"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input (InputLayer) [(None, 49, 10, 1)] 0
rescaling (QuantizedRescali (None, 49, 10, 1) 0
ng)
conv_0 (QuantizedConv2D) (None, 25, 5, 64) 1664
conv_0/relu (QuantizedReLU) (None, 25, 5, 64) 128
dw_separable_1 (QuantizedDe (None, 25, 5, 64) 704
pthwiseConv2D)
pw_separable_1 (QuantizedCo (None, 25, 5, 64) 4160
nv2D)
pw_separable_1/relu (Quanti (None, 25, 5, 64) 128
zedReLU)
dw_separable_2 (QuantizedDe (None, 25, 5, 64) 704
pthwiseConv2D)
pw_separable_2 (QuantizedCo (None, 25, 5, 64) 4160
nv2D)
pw_separable_2/relu (Quanti (None, 25, 5, 64) 128
zedReLU)
dw_separable_3 (QuantizedDe (None, 25, 5, 64) 704
pthwiseConv2D)
pw_separable_3 (QuantizedCo (None, 25, 5, 64) 4160
nv2D)
pw_separable_3/relu (Quanti (None, 25, 5, 64) 128
zedReLU)
dw_separable_4 (QuantizedDe (None, 25, 5, 64) 704
pthwiseConv2D)
pw_separable_4 (QuantizedCo (None, 25, 5, 64) 4160
nv2D)
pw_separable_4/relu (Quanti (None, 25, 5, 64) 0
zedReLU)
pw_separable_4/global_avg ( (None, 64) 2
QuantizedGlobalAveragePooli
ng2D)
reshape_1 (QuantizedReshape (None, 1, 1, 64) 0
)
flatten (QuantizedFlatten) (None, 64) 0
dense_5 (QuantizedDense) (None, 33) 2145
dequantizer (Dequantizer) (None, 33) 0
act_softmax (Activation) (None, 33) 0
=================================================================
Total params: 23,779
Trainable params: 22,753
Non-trainable params: 1,026
_________________________________________________________________
1/308 [..............................] - ETA: 10:08
9/308 [..............................] - ETA: 1s
18/308 [>.............................] - ETA: 1s
27/308 [=>............................] - ETA: 1s
36/308 [==>...........................] - ETA: 1s
44/308 [===>..........................] - ETA: 1s
53/308 [====>.........................] - ETA: 1s
62/308 [=====>........................] - ETA: 1s
71/308 [=====>........................] - ETA: 1s
80/308 [======>.......................] - ETA: 1s
89/308 [=======>......................] - ETA: 1s
98/308 [========>.....................] - ETA: 1s
107/308 [=========>....................] - ETA: 1s
116/308 [==========>...................] - ETA: 1s
125/308 [===========>..................] - ETA: 1s
134/308 [============>.................] - ETA: 1s
143/308 [============>.................] - ETA: 1s
152/308 [=============>................] - ETA: 0s
161/308 [==============>...............] - ETA: 0s
170/308 [===============>..............] - ETA: 0s
179/308 [================>.............] - ETA: 0s
188/308 [=================>............] - ETA: 0s
197/308 [==================>...........] - ETA: 0s
206/308 [===================>..........] - ETA: 0s
215/308 [===================>..........] - ETA: 0s
224/308 [====================>.........] - ETA: 0s
233/308 [=====================>........] - ETA: 0s
242/308 [======================>.......] - ETA: 0s
251/308 [=======================>......] - ETA: 0s
260/308 [========================>.....] - ETA: 0s
269/308 [=========================>....] - ETA: 0s
277/308 [=========================>....] - ETA: 0s
286/308 [==========================>...] - ETA: 0s
295/308 [===========================>..] - ETA: 0s
304/308 [============================>.] - ETA: 0s
308/308 [==============================] - 4s 6ms/step
Accuracy: 93.12%
4. Conversion to Akida
The converted model is Akida 2.0 compatible and its performance evaluation is done using the Akida simulator.
from cnn2snn import convert
# Convert the model
model_akida = convert(model_keras_quantized)
model_akida.summary()
Model Summary
______________________________________________
Input shape Output shape Sequences Layers
==============================================
[49, 10, 1] [1, 1, 33] 1 11
______________________________________________
________________________________________________________________
Layer (type) Output shape Kernel shape
=============== SW/conv_0-dequantizer (Software) ===============
conv_0 (InputConv2D) [25, 5, 64] (5, 5, 1, 64)
________________________________________________________________
dw_separable_1 (DepthwiseConv2D) [25, 5, 64] (3, 3, 64, 1)
________________________________________________________________
pw_separable_1 (Conv2D) [25, 5, 64] (1, 1, 64, 64)
________________________________________________________________
dw_separable_2 (DepthwiseConv2D) [25, 5, 64] (3, 3, 64, 1)
________________________________________________________________
pw_separable_2 (Conv2D) [25, 5, 64] (1, 1, 64, 64)
________________________________________________________________
dw_separable_3 (DepthwiseConv2D) [25, 5, 64] (3, 3, 64, 1)
________________________________________________________________
pw_separable_3 (Conv2D) [25, 5, 64] (1, 1, 64, 64)
________________________________________________________________
dw_separable_4 (DepthwiseConv2D) [25, 5, 64] (3, 3, 64, 1)
________________________________________________________________
pw_separable_4 (Conv2D) [1, 1, 64] (1, 1, 64, 64)
________________________________________________________________
dense_5 (Dense2D) [1, 1, 33] (64, 33)
________________________________________________________________
dequantizer (Dequantizer) [1, 1, 33] N/A
________________________________________________________________
# Check Akida model performance
preds_akida = model_akida.predict_classes(x_valid, num_classes=num_classes)
accuracy = accuracy_score(y_valid, preds_akida)
print("Accuracy: " + "{0:.2f}".format(100 * accuracy) + "%")
# For non-regression purposes
assert accuracy > 0.9
Accuracy: 93.13%
5. Confusion matrix
The confusion matrix provides a good summary of what mistakes the network is making.
Per scikit-learn convention it displays the true class in each row (ie on each row you can see what the network predicted for the corresponding word).
Please refer to the Tensorflow audio recognition example for a detailed explanation of the confusion matrix.
import itertools
import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix
# Create confusion matrix
cm = confusion_matrix(y_valid, preds_akida,
labels=list(word_to_index.values()))
# Normalize
cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
# Display confusion matrix
plt.rcParams["figure.figsize"] = (16, 16)
plt.figure()
title = 'Confusion matrix'
cmap = plt.cm.Blues
plt.imshow(cm, interpolation='nearest', cmap=cmap)
plt.title(title)
plt.colorbar()
tick_marks = np.arange(len(word_to_index))
plt.xticks(tick_marks, word_to_index, rotation=45)
plt.yticks(tick_marks, word_to_index)
thresh = cm.max() / 2.
for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
plt.text(j,
i,
format(cm[i, j], '.2f'),
horizontalalignment="center",
color="white" if cm[i, j] > thresh else "black")
plt.ylabel('True label')
plt.xlabel('Predicted label')
plt.autoscale()
plt.show()
Total running time of the script: (0 minutes 18.408 seconds)