utilities related to model visualization import os import sys from keras apiexport import kerasexport from keras utils import ioutils try pydotng is a fork of pydot that is better maintained import pydotng as pydot except importerror pydotplus is an improved version of pydot try import pydotplus as pydot except importerror fall back on pydot if necessary try import pydot except importerror pydot none def checkpydot returns true if both pydot and graphviz are available if not checkpydot return false try attempt to create an image of a blank graph to check the pydotgraphviz installation pydot dot createpydot dot return true except oserror pydot invocationexception return false def addedgedot src dst if not dot getedgesrc dst edge pydot edgesrc dst edge setpenwidth 2 dot addedgeedge def getlayeractivationnamelayer if hasattrlayer activation name activationname layer activation name elif hasattrlayer activation name activationname layer activation name else activationname strlayer activation return activationname def makelayerlabellayer kwargs classname layer class name showlayernames kwargs popshowlayernames showlayeractivations kwargs popshowlayeractivations showdtype kwargs popshowdtype showshapes kwargs popshowshapes showtrainable kwargs popshowtrainable if kwargs raise valueerrorfinvalid kwargs kwargs table table border0 cellborder1 bgcolorblack cellpadding10 colspan max 1 sumintx for x in showdtype showshapes showtrainable if showlayernames table f trtd colspancolspan bgcolorblack font pointsize16 colorwhite fblayer nameb classname fonttdtr else table f trtd colspancolspan bgcolorblack font pointsize16 colorwhite fbclassnameb fonttdtr if showlayeractivations and hasattrlayer activation and layer activation is not none table f trtd bgcolorwhite colspancolspan font pointsize14 factivation bgetlayeractivationnamelayerb fonttdtr cols if showshapes shape none try shape layer output shape except valueerror pass cols append td bgcolorwhitefont pointsize14 f output shape bshape or b fonttd if showdtype dtype none try dtype layer output dtype except valueerror pass cols append td bgcolorwhitefont pointsize14 f output dtype bdtype or b fonttd if showtrainable and hasattrlayer trainable and layer weights if layer trainable cols append td bgcolorforestgreen font pointsize14 colorwhite btrainablebfonttd else cols append td bgcolorfirebrick font pointsize14 colorwhite bnontrainablebfonttd if cols colspan lencols else colspan 1 if cols table tr joincols tr table table return table def makenodelayer kwargs node pydot nodestridlayer labelmakelayerlabellayer kwargs node setfontname helvetica node setborder 0 node setmargin 0 return node kerasexportkeras utils modeltodot def modeltodot model showshapesfalse showdtypefalse showlayernamestrue rankdirtb expandnestedfalse dpi200 subgraphfalse showlayeractivationsfalse showtrainablefalse kwargs from keras ops function import makenodekey if not model built raise valueerror this model has not yet been built build the model first by calling build or by calling the model on a batch of data from keras models import functional from keras models import sequential from keras layers import wrapper if not checkpydot raise importerror you must install pydot pip install pydot for modeltodot to work if subgraph dot pydot clusterstyledashed graphnamemodel name dot setlabel model name dot setlabeljust l else dot pydot dot dot setrankdir rankdir dot setconcentrate true dot setdpi dpi dot setsplines ortho dot setnodedefaultsshaperecord if kwargs poplayerrange none is not none raise valueerrorargument layerrange is no longer supported if kwargs raise valueerrorfunrecognized keyword arguments kwargs kwargs showlayernames showlayernames showlayeractivations showlayeractivations showdtype showdtype showshapes showshapes showtrainable showtrainable if isinstancemodel sequential sequential todo layers model layers elif not isinstancemodel functional functional we treat subclassed models as a single node node makenodemodel kwargs dot addnodenode return dot else layers model operations create graph nodes subnfirstnode subnlastnode for i layer in enumeratelayers process nested functional models if expandnested and isinstancelayer functional functional submodel modeltodot layer showshapes showdtype showlayernames rankdir expandnested subgraphtrue showlayeractivationsshowlayeractivations showtrainableshowtrainable subn submodel subnnodes submodel getnodes subnfirstnodelayer name subnnodes0 subnlastnodelayer name subnnodes1 dot addsubgraphsubmodel else node makenodelayer kwargs dot addnodenode connect nodes with edges sequential case if isinstancemodel sequential sequential for i in rangelenlayers 1 inboundlayerid stridlayersi layerid stridlayersi 1 addedgedot inboundlayerid layerid return dot functional case for i layer in enumeratelayers layerid stridlayer for i node in enumeratelayer inboundnodes nodekey makenodekeylayer i if nodekey in model nodes for parentnode in node parentnodes inboundlayer parentnode operation inboundlayerid stridinboundlayer if not expandnested assert dot getnodeinboundlayerid assert dot getnodelayerid addedgedot inboundlayerid layerid else if inboundlayer is not functional if not isinstanceinboundlayer functional functional if current layer is not functional if not isinstancelayer functional functional assert dot getnodeinboundlayerid assert dot getnodelayerid addedgedot inboundlayerid layerid if current layer is functional elif isinstancelayer functional functional addedge dot inboundlayerid subnfirstnodelayer name getname if inboundlayer is functional elif isinstanceinboundlayer functional functional name subnlastnode inboundlayer name getname if isinstancelayer functional functional outputname subnfirstnode layer name getname addedgedot name outputname else addedgedot name layerid return dot kerasexportkeras utils plotmodel def plotmodel model tofilemodel png showshapesfalse showdtypefalse showlayernamesfalse rankdirtb expandnestedfalse dpi200 showlayeractivationsfalse showtrainablefalse kwargs if not model built raise valueerror this model has not yet been built build the model first by calling build or by calling the model on a batch of data if not checkpydot message you must install pydot pip install pydot for plotmodel to work if ipython core magics namespace in sys modules we don t raise an exception here in order to avoid crashing notebook tests where graphviz is not available ioutils printmsgmessage return else raise importerrormessage if not checkgraphviz message you must install graphviz see instructions at https graphviz gitlab iodownload for plotmodel to work if ipython core magics namespace in sys modules we don t raise an exception here in order to avoid crashing notebook tests where graphviz is not available ioutils printmsgmessage return else raise importerrormessage if kwargs poplayerrange none is not none raise valueerrorargument layerrange is no longer supported if kwargs raise valueerrorfunrecognized keyword arguments kwargs dot modeltodot model showshapesshowshapes showdtypeshowdtype showlayernamesshowlayernames rankdirrankdir expandnestedexpandnested dpidpi showlayeractivationsshowlayeractivations showtrainableshowtrainable tofile strtofile if dot is none return extension os path splitexttofile if not extension extension png else extension extension1 save image to disk dot writetofile formatextension return the image as a jupyter image object to be displayed inline note that we cannot easily detect whether the code is running in a notebook and thus we always return the image if jupyter is available if extension pdf try from ipython import display return display imagefilenametofile except importerror pass pydot ng is a fork of pydot that is better maintained pydotplus is an improved version of pydot fall back on pydot if necessary returns true if pydot is available returns true if both pydot and graphviz are available attempt to create an image of a blank graph to check the pydot graphviz installation convert a keras model to dot format args model a keras model instance show_shapes whether to display shape information show_dtype whether to display layer dtypes show_layer_names whether to display layer names rankdir rankdir argument passed to pydot a string specifying the format of the plot tb creates a vertical plot lr creates a horizontal plot expand_nested whether to expand nested functional models into clusters dpi image resolution in dots per inch subgraph whether to return a pydot cluster instance show_layer_activations display layer activations only for layers that have an activation property show_trainable whether to display if a layer is trainable returns a pydot dot instance representing the keras model or a pydot cluster instance representing nested model if subgraph true from keras layers import wrapper todo we treat subclassed models as a single node create graph nodes process nested functional models sub_n submodel connect nodes with edges sequential case functional case if inbound_layer is not functional if current layer is not functional if current layer is functional if inbound_layer is functional converts a keras model to dot format and save to a file example python inputs outputs model keras model inputs inputs outputs outputs dot_img_file tmp model_1 png keras utils plot_model model to_file dot_img_file show_shapes true args model a keras model instance to_file file name of the plot image show_shapes whether to display shape information show_dtype whether to display layer dtypes show_layer_names whether to display layer names rankdir rankdir argument passed to pydot a string specifying the format of the plot tb creates a vertical plot lr creates a horizontal plot expand_nested whether to expand nested functional models into clusters dpi image resolution in dots per inch show_layer_activations display layer activations only for layers that have an activation property show_trainable whether to display if a layer is trainable returns a jupyter notebook image object if jupyter is installed this enables in line display of the model plots in notebooks we don t raise an exception here in order to avoid crashing notebook tests where graphviz is not available we don t raise an exception here in order to avoid crashing notebook tests where graphviz is not available save image to disk return the image as a jupyter image object to be displayed in line note that we cannot easily detect whether the code is running in a notebook and thus we always return the image if jupyter is available
import os
import sys

