5

I have never used the drawing packages available to TeX but they seem like the ideal solution for creating re-usable technical drawings, and might be better than spending a week in inkscape (I'm terrible at inkscape). This time I want to draw an explanatory diagram of a Kohonen network / SOM feature map, showing the input nodes and a 2D map. I have seen one that I like, but have no idea how I would come close to making it.

Here is the figure, it is an SVG:

enter image description here

Inkscape provides a conversion to PSTricks, but it is overwhelming considering the repeated hexagonal shapes of the feature map. Is it a diagram that lends itself to PGF/Tikz?

  • 1
    searching for "neural network" gives for instance this, this, this and this, which should help with the left part. – Tom Bombadil Nov 11 '13 at 12:23
  • Also, no you need the Feature map angled that way, so a drawing with a vanishing point? If yes, this is not possible out of the box with TikZ, but quite easy if you need it not angled. – Tom Bombadil Nov 11 '13 at 12:29
14

A more complete answer is given below, but first...

An attempt at the feature map using the nonlineartransformations library in the CVS version of PGF. I shamelessly steal Tom Bombadil's idea for specifying the map colors:

\documentclass[border=0.125cm]{standalone}
\usepackage{tikz}

\usepgfmodule{nonlineartransformations}
\begin{document}

\makeatletter
% This is executed for every point
%
% \pgf@x will contain the x-coordinate
% \pgf@y will contain the y-coordinate
%
% This should then be transformed to their
% final values
\def\nonlineartransform{%
 \pgf@xa=\pgf@x%
 \divide\pgf@xa by 256\relax%
 \advance\pgf@xa by 0.5pt\relax%
 \pgf@y=\pgfmath@tonumber{\pgf@xa}\pgf@y%
 \pgf@xa=0.625\pgf@xa
 \pgf@x=\pgfmath@tonumber{\pgf@xa}\pgf@x
}
\makeatother

\begin{tikzpicture}[x=10pt,y=10pt]

\begin{scope}[shift=(0:5)]
\pgftransformnonlinear{\nonlineartransform}
\foreach \c [count=\n from 0, evaluate={%
  \i=mod(\n,9); \j=int(\n/9);
  \x=(2*\i+mod(\j,2))*cos 30;
  \y=\j*1.5;
  \s=\c*10+10;}] in 
{   2,2,2,2,2,4,4,4,4,
    2,2,2,2,4,4,4,4,4,
    5,5,2,2,2,4,4,4,4,
    5,5,2,2,0,4,4,4,4,
    5,5,5,0,0,0,0,0,0,
    5,5,0,0,0,0,0,0,0,
    5,5,1,0,0,0,0,0,0,
    1,1,1,1,0,0,0,3,3,
    1,1,1,1,1,0,3,3,3,
    1,1,1,1,1,3,3,3,3,
    1,1,1,1,1,3,3,3,3
}
\draw [fill=black!\s, shift={(\x,6-\y)}] 
    (-30:1) -- (30:1) -- (90:1) -- (150:1) -- (210:1) -- (270:1) -- cycle;
\end{scope}
\end{tikzpicture}

\end{document}

enter image description here

Nothing of course to do with the OPs requirements, but I couldn't resist. This takes a long time to compile:

\documentclass[border=0.125cm,tikz]{standalone}
\usepackage{tikz}


\makeatletter
% This is executed for every point
%
% \pgf@x will contain the x-coordinate
% \pgf@y will contain the y-coordinate
%
% This should then be transformed to their
% final values
\def\nonlineartransform{%
 \pgf@xa=\pgf@x%
 \advance\pgf@xa by\k pt\relax%
 \pgfmathcos@{\pgfmath@tonumber{\pgf@xa}}%
 \pgf@xa=\pgfmathresult pt\relax%
 \advance\pgf@xa by 1pt\relax%
 \pgf@y=\pgfmath@tonumber{\pgf@xa}\pgf@y%
 \pgf@x=\pgf@x
}
\makeatother

\usepgfmodule{nonlineartransformations}
\begin{document}

\foreach \k in {0,-5,-10,...,-355}{
\begin{tikzpicture}[x=10pt,y=10pt]
\begin{scope}
\pgftransformnonlinear{\nonlineartransform}
\foreach \c [count=\n from 0, evaluate={%
  \i=mod(\n,9); \j=int(\n/9);
  \x=(2*\i+mod(\j,2))*cos 30;
  \y=\j*1.5;
  \s=\c*10+10;}] in 
{   2,2,2,2,2,4,4,4,4,
    2,2,2,2,4,4,4,4,4,
    5,5,2,2,2,4,4,4,4,
    5,5,2,2,0,4,4,4,4,
    5,5,5,0,0,0,0,0,0,
    5,5,0,0,0,0,0,0,0,
    5,5,1,0,0,0,0,0,0,
    1,1,1,1,0,0,0,3,3,
    1,1,1,1,1,0,3,3,3,
    1,1,1,1,1,3,3,3,3,
    1,1,1,1,1,3,3,3,3
}
\draw [fill=black!\s, shift={(\x,6-\y)}] 
    (-30:1) -- (30:1) -- (90:1) -- (150:1) -- (210:1) -- (270:1) -- cycle;
\end{scope}
\useasboundingbox (-5,-25) rectangle (20,20);
\end{tikzpicture}
}
\end{document}

enter image description here

Of course, we don't actually need the nonlienartranformations library at all, as tikz provides the facility for defining coordinate systems:

\documentclass[border=0.125cm]{standalone}
\usepackage{tikz}
\usetikzlibrary{fit}
\usetikzlibrary{positioning}
\begin{document}

\tikzset{feature map/.cd,
    x/.initial=0,
    y/.initial=0,
}

\tikzdeclarecoordinatesystem{feature map}{
    \tikzset{feature map/.cd, #1}%
    \pgfpointxy{\pgfkeysvalueof{/tikz/feature map/x}}{\pgfkeysvalueof{/tikz/feature map/y}}%
    \pgfgetlastxy{\fx}{\fy}%
    \pgfmathparse{\fx/256+1}\let\f=\pgfmathresult%
    \pgfpoint{\f*6/8*\fx}{\f*\fy}%
}

\tikzset{%
  every weight/.style={
    circle,
    draw,
    fill=gray!50,
    minimum size=0.25cm
  },
  weight missing/.style={
    draw=none,
    fill=none,
    execute at begin node=\color{black}$\vdots$
  },
  every neuron/.style={
    circle,
    draw,
    minimum size=0.75cm
  },
  neuron missing/.style={
    draw=none,
    execute at begin node=$\vdots$
  }
}
\begin{tikzpicture}[x=10pt,y=10pt, >=stealth]

\foreach \m [count=\y] in {1,2,missing,3,4}
  \node [every weight/.try, weight \m/.try ] (weight-\m) at (0,-\y*2) {};

\foreach \m [count=\y] in {1,2,3,missing,4,5}
  \node [every neuron/.try, neuron \m/.try ] (neuron-\m) at (8,4-\y*3) {};

\node [draw, inner xsep=0.25cm, fit={(weight-1.west) (neuron-1) (neuron-5)}] {};

\foreach \i in {1,...,4}
  \foreach \j in {1,...,5}
    \draw (weight-\i.east) -- (neuron-\j.west);

\foreach \l [count=\i] in {1,2,i-1,i}{
    \node [left=1cm of weight-\i] (input-\i) {$x_{\l}$};
    \draw [->, thick] (input-\i) -- (weight-\i);
}

\foreach \i in {1,...,5}
  \draw [->, thick] (neuron-\i) -- ++(4,0);

\begin{scope}[shift={(14,-5)}]
\foreach \c [count=\n from 0, evaluate={%
  \i=mod(\n,9); \j=int(\n/9);
  \x=(2*\i+mod(\j,2))*cos 30;
  \y=6-\j*1.5;
  \s=\c*10+10;}] in 
{   2,2,2,2,2,4,4,4,4,
    2,2,2,2,4,4,4,4,4,
    5,5,2,2,2,4,4,4,4,
    5,5,2,2,0,4,4,4,4,
    5,5,5,0,0,0,0,0,0,
    5,5,0,0,0,0,0,0,0,
    5,5,1,0,0,0,0,0,0,
    1,1,1,1,0,0,0,3,3,
    1,1,1,1,1,0,3,3,3,
    1,1,1,1,1,3,3,3,3,
    1,1,1,1,1,3,3,3,3
}
\draw [fill=black!\s] 
    (feature map cs:x=\x+cos -30, y=\y+sin -30) \foreach \a in {30,90,...,270}
      { -- (feature map cs:x=\x+cos \a, y=\y+sin \a)} -- cycle;
\end{scope}
\end{tikzpicture}

\end{document}

enter image description here

  • You're wery welcome to steal, especially if I learn something fancy and new that way! Any idea if there will be an offiical new TikZ release in the near future? Otherwise I'll get that shiny CVS version... – Tom Bombadil Nov 11 '13 at 14:37
  • @TomBombadil, don't know about a release, "soon" I guess. Downloading and the CVS version is easy. I "live on the edge" and just copy and paste my local CVS copy into my local texmf tree. – Mark Wibrow Nov 11 '13 at 15:31
  • I'm gonna do exactly that then. Oh, and very much apreciated that you could not resist 8-D – Tom Bombadil Nov 11 '13 at 18:53
  • @TomBombadil The latest CVS version of TikZ (on the pgfmanual title page) is labeled 3.0.0-release-candidate... – Paul Gaborit Nov 12 '13 at 6:53
  • 1
    I came to the question looking at the example image in the OP and was all like, “Haha, yeah, good luck reproducing that in TikZ”. And then I saw this answer and was like, “Hooolyyyy… wow!”. Good job, sir. Good job. – morbusg Nov 12 '13 at 11:06
9

Here's a solution for the (flat) feature map. You have to specify the colors as numbers, from topleft rowwise to bottom right. Then an ifcase expression defines a color based on that index. With a little trigonometry you can find out that thehexagons are spaced sqrt(3)*a or 1.5*sqrt(3)*a in x-direction for alternating rows and 1.5*a in y-direction. Here, a=0.5 (If you want to reuse this, it would be best to make the column count and sidelength a parameters). Finally, it draws a heagon and fills it with the specified color.

Bonus question: Which color index is wrong?

Code

\documentclass[tikz, border=2mm]{standalone}

\begin{document}

\begin{tikzpicture}
\foreach \clr [count=\c] in 
{   0,0,0,0,0,1,1,1,1,%
    0,0,0,0,1,1,1,1,1,%
    2,2,0,0,0,1,1,1,1,%
    2,2,0,0,3,1,1,1,1,%
    2,2,2,3,3,3,3,3,3,%
    2,2,3,3,3,3,8,3,3,%
    2,2,1,3,3,3,3,3,3,%
    1,1,1,1,3,3,3,0,0,%
    1,1,1,1,1,3,0,0,0,%
    1,1,1,1,1,0,0,0,0,%
    1,1,1,1,1,0,0,0,0%
}
{   \ifcase\clr
                    \colorlet{mycolor}{gray}% color 0
        \or     \colorlet{mycolor}{gray!66}% color 1
        \or     \colorlet{mycolor}{gray!50!black}%color 2
        \or     \colorlet{mycolor}{gray!33}% color 3
        \else   \colorlet{mycolor}{red!50!orange}%alternate color
    \fi
    \pgfmathsetmacro{\xcoord}{(mod(\c-1,9)+0.5*mod(div(\c-1,9),2))*sqrt(3)/2}
    \pgfmathsetmacro{\ycoord}{-1*div(\c-1,9)*0.75}
    \filldraw[mycolor,draw=black] (\xcoord,\ycoord) -- ++(30:0.5)  -- ++(330:0.5) -- ++(270:0.5)  -- ++(210:0.5)  -- ++(150:0.5) -- cycle;
}
\end{tikzpicture}

\end{document}

Output

enter image description here

6

edit (2017): since October 2014 1.1 release of xint, one needed here \usepackage{xinttools}, not \usepackage{xint}. Answer updated.

I have completely copied Mark Wibrow's answer but with a different choice of perspective projection. And I have turned it into an animation.

[the use of xint is not (really) one more shameless plug, I did try honestly with \foreach but couldn't achieve my aims] [the whole thing is a bit silly as it redoes the neurons each time, but I was not focused on optimizing, and I know too little of tikz]

[edit removes a line in the coordinate system specification which was a left-over from earlier version]

animation

\documentclass{article}
\usepackage[paperwidth=14cm,paperheight=8cm,%
            noheadfoot,nomarginpar,margin=0.125cm]{geometry}
\usepackage{tikz}
\usetikzlibrary{fit}
\usetikzlibrary{positioning}

\pagestyle{empty}

\usepackage{xinttools}
\topskip0pt\offinterlineskip

\begin{document}\thispagestyle{empty}

\tikzset{feature map/.cd,
    x/.initial=0,
    y/.initial=0,
}


\tikzset{%
  every weight/.style={
    circle,
    draw,
    fill=gray!50,
    minimum size=0.25cm
  },
  weight missing/.style={
    draw=none,
    fill=none,
    execute at begin node=\color{black}$\vdots$
  },
  every neuron/.style={
    circle,
    draw,
    minimum size=0.75cm
  },
  neuron missing/.style={
    draw=none,
    execute at begin node=$\vdots$
  }
}

%\typeout{\fx,\fy}%
    % \pgfmathparse{\fx/256+1}\let\f=\pgfmathresult%
    % \pgfpoint{\f*6/8*\fx}{\f*\fy}%
% \pgfmathparse{128pt/(512pt-\fx)}\let\f=\pgfmathresult
% \pgfmathparse{\fy/(512pt-\fx)}\let\g=\pgfmathresult
% \pgfmathparse{1024*\f-256}\let\f=\pgfmathresult
% \pgfmathparse{512*\g}\let\g=\pgfmathresult 
% \pgfpoint{\f}{\g}%

\xintFor* #1 in {\xintSeq[15] {0}{345}}
\do{%
\tikzdeclarecoordinatesystem{feature map#1}{
    \tikzset{feature map/.cd, ##1}%
    \pgfpointxy{\pgfkeysvalueof{/tikz/feature map/x}}{\pgfkeysvalueof{/tikz/feature map/y}}%
    \pgfgetlastxy{\fx}{\fy}%
% ça marche!
\pgfmathparse{346.41pt/(346.41pt+(\fx-77.942pt)*sin(#1))}%
\let\x=\pgfmathresult
\pgfmathparse{(\fx-77.942pt)*cos(#1)*\x+77.942pt}\let\f=\pgfmathresult
\pgfmathparse{(\fy+15pt)*\x-15pt}\let\g=\pgfmathresult
\pgfpoint{\f}{\g}%
}}


\xintFor* #1 in {\xintSeq[15] {0}{345}}
\do{\hrule height 0pt\vfill
\begin{tikzpicture}[x=10pt,y=10pt, >=stealth]

\foreach \m [count=\y] in {1,2,missing,3,4}
  \node [every weight/.try, weight \m/.try ] (weight-\m) at (0,-\y*2) {};

\foreach \m [count=\y] in {1,2,3,missing,4,5}
  \node [every neuron/.try, neuron \m/.try ] (neuron-\m) at (8,4-\y*3) {};

\node [draw, inner xsep=0.25cm, fit={(weight-1.west) (neuron-1) (neuron-5)}] {};

\foreach \i in {1,...,4}
  \foreach \j in {1,...,5}
    \draw (weight-\i.east) -- (neuron-\j.west);

\foreach \l [count=\i] in {1,2,i-1,i}{
    \node [left=1cm of weight-\i] (input-\i) {$x_{\l}$};
    \draw [->, thick] (input-\i) -- (weight-\i);
}

\foreach \i in {1,...,5}
  \draw [->, thick] (neuron-\i) -- ++(4,0);

\begin{scope}[shift={(14,-5)}]
\foreach \c [count=\n from 0, evaluate={%
  \i=mod(\n,9); \j=int(\n/9);
  \x=(2*\i+mod(\j,2))*cos 30;
  \y=6-\j*1.5;
  \s=\c*10+10;}] in 
{   2,2,2,2,2,4,4,4,4,
    2,2,2,2,4,4,4,4,4,
    5,5,2,2,2,4,4,4,4,
    5,5,2,2,0,4,4,4,4,
    5,5,5,0,0,0,0,0,0,
    5,5,0,0,0,0,0,0,0,
    5,5,1,0,0,0,0,0,0,
    1,1,1,1,0,0,0,3,3,
    1,1,1,1,1,0,3,3,3,
    1,1,1,1,1,3,3,3,3,
    1,1,1,1,1,3,3,3,3
}
\draw [fill=black!\s] 
    (feature map#1 cs:x=\x+cos -30, y=\y+sin -30) \foreach \a in {30,90,...,270}
      { -- (feature map#1 cs:x=\x+cos \a, y=\y+sin \a)} -- cycle;
\end{scope}
\end{tikzpicture}\vfill\hrule height 0pt\eject}

\end{document}
  • to be honest I do not understand why \tikzset{feature map/.cd, ##1} seems to work... – user4686 Nov 13 '13 at 21:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.