写在前面的后记

Beamer 这东西真不是人用的,尤其是需要进行图文排版的情况下特别难用。 所以不是要进行大量公式编辑就不要用这货了。

一些准备

我自己使用的是pdflatex,默认是无法使用中文、日文等文字的,因此需要做一些小改动。最简单的方式是使用CJK包,CJK三个字母分别代表中日韩三国文字。 首先在前言部分加入\usepackage{CJKutf8},接着把原本的

\begin{document}
...
\end{document}

改为

\begin{document}
\begin{CJK}{UTF8}{gkai}
...
\end{CJK}
\end{document}

下面是平常可能用到的字体族的名称列表,作为参考:

  • ipxm: IPAex 明朝 [日本語・明朝体]
  • ipxg: IPAex ゴシック [日本語・ゴシック体]
  • gbsn: AR PL SungtiL GB [简体中文,宋体]
  • gkai: AR PL KaitiM GB [简体中文,楷书]
  • bsmi: AR PL SungtiL GB [繁体中文,明朝体]
  • bkai: AR PL KaitiM GB [繁体中文,楷书]

Beamer

制作beamer文档和制作其他LaTeX文档的流程是一样的,只不过文件类型需要指定为beamer

\documentclass{beamer}

这之后可以选择主题及配色,\usetheme改变主题,\usecolortheme改变配色。 接着和平常一样,在前言(preamble)中加入标题×作者×日期等:

\title{开始使用beamer}
\author{我}
\date{\totay}

之后就可以开始写正文了:

\begin{document}

\maketitle

\begin{frame}{第一张幻灯片}
  Hello, world!
\end{frame}
\end{document}

接着编译就能得到pdf文件啦。这里frame相当于一张幻灯片,在frame环境中写入的内容会出现在幻灯片中。 你应该会发现有两张幻灯片出现,第一张是由\maketitle自动生成的。在beamerbasetitle.sty中,\maketitle的定义是

\def\maketitle{\[email protected]\titlepage\else\frame{\titlepage}\fi}

这一段很容易理解,就是说如果当前位置不在frame环境中的话那就帮你套上一个frame环境。这也是为了和LaTeX的语句兼容。

制作目录

和普通的LaTeX文档一样,制作目录的过程也差不多。 现在在第二张幻灯片前加上\section{}

\section{开始}

再往它下方添加下面的代码:

\begin{frame}{}
  \tableofcontents
\end{frame}

这样就生成了包含文件中所有section名字的目录幻灯片。

丰富幻灯片内容

第三张幻灯片现在还基本是空的,现在往里面添加一点内容。 frame环境中可以使用熟悉的itemizeenumerate,类似的还有description。 此外,beamer还默认内置了类似amsmath定理环境的theoremlemmaproofcorollaryexample等环境。 想要强调文字的时候可以使用\emph,不过beamer提供了更好的\alert,会把文字显示成红色,这样看起来更加注目。 例如可以把第三张幻灯片进行如下的修改:

\begin{frame}
  \frametitle{什么是质数?}
  \begin{definition}
    除了1和该数自身外,无法被其他自然数整除的数叫做质数。
    \begin{example}
      \begin{itemize}
        \item 2是质数。
        \item 3也是质数。
        \item 4不是质数。
      \end{itemize}
    \end{example}
  \end{definition}
\end{frame}

使用块

beamer中有种叫block的环境可以把文本框起来。使用方法即在block环境中添加内容:

\begin{block}
  这是block的内容。
\end{block}

制作动画

在PPT中有个很好用的功能叫动画,在beamer中,这个功能叫overlay,中文的意思叫覆盖。 为了方便,我还是把它叫做动画。 最简单的动画就是\pause,稍微改动一下上面的代码:

\begin{itemize}
\item 2是质数。
  \pause
\item 3也是质数。
  \pause
\item 4不是质数。
\end{itemize}

这样会先显示第一个条目,然后再显示第二个,最后显示第三个。 pdf文件中可以看到原来的一张幻灯片现在变成了三张,连起来就是想要表现的动画。

制作更高级的动画

虽然\pause简单好用,但是只能通过它做出按顺序表示的动画。 为了表现更加高级的动画,需要使用beameroverlay specification功能。 在之前代码后面添加一张新的幻灯片,内容如下:

\begin{frame}
  \frametitle{不存在最大质数}
  \begin{definition}
    不存在最大的质数。
    \begin{theorem}
      \begin{proof}
        \begin{enumerate}
          \item<1-> 设$p$为最大的质数。
          \item<2-> 令$q$为$p$以及之前的所有质数的乘积。
          \item<3-> 则$q+1$不会被$p$以及之前的任何一个质数整除。
          \item<1-> 即$q+1$一定能被大于$p$的质数整除,与假设矛盾。
        \end{enumerate}
      \end{proof}
      \uncover<4->{使用\alert{反证法}证明。}
    \end{theorem}
  \end{definition}
\end{frame}

其中<#->这种形式的东西就是overlay specification。 比如<2->代表的意思就是“从第二张幻灯片开始呈现这个元素”。 \uncoverbeamer的命令,在对文本添加overlay specification时使用。 此外,还可以用<1>表示只在第一张幻灯片显示,<2-4>表示在第二到第四张幻灯片中显示等。 其他的例子还有<-3><1-3,5,7>等等。

\onslide包围的文字或块在出现和消失时都占用同样的空间,使用方法和\pause类似。

\begin{frame}
  从第一张幻灯片开始出现
  \onslide<2-3>
  在第2,3幻灯片中出现
  \begin{itemize}
    \item 同样在第2,3幻灯片中出现
    \onslide+<4->
    \item 在第四张幻灯片之后出现
  \end{itemize}
  \onslide
  在所有幻灯片上出现
\end{frame}

注意到\onslide可以使用修饰器+*\onslide+相当于\visible\onslide*相当于\only,没有修饰器的\onslide相当于\uncover\only{}包含的内容在消失时完全不占空间,就像不存在一样。反而\uncover{}包含的内容在消失和出现的时候占用同一空间。 就像名字一样,uncover先是通过遮挡来藏住东西,然后到了指定幻灯片把遮挡移除,所以如果通过\setbeamercovered{transparent=数值}(数值可以为0~100)的话,可以透过遮挡看到藏住的内容。 而visible就完全不同了,它不是拿一块布遮住内容,而是直接让内容隐身,直到指定的幻灯片才让内容显示出来。

TikZ

使用beamer有很多便利的地方,尤其是书写公式,不过同时也会带来一些麻烦,尤其是绘图这些PPT中常用的功能都没有了,做出来的效果总是非常死板,全是一条一条的目录。 这时候就需要使用TikZ包了,借助它可以使幻灯片更加美观易读。而且beamer本身在制作的时候就使用了TikZ的功能,因此他们之间的关系可以说非常密切了。

首先在前言中导入TikZ包,同时一并使用\usetikzlibrary命令载入positioning库,这个库主要是用来定位节点,节点在后面会提到。

\usepackage{tikz}
\usetikzlibrary{positioning}

接下来在希望绘图的位置声明tikzpicture环境:

\begin{tikzpicture}
\end{tikzpicture}

之后只要在其中填充内容就好了。

制作树形图

首先从简单的树形图开始。在PPT中有文本框的概念,TikZ中的节点\node与之类似。首先配置一个树形图的根节点:

\begin{tikzpicture}
  \node{root};
\end{tikzpicture}

注意TikZ的行末需要添加分号,和C语言一样。 接下来,只需要修改节点的一些配置就可以用矩形包裹这个节点:

\node[rectangle,fill=cyan!10, text width=1.5cm, text centered, rounded corners, minimum height=1cm]{root};

其中只有fill=cyan!10有点难以从字面上理解,这里的cyan!10指使用10%的青色以及90%的白色来填充矩形内部。 如果要绘制多个节点的话,每次都要写这么长当然是对不重复自己原则的亵渎。因此TikZ提供了一个别名系统,使用\tikzset命令即可给冗长的配置起一个别名,重复采用就能变得很简单:

\tikzset{rect/.style={rectangle,fill=cyan!10,text width=1.5cm, text centered, rounded corners, minimum height=1cm}};
\node[rect]{root};

在这里定义了名为rect的配置集合,因此之后的节点只需要调用这个别名即可。

注意:与\tikzset功能类似的\tikzstyle已经不再推荐使用。

现在的树形图还只有一个根节点,使用child语句可以制作现有节点的子节点:

\node[rect]{root}
  child{node[rect]{child1}}
  child{node[rect]{child2}}
  child{node[rect]{child3}};