from keras.api_export import keras_export
from keras.utils import io_utils

try:
    
    import pydot_ng as pydot
except ImportError:
    
    try:
        import pydotplus as pydot
    except ImportError:
        
        try:
            import pydot
        except ImportError:
            pydot = None


def check_pydot():
    
    return pydot is not None


def check_graphviz():
    
    if not check_pydot():
        return False
    try:
        
        
        pydot.Dot.create(pydot.Dot())
        return True
    except (OSError, pydot.InvocationException):
        return False


def add_edge(dot, src, dst):
    if not dot.get_edge(src, dst):
        edge = pydot.Edge(src, dst)
        edge.set("penwidth", "2")
        dot.add_edge(edge)


def get_layer_activation_name(layer):
    if hasattr(layer.activation, "name"):
        activation_name = layer.activation.name
    elif hasattr(layer.activation, "__name__"):
        activation_name = layer.activation.__name__
    else:
        activation_name = str(layer.activation)
    return activation_name


def make_layer_label(layer, **kwargs):
    class_name = layer.__class__.__name__

    show_layer_names = kwargs.pop("show_layer_names")
    show_layer_activations = kwargs.pop("show_layer_activations")
    show_dtype = kwargs.pop("show_dtype")
    show_shapes = kwargs.pop("show_shapes")
    show_trainable = kwargs.pop("show_trainable")
    if kwargs:
        raise ValueError(f"Invalid kwargs: {kwargs}")

    table = (
        '<<table border="0" cellborder="1" bgcolor="black" cellpadding="10">'
    )

    colspan = max(
        1, sum(int(x) for x in (show_dtype, show_shapes, show_trainable))
    )

    if show_layer_names:
        table += (
            f'<tr><td colspan="{colspan}" bgcolor="black">'
            '<font point-size="16" color="white">'
            f"<b>{layer.name}</b> ({class_name})"
            "</font></td></tr>"
        )
    else:
        table += (
            f'<tr><td colspan="{colspan}" bgcolor="black">'
            '<font point-size="16" color="white">'
            f"<b>{class_name}</b>"
            "</font></td></tr>"
        )
    if (
        show_layer_activations
        and hasattr(layer, "activation")
        and layer.activation is not None
    ):
        table += (
            f'<tr><td bgcolor="white" colspan="{colspan}">'
            '<font point-size="14">'
            f"Activation: <b>{get_layer_activation_name(layer)}</b>"
            "</font></td></tr>"
        )

    cols = []
    if show_shapes:
        shape = None
        try:
            shape = layer.output.shape
        except ValueError:
            pass
        cols.append(
            (
                '<td bgcolor="white"><font point-size="14">'
                f'Output shape: <b>{shape or "?"}</b>'
                "</font></td>"
            )
        )
    if show_dtype:
        dtype = None
        try:
            dtype = layer.output.dtype
        except ValueError:
            pass
        cols.append(
            (
                '<td bgcolor="white"><font point-size="14">'
                f'Output dtype: <b>{dtype or "?"}</b>'
                "</font></td>"
            )
        )
    if show_trainable and hasattr(layer, "trainable") and layer.weights:
        if layer.trainable:
            cols.append(
                (
                    '<td bgcolor="forestgreen">'
                    '<font point-size="14" color="white">'
                    "<b>Trainable</b></font></td>"
                )
            )
        else:
            cols.append(
                (
                    '<td bgcolor="firebrick">'
                    '<font point-size="14" color="white">'
                    "<b>Non-trainable</b></font></td>"
                )
            )
    if cols:
        colspan = len(cols)
    else:
        colspan = 1

    if cols:
        table += "<tr>" + "".join(cols) + "</tr>"
    table += "</table>>"
    return table


