from __future__ import with_statement, print_function, absolute_import
import numpy as np
import bandmat as bm
import bandmat.linalg as bla
# https://github.com/MattShannon/bandmat/blob/master/example_spg.py
# Copied from the above link. Thanks to Matt shannon!
def build_win_mats(windows, frames):
"""Builds a window matrix of a given size for each window in a collection.
`windows` specifies the collection of windows as a sequence of
`(l, u, win_coeff)` triples, where `l` and `u` are non-negative integers
specifying the left and right extents of the window and `win_coeff` is an
array specifying the window coefficients.
The returned value is a list of window matrices, one for each of the
windows specified in `windows`.
Each window matrix is a `frames` by `frames` Toeplitz matrix with lower
bandwidth `l` and upper bandwidth `u`.
The non-zero coefficients in each row of this Toeplitz matrix are given by
`win_coeff`.
The returned window matrices are stored as BandMats, i.e. using a banded
representation.
"""
win_mats = []
for l, u, win_coeff in windows:
assert l >= 0 and u >= 0
assert len(win_coeff) == l + u + 1
win_coeffs = np.tile(np.reshape(win_coeff, (l + u + 1, 1)), frames)
win_mat = bm.band_c_bm(u, l, win_coeffs).T
win_mats.append(win_mat)
return win_mats
def build_poe(b_frames, tau_frames, win_mats, sdw=None):
r"""Computes natural parameters for a Gaussian product-of-experts model.
The natural parameters (b-value vector and precision matrix) are returned.
The returned precision matrix is stored as a BandMat.
Mathematically the b-value vector is given as:
b = \sum_d \transpose{W_d} \tilde{b}_d
and the precision matrix is given as:
P = \sum_d \transpose{W_d} \text{diag}(\tilde{tau}_d) W_d
where $W_d$ is the window matrix for window $d$ as specified by an element
of `win_mats`, $\tilde{b}_d$ is the sequence over time of b-value
parameters for window $d$ as given by a column of `b_frames`, and
$\tilde{\tau}_d$ is the sequence over time of precision parameters for
window $d$ as given by a column of `tau_frames`.
"""
if sdw is None:
sdw = max([win_mat.l + win_mat.u for win_mat in win_mats])
num_windows = len(win_mats)
frames = len(b_frames)
assert np.shape(b_frames) == (frames, num_windows)
assert np.shape(tau_frames) == (frames, num_windows)
assert all([win_mat.l + win_mat.u <= sdw for win_mat in win_mats])
b = np.zeros((frames,))
prec = bm.zeros(sdw, sdw, frames)
for win_index, win_mat in enumerate(win_mats):
bm.dot_mv_plus_equals(win_mat.T, b_frames[:, win_index], target=b)
bm.dot_mm_plus_equals(win_mat.T, win_mat, target_bm=prec,
diag=tau_frames[:, win_index])
return b, prec
[docs]def mlpg(mean_frames, variance_frames, windows):
"""Maximum Parameter Likelihood Generation (MLPG)
Function ``f: (T, D) -> (T, static_dim)``.
It peforms Maximum Likelihood Parameter Generation (MLPG) algorithm
to generate static features from static + dynamic features over
time frames. The implementation was heavily inspired by [1]_ and
using bandmat_ for efficient computation.
.. _bandmat: https://github.com/MattShannon/bandmat
.. [1] M. Shannon, supervised by W. Byrne (2014),
Probabilistic acoustic modelling for parametric speech synthesis
PhD thesis, University of Cambridge, UK
Args:
mean_frames (2darray): The input features (static + delta).
In statistical speech synthesis, these are means of gaussian
distributions predicted by neural networks or decision trees.
variance_frames (2d or 1darray): Variances (static + delta ) of gaussian
distributions over time frames (2d) or global variances (1d).
If global variances are given, these will get expanded over frames.
windows (list): A sequence of ``(l, u, win_coeff)`` triples, where
``l`` and ``u`` are non-negative integers specifying the left
and right extents of the window and `win_coeff` is an array
specifying the window coefficients.
Returns:
Generated static features over time
Examples:
>>> from nnmnkwii import functions as F
>>> windows = [
... (0, 0, np.array([1.0])), # static
... (1, 1, np.array([-0.5, 0.0, 0.5])), # delta
... (1, 1, np.array([1.0, -2.0, 1.0])), # delta-delta
... ]
>>> T, static_dim = 10, 24
>>> mean_frames = np.random.rand(T, static_dim * len(windows))
>>> variance_frames = np.random.rand(T, static_dim * len(windows))
>>> static_features = F.mlpg(mean_frames, variance_frames, windows)
>>> assert static_features.shape == (T, static_dim)
See also:
:func:`nnmnkwii.autograd.mlpg`
"""
T, D = mean_frames.shape
# expand variances over frames
if variance_frames.ndim == 1 and variance_frames.shape[0] == D:
variance_frames = np.tile(variance_frames, (T, 1))
assert mean_frames.shape == variance_frames.shape
static_dim = D // len(windows)
num_windows = len(windows)
win_mats = build_win_mats(windows, T)
# workspaces; those will be updated in the following generation loop
means = np.zeros((T, num_windows))
precisions = np.zeros((T, num_windows))
# Perform dimention-wise generation
y = np.zeros((T, static_dim))
for d in range(static_dim):
for win_idx in range(num_windows):
means[:, win_idx] = mean_frames[:, win_idx * static_dim + d]
precisions[:, win_idx] = 1 / \
variance_frames[:, win_idx * static_dim + d]
bs = precisions * means
b, P = build_poe(bs, precisions, win_mats)
y[:, d] = bla.solveh(P, b)
return y