{ "metadata": { "name": "", "signature": "sha256:d9eb3269c1983f409941f02e3a32bcda113cabce09e7af16f8bf6df8deab397e" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Here, you build a classifier with Scikit-Learn for use in 6b. The You'll want to read in Oscar's original data as an array of bitwise note vectors, and from there build the RBM to predict chords (the y's, perhaps build the chord bank and assign a unique number to each). After that, given a note vector (maybe plural?), you should be able to predict the chords for a note (notes?).\n", "\n", "This is for Oscar's musical data. The next step is to do the classification for your n-gram model." ] }, { "cell_type": "code", "collapsed": false, "input": [ "from collections import defaultdict\n", "import pandas as pd\n", "import numpy as np\n", "import scipy.sparse\n", "import random, cPickle" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 1 }, { "cell_type": "code", "collapsed": false, "input": [ "# Extract chords into unique ids, e.g. 1, 2, 3, 4, 5\n", "allchords = defaultdict() # remember that it's a hash table\n", "with open(\"oscar2chords_extract.txt\", 'rb') as f:\n", " for ix, line in enumerate(f):\n", " items = line.split()\n", " allchords[ix] = items\n", "assert len(allchords) == len(set(allchords)) # ensure no duplicate chords" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 2 }, { "cell_type": "code", "collapsed": false, "input": [ "# Read in Oscar's data.\n", "vectors = []\n", "notedata = pd.read_csv(open(\"oscar2notes.txt\", 'rb'), skiprows=2)\n", "allnotes = []\n", "for note, octave in zip(notedata[\"Note/Rest\"], notedata[\"Octave\"]):\n", " allnotes.append(\"%s%s\" % (note, octave))\n", "\n", "print \"Number of notes (# of samples for RBM): \", len(notedata)\n", "notedata.head()" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Number of notes (# of samples for RBM): 1344\n" ] }, { "html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Note/RestOctaveLenOffset
0 B 3 0.500000 12.625
1 A 5 0.250000 15.000
2 F 4 3.125000 16.000
3 G 4 0.666667 20.625
4 F 4 1.250000 23.875
\n", "

5 rows \u00d7 4 columns

\n", "
" ], "metadata": {}, "output_type": "pyout", "prompt_number": 3, "text": [ " Note/Rest Octave Len Offset\n", "0 B 3 0.500000 12.625\n", "1 A 5 0.250000 15.000\n", "2 F 4 3.125000 16.000\n", "3 G 4 0.666667 20.625\n", "4 F 4 1.250000 23.875\n", "\n", "[5 rows x 4 columns]" ] } ], "prompt_number": 3 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pull the function from 9. to generate an altered scale for a given note. This is important for updating the bitwise vectors for the BernoulliRBM input. The altered scalee s are hard-coded, which means that they're immutable. Also, they only go from octave 3 through octave six, wrapping around so all possible notes for a given altered scale will be included." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Generates the altered scale from octaves 3 to 6 for a pitch (e.g. G-)\n", "# for a given note (e.g. G-3) in music21 style.\n", "# Returns altered scale as list of music21 notes.\n", "def genAltered(note='C3'):\n", " # In case you have to convert a note (e.g. F#) into form below\n", " def convertSharps(note):\n", " pitch = ''.join([i for i in note if i.isdigit() is False])\n", " enharmonic = {\"C#\" : \"D-\", \"D#\" : \"E-\", \"E#\" : \"F\", \"F#\" : \"G-\", \"G#\" : \"A-\", \"A#\" : \"B-\", \"B#\" : \"C\"}\n", " if '#' in pitch: return enharmonic[pitch]\n", " return pitch\n", " \n", " # Get scale with dictionary. For example: allscales[note[:-1]]\n", " allscales = {\n", " \"C\" : [\"C3\", \"E-3\", \"F3\", \"G3\", \"B-3\",\n", " \"C4\", \"E-4\", \"F4\", \"G4\", \"B-4\",\n", " \"C5\", \"E-5\", \"F5\", \"G5\", \"B-5\",\n", " \"C6\", \"E-6\", \"F6\", \"G6\", \"B-6\"],\n", " \"D-\" : [\"D-3\", \"E3\", \"G-3\", \"A-3\", \"B3\",\n", " \"D-4\", \"E4\", \"G-4\", \"A-4\", \"B4\",\n", " \"D-5\", \"E5\", \"G-5\", \"A-5\", \"B5\",\n", " \"D-6\", \"E6\", \"G-6\", \"A-6\", \"B6\"],\n", " \"D\" : [\"C3\", \"D3\", \"F3\", \"G3\", \"A3\", \n", " \"C4\", \"D4\", \"F4\", \"G4\", \"A4\", \n", " \"C5\", \"D5\", \"F5\", \"G5\", \"A5\", \n", " \"C6\", \"D6\", \"F6\", \"G6\", \"A6\"],\n", " \"E-\" : [\"D-3\", \"E-3\", \"G-3\", \"A-3\", \"B-3\",\n", " \"D-4\", \"E-4\", \"G-4\", \"A-4\", \"B-4\",\n", " \"D-5\", \"E-5\", \"G-5\", \"A-5\", \"B-5\",\n", " \"D-6\", \"E-6\", \"G-6\", \"A-6\", \"B-6\"],\n", " \"E\" : [\"D3\", \"E3\", \"G3\", \"A3\", \"B3\",\n", " \"D4\", \"E4\", \"G4\", \"A4\", \"B4\",\n", " \"D5\", \"E5\", \"G5\", \"A5\", \"B5\",\n", " \"D6\", \"E6\", \"G6\", \"A6\", \"B6\"],\n", " \"F\" : [\"C3\", \"E-3\", \"F3\", \"A-3\", \"B-3\",\n", " \"C4\", \"E-4\", \"F4\", \"A-4\", \"B-4\",\n", " \"C5\", \"E-5\", \"F5\", \"A-5\", \"B-5\",\n", " \"C6\", \"E-6\", \"F6\", \"A-6\", \"B-6\"],\n", " \"G-\" : [\"D-3\", \"E3\", \"G-3\", \"A3\", \"B3\",\n", " \"D-4\", \"E4\", \"G-4\", \"A4\", \"B4\",\n", " \"D-5\", \"E5\", \"G-5\", \"A5\", \"B5\",\n", " \"D-6\", \"E6\", \"G-6\", \"A6\", \"B6\"],\n", " \"G\" : [\"C3\", \"D3\", \"F3\", \"G3\", \"B-3\",\n", " \"C4\", \"D4\", \"F4\", \"G4\", \"B-4\",\n", " \"C5\", \"D5\", \"F5\", \"G5\", \"B-5\",\n", " \"C6\", \"D6\", \"F6\", \"G6\", \"B-6\"],\n", " \"A-\" : [\"D-3\", \"E-3\", \"G-3\", \"A-3\", \"B3\",\n", " \"D-4\", \"E-4\", \"G-4\", \"A-4\", \"B4\",\n", " \"D-5\", \"E-5\", \"G-5\", \"A-5\", \"B5\",\n", " \"D-6\", \"E-6\", \"G-6\", \"A-6\", \"B6\"],\n", " \"A\" : [\"C3\", \"D3\", \"E3\", \"G3\", \"A3\",\n", " \"C4\", \"D4\", \"E4\", \"G4\", \"A4\",\n", " \"C5\", \"D5\", \"E5\", \"G5\", \"A5\",\n", " \"C6\", \"D6\", \"E6\", \"G6\", \"A6\"],\n", " \"B-\" : [\"D-3\", \"E-3\", \"F3\", \"A-3\", \"B-3\",\n", " \"D-4\", \"E-4\", \"F4\", \"A-4\", \"B-4\",\n", " \"D-5\", \"E-5\", \"F5\", \"A-5\", \"B-5\",\n", " \"D-6\", \"E-6\", \"F6\", \"A-6\", \"B-6\"],\n", " \"B\" : [\"D3\", \"E3\", \"G-3\", \"A3\", \"B3\",\n", " \"D4\", \"E4\", \"G-4\", \"A4\", \"B4\",\n", " \"D5\", \"E5\", \"G-5\", \"A5\", \"B5\",\n", " \"D6\", \"E6\", \"G-6\", \"A6\", \"B6\"]}\n", " pitch = ''.join([i for i in note if i.isdigit() is False])\n", " pitch = convertSharps(note) # Rm. octaveinfo, eg. G-5 --> G-, G5->G\n", " return allscales[pitch]" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 23 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, let's do the bitwise arrays! For updating and using the bitwise arrays:\n", "\n", "1. First, you should populate the bitwise array (i.e. \"turn on\" the notes) with the notes that are actually in the given chord, with all octaves. This simply means setting those notes to equal 1 or 0.\n", "2. Second, for a given chord, you want to predict which altered scale it belongs best to. To do this (performance doesn't matter since you'll be cPickling this anyway), generate the altered scales for each of the notes in the chord, and find which notes \"overlap\" between those multiple generated scales the most, and turn those on. For example, you may find for C major you get the C, E, and G altered scales. Find, say, up to 8 notes that appear in >1 of these altered scales (i.e. \"overlapping\" notes), and flip the bits in the vector accordingly. If you're like to create more training data, you can randomize this by picking k random notes from the overlapping notes." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Given a MUSIC21 note, such as C5 or D#7, convert it\n", "# into a note on the keyboard between 0 and 87 inclusive.\n", "# Don't convert it for mingus; try to use music21 note style\n", "# as much as possible for all this stuff.\n", "def quantify(note):\n", " notevals = {\n", " 'C' : 0,\n", " 'D' : 2,\n", " 'E' : 4,\n", " 'F' : 5,\n", " 'G' : 7,\n", " 'A' : 9,\n", " 'B' : 11\n", " }\n", " quantized = 0\n", " octave = int(note[-1]) - 1\n", " for i in note[:-1]:\n", " if i in notevals: quantized += notevals[i]\n", " if i == '-': quantized -= 1\n", " if i == '#': quantized += 1\n", " quantized += 12 * octave\n", " return quantized\n", "\n", "# Create bitwise note vectors for use with Restricted Boltzmann Machine.\n", "vectors = np.zeros((1, 88))\n", "for ix, note in enumerate(allnotes):\n", " vect = np.zeros((1, 88))\n", " vect[0, quantify(note)] = 1\n", " if ix == 0:\n", " vectors = vect\n", " else:\n", " vectors = np.vstack((vectors, vect))\n", "print vectors.shape" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "(1344, 88)\n" ] } ], "prompt_number": 24 }, { "cell_type": "markdown", "metadata": {}, "source": [ "See notes on what you should actually do.\n", "\n", "1. Annotate Oscar's chord data so you have a notes vector for each chord listing the notes that go well with it.\n", "2. Move onto each cluster; for each cluster, build a vector covering all of its notes.\n", "3. You need a training and a test set, so create those from Oscar's data somehow.\n", "4. Find a good classifier to use with this. It might be stacked RBMs, or it might not! Use whatever tool is best for the job.\n", "\n", "Step 1: build the vocabulary of possible notes (e.g. note vectors with # of notes >= 1) for the class labels (each chord's unique id)." ] }, { "cell_type": "code", "collapsed": false, "input": [ "\"\"\" Hard-code altered scales right below for genChordNotes(). \"\"\"\n", "\n", "# Convert mingus note back to music21 note. WORKS\n", "def unmingify(note):\n", " return note.replace('-','').replace('b','-')\n", " \n", "# Given a list of mingus notes (i.e. a chord), say ['A-2', 'A-3', 'E-3'],\n", "# Takes a chord (i.e. a list of notes) and returns a bitwise notevector with possible notes to go along with it.\n", "# Idea: what if just generate notewise vector with exact same pitches? Indepedence assumption?\n", "def genChordNotes(chord):\n", " chord = [unmingify(note) for note in chord] # really important to unmingify notes.\n", " notevect = np.zeros((1, 88))\n", " \n", " # populate with initial pitches\n", " for note in chord:\n", " notevect[0, quantify(note)] = 1\n", " \n", " # add initial pitches transposed to other octaves\n", " otheroctaves = range(3, 6)\n", " for note in chord:\n", " notebase = note[:-1]\n", " for octv in otheroctaves:\n", " put = bool(random.getrandbits(1)) # randomize other pitches\n", " if put is True:\n", " translated = \"%s%s\" % (notebase, octv)\n", " notevect[0, quantify(translated)] = 1\n", " \n", " # Add altered scale that contains most # of notes from chord notes\n", " # e.g. if chord = [e5, g5, b5] then want altered scale with as many of\n", " # those notes as possible. This lets you expand past simply\n", " # the notes already in that chord. Encode the notes of the altered\n", " # scale into the bitwise vector as with the initial pitches.\n", " # Maybe it works better w/o the altered scales; or maybe instead with pentatonics? try that.\n", " # Toggle below to include alternative notes (e.g. pentatonic/altered scales) or not\n", " altfreqs = defaultdict(int)\n", " for note in chord:\n", " for i in genAltered(note):\n", " altfreqs[i] += 1\n", " topnotes = [k for k, v in altfreqs.items() if v > 2] # get notes that overlap > 2 times\n", " for note in topnotes: # flip bits randomly from this list\n", " if bool(random.getrandbits(1)):\n", " notevect[0, quantify(note)] = 1\n", " \n", " # return the vector\n", " return notevect\n", "\n", "# Create initial arrays (1-40, one for each thing)\n", "xdata = np.zeros((1, 88))\n", "for chordID, chord in allchords.items():\n", " if chordID == 0:\n", " xdata = genChordNotes(chord)\n", " else:\n", " xdata = np.vstack((xdata, genChordNotes(chord)))\n", "ydata = allchords.keys()\n", "\n", "print \"Before adding random data: \", xdata.shape, len(ydata)\n", "\n", "# create more randomized data\n", "for chordID, chord in allchords.items():\n", " for j in xrange(50): \n", " xdata = np.vstack((xdata, genChordNotes(chord)))\n", " ydata.append(chordID)\n", "ydata = np.array(ydata).reshape(-1, )\n", "\n", "print \"After adding random data: \", xdata.shape, ydata.shape\n", "# make sure you have the right # of chords. check with # of items in \"oscarchords\" back in (5)." ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Before adding random data: (40, 88) 40\n", "After adding random data: " ] }, { "output_type": "stream", "stream": "stdout", "text": [ " (2040, 88) (2040,)\n" ] } ], "prompt_number": 42 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, it's time for some learning! Create a classifier to get a feel of what the training data is (no test) -- you want to get a deep understanding of what note vectors are associated with which class labels. Remember, the only reason you would need to use train/test sets is to test the effectiveness of the classifier - for the actual prediction in The N-Gram Pipeline, you can fit the classifier to the entire dataset." ] }, { "cell_type": "code", "collapsed": false, "input": [ "from sklearn.svm import SVC\n", "from sklearn.grid_search import GridSearchCV\n", "from sklearn.cross_validation import train_test_split\n", "from sklearn import metrics" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 43 }, { "cell_type": "code", "collapsed": false, "input": [ "# Create train, test sets\n", "xtrain, xtest, ytrain, ytest = train_test_split(xdata, ydata, test_size=0.2, random_state=50)\n", "\n", "# Use gridsearch to build the classifier. Change verbose GridSearchCV param to True if want progress on the processing.\n", "grid_search = GridSearchCV(estimator=SVC(), param_grid={'kernel' : ('linear', 'rbf'), 'C' : np.linspace(0.1, 5.1, 10)}, n_jobs=-2)\n", "\n", "# Train the classifier\n", "grid_search.fit(xtrain, ytrain)\n", "\n", "# Evaluate the classifier's effectiveness.\n", "print \"\\nPredictions for sample of n=10: \"\n", "print \"Real values: \", ytest[:20] # verifies you get the class labels, not the problem earlier (only 1-2 of labels)\n", "print \"Predicted: \", grid_search.predict(xtest[:20])\n", "print metrics.classification_report(ytest, grid_search.predict(xtest))\n", "print \"Best parameters: \", grid_search.best_params_" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\n", "Predictions for sample of n=10: \n", "Real values: [37 13 33 3 0 34 6 14 23 8 28 21 19 22 34 38 9 1 34 37]\n", "Predicted: [37 13 33 3 0 34 13 14 23 8 28 21 19 22 34 38 10 1 34 37]\n", " precision recall f1-score support\n", "\n", " 0 1.00 1.00 1.00 12\n", " 1 0.86 1.00 0.92 6\n", " 2 1.00 1.00 1.00 9\n", " 3 1.00 1.00 1.00 8\n", " 4 1.00 1.00 1.00 17\n", " 5 1.00 1.00 1.00 9\n", " 6 0.50 0.30 0.37 10\n", " 7 1.00 1.00 1.00 7\n", " 8 1.00 1.00 1.00 9\n", " 9 0.43 0.60 0.50 10\n", " 10 0.56 0.38 0.45 13\n", " 11 0.46 1.00 0.63 6\n", " 12 1.00 0.36 0.53 11\n", " 13 0.40 0.57 0.47 7\n", " 14 1.00 1.00 1.00 8\n", " 15 1.00 1.00 1.00 7\n", " 16 1.00 1.00 1.00 13\n", " 17 1.00 1.00 1.00 10\n", " 18 1.00 1.00 1.00 10\n", " 19 1.00 1.00 1.00 6\n", " 20 1.00 1.00 1.00 11\n", " 21 0.64 1.00 0.78 9\n", " 22 1.00 0.62 0.76 13\n", " 23 1.00 1.00 1.00 9\n", " 24 1.00 1.00 1.00 10\n", " 25 1.00 1.00 1.00 6\n", " 26 1.00 1.00 1.00 13\n", " 27 1.00 1.00 1.00 16\n", " 28 1.00 1.00 1.00 11\n", " 29 1.00 1.00 1.00 13\n", " 30 1.00 1.00 1.00 14\n", " 31 1.00 1.00 1.00 13\n", " 32 1.00 1.00 1.00 10\n", " 33 1.00 1.00 1.00 10\n", " 34 1.00 1.00 1.00 19\n", " 35 1.00 1.00 1.00 8\n", " 36 1.00 1.00 1.00 9\n", " 37 1.00 1.00 1.00 10\n", " 38 1.00 1.00 1.00 11\n", " 39 1.00 1.00 1.00 5\n", "\n", "avg / total 0.93 0.92 0.91 408\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", "Best parameters: {'kernel': 'rbf', 'C': 5.0999999999999996}\n" ] } ], "prompt_number": 44 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The final step is to write the classifier to disk, having already trained it on the chord data, so you can use it in the official notebook (6b)." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# save the classifier to disk for use with 6b. The N-Gram Pipeline, Part II.\n", "with open('part7clf.pkl', 'wb') as fid:\n", " cPickle.dump(grid_search, fid)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 45 }, { "cell_type": "code", "collapsed": false, "input": [ "# save the defaultdict (intID : chord) to disk for use with 6b. The N-Gram Pipeline, Part II.\n", "with open('part7cdict.pkl', 'wb') as fid:\n", " cPickle.dump(allchords, fid)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 46 }, { "cell_type": "code", "collapsed": false, "input": [], "language": "python", "metadata": {}, "outputs": [] } ], "metadata": {} } ] }