1

Hello there I am an absoulute beginner.

I took some tikz template for drawing nerual nets and tried to modify it so I can change the number of layers automatically, but it yields strange errors.

I am using these packages and have no idea if it matters

\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage[czech]{babel}
\usepackage[margin=2cm, a4paper]{geometry}
\usepackage[T1]{fontenc}
\usepackage{fontawesome}
\usepackage{xcolor}
\usepackage{graphicx}
\usepackage{array}
\usepackage[protrusion,expansion]{microtype}
\usepackage{tikz}
\usepackage[nomessages]{fp}

I came up with a following macro in a separate .sty file:

\newcommand{\mln}[3]{
    \def\layersep{#1}
    \def\inpneuroncount{#2}
    \def\hidneuroncount{#3}

    \FPeval{result}{clip((\hidneuroncount-\inpneuroncount)/2)}
    \def\nodeoffset{yshift=\result cm}
    \nodeoffset

    \begin{tikzpicture}[shorten >=1pt,->,draw=black!50, node distance=\layersep]
        \tikzstyle{every pin edge}=[<-,shorten <=3pt]
        \tikzstyle{neuron}=[circle,fill=black!25,minimum size=15pt,inner sep=0pt]
        \tikzstyle{input neuron}=[neuron, fill=green!50];
        \tikzstyle{output neuron}=[neuron, fill=red!50];
        \tikzstyle{hidden neuron}=[neuron, fill=blue!50];
        \tikzstyle{annot} = [text width=4em, text centered]

        % Draw the input layer nodes
        \foreach \name / \y in {1,...,\inpneuroncount}
        % This is the same as writing \foreach \name / \y in {1/1,2/2,3/3,4/4}
            \node[input neuron, pin=left:Input \#\y] (I-\name) at (0,-\y) {};

        % Draw the hidden layer nodes
        \foreach \name / \y in {1,...,\hidneuroncount}
            \path[\nodeoffset]
                node[hidden neuron] (H-\name) at (\layersep,-\y cm) {};

        % Draw the output layer node
        \node[output neuron,pin={[pin edge={->}]right:Output}, right of=H-1] (O) {};

        % Connect every node in the input layer with every node in the
        % hidden layer.
        \foreach \source in {1,...,\inpneuroncount}
            \foreach \dest in {1,...,\hidneuroncount}
                \path (I-\source) edge (H-\dest);

        % Connect every node in the hidden layer with the output layer
        \foreach \source in {1,...,\hidneuroncount}
            \path (H-\source) edge (O);

        % Annotate the layers
        \node[annot,above of=H-1, node distance=1cm] (hl) {Hidden layer};
        \node[annot,left of=hl] {Input layer};
        \node[annot,right of=hl] {Output layer};
    \end{tikzpicture}
    % End of code
}

What I am trying to do rn, is to make the yshift of the hidden layer to be equal to the half of the difference between number of input neurons and hidden layer neurons. I cretaed a variable \nodeoffset, which after usage clearly renders the text "yshift=0.5cm" when u use \mln{2.5cm}{2}{3}

When I try to use it in the part

% Draw the hidden layer nodes
    \foreach \name / \y in {1,...,\hidneuroncount}
        \path[\nodeoffset]

it yelds error like "Package pgfkeys Error: I do not know the key '/tikz/yshift=0.5cm' and I am going to ignore it. Perhaps you misspelled it."

How do I get the parameter there correctly?

2
  • Welcome! You can make it work by using \path[style/.expanded=\nodeoffset].... However, I'd rather replace \def\nodeoffset{yshift=\result cm} by \tikzset{my node offset/.style={yshift=\result cm}} and then use \path[my node offset] ... instead of \path[\nodeoffset]. BTW, \tikzstyle is deprecated.
    – user194703
    Commented Dec 4, 2019 at 23:41
  • 2
    I also advise against using a 3-argument macro in the way you do. Rather, you might want to use pgf keys.
    – user194703
    Commented Dec 4, 2019 at 23:44

1 Answer 1

3

You are facing a so-called expansion issue. TikZ does not fully expand the macro \nodeoffset. You can make it expand by replacing

\path[\nodeoffset] node[hidden neuron] (H-\name) at (\layersep,-\y cm) {};

by

\path[style/.expanded=\nodeoffset] node[hidden neuron] (H-\name) at (\layersep,-\y cm) {};

Full example:

\documentclass{article}
\usepackage{tikz}
\usepackage[nomessages]{fp}
\newcommand{\mln}[3]{
    \def\layersep{#1}
    \def\inpneuroncount{#2}
    \def\hidneuroncount{#3}

    \FPeval{result}{clip((\hidneuroncount-\inpneuroncount)/2)}
    \def\nodeoffset{yshift=\result cm}
    \nodeoffset

    \begin{tikzpicture}[shorten >=1pt,->,draw=black!50, node distance=\layersep]
        \tikzstyle{every pin edge}=[<-,shorten <=3pt]
        \tikzstyle{neuron}=[circle,fill=black!25,minimum size=15pt,inner sep=0pt]
        \tikzstyle{input neuron}=[neuron, fill=green!50];
        \tikzstyle{output neuron}=[neuron, fill=red!50];
        \tikzstyle{hidden neuron}=[neuron, fill=blue!50];
        \tikzstyle{annot} = [text width=4em, text centered]

        % Draw the input layer nodes
        \foreach \name / \y in {1,...,\inpneuroncount}
        % This is the same as writing \foreach \name / \y in {1/1,2/2,3/3,4/4}
            \node[input neuron, pin=left:Input \#\y] (I-\name) at (0,-\y) {};

        % Draw the hidden layer nodes
        \foreach \name / \y in {1,...,\hidneuroncount}
            \path[style/.expanded=\nodeoffset]
                node[hidden neuron] (H-\name) at (\layersep,-\y cm) {};

        % Draw the output layer node
        \node[output neuron,pin={[pin edge={->}]right:Output}, right of=H-1] (O) {};

        % Connect every node in the input layer with every node in the
        % hidden layer.
        \foreach \source in {1,...,\inpneuroncount}
            \foreach \dest in {1,...,\hidneuroncount}
                \path (I-\source) edge (H-\dest);

        % Connect every node in the hidden layer with the output layer
        \foreach \source in {1,...,\hidneuroncount}
            \path (H-\source) edge (O);

        % Annotate the layers
        \node[annot,above of=H-1, node distance=1cm] (hl) {Hidden layer};
        \node[annot,left of=hl] {Input layer};
        \node[annot,right of=hl] {Output layer};
    \end{tikzpicture}
    % End of code
}
\begin{document}
\mln{8em}{5}{4}
\end{document}

enter image description here

However, I would like to suggest some modifications/improvements.

  1. \tikzstyle is deprecated, use the corresponding \tikzset syntax (see below) instead.
  2. You actually do not need fp here.
  3. Use positioning.
  4. The \defs in the beginning do not really seem to have a purpose.

There are many more things that one may change, but this is a partly modified version.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{positioning}
\newcommand{\mln}[3]{
    \begin{tikzpicture}[shorten >=1pt,->,draw=black!50, node distance=#1]
        \tikzset{every pin edge/.style={<-,shorten <=3pt},
          neuron/.style={circle,fill=black!25,minimum size=15pt,inner
            sep=0pt},
          input neuron/.style={neuron, fill=green!50},
          output neuron/.style={neuron, fill=red!50},
          hidden neuron/.style={neuron, fill=blue!50},
          annot/.style={text width=4em, text centered},
        my offset/.style={yshift={((#3-#2)/2)*1cm}}}
        \begin{scope}[local bounding box=diag]
          % Draw the input layer nodes
          \foreach \name / \y in {1,...,#2}
          % This is the same as writing \foreach \name / \y in {1/1,2/2,3/3,4/4}
              \node[input neuron, pin={[alias=auxI]left:Input \#\y}] (I-\name) at (0,-\y) {};

          % Draw the hidden layer nodes
          \foreach \name / \y in {1,...,#3}
              {\path[my offset]
                  node[hidden neuron] (H-\name) at (#1,-\y cm) {};}

          % Draw the output layer node
          \node[output neuron,pin={[pin edge={->},alias=auxO]right:Output}, right=of H-1] (O) {};

          % Connect every node in the input layer with every node in the
          % hidden layer.
          \foreach \source in {1,...,#2}
              \foreach \dest in {1,...,#3}
                  \path (I-\source) edge (H-\dest);

          % Connect every node in the hidden layer with the output layer
          \foreach \source in {1,...,#3}
              \path (H-\source) edge (O);
        \end{scope}
        % Annotate the layers
        \path ([yshift=1ex]diag.north-|H-1.center) node[anchor=south,annot] (hl) {Hidden layer};
        \path ([yshift=1ex]diag.north-|auxI.west) node[anchor=south west,annot]  {Input layer};
        \path ([yshift=1ex]diag.north-|auxO.east) node[anchor=south east,annot]  {Output layer};
    \end{tikzpicture}
    % End of code
}
\begin{document}
\mln{8em}{5}{4}
\end{document}
6
  • 1
    I haven't thought of style/.expanded= before. Good one! Commented Dec 5, 2019 at 3:43
  • But yes, the proper way to do things is to use keys and values instead of macros (which is part of the reason why I removed \coord from let). Commented Dec 5, 2019 at 3:45
  • 1
    I'm actually planning to come your way in these terms with a new reflection library which stores all the information about a point in pgfkeys. That would also replace \tikz@dcl@coord@<x> by \pgfkeysvalueof{/tikz/reflection/<x>/declared coordinate}. Commented Dec 5, 2019 at 3:47
  • @HenriMenke Looking forward to seeing this library!
    – user194703
    Commented Dec 5, 2019 at 4:45
  • 1
    The OP is not using xfp (which I would naturally prefer) but fp. Commented Dec 5, 2019 at 6:52

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .