From 61e9a318a3e9333fd89fe43f9fd7a83ab1eb8171 Mon Sep 17 00:00:00 2001 From: idan-arm Date: Tue, 15 Feb 2022 17:19:54 +0200 Subject: [PATCH] tiny-wav2letter --- README.md | 22 +- .../tiny_wav2letter/tflite_int8/README.md | 74 ++++ .../tflite_int8/definition.yaml | 60 +++ .../demo_input/84-121550-0000.flac | Bin 0 -> 162628 bytes .../tflite_int8/inference_demo.ipynb | 323 +++++++++++++++ .../tflite_int8/model_development_guide.md | 58 +++ .../tflite_int8/recreate_code/.idea/misc.xml | 7 + .../recreate_code/.idea/workspace.xml | 156 +++++++ .../tflite_int8/recreate_code/README.md | 13 + .../__pycache__/load_mfccs.cpython-36.pyc | Bin 0 -> 5377 bytes .../__pycache__/tinywav2letter.cpython-36.pyc | Bin 0 -> 3980 bytes .../__pycache__/train_model.cpython-36.pyc | Bin 0 -> 6699 bytes .../tflite_int8/recreate_code/corpus.py | 171 ++++++++ .../recreate_code/evaluate_saved_weights.py | 52 +++ .../tflite_int8/recreate_code/load_mfccs.py | 177 ++++++++ .../recreate_code/preprocessing.py | 257 ++++++++++++ .../preprocessing_convert_to_flac.py | 100 +++++ .../preprocessing_fluent_speech_commands.py | 50 +++ .../recreate_code/prune_and_quantise_model.py | 386 ++++++++++++++++++ .../recreate_code/recreate_model.sh | 11 + .../recreate_code/requirements.txt | 10 + .../pruned_tiny_wav2letter/saved_model.pb | 3 + .../variables/variables.data-00000-of-00001 | 3 + .../variables/variables.index | 3 + .../tiny_wav2letter/saved_model.pb | 3 + .../variables/variables.data-00000-of-00001 | 3 + .../tiny_wav2letter/variables/variables.index | 3 + .../recreate_code/tinywav2letter.py | 152 +++++++ .../tflite_int8/recreate_code/train_model.py | 267 ++++++++++++ .../testing_input/input_1_int8/0.npy | 3 + .../testing_output/Identity_int8/0.npy | 3 + .../tflite_int8/tiny_wav2letter_int8.tflite | 3 + .../tflite_pruned_int8/README.md | 75 ++++ .../tflite_pruned_int8/definition.yaml | 60 +++ .../demo_input/84-121550-0000.flac | Bin 0 -> 162628 bytes .../tflite_pruned_int8/inference_demo.ipynb | 323 +++++++++++++++ .../model_development_guide.md | 58 +++ .../recreate_code/.idea/misc.xml | 7 + .../recreate_code/.idea/workspace.xml | 156 +++++++ .../recreate_code/README.md | 13 + .../__pycache__/load_mfccs.cpython-36.pyc | Bin 0 -> 5377 bytes .../__pycache__/tinywav2letter.cpython-36.pyc | Bin 0 -> 3980 bytes .../__pycache__/train_model.cpython-36.pyc | Bin 0 -> 6699 bytes .../recreate_code/corpus.py | 171 ++++++++ .../recreate_code/evaluate_saved_weights.py | 52 +++ .../recreate_code/load_mfccs.py | 177 ++++++++ .../recreate_code/preprocessing.py | 257 ++++++++++++ .../preprocessing_convert_to_flac.py | 100 +++++ .../preprocessing_fluent_speech_commands.py | 50 +++ .../recreate_code/prune_and_quantise_model.py | 386 ++++++++++++++++++ .../recreate_code/recreate_model.sh | 11 + .../recreate_code/requirements.txt | 10 + .../pruned_tiny_wav2letter/saved_model.pb | 3 + .../variables/variables.data-00000-of-00001 | 3 + .../variables/variables.index | 3 + .../tiny_wav2letter/saved_model.pb | 3 + .../variables/variables.data-00000-of-00001 | 3 + .../tiny_wav2letter/variables/variables.index | 3 + .../recreate_code/tinywav2letter.py | 152 +++++++ .../recreate_code/train_model.py | 267 ++++++++++++ .../testing_input/input_1_int8/0.npy | 3 + .../testing_output/Identity_int8/0.npy | 3 + .../tiny_wav2letter_pruned_int8.tflite | 3 + 63 files changed, 4724 insertions(+), 1 deletion(-) create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/README.md create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/definition.yaml create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/demo_input/84-121550-0000.flac create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/inference_demo.ipynb create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/model_development_guide.md create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/.idea/misc.xml create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/.idea/workspace.xml create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/README.md create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/__pycache__/load_mfccs.cpython-36.pyc create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/__pycache__/tinywav2letter.cpython-36.pyc create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/__pycache__/train_model.cpython-36.pyc create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/corpus.py create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/evaluate_saved_weights.py create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/load_mfccs.py create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/preprocessing.py create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/preprocessing_convert_to_flac.py create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/preprocessing_fluent_speech_commands.py create mode 100755 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/prune_and_quantise_model.py create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/recreate_model.sh create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/requirements.txt create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/pruned_tiny_wav2letter/saved_model.pb create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/pruned_tiny_wav2letter/variables/variables.data-00000-of-00001 create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/pruned_tiny_wav2letter/variables/variables.index create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/tiny_wav2letter/saved_model.pb create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/tiny_wav2letter/variables/variables.data-00000-of-00001 create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/tiny_wav2letter/variables/variables.index create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/tinywav2letter.py create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/train_model.py create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/testing_input/input_1_int8/0.npy create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/testing_output/Identity_int8/0.npy create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_int8/tiny_wav2letter_int8.tflite create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/README.md create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/definition.yaml create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/demo_input/84-121550-0000.flac create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/inference_demo.ipynb create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/model_development_guide.md create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/.idea/misc.xml create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/.idea/workspace.xml create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/README.md create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/__pycache__/load_mfccs.cpython-36.pyc create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/__pycache__/tinywav2letter.cpython-36.pyc create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/__pycache__/train_model.cpython-36.pyc create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/corpus.py create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/evaluate_saved_weights.py create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/load_mfccs.py create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/preprocessing.py create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/preprocessing_convert_to_flac.py create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/preprocessing_fluent_speech_commands.py create mode 100755 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/prune_and_quantise_model.py create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/recreate_model.sh create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/requirements.txt create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/pruned_tiny_wav2letter/saved_model.pb create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/pruned_tiny_wav2letter/variables/variables.data-00000-of-00001 create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/pruned_tiny_wav2letter/variables/variables.index create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/tiny_wav2letter/saved_model.pb create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/tiny_wav2letter/variables/variables.data-00000-of-00001 create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/tiny_wav2letter/variables/variables.index create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/tinywav2letter.py create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/train_model.py create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/testing_input/input_1_int8/0.npy create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/testing_output/Identity_int8/0.npy create mode 100644 models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/tiny_wav2letter_pruned_int8.tflite diff --git a/README.md b/README.md index da958db..1c3ba33 100644 --- a/README.md +++ b/README.md @@ -361,9 +361,29 @@ :heavy_check_mark: 0.0783 + + Tiny Wav2letter INT8 * + INT8 + TensorFlow Lite + :heavy_check_mark: + :heavy_check_mark: + :heavy_multiplication_x: + :heavy_check_mark: + 0.0348 + + + Tiny Wav2letter Pruned INT8 * + INT8 + TensorFlow Lite + :heavy_check_mark: + :heavy_check_mark: + :heavy_multiplication_x: + :heavy_check_mark: + 0.0283 + -**Dataset**: LibriSpeech +**Dataset**: LibriSpeech, Fluent Speech ## Superresolution diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/README.md b/models/speech_recognition/tiny_wav2letter/tflite_int8/README.md new file mode 100644 index 0000000..1a2c2c4 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/README.md @@ -0,0 +1,74 @@ +# Tiny Wav2letter INT8 + +## Description +Tiny Wav2letter is a tiny version of the original Wav2Letter model. It is a convolutional speech recognition neural network. This implementation was created by Arm, pruned to 50% sparsity, fine-tuned and quantized using the TensorFlow Model Optimization Toolkit. + + + +## License +[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) + +## Network Information +| Network Information | Value | +|---------------------|----------------| +| Framework | TensorFlow Lite | +| SHA-1 Hash | 13ca2294ba4bbb1f1c6c5e663cb532d58cd76a6b | +| Size (Bytes) | 3997112 | +| Provenance | https://github.com/ARM-software/ML-zoo/tree/master/models/speech_recognition/wav2letter | +| Paper | https://arxiv.org/abs/1609.03193 | + +## Performance + +| Platform | Optimized | +|----------|:---------:| +| Cortex-A |:heavy_check_mark: | +| Cortex-M |:heavy_check_mark: | +| Mali GPU |:heavy_multiplication_x: | +| Ethos U |:heavy_check_mark: | + +### Key +* :heavy_check_mark: - Will run on this platform. +* :heavy_multiplication_x: - Will not run on this platform. + +## Accuracy +Dataset: Fluent Speech (trianed on LibriSpeech,Mini LibrySpeech,Fluent Speech) +
+Please note that Fluent Speech dataset hosted on Kaggle is a licensed dataset. + +| Metric | Value | +|--------|-------| +| LER | 0.0348 | +| WER | 0.112 | + +## Optimizations +| Optimization | Value | +|--------------|---------| +| Quantization | INT8 | + +## Network Inputs + + + + + + + + + + + +
Input Node NameShapeDescription
input_1_int8(1, 296, 39)Speech converted to MFCCs and quantized to INT8
+ +## Network Outputs + + + + + + + + + + + +
Output Node NameShapeDescription
Identity_int8(1, 1, 148, 29)A tensor of time and class probabilities, that represents the probability of each class at each timestep. Should be passed to a decoder. For example ctc_beam_search_decoder.
diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/definition.yaml b/models/speech_recognition/tiny_wav2letter/tflite_int8/definition.yaml new file mode 100644 index 0000000..6f59991 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/definition.yaml @@ -0,0 +1,60 @@ +author_notes: null +benchmark: + benchmark_description: please note that fluent-speech-corpus dataset hosted on Kaggle + is a licensed dataset. + benchmark_link: https://www.kaggle.com/tommyngx/fluent-speech-corpus + benchmark_metrics: + LER: '0.0348' + WER: '0.1123' + benchmark_name: Fluent speech +description: "Tiny Wav2letter is a tiny version of the original Wav2Letter model.\ + \ It is a convolutional speech recognition neural network. This implementation was\ + \ created by Arm, pruned to 50% sparsity, fine-tuned and quantized using the TensorFlow\ + \ Model Optimization Toolkit.\r\n\r\n" +license: +- Apache-2.0 +network: + datatype: int8 + file_size_bytes: 3997112 + filename: tiny_wav2letter_int8.tflite + framework: TensorFlow Lite + framework_version: 2.4.1 + hash: + algorithm: sha1 + value: 13ca2294ba4bbb1f1c6c5e663cb532d58cd76a6b + provenance: https://github.com/ARM-software/ML-zoo/tree/master/models/speech_recognition/wav2letter + training: LibriSpeech,Mini LibrySpeech,fluent speech +network_parameters: + input_nodes: + - description: Speech converted to MFCCs and quantized to INT8 + example_input: + path: models/speech_recognition/tiny_wav2letter/tflite_int8/testing_input/input_1_int8 + input_datatype: int8 + name: input_1_int8 + shape: + - 1 + - 296 + - 39 + output_nodes: + - description: A tensor of time and class probabilities, that represents the probability + of each class at each timestep. Should be passed to a decoder. For example ctc_beam_search_decoder. + example_output: + path: models/speech_recognition/tiny_wav2letter/tflite_int8/testing_output/Identity_int8 + name: Identity_int8 + output_datatype: int8 + shape: + - 1 + - 1 + - 148 + - 29 +network_quality: + quality_level: Deployable + quality_level_hero_hw: null +operators: + TensorFlow Lite: + - CONV_2D + - DEQUANTIZE + - LEAKY_RELU + - QUANTIZE + - RESHAPE +paper: https://arxiv.org/abs/1609.03193 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/demo_input/84-121550-0000.flac b/models/speech_recognition/tiny_wav2letter/tflite_int8/demo_input/84-121550-0000.flac new file mode 100644 index 0000000000000000000000000000000000000000..8dd88a8f343cf1ca266017a81f58078bbe769b28 GIT binary patch literal 162628 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 string (0 -> a 1 -> b)\n", + " string = \"\"\n", + " alphabet = \"abcdefghijklmnopqrstuvwxyz' @\"\n", + " alphabet_dict = {}\n", + " count = 0\n", + " for x in alphabet:\n", + " alphabet_dict[count] = x\n", + " count += 1\n", + " for letter in trans_int:\n", + " letter_np = np.array(letter).item(0)\n", + " if letter_np != 28:\n", + " string += alphabet_dict[letter_np]\n", + " return string\n", + "\n", + "def ctc_wer(y_true, y_predict):\n", + " sparse_labels, logit_length, y_predict = ctc_preparation(y_true, y_predict)\n", + " decoded, log_probabilities = tf.nn.ctc_greedy_decoder(\n", + " y_predict, tf.cast(logit_length, tf.int32), merge_repeated=True\n", + " )\n", + " true_sentence = tf.cast(sparse_labels.values, tf.int32)\n", + " return wer(str(trans_int_to_string(decoded[0].values)), str(trans_int_to_string(true_sentence)))" + ], + "outputs": [], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The TFLite file requires inputs of size 296, so we apply a window to the input" + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 94, + "source": [ + "def evaluate_tflite(tflite_path, input_window_length = 296):\n", + " \"\"\"Evaluates tflite (fp32, int8).\"\"\"\n", + " results = []\n", + " data, label = transform_audio_to_mfcc(audio_file, transcript_ints)\n", + "\n", + " interpreter = tf.lite.Interpreter(model_path=tflite_path, num_threads=multiprocessing.cpu_count())\n", + " interpreter.allocate_tensors()\n", + " input_chunk = interpreter.get_input_details()[0]\n", + " output_details = interpreter.get_output_details()[0]\n", + "\n", + " input_shape = input_chunk[\"shape\"]\n", + " log(\"eval_model() - input_shape: {}\".format(input_shape))\n", + " input_dtype = input_chunk[\"dtype\"]\n", + " output_dtype = output_details[\"dtype\"]\n", + "\n", + " # Check if the input/output type is quantized,\n", + " # set scale and zero-point accordingly\n", + " if input_dtype != tf.float32:\n", + " input_scale, input_zero_point = input_chunk[\"quantization\"]\n", + " else:\n", + " input_scale, input_zero_point = 1, 0\n", + "\n", + " if output_dtype != tf.float32:\n", + " output_scale, output_zero_point = output_details[\"quantization\"]\n", + " else:\n", + " output_scale, output_zero_point = 1, 0\n", + "\n", + "\n", + " data = data / input_scale + input_zero_point\n", + " # Round the data up if dtype is int8, uint8 or int16\n", + " if input_dtype is not np.float32:\n", + " data = np.round(data)\n", + "\n", + " while data.shape[1] < input_window_length:\n", + " data = np.append(data, data[:, -2:-1, :], axis=1)\n", + " # Zero-pad any odd-length inputs\n", + " if data.shape[1] % 2 == 1:\n", + " # log('Input length is odd, zero-padding to even (first layer has stride 2)')\n", + " data = np.concatenate([data, np.zeros((1, 1, data.shape[2]), dtype=input_dtype)], axis=1)\n", + "\n", + " context = 24 + 2 * (7 * 3 + 16) # = 98 - theoretical max receptive field on each side\n", + " size = input_chunk['shape'][1]\n", + " inner = size - 2 * context\n", + " data_end = data.shape[1]\n", + "\n", + " # Initialize variables for the sliding window loop\n", + " data_pos = 0\n", + " outputs = []\n", + "\n", + " while data_pos < data_end:\n", + " if data_pos == 0:\n", + " # Align inputs from the first window to the start of the data and include the intial context in the output\n", + " start = data_pos\n", + " end = start + size\n", + " y_start = 0\n", + " y_end = y_start + (size - context) // 2\n", + " data_pos = end - context\n", + " elif data_pos + inner + context >= data_end:\n", + " # Shift left to align final window to the end of the data and include the final context in the output\n", + " shift = (data_pos + inner + context) - data_end\n", + " start = data_pos - context - shift\n", + " end = start + size\n", + " assert start >= 0\n", + " y_start = (shift + context) // 2 # Will be even because we assert it above\n", + " y_end = size // 2\n", + " data_pos = data_end\n", + " else:\n", + " # Capture only the inner region from mid-input inferences, excluding output from both context regions\n", + " start = data_pos - context\n", + " end = start + size\n", + " y_start = context // 2\n", + " y_end = y_start + inner // 2\n", + " data_pos = end - context\n", + "\n", + " interpreter.set_tensor(input_chunk[\"index\"], tf.cast(data[:, start:end, :], input_dtype))\n", + " interpreter.invoke()\n", + " cur_output_data = interpreter.get_tensor(output_details[\"index\"])[:, :, y_start:y_end, :]\n", + " cur_output_data = output_scale * (\n", + " cur_output_data.astype(np.float32) - output_zero_point\n", + " )\n", + " outputs.append(cur_output_data)\n", + "\n", + " complete = np.concatenate(outputs, axis=2)\n", + " LER, output = ctc_ler(label, complete)\n", + " WER = ctc_wer(label, complete)\n", + " return output, LER , WER\n" + ], + "outputs": [], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 107, + "source": [ + "wav2letter_tflite_path = \"tflite_int8/tiny_wav2letter_int8.tflite\"\n", + "output, LER , WER = evaluate_tflite(wav2letter_tflite_path)\n", + "\n", + "decoded_output = [index_dict[value] for value in output[0]]\n", + "log(f'Transcribed File: {\"\".join(decoded_output)}')\n", + "log(f'Letter Error Rate is {LER}')\n", + "log(f'Word Error Rate is {WER}')" + ], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "******* eval_model() - input_shape: [ 1 296 39]\n", + "******* Input length is odd, zero-padding to even (first layer has stride 2)\n", + "******* Transcribed File: but with full ravishment the hours of prime singing received they in the midst of leaves that everborea burden to their rimes\n", + "******* Letter Error Rate is 0.03125\n", + "******* Word Error Rate is 1.05\n" + ] + } + ], + "metadata": {} + } + ], + "metadata": { + "orig_nbformat": 4, + "language_info": { + "name": "python", + "version": "3.8.2", + "mimetype": "text/x-python", + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "pygments_lexer": "ipython3", + "nbconvert_exporter": "python", + "file_extension": ".py" + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3.8.2 64-bit ('env': venv)" + }, + "interpreter": { + "hash": "4b529a2edd0e262cfd8353ba70b138cbba10314325c544d99b9316c477c7841b" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/model_development_guide.md b/models/speech_recognition/tiny_wav2letter/tflite_int8/model_development_guide.md new file mode 100644 index 0000000..546a975 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/model_development_guide.md @@ -0,0 +1,58 @@ +# Model Development Guide + +This document describes the process of training a model from scratch, using the Tiny Wav2Letter model as an example. + +## Datasets + +The first thing to decide is which dataset the model is to be trained on. Most commonly used datasets can either be found online or in the ARM AWS S3 bucket. In the case of Tiny Wav2Letter, both the LibriSpeech dataset hosted on [OpenSLR](http://www.openslr.org/resources.php) and fluent-speech-corpus dataset hosted on [Kaggle](https://www.kaggle.com/tommyngx/fluent-speech-corpus) were used to train the model. +! ! please note that fluent-speech-corpus dataset hosted on [Kaggle](https://www.kaggle.com/tommyngx/fluent-speech-corpus) is a licensed dataset. + +## Preprocessing + +The dataset is often not in the right format for training, so preprocessing steps must be taken. In this case, the LibriSpeech dataset consists of audio files, however the paper stated that as input MFCCs should be used, so the audio files needed to be converted. It is to be recommended that all preprocessing be performed offline, as this will make the actual training process faster, as the data is already in the correct format. The most convenient way to store the preprocessed data is using [TFRecords](https://www.tensorflow.org/tutorials/load_data/tfrecord), as these are very easily loaded into TFDatasets. While it can take a long time to write the whole dataset to a TFRecord file, it is outweighed by the time saved during training. +Please note: input audio data sample rate is 16K +## Model Architecture + +The model architecture can generally be found from a variety of sources. If a similar model exists in the IMZ, then [Netron](https://netron.app) can be used to inspect the TFLite file. The original paper the model was proposed in will also define the architecture. The model should ideally be defined using the Tensorflow Functional API rather than the sequential API. + +## Loss Function and Metrics + +The loss function and desired metrics will be defined by the model. If at all possible, structure the data such that the input to the loss function is in the form (y_true, y_predicted) as this will enable model.fit to be used and avoid custom training loops. TensorFlow has lots of standard loss functions straight out of the box, but if need be custom loss functions can be defined, as was the case in TinyWav2Letter. + +## Model Training + +If everything else has been set up properly, the code here should not be complicated. Load the datasets, create an instance of the model, and then ideally run model.fit but if that's not possible use tf.GradientTape. Use callbacks to write to a log directory (tf.keras.callbacks.Tensorboard), then use Tensorboard to visualise the training process. One can use the [TensorFlow Profiler](https://www.tensorflow.org/guide/profiler) to identify bottlenecks in the training pipeline and speed up the training process. Another useful callback is tf.keras.callbacks.ModelCheckpoint which saves a checkpoint at defined intervals, so one can pick up training from where it was left off. Generally we will want a training set, a validation set and a test set, with normally about a 90:5:5 split. If the model performs well on the training set but not on the validation set or test set, then the model is overfitting. This can be reduced by introducing regularisation, increasing the amount of data, reducing model complexity or adjusting hyperparameters. In the case of TinyWav2Letter, the model was initially trained on the full-size LibriSpeech Dataset to capture the features of speech, then fine-tuned on the much smaller Mini LibriSpeech to improve the accuracy on the smaller dataset, then fine-tuned on fluent-speech-corpus dataset + +## Optimisation and Conversion + +Once the model has been trained to satisfaction, it can be optionally be optimised using the TensorFlow Model Optimization Toolkit. Pruning sets a specified percentage of the weights to be 0, so the model is sparser, which can lead to faster inference. Clustering clusters together weights of similar values, reducing the number of unique values. This again leads to faster inference. Quantisation (eg to INT8) converts all the weights to INT8 representations, giving a 4x reduction in size compared to FP32. If the quantisation process affects the metric too severely, quantisation aware training can be performed, which fine-tunes the model and makes it more robust to quantisation. Quantisation aware training requires at least Tensorflow 2.5.0. The final step is to convert the model to the TFLite model format. If using INT8 conversion, one must define a representative dataset to calibrate the model. + +## Training a smaller FP32 Keras Model + +The trained Wav2Letter model then serves as the foundation for investigations into how best to reduce the size of the network.   + +There are three hyperparameters relevant to the size of the network. They are: + +- Number of layers +- Number of filters in each convolutional layer +- Stride of each convolutional filter + +The following table shows chose architecture for Tiny Wav2Letter and the effect that it has on the output size.  + +| Identifier | Total Number of Layers | Number of middle Convolutional Layers | Corresponding number of filters  | Number of filters in the antepenultimate Conv2D Layer | Number of filters in the penultimate Conv2D Layer | +| ------ | ------ | ------ | ------ | ------ | ------ | +| Wav2Letter | 11 | 7 | 250 | 2000 | 2000 | +| Tiny Wav2Letter | 6 | 5 | 100 | 750 | 750 | + +| Identifier | Size (MB) | LER | WER | +| ------ | ------ | ------ | ------ | +| Wav2Letter INT8| 22.7 | 0.0877** | N/A | +| Wav2Letter INT8 pruned| 22.7 | 0.0783** | N/A | +| Tiny Wav2Letter FP32| 15.6* | 0.0351 | 0.0714 | +| Tiny Wav2Letter FP32 pruned| 15.6* | 0.0266 | 0.0577 | +| Tiny Wav2Letter INT8| 3.81 | 0.0348 | 0.1123 | +| Tiny Wav2Letter INT8 pruned| 3.81 | 0.0283 | 0.0886 | + +"*" - the size is according to the tflite model \ +** trained on different dataset + diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/.idea/misc.xml b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/.idea/misc.xml new file mode 100644 index 0000000..625040c --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/.idea/workspace.xml b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/.idea/workspace.xml new file mode 100644 index 0000000..1dcd832 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/.idea/workspace.xml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1639400202242 + + + + + + + + + + + file://$PROJECT_DIR$/preprocessing.py + 148 + + + file://$PROJECT_DIR$/train_model.py + 158 + + + file://$PROJECT_DIR$/train_model.py + 99 + + + + + \ No newline at end of file diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/README.md b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/README.md new file mode 100644 index 0000000..90ff4be --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/README.md @@ -0,0 +1,13 @@ +# Tiny Wav2letter FP32/INT8/INT8_Pruned Model Re-Creation +This folder contains a script that allows for the model to be re-created from scratch. +## Datasets +Tiny Wav2Letter was trianed on both LibriSpeech dataset hosted on OpenSLR and fluent-speech-corpus dataset hosted on Kaggle. +Please note that fluent-speech-corpus dataset hosted on [Kaggle](https://www.kaggle.com/tommyngx/fluent-speech-corpus) is a licensed dataset. +## Requirements +The script in this folder requires that the following must be installed: +- Python 3.6 +- Create new dir: fluent_speech_commands_dataset +- (LICENSED DATASET!!) Download and extract fluent-speech-corpus from: https://www.kaggle.com/tommyngx/fluent-speech-corpus to fluent_speech_commands_dataset dir + +## Running The Script +To run the script, run the following in a terminal: `./recreate_model.sh` diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/__pycache__/load_mfccs.cpython-36.pyc b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/__pycache__/load_mfccs.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b74e0406fb81890c022e7205cecf836d0ccdfaa7 GIT binary patch literal 5377 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 literal 0 HcmV?d00001 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/corpus.py b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/corpus.py new file mode 100644 index 0000000..b5bd5ce --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/corpus.py @@ -0,0 +1,171 @@ +# Copyright (C) 2020 Arm Limited or its affiliates. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the License); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an AS IS BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import tarfile +import urllib.request + + +class SpeechCorpusProvider: + """ + Ensures the availability and downloads the speech corpus if necessary + """ + + DATA_SOURCE = { + "librispeech_full_size": + {"DATA_SETS": + { + ('train', 'train-clean-100'), + ('train', 'train-clean-360'), + ('val', 'dev-clean') + }, + "BASE_URL": 'http://www.openslr.org/resources/12/' + }, + "librispeech_reduced_size": + {"DATA_SETS": + { + ('train', 'train-clean-5'), + ('val', 'dev-clean-2') + }, + "BASE_URL": 'http://www.openslr.org/resources/31/' + } + } + + SET_FILE_EXTENSION = '.tar.gz' + TAR_ROOT = 'LibriSpeech/' + + def __init__(self, data_directory): + """ + Creates a new SpeechCorpusProvider with the root directory `data_directory`. + The speech corpus is downloaded and extracted into sub-directories. + + Args: + data_directory: the root directory to use, e.g. data/ + """ + + self._data_directory = data_directory + self._make_dir_if_not_exists(data_directory) + self.data_sets = SpeechCorpusProvider.DATA_SOURCE[data_directory]['DATA_SETS'] + self.base_url = SpeechCorpusProvider.DATA_SOURCE[data_directory]['BASE_URL'] + + @staticmethod + def _make_dir_if_not_exists(directory): + """ + Helper function to create a directory if it doesn't exist. + + Args: + directory: directory to create + """ + + if not os.path.exists(directory): + os.makedirs(directory) + + def _download_if_not_exists(self, remote_file_name): + """ + Downloads the given `remote_file_name` if not yet stored in the `data_directory` + + Args: + remote_file_name: the file to download + + Returns: path to downloaded file + """ + + path = os.path.join(self._data_directory, remote_file_name) + if not os.path.exists(path): + print('Downloading {}...'.format(remote_file_name)) + urllib.request.urlretrieve(self.base_url + remote_file_name, path) + return path + + @staticmethod + def _extract_from_to(tar_file_name, source, target_directory): + """ + Extract all necessary files `source` from `tar_file_name` into `target_directory` + + Args: + tar_file_name: the tar file to extract from + source: the directory in the root to extract + target_directory: the directory to store the files in + """ + + print('Extracting {}...'.format(tar_file_name)) + with tarfile.open(tar_file_name, 'r:gz') as tar: + source_members = [ + tarinfo for tarinfo in tar.getmembers() + if tarinfo.name.startswith(SpeechCorpusProvider.TAR_ROOT + source) + ] + for member in source_members: + # Extract without prefix + member.name = member.name.replace(SpeechCorpusProvider.TAR_ROOT, '') + tar.extractall(target_directory, source_members) + + def _is_ready(self): + """ + Returns whether all `data_sets` are downloaded and extracted + + Args: + data_sets: list of the datasets to ensure + + Returns: bool, is ready to use + + """ + + data_set_paths = [os.path.join(set_type, set_name) + for set_type, set_name in self.data_sets] + + return all([os.path.exists(os.path.join(self._data_directory, data_set)) + for data_set in data_set_paths]) + + def _download(self): + """ + Download the given `data_sets` + + Args: + data_sets: a list of the datasets to download + """ + + for data_set_type, data_set_name in self.data_sets: + remote_file = data_set_name + SpeechCorpusProvider.SET_FILE_EXTENSION + self._download_if_not_exists(remote_file) + + def _extract(self): + """ + Extract all necessary files from the given `data_sets` + """ + + for data_set_type, data_set_name in self.data_sets: + local_file = os.path.join(self._data_directory, data_set_name + SpeechCorpusProvider.SET_FILE_EXTENSION) + target_directory = self._data_directory + self._extract_from_to(local_file, data_set_name, target_directory) + + def ensure_availability(self): + """ + Ensure that all datasets are downloaded and extracted. If this is not the case, + the download and extraction is initated. + """ + + if not self._is_ready(): + self._download() + self._extract() + + +if __name__=="__main__": + full_size_corpus = SpeechCorpusProvider("librispeech_full_size") + full_size_corpus.ensure_availability() + + reduced_size_corpus = SpeechCorpusProvider("librispeech_reduced_size") + reduced_size_corpus.ensure_availability() + + diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/evaluate_saved_weights.py b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/evaluate_saved_weights.py new file mode 100644 index 0000000..399a914 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/evaluate_saved_weights.py @@ -0,0 +1,52 @@ +# Copyright (C) 2020 Arm Limited or its affiliates. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the License); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an AS IS BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse + +import tensorflow as tf + +from tinywav2letter import get_metrics, create_tinywav2letter +from train_model import get_data + +def evaluate_saved_weights(args, pruned = False): + + model = create_tinywav2letter(batch_size = args.batch_size) + + model.load_weights('weights/tiny_wav2letter' + pruned * "_pruned" + '_weights.h5') + + opt = tf.keras.optimizers.Adam() + model.compile(loss=get_metrics("loss"), metrics=[get_metrics("ler")], optimizer=opt) + + (reduced_validation_data, reduced_validation_num_steps) = get_data(args, "val_reduced_size", args.batch_size) + + model.evaluate(reduced_validation_data) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(allow_abbrev=False) + + parser.add_argument( + "--batch_size", + dest="batch_size", + type=int, + required=False, + default=32, + help="batch size wanted when creating model", + ) + + args = parser.parse_args() + evaluate_saved_weights(args) + diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/load_mfccs.py b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/load_mfccs.py new file mode 100644 index 0000000..d36bf6b --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/load_mfccs.py @@ -0,0 +1,177 @@ +# Copyright (C) 2020 Arm Limited or its affiliates. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the License); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an AS IS BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import tensorflow as tf +import os +import numpy as np + +class MFCC_Loader: + def __init__(self, full_size_data_dir:str, reduced_size_data_dir:str, fluent_speech_data_dir:str): + """ + Args: + data_dir: Absolute path to librispeech data folder + """ + self.full_size_data_dir = full_size_data_dir + self.reduced_size_data_dir = reduced_size_data_dir + self.fluent_speech_data_dir = fluent_speech_data_dir + self.seed = 0 + self.train = False + self.batch_size = 32 + self.num_samples = 0 + self.input_files = [] + + + @staticmethod + def _extract_features(example_proto): + feature_description = { + 'mfcc_bytes': tf.io.FixedLenFeature([], tf.string), + 'sequence_bytes': tf.io.FixedLenFeature([], tf.string), + } + # Parse the input tf.train.Example proto using the dictionary above. + serialized_tensor = tf.io.parse_single_example(example_proto, feature_description) + + mfcc_features = tf.io.parse_tensor(serialized_tensor['mfcc_bytes'], out_type = tf.float32) + sequences = tf.io.parse_tensor(serialized_tensor['sequence_bytes'], out_type = tf.int32) + + return mfcc_features, sequences + + def full_training_set(self, batch_size=32, num_samples = -1): + """ + Args: + batch_size: batch size required for the set + """ + self.tfrecord_file = [ + os.path.join(self.full_size_data_dir, 'preprocessed/train-clean-100/train-clean-100.tfrecord'), + os.path.join(self.full_size_data_dir, 'preprocessed/train-clean-360/train-clean-360.tfrecord')] + + self.train = True + self.batch_size = batch_size + self.num_samples = 132553 + return self.load_dataset(num_samples) + + def reduced_training_set(self, batch_size=32, num_samples = -1): + """ + Args: + batch_size: batch size required for the set + """ + self.tfrecord_file = os.path.join(self.reduced_size_data_dir, 'preprocessed/train-clean-5/train-clean-5.tfrecord') + self.train = True + self.batch_size = batch_size + self.num_samples = 1519 + return self.load_dataset(num_samples) + + def full_validation_set(self, batch_size=32): + """ + Args: + batch_size: batch size required for the set + """ + self.tfrecord_file = os.path.join(self.full_size_data_dir, 'preprocessed/dev-clean/dev-clean.tfrecord') + self.train = False + self.batch_size = batch_size + self.num_samples = 2703 + return self.load_dataset() + + def reduced_validation_set(self, batch_size=32): + """ + Args: + batch_size: batch size required for the set + """ + self.tfrecord_file = os.path.join(self.reduced_size_data_dir, 'preprocessed/dev-clean-2/dev-clean-2.tfrecord') + self.train = False + self.batch_size = batch_size + self.num_samples = 1089 + return self.load_dataset() + + + def evaluation_set(self, batch_size=32): + """ + Args: + batch_size: batch size required for the set + """ + + self.tfrecord_file = os.path.join(self.full_size_data_dir, 'preprocessed/test-clean/test-clean.tfrecord') + self.train = False + self.batch_size = batch_size + self.num_samples = 2620 + return self.load_dataset() + + def fluent_speech_train_set(self, batch_size=32, num_samples = -1): + """ + Args: + batch_size: batch size required for the set + """ + self.tfrecord_file = os.path.join(self.fluent_speech_data_dir, 'preprocessed/train/train.tfrecord') + + self.train = True + self.batch_size = batch_size + self.num_samples = 23132 + return self.load_dataset(num_samples) + + def fluent_speech_validation_set(self, batch_size=32): + """ + Args: + batch_size: batch size required for the set + """ + self.tfrecord_file = os.path.join(self.fluent_speech_data_dir, 'preprocessed/dev/dev.tfrecord') + self.train = False + self.batch_size = batch_size + self.num_samples = 3118 + return self.load_dataset() + + def fluent_speech_test_set(self, batch_size=32): + """ + Args: + batch_size: batch size required for the set + """ + self.tfrecord_file = os.path.join(self.fluent_speech_data_dir, 'preprocessed/test/test.tfrecord') + self.train = False + self.batch_size = batch_size + self.num_samples = 3793 + return self.load_dataset() + + def num_steps(self, batch): + """ + Get the number of steps based on the given batch size and the number + of samples. + """ + return int(np.math.ceil(self.num_samples / batch)) + + + def load_dataset(self, num_samples = -1): + + # load the specified TF Record files + dataset = tf.data.TFRecordDataset(self.tfrecord_file) + + # parse the data, and take the desired number of samples + dataset = dataset.map(self._extract_features, num_parallel_calls = tf.data.AUTOTUNE).take(num_samples) + + dataset = dataset.cache() + + # shuffle the training set + if self.train: + dataset = dataset.shuffle(buffer_size=max(self.batch_size * 2, 1024), seed=self.seed) + + MFCC_coeffs = 39 + blank_index = 28 + + + # Pad the dataset so that all the data is the same size + dataset = dataset.padded_batch( + self.batch_size, + padded_shapes=(tf.TensorShape([None, MFCC_coeffs]), tf.TensorShape([None])), + padding_values=(0.0, blank_index), drop_remainder=True + ) + return dataset.prefetch(tf.data.experimental.AUTOTUNE) diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/preprocessing.py b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/preprocessing.py new file mode 100644 index 0000000..0eddb4c --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/preprocessing.py @@ -0,0 +1,257 @@ +# Copyright (C) 2020 Arm Limited or its affiliates. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the License); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an AS IS BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import fnmatch +import os +import librosa + +import numpy as np +import tensorflow as tf +from corpus import SpeechCorpusProvider +from preprocessing_fluent_speech_commands import preprocess_fluent_sppech +from preprocessing_convert_to_flac import convert_to_flac +def _bytes_feature(value): + """Returns a bytes_list from a string / byte.""" + # If the value is an eager tensor BytesList won't unpack a string from an EagerTensor. + if isinstance(value, type(tf.constant(0))): + value = value.numpy() + return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value])) + +def _int64_feature(value): + """Returns an int64_list from a bool / enum / int / uint.""" + return tf.train.Feature(int64_list=tf.train.Int64List(value=[value])) + +def normalize(values): + """ + Normalize values to mean 0 and std 1 + """ + return (values - np.mean(values)) / np.std(values) + + +def iglob_recursive(directory, file_pattern): + """ + Recursively search for `file_pattern` in `directory` + + Args: + directory: the directory to search in + file_pattern: the file pattern to match (wildcard compatible) + + Returns: + iterator for found files + + """ + for root, dir_names, file_names in os.walk(directory): + for filename in fnmatch.filter(file_names, file_pattern): + yield os.path.join(root, filename) + + +class SpeechCorpusReader: + """ + Reads preprocessed speech corpus to be used by the NN + """ + def __init__(self, data_directory): + """ + Create SpeechCorpusReader and read samples from `data_directory` + + Args: + data_directory: the directory to use + """ + self._data_directory = data_directory + self._transcript_dict = self._build_transcript() + + @staticmethod + def _get_transcript_entries(transcript_directory): + """ + Iterate over all transcript lines and yield splitted entries + + Args: + transcript_directory: open all transcript files in this directory and extract their contents + + Returns: Iterator for all entries in the form (id, sentence) + + """ + transcript_files = iglob_recursive(transcript_directory, '*.trans.txt') + for transcript_file in transcript_files: + with open(transcript_file, 'r') as f: + for line in f: + # Strip included new line symbol + line = line.rstrip('\n') + + # Each line is in the form + # 00-000000-0000 WORD1 WORD2 ... + splitted = line.split(' ', 1) + yield splitted + + def _build_transcript(self): + """ + Builds a transcript from transcript files, mapping from audio-id to a list of vocabulary ids + + Returns: the created transcript + """ + alphabet = "abcdefghijklmnopqrstuvwxyz' @" + alphabet_dict = {c: ind for (ind, c) in enumerate(alphabet)} + + # Create the transcript dictionary + transcript_dict = dict() + for splitted in self._get_transcript_entries(self._data_directory): + transcript_dict[splitted[0]] = [alphabet_dict[letter] for letter in splitted[1].lower()] + + return transcript_dict + + @classmethod + def _extract_audio_id(cls, audio_file): + file_name = os.path.basename(audio_file) + audio_id = os.path.splitext(file_name)[0] + + return audio_id + + @staticmethod + def transform_audio_to_mfcc(audio_file, transcript, n_mfcc=13, n_fft=512, hop_length=160): + """ + Calculate mfcc coefficients from the given raw audio data + + Args: + audio_file: .flac audio file + n_mfcc: the number of coefficients to generate + n_fft: the window size of the fft + hop_length: the hop length for the window + + Returns: + the mfcc coefficients in the form [time, coefficients] + sequences: the transcript of the audio file + + """ + + audio_data, sample_rate = librosa.load(audio_file, sr=16000) + + mfcc = librosa.feature.mfcc(audio_data, sr=sample_rate, n_mfcc=n_mfcc, n_fft=n_fft, hop_length=hop_length) + + # add derivatives and normalize + mfcc_delta = librosa.feature.delta(mfcc) + mfcc_delta2 = librosa.feature.delta(mfcc, order=2) + mfcc = np.concatenate((normalize(mfcc), normalize(mfcc_delta), normalize(mfcc_delta2)), axis=0) #mfcc is now 13+13+13=39 (according to our input shpe) + + seq_length = mfcc.shape[1] // 2 + + sequences = np.concatenate([[seq_length], transcript]).astype(np.int32) + mfcc_out = mfcc.T.astype(np.float32) + + return mfcc_out, sequences + + @staticmethod + def _create_feature(mfcc_bytes, sequence_bytes): + """ + Creates a tf.train.Example message ready to be written to a file. + """ + # Create a dictionary mapping the feature name to the tf.train.Example-compatible + # data type. + + feature = { + 'mfcc_bytes': _bytes_feature(mfcc_bytes), + 'sequence_bytes': _bytes_feature(sequence_bytes), + } + + # Create a Features message using tf.train.Example. + return tf.train.Example(features=tf.train.Features(feature=feature)) + + + def _get_directory(self, sub_directory): + preprocess_directory = 'preprocessed' + + directory = self._data_directory + '/' + preprocess_directory + '/' + sub_directory + + return directory + + + def process_data(self, directory): + """ + Read audio files from `directory` and store the preprocessed version in preprocessed/`directory` + + Args: + directory: the sub-directory to read from + + """ + # create a list of all the .flac files + audio_files = list(iglob_recursive(self._data_directory + '/' + directory , '*.flac')) + + out_directory = self._get_directory(directory) + + if not os.path.exists(out_directory): + os.makedirs(out_directory) + + # the file the TFRecord will be written to + filename = out_directory + f'/{directory}.tfrecord' + with tf.io.TFRecordWriter(filename) as writer: + for audio_file in audio_files: + if os.path.getsize(audio_file) >= 13885: #small files are not suported + audio_id = self._extract_audio_id(audio_file) + + # identify the transcript corresponding to the audio file + transcript = self._transcript_dict[audio_id] + + # convert the audio to MFCCs + mfcc_feature, sequences = self.transform_audio_to_mfcc(audio_file, transcript) + + # Serialise the MFCCs and transcript + mfcc_bytes = tf.io.serialize_tensor(mfcc_feature) + sequence_bytes = tf.io.serialize_tensor(sequences) + + # Write to the TFRecord file + writer.write(self._create_feature(mfcc_bytes, sequence_bytes).SerializeToString()) + + else: + print('Processed data already exists') + + +class Preprocessing: + + def __init__(self, data_dir): + self.data_dir = data_dir + + def run(self): + # Download the raw data + corpus = SpeechCorpusProvider(self.data_dir) + corpus.ensure_availability() + + corpus_reader = SpeechCorpusReader(self.data_dir) + + # initialise the datasets + data_sets = [data_set[1] for data_set in corpus.data_sets] + + for data_set in data_sets: + print(f"Preprocessing {data_set} data") + corpus_reader.process_data(data_set) + + print('Preprocessing Complete') + def run_without_download(self): + corpus_reader = SpeechCorpusReader(self.data_dir) + corpus_reader.process_data('dev') + corpus_reader.process_data('train') + corpus_reader.process_data('test') +if __name__=="__main__": + reduced_preprocessing = Preprocessing('librispeech_reduced_size') + reduced_preprocessing.run() + + full_preprocessing = Preprocessing('librispeech_full_size') + full_preprocessing.run() + + preprocess_fluent_sppech() + convert_to_flac() + fluent_speech_preprocessing = Preprocessing('fluent_speech_commands_dataset') #please note this is a license data set + fluent_speech_preprocessing.run_without_download() + + + diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/preprocessing_convert_to_flac.py b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/preprocessing_convert_to_flac.py new file mode 100644 index 0000000..9327164 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/preprocessing_convert_to_flac.py @@ -0,0 +1,100 @@ +# Copyright (C) 2020 Arm Limited or its affiliates. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the License); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an AS IS BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from queue import Queue +import logging +import os +from threading import Thread +import audiotools +from audiotools.wav import InvalidWave + +class W2F: + + logger = '' + + def __init__(self): + global logger + # create logger + logger = logging.getLogger(__name__) + logger.setLevel(logging.DEBUG) + + # create a file handler + handler = logging.FileHandler('converter.log') + handler.setLevel(logging.INFO) + + # create a logging format + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + handler.setFormatter(formatter) + + # add the handlers to the logger + logger.addHandler(handler) + + def convert(self,path): + global logger + file_queue = Queue() + num_converter_threads = 5 + + # collect files to be converted + for root, dirs, files in os.walk(path): + + for file in files: + if file.endswith(".wav"): + file_wav = os.path.join(root, file) + file_flac = file_wav.replace(".wav", ".flac") + + if (os.path.exists(file_flac)): + logger.debug(''.join(["File ",file_flac, " already exists."])) + else: + file_queue.put(file_wav) + + logger.info("Start converting: %s files", str(file_queue.qsize())) + + # Set up some threads to convert files + for i in range(num_converter_threads): + worker = Thread(target=self.process, args=(file_queue,)) + worker.setDaemon(True) + worker.start() + + file_queue.join() + + def process(self, q): + """This is the worker thread function. + It processes files in the queue one after + another. These daemon threads go into an + infinite loop, and only exit when + the main thread ends. + """ + while True: + global logger + compression_quality = '0' #min compression + file_wav = q.get() + file_flac = file_wav.replace(".wav", ".flac") + + try: + audiotools.open(file_wav).convert(file_flac,audiotools.FlacAudio, compression_quality) + logger.info(''.join(["Converted ", file_wav, " to: ", file_flac])) + q.task_done() + except InvalidWave: + logger.error(''.join(["Failed to open file ", file_wav, " to: ", file_flac," failed."]), exc_info=True) + +def convert_to_flac(): + reduced_preprocessing = W2F() + reduced_preprocessing.convert("fluent_speech_commands_dataset/train/") + reduced_preprocessing.convert("fluent_speech_commands_dataset/dev/") + reduced_preprocessing.convert("fluent_speech_commands_dataset/test/") + print('') + + diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/preprocessing_fluent_speech_commands.py b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/preprocessing_fluent_speech_commands.py new file mode 100644 index 0000000..831fb06 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/preprocessing_fluent_speech_commands.py @@ -0,0 +1,50 @@ +# Copyright (C) 2020 Arm Limited or its affiliates. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the License); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an AS IS BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from csv import reader +import os +from shutil import copyfile +from pathlib import Path +import re +def preprocess(flag): + if flag == 'train': + path_csv = 'fluent_speech_commands_dataset/data/train_data.csv' + path_dir = 'fluent_speech_commands_dataset/train/' + elif flag == 'dev': + path_csv = 'fluent_speech_commands_dataset/data/valid_data.csv' + path_dir = 'fluent_speech_commands_dataset/dev/' + else: + path_csv = 'fluent_speech_commands_dataset/data/test_data.csv' + path_dir = 'fluent_speech_commands_dataset/test/' + with open(path_csv, 'r') as read_obj: + csv_reader = reader(read_obj) + if not os.path.exists(path_dir): + os.makedirs(path_dir) + with open(path_dir + flag + '.trans.txt', 'w') as write_obj: + for row in csv_reader: + print(row) + if(row[1] == 'path'): + continue + head, file_name = os.path.split(row[1]) + copyfile('fluent_speech_commands_dataset/' + row[1],path_dir + file_name) + text = row[3] + text = text.upper() + text = re.sub('[^a-zA-Z \']+', '', text) #remove all other chars + write_obj.write(Path(file_name).stem + " " + text + '\n') +def preprocess_fluent_sppech(): + preprocess('train') + preprocess('dev') + preprocess('test') diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/prune_and_quantise_model.py b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/prune_and_quantise_model.py new file mode 100755 index 0000000..742436b --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/prune_and_quantise_model.py @@ -0,0 +1,386 @@ +# Copyright (C) 2020 Arm Limited or its affiliates. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the License); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an AS IS BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import multiprocessing +import os +import pathlib + +import tensorflow as tf +import tensorflow_model_optimization as tfmot +from tqdm import tqdm +import numpy as np + +from tinywav2letter import get_metrics +from train_model import log, get_lr_schedule, get_data + +options = tf.data.Options() +options.experimental_distribute.auto_shard_policy = tf.data.experimental.AutoShardPolicy.DATA + +gpus = tf.config.list_logical_devices('GPU') +strategy = tf.distribute.MirroredStrategy(gpus) + +def load_model(): + """ + Returns the model saved at the end of training + """ + + # load the saved model + with strategy.scope(): + model = tf.keras.models.load_model(f'saved_models/tiny_wav2letter', + custom_objects={'ctc_loss': get_metrics("loss"), 'ctc_ler':get_metrics("ler")} + ) + + return model + +def evaluate_model(args, model): + """ + Evaluates an unquantised model + + Args: + args: The command line arguments + model: The model to evaluate + """ + + # Get the data to evaluate the model on + (fluent_speech_test_data, fluent_speech_test_num_steps) = get_data(args, "test_fluent_speech", args.batch_size) + + # Compile and evaluate the model - LER + with strategy.scope(): + opt = tf.keras.optimizers.Adam(learning_rate=get_lr_schedule(learning_rate = 1e-6, steps_per_epoch=fluent_speech_test_num_steps)) + model.compile(loss=get_metrics("loss"), metrics=[get_metrics("ler")], optimizer=opt, run_eagerly=True) + log(f'Evaluating TinyWav2Letter - LER') + model.evaluate(fluent_speech_test_data) + + # Get the data to evaluate the model on + (fluent_speech_test_data, fluent_speech_test_num_steps) = get_data(args, "test_fluent_speech", batch_size=1) # #based on batch=1 + + # Compile and evaluate the model - WER + with strategy.scope(): + opt = tf.keras.optimizers.Adam(learning_rate=get_lr_schedule(learning_rate = 1e-6, steps_per_epoch=fluent_speech_test_num_steps)) + model.compile(loss=get_metrics("loss"), metrics=[get_metrics("wer")], optimizer=opt,run_eagerly=True) + log(f'Evaluating TinyWav2Letter - WER') + model.evaluate(fluent_speech_test_data) + +def prune_model(args, model): + """Performs pruning, fine-tuning and returns stripped pruned model""" + + # Get all the training and validation data + (full_training_data, full_training_num_steps) = get_data(args, "train_full_size", args.batch_size) + (full_validation_data, full_validation_num_steps) = get_data(args, "val_full_size", args.batch_size) + (fluent_speech_training_data, fluent_speech_training_num_steps) = get_data(args, "train_fluent_speech",args.batch_size) + (fluent_speech_validation_data, fluent_speech_validation_num_steps) = get_data(args, "val_fluent_speech",args.batch_size) + (fluent_speech_test_data, fluent_speech_test_num_steps) = get_data(args, "test_fluent_speech", args.batch_size) + + log("Pruning model to {} sparsity".format(args.sparsity)) + + log_dir = f"logs/pruned_model" + + # Set up the callbacks + pruning_callbacks = [ + tfmot.sparsity.keras.UpdatePruningStep(), + tfmot.sparsity.keras.PruningSummaries(log_dir=log_dir), + ] + + # Perform the pruning - full_training_data + pruning_params = { + "pruning_schedule": tfmot.sparsity.keras.ConstantSparsity( + args.sparsity, begin_step=0, end_step=int(full_training_num_steps * 0.7), frequency=10 + ) + } + + + with strategy.scope(): + pruned_model = tfmot.sparsity.keras.prune_low_magnitude(model, **pruning_params) + opt = tf.keras.optimizers.Adam(learning_rate=get_lr_schedule(learning_rate = 1e-4, steps_per_epoch=full_training_num_steps)) + pruned_model.compile(loss=get_metrics("loss"), metrics=[get_metrics("ler")], optimizer=opt) + + pruned_model.fit( + full_training_data, + epochs=5, + verbose=1, + callbacks=pruning_callbacks, + validation_data=full_validation_data, + ) + + # Perform the pruning - fluent_speech_training_data + pruning_params = { + "pruning_schedule": tfmot.sparsity.keras.ConstantSparsity( + args.sparsity, begin_step=0, end_step=int(fluent_speech_validation_num_steps * 0.7), frequency=10 + ) + } + + + with strategy.scope(): + pruned_model = tfmot.sparsity.keras.prune_low_magnitude(model, **pruning_params) + opt = tf.keras.optimizers.Adam(learning_rate=get_lr_schedule(learning_rate = 1e-4, steps_per_epoch=fluent_speech_validation_num_steps)) + pruned_model.compile(loss=get_metrics("loss"), metrics=[get_metrics("ler")], optimizer=opt) + + pruned_model.fit( + fluent_speech_training_data, + epochs=5, + verbose=1, + callbacks=pruning_callbacks, + validation_data=fluent_speech_validation_data, + ) + + stripped_model = tfmot.sparsity.keras.strip_pruning(pruned_model) + + return stripped_model + +def prepare_model_for_inference(model, input_window_length = 296): + "Takes the a model and returns a model with fixed input size" + MFCC_coeffs = 39 + + # Define the input + layer_input = tf.keras.layers.Input((input_window_length, MFCC_coeffs), batch_size=1) + static_shaped_model = tf.keras.models.Model( + inputs=[layer_input], outputs=[model.call(layer_input)] + ) + return static_shaped_model + +def tflite_conversion(model, tflite_path, conversion_type="fp32"): + """Performs tflite conversion (fp32, int8).""" + # Prepare model for inference + model = prepare_model_for_inference(model) + converter = tf.lite.TFLiteConverter.from_keras_model(model) + + # define a dataset to calibrate the conversion to INT8 + def representative_dataset_gen(input_dim): + calib_data = [] + for data in tqdm(fluent_speech_test_data.take(1000), desc="model calibration"): + input_data = data[0] + for i in range(input_data.shape[1] // input_dim): + input_chunks = [ + input_data[:, i * input_dim: (i + 1) * input_dim, :, ] + ] + for chunk in input_chunks: + calib_data.append([chunk]) + + return lambda: [ + (yield data) for data in tqdm(calib_data, desc="model calibration") + ] + + if conversion_type == "int8": + log("Quantizing Model") + (fluent_speech_test_data, fluent_speech_test_num_steps) = get_data(args, "test_fluent_speech", args.batch_size) + converter.optimizations = [tf.lite.Optimize.DEFAULT] + converter.inference_input_type = tf.int8 + converter.inference_output_type = tf.int8 + converter.representative_dataset = representative_dataset_gen(model.input_shape[1]) + + tflite_model = converter.convert() + open(tflite_path, "wb").write(tflite_model) + +def evaluate_tflite(tflite_path, input_window_length = 296): + """Evaluates tflite (fp32, int8).""" + results_ler = [] + results_wer = [] + (fluent_speech_test_data, fluent_speech_test_num_steps) = get_data(args, "test_fluent_speech", batch_size=1) + log("Setting number of used threads to {}".format(multiprocessing.cpu_count())) + interpreter = tf.lite.Interpreter( + model_path=tflite_path, num_threads=multiprocessing.cpu_count() + ) + interpreter.allocate_tensors() + input_chunk = interpreter.get_input_details()[0] + output_details = interpreter.get_output_details()[0] + + input_shape = input_chunk["shape"] + log("eval_model() - input_shape: {}".format(input_shape)) + input_dtype = input_chunk["dtype"] + output_dtype = output_details["dtype"] + + # Check if the input/output type is quantized, + # set scale and zero-point accordingly + if input_dtype != tf.float32: + input_scale, input_zero_point = input_chunk["quantization"] + else: + input_scale, input_zero_point = 1, 0 + + if output_dtype != tf.float32: + output_scale, output_zero_point = output_details["quantization"] + else: + output_scale, output_zero_point = 1, 0 + + log("Running {} iterations".format(fluent_speech_test_num_steps)) + for i_iter, (data, label) in enumerate( + tqdm(fluent_speech_test_data, total=fluent_speech_test_num_steps) + ): + data = data / input_scale + input_zero_point + # Round the data up if dtype is int8, uint8 or int16 + if input_dtype is not np.float32: + data = np.round(data) + + while data.shape[1] < input_window_length: + data = np.append(data, data[:, -2:-1, :], axis=1) + # Zero-pad any odd-length inputs + if data.shape[1] % 2 == 1: + log('Input length is odd, zero-padding to even (first layer has stride 2)') + data = np.concatenate([data, np.zeros((1, 1, data.shape[2]), dtype=input_dtype)], axis=1) + + context = 24 + 2 * (7 * 3 + 16) # = 98 - theoretical max receptive field on each side + size = input_chunk['shape'][1] + inner = size - 2 * context + data_end = data.shape[1] + + # Initialize variables for the sliding window loop + data_pos = 0 + outputs = [] + + while data_pos < data_end: + if data_pos == 0: + # Align inputs from the first window to the start of the data and include the intial context in the output + start = data_pos + end = start + size + y_start = 0 + y_end = y_start + (size - context) // 2 + data_pos = end - context + elif data_pos + inner + context >= data_end: + # Shift left to align final window to the end of the data and include the final context in the output + shift = (data_pos + inner + context) - data_end + start = data_pos - context - shift + end = start + size + assert start >= 0 + y_start = (shift + context) // 2 # Will be even because we assert it above + y_end = size // 2 + data_pos = data_end + else: + # Capture only the inner region from mid-input inferences, excluding output from both context regions + start = data_pos - context + end = start + size + y_start = context // 2 + y_end = y_start + inner // 2 + data_pos = end - context + + interpreter.set_tensor( + input_chunk["index"], tf.cast(data[:, start:end, :], input_dtype)) + interpreter.invoke() + cur_output_data = interpreter.get_tensor(output_details["index"])[:, :, y_start:y_end, :] + cur_output_data = output_scale * ( + cur_output_data.astype(np.float32) - output_zero_point + ) + outputs.append(cur_output_data) + + complete = np.concatenate(outputs, axis=2) + results_ler.append(get_metrics("ler")(label, complete)) + results_wer.append(get_metrics("wer")(label, complete)) + + log("ler: {}".format(np.mean(results_ler))) + log("wer: {}".format(np.mean(results_wer))) #based on batch=1 + +def main(args): + + model = load_model() + evaluate_model(args, model) + + if args.prune: + model = prune_model(args, model) + model.save(f"saved_models/pruned_tiny_wav2letter") + evaluate_model(args, model) + + output_directory = pathlib.Path(os.path.dirname(os.path.abspath(__file__))) + output_directory = os.path.join(output_directory, "tiny_wav2letter/tflite_models") + wav2letter_tflite_path = os.path.join(output_directory, args.prune * "pruned_" + f"tiny_wav2letter_int8.tflite") + + if not os.path.exists(os.path.dirname(wav2letter_tflite_path)): + try: + os.makedirs(os.path.dirname(wav2letter_tflite_path)) + except OSError as exc: + raise + + # Convert the saved model to TFLite and then evaluate + tflite_conversion(model, wav2letter_tflite_path, "int8") + evaluate_tflite(wav2letter_tflite_path) + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(allow_abbrev=False) + parser.add_argument( + "--training_set_size", + dest="training_set_size", + type=int, + required=False, + default = 132553, + help="The number of samples in the training set" + ) + parser.add_argument( + "--fluent_speech_training_set_size", + dest="fluent_speech_set_size", + type=int, + required=False, + default = 23132, + help="The number of samples in the fluent speech training dataset" + ) + parser.add_argument( + "--batch_size", + dest="batch_size", + type=int, + required=False, + default=32, + help="batch size wanted when creating model", + ) + + parser.add_argument( + "--finetuning_epochs", + dest="finetuning_epochs", + type=int, + required=False, + default=10, + help="Number of epochs for fine-tuning", + ) + parser.add_argument( + "--full_data_dir", + dest="full_data_dir", + type=str, + required=False, + default='librispeech_full_size', + help="Path to dataset directory", + ) + parser.add_argument( + "--reduced_data_dir", + dest="reduced_data_dir", + type=str, + required=False, + default='librispeech_reduced_size', + help="Path to dataset directory", + ) + parser.add_argument( + "--fluent_speech_data_dir", + dest="fluent_speech_data_dir", + type=str, + required=False, + default='fluent_speech_commands_dataset', + help="Path to dataset directory", + ) + parser.add_argument( + "--prune", + dest="prune", + required=False, + action='store_true', + help="Prune model true or false", + ) + parser.add_argument( + "--sparsity", + dest="sparsity", + type=float, + required=False, + default=0.5, + help="Level of sparsity required", + ) + + args = parser.parse_args() + main(args) diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/recreate_model.sh b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/recreate_model.sh new file mode 100644 index 0000000..5cc1b39 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/recreate_model.sh @@ -0,0 +1,11 @@ +python3 -m venv env +source env/bin/activate + +pip install -r requirements.txt +python preprocessing.py +python train_model.py --with_baseline --baseline_epochs 30 --with_finetuning --finetuning_epochs 10 --with_fluent_speech --fluent_speech_epochs 30 +python prune_and_quantise_model.py --prune --sparsity 0.5 --finetuning_epochs 10 +python prune_and_quantise_model.py --sparsity 0.5 --finetuning_epochs 10 + + + diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/requirements.txt b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/requirements.txt new file mode 100644 index 0000000..6ea5a87 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/requirements.txt @@ -0,0 +1,10 @@ +librosa==0.8.1 +numpy==1.19.5 +tensorboard==2.6.0 +tensorboard-data-server==0.6.1 +tensorboard-plugin-profile==2.5.0 +tensorboard-plugin-wit==1.8.0 +tensorflow==2.4.1 +tensorflow-model-optimization==0.6.0 +tqdm +jiwer==2.3.0 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/pruned_tiny_wav2letter/saved_model.pb b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/pruned_tiny_wav2letter/saved_model.pb new file mode 100644 index 0000000..5b2edf2 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/pruned_tiny_wav2letter/saved_model.pb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:081d7d86e2b6fd788ca37b9566213560140f595d7702a2126b5a4b895d00cc9e +size 206996 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/pruned_tiny_wav2letter/variables/variables.data-00000-of-00001 b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/pruned_tiny_wav2letter/variables/variables.data-00000-of-00001 new file mode 100644 index 0000000..134cd19 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/pruned_tiny_wav2letter/variables/variables.data-00000-of-00001 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df3647d94be8c5feb098f69122e22007b11873bddb11bdc606112576d4588d4f +size 15644464 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/pruned_tiny_wav2letter/variables/variables.index b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/pruned_tiny_wav2letter/variables/variables.index new file mode 100644 index 0000000..e69b315 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/pruned_tiny_wav2letter/variables/variables.index @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6ba80bc6403066c573eed4e039219c085f2d83b32c3e0e19f40a21a03c8efb7 +size 1339 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/tiny_wav2letter/saved_model.pb b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/tiny_wav2letter/saved_model.pb new file mode 100644 index 0000000..93177d0 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/tiny_wav2letter/saved_model.pb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:163b79816a803b5d03d45e2a792e62981a1102fc0d9bd58efb4947d44b5e2af7 +size 264815 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/tiny_wav2letter/variables/variables.data-00000-of-00001 b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/tiny_wav2letter/variables/variables.data-00000-of-00001 new file mode 100644 index 0000000..230a0dc --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/tiny_wav2letter/variables/variables.data-00000-of-00001 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4048120f5ff4474cf70d3161723ac756b053e7053df76ffd48387416dab24e4 +size 46925195 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/tiny_wav2letter/variables/variables.index b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/tiny_wav2letter/variables/variables.index new file mode 100644 index 0000000..b53f5fd --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/saved_models/tiny_wav2letter/variables/variables.index @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82c10539eb05229e769397e7ee90a7befa1ec22377032da89440d69c69f4e07d +size 4440 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/tinywav2letter.py b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/tinywav2letter.py new file mode 100644 index 0000000..c998497 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/tinywav2letter.py @@ -0,0 +1,152 @@ +# Copyright (C) 2020 Arm Limited or its affiliates. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the License); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an AS IS BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Model definition for Tinywav2Letter.""" +import tensorflow as tf +from tensorflow.python.ops import ctc_ops +import numpy as np +from jiwer import wer + +def get_metrics(metric): + """Get metrics needed to compile wav2letter.""" + def ctc_preparation(tensor, y_predict): + if len(y_predict.shape) == 4: + y_predict = tf.squeeze(y_predict, axis=1) + y_predict = tf.transpose(y_predict, (1, 0, 2)) + sequence_lengths, labels = tensor[:, 0], tensor[:, 1:] + idx = tf.where(tf.not_equal(labels, 28)) + sparse_labels = tf.SparseTensor( + idx, tf.gather_nd(labels, idx), tf.shape(labels, out_type=tf.int64) + ) + return sparse_labels, sequence_lengths, y_predict + + def get_loss(): + """Calculate CTC loss.""" + def ctc_loss(y_true, y_predict): + sparse_labels, logit_length, y_predict = ctc_preparation(y_true, y_predict) + return tf.reduce_mean( + ctc_ops.ctc_loss_v2( + labels=sparse_labels, + logits=y_predict, + label_length=None, + logit_length=logit_length, + blank_index=-1, + ) + ) + return ctc_loss + + def get_ler(): + """Calculate CTC LER (Letter Error Rate).""" + def ctc_ler(y_true, y_predict): + sparse_labels, logit_length, y_predict = ctc_preparation(y_true, y_predict) + decoded, log_probabilities = tf.nn.ctc_greedy_decoder( + y_predict, tf.cast(logit_length, tf.int32), merge_repeated=True + ) + return tf.reduce_mean( + tf.edit_distance( + tf.cast(decoded[0], tf.int32), tf.cast(sparse_labels, tf.int32) + ) + ) + return ctc_ler + def get_wer(): + """Calculate CTC WER (Word Error Rate) only for batch size = 1.""" + + def trans_int_to_string(trans_int): + #create dictionary int -> string (0 -> a 1 -> b) + string = "" + alphabet = "abcdefghijklmnopqrstuvwxyz' @" + alphabet_dict = {} + count = 0 + for x in alphabet: + alphabet_dict[count] = x + count += 1 + for letter in trans_int: + letter_np = np.array(letter).item(0) + if letter_np != 28: + string += alphabet_dict[letter_np] + return string + + def ctc_wer(y_true, y_predict): + sparse_labels, logit_length, y_predict = ctc_preparation(y_true, y_predict) + decoded, log_probabilities = tf.nn.ctc_greedy_decoder( + y_predict, tf.cast(logit_length, tf.int32), merge_repeated=True + ) + true_sentence = tf.cast(sparse_labels.values, tf.int32) + return wer(str(trans_int_to_string(true_sentence)),str(trans_int_to_string(decoded[0].values))) + return ctc_wer + + return {"loss": get_loss(), "ler": get_ler(), "wer": get_wer()}[metric] + + +def create_tinywav2letter(batch_size=1, no_stride_count=5, filters_small=100, filters_large_1=750, filters_large_2=750) -> tf.keras.models.Model: + """Create and return Tinywav2Letter model""" + layer = tf.keras.layers + leaky_relu = layer.LeakyReLU([0.20000000298023224]) + MFCC_coeffs = 39 + input = layer.Input(shape=[None, MFCC_coeffs], batch_size=batch_size) + # Reshape to prepare input for first layer + x = layer.Reshape([1, -1, 39])(input) + # One striding layer of output size [batch_size, max_time / 2, 250] + x = layer.Conv2D( + filters=250, + kernel_size=[1, 48], + padding="same", + activation=None, + strides=[1, 2], + )(x) + # Add non-linearity + x = leaky_relu(x) + # layers without striding of output size [batch_size, max_time / 2, 250] + for i in range(0, no_stride_count): + x = layer.Conv2D( + filters=filters_small, + kernel_size=[1, 7], + padding="same", + activation=None, + strides=[1, 1], + )(x) + # Add non-linearity + x = leaky_relu(x) + # 1 layer with high kernel width and output size [batch_size, max_time / 2, 2000] + x = layer.Conv2D( + filters=filters_large_1, + kernel_size=[1, 32], + padding="same", + activation=None, + strides=[1, 1], + )(x) + # Add non-linearity + x = leaky_relu(x) + # 1 layer of output size [batch_size, max_time / 2, 2000] + x = layer.Conv2D( + filters=filters_large_2, + kernel_size=[1, 1], + padding="same", + activation=None, + strides=[1, 1], + )(x) + # Add non-linearity + x = leaky_relu(x) + # 1 layer of output size [batch_size, max_time / 2, num_classes] + # We must not apply a non linearity in this last layer + x = layer.Conv2D( + filters=29, + kernel_size=[1, 1], + padding="same", + activation=None, + strides=[1, 1], + )(x) + return tf.keras.models.Model(inputs=[input], outputs=[x]) diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/train_model.py b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/train_model.py new file mode 100644 index 0000000..1ae4943 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/recreate_code/train_model.py @@ -0,0 +1,267 @@ +# Copyright (C) 2020 Arm Limited or its affiliates. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the License); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an AS IS BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Wav2letter training, optimisation and evaluation script""" +import argparse + +import tensorflow as tf + +from tinywav2letter import create_tinywav2letter, get_metrics +from load_mfccs import MFCC_Loader + +options = tf.data.Options() +options.experimental_distribute.auto_shard_policy = tf.data.experimental.AutoShardPolicy.DATA + +gpus = tf.config.list_logical_devices('GPU') +strategy = tf.distribute.MirroredStrategy(gpus) + + +def log(std): + """Log the given string to the standard output.""" + print("******* {}".format(std), flush=True) + +def get_data(args, dataset_type, batch_size): + """Returns particular training and validation dataset.""" + dataset = MFCC_Loader(args.full_data_dir, args.reduced_data_dir,args.fluent_speech_data_dir) + + return {"train_full_size": [dataset.full_training_set(batch_size=batch_size, num_samples = args.training_set_size).with_options(options), dataset.num_steps(batch=batch_size)], + "train_reduced_size": [dataset.reduced_training_set(batch_size=batch_size, num_samples = args.training_set_size).with_options(options), dataset.num_steps(batch=batch_size)], + "val_full_size": [dataset.full_validation_set(batch_size=batch_size).with_options(options), dataset.num_steps(batch=batch_size)], + "val_reduced_size": [dataset.reduced_validation_set(batch_size=batch_size).with_options(options), dataset.num_steps(batch=batch_size)], + "train_fluent_speech": [dataset.fluent_speech_train_set(batch_size=batch_size, num_samples = args.fluent_speech_set_size).with_options(options), dataset.num_steps(batch=batch_size)], + "val_fluent_speech": [dataset.fluent_speech_validation_set(batch_size=batch_size).with_options(options), dataset.num_steps(batch=batch_size)], + "test_fluent_speech": [dataset.fluent_speech_test_set(batch_size=batch_size).with_options(options),dataset.num_steps(batch=batch_size)], + }[dataset_type] + +def setup_callbacks(checkpoint_path, log_dir): + """Returns callbacks for baseline training and optimization fine-tuning.""" + callbacks = [ + tf.keras.callbacks.TerminateOnNaN(), + tf.keras.callbacks.ModelCheckpoint( + filepath=checkpoint_path, + verbose=1, + save_weights_only=True, + save_freq='epoch', # save every epoch + ), + tf.keras.callbacks.TensorBoard( + log_dir=log_dir, + histogram_freq=1, # update every epoch + update_freq=100, # update every 100 batch + + ), + ] + return callbacks + +def get_lr_schedule(steps_per_epoch, learning_rate=1e-4, lr_schedule_config=[[1.0, 0.1, 0.01, 0.001]]): + """Returns learn rate schedule for baseline training and optimization fine-tuning.""" + initial_learning_rate = learning_rate + lr_schedule = tf.keras.optimizers.schedules.PiecewiseConstantDecay( + boundaries=list(p[1] * steps_per_epoch for p in lr_schedule_config), + values=[initial_learning_rate] + list(p[0] * initial_learning_rate for p in lr_schedule_config)) + return lr_schedule + + +def train_model(args): + """Performs pruning, fine-tuning and returns stripped pruned model""" + log("Commencing Model Training") + + # Get all of the required datasets + (full_training_data, full_training_num_steps) = get_data(args, "train_full_size", args.batch_size) + (reduced_training_data, reduced_training_num_steps) = get_data(args, "train_reduced_size", args.batch_size) + (full_validation_data, full_validation_num_steps) = get_data(args, "val_full_size", args.batch_size) + (reduced_validation_data, reduced_validation_num_steps) = get_data(args, "val_reduced_size", args.batch_size) + (fluent_speech_training_data, fluent_speech_training_num_steps) = get_data(args, "train_fluent_speech", args.batch_size) + (fluent_speech_validation_data, fluent_speech_validation_num_steps) = get_data(args, "val_fluent_speech", args.batch_size) + (fluent_speech_test_data, fluent_speech_test_num_steps) = get_data(args, "test_fluent_speech",args.batch_size) + + # Set up checkpoint paths, directories for the log files and the callbacks + baseline_checkpoint_path = f"checkpoints/baseline/checkpoint.ckpt" + finetuning_checkpoint_path = f"checkpoints/finetuning/checkpoint.ckpt" + + baseline_log_dir = f"logs/tiny_wav2letter_baseline" + finetuning_log_dir = f"logs/tiny_wav2letter_finetuning" + + baseline_callbacks = setup_callbacks(baseline_checkpoint_path, baseline_log_dir) + finetuning_callbacks = setup_callbacks(finetuning_checkpoint_path, finetuning_log_dir) + + # Initialise the Tiny Wav2Letter model + with strategy.scope(): + model = create_tinywav2letter(batch_size = args.batch_size) + + + # Perform the baseline training with the full size LibriSpeech dataset + if args.with_baseline: + + with strategy.scope(): + opt = tf.keras.optimizers.Adam(learning_rate=get_lr_schedule(learning_rate = 1e-4, steps_per_epoch=full_training_num_steps)) + model.compile(loss=get_metrics("loss"), metrics=[get_metrics("ler")], optimizer=opt) + + model.fit( + full_training_data, + epochs=args.baseline_epochs, + verbose=1, + callbacks=baseline_callbacks, + validation_data=full_validation_data, + initial_epoch = 0 + ) + + log(f'Evaluating Tiny Wav2Letter post baseline training') + model.evaluate(fluent_speech_test_data) + + # Perform finetuning training with the reduced size MiniLibriSpeech dataset + if args.with_finetuning: + + with strategy.scope(): + opt = tf.keras.optimizers.Adam(learning_rate=get_lr_schedule(learning_rate = 1e-5, steps_per_epoch=reduced_training_num_steps)) + model.compile(loss=get_metrics("loss"), metrics=[get_metrics("ler")], optimizer=opt) + + model.fit( + reduced_training_data, + epochs=args.finetuning_epochs + args.baseline_epochs, + verbose=1, + callbacks=finetuning_callbacks, + validation_data=reduced_validation_data, + initial_epoch = args.baseline_epochs + ) + + log(f'Evaluating Tiny Wav2Letter post finetuning') + model.evaluate(x=fluent_speech_test_data) + + + if args.with_fluent_speech: + + with strategy.scope(): + opt = tf.keras.optimizers.Adam(learning_rate=get_lr_schedule(learning_rate = 1e-5, steps_per_epoch=fluent_speech_training_num_steps)) + model.compile(loss=get_metrics("loss"), metrics=[get_metrics("ler")], optimizer=opt,run_eagerly=True) + + model.fit( + fluent_speech_training_data, + epochs=args.finetuning_epochs + args.baseline_epochs, + verbose=1, + callbacks=finetuning_callbacks, + validation_data=fluent_speech_validation_data, + initial_epoch = args.baseline_epochs + ) + + model.evaluate(x=fluent_speech_test_data) + # Save the final trained model in TF SavedModel format + model.save(f"saved_models/tiny_wav2letter") + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(allow_abbrev=False) + parser.add_argument( + "--training_set_size", + dest="training_set_size", + type=int, + required=False, + default = 132553, + help="The number of samples in the training set" + ) + parser.add_argument( + "--fluent_speech_training_set_size", + dest="fluent_speech_set_size", + type=int, + required=False, + default = 23132, + help="The number of samples in the fluent speech training dataset" + ) + parser.add_argument( + "--batch_size", + dest="batch_size", + type=int, + required=False, + default=32, + help="batch size wanted when creating model", + ) + parser.add_argument( + "--full_data_dir", + dest="full_data_dir", + type=str, + required=False, + default='librispeech_full_size', + help="Path to dataset directory", + ) + parser.add_argument( + "--reduced_data_dir", + dest="reduced_data_dir", + type=str, + required=False, + default='librispeech_reduced_size', + help="Path to dataset directory", + ) + parser.add_argument( + "--fluent_speech_data_dir", + dest="fluent_speech_data_dir", + type=str, + required=False, + default='fluent_speech_commands_dataset', + help="Path to dataset directory", + ) + parser.add_argument( + "--load_model", + dest="load_model", + required=False, + action='store_true', + help="Model number to load", + ) + parser.add_argument( + "--with_baseline", + dest="with_baseline", + required=False, + action='store_true', + help="Perform pre-training baseline using the full size dataset", + ) + parser.add_argument( + "--with_finetuning", + dest="with_finetuning", + required=False, + action='store_true', + help="Perform fine-tuning training using the reduced corpus dataset", + ) + parser.add_argument( + "--with_fluent_speech", + dest="with_fluent_speech", + required=False, + action='store_true', + help="Perform fluent_speech training using the fluent speech dataset", + ) + parser.add_argument( + "--baseline_epochs", + dest="baseline_epochs", + type=int, + required=False, + default=30, + help="Number of epochs for baseline training", + ) + parser.add_argument( + "--finetuning_epochs", + dest="finetuning_epochs", + type=int, + required=False, + default=10, + help="Number of epochs for fine-tuning", + ) + parser.add_argument( + "--fluent_speech_epochs", + dest="fluent_speech_epochs", + type=int, + required=False, + default=30, + help="Number of epochs for fluent speech training", + ) + args = parser.parse_args() + train_model(args) diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/testing_input/input_1_int8/0.npy b/models/speech_recognition/tiny_wav2letter/tflite_int8/testing_input/input_1_int8/0.npy new file mode 100644 index 0000000..0d65816 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/testing_input/input_1_int8/0.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b4c5a0ac79f152bca4fe9fe66d1d2ac9981aba59e935cde12cbc40ceedad4f9 +size 11672 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/testing_output/Identity_int8/0.npy b/models/speech_recognition/tiny_wav2letter/tflite_int8/testing_output/Identity_int8/0.npy new file mode 100644 index 0000000..2bcaab3 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/testing_output/Identity_int8/0.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fcc8e66ffab57ef3e42b544349f33e8ff4c5ff5077d7505710ea115769335b63 +size 4420 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_int8/tiny_wav2letter_int8.tflite b/models/speech_recognition/tiny_wav2letter/tflite_int8/tiny_wav2letter_int8.tflite new file mode 100644 index 0000000..dc2ef41 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_int8/tiny_wav2letter_int8.tflite @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:953f63f82c375520bb34e0e80d005bdec5aa4e1090d1702e3dae56f4988edea0 +size 3997112 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/README.md b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/README.md new file mode 100644 index 0000000..c0d8535 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/README.md @@ -0,0 +1,75 @@ +# Tiny Wav2letter Pruned INT8 + +## Description +Tiny Wav2letter is a tiny version of the original Wav2Letter model. It is a convolutional speech recognition neural network. This implementation was created by Arm, pruned to 50% sparsity, fine-tuned and quantized using the TensorFlow Model Optimization Toolkit. + + +## License +[Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) + +## Network Information +| Network Information | Value | +|---------------------|----------------| +| Framework | TensorFlow Lite | +| SHA-1 Hash | edc581b85190b2bcbfba904b50645264be52f516 | +| Size (Bytes) | 3997112 | +| Provenance | https://github.com/ARM-software/ML-zoo/tree/master/models/speech_recognition/wav2letter | +| Paper | https://arxiv.org/abs/1609.03193 | + +## Performance + +| Platform | Optimized | +|----------|:---------:| +| Cortex-A |:heavy_check_mark: | +| Cortex-M |:heavy_check_mark: | +| Mali GPU |:heavy_multiplication_x: | +| Ethos U |:heavy_check_mark: | + +### Key +* :heavy_check_mark: - Will run on this platform. +* :heavy_multiplication_x: - Will not run on this platform. + +## Accuracy +Dataset: Fluent Speech (trianed on LibriSpeech,Mini LibrySpeech,Fluent Speech) +
+Please note that Fluent Speech dataset hosted on Kaggle is a licensed dataset. + + + +| Metric | Value | +|--------|-------| +| LER | 0.0283 | +| WER | 0.089 | + +## Optimizations +| Optimization | Value | +|--------------|---------| +| Quantization | INT8 | + +## Network Inputs + + + + + + + + + + + +
Input Node NameShapeDescription
input_1_int8(1, 296, 39)Speech converted to MFCCs and quantized to INT8
+ +## Network Outputs + + + + + + + + + + + +
Output Node NameShapeDescription
Identity_int8(1, 1, 148, 29)A tensor of time and class probabilities, that represents the probability of each class at each timestep. Should be passed to a decoder. For example ctc_beam_search_decoder.
diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/definition.yaml b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/definition.yaml new file mode 100644 index 0000000..4947ebb --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/definition.yaml @@ -0,0 +1,60 @@ +author_notes: null +benchmark: + benchmark_description: please note that fluent-speech-corpus dataset hosted on Kaggle + is a licensed dataset. + benchmark_link: https://www.kaggle.com/tommyngx/fluent-speech-corpus + benchmark_metrics: + LER: '0.0283' + WER: '0.0886' + benchmark_name: Fluent speech +description: "Tiny Wav2letter is a tiny version of the original Wav2Letter model.\ + \ It is a convolutional speech recognition neural network. This implementation was\ + \ created by Arm, pruned to 50% sparsity, fine-tuned and quantized using the TensorFlow\ + \ Model Optimization Toolkit.\r\n\r\n" +license: +- Apache-2.0 +network: + datatype: int8 + file_size_bytes: 3997112 + filename: tiny_wav2letter_pruned_int8.tflite + framework: TensorFlow Lite + framework_version: 2.4.1 + hash: + algorithm: sha1 + value: edc581b85190b2bcbfba904b50645264be52f516 + provenance: https://github.com/ARM-software/ML-zoo/tree/master/models/speech_recognition/wav2letter + training: LibriSpeech,Mini LibrySpeech,fluent speech +network_parameters: + input_nodes: + - description: Speech converted to MFCCs and quantized to INT8 + example_input: + path: models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/testing_input/input_1_int8 + input_datatype: int8 + name: input_1_int8 + shape: + - 1 + - 296 + - 39 + output_nodes: + - description: A tensor of time and class probabilities, that represents the probability of + each class at each timestep. Should be passed to a decoder. For example ctc_beam_search_decoder. + example_output: + path: models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/testing_output/Identity_int8 + name: Identity_int8 + output_datatype: int8 + shape: + - 1 + - 1 + - 148 + - 29 +network_quality: + quality_level: Deployable + quality_level_hero_hw: null +operators: + TensorFlow Lite: + - CONV_2D + - DEQUANTIZE + - LEAKY_RELU + - QUANTIZE + - RESHAPE +paper: https://arxiv.org/abs/1609.03193 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/demo_input/84-121550-0000.flac b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/demo_input/84-121550-0000.flac new file mode 100644 index 0000000000000000000000000000000000000000..8dd88a8f343cf1ca266017a81f58078bbe769b28 GIT binary patch literal 162628 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 string (0 -> a 1 -> b)\n", + " string = \"\"\n", + " alphabet = \"abcdefghijklmnopqrstuvwxyz' @\"\n", + " alphabet_dict = {}\n", + " count = 0\n", + " for x in alphabet:\n", + " alphabet_dict[count] = x\n", + " count += 1\n", + " for letter in trans_int:\n", + " letter_np = np.array(letter).item(0)\n", + " if letter_np != 28:\n", + " string += alphabet_dict[letter_np]\n", + " return string\n", + "\n", + "def ctc_wer(y_true, y_predict):\n", + " sparse_labels, logit_length, y_predict = ctc_preparation(y_true, y_predict)\n", + " decoded, log_probabilities = tf.nn.ctc_greedy_decoder(\n", + " y_predict, tf.cast(logit_length, tf.int32), merge_repeated=True\n", + " )\n", + " true_sentence = tf.cast(sparse_labels.values, tf.int32)\n", + " return wer(str(trans_int_to_string(decoded[0].values)), str(trans_int_to_string(true_sentence)))" + ], + "outputs": [], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The TFLite file requires inputs of size 296, so we apply a window to the input" + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 94, + "source": [ + "def evaluate_tflite(tflite_path, input_window_length = 296):\n", + " \"\"\"Evaluates tflite (fp32, int8).\"\"\"\n", + " results = []\n", + " data, label = transform_audio_to_mfcc(audio_file, transcript_ints)\n", + "\n", + " interpreter = tf.lite.Interpreter(model_path=tflite_path, num_threads=multiprocessing.cpu_count())\n", + " interpreter.allocate_tensors()\n", + " input_chunk = interpreter.get_input_details()[0]\n", + " output_details = interpreter.get_output_details()[0]\n", + "\n", + " input_shape = input_chunk[\"shape\"]\n", + " log(\"eval_model() - input_shape: {}\".format(input_shape))\n", + " input_dtype = input_chunk[\"dtype\"]\n", + " output_dtype = output_details[\"dtype\"]\n", + "\n", + " # Check if the input/output type is quantized,\n", + " # set scale and zero-point accordingly\n", + " if input_dtype != tf.float32:\n", + " input_scale, input_zero_point = input_chunk[\"quantization\"]\n", + " else:\n", + " input_scale, input_zero_point = 1, 0\n", + "\n", + " if output_dtype != tf.float32:\n", + " output_scale, output_zero_point = output_details[\"quantization\"]\n", + " else:\n", + " output_scale, output_zero_point = 1, 0\n", + "\n", + "\n", + " data = data / input_scale + input_zero_point\n", + " # Round the data up if dtype is int8, uint8 or int16\n", + " if input_dtype is not np.float32:\n", + " data = np.round(data)\n", + "\n", + " while data.shape[1] < input_window_length:\n", + " data = np.append(data, data[:, -2:-1, :], axis=1)\n", + " # Zero-pad any odd-length inputs\n", + " if data.shape[1] % 2 == 1:\n", + " # log('Input length is odd, zero-padding to even (first layer has stride 2)')\n", + " data = np.concatenate([data, np.zeros((1, 1, data.shape[2]), dtype=input_dtype)], axis=1)\n", + "\n", + " context = 24 + 2 * (7 * 3 + 16) # = 98 - theoretical max receptive field on each side\n", + " size = input_chunk['shape'][1]\n", + " inner = size - 2 * context\n", + " data_end = data.shape[1]\n", + "\n", + " # Initialize variables for the sliding window loop\n", + " data_pos = 0\n", + " outputs = []\n", + "\n", + " while data_pos < data_end:\n", + " if data_pos == 0:\n", + " # Align inputs from the first window to the start of the data and include the intial context in the output\n", + " start = data_pos\n", + " end = start + size\n", + " y_start = 0\n", + " y_end = y_start + (size - context) // 2\n", + " data_pos = end - context\n", + " elif data_pos + inner + context >= data_end:\n", + " # Shift left to align final window to the end of the data and include the final context in the output\n", + " shift = (data_pos + inner + context) - data_end\n", + " start = data_pos - context - shift\n", + " end = start + size\n", + " assert start >= 0\n", + " y_start = (shift + context) // 2 # Will be even because we assert it above\n", + " y_end = size // 2\n", + " data_pos = data_end\n", + " else:\n", + " # Capture only the inner region from mid-input inferences, excluding output from both context regions\n", + " start = data_pos - context\n", + " end = start + size\n", + " y_start = context // 2\n", + " y_end = y_start + inner // 2\n", + " data_pos = end - context\n", + "\n", + " interpreter.set_tensor(input_chunk[\"index\"], tf.cast(data[:, start:end, :], input_dtype))\n", + " interpreter.invoke()\n", + " cur_output_data = interpreter.get_tensor(output_details[\"index\"])[:, :, y_start:y_end, :]\n", + " cur_output_data = output_scale * (\n", + " cur_output_data.astype(np.float32) - output_zero_point\n", + " )\n", + " outputs.append(cur_output_data)\n", + "\n", + " complete = np.concatenate(outputs, axis=2)\n", + " LER, output = ctc_ler(label, complete)\n", + " WER = ctc_wer(label, complete)\n", + " return output, LER , WER\n" + ], + "outputs": [], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 107, + "source": [ + "wav2letter_tflite_path = \"tflite_int8/tiny_wav2letter_int8.tflite\"\n", + "output, LER , WER = evaluate_tflite(wav2letter_tflite_path)\n", + "\n", + "decoded_output = [index_dict[value] for value in output[0]]\n", + "log(f'Transcribed File: {\"\".join(decoded_output)}')\n", + "log(f'Letter Error Rate is {LER}')\n", + "log(f'Word Error Rate is {WER}')" + ], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "******* eval_model() - input_shape: [ 1 296 39]\n", + "******* Input length is odd, zero-padding to even (first layer has stride 2)\n", + "******* Transcribed File: but with full ravishment the hours of prime singing received they in the midst of leaves that everborea burden to their rimes\n", + "******* Letter Error Rate is 0.03125\n", + "******* Word Error Rate is 1.05\n" + ] + } + ], + "metadata": {} + } + ], + "metadata": { + "orig_nbformat": 4, + "language_info": { + "name": "python", + "version": "3.8.2", + "mimetype": "text/x-python", + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "pygments_lexer": "ipython3", + "nbconvert_exporter": "python", + "file_extension": ".py" + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3.8.2 64-bit ('env': venv)" + }, + "interpreter": { + "hash": "4b529a2edd0e262cfd8353ba70b138cbba10314325c544d99b9316c477c7841b" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/model_development_guide.md b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/model_development_guide.md new file mode 100644 index 0000000..546a975 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/model_development_guide.md @@ -0,0 +1,58 @@ +# Model Development Guide + +This document describes the process of training a model from scratch, using the Tiny Wav2Letter model as an example. + +## Datasets + +The first thing to decide is which dataset the model is to be trained on. Most commonly used datasets can either be found online or in the ARM AWS S3 bucket. In the case of Tiny Wav2Letter, both the LibriSpeech dataset hosted on [OpenSLR](http://www.openslr.org/resources.php) and fluent-speech-corpus dataset hosted on [Kaggle](https://www.kaggle.com/tommyngx/fluent-speech-corpus) were used to train the model. +! ! please note that fluent-speech-corpus dataset hosted on [Kaggle](https://www.kaggle.com/tommyngx/fluent-speech-corpus) is a licensed dataset. + +## Preprocessing + +The dataset is often not in the right format for training, so preprocessing steps must be taken. In this case, the LibriSpeech dataset consists of audio files, however the paper stated that as input MFCCs should be used, so the audio files needed to be converted. It is to be recommended that all preprocessing be performed offline, as this will make the actual training process faster, as the data is already in the correct format. The most convenient way to store the preprocessed data is using [TFRecords](https://www.tensorflow.org/tutorials/load_data/tfrecord), as these are very easily loaded into TFDatasets. While it can take a long time to write the whole dataset to a TFRecord file, it is outweighed by the time saved during training. +Please note: input audio data sample rate is 16K +## Model Architecture + +The model architecture can generally be found from a variety of sources. If a similar model exists in the IMZ, then [Netron](https://netron.app) can be used to inspect the TFLite file. The original paper the model was proposed in will also define the architecture. The model should ideally be defined using the Tensorflow Functional API rather than the sequential API. + +## Loss Function and Metrics + +The loss function and desired metrics will be defined by the model. If at all possible, structure the data such that the input to the loss function is in the form (y_true, y_predicted) as this will enable model.fit to be used and avoid custom training loops. TensorFlow has lots of standard loss functions straight out of the box, but if need be custom loss functions can be defined, as was the case in TinyWav2Letter. + +## Model Training + +If everything else has been set up properly, the code here should not be complicated. Load the datasets, create an instance of the model, and then ideally run model.fit but if that's not possible use tf.GradientTape. Use callbacks to write to a log directory (tf.keras.callbacks.Tensorboard), then use Tensorboard to visualise the training process. One can use the [TensorFlow Profiler](https://www.tensorflow.org/guide/profiler) to identify bottlenecks in the training pipeline and speed up the training process. Another useful callback is tf.keras.callbacks.ModelCheckpoint which saves a checkpoint at defined intervals, so one can pick up training from where it was left off. Generally we will want a training set, a validation set and a test set, with normally about a 90:5:5 split. If the model performs well on the training set but not on the validation set or test set, then the model is overfitting. This can be reduced by introducing regularisation, increasing the amount of data, reducing model complexity or adjusting hyperparameters. In the case of TinyWav2Letter, the model was initially trained on the full-size LibriSpeech Dataset to capture the features of speech, then fine-tuned on the much smaller Mini LibriSpeech to improve the accuracy on the smaller dataset, then fine-tuned on fluent-speech-corpus dataset + +## Optimisation and Conversion + +Once the model has been trained to satisfaction, it can be optionally be optimised using the TensorFlow Model Optimization Toolkit. Pruning sets a specified percentage of the weights to be 0, so the model is sparser, which can lead to faster inference. Clustering clusters together weights of similar values, reducing the number of unique values. This again leads to faster inference. Quantisation (eg to INT8) converts all the weights to INT8 representations, giving a 4x reduction in size compared to FP32. If the quantisation process affects the metric too severely, quantisation aware training can be performed, which fine-tunes the model and makes it more robust to quantisation. Quantisation aware training requires at least Tensorflow 2.5.0. The final step is to convert the model to the TFLite model format. If using INT8 conversion, one must define a representative dataset to calibrate the model. + +## Training a smaller FP32 Keras Model + +The trained Wav2Letter model then serves as the foundation for investigations into how best to reduce the size of the network.   + +There are three hyperparameters relevant to the size of the network. They are: + +- Number of layers +- Number of filters in each convolutional layer +- Stride of each convolutional filter + +The following table shows chose architecture for Tiny Wav2Letter and the effect that it has on the output size.  + +| Identifier | Total Number of Layers | Number of middle Convolutional Layers | Corresponding number of filters  | Number of filters in the antepenultimate Conv2D Layer | Number of filters in the penultimate Conv2D Layer | +| ------ | ------ | ------ | ------ | ------ | ------ | +| Wav2Letter | 11 | 7 | 250 | 2000 | 2000 | +| Tiny Wav2Letter | 6 | 5 | 100 | 750 | 750 | + +| Identifier | Size (MB) | LER | WER | +| ------ | ------ | ------ | ------ | +| Wav2Letter INT8| 22.7 | 0.0877** | N/A | +| Wav2Letter INT8 pruned| 22.7 | 0.0783** | N/A | +| Tiny Wav2Letter FP32| 15.6* | 0.0351 | 0.0714 | +| Tiny Wav2Letter FP32 pruned| 15.6* | 0.0266 | 0.0577 | +| Tiny Wav2Letter INT8| 3.81 | 0.0348 | 0.1123 | +| Tiny Wav2Letter INT8 pruned| 3.81 | 0.0283 | 0.0886 | + +"*" - the size is according to the tflite model \ +** trained on different dataset + diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/.idea/misc.xml b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/.idea/misc.xml new file mode 100644 index 0000000..625040c --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/.idea/workspace.xml b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/.idea/workspace.xml new file mode 100644 index 0000000..1dcd832 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/.idea/workspace.xml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1639400202242 + + + + + + + + + + + file://$PROJECT_DIR$/preprocessing.py + 148 + + + file://$PROJECT_DIR$/train_model.py + 158 + + + file://$PROJECT_DIR$/train_model.py + 99 + + + + + \ No newline at end of file diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/README.md b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/README.md new file mode 100644 index 0000000..90ff4be --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/README.md @@ -0,0 +1,13 @@ +# Tiny Wav2letter FP32/INT8/INT8_Pruned Model Re-Creation +This folder contains a script that allows for the model to be re-created from scratch. +## Datasets +Tiny Wav2Letter was trianed on both LibriSpeech dataset hosted on OpenSLR and fluent-speech-corpus dataset hosted on Kaggle. +Please note that fluent-speech-corpus dataset hosted on [Kaggle](https://www.kaggle.com/tommyngx/fluent-speech-corpus) is a licensed dataset. +## Requirements +The script in this folder requires that the following must be installed: +- Python 3.6 +- Create new dir: fluent_speech_commands_dataset +- (LICENSED DATASET!!) Download and extract fluent-speech-corpus from: https://www.kaggle.com/tommyngx/fluent-speech-corpus to fluent_speech_commands_dataset dir + +## Running The Script +To run the script, run the following in a terminal: `./recreate_model.sh` diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/__pycache__/load_mfccs.cpython-36.pyc b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/__pycache__/load_mfccs.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b74e0406fb81890c022e7205cecf836d0ccdfaa7 GIT binary patch literal 5377 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 literal 0 HcmV?d00001 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/corpus.py b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/corpus.py new file mode 100644 index 0000000..b5bd5ce --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/corpus.py @@ -0,0 +1,171 @@ +# Copyright (C) 2020 Arm Limited or its affiliates. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the License); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an AS IS BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import tarfile +import urllib.request + + +class SpeechCorpusProvider: + """ + Ensures the availability and downloads the speech corpus if necessary + """ + + DATA_SOURCE = { + "librispeech_full_size": + {"DATA_SETS": + { + ('train', 'train-clean-100'), + ('train', 'train-clean-360'), + ('val', 'dev-clean') + }, + "BASE_URL": 'http://www.openslr.org/resources/12/' + }, + "librispeech_reduced_size": + {"DATA_SETS": + { + ('train', 'train-clean-5'), + ('val', 'dev-clean-2') + }, + "BASE_URL": 'http://www.openslr.org/resources/31/' + } + } + + SET_FILE_EXTENSION = '.tar.gz' + TAR_ROOT = 'LibriSpeech/' + + def __init__(self, data_directory): + """ + Creates a new SpeechCorpusProvider with the root directory `data_directory`. + The speech corpus is downloaded and extracted into sub-directories. + + Args: + data_directory: the root directory to use, e.g. data/ + """ + + self._data_directory = data_directory + self._make_dir_if_not_exists(data_directory) + self.data_sets = SpeechCorpusProvider.DATA_SOURCE[data_directory]['DATA_SETS'] + self.base_url = SpeechCorpusProvider.DATA_SOURCE[data_directory]['BASE_URL'] + + @staticmethod + def _make_dir_if_not_exists(directory): + """ + Helper function to create a directory if it doesn't exist. + + Args: + directory: directory to create + """ + + if not os.path.exists(directory): + os.makedirs(directory) + + def _download_if_not_exists(self, remote_file_name): + """ + Downloads the given `remote_file_name` if not yet stored in the `data_directory` + + Args: + remote_file_name: the file to download + + Returns: path to downloaded file + """ + + path = os.path.join(self._data_directory, remote_file_name) + if not os.path.exists(path): + print('Downloading {}...'.format(remote_file_name)) + urllib.request.urlretrieve(self.base_url + remote_file_name, path) + return path + + @staticmethod + def _extract_from_to(tar_file_name, source, target_directory): + """ + Extract all necessary files `source` from `tar_file_name` into `target_directory` + + Args: + tar_file_name: the tar file to extract from + source: the directory in the root to extract + target_directory: the directory to store the files in + """ + + print('Extracting {}...'.format(tar_file_name)) + with tarfile.open(tar_file_name, 'r:gz') as tar: + source_members = [ + tarinfo for tarinfo in tar.getmembers() + if tarinfo.name.startswith(SpeechCorpusProvider.TAR_ROOT + source) + ] + for member in source_members: + # Extract without prefix + member.name = member.name.replace(SpeechCorpusProvider.TAR_ROOT, '') + tar.extractall(target_directory, source_members) + + def _is_ready(self): + """ + Returns whether all `data_sets` are downloaded and extracted + + Args: + data_sets: list of the datasets to ensure + + Returns: bool, is ready to use + + """ + + data_set_paths = [os.path.join(set_type, set_name) + for set_type, set_name in self.data_sets] + + return all([os.path.exists(os.path.join(self._data_directory, data_set)) + for data_set in data_set_paths]) + + def _download(self): + """ + Download the given `data_sets` + + Args: + data_sets: a list of the datasets to download + """ + + for data_set_type, data_set_name in self.data_sets: + remote_file = data_set_name + SpeechCorpusProvider.SET_FILE_EXTENSION + self._download_if_not_exists(remote_file) + + def _extract(self): + """ + Extract all necessary files from the given `data_sets` + """ + + for data_set_type, data_set_name in self.data_sets: + local_file = os.path.join(self._data_directory, data_set_name + SpeechCorpusProvider.SET_FILE_EXTENSION) + target_directory = self._data_directory + self._extract_from_to(local_file, data_set_name, target_directory) + + def ensure_availability(self): + """ + Ensure that all datasets are downloaded and extracted. If this is not the case, + the download and extraction is initated. + """ + + if not self._is_ready(): + self._download() + self._extract() + + +if __name__=="__main__": + full_size_corpus = SpeechCorpusProvider("librispeech_full_size") + full_size_corpus.ensure_availability() + + reduced_size_corpus = SpeechCorpusProvider("librispeech_reduced_size") + reduced_size_corpus.ensure_availability() + + diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/evaluate_saved_weights.py b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/evaluate_saved_weights.py new file mode 100644 index 0000000..399a914 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/evaluate_saved_weights.py @@ -0,0 +1,52 @@ +# Copyright (C) 2020 Arm Limited or its affiliates. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the License); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an AS IS BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse + +import tensorflow as tf + +from tinywav2letter import get_metrics, create_tinywav2letter +from train_model import get_data + +def evaluate_saved_weights(args, pruned = False): + + model = create_tinywav2letter(batch_size = args.batch_size) + + model.load_weights('weights/tiny_wav2letter' + pruned * "_pruned" + '_weights.h5') + + opt = tf.keras.optimizers.Adam() + model.compile(loss=get_metrics("loss"), metrics=[get_metrics("ler")], optimizer=opt) + + (reduced_validation_data, reduced_validation_num_steps) = get_data(args, "val_reduced_size", args.batch_size) + + model.evaluate(reduced_validation_data) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(allow_abbrev=False) + + parser.add_argument( + "--batch_size", + dest="batch_size", + type=int, + required=False, + default=32, + help="batch size wanted when creating model", + ) + + args = parser.parse_args() + evaluate_saved_weights(args) + diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/load_mfccs.py b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/load_mfccs.py new file mode 100644 index 0000000..d36bf6b --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/load_mfccs.py @@ -0,0 +1,177 @@ +# Copyright (C) 2020 Arm Limited or its affiliates. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the License); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an AS IS BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import tensorflow as tf +import os +import numpy as np + +class MFCC_Loader: + def __init__(self, full_size_data_dir:str, reduced_size_data_dir:str, fluent_speech_data_dir:str): + """ + Args: + data_dir: Absolute path to librispeech data folder + """ + self.full_size_data_dir = full_size_data_dir + self.reduced_size_data_dir = reduced_size_data_dir + self.fluent_speech_data_dir = fluent_speech_data_dir + self.seed = 0 + self.train = False + self.batch_size = 32 + self.num_samples = 0 + self.input_files = [] + + + @staticmethod + def _extract_features(example_proto): + feature_description = { + 'mfcc_bytes': tf.io.FixedLenFeature([], tf.string), + 'sequence_bytes': tf.io.FixedLenFeature([], tf.string), + } + # Parse the input tf.train.Example proto using the dictionary above. + serialized_tensor = tf.io.parse_single_example(example_proto, feature_description) + + mfcc_features = tf.io.parse_tensor(serialized_tensor['mfcc_bytes'], out_type = tf.float32) + sequences = tf.io.parse_tensor(serialized_tensor['sequence_bytes'], out_type = tf.int32) + + return mfcc_features, sequences + + def full_training_set(self, batch_size=32, num_samples = -1): + """ + Args: + batch_size: batch size required for the set + """ + self.tfrecord_file = [ + os.path.join(self.full_size_data_dir, 'preprocessed/train-clean-100/train-clean-100.tfrecord'), + os.path.join(self.full_size_data_dir, 'preprocessed/train-clean-360/train-clean-360.tfrecord')] + + self.train = True + self.batch_size = batch_size + self.num_samples = 132553 + return self.load_dataset(num_samples) + + def reduced_training_set(self, batch_size=32, num_samples = -1): + """ + Args: + batch_size: batch size required for the set + """ + self.tfrecord_file = os.path.join(self.reduced_size_data_dir, 'preprocessed/train-clean-5/train-clean-5.tfrecord') + self.train = True + self.batch_size = batch_size + self.num_samples = 1519 + return self.load_dataset(num_samples) + + def full_validation_set(self, batch_size=32): + """ + Args: + batch_size: batch size required for the set + """ + self.tfrecord_file = os.path.join(self.full_size_data_dir, 'preprocessed/dev-clean/dev-clean.tfrecord') + self.train = False + self.batch_size = batch_size + self.num_samples = 2703 + return self.load_dataset() + + def reduced_validation_set(self, batch_size=32): + """ + Args: + batch_size: batch size required for the set + """ + self.tfrecord_file = os.path.join(self.reduced_size_data_dir, 'preprocessed/dev-clean-2/dev-clean-2.tfrecord') + self.train = False + self.batch_size = batch_size + self.num_samples = 1089 + return self.load_dataset() + + + def evaluation_set(self, batch_size=32): + """ + Args: + batch_size: batch size required for the set + """ + + self.tfrecord_file = os.path.join(self.full_size_data_dir, 'preprocessed/test-clean/test-clean.tfrecord') + self.train = False + self.batch_size = batch_size + self.num_samples = 2620 + return self.load_dataset() + + def fluent_speech_train_set(self, batch_size=32, num_samples = -1): + """ + Args: + batch_size: batch size required for the set + """ + self.tfrecord_file = os.path.join(self.fluent_speech_data_dir, 'preprocessed/train/train.tfrecord') + + self.train = True + self.batch_size = batch_size + self.num_samples = 23132 + return self.load_dataset(num_samples) + + def fluent_speech_validation_set(self, batch_size=32): + """ + Args: + batch_size: batch size required for the set + """ + self.tfrecord_file = os.path.join(self.fluent_speech_data_dir, 'preprocessed/dev/dev.tfrecord') + self.train = False + self.batch_size = batch_size + self.num_samples = 3118 + return self.load_dataset() + + def fluent_speech_test_set(self, batch_size=32): + """ + Args: + batch_size: batch size required for the set + """ + self.tfrecord_file = os.path.join(self.fluent_speech_data_dir, 'preprocessed/test/test.tfrecord') + self.train = False + self.batch_size = batch_size + self.num_samples = 3793 + return self.load_dataset() + + def num_steps(self, batch): + """ + Get the number of steps based on the given batch size and the number + of samples. + """ + return int(np.math.ceil(self.num_samples / batch)) + + + def load_dataset(self, num_samples = -1): + + # load the specified TF Record files + dataset = tf.data.TFRecordDataset(self.tfrecord_file) + + # parse the data, and take the desired number of samples + dataset = dataset.map(self._extract_features, num_parallel_calls = tf.data.AUTOTUNE).take(num_samples) + + dataset = dataset.cache() + + # shuffle the training set + if self.train: + dataset = dataset.shuffle(buffer_size=max(self.batch_size * 2, 1024), seed=self.seed) + + MFCC_coeffs = 39 + blank_index = 28 + + + # Pad the dataset so that all the data is the same size + dataset = dataset.padded_batch( + self.batch_size, + padded_shapes=(tf.TensorShape([None, MFCC_coeffs]), tf.TensorShape([None])), + padding_values=(0.0, blank_index), drop_remainder=True + ) + return dataset.prefetch(tf.data.experimental.AUTOTUNE) diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/preprocessing.py b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/preprocessing.py new file mode 100644 index 0000000..0eddb4c --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/preprocessing.py @@ -0,0 +1,257 @@ +# Copyright (C) 2020 Arm Limited or its affiliates. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the License); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an AS IS BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import fnmatch +import os +import librosa + +import numpy as np +import tensorflow as tf +from corpus import SpeechCorpusProvider +from preprocessing_fluent_speech_commands import preprocess_fluent_sppech +from preprocessing_convert_to_flac import convert_to_flac +def _bytes_feature(value): + """Returns a bytes_list from a string / byte.""" + # If the value is an eager tensor BytesList won't unpack a string from an EagerTensor. + if isinstance(value, type(tf.constant(0))): + value = value.numpy() + return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value])) + +def _int64_feature(value): + """Returns an int64_list from a bool / enum / int / uint.""" + return tf.train.Feature(int64_list=tf.train.Int64List(value=[value])) + +def normalize(values): + """ + Normalize values to mean 0 and std 1 + """ + return (values - np.mean(values)) / np.std(values) + + +def iglob_recursive(directory, file_pattern): + """ + Recursively search for `file_pattern` in `directory` + + Args: + directory: the directory to search in + file_pattern: the file pattern to match (wildcard compatible) + + Returns: + iterator for found files + + """ + for root, dir_names, file_names in os.walk(directory): + for filename in fnmatch.filter(file_names, file_pattern): + yield os.path.join(root, filename) + + +class SpeechCorpusReader: + """ + Reads preprocessed speech corpus to be used by the NN + """ + def __init__(self, data_directory): + """ + Create SpeechCorpusReader and read samples from `data_directory` + + Args: + data_directory: the directory to use + """ + self._data_directory = data_directory + self._transcript_dict = self._build_transcript() + + @staticmethod + def _get_transcript_entries(transcript_directory): + """ + Iterate over all transcript lines and yield splitted entries + + Args: + transcript_directory: open all transcript files in this directory and extract their contents + + Returns: Iterator for all entries in the form (id, sentence) + + """ + transcript_files = iglob_recursive(transcript_directory, '*.trans.txt') + for transcript_file in transcript_files: + with open(transcript_file, 'r') as f: + for line in f: + # Strip included new line symbol + line = line.rstrip('\n') + + # Each line is in the form + # 00-000000-0000 WORD1 WORD2 ... + splitted = line.split(' ', 1) + yield splitted + + def _build_transcript(self): + """ + Builds a transcript from transcript files, mapping from audio-id to a list of vocabulary ids + + Returns: the created transcript + """ + alphabet = "abcdefghijklmnopqrstuvwxyz' @" + alphabet_dict = {c: ind for (ind, c) in enumerate(alphabet)} + + # Create the transcript dictionary + transcript_dict = dict() + for splitted in self._get_transcript_entries(self._data_directory): + transcript_dict[splitted[0]] = [alphabet_dict[letter] for letter in splitted[1].lower()] + + return transcript_dict + + @classmethod + def _extract_audio_id(cls, audio_file): + file_name = os.path.basename(audio_file) + audio_id = os.path.splitext(file_name)[0] + + return audio_id + + @staticmethod + def transform_audio_to_mfcc(audio_file, transcript, n_mfcc=13, n_fft=512, hop_length=160): + """ + Calculate mfcc coefficients from the given raw audio data + + Args: + audio_file: .flac audio file + n_mfcc: the number of coefficients to generate + n_fft: the window size of the fft + hop_length: the hop length for the window + + Returns: + the mfcc coefficients in the form [time, coefficients] + sequences: the transcript of the audio file + + """ + + audio_data, sample_rate = librosa.load(audio_file, sr=16000) + + mfcc = librosa.feature.mfcc(audio_data, sr=sample_rate, n_mfcc=n_mfcc, n_fft=n_fft, hop_length=hop_length) + + # add derivatives and normalize + mfcc_delta = librosa.feature.delta(mfcc) + mfcc_delta2 = librosa.feature.delta(mfcc, order=2) + mfcc = np.concatenate((normalize(mfcc), normalize(mfcc_delta), normalize(mfcc_delta2)), axis=0) #mfcc is now 13+13+13=39 (according to our input shpe) + + seq_length = mfcc.shape[1] // 2 + + sequences = np.concatenate([[seq_length], transcript]).astype(np.int32) + mfcc_out = mfcc.T.astype(np.float32) + + return mfcc_out, sequences + + @staticmethod + def _create_feature(mfcc_bytes, sequence_bytes): + """ + Creates a tf.train.Example message ready to be written to a file. + """ + # Create a dictionary mapping the feature name to the tf.train.Example-compatible + # data type. + + feature = { + 'mfcc_bytes': _bytes_feature(mfcc_bytes), + 'sequence_bytes': _bytes_feature(sequence_bytes), + } + + # Create a Features message using tf.train.Example. + return tf.train.Example(features=tf.train.Features(feature=feature)) + + + def _get_directory(self, sub_directory): + preprocess_directory = 'preprocessed' + + directory = self._data_directory + '/' + preprocess_directory + '/' + sub_directory + + return directory + + + def process_data(self, directory): + """ + Read audio files from `directory` and store the preprocessed version in preprocessed/`directory` + + Args: + directory: the sub-directory to read from + + """ + # create a list of all the .flac files + audio_files = list(iglob_recursive(self._data_directory + '/' + directory , '*.flac')) + + out_directory = self._get_directory(directory) + + if not os.path.exists(out_directory): + os.makedirs(out_directory) + + # the file the TFRecord will be written to + filename = out_directory + f'/{directory}.tfrecord' + with tf.io.TFRecordWriter(filename) as writer: + for audio_file in audio_files: + if os.path.getsize(audio_file) >= 13885: #small files are not suported + audio_id = self._extract_audio_id(audio_file) + + # identify the transcript corresponding to the audio file + transcript = self._transcript_dict[audio_id] + + # convert the audio to MFCCs + mfcc_feature, sequences = self.transform_audio_to_mfcc(audio_file, transcript) + + # Serialise the MFCCs and transcript + mfcc_bytes = tf.io.serialize_tensor(mfcc_feature) + sequence_bytes = tf.io.serialize_tensor(sequences) + + # Write to the TFRecord file + writer.write(self._create_feature(mfcc_bytes, sequence_bytes).SerializeToString()) + + else: + print('Processed data already exists') + + +class Preprocessing: + + def __init__(self, data_dir): + self.data_dir = data_dir + + def run(self): + # Download the raw data + corpus = SpeechCorpusProvider(self.data_dir) + corpus.ensure_availability() + + corpus_reader = SpeechCorpusReader(self.data_dir) + + # initialise the datasets + data_sets = [data_set[1] for data_set in corpus.data_sets] + + for data_set in data_sets: + print(f"Preprocessing {data_set} data") + corpus_reader.process_data(data_set) + + print('Preprocessing Complete') + def run_without_download(self): + corpus_reader = SpeechCorpusReader(self.data_dir) + corpus_reader.process_data('dev') + corpus_reader.process_data('train') + corpus_reader.process_data('test') +if __name__=="__main__": + reduced_preprocessing = Preprocessing('librispeech_reduced_size') + reduced_preprocessing.run() + + full_preprocessing = Preprocessing('librispeech_full_size') + full_preprocessing.run() + + preprocess_fluent_sppech() + convert_to_flac() + fluent_speech_preprocessing = Preprocessing('fluent_speech_commands_dataset') #please note this is a license data set + fluent_speech_preprocessing.run_without_download() + + + diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/preprocessing_convert_to_flac.py b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/preprocessing_convert_to_flac.py new file mode 100644 index 0000000..9327164 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/preprocessing_convert_to_flac.py @@ -0,0 +1,100 @@ +# Copyright (C) 2020 Arm Limited or its affiliates. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the License); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an AS IS BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from queue import Queue +import logging +import os +from threading import Thread +import audiotools +from audiotools.wav import InvalidWave + +class W2F: + + logger = '' + + def __init__(self): + global logger + # create logger + logger = logging.getLogger(__name__) + logger.setLevel(logging.DEBUG) + + # create a file handler + handler = logging.FileHandler('converter.log') + handler.setLevel(logging.INFO) + + # create a logging format + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + handler.setFormatter(formatter) + + # add the handlers to the logger + logger.addHandler(handler) + + def convert(self,path): + global logger + file_queue = Queue() + num_converter_threads = 5 + + # collect files to be converted + for root, dirs, files in os.walk(path): + + for file in files: + if file.endswith(".wav"): + file_wav = os.path.join(root, file) + file_flac = file_wav.replace(".wav", ".flac") + + if (os.path.exists(file_flac)): + logger.debug(''.join(["File ",file_flac, " already exists."])) + else: + file_queue.put(file_wav) + + logger.info("Start converting: %s files", str(file_queue.qsize())) + + # Set up some threads to convert files + for i in range(num_converter_threads): + worker = Thread(target=self.process, args=(file_queue,)) + worker.setDaemon(True) + worker.start() + + file_queue.join() + + def process(self, q): + """This is the worker thread function. + It processes files in the queue one after + another. These daemon threads go into an + infinite loop, and only exit when + the main thread ends. + """ + while True: + global logger + compression_quality = '0' #min compression + file_wav = q.get() + file_flac = file_wav.replace(".wav", ".flac") + + try: + audiotools.open(file_wav).convert(file_flac,audiotools.FlacAudio, compression_quality) + logger.info(''.join(["Converted ", file_wav, " to: ", file_flac])) + q.task_done() + except InvalidWave: + logger.error(''.join(["Failed to open file ", file_wav, " to: ", file_flac," failed."]), exc_info=True) + +def convert_to_flac(): + reduced_preprocessing = W2F() + reduced_preprocessing.convert("fluent_speech_commands_dataset/train/") + reduced_preprocessing.convert("fluent_speech_commands_dataset/dev/") + reduced_preprocessing.convert("fluent_speech_commands_dataset/test/") + print('') + + diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/preprocessing_fluent_speech_commands.py b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/preprocessing_fluent_speech_commands.py new file mode 100644 index 0000000..831fb06 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/preprocessing_fluent_speech_commands.py @@ -0,0 +1,50 @@ +# Copyright (C) 2020 Arm Limited or its affiliates. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the License); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an AS IS BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from csv import reader +import os +from shutil import copyfile +from pathlib import Path +import re +def preprocess(flag): + if flag == 'train': + path_csv = 'fluent_speech_commands_dataset/data/train_data.csv' + path_dir = 'fluent_speech_commands_dataset/train/' + elif flag == 'dev': + path_csv = 'fluent_speech_commands_dataset/data/valid_data.csv' + path_dir = 'fluent_speech_commands_dataset/dev/' + else: + path_csv = 'fluent_speech_commands_dataset/data/test_data.csv' + path_dir = 'fluent_speech_commands_dataset/test/' + with open(path_csv, 'r') as read_obj: + csv_reader = reader(read_obj) + if not os.path.exists(path_dir): + os.makedirs(path_dir) + with open(path_dir + flag + '.trans.txt', 'w') as write_obj: + for row in csv_reader: + print(row) + if(row[1] == 'path'): + continue + head, file_name = os.path.split(row[1]) + copyfile('fluent_speech_commands_dataset/' + row[1],path_dir + file_name) + text = row[3] + text = text.upper() + text = re.sub('[^a-zA-Z \']+', '', text) #remove all other chars + write_obj.write(Path(file_name).stem + " " + text + '\n') +def preprocess_fluent_sppech(): + preprocess('train') + preprocess('dev') + preprocess('test') diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/prune_and_quantise_model.py b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/prune_and_quantise_model.py new file mode 100755 index 0000000..742436b --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/prune_and_quantise_model.py @@ -0,0 +1,386 @@ +# Copyright (C) 2020 Arm Limited or its affiliates. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the License); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an AS IS BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import multiprocessing +import os +import pathlib + +import tensorflow as tf +import tensorflow_model_optimization as tfmot +from tqdm import tqdm +import numpy as np + +from tinywav2letter import get_metrics +from train_model import log, get_lr_schedule, get_data + +options = tf.data.Options() +options.experimental_distribute.auto_shard_policy = tf.data.experimental.AutoShardPolicy.DATA + +gpus = tf.config.list_logical_devices('GPU') +strategy = tf.distribute.MirroredStrategy(gpus) + +def load_model(): + """ + Returns the model saved at the end of training + """ + + # load the saved model + with strategy.scope(): + model = tf.keras.models.load_model(f'saved_models/tiny_wav2letter', + custom_objects={'ctc_loss': get_metrics("loss"), 'ctc_ler':get_metrics("ler")} + ) + + return model + +def evaluate_model(args, model): + """ + Evaluates an unquantised model + + Args: + args: The command line arguments + model: The model to evaluate + """ + + # Get the data to evaluate the model on + (fluent_speech_test_data, fluent_speech_test_num_steps) = get_data(args, "test_fluent_speech", args.batch_size) + + # Compile and evaluate the model - LER + with strategy.scope(): + opt = tf.keras.optimizers.Adam(learning_rate=get_lr_schedule(learning_rate = 1e-6, steps_per_epoch=fluent_speech_test_num_steps)) + model.compile(loss=get_metrics("loss"), metrics=[get_metrics("ler")], optimizer=opt, run_eagerly=True) + log(f'Evaluating TinyWav2Letter - LER') + model.evaluate(fluent_speech_test_data) + + # Get the data to evaluate the model on + (fluent_speech_test_data, fluent_speech_test_num_steps) = get_data(args, "test_fluent_speech", batch_size=1) # #based on batch=1 + + # Compile and evaluate the model - WER + with strategy.scope(): + opt = tf.keras.optimizers.Adam(learning_rate=get_lr_schedule(learning_rate = 1e-6, steps_per_epoch=fluent_speech_test_num_steps)) + model.compile(loss=get_metrics("loss"), metrics=[get_metrics("wer")], optimizer=opt,run_eagerly=True) + log(f'Evaluating TinyWav2Letter - WER') + model.evaluate(fluent_speech_test_data) + +def prune_model(args, model): + """Performs pruning, fine-tuning and returns stripped pruned model""" + + # Get all the training and validation data + (full_training_data, full_training_num_steps) = get_data(args, "train_full_size", args.batch_size) + (full_validation_data, full_validation_num_steps) = get_data(args, "val_full_size", args.batch_size) + (fluent_speech_training_data, fluent_speech_training_num_steps) = get_data(args, "train_fluent_speech",args.batch_size) + (fluent_speech_validation_data, fluent_speech_validation_num_steps) = get_data(args, "val_fluent_speech",args.batch_size) + (fluent_speech_test_data, fluent_speech_test_num_steps) = get_data(args, "test_fluent_speech", args.batch_size) + + log("Pruning model to {} sparsity".format(args.sparsity)) + + log_dir = f"logs/pruned_model" + + # Set up the callbacks + pruning_callbacks = [ + tfmot.sparsity.keras.UpdatePruningStep(), + tfmot.sparsity.keras.PruningSummaries(log_dir=log_dir), + ] + + # Perform the pruning - full_training_data + pruning_params = { + "pruning_schedule": tfmot.sparsity.keras.ConstantSparsity( + args.sparsity, begin_step=0, end_step=int(full_training_num_steps * 0.7), frequency=10 + ) + } + + + with strategy.scope(): + pruned_model = tfmot.sparsity.keras.prune_low_magnitude(model, **pruning_params) + opt = tf.keras.optimizers.Adam(learning_rate=get_lr_schedule(learning_rate = 1e-4, steps_per_epoch=full_training_num_steps)) + pruned_model.compile(loss=get_metrics("loss"), metrics=[get_metrics("ler")], optimizer=opt) + + pruned_model.fit( + full_training_data, + epochs=5, + verbose=1, + callbacks=pruning_callbacks, + validation_data=full_validation_data, + ) + + # Perform the pruning - fluent_speech_training_data + pruning_params = { + "pruning_schedule": tfmot.sparsity.keras.ConstantSparsity( + args.sparsity, begin_step=0, end_step=int(fluent_speech_validation_num_steps * 0.7), frequency=10 + ) + } + + + with strategy.scope(): + pruned_model = tfmot.sparsity.keras.prune_low_magnitude(model, **pruning_params) + opt = tf.keras.optimizers.Adam(learning_rate=get_lr_schedule(learning_rate = 1e-4, steps_per_epoch=fluent_speech_validation_num_steps)) + pruned_model.compile(loss=get_metrics("loss"), metrics=[get_metrics("ler")], optimizer=opt) + + pruned_model.fit( + fluent_speech_training_data, + epochs=5, + verbose=1, + callbacks=pruning_callbacks, + validation_data=fluent_speech_validation_data, + ) + + stripped_model = tfmot.sparsity.keras.strip_pruning(pruned_model) + + return stripped_model + +def prepare_model_for_inference(model, input_window_length = 296): + "Takes the a model and returns a model with fixed input size" + MFCC_coeffs = 39 + + # Define the input + layer_input = tf.keras.layers.Input((input_window_length, MFCC_coeffs), batch_size=1) + static_shaped_model = tf.keras.models.Model( + inputs=[layer_input], outputs=[model.call(layer_input)] + ) + return static_shaped_model + +def tflite_conversion(model, tflite_path, conversion_type="fp32"): + """Performs tflite conversion (fp32, int8).""" + # Prepare model for inference + model = prepare_model_for_inference(model) + converter = tf.lite.TFLiteConverter.from_keras_model(model) + + # define a dataset to calibrate the conversion to INT8 + def representative_dataset_gen(input_dim): + calib_data = [] + for data in tqdm(fluent_speech_test_data.take(1000), desc="model calibration"): + input_data = data[0] + for i in range(input_data.shape[1] // input_dim): + input_chunks = [ + input_data[:, i * input_dim: (i + 1) * input_dim, :, ] + ] + for chunk in input_chunks: + calib_data.append([chunk]) + + return lambda: [ + (yield data) for data in tqdm(calib_data, desc="model calibration") + ] + + if conversion_type == "int8": + log("Quantizing Model") + (fluent_speech_test_data, fluent_speech_test_num_steps) = get_data(args, "test_fluent_speech", args.batch_size) + converter.optimizations = [tf.lite.Optimize.DEFAULT] + converter.inference_input_type = tf.int8 + converter.inference_output_type = tf.int8 + converter.representative_dataset = representative_dataset_gen(model.input_shape[1]) + + tflite_model = converter.convert() + open(tflite_path, "wb").write(tflite_model) + +def evaluate_tflite(tflite_path, input_window_length = 296): + """Evaluates tflite (fp32, int8).""" + results_ler = [] + results_wer = [] + (fluent_speech_test_data, fluent_speech_test_num_steps) = get_data(args, "test_fluent_speech", batch_size=1) + log("Setting number of used threads to {}".format(multiprocessing.cpu_count())) + interpreter = tf.lite.Interpreter( + model_path=tflite_path, num_threads=multiprocessing.cpu_count() + ) + interpreter.allocate_tensors() + input_chunk = interpreter.get_input_details()[0] + output_details = interpreter.get_output_details()[0] + + input_shape = input_chunk["shape"] + log("eval_model() - input_shape: {}".format(input_shape)) + input_dtype = input_chunk["dtype"] + output_dtype = output_details["dtype"] + + # Check if the input/output type is quantized, + # set scale and zero-point accordingly + if input_dtype != tf.float32: + input_scale, input_zero_point = input_chunk["quantization"] + else: + input_scale, input_zero_point = 1, 0 + + if output_dtype != tf.float32: + output_scale, output_zero_point = output_details["quantization"] + else: + output_scale, output_zero_point = 1, 0 + + log("Running {} iterations".format(fluent_speech_test_num_steps)) + for i_iter, (data, label) in enumerate( + tqdm(fluent_speech_test_data, total=fluent_speech_test_num_steps) + ): + data = data / input_scale + input_zero_point + # Round the data up if dtype is int8, uint8 or int16 + if input_dtype is not np.float32: + data = np.round(data) + + while data.shape[1] < input_window_length: + data = np.append(data, data[:, -2:-1, :], axis=1) + # Zero-pad any odd-length inputs + if data.shape[1] % 2 == 1: + log('Input length is odd, zero-padding to even (first layer has stride 2)') + data = np.concatenate([data, np.zeros((1, 1, data.shape[2]), dtype=input_dtype)], axis=1) + + context = 24 + 2 * (7 * 3 + 16) # = 98 - theoretical max receptive field on each side + size = input_chunk['shape'][1] + inner = size - 2 * context + data_end = data.shape[1] + + # Initialize variables for the sliding window loop + data_pos = 0 + outputs = [] + + while data_pos < data_end: + if data_pos == 0: + # Align inputs from the first window to the start of the data and include the intial context in the output + start = data_pos + end = start + size + y_start = 0 + y_end = y_start + (size - context) // 2 + data_pos = end - context + elif data_pos + inner + context >= data_end: + # Shift left to align final window to the end of the data and include the final context in the output + shift = (data_pos + inner + context) - data_end + start = data_pos - context - shift + end = start + size + assert start >= 0 + y_start = (shift + context) // 2 # Will be even because we assert it above + y_end = size // 2 + data_pos = data_end + else: + # Capture only the inner region from mid-input inferences, excluding output from both context regions + start = data_pos - context + end = start + size + y_start = context // 2 + y_end = y_start + inner // 2 + data_pos = end - context + + interpreter.set_tensor( + input_chunk["index"], tf.cast(data[:, start:end, :], input_dtype)) + interpreter.invoke() + cur_output_data = interpreter.get_tensor(output_details["index"])[:, :, y_start:y_end, :] + cur_output_data = output_scale * ( + cur_output_data.astype(np.float32) - output_zero_point + ) + outputs.append(cur_output_data) + + complete = np.concatenate(outputs, axis=2) + results_ler.append(get_metrics("ler")(label, complete)) + results_wer.append(get_metrics("wer")(label, complete)) + + log("ler: {}".format(np.mean(results_ler))) + log("wer: {}".format(np.mean(results_wer))) #based on batch=1 + +def main(args): + + model = load_model() + evaluate_model(args, model) + + if args.prune: + model = prune_model(args, model) + model.save(f"saved_models/pruned_tiny_wav2letter") + evaluate_model(args, model) + + output_directory = pathlib.Path(os.path.dirname(os.path.abspath(__file__))) + output_directory = os.path.join(output_directory, "tiny_wav2letter/tflite_models") + wav2letter_tflite_path = os.path.join(output_directory, args.prune * "pruned_" + f"tiny_wav2letter_int8.tflite") + + if not os.path.exists(os.path.dirname(wav2letter_tflite_path)): + try: + os.makedirs(os.path.dirname(wav2letter_tflite_path)) + except OSError as exc: + raise + + # Convert the saved model to TFLite and then evaluate + tflite_conversion(model, wav2letter_tflite_path, "int8") + evaluate_tflite(wav2letter_tflite_path) + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(allow_abbrev=False) + parser.add_argument( + "--training_set_size", + dest="training_set_size", + type=int, + required=False, + default = 132553, + help="The number of samples in the training set" + ) + parser.add_argument( + "--fluent_speech_training_set_size", + dest="fluent_speech_set_size", + type=int, + required=False, + default = 23132, + help="The number of samples in the fluent speech training dataset" + ) + parser.add_argument( + "--batch_size", + dest="batch_size", + type=int, + required=False, + default=32, + help="batch size wanted when creating model", + ) + + parser.add_argument( + "--finetuning_epochs", + dest="finetuning_epochs", + type=int, + required=False, + default=10, + help="Number of epochs for fine-tuning", + ) + parser.add_argument( + "--full_data_dir", + dest="full_data_dir", + type=str, + required=False, + default='librispeech_full_size', + help="Path to dataset directory", + ) + parser.add_argument( + "--reduced_data_dir", + dest="reduced_data_dir", + type=str, + required=False, + default='librispeech_reduced_size', + help="Path to dataset directory", + ) + parser.add_argument( + "--fluent_speech_data_dir", + dest="fluent_speech_data_dir", + type=str, + required=False, + default='fluent_speech_commands_dataset', + help="Path to dataset directory", + ) + parser.add_argument( + "--prune", + dest="prune", + required=False, + action='store_true', + help="Prune model true or false", + ) + parser.add_argument( + "--sparsity", + dest="sparsity", + type=float, + required=False, + default=0.5, + help="Level of sparsity required", + ) + + args = parser.parse_args() + main(args) diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/recreate_model.sh b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/recreate_model.sh new file mode 100644 index 0000000..5cc1b39 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/recreate_model.sh @@ -0,0 +1,11 @@ +python3 -m venv env +source env/bin/activate + +pip install -r requirements.txt +python preprocessing.py +python train_model.py --with_baseline --baseline_epochs 30 --with_finetuning --finetuning_epochs 10 --with_fluent_speech --fluent_speech_epochs 30 +python prune_and_quantise_model.py --prune --sparsity 0.5 --finetuning_epochs 10 +python prune_and_quantise_model.py --sparsity 0.5 --finetuning_epochs 10 + + + diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/requirements.txt b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/requirements.txt new file mode 100644 index 0000000..6ea5a87 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/requirements.txt @@ -0,0 +1,10 @@ +librosa==0.8.1 +numpy==1.19.5 +tensorboard==2.6.0 +tensorboard-data-server==0.6.1 +tensorboard-plugin-profile==2.5.0 +tensorboard-plugin-wit==1.8.0 +tensorflow==2.4.1 +tensorflow-model-optimization==0.6.0 +tqdm +jiwer==2.3.0 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/pruned_tiny_wav2letter/saved_model.pb b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/pruned_tiny_wav2letter/saved_model.pb new file mode 100644 index 0000000..5b2edf2 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/pruned_tiny_wav2letter/saved_model.pb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:081d7d86e2b6fd788ca37b9566213560140f595d7702a2126b5a4b895d00cc9e +size 206996 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/pruned_tiny_wav2letter/variables/variables.data-00000-of-00001 b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/pruned_tiny_wav2letter/variables/variables.data-00000-of-00001 new file mode 100644 index 0000000..134cd19 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/pruned_tiny_wav2letter/variables/variables.data-00000-of-00001 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df3647d94be8c5feb098f69122e22007b11873bddb11bdc606112576d4588d4f +size 15644464 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/pruned_tiny_wav2letter/variables/variables.index b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/pruned_tiny_wav2letter/variables/variables.index new file mode 100644 index 0000000..e69b315 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/pruned_tiny_wav2letter/variables/variables.index @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6ba80bc6403066c573eed4e039219c085f2d83b32c3e0e19f40a21a03c8efb7 +size 1339 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/tiny_wav2letter/saved_model.pb b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/tiny_wav2letter/saved_model.pb new file mode 100644 index 0000000..93177d0 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/tiny_wav2letter/saved_model.pb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:163b79816a803b5d03d45e2a792e62981a1102fc0d9bd58efb4947d44b5e2af7 +size 264815 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/tiny_wav2letter/variables/variables.data-00000-of-00001 b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/tiny_wav2letter/variables/variables.data-00000-of-00001 new file mode 100644 index 0000000..230a0dc --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/tiny_wav2letter/variables/variables.data-00000-of-00001 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4048120f5ff4474cf70d3161723ac756b053e7053df76ffd48387416dab24e4 +size 46925195 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/tiny_wav2letter/variables/variables.index b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/tiny_wav2letter/variables/variables.index new file mode 100644 index 0000000..b53f5fd --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/saved_models/tiny_wav2letter/variables/variables.index @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82c10539eb05229e769397e7ee90a7befa1ec22377032da89440d69c69f4e07d +size 4440 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/tinywav2letter.py b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/tinywav2letter.py new file mode 100644 index 0000000..c998497 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/tinywav2letter.py @@ -0,0 +1,152 @@ +# Copyright (C) 2020 Arm Limited or its affiliates. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the License); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an AS IS BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Model definition for Tinywav2Letter.""" +import tensorflow as tf +from tensorflow.python.ops import ctc_ops +import numpy as np +from jiwer import wer + +def get_metrics(metric): + """Get metrics needed to compile wav2letter.""" + def ctc_preparation(tensor, y_predict): + if len(y_predict.shape) == 4: + y_predict = tf.squeeze(y_predict, axis=1) + y_predict = tf.transpose(y_predict, (1, 0, 2)) + sequence_lengths, labels = tensor[:, 0], tensor[:, 1:] + idx = tf.where(tf.not_equal(labels, 28)) + sparse_labels = tf.SparseTensor( + idx, tf.gather_nd(labels, idx), tf.shape(labels, out_type=tf.int64) + ) + return sparse_labels, sequence_lengths, y_predict + + def get_loss(): + """Calculate CTC loss.""" + def ctc_loss(y_true, y_predict): + sparse_labels, logit_length, y_predict = ctc_preparation(y_true, y_predict) + return tf.reduce_mean( + ctc_ops.ctc_loss_v2( + labels=sparse_labels, + logits=y_predict, + label_length=None, + logit_length=logit_length, + blank_index=-1, + ) + ) + return ctc_loss + + def get_ler(): + """Calculate CTC LER (Letter Error Rate).""" + def ctc_ler(y_true, y_predict): + sparse_labels, logit_length, y_predict = ctc_preparation(y_true, y_predict) + decoded, log_probabilities = tf.nn.ctc_greedy_decoder( + y_predict, tf.cast(logit_length, tf.int32), merge_repeated=True + ) + return tf.reduce_mean( + tf.edit_distance( + tf.cast(decoded[0], tf.int32), tf.cast(sparse_labels, tf.int32) + ) + ) + return ctc_ler + def get_wer(): + """Calculate CTC WER (Word Error Rate) only for batch size = 1.""" + + def trans_int_to_string(trans_int): + #create dictionary int -> string (0 -> a 1 -> b) + string = "" + alphabet = "abcdefghijklmnopqrstuvwxyz' @" + alphabet_dict = {} + count = 0 + for x in alphabet: + alphabet_dict[count] = x + count += 1 + for letter in trans_int: + letter_np = np.array(letter).item(0) + if letter_np != 28: + string += alphabet_dict[letter_np] + return string + + def ctc_wer(y_true, y_predict): + sparse_labels, logit_length, y_predict = ctc_preparation(y_true, y_predict) + decoded, log_probabilities = tf.nn.ctc_greedy_decoder( + y_predict, tf.cast(logit_length, tf.int32), merge_repeated=True + ) + true_sentence = tf.cast(sparse_labels.values, tf.int32) + return wer(str(trans_int_to_string(true_sentence)),str(trans_int_to_string(decoded[0].values))) + return ctc_wer + + return {"loss": get_loss(), "ler": get_ler(), "wer": get_wer()}[metric] + + +def create_tinywav2letter(batch_size=1, no_stride_count=5, filters_small=100, filters_large_1=750, filters_large_2=750) -> tf.keras.models.Model: + """Create and return Tinywav2Letter model""" + layer = tf.keras.layers + leaky_relu = layer.LeakyReLU([0.20000000298023224]) + MFCC_coeffs = 39 + input = layer.Input(shape=[None, MFCC_coeffs], batch_size=batch_size) + # Reshape to prepare input for first layer + x = layer.Reshape([1, -1, 39])(input) + # One striding layer of output size [batch_size, max_time / 2, 250] + x = layer.Conv2D( + filters=250, + kernel_size=[1, 48], + padding="same", + activation=None, + strides=[1, 2], + )(x) + # Add non-linearity + x = leaky_relu(x) + # layers without striding of output size [batch_size, max_time / 2, 250] + for i in range(0, no_stride_count): + x = layer.Conv2D( + filters=filters_small, + kernel_size=[1, 7], + padding="same", + activation=None, + strides=[1, 1], + )(x) + # Add non-linearity + x = leaky_relu(x) + # 1 layer with high kernel width and output size [batch_size, max_time / 2, 2000] + x = layer.Conv2D( + filters=filters_large_1, + kernel_size=[1, 32], + padding="same", + activation=None, + strides=[1, 1], + )(x) + # Add non-linearity + x = leaky_relu(x) + # 1 layer of output size [batch_size, max_time / 2, 2000] + x = layer.Conv2D( + filters=filters_large_2, + kernel_size=[1, 1], + padding="same", + activation=None, + strides=[1, 1], + )(x) + # Add non-linearity + x = leaky_relu(x) + # 1 layer of output size [batch_size, max_time / 2, num_classes] + # We must not apply a non linearity in this last layer + x = layer.Conv2D( + filters=29, + kernel_size=[1, 1], + padding="same", + activation=None, + strides=[1, 1], + )(x) + return tf.keras.models.Model(inputs=[input], outputs=[x]) diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/train_model.py b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/train_model.py new file mode 100644 index 0000000..1ae4943 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/recreate_code/train_model.py @@ -0,0 +1,267 @@ +# Copyright (C) 2020 Arm Limited or its affiliates. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the License); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an AS IS BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Wav2letter training, optimisation and evaluation script""" +import argparse + +import tensorflow as tf + +from tinywav2letter import create_tinywav2letter, get_metrics +from load_mfccs import MFCC_Loader + +options = tf.data.Options() +options.experimental_distribute.auto_shard_policy = tf.data.experimental.AutoShardPolicy.DATA + +gpus = tf.config.list_logical_devices('GPU') +strategy = tf.distribute.MirroredStrategy(gpus) + + +def log(std): + """Log the given string to the standard output.""" + print("******* {}".format(std), flush=True) + +def get_data(args, dataset_type, batch_size): + """Returns particular training and validation dataset.""" + dataset = MFCC_Loader(args.full_data_dir, args.reduced_data_dir,args.fluent_speech_data_dir) + + return {"train_full_size": [dataset.full_training_set(batch_size=batch_size, num_samples = args.training_set_size).with_options(options), dataset.num_steps(batch=batch_size)], + "train_reduced_size": [dataset.reduced_training_set(batch_size=batch_size, num_samples = args.training_set_size).with_options(options), dataset.num_steps(batch=batch_size)], + "val_full_size": [dataset.full_validation_set(batch_size=batch_size).with_options(options), dataset.num_steps(batch=batch_size)], + "val_reduced_size": [dataset.reduced_validation_set(batch_size=batch_size).with_options(options), dataset.num_steps(batch=batch_size)], + "train_fluent_speech": [dataset.fluent_speech_train_set(batch_size=batch_size, num_samples = args.fluent_speech_set_size).with_options(options), dataset.num_steps(batch=batch_size)], + "val_fluent_speech": [dataset.fluent_speech_validation_set(batch_size=batch_size).with_options(options), dataset.num_steps(batch=batch_size)], + "test_fluent_speech": [dataset.fluent_speech_test_set(batch_size=batch_size).with_options(options),dataset.num_steps(batch=batch_size)], + }[dataset_type] + +def setup_callbacks(checkpoint_path, log_dir): + """Returns callbacks for baseline training and optimization fine-tuning.""" + callbacks = [ + tf.keras.callbacks.TerminateOnNaN(), + tf.keras.callbacks.ModelCheckpoint( + filepath=checkpoint_path, + verbose=1, + save_weights_only=True, + save_freq='epoch', # save every epoch + ), + tf.keras.callbacks.TensorBoard( + log_dir=log_dir, + histogram_freq=1, # update every epoch + update_freq=100, # update every 100 batch + + ), + ] + return callbacks + +def get_lr_schedule(steps_per_epoch, learning_rate=1e-4, lr_schedule_config=[[1.0, 0.1, 0.01, 0.001]]): + """Returns learn rate schedule for baseline training and optimization fine-tuning.""" + initial_learning_rate = learning_rate + lr_schedule = tf.keras.optimizers.schedules.PiecewiseConstantDecay( + boundaries=list(p[1] * steps_per_epoch for p in lr_schedule_config), + values=[initial_learning_rate] + list(p[0] * initial_learning_rate for p in lr_schedule_config)) + return lr_schedule + + +def train_model(args): + """Performs pruning, fine-tuning and returns stripped pruned model""" + log("Commencing Model Training") + + # Get all of the required datasets + (full_training_data, full_training_num_steps) = get_data(args, "train_full_size", args.batch_size) + (reduced_training_data, reduced_training_num_steps) = get_data(args, "train_reduced_size", args.batch_size) + (full_validation_data, full_validation_num_steps) = get_data(args, "val_full_size", args.batch_size) + (reduced_validation_data, reduced_validation_num_steps) = get_data(args, "val_reduced_size", args.batch_size) + (fluent_speech_training_data, fluent_speech_training_num_steps) = get_data(args, "train_fluent_speech", args.batch_size) + (fluent_speech_validation_data, fluent_speech_validation_num_steps) = get_data(args, "val_fluent_speech", args.batch_size) + (fluent_speech_test_data, fluent_speech_test_num_steps) = get_data(args, "test_fluent_speech",args.batch_size) + + # Set up checkpoint paths, directories for the log files and the callbacks + baseline_checkpoint_path = f"checkpoints/baseline/checkpoint.ckpt" + finetuning_checkpoint_path = f"checkpoints/finetuning/checkpoint.ckpt" + + baseline_log_dir = f"logs/tiny_wav2letter_baseline" + finetuning_log_dir = f"logs/tiny_wav2letter_finetuning" + + baseline_callbacks = setup_callbacks(baseline_checkpoint_path, baseline_log_dir) + finetuning_callbacks = setup_callbacks(finetuning_checkpoint_path, finetuning_log_dir) + + # Initialise the Tiny Wav2Letter model + with strategy.scope(): + model = create_tinywav2letter(batch_size = args.batch_size) + + + # Perform the baseline training with the full size LibriSpeech dataset + if args.with_baseline: + + with strategy.scope(): + opt = tf.keras.optimizers.Adam(learning_rate=get_lr_schedule(learning_rate = 1e-4, steps_per_epoch=full_training_num_steps)) + model.compile(loss=get_metrics("loss"), metrics=[get_metrics("ler")], optimizer=opt) + + model.fit( + full_training_data, + epochs=args.baseline_epochs, + verbose=1, + callbacks=baseline_callbacks, + validation_data=full_validation_data, + initial_epoch = 0 + ) + + log(f'Evaluating Tiny Wav2Letter post baseline training') + model.evaluate(fluent_speech_test_data) + + # Perform finetuning training with the reduced size MiniLibriSpeech dataset + if args.with_finetuning: + + with strategy.scope(): + opt = tf.keras.optimizers.Adam(learning_rate=get_lr_schedule(learning_rate = 1e-5, steps_per_epoch=reduced_training_num_steps)) + model.compile(loss=get_metrics("loss"), metrics=[get_metrics("ler")], optimizer=opt) + + model.fit( + reduced_training_data, + epochs=args.finetuning_epochs + args.baseline_epochs, + verbose=1, + callbacks=finetuning_callbacks, + validation_data=reduced_validation_data, + initial_epoch = args.baseline_epochs + ) + + log(f'Evaluating Tiny Wav2Letter post finetuning') + model.evaluate(x=fluent_speech_test_data) + + + if args.with_fluent_speech: + + with strategy.scope(): + opt = tf.keras.optimizers.Adam(learning_rate=get_lr_schedule(learning_rate = 1e-5, steps_per_epoch=fluent_speech_training_num_steps)) + model.compile(loss=get_metrics("loss"), metrics=[get_metrics("ler")], optimizer=opt,run_eagerly=True) + + model.fit( + fluent_speech_training_data, + epochs=args.finetuning_epochs + args.baseline_epochs, + verbose=1, + callbacks=finetuning_callbacks, + validation_data=fluent_speech_validation_data, + initial_epoch = args.baseline_epochs + ) + + model.evaluate(x=fluent_speech_test_data) + # Save the final trained model in TF SavedModel format + model.save(f"saved_models/tiny_wav2letter") + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(allow_abbrev=False) + parser.add_argument( + "--training_set_size", + dest="training_set_size", + type=int, + required=False, + default = 132553, + help="The number of samples in the training set" + ) + parser.add_argument( + "--fluent_speech_training_set_size", + dest="fluent_speech_set_size", + type=int, + required=False, + default = 23132, + help="The number of samples in the fluent speech training dataset" + ) + parser.add_argument( + "--batch_size", + dest="batch_size", + type=int, + required=False, + default=32, + help="batch size wanted when creating model", + ) + parser.add_argument( + "--full_data_dir", + dest="full_data_dir", + type=str, + required=False, + default='librispeech_full_size', + help="Path to dataset directory", + ) + parser.add_argument( + "--reduced_data_dir", + dest="reduced_data_dir", + type=str, + required=False, + default='librispeech_reduced_size', + help="Path to dataset directory", + ) + parser.add_argument( + "--fluent_speech_data_dir", + dest="fluent_speech_data_dir", + type=str, + required=False, + default='fluent_speech_commands_dataset', + help="Path to dataset directory", + ) + parser.add_argument( + "--load_model", + dest="load_model", + required=False, + action='store_true', + help="Model number to load", + ) + parser.add_argument( + "--with_baseline", + dest="with_baseline", + required=False, + action='store_true', + help="Perform pre-training baseline using the full size dataset", + ) + parser.add_argument( + "--with_finetuning", + dest="with_finetuning", + required=False, + action='store_true', + help="Perform fine-tuning training using the reduced corpus dataset", + ) + parser.add_argument( + "--with_fluent_speech", + dest="with_fluent_speech", + required=False, + action='store_true', + help="Perform fluent_speech training using the fluent speech dataset", + ) + parser.add_argument( + "--baseline_epochs", + dest="baseline_epochs", + type=int, + required=False, + default=30, + help="Number of epochs for baseline training", + ) + parser.add_argument( + "--finetuning_epochs", + dest="finetuning_epochs", + type=int, + required=False, + default=10, + help="Number of epochs for fine-tuning", + ) + parser.add_argument( + "--fluent_speech_epochs", + dest="fluent_speech_epochs", + type=int, + required=False, + default=30, + help="Number of epochs for fluent speech training", + ) + args = parser.parse_args() + train_model(args) diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/testing_input/input_1_int8/0.npy b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/testing_input/input_1_int8/0.npy new file mode 100644 index 0000000..61a809d --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/testing_input/input_1_int8/0.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e373de705b3eae4fc8b51998317cf1ebf7cf26e728b8524fd1c97a2822811757 +size 11672 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/testing_output/Identity_int8/0.npy b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/testing_output/Identity_int8/0.npy new file mode 100644 index 0000000..74b5cb7 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/testing_output/Identity_int8/0.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ab109a9f565cd48cf95d5f83cfdd9c0694db812407b01f0c535172ac68f0bf8 +size 4420 diff --git a/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/tiny_wav2letter_pruned_int8.tflite b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/tiny_wav2letter_pruned_int8.tflite new file mode 100644 index 0000000..fbf9521 --- /dev/null +++ b/models/speech_recognition/tiny_wav2letter/tflite_pruned_int8/tiny_wav2letter_pruned_int8.tflite @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4487c1f55e60b0824939f9ae0bce9522489700a8d9bcb1830f17822d4a5c0041 +size 3997112