annotate snmf.py @ 19:890cfe424f4a tip

added annotations
author mitian
date Fri, 11 Dec 2015 09:47:40 +0000
parents 8b814fe5781d
children
rev   line source
mitian@16 1 #!/usr/bin/env python
mitian@16 2 # encoding: utf-8
mitian@16 3 """
mitian@16 4 snmf.py
mitian@16 5 -- A modified version of C-NMF by Nieto. Input is no longer feature matrix but SSM.
mitian@16 6
mitian@16 7 C-NMF method for segmentation, modified from here:
mitian@16 8
mitian@16 9 Nieto, O., Jehan, T., Convex Non-negative Matrix Factorization For Automatic
mitian@16 10 Music Structure Identification. Proc. of the 38th IEEE International Conference
mitian@16 11 on Acoustics, Speech, and Signal Processing (ICASSP). Vancouver, Canada, 2013.
mitian@16 12
mitian@16 13 """
mitian@16 14
mitian@16 15 import sys
mitian@16 16 import os
mitian@16 17 import pymf
mitian@16 18 from utils import SegUtil
mitian@16 19 from utils.SegUtil import median_filter
mitian@16 20 import numpy as np
mitian@16 21
mitian@16 22 # Algorithm params
mitian@16 23 h = 8 # Size of median filter for SSMs in C-NMF
mitian@16 24 R = 16 # Size of the median filter for the activation matrix C-NMF
mitian@16 25 rank = 3 # Rank of decomposition for the boundaries
mitian@16 26 rank_labels = 16 # Rank of decomposition for the labels
mitian@16 27 R_labels = 4 # Size of the median filter for the labels
mitian@16 28
mitian@16 29
mitian@16 30 def cnmf(S, rank, niter=500):
mitian@16 31 """(Convex) Non-Negative Matrix Factorization.
mitian@16 32
mitian@16 33 Parameters
mitian@16 34 ----------
mitian@16 35 S: np.array(N, N)
mitian@16 36 SSM.
mitian@16 37 rank: int
mitian@16 38 Rank of decomposition
mitian@16 39 niter: int
mitian@16 40 Number of iterations to be used
mitian@16 41
mitian@16 42 Returns
mitian@16 43 -------
mitian@16 44 F: np.array
mitian@16 45 Cluster matrix (decomposed matrix)
mitian@16 46 G: np.array
mitian@16 47 Activation matrix (decomposed matrix)
mitian@16 48 (s.t. S ~= F * G)
mitian@16 49 """
mitian@16 50 nmf_mdl = pymf.CNMF(S, num_bases=rank)
mitian@16 51 nmf_mdl.factorize(niter=niter)
mitian@16 52 F = np.asarray(nmf_mdl.W)
mitian@16 53 G = np.asarray(nmf_mdl.H)
mitian@16 54 return F, G
mitian@16 55
mitian@16 56 def nmf(S, rank, nither=500):
mitian@16 57 nmf_mdl = pymf.NMF(S, num_bases=rank, niter=nither)
mitian@16 58 nmf_mdl.factorize()
mitian@16 59 F = np.asarray(nmf_mdl.W)
mitian@16 60 G = np.asarray(nmf_mdl.H)
mitian@16 61 return F, G
mitian@16 62
mitian@16 63
mitian@16 64 def most_frequent(x):
mitian@16 65 """Returns the most frequent value in x."""
mitian@16 66 return np.argmax(np.bincount(x))
mitian@16 67
mitian@16 68
mitian@16 69 def compute_labels(X, rank, R, bound_idxs, niter=300):
mitian@16 70 """Computes the labels using the bounds."""
mitian@16 71
mitian@16 72 X = X.T
mitian@16 73 try:
mitian@16 74 F, G = cnmf(X, rank, niter=niter)
mitian@16 75 except:
mitian@16 76 return [1]
mitian@16 77
mitian@16 78 label_frames = filter_activation_matrix(G.T, R)
mitian@16 79 label_frames = np.asarray(label_frames, dtype=int)
mitian@16 80
mitian@16 81 # Get labels from the label frames
mitian@16 82 labels = []
mitian@16 83 bound_inters = zip(bound_idxs[:-1], bound_idxs[1:])
mitian@16 84 for bound_inter in bound_inters:
mitian@16 85 if bound_inter[1] - bound_inter[0] <= 0:
mitian@16 86 labels.append(np.max(label_frames) + 1)
mitian@16 87 else:
mitian@16 88 labels.append(most_frequent(
mitian@16 89 label_frames[bound_inter[0]:bound_inter[1]]))
mitian@16 90
mitian@16 91 return labels
mitian@16 92
mitian@16 93
mitian@16 94 def filter_activation_matrix(G, R):
mitian@16 95 """Filters the activation matrix G, and returns a flattened copy."""
mitian@16 96 idx = np.argmax(G, axis=1)
mitian@16 97 max_idx = np.arange(G.shape[0])
mitian@16 98 max_idx = (max_idx, idx.flatten())
mitian@16 99 G[:, :] = 0
mitian@16 100 G[max_idx] = idx + 1
mitian@16 101 G = np.sum(G, axis=1)
mitian@16 102 G = SegUtil.median_filter(G[:, np.newaxis], R)
mitian@16 103 return G.flatten()
mitian@16 104
mitian@16 105
mitian@16 106 def segmentation(X, rank=4, R=15, h=8, niter=300, CNMF=True):
mitian@16 107 """
mitian@16 108 Gets the segmentation (boundaries and labels) from the factorization
mitian@16 109 matrices.
mitian@16 110
mitian@16 111 Parameters
mitian@16 112 ----------
mitian@16 113 X: np.array()
mitian@16 114 Features matrix (e.g. chromagram)
mitian@16 115 rank: int
mitian@16 116 Rank of decomposition
mitian@16 117 R: int
mitian@16 118 Size of the median filter for activation matrix
mitian@16 119 niter: int
mitian@16 120 Number of iterations for k-means
mitian@16 121 bound_idxs : list
mitian@16 122 Use previously found boundaries (None to detect them)
mitian@16 123 CNMF : bool
mitian@16 124 If True, use CNMF; otherwise use NMF
mitian@16 125
mitian@16 126 Returns
mitian@16 127 -------
mitian@16 128 bounds_idx: np.array
mitian@16 129 Bound indeces found
mitian@16 130 labels: np.array
mitian@16 131 Indeces of the labels representing the similarity between segments.
mitian@16 132 """
mitian@16 133
mitian@16 134 # Filter
mitian@16 135 X = median_filter(X, M=h)
mitian@16 136 X = X.T
mitian@16 137
mitian@16 138 # Find non filtered boundaries
mitian@16 139 bound_idxs = None
mitian@16 140 while True:
mitian@16 141 if bound_idxs is None:
mitian@16 142 try:
mitian@16 143 if CNMF: F, G = cnmf(X, rank, niter=niter)
mitian@16 144 else: F, G = nmf(X, rank, niter=niter)
mitian@16 145 except:
mitian@16 146 return np.empty(0), [1]
mitian@16 147
mitian@16 148 # Filter G
mitian@16 149 G = filter_activation_matrix(G.T, R)
mitian@16 150 if bound_idxs is None:
mitian@16 151 bound_idxs = np.where(np.diff(G) != 0)[0] + 1
mitian@16 152
mitian@16 153 if len(np.unique(bound_idxs)) <= 2:
mitian@16 154 rank += 1
mitian@16 155 bound_idxs = None
mitian@16 156 else:
mitian@16 157 break
mitian@16 158
mitian@16 159 return G, bound_idxs