快速入門使用tikz繪制深度學習網絡圖


【GiantPandaCV導語】本文主要介紹最最最基礎的tikz命令和一些繪制CNN時需要的基礎的LaTeX知識,希望能在盡可能短的時間內學會並實現使用tikz這個LaTeX工具包來繪制卷積神經網絡示意圖。

https://github.com/HarisIqbal88/PlotNeuralNet

之前看到tikz可以畫出這種圖,感覺特別專業,所以萌發出了解一下tikz的想法。

1. overleaf平台

在電腦上安裝過LaTeX都知道,LaTeX安裝包巨大,並且安裝速度緩慢,下載和安裝的時間需要幾乎一下午才能完成。慶幸的是有一個平台可以在線編譯文檔,那就是overleaf,如今overleaf也推出了中文版本網站:https://cn.overleaf.com/ 以下代碼全部是在overleaf平台上編寫運行得到的。

主頁面

進入其中一個項目

最左側是項目文件列表,中間是代碼編輯區,右側是可視化區,十分方便,只要網絡通常,就可以方便地得到結果。並且這個平台提供了好多模板,可以直接使用,太太太太太棒啦。

2. 快速入門tikz

快速熟悉還是要推薦《minimaltikz》這本電子書,可以直接訪問http://cremeronline.com/LaTeX/minimaltikz.pdf獲取或者在后台回復latex獲取。

電子書封面

這本書一共24頁,算是盡量壓縮了內容了,在這一節中將分析一下其中給的幾個例子,用於快速入門:

所有tikz繪制圖像的代碼都應該在tikzpicture這個環境中使用。

\begin{tikzpicture}
...
\end{tikzpicture}

直角坐標系下:(\(<a>\),\(<b>\))的形式代表二維坐標系中的一個點,單位是cm。

極坐標系下:(\(<\theta>\):\(<r>\)),\(\theta\)代表極角,單位是度。

\coordinate可以對某個點進行重命名如:

\coordinate (s) at (0,1);

2.1 直線

那最基礎的畫幾條線的實現是通過\draw完成:

    \begin{tikzpicture}
    \draw[help lines] (0,0) grid(3,3);
    \coordinate (a) at (0,1);
    \coordinate (b) at (3,3);
    \coordinate (c) at (2,0);
    \draw (a) -- (b) -- (c) --cycle;
    \end{tikzpicture}

--符號代表兩點之間的連線,可以連續鏈接多段。cycle代表讓路徑回到起點,生成閉合路徑。

結果展示

\draw還可以添加選項,比如讓線變粗、變紅、箭頭等需求,都很簡單。

\begin{tikzpicture}[scale=1]
\draw[help lines] (0,0) grid(5,5);
\draw (0,0) -- (1,2)--(3,0) --(5,5);
\draw [->] (0,0) -- (2,1);
\draw [<-] (2,3) -- (5,0);
\draw [|->] (0.5,3) -- (0,4);
\draw [<->] (0,6) -- (0,0) -- (6,0);
\end{tikzpicture}

不同的箭頭

\begin{tikzpicture}
\draw[help lines] (0,0) grid(5,5);
\draw[thick] (0.5, 0.5) -- (3,3);
% [ultra thick, thick, thin, very thick]
\draw[line width=0.2cm] (1,0) -- (3,2);
\end{tikzpicture}

粗細控制

\begin{tikzpicture}
\draw[help lines] (0,0) grid(5,5);
\draw[ultra thick, dotted] (0,0) -- (2,3);
\draw[line width=0.2cm, dotted,red] (2,2) -- (4,0);
%[red, blue, green, cyan, magenta, yellow, black, gray, darkgray, lightgray, browbn, lime, olive, orange, pink, purple, teal, violet, white]
\end{tikzpicture}

顏色控制

2.2 曲線

畫一些曲線就需要使用circle、rectangle、arc等進行約束。

\begin{tikzpicture}
\draw[help lines] (0,0) grid(5,5);
\draw[blue] (1,1) rectangle(3,3); % 正方形 需要左下角坐標和右上角坐標
\draw[red] (2,2) circle[radius=2]; %圓形 需要圓心坐標和半徑
\draw[green] (1,0) arc [radius=1,start angle=180,end angle=360];
\draw[<->, rounded corners, thick, purple] (0,5) -- (0,0) -- (5,0);
\end{tikzpicture}

結果展示