def make_node(layer, **kwargs):
    node = pydot.Node(str(id(layer)), label=make_layer_label(layer, **kwargs))
    node.set("fontname", "Helvetica")
    node.set("border", "0")
    node.set("margin", "0")
    return node


@keras_export("keras.utils.model_to_dot")
def model_to_dot(
    model,
    show_shapes=False,
    show_dtype=False,
    show_layer_names=True,
    rankdir="TB",
    expand_nested=False,
    dpi=200,
    subgraph=False,
    show_layer_activations=False,
    show_trainable=False,
    **kwargs,
):
    
    from keras.ops.function import make_node_key

    if not model.built:
        raise ValueError(
            "This model has not yet been built. "
            "Build the model first by calling `build()` or by calling "
            "the model on a batch of data."
        )

    from keras.models import functional
    from keras.models import sequential

    

    if not check_pydot():
        raise ImportError(
            "You must install pydot (`pip install pydot`) for "
            "model_to_dot to work."
        )

    if subgraph:
        dot = pydot.Cluster(style="dashed", graph_name=model.name)
        dot.set("label", model.name)
        dot.set("labeljust", "l")
    else:
        dot = pydot.Dot()
        dot.set("rankdir", rankdir)
        dot.set("concentrate", True)
        dot.set("dpi", dpi)
        dot.set("splines", "ortho")
        dot.set_node_defaults(shape="record")

    if kwargs.pop("layer_range", None) is not None:
        raise ValueError("Argument `layer_range` is no longer supported.")
    if kwargs:
        raise ValueError(f"Unrecognized keyword arguments: {kwargs}")

    kwargs = {
        "show_layer_names": show_layer_names,
        "show_layer_activations": show_layer_activations,
        "show_dtype": show_dtype,
        "show_shapes": show_shapes,
        "show_trainable": show_trainable,
    }

    if isinstance(model, sequential.Sequential):
        
        layers = model.layers
    elif not isinstance(model, functional.Functional):
        
        node = make_node(model, **kwargs)
        dot.add_node(node)
        return dot
    else:
        layers = model._operations

    
    sub_n_first_node = {}
    sub_n_last_node = {}
    for i, layer in enumerate(layers):
        
        if expand_nested and isinstance(layer, functional.Functional):
            submodel = model_to_dot(
                layer,
                show_shapes,
                show_dtype,
                show_layer_names,
                rankdir,
                expand_nested,
                subgraph=True,
                show_layer_activations=show_layer_activations,
                show_trainable=show_trainable,
            )
            
            sub_n_nodes = submodel.get_nodes()
            sub_n_first_node[layer.name] = sub_n_nodes[0]
            sub_n_last_node[layer.name] = sub_n_nodes[-1]
            dot.add_subgraph(submodel)

        else:
            node = make_node(layer, **kwargs)
            dot.add_node(node)

    
    
    if isinstance(model, sequential.Sequential):
        for i in range(len(layers) - 1):
            inbound_layer_id = str(id(layers[i]))
            layer_id = str(id(layers[i + 1]))
            add_edge(dot, inbound_layer_id, layer_id)
        return dot

    
    for i, layer in enumerate(layers):
        layer_id = str(id(layer))
        for i, node in enumerate(layer._inbound_nodes):
            node_key = make_node_key(layer, i)
            if node_key in model._nodes:
                for parent_node in node.parent_nodes:
                    inbound_layer = parent_node.operation
                    inbound_layer_id = str(id(inbound_layer))
                    if not expand_nested:
                        assert dot.get_node(inbound_layer_id)
                        assert dot.get_node(layer_id)
                        add_edge(dot, inbound_layer_id, layer_id)
                    else:
                        
                        if not isinstance(inbound_layer, functional.Functional):
                            
                            if not isinstance(layer, functional.Functional):
                                assert dot.get_node(inbound_layer_id)
                                assert dot.get_node(layer_id)
                                add_edge(dot, inbound_layer_id, layer_id)
                            
                            elif isinstance(layer, functional.Functional):
                                add_edge(
                                    dot,
                                    inbound_layer_id,
                                    sub_n_first_node[layer.name].get_name(),
                                )
                        
                        elif isinstance(inbound_layer, functional.Functional):
                            name = sub_n_last_node[
                                inbound_layer.name
                            ].get_name()
                            if isinstance(layer, functional.Functional):
                                output_name = sub_n_first_node[
                                    layer.name
                                ].get_name()
                                add_edge(dot, name, output_name)
                            else:
                                add_edge(dot, name, layer_id)
    return dot


