impsy 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- impsy/__init__.py +0 -0
- impsy/__main__.py +5 -0
- impsy/dataset.py +72 -0
- impsy/impsy.py +22 -0
- impsy/interaction.py +323 -0
- impsy/mdrnn/__init__.py +267 -0
- impsy/mdrnn/sample_data.py +59 -0
- impsy/mdrnn/test_model.py +17 -0
- impsy/tests.py +36 -0
- impsy/train.py +140 -0
- impsy/utils.py +41 -0
- impsy-0.5.0.dist-info/LICENSE.md +7 -0
- impsy-0.5.0.dist-info/METADATA +142 -0
- impsy-0.5.0.dist-info/RECORD +15 -0
- impsy-0.5.0.dist-info/WHEEL +4 -0
impsy/__init__.py
ADDED
|
File without changes
|
impsy/__main__.py
ADDED
impsy/dataset.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""impsy.dataset: functions for generating a dataset from .log files in the log directory."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import os
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def transform_log_to_sequence_example(logfile, dimension):
|
|
11
|
+
data_names = ['x'+str(i) for i in range(dimension-1)]
|
|
12
|
+
column_names = ['date', 'source'] + data_names
|
|
13
|
+
perf_df = pd.read_csv(logfile,
|
|
14
|
+
header=None, parse_dates=True,
|
|
15
|
+
index_col=0, names=column_names)
|
|
16
|
+
# Filter out RNN lines, just keep 'interface'
|
|
17
|
+
perf_df = perf_df[perf_df.source == 'interface']
|
|
18
|
+
# Process times.
|
|
19
|
+
perf_df['t'] = perf_df.index
|
|
20
|
+
perf_df.t = perf_df.t.diff()
|
|
21
|
+
perf_df.t = perf_df.t.dt.total_seconds()
|
|
22
|
+
perf_df = perf_df.dropna()
|
|
23
|
+
return np.array(perf_df[['t']+data_names])
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@click.command(name="dataset")
|
|
27
|
+
@click.option("-D", "--dimension", type=int, default=2, help="The dimension of the data to model, must be >= 2.")
|
|
28
|
+
@click.option("-S", "--source", type=str, default="logs", help="The source directory to obtain .log files.")
|
|
29
|
+
def dataset(dimension: int, source: str):
|
|
30
|
+
"""Generate a dataset from .log files in the log directory."""
|
|
31
|
+
# Load up the performances
|
|
32
|
+
log_location = f"{source}/"
|
|
33
|
+
log_file_ending = "-" + str(dimension) + "d-mdrnn.log"
|
|
34
|
+
log_arrays = []
|
|
35
|
+
|
|
36
|
+
for local_file in os.listdir(log_location):
|
|
37
|
+
if local_file.endswith(log_file_ending):
|
|
38
|
+
print("Processing:", local_file)
|
|
39
|
+
try:
|
|
40
|
+
log = transform_log_to_sequence_example(log_location + local_file,
|
|
41
|
+
dimension)
|
|
42
|
+
log_arrays.append(log)
|
|
43
|
+
except Exception:
|
|
44
|
+
print("Processing failed for", local_file)
|
|
45
|
+
|
|
46
|
+
# Save Performance Data in a compressed numpy file.
|
|
47
|
+
dataset_location = 'datasets/'
|
|
48
|
+
dataset_filename = 'training-dataset-' + str(dimension) + 'd.npz'
|
|
49
|
+
|
|
50
|
+
# Input format is:
|
|
51
|
+
# 0. 1. 2. ... n.
|
|
52
|
+
# dt x1 x2 ... xn
|
|
53
|
+
|
|
54
|
+
raw_perfs = []
|
|
55
|
+
|
|
56
|
+
acc = 0
|
|
57
|
+
time = 0
|
|
58
|
+
interactions = 0
|
|
59
|
+
for l in log_arrays:
|
|
60
|
+
acc += l.shape[0] * l.shape[1]
|
|
61
|
+
interactions += l.shape[0]
|
|
62
|
+
time += l.T[0].sum()
|
|
63
|
+
raw = l.astype('float32') # dt, x_1, ... , x_n
|
|
64
|
+
raw_perfs.append(raw)
|
|
65
|
+
|
|
66
|
+
print("total number of values:", acc)
|
|
67
|
+
print("total number of interactions:", interactions)
|
|
68
|
+
print("total time represented:", time)
|
|
69
|
+
print("total number of perfs in raw array:", len(raw_perfs))
|
|
70
|
+
raw_perfs = np.array(raw_perfs)
|
|
71
|
+
np.savez_compressed(dataset_location + dataset_filename, perfs=raw_perfs)
|
|
72
|
+
print("done saving:", dataset_location + dataset_filename)
|
impsy/impsy.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""impsy.impsy: provides entry point main() to impsy."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from .dataset import dataset
|
|
6
|
+
from .train import train
|
|
7
|
+
from .interaction import run
|
|
8
|
+
from .tests import test_mdrnn
|
|
9
|
+
|
|
10
|
+
@click.group()
|
|
11
|
+
def cli():
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main():
|
|
16
|
+
"""The entry point function for IMPSY, this just passes through the interfaces for each command"""
|
|
17
|
+
cli.add_command(dataset)
|
|
18
|
+
cli.add_command(train)
|
|
19
|
+
cli.add_command(run)
|
|
20
|
+
cli.add_command(test_mdrnn)
|
|
21
|
+
# runs the command line interface
|
|
22
|
+
cli()
|
impsy/interaction.py
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""impsy.interaction: Functions for using imps as an interactive music system. This server has OSC input and output."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import time
|
|
5
|
+
import datetime
|
|
6
|
+
import numpy as np
|
|
7
|
+
import queue
|
|
8
|
+
import click
|
|
9
|
+
from pythonosc import dispatcher
|
|
10
|
+
from pythonosc import osc_server
|
|
11
|
+
from pythonosc import udp_client
|
|
12
|
+
from threading import Thread
|
|
13
|
+
from .utils import mdrnn_config
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
np.set_printoptions(precision=2)
|
|
17
|
+
|
|
18
|
+
# Interaction Modes:
|
|
19
|
+
|
|
20
|
+
# user, callresponse, filter, battle.
|
|
21
|
+
# user: no AI generation, but user interaction is passed on and logged.
|
|
22
|
+
# callresponse: AI responds after 'threshold' seconds, typical turn-based arrangement.
|
|
23
|
+
# filter: AI responds directly to every input, could be providing second part etc.
|
|
24
|
+
# battle: AI disconnected from human input, both running simultaneously.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Global variables
|
|
28
|
+
# TODO: get rid of these, use a Class instead for data storage.
|
|
29
|
+
call_response_mode = False
|
|
30
|
+
user_to_rnn = False
|
|
31
|
+
rnn_to_rnn = False
|
|
32
|
+
rnn_to_sound = False
|
|
33
|
+
last_user_interaction_time = None
|
|
34
|
+
last_user_interaction_data = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@click.command(name="run")
|
|
38
|
+
@click.option("--log/--no-log", default=True, help="Save input and output data to a log file.")
|
|
39
|
+
@click.option("--verbose/--no-verbose", default=True, help="Verbose mode, print prediction results.")
|
|
40
|
+
# Performance modes
|
|
41
|
+
@click.option("-O", "--mode", type=str, default="callresponse", help="Select interaction mode, one of: user, callresponse, filter, battle. user: no AI generation, callresponse: AI responds after 'threshold' seconds, filter: AI responds directly to every input, battle: AI disconnected from human input")
|
|
42
|
+
@click.option("-T", "--threshold", type=float, default=2.0, help="Seconds to wait before switching to response")
|
|
43
|
+
# MDRNN arguments.
|
|
44
|
+
@click.option("-D", "--dimension", type=int, default=2, help="The dimension of the data to model, must be >= 2.")
|
|
45
|
+
@click.option("-M", "--modelsize", default="s", help="The model size: xs, s, m, l, xl", type=str)
|
|
46
|
+
@click.option("-S", "--sigmatemp", type=float, default=0.01, help="The sigma temperature for sampling.")
|
|
47
|
+
@click.option("-P", "--pitemp", type=float, default=1, help="The pi temperature for sampling.")
|
|
48
|
+
# OSC addresses
|
|
49
|
+
@click.option("--clientip", type=str, default="localhost", help="The address of output device, default is 'localhost'.")
|
|
50
|
+
@click.option("--clientport", type=int, default=5000, help="The port the output device is listening on, default is 5000.")
|
|
51
|
+
@click.option("--serverip", type=str, default="localhost", help="The address of this server, default is 'localhost'.")
|
|
52
|
+
@click.option("--serverport", type=int, default=5001, help="The port this server should listen on, default is 5001.")
|
|
53
|
+
def run(log: bool, verbose: bool, mode: str, threshold: float, dimension: int, modelsize: str, sigmatemp: float, pitemp: float, clientip: str, clientport: int, serverip:str, serverport: int):
|
|
54
|
+
"""Run IMPS predictive musical interaction system as a server with OSC input and output."""
|
|
55
|
+
global call_response_mode
|
|
56
|
+
global user_to_rnn
|
|
57
|
+
global rnn_to_rnn
|
|
58
|
+
global rnn_to_sound
|
|
59
|
+
global last_user_interaction_time
|
|
60
|
+
global last_user_interaction_data
|
|
61
|
+
|
|
62
|
+
# import tensorflow, do this now to make CLI more responsive.
|
|
63
|
+
print("Importing MDRNN.")
|
|
64
|
+
start_import = time.time()
|
|
65
|
+
import impsy.mdrnn as mdrnn
|
|
66
|
+
import tensorflow.compat.v1 as tf
|
|
67
|
+
print("Done. That took", time.time() - start_import, "seconds.")
|
|
68
|
+
|
|
69
|
+
model_config = mdrnn_config(modelsize)
|
|
70
|
+
mdrnn_units = model_config["units"]
|
|
71
|
+
mdrnn_layers = model_config["layers"]
|
|
72
|
+
mdrnn_mixes = model_config["mixes"]
|
|
73
|
+
|
|
74
|
+
# Interaction Loop Parameters
|
|
75
|
+
# All set to false before setting is chosen.
|
|
76
|
+
user_to_rnn = False
|
|
77
|
+
rnn_to_rnn = False
|
|
78
|
+
rnn_to_sound = False
|
|
79
|
+
|
|
80
|
+
# Interactive Mapping
|
|
81
|
+
if mode == "callresponse":
|
|
82
|
+
print("Entering call and response mode.")
|
|
83
|
+
# set initial conditions.
|
|
84
|
+
user_to_rnn = True
|
|
85
|
+
rnn_to_rnn = False
|
|
86
|
+
rnn_to_sound = False
|
|
87
|
+
elif mode == "filter":
|
|
88
|
+
print("Entering filter mode.")
|
|
89
|
+
user_to_rnn = True
|
|
90
|
+
rnn_to_rnn = False
|
|
91
|
+
rnn_to_sound = True
|
|
92
|
+
elif mode == "battle":
|
|
93
|
+
print("Entering battle royale mode.")
|
|
94
|
+
user_to_rnn = False
|
|
95
|
+
rnn_to_rnn = True
|
|
96
|
+
rnn_to_sound = True
|
|
97
|
+
elif mode == "user":
|
|
98
|
+
print("Entering user only mode.")
|
|
99
|
+
user_to_rnn = False
|
|
100
|
+
rnn_to_rnn = False
|
|
101
|
+
rnn_to_sound = False
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def build_network(sess):
|
|
105
|
+
"""Build the MDRNN."""
|
|
106
|
+
mdrnn.MODEL_DIR = "./models/"
|
|
107
|
+
tf.keras.backend.set_session(sess)
|
|
108
|
+
with compute_graph.as_default():
|
|
109
|
+
net = mdrnn.PredictiveMusicMDRNN(mode=mdrnn.NET_MODE_RUN,
|
|
110
|
+
dimension=dimension,
|
|
111
|
+
n_hidden_units=mdrnn_units,
|
|
112
|
+
n_mixtures=mdrnn_mixes,
|
|
113
|
+
layers=mdrnn_layers)
|
|
114
|
+
net.pi_temp = pitemp
|
|
115
|
+
net.sigma_temp = sigmatemp
|
|
116
|
+
print("MDRNN Loaded.")
|
|
117
|
+
return net
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def handle_interface_message(address: str, *osc_arguments) -> None:
|
|
121
|
+
"""Handler for OSC messages from the interface"""
|
|
122
|
+
global last_user_interaction_time
|
|
123
|
+
global last_user_interaction_data
|
|
124
|
+
if verbose:
|
|
125
|
+
# print out OSC message.
|
|
126
|
+
print("User:", ', '.join(["{0:0.2f}".format(abs(i)) for i in osc_arguments]))
|
|
127
|
+
int_input = osc_arguments
|
|
128
|
+
logger = logging.getLogger("impslogger")
|
|
129
|
+
logger.info("{1},interface,{0}".format(','.join(map(str, int_input)),
|
|
130
|
+
datetime.datetime.now().isoformat()))
|
|
131
|
+
dt = time.time() - last_user_interaction_time
|
|
132
|
+
last_user_interaction_time = time.time()
|
|
133
|
+
last_user_interaction_data = np.array([dt, *int_input])
|
|
134
|
+
assert len(last_user_interaction_data) == dimension, "Input is incorrect dimension, set dimension to %r" % len(last_user_interaction_data)
|
|
135
|
+
# These values are accessed by the RNN in the interaction loop function.
|
|
136
|
+
interface_input_queue.put_nowait(last_user_interaction_data)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def handle_temperature_message(address: str, *osc_arguments) -> None:
|
|
140
|
+
"""Handler for temperature messages from the interface: format is ff [sigma temp, pi temp]"""
|
|
141
|
+
new_sigma_temp = osc_arguments[0]
|
|
142
|
+
new_pi_temp = osc_arguments[1]
|
|
143
|
+
if verbose:
|
|
144
|
+
print(f"Temperature -- Sigma: {new_sigma_temp}, Pi: {new_pi_temp}")
|
|
145
|
+
net.sigma_temp = new_sigma_temp
|
|
146
|
+
net.pi_temp = new_pi_temp
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def handle_timescale_message(address: str, *osc_arguments) -> None:
|
|
150
|
+
"""Handler for timescale messages: format is f [timescale]"""
|
|
151
|
+
new_timescale = osc_arguments[0]
|
|
152
|
+
if verbose:
|
|
153
|
+
print(f"Timescale: {new_timescale}")
|
|
154
|
+
# TODO: implement this on the prediction end.
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def request_rnn_prediction(input_value):
|
|
158
|
+
""" Accesses a single prediction from the RNN. """
|
|
159
|
+
output_value = net.generate_touch(input_value)
|
|
160
|
+
return output_value
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def make_prediction(sess, compute_graph):
|
|
164
|
+
"""Interaction loop: reads input, makes predictions, outputs results."""
|
|
165
|
+
# Make predictions.
|
|
166
|
+
|
|
167
|
+
# First deal with user --> MDRNN prediction
|
|
168
|
+
if user_to_rnn and not interface_input_queue.empty():
|
|
169
|
+
item = interface_input_queue.get(block=True, timeout=None)
|
|
170
|
+
tf.keras.backend.set_session(sess)
|
|
171
|
+
with compute_graph.as_default():
|
|
172
|
+
rnn_output = request_rnn_prediction(item)
|
|
173
|
+
# if verbose:
|
|
174
|
+
# print("User->RNN:", ",".join(["{0:0.2f}".format(float(i)) for i in rnn_output]))
|
|
175
|
+
if rnn_to_sound:
|
|
176
|
+
rnn_output_buffer.put_nowait(rnn_output)
|
|
177
|
+
interface_input_queue.task_done()
|
|
178
|
+
|
|
179
|
+
# Now deal with MDRNN --> MDRNN prediction.
|
|
180
|
+
if rnn_to_rnn and rnn_output_buffer.empty() and not rnn_prediction_queue.empty():
|
|
181
|
+
item = rnn_prediction_queue.get(block=True, timeout=None)
|
|
182
|
+
tf.keras.backend.set_session(sess)
|
|
183
|
+
with compute_graph.as_default():
|
|
184
|
+
rnn_output = request_rnn_prediction(item)
|
|
185
|
+
if verbose:
|
|
186
|
+
print("RNN: ", ', '.join(["{0:0.2f}".format(abs(i)) for i in rnn_output[1:]]), 'dt:', "{0:0.2f}".format(abs(rnn_output[0])))
|
|
187
|
+
rnn_output_buffer.put_nowait(rnn_output) # put it in the playback queue.
|
|
188
|
+
rnn_prediction_queue.task_done()
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def send_sound_command(command_args):
|
|
192
|
+
"""Send a sound command back to the interface/synth"""
|
|
193
|
+
assert len(command_args)+1 == dimension, "Dimension not same as prediction size." # Todo more useful error.
|
|
194
|
+
osc_client.send_message(OUTPUT_MESSAGE_ADDRESS, command_args)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def playback_rnn_loop():
|
|
198
|
+
"""Plays back RNN notes from its buffer queue."""
|
|
199
|
+
while True:
|
|
200
|
+
item = rnn_output_buffer.get(block=True, timeout=None) # Blocks until next item is available.
|
|
201
|
+
# print("processing an rnn command", time.time())
|
|
202
|
+
dt = item[0]
|
|
203
|
+
x_pred = np.minimum(np.maximum(item[1:], 0), 1)
|
|
204
|
+
dt = max(dt, 0.001) # stop accidental minus and zero dt.
|
|
205
|
+
time.sleep(dt) # wait until time to play the sound
|
|
206
|
+
# put last played in queue for prediction.
|
|
207
|
+
rnn_prediction_queue.put_nowait(np.concatenate([np.array([dt]), x_pred]))
|
|
208
|
+
if rnn_to_sound:
|
|
209
|
+
send_sound_command(x_pred)
|
|
210
|
+
# print("RNN Played:", x_pred, "at", dt)
|
|
211
|
+
logger = logging.getLogger("impslogger")
|
|
212
|
+
logger.info("{1},rnn,{0}".format(','.join(map(str, x_pred)),
|
|
213
|
+
datetime.datetime.now().isoformat()))
|
|
214
|
+
rnn_output_buffer.task_done()
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def monitor_user_action():
|
|
218
|
+
"""Handles changing action responsibility in Call-Response mode."""
|
|
219
|
+
global call_response_mode
|
|
220
|
+
global user_to_rnn
|
|
221
|
+
global rnn_to_rnn
|
|
222
|
+
global rnn_to_sound
|
|
223
|
+
# Check when the last user interaction was
|
|
224
|
+
dt = time.time() - last_user_interaction_time
|
|
225
|
+
if dt > threshold:
|
|
226
|
+
# switch to response modes.
|
|
227
|
+
user_to_rnn = False
|
|
228
|
+
rnn_to_rnn = True
|
|
229
|
+
rnn_to_sound = True
|
|
230
|
+
if call_response_mode == 'call':
|
|
231
|
+
print("switching to response.")
|
|
232
|
+
call_response_mode = 'response'
|
|
233
|
+
while not rnn_prediction_queue.empty():
|
|
234
|
+
# Make sure there's no inputs waiting to be predicted.
|
|
235
|
+
rnn_prediction_queue.get()
|
|
236
|
+
rnn_prediction_queue.task_done()
|
|
237
|
+
rnn_prediction_queue.put_nowait(last_user_interaction_data) # prime the RNN queue
|
|
238
|
+
else:
|
|
239
|
+
# switch to call mode.
|
|
240
|
+
user_to_rnn = True
|
|
241
|
+
rnn_to_rnn = False
|
|
242
|
+
rnn_to_sound = False
|
|
243
|
+
if call_response_mode == 'response':
|
|
244
|
+
print("switching to call.")
|
|
245
|
+
call_response_mode = 'call'
|
|
246
|
+
# Empty the RNN queues.
|
|
247
|
+
while not rnn_output_buffer.empty():
|
|
248
|
+
# Make sure there's no actions waiting to be synthesised.
|
|
249
|
+
rnn_output_buffer.get()
|
|
250
|
+
rnn_output_buffer.task_done()
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# Logging
|
|
254
|
+
LOG_FILE = datetime.datetime.now().isoformat().replace(":", "-")[:19] + "-" + str(dimension) + "d" + "-mdrnn.log" # Log file name.
|
|
255
|
+
LOG_FILE = "logs/" + LOG_FILE
|
|
256
|
+
LOG_FORMAT = '%(message)s'
|
|
257
|
+
|
|
258
|
+
if log:
|
|
259
|
+
formatter = logging.Formatter(LOG_FORMAT)
|
|
260
|
+
handler = logging.FileHandler(LOG_FILE)
|
|
261
|
+
handler.setFormatter(formatter)
|
|
262
|
+
logger = logging.getLogger("impslogger")
|
|
263
|
+
logger.setLevel(logging.INFO)
|
|
264
|
+
logger.addHandler(handler)
|
|
265
|
+
print("Logging enabled:", LOG_FILE)
|
|
266
|
+
# Details for OSC output
|
|
267
|
+
INPUT_MESSAGE_ADDRESS = "/interface"
|
|
268
|
+
OUTPUT_MESSAGE_ADDRESS = "/prediction"
|
|
269
|
+
TEMPERATURE_MESSAGE_ADDRESS = "/temperature"
|
|
270
|
+
TIMESCALE_MESSAGE_ADDRESS = "/timescale"
|
|
271
|
+
|
|
272
|
+
# Set up runtime variables.
|
|
273
|
+
# ## Load the Model
|
|
274
|
+
compute_graph = tf.Graph()
|
|
275
|
+
with compute_graph.as_default():
|
|
276
|
+
sess = tf.Session()
|
|
277
|
+
net = build_network(sess)
|
|
278
|
+
interface_input_queue = queue.Queue()
|
|
279
|
+
rnn_prediction_queue = queue.Queue()
|
|
280
|
+
rnn_output_buffer = queue.Queue()
|
|
281
|
+
writing_queue = queue.Queue()
|
|
282
|
+
last_user_interaction_time = time.time()
|
|
283
|
+
last_user_interaction_data = mdrnn.random_sample(out_dim=dimension)
|
|
284
|
+
rnn_prediction_queue.put_nowait(mdrnn.random_sample(out_dim=dimension))
|
|
285
|
+
call_response_mode = 'call'
|
|
286
|
+
|
|
287
|
+
# Set up OSC client and server
|
|
288
|
+
osc_client = udp_client.SimpleUDPClient(clientip, clientport)
|
|
289
|
+
disp = dispatcher.Dispatcher()
|
|
290
|
+
disp.map(INPUT_MESSAGE_ADDRESS, handle_interface_message)
|
|
291
|
+
disp.map(TEMPERATURE_MESSAGE_ADDRESS, handle_temperature_message)
|
|
292
|
+
disp.map(TIMESCALE_MESSAGE_ADDRESS, handle_timescale_message)
|
|
293
|
+
server = osc_server.ThreadingOSCUDPServer((serverip, serverport), disp)
|
|
294
|
+
|
|
295
|
+
thread_running = True # TODO: is this line needed?
|
|
296
|
+
|
|
297
|
+
# Set up run loop.
|
|
298
|
+
print("Preparing MDRNN.")
|
|
299
|
+
tf.keras.backend.set_session(sess)
|
|
300
|
+
with compute_graph.as_default():
|
|
301
|
+
net.load_model() # try loading from default file location.
|
|
302
|
+
print("Preparting MDRNN thread.")
|
|
303
|
+
rnn_thread = Thread(target=playback_rnn_loop, name="rnn_player_thread", daemon=True)
|
|
304
|
+
print("Preparing Server thread.")
|
|
305
|
+
server_thread = Thread(target=server.serve_forever, name="server_thread", daemon=True)
|
|
306
|
+
|
|
307
|
+
try:
|
|
308
|
+
rnn_thread.start()
|
|
309
|
+
server_thread.start()
|
|
310
|
+
print("Prediction server started.")
|
|
311
|
+
print("Serving on {}".format(server.server_address))
|
|
312
|
+
while True:
|
|
313
|
+
make_prediction(sess, compute_graph)
|
|
314
|
+
if mode == "callresponse":
|
|
315
|
+
monitor_user_action()
|
|
316
|
+
except KeyboardInterrupt:
|
|
317
|
+
print("\nCtrl-C received... exiting.")
|
|
318
|
+
thread_running = False
|
|
319
|
+
rnn_thread.join(timeout=0.1)
|
|
320
|
+
server_thread.join(timeout=0.1)
|
|
321
|
+
pass
|
|
322
|
+
finally:
|
|
323
|
+
print("\nDone, shutting down.")
|
impsy/mdrnn/__init__.py
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""
|
|
2
|
+
EMPI MDRNN Model.
|
|
3
|
+
Charles P. Martin, 2018
|
|
4
|
+
University of Oslo, Norway.
|
|
5
|
+
"""
|
|
6
|
+
import numpy as np
|
|
7
|
+
import tensorflow.compat.v1 as tf
|
|
8
|
+
import mdn
|
|
9
|
+
import time
|
|
10
|
+
|
|
11
|
+
tf.logging.set_verbosity(tf.logging.INFO) # set logging.
|
|
12
|
+
NET_MODE_TRAIN = 'train'
|
|
13
|
+
NET_MODE_RUN = 'run'
|
|
14
|
+
MODEL_DIR = "./models/"
|
|
15
|
+
LOG_PATH = "./logs/"
|
|
16
|
+
SCALE_FACTOR = 10 # scales input and output from the model. Should be the same between training and inference.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Functions for slicing up data
|
|
20
|
+
def slice_sequence_examples(sequence, num_steps, step_size=1):
|
|
21
|
+
""" Slices a sequence into examples of length
|
|
22
|
+
num_steps with step size step_size."""
|
|
23
|
+
xs = []
|
|
24
|
+
for i in range((len(sequence) - num_steps) // step_size + 1):
|
|
25
|
+
example = sequence[(i * step_size): (i * step_size) + num_steps]
|
|
26
|
+
xs.append(example)
|
|
27
|
+
return xs
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def seq_to_overlapping_format(examples):
|
|
31
|
+
"""Takes sequences of seq_len+1 and returns overlapping
|
|
32
|
+
sequences of seq_len."""
|
|
33
|
+
xs = []
|
|
34
|
+
ys = []
|
|
35
|
+
for ex in examples:
|
|
36
|
+
xs.append(ex[:-1])
|
|
37
|
+
ys.append(ex[1:])
|
|
38
|
+
return (xs, ys)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def seq_to_singleton_format(examples):
|
|
42
|
+
"""Return the examples in seq to singleton format.
|
|
43
|
+
"""
|
|
44
|
+
xs = []
|
|
45
|
+
ys = []
|
|
46
|
+
for ex in examples:
|
|
47
|
+
xs.append(ex[:-1])
|
|
48
|
+
ys.append(ex[-1])
|
|
49
|
+
return (xs, ys)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def build_model(seq_len=30, hidden_units=256, num_mixtures=5, layers=2,
|
|
53
|
+
out_dim=2, time_dist=True, inference=False, compile_model=True,
|
|
54
|
+
print_summary=True):
|
|
55
|
+
"""Builds a EMPI MDRNN model for training or inference.
|
|
56
|
+
|
|
57
|
+
Keyword Arguments:
|
|
58
|
+
seq_len : sequence length to unroll
|
|
59
|
+
hidden_units : number of LSTM units in each layer
|
|
60
|
+
num_mixtures : number of mixture components (5-10 is good)
|
|
61
|
+
layers : number of layers (2 is good)
|
|
62
|
+
out_dim : number of dimensions for the model = number of degrees of freedom + 1 (time)
|
|
63
|
+
time_dist : time distributed or not (default True)
|
|
64
|
+
inference : inference network or training (default False)
|
|
65
|
+
compile_model : compiles the model (default True)
|
|
66
|
+
print_summary : print summary after creating mdoe (default True)
|
|
67
|
+
"""
|
|
68
|
+
print("Building EMPI Model...")
|
|
69
|
+
# Set up training mode
|
|
70
|
+
stateful = False
|
|
71
|
+
# batch_shape = None
|
|
72
|
+
batch_size = None
|
|
73
|
+
# Set up inference mode.
|
|
74
|
+
if inference:
|
|
75
|
+
stateful = True
|
|
76
|
+
batch_size = 1
|
|
77
|
+
#batch_shape = (1, 1, out_dim)
|
|
78
|
+
inputs = tf.keras.layers.Input(shape=(seq_len, out_dim), name='inputs',
|
|
79
|
+
batch_size=batch_size)
|
|
80
|
+
# batch_shape=batch_shape)
|
|
81
|
+
lstm_in = inputs # starter input for lstm
|
|
82
|
+
for layer_i in range(layers):
|
|
83
|
+
ret_seq = True
|
|
84
|
+
if (layer_i == layers - 1) and not time_dist:
|
|
85
|
+
# return sequences false if last layer, and not time distributed.
|
|
86
|
+
ret_seq = False
|
|
87
|
+
lstm_out = tf.keras.layers.LSTM(hidden_units, name='lstm'+str(layer_i),
|
|
88
|
+
return_sequences=ret_seq,
|
|
89
|
+
stateful=stateful)(lstm_in)
|
|
90
|
+
lstm_in = lstm_out
|
|
91
|
+
|
|
92
|
+
mdn_layer = mdn.MDN(out_dim, num_mixtures, name='mdn_outputs')
|
|
93
|
+
if time_dist:
|
|
94
|
+
mdn_layer = tf.keras.layers.TimeDistributed(mdn_layer, name='td_mdn')
|
|
95
|
+
mdn_out = mdn_layer(lstm_out) # apply mdn
|
|
96
|
+
model = tf.keras.models.Model(inputs=inputs, outputs=mdn_out)
|
|
97
|
+
|
|
98
|
+
if compile_model:
|
|
99
|
+
loss_func = mdn.get_mixture_loss_func(out_dim, num_mixtures)
|
|
100
|
+
optimizer = tf.keras.optimizers.Adam()
|
|
101
|
+
model.compile(loss=loss_func, optimizer=optimizer)
|
|
102
|
+
|
|
103
|
+
model.summary()
|
|
104
|
+
return model
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def load_inference_model(model_file="", layers=2, units=512, mixtures=5, predict_moving=False):
|
|
108
|
+
"""Returns an IMPS model loaded from a file"""
|
|
109
|
+
# TODO: make this parse the name to get the hyperparameters.
|
|
110
|
+
decoder = decoder = build_model(seq_len=1, hidden_units=units, num_mixtures=mixtures, layers=layers, time_dist=False, inference=True, compile_model=False, print_summary=True, predict_moving=predict_moving)
|
|
111
|
+
decoder.load_weights(model_file)
|
|
112
|
+
return decoder
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def random_sample(out_dim=2):
|
|
116
|
+
""" Generate a random sample in format (dt, x_1, ..., x_n), where dt is positive
|
|
117
|
+
and the x_i are between 0 and 1."""
|
|
118
|
+
output = np.random.rand(out_dim)
|
|
119
|
+
output[0] = (0.01 + (np.random.rand()-0.5)*0.005) # TODO: see if this dt heuristic should change
|
|
120
|
+
return output
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def proc_generated_touch(x_input, out_dim=2):
|
|
124
|
+
""" Processes a generated touch in the format (dt, x)
|
|
125
|
+
such that dt > 0, and 0 <= x <= 1 """
|
|
126
|
+
dt = np.maximum(x_input[0], 0.000454) # TODO: see if the min value of dt shoud change.
|
|
127
|
+
x_output = np.minimum(np.maximum(x_input[1:], 0), 1)
|
|
128
|
+
return np.concatenate([np.array([dt]), x_output])
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def generate_sample(model, n_mixtures, prev_sample, pi_temp=1.0, sigma_temp=0.0, out_dim=2):
|
|
132
|
+
"""Generate one forward prediction from a previous sample in format
|
|
133
|
+
(dt, x_1,...,x_n). Pi and Sigma temperature are adjustable."""
|
|
134
|
+
params = model.predict(prev_sample.reshape(1, 1, out_dim) * SCALE_FACTOR)
|
|
135
|
+
new_sample = mdn.sample_from_output(params[0], out_dim, n_mixtures, temp=pi_temp, sigma_temp=sigma_temp) / SCALE_FACTOR
|
|
136
|
+
new_sample = new_sample.reshape(out_dim,)
|
|
137
|
+
return new_sample
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def generate_performance(model, n_mixtures, first_sample, time_limit=None, steps_limit=1000, pi_temp=1.0, sigma_temp=0.0, out_dim=2):
|
|
141
|
+
"""Generates a performance of (dt, x) pairs, up to a step_limit.
|
|
142
|
+
Time limit is not presently implemented.
|
|
143
|
+
"""
|
|
144
|
+
time = 0
|
|
145
|
+
steps = 0
|
|
146
|
+
prev_sample = first_sample
|
|
147
|
+
print(prev_sample)
|
|
148
|
+
performance = [prev_sample.reshape((out_dim,))]
|
|
149
|
+
while (steps < steps_limit): # and time < time_limit
|
|
150
|
+
params = model.predict(prev_sample.reshape(1, 1, out_dim) * SCALE_FACTOR)
|
|
151
|
+
prev_sample = mdn.sample_from_output(params[0], out_dim, n_mixtures,
|
|
152
|
+
temp=pi_temp,
|
|
153
|
+
sigma_temp=sigma_temp)
|
|
154
|
+
prev_sample = prev_sample / SCALE_FACTOR
|
|
155
|
+
output_touch = prev_sample.reshape(out_dim,)
|
|
156
|
+
output_touch = proc_generated_touch(output_touch)
|
|
157
|
+
performance.append(output_touch.reshape((out_dim,)))
|
|
158
|
+
steps += 1
|
|
159
|
+
time += output_touch[0]
|
|
160
|
+
return np.array(performance)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class PredictiveMusicMDRNN(object):
|
|
164
|
+
"""EMPI MDRNN object for convenience in the run script."""
|
|
165
|
+
|
|
166
|
+
def __init__(self, mode=NET_MODE_TRAIN, dimension=2, n_hidden_units=128, n_mixtures=5, batch_size=100, sequence_length=120, layers=2):
|
|
167
|
+
"""Initialise the MDRNN model. Use mode='run' for evaluation graph and
|
|
168
|
+
mode='train' for training graph."""
|
|
169
|
+
# network parameters
|
|
170
|
+
self.dimension = dimension
|
|
171
|
+
self.mode = mode
|
|
172
|
+
self.n_hidden_units = n_hidden_units
|
|
173
|
+
self.n_rnn_layers = layers
|
|
174
|
+
self.n_mixtures = n_mixtures # number of mixtures
|
|
175
|
+
# Training parameters
|
|
176
|
+
self.batch_size = batch_size
|
|
177
|
+
self.sequence_length = sequence_length
|
|
178
|
+
self.val_split = 0.10
|
|
179
|
+
# Sampling hyperparameters
|
|
180
|
+
self.pi_temp = 1.5
|
|
181
|
+
self.sigma_temp = 0.01
|
|
182
|
+
|
|
183
|
+
if self.mode is NET_MODE_TRAIN:
|
|
184
|
+
self.model = build_model(seq_len=self.sequence_length,
|
|
185
|
+
hidden_units=self.n_hidden_units,
|
|
186
|
+
num_mixtures=self.n_mixtures,
|
|
187
|
+
layers=self.n_rnn_layers,
|
|
188
|
+
out_dim=self.dimension,
|
|
189
|
+
time_dist=True,
|
|
190
|
+
inference=False,
|
|
191
|
+
compile_model=True,
|
|
192
|
+
print_summary=True)
|
|
193
|
+
else:
|
|
194
|
+
self.model = build_model(seq_len=1,
|
|
195
|
+
hidden_units=self.n_hidden_units,
|
|
196
|
+
num_mixtures=self.n_mixtures,
|
|
197
|
+
layers=self.n_rnn_layers,
|
|
198
|
+
out_dim=self.dimension,
|
|
199
|
+
time_dist=False,
|
|
200
|
+
inference=True,
|
|
201
|
+
compile_model=False,
|
|
202
|
+
print_summary=True)
|
|
203
|
+
|
|
204
|
+
self.run_name = self.get_run_name()
|
|
205
|
+
|
|
206
|
+
def model_name(self):
|
|
207
|
+
"""Returns the name of the present model for saving to disk"""
|
|
208
|
+
return "musicMDRNN" + "-dim" + str(self.dimension) + "-layers" + str(self.n_rnn_layers) + "-units" + str(self.n_hidden_units) + "-mixtures" + str(self.n_mixtures) + "-scale" + str(SCALE_FACTOR)
|
|
209
|
+
|
|
210
|
+
def load_model(self, model_file=None):
|
|
211
|
+
if model_file is None:
|
|
212
|
+
model_file = MODEL_DIR + self.model_name() + ".h5"
|
|
213
|
+
try:
|
|
214
|
+
self.model.load_weights(model_file)
|
|
215
|
+
except OSError as err:
|
|
216
|
+
print("OS error: {0}".format(err))
|
|
217
|
+
print("MDRNN could not be loaded from file:", model_file)
|
|
218
|
+
print("MDRNN is untrained.")
|
|
219
|
+
|
|
220
|
+
def get_run_name(self):
|
|
221
|
+
out = self.model_name() + "-"
|
|
222
|
+
out += time.strftime("%Y%m%d-%H%M%S")
|
|
223
|
+
return out
|
|
224
|
+
|
|
225
|
+
def train(self, X, y, num_epochs=10, saving=True):
|
|
226
|
+
"""Train the network for the a number of epochs."""
|
|
227
|
+
# Setup callbacks
|
|
228
|
+
filepath = MODEL_DIR + self.model_name() + "-E{epoch:02d}-VL{val_loss:.2f}.hdf5"
|
|
229
|
+
checkpoint = tf.keras.callbacks.ModelCheckpoint(filepath, monitor='val_loss', verbose=1, save_best_only=True, mode='min')
|
|
230
|
+
terminateOnNaN = tf.keras.callbacks.TerminateOnNaN()
|
|
231
|
+
tboard = tf.keras.callbacks.TensorBoard(log_dir=LOG_PATH+self.run_name, histogram_freq=2, batch_size=32, write_graph=True, update_freq='epoch')
|
|
232
|
+
callbacks = [terminateOnNaN, tboard]
|
|
233
|
+
if saving:
|
|
234
|
+
callbacks.append(checkpoint)
|
|
235
|
+
|
|
236
|
+
# Do the data scaling in here.
|
|
237
|
+
X = np.array(X) * SCALE_FACTOR
|
|
238
|
+
y = np.array(y) * SCALE_FACTOR
|
|
239
|
+
print("Training corpus has shape:")
|
|
240
|
+
print("X:", X.shape)
|
|
241
|
+
print("y:", y.shape)
|
|
242
|
+
|
|
243
|
+
# Train
|
|
244
|
+
history = self.model.fit(X, y, batch_size=self.batch_size,
|
|
245
|
+
epochs=num_epochs,
|
|
246
|
+
validation_split=self.val_split,
|
|
247
|
+
callbacks=callbacks)
|
|
248
|
+
return history
|
|
249
|
+
|
|
250
|
+
def prepare_model_for_running(self):
|
|
251
|
+
"""Reset RNN state."""
|
|
252
|
+
self.model.reset_states() # reset LSTM state.
|
|
253
|
+
|
|
254
|
+
def generate_touch(self, prev_sample):
|
|
255
|
+
# TODO - do something with the session.
|
|
256
|
+
output = generate_sample(self.model, self.n_mixtures, prev_sample,
|
|
257
|
+
pi_temp=self.pi_temp,
|
|
258
|
+
sigma_temp=self.sigma_temp,
|
|
259
|
+
out_dim=self.dimension)
|
|
260
|
+
return output
|
|
261
|
+
|
|
262
|
+
def generate_performance(self, first_sample, number):
|
|
263
|
+
return generate_performance(self.model, self.n_mixtures,
|
|
264
|
+
first_sample, time_limit=None,
|
|
265
|
+
steps_limit=number, pi_temp=self.pi_temp,
|
|
266
|
+
sigma_temp=self.sigma_temp,
|
|
267
|
+
out_dim=self.dimension)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Manages Training Data for the Musical MDN and can generate fake datsets for testing."""
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import random
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def batch_generator(seq_len, batch_size, dim, corpus):
|
|
8
|
+
"""Returns a generator to cut up datasets into
|
|
9
|
+
batches of features and labels."""
|
|
10
|
+
# generator = batch_generator(SEQ_LEN, BATCH_SIZE, 3, corpus)
|
|
11
|
+
batch_X = np.zeros((batch_size, seq_len, dim))
|
|
12
|
+
batch_y = np.zeros((batch_size, dim))
|
|
13
|
+
while True:
|
|
14
|
+
for i in range(batch_size):
|
|
15
|
+
# choose random example
|
|
16
|
+
l = random.choice(corpus)
|
|
17
|
+
last_index = len(l) - seq_len - 1
|
|
18
|
+
start_index = np.random.randint(0, high=last_index)
|
|
19
|
+
batch_X[i] = l[start_index:start_index+seq_len]
|
|
20
|
+
batch_y[i] = l[start_index+1:start_index+seq_len+1] # .reshape(1,dim)
|
|
21
|
+
yield batch_X, batch_y
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def generate_data():
|
|
25
|
+
"""Generating some Slightly fuzzy sine wave data."""
|
|
26
|
+
NSAMPLE = 50000
|
|
27
|
+
print("Generating", str(NSAMPLE), "toy data samples.")
|
|
28
|
+
t_data = np.float32(np.array(range(NSAMPLE)) / 10.0)
|
|
29
|
+
t_interval = t_data[1] - t_data[0]
|
|
30
|
+
t_r_data = np.random.normal(0, t_interval / 20.0, size=NSAMPLE)
|
|
31
|
+
t_data = t_data + t_r_data
|
|
32
|
+
r_data = np.random.normal(size=NSAMPLE)
|
|
33
|
+
x_data = np.sin(t_data) * 1.0 + (r_data * 0.05)
|
|
34
|
+
df = pd.DataFrame({'t': t_data, 'x': x_data})
|
|
35
|
+
df.t = df.t.diff()
|
|
36
|
+
df.t = df.t.fillna(1e-4)
|
|
37
|
+
print(df.describe())
|
|
38
|
+
return np.array(df)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def generate_synthetic_3D_data():
|
|
42
|
+
"""
|
|
43
|
+
Generates some slightly fuzzy sine wave data
|
|
44
|
+
in two dimensions (plus time).
|
|
45
|
+
"""
|
|
46
|
+
NSAMPLE = 50000
|
|
47
|
+
print("Generating", str(NSAMPLE), "toy data samples.")
|
|
48
|
+
t_data = np.float32(np.array(range(NSAMPLE)) / 10.0)
|
|
49
|
+
t_interval = t_data[1] - t_data[0]
|
|
50
|
+
t_r_data = np.random.normal(0, t_interval / 20.0, size=NSAMPLE)
|
|
51
|
+
t_data = t_data + t_r_data
|
|
52
|
+
r_data = np.random.normal(size=NSAMPLE)
|
|
53
|
+
x_data = (np.sin(t_data) + (r_data / 10.0) + 1) / 2.0
|
|
54
|
+
y_data = (np.sin(t_data * 3.0) + (r_data / 10.0) + 1) / 2.0
|
|
55
|
+
df = pd.DataFrame({'a': x_data, 'b': y_data, 't': t_data})
|
|
56
|
+
df.t = df.t.diff()
|
|
57
|
+
df.t = df.t.fillna(1e-4)
|
|
58
|
+
print(df.describe())
|
|
59
|
+
return np.array(df)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from . import model
|
|
2
|
+
from . import sample_data
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def train_epochs(num_epochs=1):
|
|
6
|
+
print("Training Mixture RNN for", num_epochs, "epochs")
|
|
7
|
+
net = model.MixtureRNN(mode=model.NET_MODE_TRAIN, n_hidden_units=128, n_mixtures=10, batch_size=100, sequence_length=120)
|
|
8
|
+
x_t_log = sample_data.generate_data()
|
|
9
|
+
loader = sample_data.SequenceDataLoader(num_steps=121, batch_size=100, corpus=x_t_log)
|
|
10
|
+
losses = net.train(loader, num_epochs, saving=True)
|
|
11
|
+
print("Training Done.")
|
|
12
|
+
print("Mean Losses per Batch:")
|
|
13
|
+
print(losses)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
if __name__ == "__main__":
|
|
17
|
+
train_epochs(30)
|
impsy/tests.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import time
|
|
3
|
+
from .utils import mdrnn_config
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.command(name="test-mdrnn")
|
|
7
|
+
def test_mdrnn():
|
|
8
|
+
"""This command simply loads the MDRNN to test that it works and how long it takes."""
|
|
9
|
+
# import tensorflow, do this now to make CLI more responsive.
|
|
10
|
+
print("Importing MDRNN.")
|
|
11
|
+
start_import = time.time()
|
|
12
|
+
import impsy.mdrnn as mdrnn
|
|
13
|
+
import tensorflow.compat.v1 as tf
|
|
14
|
+
print("Done. That took", time.time() - start_import, "seconds.")
|
|
15
|
+
|
|
16
|
+
model_config = mdrnn_config("s")
|
|
17
|
+
|
|
18
|
+
def build_network(sess, dimension, units, mixes, layers):
|
|
19
|
+
"""Build the MDRNN."""
|
|
20
|
+
mdrnn.MODEL_DIR = "./models/"
|
|
21
|
+
tf.keras.backend.set_session(sess)
|
|
22
|
+
with compute_graph.as_default():
|
|
23
|
+
net = mdrnn.PredictiveMusicMDRNN(mode=mdrnn.NET_MODE_RUN,
|
|
24
|
+
dimension=dimension,
|
|
25
|
+
n_hidden_units=units,
|
|
26
|
+
n_mixtures=mixes,
|
|
27
|
+
layers=layers)
|
|
28
|
+
print("MDRNN Loaded.")
|
|
29
|
+
return net
|
|
30
|
+
|
|
31
|
+
start_build = time.time()
|
|
32
|
+
compute_graph = tf.Graph()
|
|
33
|
+
with compute_graph.as_default():
|
|
34
|
+
sess = tf.Session()
|
|
35
|
+
build_network(sess, 4, model_config["units"], model_config["mixes"], model_config["layers"])
|
|
36
|
+
print("Done. That took", time.time() - start_build, "seconds.")
|
impsy/train.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""impsy.train: Functions for training an impsy mdrnn model."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import random
|
|
5
|
+
import numpy as np
|
|
6
|
+
import os
|
|
7
|
+
import datetime
|
|
8
|
+
import click
|
|
9
|
+
from .utils import mdrnn_config
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Input and output to serial are bytes (0-255)
|
|
13
|
+
# Output to Pd is a float (0-1)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Model Hyperparameters
|
|
17
|
+
SEQ_LEN = 50
|
|
18
|
+
SEQ_STEP = 1
|
|
19
|
+
TIME_DIST = True
|
|
20
|
+
# Training Hyperparameters:
|
|
21
|
+
VAL_SPLIT = 0.10
|
|
22
|
+
# Set random seed for reproducibility
|
|
23
|
+
SEED = 2345
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@click.command(name="train")
|
|
27
|
+
@click.option("-D", "--dimension", type=int, default=2, help="The dimension of the data to model, must be >= 2.")
|
|
28
|
+
@click.option("-S", "--source", type=str, default="datasets", help="The source directory to obtain .npz dataset files.")
|
|
29
|
+
@click.option("-M", "--modelsize", default="s", help="The model size: xxs, xs, s, m, l, xl.", type=str)
|
|
30
|
+
@click.option("--earlystopping/--no-earlystopping", default=True, help="Use early stopping.")
|
|
31
|
+
@click.option("-P", "--patience", type=int, default=10, help="The number of epochs patience for early stopping.")
|
|
32
|
+
@click.option("-N", "--numepochs", type=int, default=100, help="The maximum number of epochs.")
|
|
33
|
+
@click.option("-B", "--batchsize", type=int, default=64, help="Batch size for training, default=64.")
|
|
34
|
+
def train(dimension: int, source: str, modelsize: str, earlystopping: bool, patience: int, numepochs: int, batchsize: int):
|
|
35
|
+
"""Trains a predictive music interaction model."""
|
|
36
|
+
|
|
37
|
+
# Hack to get openMP working annoyingly.
|
|
38
|
+
os.environ['KMP_DUPLICATE_LIB_OK']='True' # TODO: is this necessary?
|
|
39
|
+
# Import IMPS MDRNN at this point so CLI is fast.
|
|
40
|
+
import impsy.mdrnn as mdrnn
|
|
41
|
+
from tensorflow.compat.v1 import keras
|
|
42
|
+
import tensorflow.compat.v1 as tf
|
|
43
|
+
# Set up environment.
|
|
44
|
+
# Only for GPU use:
|
|
45
|
+
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
|
|
46
|
+
config = tf.ConfigProto()
|
|
47
|
+
config.gpu_options.allow_growth = True
|
|
48
|
+
sess = tf.Session(config=config)
|
|
49
|
+
tf.keras.backend.set_session(sess)
|
|
50
|
+
|
|
51
|
+
model_config = mdrnn_config(modelsize)
|
|
52
|
+
mdrnn_units = model_config["units"]
|
|
53
|
+
mdrnn_layers = model_config["layers"]
|
|
54
|
+
mdrnn_mixes = model_config["mixes"]
|
|
55
|
+
|
|
56
|
+
print("Model size:", modelsize)
|
|
57
|
+
print("Units:", mdrnn_units)
|
|
58
|
+
print("Layers:", mdrnn_layers)
|
|
59
|
+
print("Mixtures:", mdrnn_mixes)
|
|
60
|
+
|
|
61
|
+
random.seed(SEED)
|
|
62
|
+
np.random.seed(SEED)
|
|
63
|
+
|
|
64
|
+
# Load dataset
|
|
65
|
+
dataset_location = f'{source}/'
|
|
66
|
+
dataset_filename = f'training-dataset-{str(dimension)}d.npz'
|
|
67
|
+
|
|
68
|
+
with np.load(dataset_location + dataset_filename, allow_pickle=True) as loaded:
|
|
69
|
+
perfs = loaded['perfs']
|
|
70
|
+
|
|
71
|
+
print("Loaded perfs:", len(perfs))
|
|
72
|
+
print("Num touches:", np.sum([len(l) for l in perfs]))
|
|
73
|
+
corpus = perfs # might need to do some processing here...processing
|
|
74
|
+
# Restrict corpus to sequences longer than the corpus.
|
|
75
|
+
corpus = [l for l in corpus if len(l) > SEQ_LEN+1]
|
|
76
|
+
print("Corpus Examples:", len(corpus))
|
|
77
|
+
# Prepare training data as X and Y.
|
|
78
|
+
slices = []
|
|
79
|
+
for seq in corpus:
|
|
80
|
+
slices += mdrnn.slice_sequence_examples(seq,
|
|
81
|
+
SEQ_LEN+1,
|
|
82
|
+
step_size=SEQ_STEP)
|
|
83
|
+
X, y = mdrnn.seq_to_overlapping_format(slices)
|
|
84
|
+
X = np.array(X) * mdrnn.SCALE_FACTOR
|
|
85
|
+
y = np.array(y) * mdrnn.SCALE_FACTOR
|
|
86
|
+
|
|
87
|
+
print("Number of training examples:")
|
|
88
|
+
print("X:", X.shape)
|
|
89
|
+
print("y:", y.shape)
|
|
90
|
+
|
|
91
|
+
# Setup Training Model
|
|
92
|
+
model = mdrnn.build_model(seq_len=SEQ_LEN,
|
|
93
|
+
hidden_units=mdrnn_units,
|
|
94
|
+
num_mixtures=mdrnn_mixes,
|
|
95
|
+
layers=mdrnn_layers,
|
|
96
|
+
out_dim=dimension,
|
|
97
|
+
time_dist=TIME_DIST,
|
|
98
|
+
inference=False,
|
|
99
|
+
compile_model=True,
|
|
100
|
+
print_summary=True)
|
|
101
|
+
|
|
102
|
+
model_dir = "models/"
|
|
103
|
+
model_name = "musicMDRNN" + "-dim" + str(dimension) + "-layers" + str(mdrnn_layers) + "-units" + str(mdrnn_units) + "-mixtures" + str(mdrnn_mixes) + "-scale" + str(mdrnn.SCALE_FACTOR)
|
|
104
|
+
date_string = datetime.datetime.today().strftime('%Y%m%d-%H_%M_%S')
|
|
105
|
+
|
|
106
|
+
filepath = model_dir + model_name + "-E{epoch:02d}-VL{val_loss:.2f}.hdf5"
|
|
107
|
+
checkpoint = keras.callbacks.ModelCheckpoint(filepath,
|
|
108
|
+
monitor='val_loss',
|
|
109
|
+
verbose=1,
|
|
110
|
+
save_best_only=True,
|
|
111
|
+
mode='min')
|
|
112
|
+
terminateOnNaN = keras.callbacks.TerminateOnNaN()
|
|
113
|
+
early_stopping = keras.callbacks.EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=patience)
|
|
114
|
+
tboard = keras.callbacks.TensorBoard(log_dir='./logs/' + date_string + model_name,
|
|
115
|
+
histogram_freq=0,
|
|
116
|
+
write_graph=True,
|
|
117
|
+
update_freq='epoch')
|
|
118
|
+
|
|
119
|
+
callbacks = [checkpoint, terminateOnNaN, tboard]
|
|
120
|
+
if earlystopping:
|
|
121
|
+
print("Enabling Early Stopping.")
|
|
122
|
+
callbacks.append(early_stopping)
|
|
123
|
+
# Train
|
|
124
|
+
history = model.fit(X, y, batch_size=batchsize,
|
|
125
|
+
epochs=numepochs,
|
|
126
|
+
validation_split=VAL_SPLIT,
|
|
127
|
+
callbacks=callbacks)
|
|
128
|
+
|
|
129
|
+
# Save final Model
|
|
130
|
+
model.save_weights(model_dir + model_name + ".h5")
|
|
131
|
+
|
|
132
|
+
# ## Converting for tensorflow lite.
|
|
133
|
+
# # Convert the model.
|
|
134
|
+
# converter = tf.lite.TFLiteConverter.from_keras_model(model)
|
|
135
|
+
# tflite_model = converter.convert()
|
|
136
|
+
# tflite_model_name = f`{model_dir}{model_name}-lite.h5`
|
|
137
|
+
# with open(tflite_model_name, 'wb') as f:
|
|
138
|
+
# f.write(tflite_model)
|
|
139
|
+
|
|
140
|
+
print("Training done, bye.")
|
impsy/utils.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
SIZE_TO_PARAMETERS = {
|
|
2
|
+
'xxs': {
|
|
3
|
+
"units": 16,
|
|
4
|
+
"mixes": 5,
|
|
5
|
+
"layers": 2,
|
|
6
|
+
},
|
|
7
|
+
'xs': {
|
|
8
|
+
"units": 32,
|
|
9
|
+
"mixes": 5,
|
|
10
|
+
"layers": 2,
|
|
11
|
+
},
|
|
12
|
+
's': {
|
|
13
|
+
"units": 64,
|
|
14
|
+
"mixes": 5,
|
|
15
|
+
"layers": 2
|
|
16
|
+
},
|
|
17
|
+
'm': {
|
|
18
|
+
"units": 128,
|
|
19
|
+
"mixes": 5,
|
|
20
|
+
"layers": 2
|
|
21
|
+
},
|
|
22
|
+
'l': {
|
|
23
|
+
"units": 256,
|
|
24
|
+
"mixes": 5,
|
|
25
|
+
"layers": 2
|
|
26
|
+
},
|
|
27
|
+
'xl': {
|
|
28
|
+
"units": 512,
|
|
29
|
+
"mixes": 5,
|
|
30
|
+
"layers": 3
|
|
31
|
+
},
|
|
32
|
+
'default': {
|
|
33
|
+
"units": 128,
|
|
34
|
+
"mixes": 5,
|
|
35
|
+
"layers": 2
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
def mdrnn_config(size: str):
|
|
40
|
+
"""Get a config dictionary from a size string as used in the IMPS command line interface."""
|
|
41
|
+
return SIZE_TO_PARAMETERS[size]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2019 Charles P Martin
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: impsy
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: IMPSY is the Interactive Musical Prediction SYstem, a tool for creative interactive intelligent musical instruments using a recurrent mixture density neural network.
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Charles Martin
|
|
7
|
+
Author-email: cpm@charlesmartin.au
|
|
8
|
+
Requires-Python: >=3.11,<3.12
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Requires-Dist: click (>=8.1.7,<9.0.0)
|
|
13
|
+
Requires-Dist: keras-mdn-layer (>=0.3.0,<0.4.0)
|
|
14
|
+
Requires-Dist: pandas (>=2.0.3,<3.0.0)
|
|
15
|
+
Requires-Dist: python-osc (>=1.8.3,<2.0.0)
|
|
16
|
+
Requires-Dist: tensorflow (==2.15.0) ; sys_platform == "linux"
|
|
17
|
+
Requires-Dist: tensorflow-macos (==2.15.0) ; sys_platform == "darwin"
|
|
18
|
+
Requires-Dist: tensorflow-probability (==0.23.0)
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# IMPSY: The Interactive Musical Predictive System
|
|
22
|
+
|
|
23
|
+

|
|
24
|
+
[](https://doi.org/10.5281/zenodo.2580176)
|
|
25
|
+
|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
IMPSY is a system for predicting musical control data in live performance. It uses a mixture density recurrent neural network (MDRNN) to observe control inputs over multiple time steps, predicting the next value of each step, and the time that expects the next value to occur. It provides an input and output interface over OSC and can work with musical interfaces with any number of real valued inputs (we've tried from 1-8). Several interactive paradigms are supported for call-response improvisation, as well as independent operation, and "filtering" of the performer's input. Whenever you use IMPSY, your input data is logged to build up a training corpus and a script is provided to train new versions of your model.
|
|
29
|
+
|
|
30
|
+
Here's a [demonstration video showing how IMPSY can be used with different musical interfaces:](https://www.youtube.com/embed/Kdmhrp2dfHw)
|
|
31
|
+
|
|
32
|
+
<!-- <iframe width="560" height="315" src="https://www.youtube.com/embed/Kdmhrp2dfHw" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> -->
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
IMPSY is written in Python with Keras and TensorFlow Probability, so it should work on any platform where Tensorflow can be installed. Python 3 is required and we use [Poetry](https://python-poetry.org) for managing dependencies. IMPSY currently relies on Python 3.11, TensorFlow 2.15.0, TensorFlow Probability 0.23.0, and keras-mdn-layer 0.3.0. You can see the dependencies in `pyproject.toml`.
|
|
37
|
+
|
|
38
|
+
To install IMPSY, first **ensure that you have a Python 3.11** installation available, then install [Poetry](https://python-poetry.org). The poetry install instructions vary depending on your preferences for a python setup this is likely to work on Linux, macOS or Windows (WSL):
|
|
39
|
+
|
|
40
|
+
curl -sSL https://install.python-poetry.org | python3 -
|
|
41
|
+
|
|
42
|
+
Then you should clone this repository or download it to your computer:
|
|
43
|
+
|
|
44
|
+
git clone https://github.com/cpmpercussion/impsy.git
|
|
45
|
+
cd impsy
|
|
46
|
+
|
|
47
|
+
Then you can install the dependencies using Poetry:
|
|
48
|
+
|
|
49
|
+
poetry install
|
|
50
|
+
|
|
51
|
+
Finally, you can test that IMPSY works:
|
|
52
|
+
|
|
53
|
+
poetry run ./start_impsy.py --help
|
|
54
|
+
|
|
55
|
+
## How to use
|
|
56
|
+
|
|
57
|
+
There are four steps for using IMPSY. First, you'll need to setup your musical interface to send it OSC data and receive predictions the same way. Then you can log data, train the MDRNN, and make predictions using our provided scripts.
|
|
58
|
+
|
|
59
|
+
### 1. Connect music interface and synthesis software
|
|
60
|
+
|
|
61
|
+
You'll need:
|
|
62
|
+
|
|
63
|
+
- A music interface that can output data as OSC.
|
|
64
|
+
- Some synthesiser software that can take OSC as input.
|
|
65
|
+
|
|
66
|
+
These could be the same piece of software or hardware!
|
|
67
|
+
|
|
68
|
+
You need to decide on the number of inputs (or dimension) for your predictive model. This is the number of continuous outputs from your interface plus one (for time). So for an interface with 8 faders, the dimension will be 9.
|
|
69
|
+
|
|
70
|
+
Now you need your music interface to send messages to IMPSY over OSC. The default address for IMPSY is: localhost:5001. The messages to IMPSY should have the OSC address `/interface`, and then a float between 0 and 1 for each continuous output on your interface, e.g.:
|
|
71
|
+
|
|
72
|
+
/interface 0 0.5 0.23 0.87 0.9 0.7 0.45 0.654
|
|
73
|
+
|
|
74
|
+
For an 8-dimensional interface.
|
|
75
|
+
|
|
76
|
+
Your synthesiser software or interface needs to listen for messages from the IMPSY system as well. These have the same format with the OSC address `/prediction`. You can interpret these as interactions predicted to occur right when the message is sent.
|
|
77
|
+
|
|
78
|
+
Here's an example diagram for our 8-controller example, the [xtouch mini controller](https://www.musictribe.com/Categories/Behringer/Computer-Audio/Desktop-Controllers/X-TOUCH-MINI/p/P0B3M).
|
|
79
|
+
|
|
80
|
+

|
|
81
|
+
|
|
82
|
+
In this example we've used Pd to connect the xtouch mini to IMPSY and to synthesis sounds. Our Pd mapping patch takes data from the xtouch and sends `/interface` OSC messages to IMPSY, it also receives `/prediction` OSC message back from IMPSY whenever they occur. Of course, whenever the user performs with the controller, the mapping patch sends commands to the synthesiser patch to make sound. Whenever `/prediction` messages are received, these also trigger changes in the synth patch, and we also send MIDI messages back to the xtouch controller to update its lights so that the performer knows what IMPSY is predicting.
|
|
83
|
+
|
|
84
|
+
So what happens if IMPSY and the performer play at the same time? In this example, it doesn't make sense for both to control the synthesiser at the same time, so we set IMPSY to run in "call and response" mode, so that it only makes predictions when the human has stopped performing. We could also set up our mapping patch to use prediction messages for a different synth and use one of the simultaneous performance modes of IMPS.
|
|
85
|
+
|
|
86
|
+
### 2. Log some training data
|
|
87
|
+
|
|
88
|
+
You use the `run` command to log training data. If your interface has N inputs the dimension is N+1:
|
|
89
|
+
|
|
90
|
+
poetry run ./start_impsy run --dimension (N+1) --log
|
|
91
|
+
|
|
92
|
+
This command creates files in the `logs` directory with data like this:
|
|
93
|
+
|
|
94
|
+
2019-01-17T12:37:38.109979,interface,0.3359375,0.296875,0.5078125
|
|
95
|
+
2019-01-17T12:37:38.137938,interface,0.359375,0.296875,0.53125
|
|
96
|
+
2019-01-17T12:37:38.160842,interface,0.375,0.3046875,0.1953125
|
|
97
|
+
|
|
98
|
+
These CSV files have the format: timestamp, source of message (interface or rnn), x_1, x_2, ..., x_N.
|
|
99
|
+
|
|
100
|
+
You can log training data without using the RNN with the `mode` option to select "user" if you like, or use a partially trained RNN and then collect more data.
|
|
101
|
+
|
|
102
|
+
poetry run ./start_impsy run --dimension (N+1) --log -mode user
|
|
103
|
+
|
|
104
|
+
Every time you use IMPS' "run" command, a new log file is created so that you can build up a significant dataset!
|
|
105
|
+
|
|
106
|
+
### 3. Train an MDRNN
|
|
107
|
+
|
|
108
|
+
There's two steps for training: Generate a dataset file, and train the predictive model.
|
|
109
|
+
|
|
110
|
+
Use the `dataset` command:
|
|
111
|
+
|
|
112
|
+
poetry run ./start_impsy dataset --dimension (N+1)
|
|
113
|
+
|
|
114
|
+
This command collates all logs of dimension N+1 from the logs directory and saves the data in a compressed `.npz` file in the datasets directory. It will also print out some information about your dataset, in particular the total number of individual interactions. To have a useful dataset, it's good to start with more than 10,000 individual interactions but YMMV.
|
|
115
|
+
|
|
116
|
+
To train the model, use the `train` command---this can take a while on a normal computer, so be prepared to let your computer sit and think for a few hours! You'll have to decide what _size_ model to try to train: `xs`, `s`, `m`, `l`, `xl`. The size refers to the number of LSTM units in each layer of your model and roughly corresponds to "learning capacity" at a cost of slower training and predictions.
|
|
117
|
+
It's a good idea to start with an `xs` or `s` model, and the larger models are more relevant for quite large datasets (e.g., >1M individual interactions).
|
|
118
|
+
|
|
119
|
+
poetry run ./start_impsy train --dimension (N+1) --modelsize s
|
|
120
|
+
|
|
121
|
+
It's a good idea to use the `earlystopping` option to stop training after the model stops improving for 10 epochs.
|
|
122
|
+
|
|
123
|
+
### 4. Perform with your predictive model
|
|
124
|
+
|
|
125
|
+
Now that you have a trained model, you can run this command to start making predictions:
|
|
126
|
+
|
|
127
|
+
poetry run ./start_impsy run --dimension (N+1) --modelsize xs --log
|
|
128
|
+
|
|
129
|
+
The `--log` switch logs all of your interactions as well as predictions for later re-training. (The dataset generator filters out RNN records so that you only train on human sourced data).
|
|
130
|
+
|
|
131
|
+
PS: all the IMPSY commands respond to the `--help` switch to show command line options. If there's something not documented or working, it would be great if you add an issue above to let me know.
|
|
132
|
+
|
|
133
|
+
## More about Mixture Density Recurrent Neural Networks
|
|
134
|
+
|
|
135
|
+
IMPSY uses a mixture density recurrent neural network MDRNN to make predictions. This machine learning architecture is set up to predict the next in a sequence of multi-valued elements. The recurrent neural network uses LSTM units to remember information about past inputs and use this to help make decisions. The mixture density model at the end of the network allows continuous multi-valued elements to be sampled from a rich probability distribution.
|
|
136
|
+
|
|
137
|
+
The network is illustrated here---every time IMPSY receives an interaction message from your interface, it is sent to thorugh the LSTM layers to produce the parameters of a Gaussian mixture model. The predicted next interaction is sampled from this probability model.
|
|
138
|
+
|
|
139
|
+

|
|
140
|
+
|
|
141
|
+
The MDRNN is written in Keras and uses the [keras-mdn-layer](https://github.com/cpmpercussion/keras-mdn-layer) package. There's more info and tutorials about MDNs on [that github repo](https://github.com/cpmpercussion/keras-mdn-layer).
|
|
142
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
impsy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
impsy/__main__.py,sha256=zpRvI5LqD_MKQM20J7YFi0RWsoB9YrF6g_vOzPAv-qY,105
|
|
3
|
+
impsy/dataset.py,sha256=_d6cVFPiPfB6XmlnwoUOeXvi2X-WTLr8wP3Hw5EYqSM,2639
|
|
4
|
+
impsy/impsy.py,sha256=iGPym9IjH2uXUC1ehOzvwUIH6PuaWnfEvZDtWCx_j_E,499
|
|
5
|
+
impsy/interaction.py,sha256=W7xyr6SzxxBGPAdYYCzwRfXHn59e52sRLqOb8WQiLZE,14005
|
|
6
|
+
impsy/mdrnn/__init__.py,sha256=qB4Oq_gL75o7RW5OfWOjwl31nBFrd3rvlDCZ6CtBlMg,11147
|
|
7
|
+
impsy/mdrnn/sample_data.py,sha256=dOE0JwhAxaiCCkkaszIx4TvWx9FkpI8UoXRRhML8MMw,2201
|
|
8
|
+
impsy/mdrnn/test_model.py,sha256=ngrxhKeMdijt2MXmPRcFyNAEk31tyJyfzjUDcvMyhPI,587
|
|
9
|
+
impsy/tests.py,sha256=Mkqhrrq5eDvuEleDqNLSh8gu1hsArvzPx2vr335_oCs,1328
|
|
10
|
+
impsy/train.py,sha256=UPqzt2d8aXZ7RwJG18tGrIbwuizs9pWDdMuoXpH4wQw,5717
|
|
11
|
+
impsy/utils.py,sha256=oaPL_WSTd_w2mxYG4CcrRvcbilTugZ6AYOBCIxVXgcg,682
|
|
12
|
+
impsy-0.5.0.dist-info/LICENSE.md,sha256=jnbna6p-Yx2oDGjcXqA6StZU1XFF9KkvUo8QIOYjZe0,1055
|
|
13
|
+
impsy-0.5.0.dist-info/METADATA,sha256=frYTmNpTsObI8Op9c-Ielrx-son9erws6nqKil-whSQ,9995
|
|
14
|
+
impsy-0.5.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
15
|
+
impsy-0.5.0.dist-info/RECORD,,
|