\begin{tikzpicture}
\draw[help lines] (0,0) grid(6,3);
\draw[blue, thick] (0,0) to[out=90,in=180] (1,1) to[in=270,out=360] (2,2)
to[in=180,out=90] (3,3) to[in=90,out=360] (4,2) to[in=180,out=270] (5,1) 
to[in=90, out=0] (6,0);
\end{tikzpicture}

這是練習畫弧線的時候想練習的一個例子,結果如下

結果展示

in代表進入的角度,out代表出來時候的角度,為了方便,筆者畫了一個輔助圖,對照代碼方便理解。

參考

2.3 畫函數曲線

\begin{tikzpicture}[xscale=6,yscale=6]
\draw[<->] (0,0.8) -- (0,0) -- (0.8,0);
\draw[green,thick,domain=0:0.5]
plot(\x, {0.025+\x*\x});
\draw[red, thick, domain=0:0.5]
plot(\x, {sqrt(\x)});
\draw[blue, thick, domain=0:0.5]
plot(\x, {abs(\x)});
\end{tikzpicture}

domain限制變量范圍,然后可以畫圖,結果如下:

繪制函數曲線

2.4 填充

\begin{tikzpicture}
\draw[fill=red,ultra thick] (0,0) rectangle(1,1);
\draw[fill=red,ultra thin, red] (2,0) rectangle(3,1);
\draw[fill] (5,0) circle[radius=1];
\draw [fill=orange] (9,0) rectangle (11,1);
\draw [fill=white] (9.25,0.25) rectangle (10,1.5);
\path [fill=gray] (0,-2) rectangle (1.5,-3);
\draw [fill=yellow] (2,-2) rectangle (3.5,-3);
\end{tikzpicture}    

通過fill參數控制結果,效果如下:

填充結果

2.6 添加文字

使用\node

\node [<options>] (<name>) at (<coordinate>) {<text>};

舉個例子:

\begin{tikzpicture}[scale=2]
\draw [thick, <->] (0,1) -- (0,0) -- (1,0);
\draw[fill] (1,1) circle [radius=0.025];
\node [below right, red] at (.5,.75) {below right};
\node [above left, green] at (.5,.75) {above left};
\node [below left, purple] at (.5,.75) {below left};
\node [above right, magenta] at (.5,.75) {above right};
\end{tikzpicture}

添加文字效果

其實CNN畫圖主要用的是畫一條線的功能,下面來看如何畫CNN。

3. 繪制一個CNN模塊

對於一個初學者來說,https://github.com/HarisIqbal88/PlotNeuralNet 這個庫雖然畫的很好,但是難度曲線太高了,退而求其次,使用https://github.com/pprp/SimpleCVReproduction/tree/master/tikz_cnn 進行解析。

首先介紹一個LaTeX中用於封裝的命令,\newcommand,當我們不希望寫很長的命令,那就需要類似函數的一個方式,封裝好固定的操作,根據傳入參數完成執行。

\newcommand<命令>[<參數個數>][<首參數默認值>]{<具體的定義>}

舉一個例子:

