1 """Tagger to combine a chord labeling model with a chord-input supertagger.
2
3 """
4 """
5 ============================== License ========================================
6 Copyright (C) 2008, 2010-12 University of Edinburgh, Mark Granroth-Wilding
7
8 This file is part of The Jazz Parser.
9
10 The Jazz Parser is free software: you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation, either version 3 of the License, or
13 (at your option) any later version.
14
15 The Jazz Parser is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with The Jazz Parser. If not, see <http://www.gnu.org/licenses/>.
22
23 ============================ End license ======================================
24
25 """
26 __author__ = "Mark Granroth-Wilding <mark.granroth-wilding@ed.ac.uk>"
27
28 from jazzparser.taggers.tagger import Tagger
29 from jazzparser.taggers.ngram_multi.tagger import MultiChordNgramTagger
30 from jazzparser.misc.chordlabel.hmm import HPChordLabeler
31 from jazzparser.misc.chordlabel.midi import midi_to_emission_stream
32 from jazzparser.utils.options import ModuleOption
33 from jazzparser.data.db_mirrors import Chord
34 from jazzparser.data.input import DbInput
35 from jazzparser.utils.strings import str_to_bool
36 from . import tools
37
39 """
40 Tagger that loads a chord labeling model to assign chord labels to MIDI
41 data, then hands over to a chord supertagger to process the output of
42 the labeler.
43
44 """
45 COMPATIBLE_FORMALISMS = ['music_halfspan']
46 TAGGER_OPTIONS = MultiChordNgramTagger.TAGGER_OPTIONS + [
47 ModuleOption('labeling_model',
48 help_text="Model name for chord labeler",
49 usage="labeling_model=M, where M is a trained chord labeling model",
50 required=True),
51 ModuleOption('partition_labeler', filter=str_to_bool,
52 help_text="By default, the chord labeling model is not loaded "\
53 "with a partition number, even if the supertagging model is. "\
54 "If this is True, the same partition number will be used for "\
55 "the labeler's model as was given to the supertagger.",
56 usage="partition_labeler=B, where B is True or False",
57 default=False),
58 ModuleOption('latticen', filter=int,
59 help_text="Number of chords per segment to get in the lattice",
60 usage="latticen=N, where N is an integer",
61 default=3),
62 ModuleOption('lattice_beam', filter=float,
63 help_text="Beam ratio to apply to the chord lattice. Removes all "\
64 "chord labels with probability < ratio * highest probability "\
65 "in timestep. Default: 1e-5",
66 usage="lattice_beam=F, where F is a float < 1.0 (e.g. 1e-6)",
67 default=1e-5),
68 ModuleOption('label_viterbi', filter=str_to_bool,
69 help_text="Use Viterbi decoding instead of forward-backward. "\
70 "Only one chord will be returned for every timestep, no "\
71 "matter what latticen is. Default: False",
72 usage="viterbi=B, where B is True or False",
73 default=False),
74 ]
75 INPUT_TYPES = ['segmidi']
76 name = "chordlabel"
77 shell_tools = Tagger.shell_tools + [
78 tools.ChordLabelTool(),
79 ]
80 LEXICAL_PROBABILITY = True
81
82 - def __init__(self, grammar, input, options={}, logger=None, *args, **kwargs):
83 Tagger.__init__(self, grammar, input, options, logger=logger)
84
85 options = self.options.copy()
86
87 labeling_model_name = options.pop('labeling_model')
88 latticen = options.pop('latticen')
89 beam_ratio = options.pop('lattice_beam')
90 viterbi = options.pop('label_viterbi')
91 partition_labeler = options.pop('partition_labeler')
92
93
94
95 if partition_labeler and 'partition' in self.options and \
96 self.options['partition'] is not None:
97 labeling_model_name += "%d" % self.options['partition']
98
99 self.logger.info("Labeling model: %s" % labeling_model_name)
100
101
102 labeler = HPChordLabeler.load_model(labeling_model_name)
103 self.labeler = labeler
104
105 lattice = labeler.label_lattice(input, options={
106 'n' : latticen,
107 'nokey' : True,
108 'viterbi' : viterbi },
109 corpus=True)
110
111 self.lattice = lattice
112
113 emissions = midi_to_emission_stream(input, remove_empty=False)[0]
114 self.labeler_emission_matrix = labeler.get_small_emission_matrix(emissions)
115
116 lattice.apply_ratio_beam(ratio=beam_ratio)
117
118 self.tagger = MultiChordNgramTagger(grammar, lattice, options,
119 logger=logger, *args, **kwargs)
120 self._lex_prob_cache = [{} for i in range(self.input_length)]
121
124
126 return self.input[index]
127
130
132 """
133 Lexical probabilities for a probabilistic parser. This will only
134 get used if the parsing model can't compute a probability itself
135 (i.e. in the case of MIDI input).
136
137 """
138
139 prob = 1.0
140 for time in range(start_time, end_time):
141 prob *= self.single_step_lexical_probability(time, span_label)
142 return prob
143
145 if span_label not in self._lex_prob_cache[time]:
146
147
148 chord_probs = []
149 for (chord,prob) in self.lattice[time]:
150
151
152 chord_label = self.labeler.chord_types.index(chord.model_label)
153
154 chord_em_prob = self.labeler_emission_matrix[time, chord.root, chord_label]
155
156
157 tagger_label = self.tagger.model.chordmap[chord.label]
158 tagger_em_prob = self.tagger.model.model.emission_probability(
159 (chord.root,tagger_label), span_label)
160
161
162 chord_probs.append(chord_em_prob * tagger_em_prob)
163
164
165 self._lex_prob_cache[time][span_label] = sum(chord_probs, 0.0)
166 return self._lex_prob_cache[time][span_label]
167