diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index b1f68e14a..d3483a360 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -38,4 +38,6 @@ pages: - [preprocessing/sequence.md, Preprocessing, Sequence Preprocessing] - [preprocessing/text.md, Preprocessing, Text Preprocessing] -- [preprocessing/image.md, Preprocessing, Image Preprocessing] \ No newline at end of file +- [preprocessing/image.md, Preprocessing, Image Preprocessing] + +- [utils/visualization.md, Utils, Visualization Utilities] diff --git a/docs/sources/models.md b/docs/sources/models.md index 1b78fca91..6dafacfc6 100644 --- a/docs/sources/models.md +++ b/docs/sources/models.md @@ -39,8 +39,8 @@ model = keras.models.Sequential() - __Return__: loss over the data, or tuple `(loss, accuracy)` if `accuracy=True`. - __test__(X, y, accuracy=False): Single performance evaluation on one batch. if accuracy==False, return tuple (loss_on_batch, accuracy_on_batch). Else, return loss_on_batch. - __Return__: loss over the data, or tuple `(loss, accuracy)` if `accuracy=True`. - - __save_weights__(fname): Store the weights of all layers to a HDF5 file. - - __load_weights__(fname): Sets the weights of a model, based to weights stored by __save_weights__. You can only __load_weights__ on a savefile from a model with an identical architecture. __load_weights__ can be called either before or after the __compile__ step. + - __save_weights__(fname, overwrite=False): Store the weights of all layers to a HDF5 file. If overwrite==False and the file already exists, an exception will be thrown. + - __load_weights__(fname): Sets the weights of a model, based to weights stored by __save__weights__. You can only __load__weights__ on a savefile from a model with an identical architecture. __load_weights__ can be called either before or after the __compile__ step. __Examples__: diff --git a/docs/sources/utils/visualization.md b/docs/sources/utils/visualization.md new file mode 100644 index 000000000..1c7b67fa3 --- /dev/null +++ b/docs/sources/utils/visualization.md @@ -0,0 +1,28 @@ +## Grapher + +Creates a visualization of the model structure using `pydot`. + +```python +grapher = keras.utils.dot_utils.Grapher() +``` +- __Methods__: + - __plot__(model, to_file): creates a graph visualizing the structure of `model` and writes it to `to_file`. + - __Arguments__: + - __model__: an instance of a Keras model (e.g. `Sequential`) + - __to_file__: the filename to save the visualization png to. + +__Examples__: + +```python +from keras.models import Sequential +from keras.layers.core import Dense, Activation +from keras.utils.dot_utils import Grapher + +grapher = Grapher() + +model = Sequential() +model.add(Dense(64, 2, init='uniform')) +model.add(Activation('softmax')) +grapher.plot(model, 'model.png') +``` + diff --git a/examples/reuters_mlp.py b/examples/reuters_mlp.py index 8a33fa06d..258101745 100644 --- a/examples/reuters_mlp.py +++ b/examples/reuters_mlp.py @@ -18,7 +18,7 @@ from keras.preprocessing.text import Tokenizer ''' max_words = 10000 -batch_size = 16 +batch_size = 32 print("Loading data...") (X_train, y_train), (X_test, y_test) = reuters.load_data(nb_words=max_words, test_split=0.2) @@ -51,7 +51,8 @@ model.add(Activation('softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam') -model.fit(X_train, Y_train, nb_epoch=4, batch_size=batch_size, verbose=1, show_accuracy=True, validation_split=0.1) +history = model.fit(X_train, Y_train, nb_epoch=3, batch_size=batch_size, verbose=1, show_accuracy=True, validation_split=0.1) +print(history) score = model.evaluate(X_test, Y_test, batch_size=batch_size, verbose=1, show_accuracy=True) print('Test score:', score[0]) print('Test accuracy:', score[1]) diff --git a/keras/models.py b/keras/models.py index 0b24b7a0b..78a54cca6 100644 --- a/keras/models.py +++ b/keras/models.py @@ -382,10 +382,13 @@ class Sequential(Model): self.layers[i].set_weights(weights[:nb_param]) weights = weights[nb_param:] - def save_weights(self, filepath): + def save_weights(self, filepath, overwrite=False): # Save weights from all layers to HDF5 import h5py - # FIXME: fail if file exists, or add option to overwrite! + import os.path + # if file exists and should not be overwritten + if not overwrite and os.path.isfile(filepath): + raise IOError('%s already exists' % (filepath)) f = h5py.File(filepath, 'w') f.attrs['nb_layers'] = len(self.layers) for k, l in enumerate(self.layers): @@ -408,4 +411,4 @@ class Sequential(Model): weights = [g['param_{}'.format(p)] for p in range(g.attrs['nb_params'])] self.layers[k].set_weights(weights) f.close() - \ No newline at end of file + diff --git a/keras/utils/dot_utils.py b/keras/utils/dot_utils.py new file mode 100644 index 000000000..5e6d516ac --- /dev/null +++ b/keras/utils/dot_utils.py @@ -0,0 +1,51 @@ +import pydot +from keras.layers.core import Merge +from keras.models import Model +from collections import Counter + +class Grapher(object): + + def __init__(self): + self.names = {} + self.class_counts = Counter() + + def get_name(self, model): + """ + returns the name of the model instance. If model does not have a `name` attribute, then it will be assigned + a generic (and unique) identifier based on its class + """ + if hasattr(model, 'name'): + return model.name + clz = model.__class__.__name__ + if model not in self.names: + self.class_counts[clz] += 1 + self.names[model] = clz + str(self.class_counts[clz]) + return self.names[model] + + def add_edge(self, f, t, graph): + if f: graph.add_edge(pydot.Edge(f, t)) + return t + + def add_model(self, model, graph, parent=None): + """ + Recursively adds `model` and its components to the pydot graph + """ + this = self.get_name(model) + if isinstance(model, Model): + parent = self.add_edge(parent, this, graph) + for child in reversed(model.layers): + parent = self.add_model(child, graph, parent) + elif isinstance(model, Merge): + for child in model.models: + self.add_model(child, graph, this) + return self.add_edge(parent, this, graph) + else: + return self.add_edge(parent, this, graph) + + def plot(self, model, to_file): + """ + creates a graph visualizing the structure of `model` and writes it to `to_file` + """ + graph = pydot.Dot(graph_type='graph') + self.add_model(model, graph) + graph.write_png(to_file) diff --git a/test/test_dot_utils.py b/test/test_dot_utils.py new file mode 100644 index 000000000..357c89f39 --- /dev/null +++ b/test/test_dot_utils.py @@ -0,0 +1,27 @@ +from keras.utils.dot_utils import Grapher + +from keras.models import Sequential +from keras.layers.core import Dense, Activation, Merge, Flatten +from keras.layers.embeddings import Embedding +from keras.layers.recurrent import GRU + +ent_lookup = Sequential() +ent_lookup.add(Embedding(10, 2)) +ent_lookup.add(Flatten()) + +rel_lookup = Sequential() +rel_lookup.add(Embedding(20, 2)) +rel_lookup.add(Flatten()) + +word_sequence = Sequential() +word_sequence.add(Embedding(10, 5)) +word_sequence.add(GRU(5, 2)) + +model = Sequential() +model.add(Merge([word_sequence, ent_lookup, rel_lookup], mode='concat')) +model.add(Activation('relu')) +model.add(Dense(6, 2)) +model.add(Activation('softmax')) + +g = Grapher() +g.plot(model, 'mymodel.png') \ No newline at end of file