前言

在学术论文和博客中,神经网络架构图是不可或缺的可视化工具。虽然Python的matplotlib、TensorBoard等工具可以生成网络图,但LaTeX的TikZ包提供了更精确、更美观的绘图方案。本文将详细介绍如何使用TikZ绘制专业的神经网络图,并通过一个完整的Dueling DQN网络案例来演示。

一、准备工作

1.1 环境配置

首先确保您的LaTeX环境已安装TikZ包。在文档开头添加必要的包:


\documentclass[tikz,border=2pt]{standalone}
\usetikzlibrary{fpu}    % 修复ifthenelse错误
\usetikzlibrary{math}   % 数学计算功能

`standalone`类允许我们生成独立的图片文件,`border=2pt`设置边框宽度。`fpu`库解决了某些数学运算的兼容性问题,`math`库提供了强大的数学计算功能。

1.2 基本概念

TikZ的核心思想是"用代码画图"。每个图形元素(点、线、圆等)都可以通过坐标和属性来精确控制。对于神经网络图,我们需要掌握:

- 节点绘制和定位

- 连接线绘制

- 颜色和样式设置

- 数学计算和循环

二、TikZ核心语法详解

2.1 坐标系统

TikZ使用笛卡尔坐标系,原点在左下角:

\draw (0,0) -- (2,1);  % 从(0,0)到(2,1)的直线
\filldraw (1,1) circle (0.3);  % 在(1,1)处画半径为0.3的圆

2.2 节点绘制

神经网络中的神经元通常用圆形表示:

\filldraw[line width=0.5pt, fill=blue!10] (x,y) circle (radius);

- `line width`:边框粗细

- `fill=blue!10`:填充颜色(10%蓝色)

- `circle (radius)`:圆形,指定半径

2.3 连接线绘制

层间连接用直线表示:

\draw (x1,y1) -- (x2,y2);  % 普通直线
\draw[-latex] (x1,y1) -- (x2,y2);  % 带箭头的直线

2.4 数学计算和循环

TikZ的`math`库提供了强大的数学功能:

\tikzmath{
    function paint_nodes(\radius, \gapy, \posx, \num){
        \gapy = \gapy + \radius*2;        % 计算节点间距
        \starty = \gapy*(\num-1)/2;      % 计算起始Y坐标(垂直居中)
        for \i in {0,...,\num-1}{        % 循环绘制每个节点
            \drawy = \starty - \i*\gapy; % 计算当前节点的Y坐标
            {
                \filldraw[line width=0.5pt, fill=blue!10] (\posx,\drawy) circle (\radius);
            };
        };
    };
}

1. **函数定义**:`function paint_nodes(\radius, \gapy, \posx, \num)`

   - `\radius`:节点半径

   - `\gapy`:节点间垂直间距

   - `\posx`:节点的X坐标(整层节点在同一X位置)

   - `\num`:节点数量

2. **间距计算**:`\gapy = \gapy + \radius*2`

   - 将节点半径的两倍加到间距中,确保节点不重叠

   - 例如:半径0.3,原间距0.2,则实际间距为0.8

3. **垂直居中**:`\starty = \gapy*(\num-1)/2`

   - 计算第一个节点的Y坐标,使整层节点垂直居中

   - 例如:5个节点,间距0.8,则起始Y坐标为1.6

4. **循环绘制**:`for \i in {0,...,\num-1}`

   - 从0到num-1循环,绘制每个节点

   - `\drawy = \starty - \i*\gapy`:每个节点比前一个低一个间距

5. **节点绘制**:`\filldraw[line width=0.5pt, fill=blue!10] (\posx,\drawy) circle (\radius)`

   - 在计算出的位置绘制填充的圆形节点

   - 边框0.5pt,填充10%蓝色

三、实战案例:D3QN网络图

function paint_input_nodes(\radius,\gapy,\posx,\num){
    \gapy = \gapy+\radius*2;
    \starty = \gapy*(\num-1)/2;
    for \i in {0,...,\num-1}{
        \drawy = \starty - \i*\gapy;
        {
            \filldraw[line width=0.5pt, fill=blue!10] (\posx,\drawy) circle (\radius);
        };
    };
};

