Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ShawnHymel/610d31df82b3ffdd7416dfec99badf74 to your computer and use it in GitHub Desktop.
Save ShawnHymel/610d31df82b3ffdd7416dfec99badf74 to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from os import listdir\n",
"from os.path import isdir, join\n",
"import array\n",
"import struct\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import random\n",
"import librosa\n",
"import sounddevice as sd\n",
"import tensorflow as tf\n",
"from tensorflow.keras import layers, models, optimizers, regularizers, backend\n",
"from tensorflow import lite"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"TensorFlow v2.0.0\n",
"Keras v2.2.4-tf\n"
]
}
],
"source": [
"# Print versions\n",
"print('TensorFlow v' + tf.__version__)\n",
"print('Keras v' + tf.keras.__version__)"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"# Settings (some of the many hyperparameters)\n",
"dataset_path = 'C:\\\\Users\\\\sgmustadio\\\\Documents\\\\Python\\\\datasets\\\\speech_commands_dataset'\n",
"target_word = 'stop'\n",
"model_filename = 'wake_word_stft_stop_model.h5'\n",
"tflite_model_filename = 'wake_word_stft_stop_model.tflite'\n",
"c_model_name = 'wake_word_stft_stop_model'\n",
"rec_length = 1.0 # Time (seconds) of expected recordings\n",
"perc_target = 0.1 # Percentage of samples that should be our target wake word\n",
"val_ratio = 0.2 # Percentage of samples that should be held for validation set\n",
"test_ratio = 0.2 # Percentage of samples that should be held for test set\n",
"sample_rate = 16000 # The Arduino Nano 33 BLE basically forces 16 kHz sampling on us\n",
"stft_n_fft = 512 # Number of FFT bins (also, number of samples in each slice)\n",
"stft_hop_length = 340 # Distance between each FFT slice (number of samples)\n",
"stft_window = 'hanning' # \"The window of choice if you don't have any better ideas\"\n",
"stft_n_windows = 46 # Number of slices (windows) to look for in each STFT\n",
"stft_min_bin = 1 # Lowest bin to use (inclusive; basically, filter out DC) \n",
"stft_max_bin = 65 # Highest bin (exclusive; basically, filter out >2kHz)\n",
"shift_n_bits = 3 # Number of bits to shift 16-bit STFT values to make 8-bit values (before clipping)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"['backward', 'bed', 'bird', 'cat', 'dog', 'down', 'eight', 'five', 'follow', 'forward', 'four', 'go', 'happy', 'house', 'learn', 'left', 'marvin', 'nine', 'no', 'off', 'on', 'one', 'right', 'seven', 'sheila', 'six', 'stop', 'three', 'tree', 'two', 'up', 'visual', 'wow', 'yes', 'zero']\n"
]
}
],
"source": [
"# Create an all targets list (without background noise set)\n",
"all_targets = [name for name in listdir(dataset_path) if isdir(join(dataset_path, name))]\n",
"all_targets.remove('_background_noise_')\n",
"print(all_targets)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Total number of samples: 105829\n"
]
}
],
"source": [
"# Create a list of file paths along with ground truth vector (y)\n",
"# Note that y[i] is 0 for \"not <target word>\" and 1 for \"<target word>\"\n",
"target_filenames = []\n",
"target_y = []\n",
"other_filenames = []\n",
"other_y = []\n",
"num_samples = 0\n",
"for index, target in enumerate(all_targets):\n",
" num_samples += len(listdir(join(dataset_path, target)))\n",
" samples_in_dir = listdir(join(dataset_path, target))\n",
" samples_in_dir = [join(dataset_path, target, sample) for sample in samples_in_dir]\n",
" if target == target_word:\n",
" target_filenames.append(samples_in_dir)\n",
" target_y.append(np.ones(len(samples_in_dir)))\n",
" else:\n",
" other_filenames.append(samples_in_dir)\n",
" other_y.append(np.zeros(len(samples_in_dir)))\n",
"print('Total number of samples:', num_samples)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Number of target samples: 3872\n",
"Number of other samples: 34848\n",
"Number of total samples: 38720\n"
]
}
],
"source": [
"# Calculate number of target and other samples based on desired percentage\n",
"num_target_samples = len(listdir(join(dataset_path, target_word)))\n",
"if (num_target_samples / num_samples) < perc_target:\n",
" num_total_samples = round(num_target_samples / perc_target)\n",
"else:\n",
" num_total_samples = num_samples\n",
" num_target_samples = round(perc_target * num_total_samples)\n",
"num_other_samples = num_total_samples - num_target_samples\n",
"print('Number of target samples:', num_target_samples)\n",
"print('Number of other samples:', num_other_samples)\n",
"print('Number of total samples:', num_total_samples)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"# Flatten target filename and y vectors. Zip them.\n",
"target_filenames = [item for sublist in target_filenames for item in sublist]\n",
"target_y = [item for sublist in target_y for item in sublist]\n",
"target_filenames_y = list(zip(target_filenames, target_y))\n",
"\n",
"# Flatten other filename and y vectors. Zip them.\n",
"other_filenames = [item for sublist in other_filenames for item in sublist]\n",
"other_y = [item for sublist in other_y for item in sublist]\n",
"other_filenames_y = list(zip(other_filenames, other_y))"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"# Shuffle target filename/truth list and only keep the desired amount\n",
"random.shuffle(target_filenames_y)\n",
"target_filenames_y = target_filenames_y[:num_target_samples]\n",
"\n",
"# Shuffle other filename/truth list and only keep the desired amount\n",
"random.shuffle(other_filenames_y)\n",
"other_filenames_y = other_filenames_y[:num_other_samples]"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"# Combine target and other lists, shuffle, and unzip\n",
"filenames_y = target_filenames_y + other_filenames_y\n",
"random.shuffle(filenames_y)\n",
"filenames, y = zip(*filenames_y)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"# Calculate validation and test set sizes\n",
"val_set_size = int(len(filenames) * val_ratio)\n",
"test_set_size = int(len(filenames) * test_ratio)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Number of training samples: 23232\n",
"Number of validation samples: 7744\n",
"Number of test samples: 7744\n"
]
}
],
"source": [
"# Break dataset apart into train, validation, and test sets\n",
"filenames_val = filenames[:val_set_size]\n",
"filenames_test = filenames[val_set_size:(val_set_size + test_set_size)]\n",
"filenames_train = filenames[(val_set_size + test_set_size):]\n",
"print('Number of training samples:', len(filenames_train))\n",
"print('Number of validation samples:', len(filenames_val))\n",
"print('Number of test samples:', len(filenames_test))"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"# Break y apart into train, validation, and test sets\n",
"y_orig_val = y[:val_set_size]\n",
"y_orig_test = y[val_set_size:(val_set_size + test_set_size)]\n",
"y_orig_train = y[(val_set_size + test_set_size):]"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"# Function: load file, play it, draw waveform, draw spectrogram\n",
"def analyze_clip(file_path):\n",
" \n",
" # Load file\n",
" waveform, fs = librosa.load(file_path, sr=sample_rate)\n",
" \n",
" # Test playing it\n",
" sd.play(waveform, fs)\n",
" \n",
" # Convert floating point wav data (-1.0 to 1.0) to 16-bit PCM\n",
" waveform = np.around(waveform * 32767)\n",
" \n",
" # Calculate STFT\n",
" stft = np.abs(librosa.stft(waveform,\n",
" n_fft=stft_n_fft,\n",
" hop_length=stft_hop_length,\n",
" win_length=stft_n_fft,\n",
" window=stft_window,\n",
" center=False))\n",
" \n",
" # Adjust for quantization and scaling in 16-bit fixed point FFT\n",
" stft = np.around(stft / stft_n_fft)\n",
" \n",
" # Reduce precision by converting to 8-bit unsigned values [0..255]\n",
" stft = np.around(stft / (2 ** shift_n_bits))\n",
" stft = np.clip(stft, a_min=0, a_max=255)\n",
" \n",
" # Only keep the frequency bins we care about (i.e. filter out unwanted frequencies)\n",
" stft = stft[stft_min_bin:stft_max_bin,:]\n",
" \n",
" # Average every 2 bins together to reduce size of STFT\n",
" stft_comp = np.zeros((int(stft.shape[0] / 2), stft.shape[1]))\n",
" print(stft_comp.shape)\n",
" for idx, slice in enumerate(stft.T):\n",
" stft_comp[:, idx] = np.mean(slice.reshape(-1, 2), axis=1)\n",
" \n",
" # Print information about clip\n",
" print(file_path)\n",
" print('Waveform shape:', waveform.shape)\n",
" print('Compressed STFT shape:', stft_comp.shape)\n",
" \n",
" # Draw time domain signal\n",
" plt.plot(waveform)\n",
" plt.show()\n",
" \n",
" # Draw spectrogram\n",
" plt.figure()\n",
" plt.imshow(stft_comp, cmap='inferno', origin='lower')\n",
" \n",
" return stft_comp"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(32, 46)\n",
"C:\\Users\\sgmustadio\\Documents\\Python\\datasets\\speech_commands_dataset\\off\\716757ce_nohash_0.wav\n",
"Waveform shape: (16000,)\n",
"Compressed STFT shape: (32, 46)\n"
]
},
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
},
{
"data": {
"text/plain": [
"<Figure size 432x288 with 0 Axes>"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
},
{
"data": {
"text/plain": [
"<Figure size 432x288 with 0 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"stft = analyze_clip(filenames_train[1])\n",
"plt.figure()"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"# Function: Create STFT, keeping only ones of desired length\n",
"def extract_stft(in_files, in_y):\n",
" prob_cnt = 0\n",
" out_x = []\n",
" out_y = []\n",
" \n",
" for index, path in enumerate(in_files):\n",
" \n",
" # Check to make sure we're reading a .wav file\n",
" if not path.endswith('.wav'):\n",
" continue\n",
" \n",
" # Load .wav file\n",
" waveform, fs = librosa.load(path, sr=sample_rate)\n",
" \n",
" # Convert to something that approximates a 16-bit PCM waveform\n",
" waveform = np.around(waveform * 32767)\n",
" \n",
" # Calculate STFT\n",
" stft = np.abs(librosa.stft(waveform,\n",
" n_fft=stft_n_fft,\n",
" hop_length=stft_hop_length,\n",
" win_length=stft_n_fft,\n",
" window=stft_window,\n",
" center=False))\n",
" \n",
" # Adjust for quantization and scaling in 16-bit fixed point FFT\n",
" stft = np.around(stft / stft_n_fft)\n",
" \n",
" # Reduce precision by converting to 8-bit unsigned values [0..255]\n",
" stft = np.around(stft / (2 ** shift_n_bits))\n",
" stft = np.clip(stft, a_min=0, a_max=255)\n",
" \n",
" # Only keep the frequency bins we care about\n",
" stft = stft[stft_min_bin:stft_max_bin,:]\n",
" \n",
" # Average every 2 bins together to reduce size of STFT\n",
" stft_comp = np.zeros((int(stft.shape[0] / 2), stft.shape[1]))\n",
" for idx, slice in enumerate(stft.T):\n",
" stft_comp[:, idx] = np.mean(slice.reshape(-1, 2), axis=1)\n",
" \n",
" # Only keep STFTs with given length\n",
" if stft.shape[1] == stft_n_windows:\n",
" out_x.append(stft_comp)\n",
" out_y.append(in_y[index])\n",
" else:\n",
" #print('Dropped:', index, stft_comp.shape)\n",
" prob_cnt += 1\n",
" \n",
" return out_x, out_y, prob_cnt"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Extracting features from training set...\n",
"Removed percentage: 0.09633264462809918\n",
"Extracting features from validation set...\n",
"Removed percentage: 0.10046487603305786\n",
"Extracting features from test set...\n",
"Removed percentage: 0.09517045454545454\n"
]
}
],
"source": [
"# Create training, validation, and test sets\n",
"print('Extracting features from training set...')\n",
"x_train, y_train, prob_cnt = extract_stft(filenames_train, \n",
" y_orig_train)\n",
"print('Removed percentage:', prob_cnt / len(y_orig_train))\n",
"print('Extracting features from validation set...')\n",
"x_val, y_val, prob_cnt = extract_stft(filenames_val, y_orig_val)\n",
"print('Removed percentage:', prob_cnt / len(y_orig_val))\n",
"print('Extracting features from test set...')\n",
"x_test, y_test, prob_cnt = extract_stft(filenames_test, y_orig_test)\n",
"print('Removed percentage:', prob_cnt / len(y_orig_test))"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [],
"source": [
"# Convert feature sets to numpy tensors\n",
"x_train = np.array(x_train)\n",
"x_val = np.array(x_val)\n",
"x_test = np.array(x_test)\n",
"\n",
"y_train = np.array(y_train)\n",
"y_val = np.array(y_val)\n",
"y_test = np.array(y_test)"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Training samples:\t (20994, 32, 46)\n",
"Validation samples:\t (6966, 32, 46)\n",
"Test samples:\t\t (7007, 32, 46)\n",
"Training truth set:\t (20994,)\n",
"Validation truth set:\t (6966,)\n",
"Test truth set:\t\t (7007,)\n"
]
}
],
"source": [
"# View tensor dimensions\n",
"print('Training samples:\\t', x_train.shape)\n",
"print('Validation samples:\\t', x_val.shape)\n",
"print('Test samples:\\t\\t', x_test.shape)\n",
"print('Training truth set:\\t', y_train.shape)\n",
"print('Validation truth set:\\t', y_val.shape)\n",
"print('Test truth set:\\t\\t', y_test.shape)"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(20994, 32, 46, 1)\n",
"(6966, 32, 46, 1)\n",
"(7007, 32, 46, 1)\n"
]
}
],
"source": [
"# CNN for TF expects (batch, height, width, channels)\n",
"# So we reshape the input tensors with a \"color\" channel of 1\n",
"x_train = x_train.reshape(x_train.shape[0], \n",
" x_train.shape[1], \n",
" x_train.shape[2], \n",
" 1)\n",
"x_val = x_val.reshape(x_val.shape[0], \n",
" x_val.shape[1], \n",
" x_val.shape[2], \n",
" 1)\n",
"x_test = x_test.reshape(x_test.shape[0], \n",
" x_test.shape[1], \n",
" x_test.shape[2], \n",
" 1)\n",
"print(x_train.shape)\n",
"print(x_val.shape)\n",
"print(x_test.shape)"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(32, 46, 1)\n"
]
}
],
"source": [
"# Input shape for CNN is size of STFT of 1 sample\n",
"sample_shape = x_test.shape[1:]\n",
"print(sample_shape)"
]
},
{
"cell_type": "code",
"execution_count": 66,
"metadata": {},
"outputs": [],
"source": [
"# Build model\n",
"# Based on: https://www.geeksforgeeks.org/python-image-classification-using-keras/\n",
"model = models.Sequential()\n",
"\n",
"model.add(layers.Conv2D(16, \n",
" (2, 2), \n",
" activation='relu',\n",
" input_shape=sample_shape))\n",
"model.add(layers.MaxPooling2D(pool_size=(2, 2)))\n",
"model.add(layers.Dropout(0.1))\n",
"\n",
"model.add(layers.Conv2D(16, \n",
" (2, 2), \n",
" activation='relu'))\n",
"model.add(layers.MaxPooling2D(pool_size=(2, 2)))\n",
"model.add(layers.Dropout(0.1))\n",
"\n",
"model.add(layers.Conv2D(16, \n",
" (2, 2), \n",
" activation='relu'))\n",
"model.add(layers.MaxPooling2D(pool_size=(2, 2)))\n",
"model.add(layers.Dropout(0.1))\n",
"\n",
"model.add(layers.Conv2D(16, \n",
" (2, 2), \n",
" activation='relu'))\n",
"model.add(layers.MaxPooling2D(pool_size=(2, 2)))\n",
"model.add(layers.Dropout(0.1))\n",
"\n",
"#model.add(layers.Conv2D(16, (2, 2), activation='relu'))\n",
"#model.add(layers.MaxPooling2D(pool_size=(2, 2)))\n",
"\n",
"# Classifier\n",
"model.add(layers.Flatten())\n",
"model.add(layers.Dense(16, activation='relu', kernel_regularizer=regularizers.l1(0.01)))\n",
"model.add(layers.Dropout(0.5))\n",
"model.add(layers.Dense(1, activation='sigmoid'))"
]
},
{
"cell_type": "code",
"execution_count": 67,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Model: \"sequential_2\"\n",
"_________________________________________________________________\n",
"Layer (type) Output Shape Param # \n",
"=================================================================\n",
"conv2d_8 (Conv2D) (None, 31, 45, 16) 80 \n",
"_________________________________________________________________\n",
"max_pooling2d_8 (MaxPooling2 (None, 15, 22, 16) 0 \n",
"_________________________________________________________________\n",
"dropout_6 (Dropout) (None, 15, 22, 16) 0 \n",
"_________________________________________________________________\n",
"conv2d_9 (Conv2D) (None, 14, 21, 16) 1040 \n",
"_________________________________________________________________\n",
"max_pooling2d_9 (MaxPooling2 (None, 7, 10, 16) 0 \n",
"_________________________________________________________________\n",
"dropout_7 (Dropout) (None, 7, 10, 16) 0 \n",
"_________________________________________________________________\n",
"conv2d_10 (Conv2D) (None, 6, 9, 16) 1040 \n",
"_________________________________________________________________\n",
"max_pooling2d_10 (MaxPooling (None, 3, 4, 16) 0 \n",
"_________________________________________________________________\n",
"dropout_8 (Dropout) (None, 3, 4, 16) 0 \n",
"_________________________________________________________________\n",
"conv2d_11 (Conv2D) (None, 2, 3, 16) 1040 \n",
"_________________________________________________________________\n",
"max_pooling2d_11 (MaxPooling (None, 1, 1, 16) 0 \n",
"_________________________________________________________________\n",
"dropout_9 (Dropout) (None, 1, 1, 16) 0 \n",
"_________________________________________________________________\n",
"flatten_2 (Flatten) (None, 16) 0 \n",
"_________________________________________________________________\n",
"dense_4 (Dense) (None, 16) 272 \n",
"_________________________________________________________________\n",
"dropout_10 (Dropout) (None, 16) 0 \n",
"_________________________________________________________________\n",
"dense_5 (Dense) (None, 1) 17 \n",
"=================================================================\n",
"Total params: 3,489\n",
"Trainable params: 3,489\n",
"Non-trainable params: 0\n",
"_________________________________________________________________\n"
]
}
],
"source": [
"# Display model\n",
"model.summary()"
]
},
{
"cell_type": "code",
"execution_count": 68,
"metadata": {},
"outputs": [],
"source": [
"# Quantization aware training (Does not seem to be supported on this version)\n",
"#sess = tf.compat.v1.keras.backend.get_session()\n",
"#tf.contrib.quantize.create_training_graph(sess.graph)\n",
"#sess.run(tf.global_variables_initializer())"
]
},
{
"cell_type": "code",
"execution_count": 69,
"metadata": {},
"outputs": [],
"source": [
"# Add training parameters to model\n",
"rmsprop = optimizers.RMSprop(learning_rate=0.001)\n",
"model.compile(loss='binary_crossentropy', \n",
" optimizer=rmsprop, \n",
" metrics=['acc'])"
]
},
{
"cell_type": "code",
"execution_count": 70,
"metadata": {},
"outputs": [],
"source": [
"# Train\n",
"history = model.fit(x_train, \n",
" y_train, \n",
" epochs=100, \n",
" batch_size=100, \n",
" validation_data=(x_val, y_val),\n",
" verbose=0)"
]
},
{
"cell_type": "code",
"execution_count": 71,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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ZozWqVNHO3furL169VJV1datW+vKlStVVfW+++7TTp06aadOnXTUqFE77tehQwe98sortWPHjnrKKadoSUnJTv166aWXtGfPntqtWzc96aST9Ntvv1VV1Q0bNuhll12mnTt31kMOOUSfffZZVVV97bXXtHv37tqlSxc98cQTd7peZf+dqjMlJTZx6LTTVG+91eK1584tPf7yy6pnn636+9+rvveexYK3b2/1GjRQbd7c4stT0a+faps2FiO/fLnqH/+o+thjquvXp9feYcPsvrffbhOaVq9O7/x0+O476+fo0RZ3v9depTH+nTur9u5tk51OOSXxNTZuTC++P5YlS8o3IWxXQa5NmBo2TPX44zO7DRuW+oPs27fvDhG/6667dPjw4apqs1XXrVunqqorV67UAw44QLdHvhHxhP6+++7TwYMHq6pqUVGR5uXl7RD61ZH/lq1bt+rxxx+vRUVFqlpW2IP7hYWF2rlzZ/3hhx90w4YN2rFjR/3kk0908eLFmpeXpzNnzlRV1fPPP1/HjRu3U5/WrFmzo60PP/yw3njjjaqqetNNN+mwwIeyZs0a/e6777RFixa6aNGiMm0N4kJffv76V/uPnDLFZms2aKA6aJAde/tt1Tp1TOBErB6o7r+/6muvqRYVqe65p2qHDqqrVtk5GzbYTNagOP30k01qGjq04u0dN87a0KKFapcuFb9eMrZvt75ffbXNej3hhNJjV11lk6Ly8uwBWVNJJvQ5mdQsWwTdN0G3japyyy230KVLF04++WSWLVvGihUrEl7n/fffZ1AkNKBLly506dJlx7FnnnmGHj160L17d+bOnRs3aVmQDz74gHPOOYfdd9+dhg0b0r9//x1pj9u2bUu3iEMymOo4SHFxMaeddhqHHHII99xzD3PnzgUsdXFwxasmTZrw8ccfc9xxx9E28tvX0xmnz6ZNpSGJQTZvNr/8McdYVsSmTW3C0dNPW06Xc84xl8TChZYw66WXLD/LnDmWBrdLFytbvNh85UccYdErhx5qLpkoH35oC22cdlrF+xIdkC0uzpx/PhEi5r6ZO9d89EE/+xFH2NjGtm3hM1DWNKplUrPIIky7nLPPPpsbb7yRTz75hI0bN9KjRw/AEoWtXLmSGTNmUKdOHdq0aRM3PXGQeGmBFy9ezL333sv06dNp0qQJl112Wcrr2IM8PrsFQhry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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
},
{
"data": {
"image/png": "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G0a3bt244oor2LNnz/5yN9100/5b7/7qV78CYMKECaxfv54BAwYwYMAAADp27MjmzSEz/NBDD9G1a1e6du26/9a7q1at4sQTT+T73/8+Xbp04fzzzy+ynnjmz59Pnz596NatG5dddhlfffXV/vV37tyZbt267b8p2FtvvbX/AR89e/Zkx44d5f5uo9K/J72IlNktt0CqH8TTowdEYmFcLVu2pHfv3vzjH/9gyJAhTJs2jSuuuAIzo379+kyfPp2mTZuyefNm+vTpwyWXXJLwuZpPPPEEDRs2ZMGCBSxYsIBevXrtnzdu3DgOPfRQ8vPzOeecc1iwYAH/8z//w0MPPcTs2bNp1apVkWXNmzePZ599lg8++AB359RTT6Vfv360aNGCZcuWMXXqVH7/+9/z3e9+lxdeeKHE+5tfc801PProo/Tr149f/vKX3H333TzyyCPcf//9rFy5knr16u1P84wfP56JEydyxhlnsHPnTurXr1+Gbzs+tdBFpNLEpl1i0y3uzu233063bt0499xzWbduHV988UXC5cyZM2d/YO3WrRvdunXbP++5556jV69e9OzZk0WLFpV646133nmHyy67jEaNGtG4cWMuv/xy3n77bQA6depEjx49gJJv0Qvh/uxbt26lX79+AFx77bXMmTNnfx1HjBjB5MmT91+ResYZZ3DrrbcyYcIEtm7dmpIrVdVCF6mBSmpJV6RLL72UW2+9lY8++og9e/bsb1lPmTKF3Nxc5s2bR506dejYsWPcW+bGitd6X7lyJePHj2fu3Lm0aNGC6667rtTllHQ/q+itdyHcfre0lEsir776KnPmzGHGjBnce++9LFq0iLFjx3LhhRfy2muv0adPH/79739zwgkHd/mOWugiUmkaN25M//79uf7664ucDN22bRtt2rShTp06zJ49m9XxHiAc46yzztr/IOiFCxeyYMECINx6t1GjRjRr1owvvviCmTNn7v9MkyZN4uapzzrrLF588UV2797Nrl27mD59OmeeeWaZt61Zs2a0aNFif+v+z3/+M/369aOgoIC1a9cyYMAAHnjgAbZu3crOnTv5/PPPOemkk7jtttvIysris88+K/M6i1MLXUQq1fDhw7n88suL9HgZMWIEF198MVlZWfTo0aPUlupNN93E9773Pbp160aPHj3o3bs3EJ4+1LNnT7p06XLArXdH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dty4su0kjjoqnIOI7hRvvjl11zXEy1cnezuMsh6tJPp88c9Ueg69ogYFdKlIW7e6n332gS1697BzePTR0KMn9p/snHNC/v/IIwv/4Zo3d7/yynDy9frrQ8op2kso3jB4cMjXR737rvvppxfuEH796/APPHFieP/88+6bNpW+PXv2uL/2mvt77xWd/tZbhdchnHZauFI41fLzwwn1Rx8N5yHiHcXEmjUr1KdevbCDe/DB5I/OnnwyfPb990Mqr337cD1Fsg6mi+HBXExV0snwVPezV0AXiWP79nDy9a673FesKJwevWr3nXfid4vMz3dfujR0+bz3Xvff/jbcZ+eVV+IHroKCcIL33HMT7wg6dw4nkidNCi3/Tz91nzEj9M657LKi/aiHDg1HBP/4R0hFnXBC2EEcemgIov/v/4WdVqr89KdF69qmTbiwLZ69e92PPTYMOTmh7uB+5plFU1+JnHJKCOQFBWFnBe6/+EXqtqUiVdbVsgroItXE8uWhV87GjeFE83vvhZb6wIHhBG+8YN+2bbg6dubMkBqqXz8E+Lp1Q1ojekHZxo2h50801RS95cPnn4egePrpIc100UXu11wTUkqlnRyOdhMdPTos5w9/CDeLgxDoi7fW77svzIt2Py0oCOmr6Anz884Lt7aYMcP9738PO82oBQtCmYcfLpw2fHjYSV1/fajLzJnxL6CraM8/737xxSVfzBYrPz+cT/ryy9TXRQFdJA0UFLgvWxZurTx1arjgauPGA4PmypUhz3/BBfEDxssvh/MBEHoARQ//Tz89nD/o1asw3dS8eThPcMMN4UKzs84Kwf7hh0NPHQjris3P5+eH7qAQAm30grAVK8LOZujQA+u0a1cIyNH1xg4jR4Ydyy23hBRNbO+n9esLeyYVP5H4ne+E7rDRlMyePe6PPx6ODg4/POzsBg50/9Wv3P/73/L/JnffHdZ5yCFh5/LwwyWnkHbsCF1rwf1b33JfuLB8606kpIBuYX7ly7QHXIhUJ3v2hKdkzZoFF10E11wDRx1VOD8/H954Izzy8JVXoEGD8FjApk1h+XLYuDGUO+ccePVVqFev6PLd4e67w9CgAdSuHR7yUqsWfPYZtGsXv167dsHChaFcnTowbRrcfz/07BmeQDVgAPztb/E/u3lz+Gx2dnie77vvwrp10KQJXHghvPUWbNgAffpA165hG9atCw95dw/PAj755FDPffugoCDUo1at8OjI6KMUDzkEevSAXr3g9ddDHa++OjwYZcyY8AjKM84ID44/7DA44gg44QQ48cRQx0sugU8/hZ/8JDzUZufO8CjJ3r1h3rwwnHoqDBxYvt9WTywSkTLZuBE+/xyysg4M5rGeew4+/DAEx4KCEMzOPrts63r55RAwt22DmTOTD3QFBTBnTgia06eHncKdd4adgllhuXXrQlCeMgXWrg07kjp1QuDOzw9DnTrhyVYtW4ZHSs6fD9u3h+X8+tfws58VPgHrD38IO6F168IjJqPMwndVty789a9hO9atg8suCzug2HK33w733Ve276nw8wroIlKNrVwZHuF4ww0h0Fa1goLwnOD8fDj++Phl3GHHDsjJgSVLwtHD+vVw883QuXNhub17YcKEEOhPPjm0/ps0KX/dFNBFRDJESQG9GuwLRUQkFRTQRUQyhAK6iEiGUEAXEckQCugiIhlCAV1EJEMooIuIZAgFdBGRDFFlFxaZWS6wugwfaQVsrqDqVGc1cbtr4jZDzdzumrjNcHDb3cHdW8ebUWUBvazMLDvR1VGZrCZud03cZqiZ210TtxkqbruVchERyRAK6CIiGSKdAvqkqq5AFamJ210Ttxlq5nbXxG2GCtrutMmhi4hIydKphS4iIiVQQBcRyRBpEdDNbKCZLTWz5WY2tqrrUxHM7Cgzm21mS8xskZndHJl+qJn9y8yWRV5bVHVdU83MapnZx2b2SmS8JmxzczN73sw+i/zmp9WQ7f5x5O97oZlNNbP6mbbdZvaMmW0ys4Ux0xJuo5n9PBLblprZBQez7mof0M2sFjARGAR0BoabWeeSP5WW8oD/dfcTgT7ADyPbORZ43d2PBV6PjGeam4ElMeM1YZt/B/zD3U8AuhO2P6O328zaAv8DZLl7V6AWMIzM2+4/AsWfjBp3GyP/48OALpHPPB6JeeVS7QM60BtY7u4r3P0bYBowpIrrlHLuvsHdP4q830H4B29L2NY/RYr9Cbi0ampYMcysHXAh8HTM5Ezf5qbAWcAfANz9G3ffSoZvd0RtoIGZ1QYaAuvJsO129znAl8UmJ9rGIcA0d//a3VcCywkxr1zSIaC3BdbGjOdEpmUsM+sI9AQ+AA5z9w0Qgj7QpupqViEeAX4GFMRMy/RtPhrIBZ6NpJqeNrNGZPh2u/s6YDywBtgAbHP3f5Lh2x2RaBtTGt/SIaBbnGkZ29fSzBoDLwC3uPv2qq5PRTKzi4BN7j6vqutSyWoDvYAn3L0nsIv0TzOUKpI3HgJ0Ao4EGpnZVVVbqyqX0viWDgE9BzgqZrwd4TAt45hZHUIwn+Luf49M/sLMjojMPwLYVFX1qwBnAJeY2SpCKu1sM5tMZm8zhL/pHHf/IDL+PCHAZ/p2nwusdPdcd98H/B04nczfbki8jSmNb+kQ0OcCx5pZJzOrSziBMKOK65RyZmaEnOoSd38oZtYM4NrI+2uBlyq7bhXF3X/u7u3cvSPhd33D3a8ig7cZwN03AmvN7PjIpHOAxWT4dhNSLX3MrGHk7/0cwrmiTN9uSLyNM4BhZlbPzDoBxwIflnst7l7tB2Aw8F/gc+COqq5PBW1jX8Kh1gJgfmQYDLQknBVfFnk9tKrrWkHb3x94JfI+47cZ6AFkR37vF4EWNWS77wY+AxYCfwbqZdp2A1MJ5wj2EVrgN5S0jcAdkdi2FBh0MOvWpf8iIhkiHVIuIiKSBAV0EZEMoYAuIpIhFNBFRDKEArqISIZQQBcRyRAK6CIiGeL/AzPDBiEUwDasAAAAAElFTkSuQmCC\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"# Plot results\n",
"import matplotlib.pyplot as plt\n",
"\n",
"acc = history.history['acc']\n",
"val_acc = history.history['val_acc']\n",
"loss = history.history['loss']\n",
"val_loss = history.history['val_loss']\n",
"\n",
"epochs = range(1, len(acc) + 1)\n",
"\n",
"plt.plot(epochs, acc, 'bo', label='Training acc')\n",
"plt.plot(epochs, val_acc, 'b', label='Validation acc')\n",
"plt.title('Training and validation accuracy')\n",
"plt.legend()\n",
"\n",
"plt.figure()\n",
"\n",
"plt.plot(epochs, loss, 'bo', label='Training loss')\n",
"plt.plot(epochs, val_loss, 'b', label='Validation loss')\n",
"plt.title('Training and validation loss')\n",
"plt.legend()\n",
"\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 72,
"metadata": {},
"outputs": [],
"source": [
"# Save the TensorFlow model as a file\n",
"models.save_model(model, model_filename)"
]
},
{
"cell_type": "code",
"execution_count": 73,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"8344"
]
},
"execution_count": 73,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Convert the model to a TF Lite model file with uint8 quantization\n",
"converter = lite.TFLiteConverter.from_keras_model(model)\n",
"converter.optimizations = [lite.Optimize.DEFAULT]\n",
"#converter.post_training_quantize = True\n",
"converter.target_spec.supported_types = [tf.uint8]\n",
"tflite_model = converter.convert()\n",
"open(tflite_model_filename, 'wb').write(tflite_model)"
]
},
{
"cell_type": "code",
"execution_count": 74,
"metadata": {},
"outputs": [],
"source": [
"# Function: Convert some hex value into an array for C programming\n",
"def hex_to_c_array(hex_data, var_name):\n",
" \n",
" c_str = ''\n",
" \n",
" # Create header guard\n",
" c_str += '#ifndef ' + var_name.upper() + '_H\\n'\n",
" c_str += '#define ' + var_name.upper() + '_H\\n\\n'\n",
" \n",
" # Add array length at top of file\n",
" c_str += '\\nunsigned int ' + var_name + '_len = ' + str(len(hex_data)) + ';\\n'\n",
" \n",
" # Declare C variable\n",
" c_str += 'unsigned char ' + var_name + '[] = {'\n",
" hex_array = []\n",
" for i, val in enumerate(hex_data):\n",
"\n",
" # Construct string from hex\n",
" hex_str = format(val, '#04x')\n",
" \n",
" # Add formatting so each line stays within 80 characters\n",
" if (i + 1) < len(hex_data):\n",
" hex_str += ','\n",
" if (i + 1) % 12 == 0:\n",
" hex_str += '\\n '\n",
" hex_array.append(hex_str)\n",
" \n",
" # Add closing brace\n",
" c_str += '\\n ' + format(' '.join(hex_array)) + '\\n};\\n\\n'\n",
" \n",
" # Close out header guard\n",
" c_str += '#endif //' + var_name.upper() + '_H'\n",
" \n",
" return c_str"
]
},
{
"cell_type": "code",
"execution_count": 75,
"metadata": {},
"outputs": [],
"source": [
"# Write TFLite model to a C source file\n",
"with open(c_model_name + '.h', 'w') as file:\n",
" file.write(hex_to_c_array(tflite_model, c_model_name))"
]
},
{
"cell_type": "code",
"execution_count": 76,
"metadata": {},
"outputs": [],
"source": [
"#print(hex_to_c_array(tflite_model, c_model_name))"
]
},
{
"cell_type": "code",
"execution_count": 77,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[0.1499670784922049, 0.94976455]\n"
]
}
],
"source": [
"# Evaulate model with test set\n",
"eval = model.evaluate(x=x_test, y=y_test, verbose=0);\n",
"print(eval)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.5"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment