From d99006d630a97b78d3e0b556989b4e8cee06e326 Mon Sep 17 00:00:00 2001 From: Dan Wysocki Date: Thu, 30 Apr 2015 20:36:58 -0400 Subject: [PATCH] Command line interface implemented and polished --- project.clj | 2 +- src/hidden_markov_music/core.clj | 5 +++- src/hidden_markov_music/hmm.clj | 23 ++++++++++++-- src/hidden_markov_music/model/init.clj | 20 ++++++++----- src/hidden_markov_music/model/sample.clj | 8 ++--- src/hidden_markov_music/model/train.clj | 16 +++++----- src/hidden_markov_music/stats.clj | 38 +++++++++++++++++++++++- src/hidden_markov_music/util.clj | 3 +- 8 files changed, 88 insertions(+), 27 deletions(-) diff --git a/project.clj b/project.clj index 6fa4358..5595fc3 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject hidden-markov-music "0.1.4-SNAPSHOT" +(defproject hidden-markov-music "0.1.4" :description "Generate original musical scores by means of a hidden Markov model." :url "https://github.com/dwysocki/hidden-markov-music" diff --git a/src/hidden_markov_music/core.clj b/src/hidden_markov_music/core.clj index 206f706..bbc14b5 100644 --- a/src/hidden_markov_music/core.clj +++ b/src/hidden_markov_music/core.clj @@ -19,7 +19,10 @@ "demo" [demo/main "Various demonstrations"]}) (def description - "hidden-markov-music [] []") + (->> ["hidden-markov-music [] []" + "" + "Perform an operation on a musical hidden Markov model."] + (string/join \newline))) (defn -main [& args] diff --git a/src/hidden_markov_music/hmm.clj b/src/hidden_markov_music/hmm.clj index c0a95f8..1e06c37 100644 --- a/src/hidden_markov_music/hmm.clj +++ b/src/hidden_markov_music/hmm.clj @@ -95,10 +95,10 @@ labels." [states observations] {:type :HMM, - :states states, + :states states, :observations observations, - :initial-prob (stats/random-stochastic-map states), - :transition-prob (stats/random-row-stochastic-map states states), + :initial-prob (stats/random-stochastic-map states), + :transition-prob (stats/random-row-stochastic-map states states), :observation-prob (stats/random-row-stochastic-map states observations)}) (defn random-LogHMM @@ -107,6 +107,23 @@ [states observations] (HMM->LogHMM (random-HMM states observations))) +(defn uniform-HMM + "Returns an HMM with uniform probabilities, given the state and observation + labels." + [states observations] + {:type :HMM, + :states states + :observations observations + :initial-prob (stats/uniform-stochastic-map states), + :transition-prob (stats/uniform-row-stochastic-map states states), + :observation-prob (stats/uniform-row-stochastic-map states observations)}) + +(defn uniform-LogHMM + "Returns a logarithmic HMM with uniform probabilities, given the state and + observation labels." + [states observations] + (HMM->LogHMM (uniform-HMM states observations))) + (defn stream->model "Reads an HMM or LogHMM from an EDN stream, and throws an exception if not a valid model." diff --git a/src/hidden_markov_music/model/init.clj b/src/hidden_markov_music/model/init.clj index babf21b..b9585d8 100644 --- a/src/hidden_markov_music/model/init.clj +++ b/src/hidden_markov_music/model/init.clj @@ -6,17 +6,17 @@ (def usage (util/usage-descriptor - (->> ["Initialize a hidden Markov model with an alphabet read from stdin." - "Alphabet must contain one unique observation symbol per line." - "Mode may either be 'random' or 'uniform', and determines whether" - "the probabilities are initialized randomly or uniformly." - "" - "Usage: hidden-markov-music init [] "] + (->> ["Usage: hidden-markov-music init [] " + "" + "Initialize a hidden Markov model with an alphabet read from stdin." + "Alphabet must contain one unique observation symbol per line." + "Mode may either be 'random' or 'uniform', and determines whether" + "the probabilities are initialized randomly or uniformly."] (string/join \newline)))) (def cli-options [["-s" "--states N" - "Number of hidden states" + "Number of hidden states (required)" :parse-fn util/parse-int :validate [#(< % 0x10000) "Must be an integer between 0 and 65536"]] ["-h" "--help"]]) @@ -33,6 +33,9 @@ (not= 1 (count arguments)) (util/exit 1 (usage summary)) + (nil? (:states options)) + (util/exit 1 (usage summary)) + errors (util/exit 1 (util/error-msg errors))) @@ -47,7 +50,8 @@ states (range (:states options)) mode (first arguments) init-fn (case mode - "random" hmm/random-LogHMM + "random" hmm/random-LogHMM + "uniform" hmm/uniform-LogHMM (util/exit 1 (str mode " is not a valid mode"))) model (init-fn states alphabet)] (if (hmm/valid-hmm? model) diff --git a/src/hidden_markov_music/model/sample.clj b/src/hidden_markov_music/model/sample.clj index b4bb839..5dde199 100644 --- a/src/hidden_markov_music/model/sample.clj +++ b/src/hidden_markov_music/model/sample.clj @@ -7,10 +7,10 @@ (def usage (util/usage-descriptor - (->> ["Takes a model from standard input and writes a sample observation" - "sequence to a file." - "" - "Usage: hidden-markov-music sample [] "] + (->> ["Usage: hidden-markov-music sample [] " + "" + "Reads a model from standard input and generates a song, and saves it" + "to the given file."] (string/join \newline)))) (def cli-options diff --git a/src/hidden_markov_music/model/train.clj b/src/hidden_markov_music/model/train.clj index 1e39234..4c51c03 100644 --- a/src/hidden_markov_music/model/train.clj +++ b/src/hidden_markov_music/model/train.clj @@ -8,14 +8,14 @@ (def usage (util/usage-descriptor - (->> ["Takes a model from standard input and a file containing" - "observations, and writes an updated model to standard output." - "Trains the model using the Baum-Welch algorithm. If the" - "observation file extension is recognized, the file is parsed" - "appropriately, otherwise each line of the file is treated as an" - "observation symbol." - "" - "Usage: hidden-markov-music train [] "] + (->> ["Usage: hidden-markov-music train [] " + "" + "Reads a model from standard input, and the given music file." + "Writes an updated model to standard output, as obtained by the" + "Baum-Welch algorithm." + "Tries to recognize the file format from its extension. If format is" + "unsupported, assumes file is a text file where each line contains" + "one observation symbol."] (string/join \newline)))) (def cli-options diff --git a/src/hidden_markov_music/stats.clj b/src/hidden_markov_music/stats.clj index 9f868ac..85b6d6c 100644 --- a/src/hidden_markov_music/stats.clj +++ b/src/hidden_markov_music/stats.clj @@ -16,12 +16,24 @@ [size] (into [] (normalize (repeatedly size rand)))) +(defn uniform-stochastic-vector + "Returns a uniformly distributed stochastic vector of the given size." + [size] + (into [] (repeat size (/ 1 size)))) + (defn random-stochastic-map - "Returns a hash-map representing a stochastic vector." + "Returns a hash-map representing a randomly distributed stochastic vector." [keys] (zipmap keys (random-stochastic-vector (count keys)))) +(defn uniform-stochastic-map + "Returns a hash-map representing a uniformly distributed stochastic vector." + [keys] + (zipmap keys + (uniform-stochastic-vector (count keys)))) + + (defn random-row-stochastic-map "Returns a nested hash-map representing a row-stochastic matrix. The outer key corresponds to a row, and the nested key corresponds to a @@ -46,6 +58,30 @@ (let [col-probs (random-stochastic-vector n-cols)] (zipmap col-keys col-probs))))) +(defn uniform-row-stochastic-map + "Returns a nested hash-map representing a row-stochastic matrix. + The outer key corresponds to a row, and the nested key corresponds to a + column. + + For example, take the matrix `X` given by: + + ``` + 0 1 2 + +-------+ + 0 | a b c | + 1 | d e f | + 2 | g h i | + +-------+ + ``` + + To obtain the element `d`, one could use `(get-in X [1 0])`, or to obtain the + element `h`, one could use `(get-in X [2 1])`." + [row-keys col-keys] + (let [n-cols (count col-keys)] + (map-for [r row-keys] + (let [col-probs (uniform-stochastic-vector n-cols)] + (zipmap col-keys col-probs))))) + (defn stochastic-map? "Returns true if the map is stochastic to the given precision." [m & {:keys [decimal] :or {decimal 10}}] diff --git a/src/hidden_markov_music/util.clj b/src/hidden_markov_music/util.clj index 3e5c49f..d942a55 100644 --- a/src/hidden_markov_music/util.clj +++ b/src/hidden_markov_music/util.clj @@ -21,7 +21,8 @@ (string/join \newline errors))) (defn exit [status msg] - (println msg) + (binding [*out* *err*] + (println msg)) (System/exit status)) (defn parse-int [x]