\newcommand\loves[2]{#1 喜歡 #2}
\loves{我}{你}

輸出結果就是:我喜歡你

\newcommand{\networkLayer}[9]{
	% Define the macro.
	% 1st argument: Height and width of the layer rectangle slice.
	% 2nd argument: Depth of the layer slice
	% 3rd argument: X Offset --> use it to offset layers from previously drawn layers.
	% 4th argument: Y Offset --> Use it when an output needs to be fed to multiple layers that are on the same X offset.
	% 5th argument: Z Offset --> Use to offset layers from previous 
	% 6th argument: Options for filldraw.
	% 7th argument: Text to be placed below this layer.
	% 8th argument: Name of coordinates. When name = "start" this resets the offset counter
	% 9th argument: list of nodes to connect to (previous layers)
	% 全局變量
	\xdef\totalOffset{\totalOffset}
 	\ifthenelse{\equal{#8} {start}}
 	{\FPset{totalOffset}{0}}
 	{}
 	\FPeval\currentOffset{0+(totalOffset)+(#3)}

	\def\hw{#1} % Used to distinguish input resolution for current layer.
	\def\b{0.02}
	\def\c{#2} % Width of the cube to distinguish number of input channels for current layer.
	\def\x{\currentOffset} % X offset for current layer.
	\def\y{#4} % Y offset for current layer.
	\def\z{#5} % Z offset for current layer.
	\def\inText{#7}

    % Define references to points on the cube surfaces
    \coordinate (#8_front) at  (\x+\c  , \z      , \y);
    \coordinate (#8_back) at   (\x     , \z      , \y);
    \coordinate (#8_top) at    (\x+\c/2, \z+\hw/2, \y);
    \coordinate (#8_bottom) at (\x+\c/2, \z-\hw/2, \y);
    
 	% Define cube coords
	\coordinate (blr) at (\c+\x,  -\hw/2+\z,  -\hw/2+\y); %back lower right
	\coordinate (bur) at (\c+\x,   \hw/2+\z,  -\hw/2+\y); %back upper right
	\coordinate (bul) at (0 +\x,   \hw/2+\z,  -\hw/2+\y); %back upper left
	\coordinate (fll) at (0 +\x,  -\hw/2+\z,   \hw/2+\y); %front lower left
	\coordinate (flr) at (\c+\x,  -\hw/2+\z,   \hw/2+\y); %front lower right
	\coordinate (fur) at (\c+\x,   \hw/2+\z,   \hw/2+\y); %front upper right
	\coordinate (ful) at (0 +\x,   \hw/2+\z,   \hw/2+\y); %front upper left
	

    % Draw connections from other points to the back of this node
    \ifthenelse{\equal{#9} {}}
 	{} % 為空什么都不做
 	{ % 非空 開始畫層與層之間的連線
 	    \foreach \val in #9
 	    % \val = start_front
 	        \draw[line width=0.3mm] (\val)--(#8_back);
 	}
 	
	% Draw the layer body.
	% back plane
	\draw[line width=0.3mm](blr) -- (bur) -- (bul);
	% front plane
	\draw[line width=0.3mm](fll) -- (flr) node[midway,below] {\inText} -- (fur) -- (ful) -- (fll);
	\draw[line width=0.3mm](blr) -- (flr);
	\draw[line width=0.3mm](bur) -- (fur);
	\draw[line width=0.3mm](bul) -- (ful);

	% Recolor visible surfaces
	% front plane
	\filldraw[#6] ($(fll)+(\b,\b,0)$) -- ($(flr)+(-\b,\b,0)$) -- ($(fur)+(-\b,-\b,0)$) -- ($(ful)+(\b,-\b,0)$) -- ($(fll)+(\b,\b,0)$);
	\filldraw[#6] ($(ful)+(\b,0,-\b)$) -- ($(fur)+(-\b,0,-\b)$) -- ($(bur)+(-\b,0,\b)$) -- ($(bul)+(\b,0,\b)$);

	% Colored slice.
	\ifthenelse {\equal{#6} {}}
	{} % Do not draw colored slice if #6 is blank.
	% Else, draw a colored slice.
	{\filldraw[#6] ($(flr)+(0,\b,-\b)$) -- ($(blr)+(0,\b,\b)$) -- ($(bur)+(0,-\b,\b)$) -- ($(fur)+(0,-\b,-\b)$);}

	\FPeval\totalOffset{0+(currentOffset)+\c}

	\draw[ultra thick, red] (#8_back) circle[radius=0.02];
	\node[left] at (#8_back) {back};
	
	\draw[ultra thick, red] (#8_top) circle[radius=0.02];
	\node[above] at (#8_top) {top};
	
	\draw[ultra thick, red] (#8_bottom) circle[radius=0.02];
	\node[below] at (#8_bottom) {bottom};
	
	\draw[ultra thick, red] (#8_front) circle[radius=0.02];
	\node[left] at (#8_front) {front};
}

假設以下命令調用,結果會是什么?

\begin{tikzpicture}[scale=2]
\draw[help lines] (0,0) grid(2,2);
\draw[->,thick] (0,0,0) -- (0,0,2); 
\draw[->,thick] (0,0,0) -- (0,2,0);
\draw[->,thick] (0,0,0) -- (2,0,0);
\draw[->,thick] (0,0,0) -- (2,2,0);
\draw[->,thick] (0,0,0) -- (1,2,0);
\draw[->,thick] (0,0,0) -- (0,2,2);
\draw[->,thick] (0,0,0) -- (2,0,2);
\draw[dotted,thick] (0,0,2) -- (0,2,2);
\draw[dotted,thick] (0,2,0) -- (0,2,2);
\draw[dotted,thick] (0,0,2) -- (2,0,2);
\draw[dotted,thick] (2,0,0) -- (2,0,2);
\draw[dotted,thick] (1,0,0) -- (1,0,2);
\draw[dotted,thick] (0,0,1) -- (2,0,1);
\draw[<->, thick] (0,2) -- (0,0) -- (2,0);
			%HW -D -  x- y- z - fill color -  text - 坐標 - 鏈接
\networkLayer{1}{0.5}{0}{0}{0}{color=green!20}{conv1}{}{}
\end{tikzpicture}

顯示結果如下:

可視化一個模塊

卷積神經網絡的示意圖實際上是一個個立方體構成的,立方體之間可能會有額外連線,代表特征融合;還可能需要題注,為這個特征圖立方體進行命名;必須要有立方體的位置信息,長寬高;還需要顏色填充的功能;

綜合以上需求,這個函數提供了9個參數分別是:

  • 1 H&W 控制立方體右側這一面的高度,默認為正方形。

  • 2 Depth 控制深度

  • 3 X 方向上的偏置

  • 4 Y方向上的偏置

  • 5 Z方向上的偏置

  • 6 填充的顏色

  • 7 Text展示的文本,放在最下側

  • 8 坐標名稱,通過命名便於#9訪問

  • 9 通過名稱指定連接位置,用於連接前方層的時候使用

前兩個參數示意圖

由於每繪制一個立方體,右側立方體的X偏置就應該加上左側立方體的Depth值,這部分代碼這樣處理的。

\FPset{totalOffset}{0} % 設置全局變量totaloffset	
\xdef\totalOffset{\totalOffset}
\ifthenelse{\equal{#8} {start}}
% 如果#8坐標名稱為start,那么將totaloffset歸零
{\FPset{totalOffset}{0}}
{}% 否則什么都不做
\FPeval\currentOffset{0+(totalOffset)+(#3)}
% 計算當前offset也就是#3 X+totalOffset

賦值過程:

\def\hw{#1} % Used to distinguish input resolution for current layer.
\def\b{0.02}
\def\c{#2} % Width of the cube to distinguish number of input channels for current layer.
\def\x{\currentOffset} % X offset for current layer.
\def\y{#4} % Y offset for current layer.
\def\z{#5} % Z offset for current layer.
\def\inText{#7}

計算立方體表面坐標(將點可視化是額外添加的,為了便於理解)

% Define references to points on the cube surfaces
\coordinate (#8_front) at  (\x+\c  , \z      , \y);
\coordinate (#8_back) at   (\x     , \z      , \y);
\coordinate (#8_top) at    (\x+\c/2, \z+\hw/2, \y);
\coordinate (#8_bottom) at (\x+\c/2, \z-\hw/2, \y);

計算7個頂點位置,被擋住的也可以計算,但是因為這里不打算繪制所以不計算。

7個頂點示意圖

% Define cube coords
\coordinate (blr) at (\c+\x,  -\hw/2+\z,  -\hw/2+\y); %back lower right
\coordinate (bur) at (\c+\x,   \hw/2+\z,  -\hw/2+\y); %back upper right
\coordinate (bul) at (0 +\x,   \hw/2+\z,  -\hw/2+\y); %back upper left
\coordinate (fll) at (0 +\x,  -\hw/2+\z,   \hw/2+\y); %front lower left
\coordinate (flr) at (\c+\x,  -\hw/2+\z,   \hw/2+\y); %front lower right
\coordinate (fur) at (\c+\x,   \hw/2+\z,   \hw/2+\y); %front upper right
\coordinate (ful) at (0 +\x,   \hw/2+\z,   \hw/2+\y); %front upper left

繪制立方塊之間的連線:

% Draw connections from other points to the back of this node
\ifthenelse{\equal{#9} {}}
{} % 為空什么都不做
{ % 非空 開始畫層與層之間的連線
\foreach \val in #9
% \val = start_front
\draw[line width=0.3mm] (\val)--(#8_back);
}

繪制立方體主體部分,也就是將7個點連接起來。

% back plane
\draw[line width=0.3mm](blr) -- (bur) -- (bul);
% front plane
\draw[line width=0.3mm](fll) -- (flr) node[midway,below] {\inText} -- (fur) -- (ful) -- (fll);
\draw[line width=0.3mm](blr) -- (flr);
\draw[line width=0.3mm](bur) -- (fur);
\draw[line width=0.3mm](bul) -- (ful);

填充顏色:

% front plane
\filldraw[#6] ($(fll)+(\b,\b,0)$) -- ($(flr)+(-\b,\b,0)$) -- ($(fur)+(-\b,-\b,0)$) -- ($(ful)+(\b,-\b,0)$) -- ($(fll)+(\b,\b,0)$);
\filldraw[#6] ($(ful)+(\b,0,-\b)$) -- ($(fur)+(-\b,0,-\b)$) -- ($(bur)+(-\b,0,\b)$) -- ($(bul)+(\b,0,\b)$);

% Colored slice.
\ifthenelse {\equal{#6} {}}
{} % Do not draw colored slice if #6 is blank.
% Else, draw a colored slice.
{\filldraw[#6] ($(flr)+(0,\b,-\b)$) -- ($(blr)+(0,\b,\b)$) -- ($(bur)+(0,-\b,\b)$) -- ($(fur)+(0,-\b,-\b)$);}

一個卷積神經網絡結構圖

上邊的圖是通過以下代碼生成的:

\begin{tikzpicture}

% INPUT
\networkLayer{3.0}{0.03}{0.0}{0.0}{0.0}{color=gray!80}{}{start}{}

% ENCODER
\networkLayer{3.0}{0.1}{0.5}{0.0}{0.0}{color=white}{conv}{}{}    % S1
\networkLayer{3.0}{0.1}{0.1}{0.0}{0.0}{color=white}{}{}{}        % S2
\networkLayer{2.5}{0.2}{0.1}{0.0}{0.0}{color=white}{conv}{}{}    % S1
\networkLayer{2.5}{0.2}{0.1}{0.0}{0.0}{color=white}{}{}{}        % S2
\networkLayer{2.0}{0.4}{0.1}{0.0}{0.0}{color=white}{conv}{}{}    % S1
\networkLayer{2.0}{0.4}{0.1}{0.0}{0.0}{color=white}{}{}{}        % S2
\networkLayer{1.5}{0.8}{0.1}{0.0}{0.0}{color=white}{conv}{}{}    % S1
\networkLayer{1.5}{0.8}{0.1}{0.0}{0.0}{color=white}{}{}{}        % S2
\networkLayer{1.0}{1.5}{0.1}{0.0}{0.0}{color=white}{conv}{}{}    % S1
\networkLayer{1.0}{1.5}{0.1}{0.0}{0.0}{color=white}{}{mid}{}        % S2

\networkLayer{1.0}{0.5}{1.5}{0.0}{-1.5}{color=green!50}{}{bot}{{mid_front}}
\networkLayer{1.0}{0.5}{-0.5}{0.0}{1.5}{color=green!50}{}{top}{{mid_front}}
\networkLayer{1.0}{0.5}{1.5}{0.0}{0.0}{color=blue!50}{sum}{}{{bot_front,top_front}}

% DECODER
\networkLayer{1.0}{1.5}{0.1}{0.0}{0.0}{color=white}{deconv}{}{} % S1
\networkLayer{1.0}{1.5}{0.1}{0.0}{0.0}{color=white}{}{}{}       % S2
\networkLayer{1.5}{0.8}{0.1}{0.0}{0.0}{color=white}{deconv}{}{} % S1
\networkLayer{1.5}{0.8}{0.1}{0.0}{0.0}{color=white}{}{}{}       % S2
\networkLayer{2.0}{0.4}{0.1}{0.0}{0.0}{color=white}{}{}{}       % S1
\networkLayer{2.0}{0.4}{0.1}{0.0}{0.0}{color=white}{}{}{}       % S2
\networkLayer{2.5}{0.2}{0.1}{0.0}{0.0}{color=white}{}{}{}       % S1
\networkLayer{2.5}{0.2}{0.1}{0.0}{0.0}{color=white}{}{}{}       % S2
\networkLayer{3.0}{0.1}{0.1}{0.0}{0.0}{color=white}{}{}{}       % S1
\networkLayer{3.0}{0.1}{0.1}{0.0}{0.0}{color=white}{}{}{}       % S2

% OUTPUT
\networkLayer{3.0}{0.05}{0.9}{0.0}{0.0}{color=red!40}{}{}{}     % Pixelwise segmentation with classes.

\end{tikzpicture}

需要注意的是#8和#9命令,mid_front代表的是鏈接#8=mid的front部分,front也可以被top、back、bottom取代。

4. 資源推薦

https://cn.overleaf.com/project/5e8c38c31cccb20001a4998d

https://cn.overleaf.com/project/5f50b21ae802b6000155ec4f

https://github.com/HarisIqbal88/PlotNeuralNet

https://github.com/pprp/SimpleCVReproduction/tree/master/tikz_cnn


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM