mirror of
https://github.com/mozilla/DeepSpeech.git
synced 2025-10-26 11:19:39 +00:00
199 lines
8.2 KiB
Python
199 lines
8.2 KiB
Python
import numpy as np
|
|
import tensorflow as tf
|
|
|
|
from math import ceil
|
|
from six.moves import range
|
|
from threading import Thread
|
|
from util.gpu import get_available_gpus
|
|
|
|
|
|
class ModelFeeder(object):
|
|
'''
|
|
Feeds data into a model.
|
|
Feeding is parallelized by independent units called tower feeders (usually one per GPU).
|
|
Each tower feeder provides data from runtime switchable sources (train, dev).
|
|
These sources are to be provided by the DataSet instances whose references are kept.
|
|
Creates, owns and delegates to tower_feeder_count internal tower feeder objects.
|
|
'''
|
|
def __init__(self,
|
|
train_set,
|
|
dev_set,
|
|
numcep,
|
|
numcontext,
|
|
alphabet,
|
|
tower_feeder_count=-1,
|
|
threads_per_queue=4):
|
|
|
|
self.train = train_set
|
|
self.dev = dev_set
|
|
self.sets = [train_set, dev_set]
|
|
self.numcep = numcep
|
|
self.numcontext = numcontext
|
|
self.tower_feeder_count = max(len(get_available_gpus()), 1) if tower_feeder_count < 0 else tower_feeder_count
|
|
self.threads_per_queue = threads_per_queue
|
|
|
|
self.ph_x = tf.placeholder(tf.float32, [None, 2*numcontext+1, numcep])
|
|
self.ph_x_length = tf.placeholder(tf.int32, [])
|
|
self.ph_y = tf.placeholder(tf.int32, [None,])
|
|
self.ph_y_length = tf.placeholder(tf.int32, [])
|
|
self.ph_batch_size = tf.placeholder(tf.int32, [])
|
|
self.ph_queue_selector = tf.placeholder(tf.int32, name='Queue_Selector')
|
|
|
|
self._tower_feeders = [_TowerFeeder(self, i, alphabet) for i in range(self.tower_feeder_count)]
|
|
|
|
def start_queue_threads(self, session, coord):
|
|
'''
|
|
Starts required queue threads on all tower feeders.
|
|
'''
|
|
queue_threads = []
|
|
for tower_feeder in self._tower_feeders:
|
|
queue_threads += tower_feeder.start_queue_threads(session, coord)
|
|
return queue_threads
|
|
|
|
def close_queues(self, session):
|
|
'''
|
|
Closes queues of all tower feeders.
|
|
'''
|
|
for tower_feeder in self._tower_feeders:
|
|
tower_feeder.close_queues(session)
|
|
|
|
def set_data_set(self, feed_dict, data_set):
|
|
'''
|
|
Switches all tower feeders to a different source DataSet.
|
|
The provided feed_dict will get enriched with required placeholder/value pairs.
|
|
The DataSet has to be one of those that got passed into the constructor.
|
|
'''
|
|
index = self.sets.index(data_set)
|
|
assert index >= 0
|
|
feed_dict[self.ph_queue_selector] = index
|
|
feed_dict[self.ph_batch_size] = data_set.batch_size
|
|
|
|
def next_batch(self, tower_feeder_index):
|
|
'''
|
|
Draw the next batch from one of the tower feeders.
|
|
'''
|
|
return self._tower_feeders[tower_feeder_index].next_batch()
|
|
|
|
|
|
class DataSet(object):
|
|
'''
|
|
Represents a collection of audio samples and their respective transcriptions.
|
|
Takes a set of CSV files produced by importers in /bin.
|
|
'''
|
|
def __init__(self, data, batch_size, skip=0, limit=0, ascending=True, next_index=lambda i: i + 1):
|
|
self.data = data
|
|
self.data.sort_values(by="features_len", ascending=ascending, inplace=True)
|
|
self.batch_size = batch_size
|
|
self.next_index = next_index
|
|
self.total_batches = int(ceil(len(self.data) / batch_size))
|
|
|
|
|
|
class _DataSetLoader(object):
|
|
'''
|
|
Internal class that represents an input queue with data from one of the DataSet objects.
|
|
Each tower feeder will create and combine three data set loaders to one switchable queue.
|
|
Keeps a ModelFeeder reference for accessing shared settings and placeholders.
|
|
Keeps a DataSet reference to access its samples.
|
|
'''
|
|
def __init__(self, model_feeder, data_set, alphabet):
|
|
self._model_feeder = model_feeder
|
|
self._data_set = data_set
|
|
self.queue = tf.PaddingFIFOQueue(shapes=[[None, 2 * model_feeder.numcontext + 1, model_feeder.numcep], [], [None,], []],
|
|
dtypes=[tf.float32, tf.int32, tf.int32, tf.int32],
|
|
capacity=data_set.batch_size * 8)
|
|
self._enqueue_op = self.queue.enqueue([model_feeder.ph_x, model_feeder.ph_x_length, model_feeder.ph_y, model_feeder.ph_y_length])
|
|
self._close_op = self.queue.close(cancel_pending_enqueues=True)
|
|
self._alphabet = alphabet
|
|
|
|
def start_queue_threads(self, session, coord):
|
|
'''
|
|
Starts concurrent queue threads for reading samples from the data set.
|
|
'''
|
|
queue_threads = [Thread(target=self._populate_batch_queue, args=(session, coord))
|
|
for i in range(self._model_feeder.threads_per_queue)]
|
|
for queue_thread in queue_threads:
|
|
coord.register_thread(queue_thread)
|
|
queue_thread.daemon = True
|
|
queue_thread.start()
|
|
return queue_threads
|
|
|
|
def close_queue(self, session):
|
|
'''
|
|
Closes the data set queue.
|
|
'''
|
|
session.run(self._close_op)
|
|
|
|
def _populate_batch_queue(self, session, coord):
|
|
'''
|
|
Queue thread routine.
|
|
'''
|
|
file_count = len(self._data_set.data)
|
|
index = -1
|
|
while not coord.should_stop():
|
|
index = self._data_set.next_index(index) % file_count
|
|
features, num_strides, transcript, transcript_len = self._data_set.data.iloc[index]
|
|
|
|
# Create a view into the array with overlapping strides of size
|
|
# numcontext (past) + 1 (present) + numcontext (future)
|
|
window_size = 2*self._model_feeder.numcontext+1
|
|
features = np.lib.stride_tricks.as_strided(
|
|
features,
|
|
(num_strides, window_size, self._model_feeder.numcep),
|
|
(features.strides[0], features.strides[0], features.strides[1]),
|
|
writeable=False)
|
|
|
|
# We add 1 to all elements of the transcript here to avoid any zero
|
|
# values since we use that as an end-of-sequence token for converting
|
|
# the batch into a SparseTensor.
|
|
try:
|
|
session.run(self._enqueue_op, feed_dict={
|
|
self._model_feeder.ph_x: features,
|
|
self._model_feeder.ph_x_length: num_strides,
|
|
self._model_feeder.ph_y: transcript + 1,
|
|
self._model_feeder.ph_y_length: transcript_len
|
|
})
|
|
except tf.errors.CancelledError:
|
|
return
|
|
|
|
|
|
class _TowerFeeder(object):
|
|
'''
|
|
Internal class that represents a switchable input queue for one tower.
|
|
It creates, owns and combines three _DataSetLoader instances.
|
|
Keeps a ModelFeeder reference for accessing shared settings and placeholders.
|
|
'''
|
|
def __init__(self, model_feeder, index, alphabet):
|
|
self._model_feeder = model_feeder
|
|
self.index = index
|
|
self._loaders = [_DataSetLoader(model_feeder, data_set, alphabet) for data_set in model_feeder.sets]
|
|
self._queues = [set_queue.queue for set_queue in self._loaders]
|
|
self._queue = tf.QueueBase.from_list(model_feeder.ph_queue_selector, self._queues)
|
|
self._close_op = self._queue.close(cancel_pending_enqueues=True)
|
|
|
|
def next_batch(self):
|
|
'''
|
|
Draw the next batch from from the combined switchable queue.
|
|
'''
|
|
source, source_lengths, target, target_lengths = self._queue.dequeue_many(self._model_feeder.ph_batch_size)
|
|
# Back to sparse, then subtract one to get the real labels
|
|
sparse_labels = tf.contrib.layers.dense_to_sparse(target)
|
|
neg_ones = tf.SparseTensor(sparse_labels.indices, -1 * tf.ones_like(sparse_labels.values), sparse_labels.dense_shape)
|
|
return source, source_lengths, tf.sparse_add(sparse_labels, neg_ones)
|
|
|
|
def start_queue_threads(self, session, coord):
|
|
'''
|
|
Starts the queue threads of all owned _DataSetLoader instances.
|
|
'''
|
|
queue_threads = []
|
|
for set_queue in self._loaders:
|
|
queue_threads += set_queue.start_queue_threads(session, coord)
|
|
return queue_threads
|
|
|
|
def close_queues(self, session):
|
|
'''
|
|
Closes queues of all owned _DataSetLoader instances.
|
|
'''
|
|
for set_queue in self._loaders:
|
|
set_queue.close_queue(session)
|
|
|