LaTeX 排版 02:数学公式、交叉引用与参考文献

写技术笔记时,公式最难维护的部分往往不是某个希腊字母,而是:推导换行以后等号能否对齐,插入一个公式以后正文引用会不会过期,参考文献增加以后编号能否自动更新。本篇用一个最小二乘问题,把这些工作连成一套可以实际练习的写作方法。

学习前先完成文档结构与中文编译。完整路线、项目入口和编译环境见学习指南,后续的图表内容见图片、表格与长文档。所有内容集中在独立的 LaTeX 分类。

配套材料:下载完整练习工程 · 查看编译后的 PDF。工程将导言区、数学章节、图表章节和 references.bib分开保存;本文的短代码用于逐项理解,完整工程用于一起练习。

一、先固定符号约定和宏包

本文约定小写斜体表示标量,粗斜体表示向量或矩阵,正体下标表示文字说明。它是一套便于练习的约定;写论文时应优先遵守所用模板和学科规范。符号一旦选定,就不要在同一篇文章里让 x、\mathbf{x}、\bm{x}轮流表示同一个向量。

导言区先加入:

\usepackage{amsmath,amssymb,bm}
\usepackage{amsthm}

\DeclareMathOperator{\rank}{rank}
\DeclareMathOperator*{\argmin}{arg\,min}
\newcommand{\vect}[1]{\bm{#1}}
\newcommand{\R}{\mathbb{R}}

amsmath提供公式环境、文本嵌入和运算符声明;amssymb扩展数学符号;bm提供数学对象的加粗命令,包含希腊字母;amsthm处理定理和证明。它们各有职责,不需要为了“公式更专业”再一次性加载十几个宏包。AMS 数学宏包说明、bm 宏包说明和amsthm 宏包说明可以用来查各自边界。

自己定义 \vect 有一个实际好处:以后需要把向量改为带箭头的样式,可以集中修改定义。宏的名字应表达含义;不建议为了少敲几个字符,把 \section 改成难以理解的单字母命令。

这里使用传统数学字体配置。若以后采用 unicode-math 和 OpenType 数学字体,应重新确认粗体命令和符号配置,不要原样叠加两套字体方案。

二、先区分行内、行间和编号

短表达式是句子的一部分,例如“当 n>mn>m 时继续检查矩阵的秩”。较长的目标函数应独立成行。下面是同一个概念在 .tex 文件中的两种写法:

设 \(\vect{x}\in\R^n\),\(\vect{A}\in\R^{m\times n}\),
观测向量为 \(\vect{b}\in\R^m\)。
考虑目标函数
\[
J(\vect{x})=\frac{1}{2}
\lVert\vect{A}\vect{x}-\vect{b}\rVert_2^2.
\]

显示效果:

J(x)=12∥Ax−b∥22.J(\boldsymbol{x})=\frac{1}{2}\lVert\boldsymbol{A}\boldsymbol{x}-\boldsymbol{b}\rVert_2^2.

注意范数下标里的 2 表示二范数,右上角的 2 表示平方,两者不可互相替代。\frac{1}{2} 是排版命令,也不会自动检查目标函数是否符合你要描述的模型。

需要在下文引用时,改用带编号的环境:

\begin{equation}
J(\vect{x})=\frac{1}{2}
\lVert\vect{A}\vect{x}-\vect{b}\rVert_2^2.
\label{eq:least-squares}
\end{equation}

目标函数见式~\eqref{eq:least-squares}。

\eqref自动带括号;\ref只输出编号。~是不换行空格,用来避免“式”和编号分到两行。标签名表示内容,例如 eq:least-squares,不要写成 eq:3,因为第三个公式随时可能变成第四个。

要表达的结构 使用方式 编号行为
句子里的短表达式 \(...\),也可使用 $...$ 不编号
独立的一条公式 \[...\] 或 equation* 不编号
一条可引用的公式 equation 一个编号
多条需要对齐的公式 align 默认每行一个编号
一个公式内部的多行结构 equation 包住 aligned 整体一个编号
分段定义 数学环境内使用 cases 由外层决定

在 .tex 文档中不要以 $$...$$作为行间公式的常规写法,使用 LaTeX 环境更容易与编号、间距和宏包配合。本网站的 Markdown 数学渲染器使用美元分隔符,网页写法和 .tex 源文件写法应分别理解;复制上方标明 latex 的代码即可。

三、对齐是在描述结构

多条公式:align

设矩阵与向量都是实数,且维度与上一节一致,则最小二乘目标函数的梯度和 Hessian 为:

\begin{align}
\nabla J(\vect{x})
&= \vect{A}^{\mathsf{T}}(\vect{A}\vect{x}-\vect{b})
\label{eq:gradient} \\
\nabla^2 J(\vect{x})
&= \vect{A}^{\mathsf{T}}\vect{A}.
\label{eq:hessian}
\end{align}

每个 &标记对齐位置,这里放在等号前;\\结束当前行。不要用若干空格把等号推到一起,数学模式里的普通空格不会承担这种布局职责。align已经是行间数学环境,外面不要再包一层 \[...\] 或 equation。

如果中间行不需要编号,在该行加 \notag;如果整组都不需要编号,使用 align*。准备引用的行必须保留编号,并把 \label放在该行中、换行命令之前。amsmath 使用手册的 displayed equations 部分说明了这些环境的差别。

一条推导分成多行:equation 加 aligned

\begin{equation}
\begin{aligned}
J(\vect{x})
&= \frac{1}{2}(\vect{A}\vect{x}-\vect{b})^{\mathsf{T}}
(\vect{A}\vect{x}-\vect{b}) \\
&= \frac{1}{2}\vect{x}^{\mathsf{T}}
\vect{A}^{\mathsf{T}}\vect{A}\vect{x}
- \vect{b}^{\mathsf{T}}\vect{A}\vect{x}
+ \frac{1}{2}\vect{b}^{\mathsf{T}}\vect{b}.
\end{aligned}
\label{eq:expanded-loss}
\end{equation}

外层表示一个带编号的公式,内层负责两个等号的对齐。aligned本身不负责开启数学模式;它也可以放在 \[...\]中,得到不编号的多行结构。长公式最好在等号、加号或乘积结构边界处拆开,不要先缩小字体,也不要把整页文字宽度改大来容纳一个局部问题。

分段函数:cases

下面的 Huber 损失中,阈值 δ>0\delta>0。两段在边界处的值应相等,这个数学条件需要作者自己确认。

\begin{equation}
\rho_\delta(r)=
\begin{cases}
\dfrac{1}{2}r^2, & \lvert r\rvert\leq\delta, \\
\delta\bigl(\lvert r\rvert-\dfrac{1}{2}\delta\bigr),
& \lvert r\rvert>\delta.
\end{cases}
\label{eq:huber}
\end{equation}

cases中的 &分开“取值”和“条件”,\\分开各个分支。这里只用 \dfrac让分数容易阅读;在行内表达式里强行使用大分数,可能撑高行距。

ρδ(r)={12r2,∣r∣≤δ,δ(∣r∣−12δ),∣r∣>δ.\rho_\delta(r)=\begin{cases} \frac{1}{2}r^2,&\lvert r\rvert\leq\delta,\\ \delta(\lvert r\rvert-\frac{1}{2}\delta),&\lvert r\rvert>\delta. \end{cases}

四、字形要告诉读者“这是什么”

数学模式默认把字母当作变量。直接写 sin(x) 会把 s、i、n当成连续变量;应写 \sin x。同样,直接写 rank(A) 不如定义并使用 \rank(\vect{A})。

目的 示例 含义
已有函数 \sin\theta、\log x 使用定义好的运算符及其间距
临时运算符 \operatorname{diag}(1,2,3) 正体运算符名称
重复使用的运算符 \DeclareMathOperator{\rank}{rank} 在导言区统一声明
带下方约束的运算符 \DeclareMathOperator*{\argmin}{arg\,min} 星号允许行间样式把约束放在下方
表示说明的下标 x_{\mathrm{ref}} ref是一项说明,不是三个变量的乘积
公式内的短语 \text{subject to} 按文本处理,保留单词之间的空格
粗体希腊符号 \bm{\theta} 表示约定中的向量参数

\mathrm适合简短的正体数学标记,不适合拿来排一整句自然语言;说明语句使用 \text。运算符需要的不只是正体字形,还有左右间距,因此不要把 \mathrm{rank}当作所有运算符声明的替代品。

量值和单位也要分清。例如 \(t=2\,\mathrm{s}\)中,t是变量,s是秒,\,提供一个小间隔。大量含单位的数据请统一使用 siunitx,对应写法与表格对齐见图表篇。微分记号可按本文约定写为 \mathrm{d}t;不同出版规范可能采用不同字形,要先统一约定再应用。

五、矩阵和定理也有结构

另一种常见向量约定是箭头记号:\vec{v} 适合单字母,\overrightarrow{AB} 适合从点 A 到点 B 的方向。它们与本篇的粗体约定都是排版选择;一份文档中选定一种约定后保持一致。可以修改 \vect 的定义来统一切换,而不用逐条改公式。

矩阵与维度检查

\[
\vect{A}=\begin{bmatrix}
1 & 0 \\
1 & 1 \\
1 & 2
\end{bmatrix},\qquad
\vect{x}=\begin{bmatrix}a\\b\end{bmatrix},\qquad
\vect{b}=\begin{bmatrix}1\\2\\2\end{bmatrix}.
\]

bmatrix自带方括号,pmatrix自带圆括号;不带括号时可用 matrix。矩阵里 &分列,\\分行。上述矩阵是三行两列,参数向量是两行一列,所以乘积是三行一列,能与观测向量比较。

LaTeX 不会替你检查矩阵乘法是否合法。即使把参数向量多加一个元素,它也可能正常生成 PDF;因此每次引入矩阵,先在正文说明维度,再检查乘积、转置和逆矩阵是否存在。需要括号自动伸缩时可用成对的 \left、\right,但不要跨 align 的换行符配对;分行时分别选择合适大小的括号更清楚。

定理与证明

在加载 amsthm之后声明环境;如果还使用下一节的 cleveref,把环境声明放在该宏包加载之后:

% 导言区
\newtheorem{theorem}{定理}[section]

% 正文
\begin{theorem}[正定二次型]\label{thm:positive-quadratic}
设实对称矩阵 \(\vect{Q}\) 正定,则
\(f(\vect{x})=\vect{x}^{\mathsf{T}}\vect{Q}\vect{x}\)
的唯一全局最小点为 \(\vect{x}=\vect{0}\)。
\end{theorem}

\begin{proof}
由正定性,任何非零 \(\vect{x}\) 都满足
\(\vect{x}^{\mathsf{T}}\vect{Q}\vect{x}>0\),
而 \(f(\vect{0})=0\),因此结论成立。
\end{proof}

[section]让定理编号与节关联;proof提供证明结构和结束标记。如果把“正定”改成“半正定”,唯一性结论就不再普遍成立,排版系统仍然不会报错。这正是“数学正确”和“排版正确”需要分开验收的原因。

六、交叉引用与超链接

在本文使用的宏包组合中,先加载数学、图表和文献宏包,再加载 hyperref,最后加载 cleveref。hyperref使引用可点击,cleveref按被引用对象的类型添加名称。后者对加载顺序有要求,不能把“hyperref 最后加载”当成没有例外的口诀。见 hyperref 文档和 cleveref 手册的宏包交互说明。

\usepackage[hidelinks]{hyperref}
\usepackage[nameinlink,noabbrev]{cleveref}
\crefname{equation}{式}{式}
\Crefname{equation}{式}{式}
\crefname{figure}{图}{图}
\crefname{table}{表}{表}

% 若上一节已经声明 theorem,将原声明移到这里,只保留一次。
\newtheorem{theorem}{定理}[section]
\crefname{theorem}{定理}{定理}
\Crefname{theorem}{定理}{定理}

正文可写 \cref{eq:gradient,eq:hessian},把名称和多个编号交给宏包处理。中文名称需要像上面这样明确配置;不能假设英文默认设置会自动变成中文。不要在 \cref之前再手写一个“式”,否则可能重复。

把 \label放在产生目标编号的位置:章节的 \section之后,图表的 \caption之后,公式环境或相应公式行内部。不要把图的标签放在 \caption之前,再指望它一定得到该图编号。

交叉引用至少经历“记录标签”和“读取标签”两个阶段,因此首次编译出现 ??并不罕见。但完整构建结束后仍然存在 ??,就应检查标签拼写、重复标签、位置和构建日志,不能把问号当成正常结果交付。也不要手写“见公式 (7)”或用 \tag{7}掩盖引用问题;\tag用于确实需要指定特殊标记的场景,不承担自动编号管理。

七、参考文献:只建立一条完整链路

本文选定 BibLaTeX + Biber:.bib保存文献数据,BibLaTeX 控制引用和文献表样式,Biber 处理数据。这样可以把“引用哪篇文献”和“使用什么编号样式”分开管理。BibLaTeX 官方说明介绍了这种职责划分。

1. 建立 references.bib

新建 UTF-8 文件 references.bib:

@book{knuth1984texbook,
author = {Knuth, Donald E.},
title = {The {TeXbook}},
date = {1984},
publisher = {Addison-Wesley},
location = {Reading, Massachusetts},
isbn = {0-201-13448-9}
}

条目中的书名、年份、出版社和 ISBN 对应 Knuth 本人的书目信息。参考文献练习也要使用能追溯的真实条目,不要随手编造 DOI 或页码。

knuth1984texbook是引用键,不会因为书在文献表里排第几而变化。作者用“姓, 名”便于解析;多名作者用 and分隔,不要用逗号连接整个作者列表。书名中内层的 {TeXbook}保护这一部分的大小写。代码围栏标为 bibtex只是语法高亮名称,不代表本例要运行 BibTeX 程序。

2. 写一份最小 main.tex

下面的完整例子可单独用于排查文献环境,不依赖本篇其它数学代码:

\documentclass[UTF8,fontset=fandol]{ctexart}
\usepackage{csquotes}
\usepackage[backend=biber,style=numeric,sorting=none]{biblatex}
\addbibresource{references.bib}
\usepackage[hidelinks]{hyperref}

\begin{document}
\section{阅读记录}
了解 TeX 的排版模型可以从其作者的著作入手
\cite{knuth1984texbook}。

\printbibliography[title={参考文献}]
\end{document}

numeric采用数字引用,sorting=none让文献表按引用顺序排列。默认只列出实际引用的条目;把一百条资料放入 .bib,并不意味着它们都应该出现在文末。\nocite{*}可以列出全部条目,但练习时不应靠它掩盖正文漏引。字段和命令的详细定义见 BibLaTeX 用户手册。

这套配置不要再混入旧模板的 \bibliographystyle、\bibliography或另一个 natbib加载语句。以后遇到期刊强制的旧模板,应按该模板单独建立构建方案,而不是把两套链路拼到同一份文档里。

3. 让 latexmk 驱动构建

在同时包含 main.tex与 references.bib的目录执行:

latexmk -xelatex -interaction=nonstopmode -halt-on-error main.tex

latexmk会根据依赖和辅助文件判断何时调用 XeLaTeX、何时运行 Biber,以及是否需要再次编译。它需要环境中已经提供相应程序,不会替你凭空安装缺失的 Biber。自动构建的职责见 latexmk 文档。

为理解中间过程,可以在这个简单例子中手动执行:

xelatex -interaction=nonstopmode -halt-on-error main.tex
biber main
xelatex -interaction=nonstopmode -halt-on-error main.tex
xelatex -interaction=nonstopmode -halt-on-error main.tex

第一步让文档写出 Biber 所需的 .bcf等信息;Biber 读取文献数据库并生成 .bbl;后续编译读入结果,继续稳定引用编号。这里的 biber main对应文档任务名,参数不是 references.bib。平时用 latexmk判断是否完成,不必机械地背诵“永远编译四次”。

本仓库提供统一的 Docker 练习环境。在仓库根目录执行:

./bin/site.sh latex-build
./bin/site.sh latex-test

构建项目、输出位置及已有目录保护规则见学习指南。可以传入新的输出目录保留一次独立结果,例如 ./bin/site.sh latex-test build/latex-practice-02。这些命令用于本仓库的完整练习项目;上面的两文件例子用于理解最小依赖。

八、从现象定位问题

现象 先检查什么 为什么
Missing $ inserted 数学命令是否落在正文,正文下划线是否转义 TeX 发现需要数学模式的内容
Misplaced alignment tab character & &是否写在对齐环境之外 普通正文中的符号应写 \&
Undefined control sequence 宏包是否加载、命令是否拼错 后续报错可能只是第一次错误的连锁结果
公式越过右边界 看日志中的 Overfull \hbox,拆分长表达式 成功生成 PDF 不等于布局合格
公式编号显示 ?? 标签名、标签位置、重复标签、是否完成重编译 引用数据还未生成或没有正确读取
引文显示引用键或未定义引用 .bib路径和引用键、Biber 是否执行 文献处理链路尚未闭合
Please (re)run Biber 查看 .blg、确认使用 Biber 后端 只反复执行 XeLaTeX 不能替代文献处理
Biber 报数据版本不兼容 BibLaTeX 与 Biber 是否来自兼容的发行环境 混用系统包和手工更新可能造成版本不匹配
文献表为空 有无 \cite和 \printbibliography 导入数据库与输出文献表是不同操作

排查时先看 .log中的第一条实质错误,再看 Biber 的 .blg。只对自己能够解释的辅助文件执行清理;不要靠删除全部文件碰运气,更不要误删 .tex、.bib和原始图片。若更改过后端或大幅调整构建配置,可在保留源文件的前提下清理派生文件,再做完整构建。

九、做三次会暴露问题的练习

练习 A:让编号自动改变。 在 eq:gradient之前插入一条新的带编号公式,再构建。检查正文引用是否自动更新、PDF 链接是否跳到正确位置。验收时要同时检查编号文本和点击目标;只看编号“像是对的”还不够。

练习 B:让数学条件发生变化。 把定理中的正定改为半正定,构造 diag(1,0)的例子。确认 LaTeX 仍会编译,但最小点不再唯一。然后修正定理表述。把这次练习记录为“排版检查通过,数学检查未通过”,养成分开判断的习惯。

练习 C:故意破坏一条引用。 将正文的 knuth1984texbook改成不存在的键,完整构建并寻找 warning;记录 PDF 与日志的表现,然后恢复。再添加一条来自真实出版社或作者主页的文献,先核对元数据,后添加引用。验收标准是没有未定义引用、文献表信息可追溯、正文顺序改变后编号仍正确。

完成后应当能回答:为什么 aligned需要外层数学环境?为什么 \operatorname与 \mathrm不能随意互换?为什么插入公式不该手改十处数字?为什么 .bib已经存在,仍然可能没有参考文献?把答案连同你编译出的 PDF 留在自己的练习记录里,再继续图表与长文档篇。

知识共享许可协议