这样子节点就会被自动生成了,不过rect样式导致节点重叠在一起,给根节点再加一点配置调整节点间的距离:

\node[rect]{root}[level distance=1.5cm, sibling distance=2cm, edge from parent/.style={<-,draw}]
  child{node[rect]{child1}}
  child{node[rect]{child2}}
  child{node[rect]{child3}};

其中level distance是父子节点之间的层间距离,sibling distance是兄弟节点之间的层内距离。最后的edge from parent是从父节点连向子节点的线,采用了<-样式(终点指向起点的箭头)进行绘制(draw)。 到这里,简单的树形图就完成了。当然还可以继续给子节点添加更深层的子节点:

child{node[rect]{child2}
  child{node[rect]{child2-1}}
}

制作有向图

接着来制作一下有向图。 有向图中的节点一般都是圆,定义一个节点:

\begin{tikzpicture}
  \node[circle, fill=cyan, text=white]{1};
\end{tikzpicture}

接下来要添加更多的节点了,不过如果只是一味增加节点的话它们都会被放到同一个位置,为了避免这种情况必须要设置节点之间的位置关系。比如第二个节点在第一个节点右上方等等:

\begin{tikzpicture}
  \node[circle, fill=cyan, text=white](1){1};
  \node[above right=of 1](2){2};
\end{tikzpicture}

注意在\node命令后面添加了()用以命名节点。接着通过above right=of 1表现节点1的右上方。 这时候又发现了一个问题,新的节点没有设置样式。 不过之前已经知道了\tikzset,直接起别名?当然是可以的,不过因为这里的节点全都用一样的样式,直接给所有节点设置一个默认样式会更加方便,同时再补充一些新的节点:

\begin{tikzpicture}[every node/.style={circle, fill=cyan, text=white}]
  \node(1){1};
  \node[above right=of 1](2){2};
  \node[below right=of 1](3){3};
  \node[right=1.5cm of 2](4){4};
  \node[right=1.5cm of 3](5){5};
  \node[below right=of 4](6){6};
\end{tikzpicture}

right=1.5cm of 2表示节点2的右侧1.5cm处。这样所有的节点就画完了。 不过还需要把节点连起来。连线用\draw就可以绘制,比如连接节点1,2只需要写:

\draw[->](1)--(2)

其中->是从起点到终点的箭头,如果是无向图的话不需要这个选项。(1)--(2)表示两个节点间的通道。 那么接下来就是一个个全部连接起来咯?当然是可以的,不过太麻烦了,这种任务用\foreach就能一步到位。

\foreach \u / \v in {1/2,1/3,2/3,2/4,3/4,3/5,4/5,4/6,5/6}
  \draw[->](\u)--(\v);

其中,\u\v为循环变量,类似于Python里面的for u,v in [(1,2), (1,3)...]

为公式添加标记说明

有时候在一个公式中,不同项有不同的含义,比如深度学习中的损失函数可能包含二乘误差、交叉熵、正则化项等等成分,经常会需要标记出每一个部分并添加一些描述性的文字。 比如对$f(\mathbf{w}, b)=\sum_{i=1}^{k}(y_i - \mathbf{w}^\top\mathbf{x}_i - b)^2 + \frac{\lambda}{2} | \mathbf{w} |^2$添加说明:

\begin{align*}
  f(\mathbf{w}, b) =
  \tikz[baseline=(1.base)]{
    \node(1)[rectangle, fill=cyan!10, rounded corners]{$\displaystyle \frac{\lambda}{2} \| \mathbf{w} \|^2$} node [blue, below of=1]{正则化项};
  }
\end{align*}

这里只呈现了正则化项。baseline=(1.base)将绘制节点1的基准线与锚点(中心)对齐,这样可以避免一些莫名其妙的位置偏移。 每次写这么多太麻烦,可以用\newcommand创建宏来处理。