@keras_export("keras.utils.plot_model")
def plot_model(
    model,
    to_file="model.png",
    show_shapes=False,
    show_dtype=False,
    show_layer_names=False,
    rankdir="TB",
    expand_nested=False,
    dpi=200,
    show_layer_activations=False,
    show_trainable=False,
    **kwargs,
):
    

    if not model.built:
        raise ValueError(
            "This model has not yet been built. "
            "Build the model first by calling `build()` or by calling "
            "the model on a batch of data."
        )
    if not check_pydot():
        message = (
            "You must install pydot (`pip install pydot`) "
            "for `plot_model` to work."
        )
        if "IPython.core.magics.namespace" in sys.modules:
            
            
            io_utils.print_msg(message)
            return
        else:
            raise ImportError(message)
    if not check_graphviz():
        message = (
            "You must install graphviz "
            "(see instructions at https://graphviz.gitlab.io/download/) "
            "for `plot_model` to work."
        )
        if "IPython.core.magics.namespace" in sys.modules:
            
            
            io_utils.print_msg(message)
            return
        else:
            raise ImportError(message)

    if kwargs.pop("layer_range", None) is not None:
        raise ValueError("Argument `layer_range` is no longer supported.")
    if kwargs:
        raise ValueError(f"Unrecognized keyword arguments: {kwargs}")

    dot = model_to_dot(
        model,
        show_shapes=show_shapes,
        show_dtype=show_dtype,
        show_layer_names=show_layer_names,
        rankdir=rankdir,
        expand_nested=expand_nested,
        dpi=dpi,
        show_layer_activations=show_layer_activations,
        show_trainable=show_trainable,
    )
    to_file = str(to_file)
    if dot is None:
        return
    _, extension = os.path.splitext(to_file)
    if not extension:
        extension = "png"
    else:
        extension = extension[1:]
    
    dot.write(to_file, format=extension)
    
    
    
    if extension != "pdf":
        try:
            from IPython import display

            return display.Image(filename=to_file)
        except ImportError:
            pass