这个函数实现了:自动计算节点间距 垂直居中对齐 循环绘制指定数量的节点

function paint_lines(\radius,\gapy,\posx,\num,\nextposx,\nextnum){
    \gapy = \gapy+\radius*2;              % 计算节点间距
    \starty = \gapy*(\num-1)/2;          % 当前层起始Y坐标
    \startyy = \gapy*(\nextnum-1)/2;     % 下一层起始Y坐标
    for \i in {0,...,\num-1}{            % 遍历当前层的每个节点
        \drawy = \starty - \i*\gapy;     % 当前层第i个节点的Y坐标
        for \j in {0,...,\nextnum-1}{    % 遍历下一层的每个节点
            \drawyy = \startyy - \j*\gapy; % 下一层第j个节点的Y坐标
            {
                \draw (\posx,\drawy) -- (\nextposx,\drawyy); % 绘制连接线
            };
        };
    };
};

1. **函数参数**:

   - `\radius, \gapy`:节点半径和间距(与节点绘制函数一致)

   - `\posx, \num`:当前层的X坐标和节点数量

   - `\nextposx, \nextnum`:下一层的X坐标和节点数量

2. **间距计算**:`\gapy = \gapy+\radius*2`

   - 与节点绘制函数保持一致,确保连接线从节点边缘开始

3. **两层起始坐标计算**:

   - `\starty = \gapy*(\num-1)/2`:当前层垂直居中的起始Y坐标

   - `\startyy = \gapy*(\nextnum-1)/2`:下一层垂直居中的起始Y坐标

   - 两层可能节点数不同,需要分别计算

4. **双重循环结构**:

   - **外层循环**:`for \i in {0,...,\num-1}` 遍历当前层的每个节点

   - **内层循环**:`for \j in {0,...,\nextnum-1}` 遍历下一层的每个节点

   - 这实现了**全连接**:每个节点连接到下一层的所有节点

5. **坐标计算**:

   - `\drawy = \starty - \i*\gapy`:当前层第i个节点的Y坐标

   - `\drawyy = \startyy - \j*\gapy`:下一层第j个节点的Y坐标

6. **连接线绘制**:`\draw (\posx,\drawy) -- (\nextposx,\drawyy)`

   - 从当前层节点到下一层节点绘制直线

   - 形成完整的全连接网络

**连接数量计算**:

- 如果当前层有m个节点,下一层有n个节点

- 总共需要绘制 m × n 条连接线

- 例如:7个节点连接到10个节点,需要70条连接线

**视觉效果**: 形成密集的连接网络,体现神经网络的"全连接"特性,连接线密度随层间节点数乘积增加

3.3 特殊输出层处理

D3QN的输出层需要特殊处理,因为价值函数和优势函数有不同的结构:

1. **输出结构不同**

- **普通网络**:所有输出节点均匀分布,使用`paint_lines`函数

- **Dueling DQN**:输出分为两部分,需要特殊处理

function paint_output_connections(\radius,\gapy,\fromposx,\fromnum,\toposx){
    \gapy = \gapy+\radius*2;
    \starty = \gapy*(\fromnum-1)/2;
    % 连接到价值函数节点 - **特点**:所有隐藏层节点都连接到**同一个位置**(Y=3)
    %                   - **含义**:价值函数V(s)只依赖于状态,与动作无关
    for \i in {0,...,\fromnum-1}{
        \drawy = \starty - \i*\gapy;
        {
            \draw (\fromposx,\drawy) -- (\toposx,3);
        };
    };
    % 连接到优势函数节点 - **特点**:每个隐藏层节点连接到**4个不同位置**(Y=0,-1,-2,-3)
    %                   - **含义**:优势函数A(s,a)依赖于状态和动作
    for \i in {0,...,\fromnum-1}{
        \drawy = \starty - \i*\gapy;
        for \j in {0,...,3}{
            {
                \draw (\fromposx,\drawy) -- (\toposx,\j-3);
            };
        };
    };
};
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