\newcommand{\highlight}[2][cyan]{\tikz[baseline=(0.base)]{\node[rectangle,rounded corners,fill=#1!10](0){#2};}}
\newcommand{\highlightcap}[3][cyan]{\tikz[baseline=(0.base)]{\node[rectangle,rounded corners,fill=#1!10](0){#2} node[below of=0, color=#1]{#3};}}

第一个命令用于添加矩形块强调内容,第二个命令多了描述选项,可以在矩形块下方添加描述。[cyan]表示缺省值,如果不指定颜色则使用青色。 利用这些宏,可以写出:

\begin{align*}
  f(\mathbf{w}, b) =
  \highlightcap[red]{$\displaystyle \sum_{i=1}^{k}(y_i - \mathbf{w}^\top\mathbf{x}_i - b)^2$}{经验误差}
  +
  \highlightcap[blue]{$\displaystyle \frac{\lambda}{2} \| \mathbf{w} \|^2$}{正则化项}
\end{align*}

注意在\highlightcap内部需要把公式用$包围起来。

制作气泡

和上面的矩形块不同,气泡更加自由,可以加在任何位置。 在这之前,导入一个叫做remember picture的包,它使得tikzpicture环境间的所有节点名称可以共享。 使用pdflatex的话不需要显式载入包,不过也可以为了保险载入:

\usepackage{pxpgfmark}

气泡包含在shapes库里面,因此需要使用该库:

\usetikzlibrary{shapes.callouts}

首先随便写一个东西,在此基础上添加气泡作为描述:

\tikz[remember picture]{\node(equ){$1+1=2$};}

因为气泡需要参照该节点的名字,所以一定要起节点名,如果出现奇怪的偏移的话,可以在\tikz的选项中添加baseline=(equ.base)。 接着来绘制气泡本身:

\begin{tikzpicture}[remember picture, overlay]
  \node[rectangle callout, fill=red!50, text=white, rounded corners, callout absolute pointer={(equ.north)}, above=0.5cm of equ]{正确};
\end{tikzpicture}

overlay表示TikZ环境在重叠其他要素上呈现。rectangle callout是矩形气泡。callout absolute pointer指定气泡指向的节点坐标,这里使用的是equ.north,即equ节点的上端。其他 的位置设定可以查看TikZ手册的PositioningCoordinate System章节。

使用坐标绘图

除了通过指定节点的名称间接引用坐标外,还可以通过绝对坐标进行绘图。 比如二维直角坐标系中,原点可以表示成(0,0)。此外还可以使用极坐标表示法,比如$\rho=1$,$\theta=45^\circ$可以用(30:1)表示。 之前已经提到了路径\path命令,大致上把它当作曲线或者直线就可以,不过实际TikZ中的路径并不只是这样,比如可以将闭合路径内部涂上某种颜色,也可以用来剪裁别的图形。 使用坐标绘制路径:

\path (0,0) -- (0,1) -- (1,0) -- (1,1);

有中断也没关系:

\path (0,0) -- (0,1) (1,0) -- (1,1);

不过只是定义了路径还不能把它显示出来,需要加上draw选项才能真正绘制:

\path[draw] (0,0) -- (0,1) -- (1,0) -- (1,1);

也可以使用缩略的\draw代替\path[draw]。还能采用各种各样的选项来定制图形:

\draw[->, red, line width=2pt] (0,0) -- (0,1) -- (1,0) -- (1,1);

除了使用坐标来绘制路径之外,也可以将其用在节点上:

\node at (0,0) {content} (nodename);

注意(0,0)是坐标的缺省值。

在路径中间添加节点也是可以的,\node命令实际上是\path node的简写:

\draw[->](0,0) -- node[above]{to} (2,0);

这里的例子是在(0,0)(2,0)的连线之上添加文字说明to。通常添加的节点会放在路径的中间,但是由于添加了above选项因此文字会被放置在上方。 当路径有倾角的时候如果想让文字说明的话需要采用sloped选项。

\draw[->](0,0) -- node[sloped, above]{to} (3,3);

除了above之外还可以使用leftright等表示一些位置关系。

上面提到过路径还能用来绘制闭合曲线并且填充颜色,这里也举一个例子:

\draw[fill=cyan, draw=none] (1,0) rectangle (2,1);

(0,0)(1,1)分别是矩形左上和右下两点,fill=cyan表示填充色,draw=none表示不要绘制边框。当然在不需要绘制路径的时候可以不用\draw而是直接使用\path

\path[fill=cyan] (1,0) rectangle (2,1);

甚至还能简化为

\fill[cyan] (1,0) rectangle (2,1);

除了明确写出两个绝对坐标来绘制矩形,也可以使用长宽作为相对值来绘制矩形:

\fill[cyan] (1,0) rectangle ++(1,1);