mirror of
https://github.com/dscripka/openWakeWord.git
synced 2026-08-27 18:17:20 -04:00
Set fix input frame size to simplify code, added benchmark script to estimate inference efficiency
This commit is contained in:
parent
8d89349179
commit
a8949f3b48
4 changed files with 101 additions and 15 deletions
50
benchmark/benchmark.py
Normal file
50
benchmark/benchmark.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# Copyright 2022 David Scripka. All rights reserved.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# http://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.
|
||||
|
||||
# Imports
|
||||
import openwakeword
|
||||
import numpy as np
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
# Define benchmark to assess inference speed of models at different audio chunk sizes
|
||||
# Smaller chunk sizes may increase model performance, at the cost of inference efficiency
|
||||
def run_benchmark():
|
||||
# Load models
|
||||
model_paths = [str(i) for i in Path("openwakeword/resources/models").glob("*.onnx") \
|
||||
if "embedding" not in str(i) and "melspectrogram" not in str(i)]
|
||||
M = openwakeword.Model(
|
||||
wakeword_model_paths=model_paths,
|
||||
input_sizes=[16]
|
||||
)
|
||||
|
||||
# Create random data to use for benchmarking
|
||||
clip = np.random.random(16000*10).astype(np.float32)
|
||||
|
||||
# Run the benchmark
|
||||
step_size = 1280
|
||||
preprocessing_times = []
|
||||
model_times = defaultdict(list)
|
||||
for i in range(0, clip.shape[0]-step_size, step_size):
|
||||
pred, timing_dict = M.predict(clip[i:i+step_size], timing=True)
|
||||
preprocessing_times.append(timing_dict["preprocessor"])
|
||||
for mdl_name in M.models.keys():
|
||||
model_times[mdl_name].append(timing_dict["models"][mdl_name])
|
||||
|
||||
print(f"Average of {np.mean(preprocessing_times)} for audio preprocessing with a frame size of {step_size/16000} seconds")
|
||||
for mdl_name in M.models.keys():
|
||||
print(f"Average of {np.mean(model_times[mdl_name])} for model \"{mdl_name}\"", "\n\n")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_benchmark()
|
||||
|
|
@ -18,6 +18,7 @@ import numpy as np
|
|||
import os
|
||||
from openwakeword.utils import AudioFeatures
|
||||
from typing import List
|
||||
import time
|
||||
|
||||
class Model():
|
||||
def __init__(self, wakeword_model_paths: List[str], input_sizes: List[int], **kwargs):
|
||||
|
|
@ -36,16 +37,40 @@ class Model():
|
|||
# Create AudioFeatures object
|
||||
self.preprocessor = AudioFeatures(**kwargs)
|
||||
|
||||
def predict(self, x):
|
||||
def predict(self, x, timing=False):
|
||||
"""Predict with all of the wakeword models on the input audio frames"""
|
||||
# Get audio features
|
||||
if timing:
|
||||
timing_dict = {}
|
||||
timing_dict["models"] = {}
|
||||
feature_start = time.time()
|
||||
|
||||
self.preprocessor(x)
|
||||
|
||||
if timing:
|
||||
feature_end = time.time()
|
||||
timing_dict["preprocessor"] = feature_end - feature_start
|
||||
|
||||
# Get predictions from model(s)
|
||||
predictions = {}
|
||||
for mdl in self.models.keys():
|
||||
input_name = self.models[mdl].get_inputs()[0].name
|
||||
|
||||
if timing:
|
||||
model_start = time.time()
|
||||
|
||||
predictions[mdl] = self.models[mdl].run(
|
||||
None,
|
||||
{input_name: self.preprocessor.get_features(self.model_inputs[mdl])}
|
||||
)[0][0][0]
|
||||
return predictions
|
||||
|
||||
if timing:
|
||||
model_end = time.time()
|
||||
timing_dict["models"][mdl] = model_end - model_start
|
||||
|
||||
if timing:
|
||||
return predictions, timing_dict
|
||||
else:
|
||||
return predictions
|
||||
|
||||
|
||||
|
|
@ -12,17 +12,19 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
# Imports
|
||||
import os
|
||||
import onnxruntime as ort
|
||||
import numpy as np
|
||||
import pathlib
|
||||
from collections import deque
|
||||
|
||||
# Base class for computing audio features using Google's speech_embedding model (https://tfhub.dev/google/speech_embedding/1)
|
||||
class AudioFeatures():
|
||||
def __init__(self,
|
||||
melspec_onnx_model_path=os.path.join("openwakeword", "resources", "models", "melspectrogram.onnx"),
|
||||
embedding_onnx_model_path=os.path.join("openwakeword", "resources", "models", "embedding_model.onnx"),
|
||||
melspec_onnx_model_path=os.path.join(pathlib.Path(__file__).parent.resolve(), "resources", "models", "melspectrogram.onnx"),
|
||||
embedding_onnx_model_path=os.path.join(pathlib.Path(__file__).parent.resolve(), "resources", "models", "embedding_model.onnx"),
|
||||
sr=16000,
|
||||
ncpu=1
|
||||
):
|
||||
|
|
@ -80,22 +82,17 @@ class AudioFeatures():
|
|||
self.melspectrogram_buffer = self.melspectrogram_buffer[-self.melspectrogram_max_len:, :]
|
||||
|
||||
def _streaming_features(self, x):
|
||||
assert len(x) == 1280*3
|
||||
if len(x) != 1280:
|
||||
raise ValueError(f"You must provide input samples in frames of 1280 samples @ 1600khz. Received a frame of {len(x)} samples.")
|
||||
self._streaming_melspectrogram(x)
|
||||
if self.melspectrogram_buffer.shape[0] < 76:
|
||||
pass
|
||||
for i in range(-3,0,1):
|
||||
start = -76+8*i
|
||||
end = start + 76
|
||||
x = self.melspectrogram_buffer[start:end].astype(np.float32)[None,:,:,None]
|
||||
if x.shape[1] != 76:
|
||||
continue
|
||||
x = self.melspectrogram_buffer[-76:].astype(np.float32)[None,:,:,None]
|
||||
if x.shape[1] == 76:
|
||||
self.feature_buffer = np.vstack((self.feature_buffer, self.embedding_model.run(None, {'input_1': x})[0].squeeze()))
|
||||
|
||||
if self.feature_buffer.shape[0] > self.feature_buffer_max_len:
|
||||
self.feature_buffer = self.feature_buffer[-self.feature_buffer_max_len:, :]
|
||||
|
||||
def get_features(self, x, n_feature_frames=16):
|
||||
def get_features(self, n_feature_frames=16):
|
||||
return self.feature_buffer[-n_feature_frames:, :][None,].astype(np.float32)
|
||||
|
||||
def __call__(self, x):
|
||||
|
|
|
|||
|
|
@ -11,6 +11,20 @@
|
|||
# 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.
|
||||
# Copyright 2022 David Scripka. All rights reserved.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# http://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.
|
||||
|
||||
|
||||
# Imports
|
||||
import openwakeword
|
||||
|
|
@ -30,7 +44,7 @@ class TestModels:
|
|||
input_sizes=[16]
|
||||
)
|
||||
|
||||
step_size = 1280*3
|
||||
step_size = 1280
|
||||
predictions = []
|
||||
for i in range(0, hey_jane_clip.shape[0]-step_size, step_size):
|
||||
predictions.append(model.predict(hey_jane_clip[i:i+step_size])["hey_jane"])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue