1 """Raphael and Stoddard's model for chord labelling from midi data.
2
3 An implementation of the models described in Function Harmonic Analysis Using
4 Probabilistic Models, Raphael and Stoddard, 2004.
5
6 In the future, this should be fitted into the tagger framework. For now it's
7 just a rough prototype, so I'm not bothering to work out how it will implement
8 the tagger interface, etc.
9
10 """
11 """
12 ============================== License ========================================
13 Copyright (C) 2008, 2010-12 University of Edinburgh, Mark Granroth-Wilding
14
15 This file is part of The Jazz Parser.
16
17 The Jazz Parser is free software: you can redistribute it and/or modify
18 it under the terms of the GNU General Public License as published by
19 the Free Software Foundation, either version 3 of the License, or
20 (at your option) any later version.
21
22 The Jazz Parser is distributed in the hope that it will be useful,
23 but WITHOUT ANY WARRANTY; without even the implied warranty of
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 GNU General Public License for more details.
26
27 You should have received a copy of the GNU General Public License
28 along with The Jazz Parser. If not, see <http://www.gnu.org/licenses/>.
29
30 ============================ End license ======================================
31
32 """
33 __author__ = "Mark Granroth-Wilding <mark.granroth-wilding@ed.ac.uk>"
34
35 import numpy, os, re
36 from numpy import ones, float64, sum as array_sum, zeros
37 import cPickle as pickle
38 from datetime import datetime
39
40 from jazzparser.utils.nltk.ngram import NgramModel
41 from jazzparser.utils.nltk.probability import mle_estimator, logprob, add_logs, \
42 sum_logs, prob_dist_to_dictionary_prob_dist, \
43 cond_prob_dist_to_dictionary_cond_prob_dist
44 from jazzparser import settings
45 from . import constants
46 from .midi import MidiHandler
47
48 from nltk.probability import ConditionalProbDist, FreqDist, \
49 ConditionalFreqDist, DictionaryProbDist, \
50 DictionaryConditionalProbDist, MutableProbDist
51
52 FILE_EXTENSION = "mdl"
64
78
102
104 """
105 Takes a state and previous state representation and returns the
106 (state, previous state) pair that is used by the model. This mapping
107 effectively implements the parameter tying of the first part of the
108 transition distribution.
109
110 """
111 tonic, mode, chord = state
112 ptonic, pmode, pchord = previous_state
113 return ( ((tonic-ptonic)%12, mode), pmode )
114
116 """
117 The function described in the paper as d. Maps a pitch class to an
118 abstract representation that depends on the state.
119
120 This gets called a bajillion times, so needs to be as efficient as
121 possible.
122
123 """
124 tonic, mode, chord = state
125
126 key_pitch_class = (pitch_class - tonic) % 12
127
128
129 try:
130 chord_pos = constants.CHORD_NOTES[mode][chord].index(key_pitch_class)
131 except ValueError:
132
133
134 scale = constants.SCALES[mode]
135 if key_pitch_class in scale:
136
137 return 3
138 else:
139
140 return 4
141 else:
142 if chord_pos == 0:
143
144 return 0
145 elif chord_pos == 1:
146
147 return 1
148 else:
149
150 return 2
151
154 """
155 Hidden Markov Model that implements the model described in the paper.
156
157 States are in the form of a tuple (tonic,mode,chord) where tonic is in
158 \{0, ..., 11\}; mode is one of \{L{constants.MODE_MAJOR},
159 L{constants.MODE_MINOR}\}; chord is one of \{L{constants.CHORD_I},
160 ..., L{constants.CHORD_VII}\}.
161
162 Emissions are in the form of a list of pairs (pc,r), where pc is a pitch
163 class (like C{tonic} above) and r is an onset time abstraction and is
164 one of \{0, ...,3\}.
165
166 Unlike with NgramModel, the emission domain is the domain of values
167 from which each element of an emission is selected. In other words,
168 the actually domain of emissions is the powerset of C{emission_dom}.
169
170 In the description of the model, r is described as a condition of the
171 emission distribution. Although the model is truly replicated here, the
172 interface suggests otherwise, since we treat the rythmic markers as if
173 they're part of the emissions. From a conceptual point of view, this makes
174 more sense and I think it's rather odd that the model doesn't treat them
175 this way.
176
177 As for prior distributions (start state distribution), we ignore
178 the tonic of the first state - it doesn't make any sense to look
179 at it since the model is pitch-invariant throughout. We then
180 just use our marginalized chord distribution and assume that the
181 mode distribution is uniform (there are only two and it probably
182 won't make much difference).
183
184 @note: B{mutable distributions}: if you use mutable distributions for
185 transition or emission distributions, make sure you invalidate the cache
186 by calling L{clear_cache} after updating the distributions. Various
187 caches are used to speed up retreival of probabilities. If you fail to
188 do this, you'll end up getting some values unpredictably from the old
189 distributions
190
191 """
192 V = {
193 0 : 1,
194 1 : 1,
195 2 : 1,
196 3 : 4,
197 4 : 5
198 }
199 """ This is the function (mapping) described in the model as V. """
200 LABEL_DOM = None
201
202 - def __init__(self, key_transition_dist, chord_transition_dist, \
203 emission_dist, chord_dist, model_name="default",
204 history="", description="", chord_set="scale+dom7"):
205
206
207
208
209 self.order = 2
210
211 self.chord_set = chord_set
212
213
214 self.key_transition_dom = [(tonic,mode) for tonic in range(12) for mode in constants.MODES]
215 self.chord_transition_dom = constants.CHORD_SETS[chord_set]
216 self.emission_dom = [(pc,rhythm) for pc in range(12) for rhythm in range(4)]
217
218
219 self.emission_dist_dom = range(5)
220 self.beat_dom = range(4)
221
222
223
224 self.label_dom = self.get_label_dom(chord_set=chord_set)
225
226 self.num_key_transitions = len(self.key_transition_dom)
227 self.num_chord_transitions = len(self.chord_transition_dom)
228 self.num_labels = len(self.label_dom)
229 self.num_emissions = len(self.emission_dom)
230
231 self.key_transition_dist = key_transition_dist
232 self.chord_transition_dist = chord_transition_dist
233 self.emission_dist = emission_dist
234 self.chord_dist = chord_dist
235
236
237 self.backoff_model = None
238
239
240 self.model_name = model_name
241
242
243 self.history = history
244
245 self.description = description
246
247
248
249 self.clear_cache()
250
251 - def label(self, handler):
252 """
253 Produces labels for the midi data using the model.
254
255 Input is given in the form of a
256 L{jazzparser.misc.raphsto.midi.MidiHandler} instance.
257
258 Uses Viterbi on the model to get a state sequence and returns a list
259 containing a (state,time) pair for each state change.
260
261 """
262 emissions,times = handler.get_emission_stream()
263
264 states = self.viterbi_decode(emissions)
265
266
267 last_state = None
268 state_changes = []
269 for state,time in zip(states,times):
270 if state != last_state:
271 state_changes.append((state,time))
272 last_state = state
273 return state_changes
274
275 @classmethod
285
286 @staticmethod
290
292 """
293 Initializes or empties probability distribution caches.
294
295 Make sure to call this if you change or update the distributions.
296
297 """
298
299 self._emission_cache = {}
300
301 self._transition_cache = {}
302
303 self._note_emission_cache = {}
304
305 - def add_history(self, string):
306 """
307 Adds a line to the end of this model's history string.
308
309 """
310 self.history += "%s: %s\n" % (datetime.now().isoformat(' '), string)
311
313 if len(seq) > 1:
314 raise RaphstoHmmError, "sequence_to_ngram() got a sequence with "\
315 "more than one value. This shouldn't happen on an HMM"
316 elif len(seq) == 0:
317 return None
318 else:
319 return seq[0]
320
322 if ngram is None:
323 return []
324 else:
325 return [ngram]
326
329
331 raise RaphstoHmmError, "backoff_ngram() should never be called on "\
332 "a RaphstoHmm, since we don't use backoff models"
333
334 @staticmethod
335 - def train(*args, **kwargs):
336 """
337 We don't train a RaphstoHmm using the C{train} method, since our
338 training procedure is not the same as the superclass, so this would
339 be confusing, as this method would require completely different
340 input.
341
342 We train our models by initializing in some way (usually hand-setting
343 of parameters), then using Baum-Welch on unlabelled data.
344
345 This method will just raise a RaphstoHmmError.
346
347 """
348 raise RaphstoHmmError, "don't use train() to train a RaphstoHmm. "\
349 "Instead initialize and then train unsupervisedly"
350
351 @classmethod
353 """
354 Creates a new model with the distributions initialized naively to
355 favour simple chord-types, as R&S do in the paper. They don't say
356 what values they use for C{probs}, except that they're high, medium
357 and low respectively.
358
359 The transition distribution is initialized so that everything is
360 equiprobable.
361
362 @type probs: 3-tuple of floats
363 @param probs: probability mass to assign to (0.) chord notes, (1.)
364 scale notes and (2.) other notes. The three values should sum to
365 1.0 (but will be normalized to if they don't)
366
367 """
368 prob_sum = sum(probs)
369 probs = [p/prob_sum for p in probs]
370
371
372
373 dists = {}
374
375 for r in range(4):
376 probabilities = {}
377 for d in [0,1,2]:
378 probabilities[d] = probs[0]/3.0
379 probabilities[3] = probs[1]
380 probabilities[4] = probs[2]
381 dists[r] = DictionaryProbDist(probabilities)
382 emission_dist = DictionaryConditionalProbDist(dists)
383
384
385 key_transition_counts = ConditionalFreqDist()
386 chord_transition_counts = ConditionalFreqDist()
387 chord_counts = {}
388
389 label_dom = cls.get_label_dom(chord_set=chord_set)
390
391 for label0 in label_dom:
392 for label1 in label_dom:
393 key,pkey = states_to_key_transition(label1, label0)
394
395 key_transition_counts[pkey].inc(key)
396
397 if label0[0] == label1[0] and label0[1] == label1[1]:
398
399 chord_transition_counts[label0[2]].inc(label1[2])
400 else:
401 chord_counts.setdefault(label1[2], 0)
402 chord_counts[label1[2]] += 1
403
404
405 key_dist = ConditionalProbDist(key_transition_counts, mle_estimator, None)
406 chord_trans_dist = ConditionalProbDist(chord_transition_counts, mle_estimator, None)
407 chord_dist = DictionaryProbDist(chord_counts)
408
409 key_dist = cond_prob_dist_to_dictionary_cond_prob_dist(key_dist)
410 chord_trans_dist = cond_prob_dist_to_dictionary_cond_prob_dist(chord_trans_dist)
411 chord_dist = prob_dist_to_dictionary_prob_dist(chord_dist)
412
413 model = cls(key_dist, \
414 chord_trans_dist, \
415 emission_dist, \
416 chord_dist, \
417 model_name=model_name,
418 chord_set=chord_set)
419 model.add_history(\
420 "Initialized model '%s' to chord type probabilities, using "\
421 "parameters: %s, %s, %s" % (model_name, probs[0], probs[1], probs[2]))
422 return model
423
424 @classmethod
426 """
427 Initializes a model using parameters from an already trained model.
428
429 """
430
431 model = cls.load_model(old_model_name)
432
433 model.model_name = model_name
434
435 model.add_history("Parameters from '%s' used as initialization for "\
436 "model '%s'" % (old_model_name,model_name))
437 model.save()
438
439 return model
440
442 """
443 Sets the parameters of the chord transition distribution. This is used
444 in initialization. The parameters are extracted from a string: this is
445 so that it can be specified in a script option.
446
447 The required format of the string is a comma-separated list of
448 parameters given as C0->C1-P, where C0 and C1 are chords (I, II, etc)
449 that are in the model's distribution and P is a float probability.
450 Parameters not specified will be evenly distributed the remaining
451 probability mass.
452
453 """
454 params = {}
455 param_re = re.compile(r'(?P<chord0>.+)->(?P<chord1>.+)-(?P<prob>.+)')
456 chord_ids = dict((name,num) for (num,name) in constants.CHORD_NAMES.items())
457
458 def _chord_id(name):
459
460 if name not in chord_ids:
461 raise RaphstoHmmParameterError, "unrecognised chord name '%s' "\
462 "in parameter spec: %s" % (name,spec)
463 cid = chord_ids[name]
464 if cid not in self.chord_transition_dom:
465 raise RaphstoHmmParameterError, "chord %s is not used with this "\
466 "model (in parameter spec: %s)" % (name,spec)
467 return cid
468
469 for param_str in spec.split(","):
470
471 match = param_re.match(param_str.strip())
472 if not match:
473 raise RaphstoHmmParameterError, "could not parse parameter "\
474 "spec: %s (in: %s)" % (param_str, spec)
475 parts = match.groupdict()
476 chord0 = _chord_id(parts['chord0'])
477 chord1 = _chord_id(parts['chord1'])
478 try:
479 prob = float(parts['prob'])
480 except ValueError:
481 raise RaphstoHmmParameterError, "not a valid probability: %s "\
482 "(in %s)" % (parts['prob'], spec)
483
484 params.setdefault(chord0, {})[chord1] = prob
485
486
487 dists = {}
488 for chord0 in self.chord_transition_dom:
489 dist_params = {}
490 if chord0 not in params:
491
492 uniform_mass = 1.0 / len(self.chord_transition_dom)
493 for chord1 in self.chord_transition_dom:
494 dist_params[chord1] = uniform_mass
495 else:
496
497 not_given = len(self.chord_transition_dom) - len(params[chord0])
498 if not_given > 0:
499 given_mass = sum(params[chord0].values(), 0.0)
500 uniform_mass = (1.0 - given_mass) / not_given
501 else:
502 uniform_mass = 0.0
503
504 for chord1 in self.chord_transition_dom:
505 if chord1 in params[chord0]:
506 dist_params[chord1] = params[chord0][chord1]
507 else:
508 dist_params[chord1] = uniform_mass
509 dists[chord0] = DictionaryProbDist(dist_params)
510
511 self.chord_transition_dist = DictionaryConditionalProbDist(dists)
512
513 self.add_history("Set chord transition distribution using "\
514 "parameters: %s" % spec)
515
517 """
518 Unsupervised training. Passes straight over to the
519 L{train.RaphstoBaumWelchTrainer}.
520
521 @see: jazzparser.misc.raphsto.train.RaphstoBaumWelchTrainer
522
523 """
524 from .train import RaphstoBaumWelchTrainer
525 trainer = RaphstoBaumWelchTrainer(self)
526 trainer.train(*args, **kwargs)
527
529
530
531
532 if state is None:
533 return 0.0
534 cache_key = (state, previous_state)
535 if cache_key not in self._transition_cache:
536 tonic, mode, chord = state
537 if previous_state is None:
538
539
540
541
542
543 chord_transition_prob = self.chord_dist.logprob(chord)
544 self._transition_cache[cache_key] = chord_transition_prob - 24.0
545 else:
546 ptonic, pmode, pchord = previous_state
547
548
549 key,pkey = states_to_key_transition(state, previous_state)
550 key_transition_prob = self.key_transition_dist[pkey].logprob(key)
551
552 if tonic == ptonic and mode == pmode:
553
554 chord_transition_prob = self.chord_transition_dist[pchord].logprob(chord)
555 else:
556
557 chord_transition_prob = self.chord_dist.logprob(chord)
558 self._transition_cache[cache_key] = \
559 key_transition_prob + chord_transition_prob
560 return self._transition_cache[cache_key]
561
563 if len(emission) == 0:
564
565
566
567 return float('-inf')
568 cache_key = (tuple(sorted(emission)), state)
569 if cache_key not in self._emission_cache:
570 prob = 0.0
571
572 for pc,beat in emission:
573
574 d = raphsto_d(pc, state)
575
576 note_cache_key = (beat, d)
577 if note_cache_key not in self._note_emission_cache:
578
579
580
581 self._note_emission_cache[note_cache_key] = \
582 self.emission_dist[beat].logprob(d) - RaphstoHmm.V[d]
583 prob += self._note_emission_cache[note_cache_key]
584 self._emission_cache[cache_key] = prob
585 return self._emission_cache[cache_key]
586
587
589 """We override this to provide a faster implementation.
590
591 It might also be possible to speed up the superclass' implementation
592 using numpy, but it's easier here because we know we're using an
593 HMM, not a higher-order ngram.
594
595 This is based on the fwd prob calculation in NLTK's HMM implementation.
596
597 Returns a numpy 2d array instead of a list of lists.
598
599 """
600 T = len(sequence)
601 N = len(self.label_dom)
602 alpha = numpy.zeros((T, N), numpy.float64)
603
604
605
606 for i,state in enumerate(self.label_dom):
607 alpha[0,i] = self.transition_log_probability(state, None) + \
608 self.emission_log_probability(sequence[0], state)
609
610
611 for t in range(1, T):
612 for j,sj in enumerate(self.label_dom):
613
614
615 log_probs = [
616 alpha[t-1, i] + self.transition_log_probability(sj, si) \
617 for i,si in enumerate(self.label_dom)]
618
619 alpha[t, j] = sum_logs(log_probs) + \
620 self.emission_log_probability(sequence[t], sj)
621
622 if normalize:
623 for t in range(T):
624 total = sum_logs(alpha[t,:])
625 for j in range(N):
626 alpha[t,j] -= total
627 return alpha
628
630 """We override this to provide a faster implementation.
631
632 @see: forward_log_probability
633
634 Returns a numpy 2d array instead of a list of lists.
635
636 """
637 T = len(sequence)
638 N = len(self.label_dom)
639 beta = numpy.zeros((T, N), numpy.float64)
640
641
642 beta[T-1, :] = numpy.log2(1.0/N)
643
644
645 for t in range(T-2, -1, -1):
646 for i,si in enumerate(self.label_dom):
647
648
649
650 log_probs = [
651 beta[t+1, j] + self.transition_log_probability(sj, si) + \
652 self.emission_log_probability(sequence[t+1], sj) \
653 for j,sj in enumerate(self.label_dom)]
654 beta[t, i] = sum_logs(log_probs)
655
656 if normalize:
657 total = sum_logs(beta[t,:])
658 for j in range(N):
659 beta[t,j] -= total
660 return beta
661
662
664 """If you want the normalized matrix of forward probabilities, it's
665 ok to use normal (non-log) probabilities and these can be computed
666 more quickly, since you don't need to sum logs (which is time
667 consuming).
668
669 Returns the matrix, and also the vector of values that each timestep
670 was divided by to normalize (i.e. total probability of each timestep
671 over all states).
672 Also returns the total log probability of the sequence.
673
674 @return: (matrix,normalizing vector,log prob)
675
676 """
677 T = len(sequence)
678 N = len(self.label_dom)
679 alpha = numpy.zeros((T, N), numpy.float64)
680 scale = numpy.zeros(T, numpy.float64)
681
682
683
684 for i,state in enumerate(self.label_dom):
685 alpha[0,i] = self.transition_probability(state, None) * \
686 self.emission_probability(sequence[0], state)
687
688 total = sum(alpha[0,:])
689 for i in range(N):
690 alpha[0,i] /= total
691 scale[0] = total
692
693
694 for t in range(1, T):
695 for j,sj in enumerate(self.label_dom):
696
697
698 log_prob = sum(
699 (alpha[t-1, i] * self.transition_probability(sj, si) \
700 for i,si in enumerate(self.label_dom)), 0.0)
701
702 alpha[t, j] = log_prob * \
703 self.emission_probability(sequence[t], sj)
704
705 total = sum(alpha[t,:])
706 for j in range(N):
707 alpha[t,j] /= total
708 scale[t] = total
709
710
711
712 log_prob = sum((logprob(total) for total in scale), 0.0)
713 return alpha,scale,log_prob
714
716 """
717 @see: normal_forward_probabilities
718
719 (except that this doesn't return the logprob)
720
721 """
722 T = len(sequence)
723 N = len(self.label_dom)
724 beta = numpy.zeros((T, N), numpy.float64)
725 scale = numpy.zeros(T, numpy.float64)
726
727
728 beta[T-1, :] = 1.0/N
729 scale[T-1] = 1.0
730
731
732 for t in range(T-2, -1, -1):
733 for i,si in enumerate(self.label_dom):
734
735
736
737 beta[t, i] = sum(
738 (beta[t+1, j] * self.transition_probability(sj, si) * \
739 self.emission_probability(sequence[t+1], sj) \
740 for j,sj in enumerate(self.label_dom)), 0.0)
741
742 total = sum(beta[t,:])
743 for j in range(N):
744 beta[t,j] /= total
745 scale[t] = total
746 return beta,scale
747
749 """
750 Computes the gamma matrix used in Baum-Welch. This is the matrix
751 of state occupation probabilities for each timestep. It is computed
752 from the forward and backward matrices.
753
754 These can be passed in as
755 arguments to avoid recomputing if you need to reuse them, but will
756 be computed from the model if not given. They are assumed to be
757 the matrices computed by L{normal_forward_probabilities} and
758 L{normal_backward_probabilities} (i.e. normalized, non-log
759 probabilities).
760
761 """
762 if forward is None:
763 forward = self.normal_forward_probabilities(sequence)
764 if backward is None:
765 backward = self.normal_backward_probabilities(sequence)
766
767
768 T,N = forward.shape
769
770 gamma = zeros((T,N), float64)
771 for t in range(T):
772 for i in range(N):
773 gamma[t][i] = forward[t][i]*backward[t][i]
774 denominator = array_sum(gamma[t])
775
776 for i in range(N):
777 gamma[t][i] /= denominator
778 return gamma
779
780 - def compute_xi(self, sequence, forward=None, backward=None):
781 """
782 Computes the xi matrix used by Baum-Welch. It is the matrix of joint
783 probabilities of occupation of pairs of conecutive states:
784 P(i_t, j_{t+1} | O).
785
786 As with L{compute_gamma} forward and backward matrices can optionally
787 be passed in to avoid recomputing.
788
789 """
790 if forward is None:
791 forward = self.normal_forward_probabilities(sequence)
792 if backward is None:
793 backward = self.normal_backward_probabilities(sequence)
794
795
796 T,N = forward.shape
797
798 xi = zeros((T-1,N,N), float64)
799 for t in range(T-1):
800 total = 0.0
801
802 for i,statei in enumerate(self.label_dom):
803
804 for j,statej in enumerate(self.label_dom):
805
806 prob = forward[t][i] * backward[t+1][j] * \
807 self.transition_probability(statej, statei) * \
808 self.emission_probability(sequence[t+1], statej)
809 xi[t][i][j] = prob
810 total += prob
811
812 for i in range(N):
813 for j in range(N):
814 xi[t][i][j] /= total
815 return xi
816
818 """
819 Produces a picklable representation of model as a dict.
820 You can't just pickle the object directly because some of the
821 NLTK classes can't be pickled. You can pickle this dict and
822 reconstruct the model using NgramModel.from_picklable_dict(dict).
823
824 """
825 from jazzparser.utils.nltk.storage import object_to_dict
826 return {
827 'key_transition_dist' : object_to_dict(self.key_transition_dist),
828 'chord_transition_dist' : object_to_dict(self.chord_transition_dist),
829 'emission_dist' : object_to_dict(self.emission_dist),
830 'chord_dist' : object_to_dict(self.chord_dist),
831 'history' : self.history,
832 'description' : self.description,
833 'chord_set' : self.chord_set,
834 }
835
836 @classmethod
838 """
839 Reproduces an n-gram model that was converted to a picklable
840 form using to_picklable_dict.
841
842 """
843 from jazzparser.utils.nltk.storage import dict_to_object
844 return cls(dict_to_object(data['key_transition_dist']),
845 dict_to_object(data['chord_transition_dist']),
846 dict_to_object(data['emission_dist']),
847 dict_to_object(data['chord_dist']),
848 model_name=model_name,
849 history=data.get('history', ''),
850 description=data.get('description', ''),
851 chord_set=data.get('chord_set', 'scale+dom7'))
852
853 @classmethod
856
857 @classmethod
862 _filename = property(_get_my_filename)
863
864 @classmethod
866 """
867 Returns a list of the names of available models.
868
869 """
870 model_dir = cls._get_model_dir()
871 if not os.path.exists(model_dir):
872 return []
873 model_ext = ".%s" % FILE_EXTENSION
874 names = [name.rpartition(model_ext) for name in os.listdir(model_dir)]
875 return [name for name,ext,right in names if ext == model_ext and len(right) == 0]
876
878 """
879 Saves the model data to a file.
880
881 """
882 data = pickle.dumps(self.to_picklable_dict(), 2)
883 filename = self._filename
884
885 filedir = os.path.dirname(filename)
886 if not os.path.exists(filedir):
887 os.mkdir(filedir)
888 f = open(filename, 'w')
889 f.write(data)
890 f.close()
891
893 """
894 Removes all the model's data.
895
896 """
897 fn = self._filename
898 if os.path.exists(fn):
899 os.remove(fn)
900
901 @classmethod
903 filename = cls._get_filename(model_name)
904
905 if os.path.exists(filename):
906 f = open(filename, 'r')
907 model_data = f.read()
908 model_data = pickle.loads(model_data)
909 f.close()
910 else:
911 raise RaphstoModelLoadError, "the model '%s' has not been trained" % model_name
912 return cls.from_picklable_dict(model_data, model_name=model_name)
913
917 """
918 Modification of the Raphsto algorithm that allows it only to assign one
919 of three chords: I, IV and V. They say that secondary dominants are
920 treated using modulation, but in fact their model uses IIs, VIs, etc.
921 This version forces these things to be handled using modulation.
922
923 """
927
928 @classmethod
932
936 """
937 Like L{RaphstoHmmThreeChord}, but also allows a minor secondary dominant
938 chord (II).
939
940 """
944
945 @classmethod
949
951 """
952 Like L{RaphstoHmm}, but always gives all state transitions equal
953 probability, so that it is effectively a unigram model.
954
955 Train with L{jazzparser.misc.raphsto.train.RaphstoBaumWelchUnigramTrainer}.
956
957 """
959 super(RaphstoHmmUnigram, self).__init__(*args, **kwargs)
960
961 self._uniform_transition = 1.0 / len(self.label_dom)
962 self._uniform_transition_log = - logprob(len(self.label_dom))
963
965
966 if state is None:
967 return 0.0
968 else:
969 return self._uniform_transition_log
970
972 if state is None:
973 return 1.0
974 else:
975 return self._uniform_transition
976
977 @staticmethod
981
982 @classmethod
986
989
992
995
998
999 MODEL_TYPES = {
1000 'standard' : RaphstoHmm,
1001 'three-chord' : RaphstoHmmThreeChord,
1002 'four-chord' : RaphstoHmmFourChord,
1003 'unigram' : RaphstoHmmUnigram,
1004 }
1005