Reputation: 2790
I have quite huge model created in Keras and I am writing an article in LaTeX about that. To nicely describe the keras model in LaTeX I want to create a LaTeX table out of it. I can achieve it by hand but I was wonderring if there is any 'nicer' way to acomplish this.
I was looking here and there and found some posts like is there a nice output of Keras model.summary( )? where it is solved by plotting to image. I however want to have it as textual data (Yes, a possesion of MRE :) ), the table looks better and formats well. The best option if there was something similar to this: statsmodels summary to latex. I was however unable to find any method of converting model.summary()
output to a tabular representation.
I was thinking if there is a way to convert it somehow to pandas dataframe which could be then processed using df.to_latex()
. I have tried to do it with model.to_json()
, but this function does not return any information about output shape as the model.summary()
prints. Here is my attempt:
df = pd.DataFrame(model.to_json())
df2 = pd.DataFrame(df.loc["layers","config"])
#for example select filters, need to do it like this as it is not always contained
filters = ["-" if "filters" not in x else x["filters"] for x in df2.loc[:,"config"]]
The model.to_json()
returns following json for my model:
{"class_name": "Model", "config": {"name": "Discriminator", "layers": [{"name": "input_3", "class_name": "InputLayer", "config": {"batch_input_shape": [null, 256, 256, 1], "dtype": "float32", "sparse": false, "name": "input_3"}, "inbound_nodes": []}, {"name": "input_4", "class_name": "InputLayer", "config": {"batch_input_shape": [null, 256, 256, 1], "dtype": "float32", "sparse": false, "name": "input_4"}, "inbound_nodes": []}, {"name": "concatenate_2", "class_name": "Concatenate", "config": {"name": "concatenate_2", "trainable": true, "dtype": "float32", "axis": -1}, "inbound_nodes": [[["input_3", 0, 0, {}], ["input_4", 0, 0, {}]]]}, {"name": "conv2d_6", "class_name": "Conv2D", "config": {"name": "conv2d_6", "trainable": true, "dtype": "float32", "filters": 8, "kernel_size": [4, 4], "strides": [2, 2], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "inbound_nodes": [[["concatenate_2", 0, 0, {}]]]}, {"name": "leaky_re_lu_5", "class_name": "LeakyReLU", "config": {"name": "leaky_re_lu_5", "trainable": true, "dtype": "float32", "alpha": 0.20000000298023224}, "inbound_nodes": [[["conv2d_6", 0, 0, {}]]]}, {"name": "conv2d_7", "class_name": "Conv2D", "config": {"name": "conv2d_7", "trainable": true, "dtype": "float32", "filters": 16, "kernel_size": [4, 4], "strides": [2, 2], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "inbound_nodes": [[["leaky_re_lu_5", 0, 0, {}]]]}, {"name": "leaky_re_lu_6", "class_name": "LeakyReLU", "config": {"name": "leaky_re_lu_6", "trainable": true, "dtype": "float32", "alpha": 0.20000000298023224}, "inbound_nodes": [[["conv2d_7", 0, 0, {}]]]}, {"name": "batch_normalization_4", "class_name": "BatchNormalization", "config": {"name": "batch_normalization_4", "trainable": true, "dtype": "float32", "axis": -1, "momentum": 0.8, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "inbound_nodes": [[["leaky_re_lu_6", 0, 0, {}]]]}, {"name": "conv2d_8", "class_name": "Conv2D", "config": {"name": "conv2d_8", "trainable": true, "dtype": "float32", "filters": 32, "kernel_size": [4, 4], "strides": [2, 2], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "inbound_nodes": [[["batch_normalization_4", 0, 0, {}]]]}, {"name": "leaky_re_lu_7", "class_name": "LeakyReLU", "config": {"name": "leaky_re_lu_7", "trainable": true, "dtype": "float32", "alpha": 0.20000000298023224}, "inbound_nodes": [[["conv2d_8", 0, 0, {}]]]}, {"name": "batch_normalization_5", "class_name": "BatchNormalization", "config": {"name": "batch_normalization_5", "trainable": true, "dtype": "float32", "axis": -1, "momentum": 0.8, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "inbound_nodes": [[["leaky_re_lu_7", 0, 0, {}]]]}, {"name": "conv2d_9", "class_name": "Conv2D", "config": {"name": "conv2d_9", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": [4, 4], "strides": [2, 2], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "inbound_nodes": [[["batch_normalization_5", 0, 0, {}]]]}, {"name": "leaky_re_lu_8", "class_name": "LeakyReLU", "config": {"name": "leaky_re_lu_8", "trainable": true, "dtype": "float32", "alpha": 0.20000000298023224}, "inbound_nodes": [[["conv2d_9", 0, 0, {}]]]}, {"name": "batch_normalization_6", "class_name": "BatchNormalization", "config": {"name": "batch_normalization_6", "trainable": true, "dtype": "float32", "axis": -1, "momentum": 0.8, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {}}, "gamma_initializer": {"class_name": "Ones", "config": {}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {}}, "moving_variance_initializer": {"class_name": "Ones", "config": {}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "inbound_nodes": [[["leaky_re_lu_8", 0, 0, {}]]]}, {"name": "conv2d_10", "class_name": "Conv2D", "config": {"name": "conv2d_10", "trainable": true, "dtype": "float32", "filters": 1, "kernel_size": [4, 4], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "inbound_nodes": [[["batch_normalization_6", 0, 0, {}]]]}], "input_layers": [["input_3", 0, 0], ["input_4", 0, 0]], "output_layers": [["conv2d_10", 0, 0]]}, "keras_version": "2.3.1", "backend": "tensorflow"}
While I want model.summary()
-like info:
Model: "Discriminator"
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
input_3 (InputLayer) (None, 256, 256, 1) 0
__________________________________________________________________________________________________
input_4 (InputLayer) (None, 256, 256, 1) 0
...
Maybe there is some good way if I convert summary output to string (Keras model.summary() object to string) and parse the string output?
Upvotes: 3
Views: 4701
Reputation: 2790
As I got more time I got the solution myself. Parsing the model summary was not a greatest idea. I have rather used loop over the model layers and extracted the wanted information saving it to a dataframe, so I can use it later. The code is as follows:
table=pd.DataFrame(columns=["Name","Type","Shape"])
for layer in model.layers:
table = table.append({"Name":layer.name, "Type": layer.__class__.__name__,"Shape":layer.output_shape}, ignore_index=True)
The output of table.head()
:
Name Type Shape
0 input_21 InputLayer [(None, 256, 256, 1)]
1 input_22 InputLayer [(None, 256, 256, 1)]
2 concatenate_32 Concatenate (None, 256, 256, 2)
3 conv2d_96 Conv2D (None, 128, 128, 8)
4 leaky_re_lu_60 LeakyReLU (None, 128, 128, 8)
Upvotes: 1
Reputation: 65
Following nico's response, I touched a little bit of his code and created a github repo
def m2tex(model,modelName):
stringlist = []
model.summary(line_length=70, print_fn=lambda x: stringlist.append(x))
del stringlist[1:-4:2]
del stringlist[-1]
for ix in range(1,len(stringlist)-3):
tmp = stringlist[ix]
stringlist[ix] = tmp[0:31]+"& "+tmp[31:59]+"& "+tmp[59:]+"\\\\ \hline"
stringlist[0] = "Model: {} \\\\ \hline".format(modelName)
stringlist[1] = stringlist[1]+" \hline"
stringlist[-4] += " \hline"
stringlist[-3] += " \\\\"
stringlist[-2] += " \\\\"
stringlist[-1] += " \\\\ \hline"
prefix = ["\\begin{table}[]", "\\begin{tabular}{lll}"]
suffix = ["\end{tabular}", "\caption{{Model summary for {}.}}".format(modelName), "\label{tab:model-summary}" , "\end{table}"]
stringlist = prefix + stringlist + suffix
out_str = " \n".join(stringlist)
out_str = out_str.replace("_", "\_")
out_str = out_str.replace("#", "\#")
print(out_str)
Upvotes: 6
Reputation: 103
I wrote a method to modify the model.summary() output such that it can be copied to latex and look the same as the original (except the spacing between lines is a bit smaller).
def m2tex(model):
stringlist = []
model.summary(line_length=70, print_fn=lambda x: stringlist.append(x))
del stringlist[1:-4:2]
del stringlist[-1]
for ix in range(1,len(stringlist)-3):
tmp = stringlist[ix]
stringlist[ix] = tmp[0:31]+"& "+tmp[31:59]+"& "+tmp[59:]+"\\\\ \hline"
stringlist[0] = "Model: test \\\\ \hline"
stringlist[1] = stringlist[1]+" \hline"
stringlist[-4] = stringlist[-4]+" \hline"
stringlist[-3] = stringlist[-3]+" \\\\"
stringlist[-2] = stringlist[-2]+" \\\\"
stringlist[-1] = stringlist[-1]+" \\\\ \hline"
prefix = ["\\begin{table}[]", "\\begin{tabular}{lll}"]
suffix = ["\end{tabular}", "\caption{Model summary for test.}", "\label{tab:model-summary}" , "\end{table}"]
stringlist = prefix + stringlist + suffix
out_str = " \n".join(stringlist)
out_str = out_str.replace("_", "\_")
out_str = out_str.replace("#", "\#")
print(out_str)
As you can see it is quite ugly, but worked for me. If you have long layer names, you might need to increase the line_length=70 parameter and the indices in line 8 accordingly.
An example output is:
\begin{table}[]
\begin{tabular}{lll}
Model: test \\ \hline
Layer (type) & Output Shape & Param \# \\ \hline \hline
input\_1 (InputLayer) & [(None, 28, 28, 1)] & 0 \\ \hline
dropout (Dropout) & (None, 28, 28, 1) & 0 \\ \hline
conv2d (Conv2D) & (None, 26, 26, 8) & 80 \\ \hline
dropout\_1 (Dropout) & (None, 26, 26, 8) & 0 \\ \hline
conv2d\_1 (Conv2D) & (None, 24, 24, 8) & 584 \\ \hline
dropout\_2 (Dropout) & (None, 24, 24, 8) & 0 \\ \hline
max\_pooling2d (MaxPooling2D) & (None, 12, 12, 8) & 0 \\ \hline
conv2d\_2 (Conv2D) & (None, 10, 10, 10) & 730 \\ \hline
dropout\_3 (Dropout) & (None, 10, 10, 10) & 0 \\ \hline
conv2d\_3 (Conv2D) & (None, 8, 8, 10) & 910 \\ \hline
dropout\_4 (Dropout) & (None, 8, 8, 10) & 0 \\ \hline
max\_pooling2d\_1 (MaxPooling2D) & (None, 4, 4, 10) & 0 \\ \hline
flatten (Flatten) & (None, 160) & 0 \\ \hline
dense (Dense) & (None, 16) & 2576 \\ \hline
dropout\_5 (Dropout) & (None, 16) & 0 \\ \hline
dense\_1 (Dense) & (None, 10) & 170 \\ \hline \hline
Total params: 5,050 \\
Trainable params: 5,050 \\
Non-trainable params: 0 \\ \hline
\end{tabular}
\caption{Model summary for test.}
\label{tab:model-summary}
\end{table}
Upvotes: 8