编写 TensorFlow 文档

我们欢迎社区为 TensorFlow 文档做贡献。本文介绍了如何为该文档做贡献。具体来说,本文介绍了以下内容:

  • 该文档所在的位置。
  • 如何进行符合规范的修改。
  • 在提交对文档所做的更改之前,如何编译和测试更改。

您可以在 https://www.tensorflow.org 上查看 TensorFlow 文档,并且可以在 GitHub 上查看和修改原始文件。我们会将文档发布到 GitHub 上,以便每个人都可以做贡献。所有经核检后添加到 tensorflow/docs_src 的内容都会很快在 https://www.tensorflow.org 上发布。

我们完全允许以不同格式重新发布 TensorFlow 文档,但我们的文档库中可能无法接受其他文档格式(或生成它们的工具)。如果您一定要选择以其他格式重新发布我们的文档,请务必包括以下内容:

  • 文档代表的 API 版本(例如,r1.0、master 等)
  • 生成文档时所基于的 commit 或版本
  • 在哪里可以获得最新文档(即 https://www.tensorflow.org)
  • Apache 2.0 许可。

版本备注

tensorflow.org(位于根目录下)会显示最新的稳定二进制分支的文档。如果您使用 pip 安装 TensorFlow,则应阅读该文档。

不过,大多数开发者都是为 master GitHub 分支贡献文档,我们会不定期在 tensorflow.org/versions/master 上发布此类分支。

如果您希望文档更改显示在根目录下,那么您还需要对当前的稳定二进制分支(和/或 cherrypick)贡献相应更改。

参考文档与非参考文档

以下参考文档是根据代码中的注释自动生成的:

  • C++ API 参考文档
  • Java API 参考文档
  • Python API 参考文档

要修改参考文档,请修改相应的代码注释。

非参考文档(例如,TensorFlow 安装指南)是人工编写的。该文档位于 tensorflow/docs_src 目录中。docs_src 的每个子目录中都包含一组相关的 TensorFlow 文档。例如,TensorFlow 安装指南都是位于 docs_src/install 目录中。

C++ 文档是根据通过 doxygen 生成的 XML 文件生成的;不过,目前在开放源代码中无法使用这些工具。

Markdown

可修改的 TensorFlow 文档是使用 Markdown 编写的。除少数例外情况,TensorFlow 均遵循标准 Markdown 规则

本部分介绍了标准 Markdown 规则与可修改的 TensorFlow 文档使用的 Markdown 规则之间的主要区别。

Markdown 中的数学内容

修改 Markdown 文件时,您可以在 TensorFlow 中使用 MathJax,但要注意以下两点:

编写 MathJax 时,您可以使用 $$ 以及 \\(\\) 将数学内容包围起来。$$ 保护字符会导致换行,所以在文字中,请使用 \\( \\)

链接分为以下几种:

  • 链接到同一文件的不同部分
  • 链接到 tensorflow.org 之外的网址
  • 从一个 Markdown 文件(或代码注释)链接到 tensorflow.org 中的另一个文件

对于前两种链接,您可以使用标准的 Markdown 链接,但要将整个链接放在一行中,而不要分开放到多行中。例如:

  • [text](link) # Good link
  • [text]\n(link) # Bad link
  • [text](\nlink) # Bad link

对于最后一种链接(链接到 tensorflow.org 中的另一个文件),请使用特殊的链接参数化机制。利用该机制,作者能够在不破坏链接的情况下移动和重新组织文件。

参数化方案如下所述:

  • 使用 @{tf.symbol} 为 Python 符号创建一个指向参考页面的链接。请注意,类成员不会获得自己的页面,但该句法规则仍然适用,因为 @{tf.MyClass.method} 会链接到 tf.MyClass 页面的相应部分。

  • 使用 @{tensorflow::symbol} 为 C++ 符号创建一个指向参考页面的链接。

  • 使用 @{$doc_page} 创建一个指向另一个(不是 API 参考)文档页面的链接。要链接到

    • red/green/blue/index.md,请使用 @{$blue}@{$green/blue}

    • foo/bar/baz.md,请使用 @{$baz}@{$bar/baz}

    建议您使用较短的一个,以便我们能够在不破坏这些参考的情况下移动页面。主要的例外情况是,参考 Python API 指南时可能应使用 @{$python/},以避免歧义。

  • 使用 @{$doc_page#anchor-tag$link-text} 链接到相应文档中的锚,并使用不同的链接文字(默认情况下,链接文字是目标页面的标题)。

    如果要仅替换链接文字,可以省略 #anchor-tag

要链接到源代码,请使用以 https://www.github.com/tensorflow/tensorflow/blob/r1.8/ 开头的链接,后接从 github 根目录开始的文件名。例如,对于您正在阅读的文件,链接应写为 https://www.github.com/tensorflow/tensorflow/blob/r1.8/tensorflow/docs_src/community/documentation.md

这种网址命名方案有助于确保 tensorflow.org 可以将链接定向到与您正在查看的文档的版本对应的代码分支。请勿在源代码网址中包含网址参数。

在编译文档之前,您必须先执行以下操作来设置环境:

  1. 如果您的机器上未安装 Bazel,请立即安装。如果您使用的是 Linux,请使用以下命令安装 Bazel:

    $ sudo apt-get install bazel  # Linux
    

    如果您使用的是 Mac OS,可以在此页面上找到 Bazel 安装说明。

  2. 将目录更改为 TensorFlow 源代码的顶级 tensorflow 目录。

  3. 运行 configure 脚本,并根据您使用的系统回答提示。

    $ ./configure
    

然后,切换到包含 docs_src (cd tensorflow) 的 tensorflow 目录。运行以下命令,以便编译 TensorFlow 并在 /tmp/tfdocs 目录中生成文档:

bazel run tools/docs:generate -- \
          --src_dir="$(pwd)/docs_src/" \
          --output_dir=/tmp/tfdocs/

生成 Python API 文档

操作、类和效用函数是在 Python 模块(例如 image_ops.py)中定义的。Python 模块都会包含一个模块文档字符串。例如:

"""Image processing and decoding ops."""

文档生成器会将此模块文档字符串放置在为相应模块(本例中是 tf.image)生成的 Markdown 文件的开头。

过去要求在模块文件的开头列出模块的每个成员,并在每个成员之前放置 @@@@member_name 句法规则已被弃用,不再生成任何文档。不过,可能仍需要将模块内容的元素标记为公开,具体取决于模块是如何密封的。调用的操作、函数或类并非必须在同一个文件中定义。本文档中接下来的几个部分将会讨论模块密封以及如何向公共文档添加元素。

新的文档系统会自动将公共符号记入文档,但以下情况除外:

  • 专用符号,其名称以下划线开头。
  • 最初在 object 或 protobuf 的 Message 中定义的符号。
  • 一些类成员(如 __base____class__),这些成员是动态创建的,但通常没有实用的文档。

只有顶级模块(目前只有 tftfdbg)需要手动添加到 generate 脚本中。

密封模块

由于文档生成器会遍历所有可见符号,并会下溯到发现的任何内容,因此它会将所有意外暴露的符号记入文档。如果模块仅暴露了那些旨在成为公共 API 一部分的符号,我们将其称为密封的模块。由于 Python 的导入和可见性规范非常宽松,因此以不严谨的方式编写的 Python 代码将无意中暴露大量属于实现细节的模块。密封不当的模块可能会暴露其他未密封的模块,这通常会导致文档生成器失败。这种失败是预期的行为, 有助于确保我们的 API 具有规范的定义,并可让我们更改实现细节(包括哪些模块导入到哪里),而不必担心会意外中断用户。

如果模块被意外导入,通常会破坏文档生成器 (generate_test)。这是一个明确的信号,表明您需要密封模块。不过,即使文档生成器成功,文档中也可能会出现不应出现的符号。检查生成的文档,确保记入文档的所有符号都是应记录的。如果出现了不应出现的符号,您可以采用以下方式处理它们:

  • 专用符号和导入
  • remove_undocumented 过滤条件
  • 遍历黑名单。

我们将在下面详细讨论这些方式。

专用符号和导入

要符合 API 密封要求,最简单的方法是将非公共符号设为专用符号(通过在前面加下划线 _)。文档生成器认可专用符号。这也适用于模块。如果唯一的问题是有少量导入的模块出现在文档中(或破坏生成器),只需在导入时将其重命名即可,例如:import sys as _sys

由于 Python 会将所有文件视为模块,因此这也适用于文件。如果您有包含以下两个文件/模块的目录:

module/__init__.py
module/private_impl.py

那么,module 导入之后,将可以访问 module.private_impl。将 private_impl.py 重命名为 _private_impl.py 可以解决该问题。如果重命名模块不方便,请继续阅读下面的内容。

使用 remove_undocumented 过滤条件

另一种密封模块的方法是将实现从 API 中分离出来。要执行此操作,不妨使用 remove_undocumented,它会保留一系列允许的符号,并从模块中删除所有其他内容。例如,以下代码段展示了如何将 remove_undocumented 放入模块的 __init__.py 文件中:

init.py:

# Use * imports only if __all__ defined in some_file
from tensorflow.some_module.some_file import *

# Otherwise import symbols directly
from tensorflow.some_module.some_other_file import some_symbol

from tensorflow.python.util.all_util import remove_undocumented

_allowed_symbols = [‘some_symbol’, ‘some_other_symbol’]

remove_undocumented(__name__, allowed_exception_list=_allowed_symbols)

@@member_name 句法规则已被弃用,但它仍存在于文档中的某些地方,用于向 remove_undocumented 指明这些符号是公共符号。所有 @@ 最终都会被移除。不过,如果您看到它们,请不要随意将其删除,因为我们的一些系统仍然会使用它们。

遍历黑名单

如果所有其他方式都失败了,您可以将相应条目添加到 generate_lib.py. 内的遍历黑名单中。 这个名单中的几乎所有条目都滥用了其目的;请尽可能避免向其中增加条目!

遍历黑名单会将符合条件的模块名称(不包括前导的 tf.)映射到那些不会被下溯到的本地名称。例如,以下条目将导致在遍历时排除 some_module

{ ...
  ‘contrib.my_module’: [‘some_module’]
  ...
}

这意味着文档生成器将显示存在 some_module,但不会枚举其内容。

这个黑名单的初衷是确保为了实现平台抽象化而添加的系统模块(mock、标记、…)可以被记入文档,而其内部内容则不会被记入文档。对于 contrib,超出此目的使用这个黑名单或许是可接受的捷径,但对于核心 TensorFlow,则是不可接受的。

操作文档样式指南

模块的模块级文档(详细的描述性文档)应该放在 docs_src/api_guides/python 中的“API 指南”内。

对于类和操作,您最好提供以下信息(按出现顺序排列):

  • 简短说明相应操作做些什么。
  • 简短说明将参数传递给相应操作后会发生什么。
  • 展示相应操作如何运作的示例(最好提供伪代码)。
  • 要求、注意事项、重要备注(如果有的话)。
  • 说明操作构造函数的输入、输出、属性或其他参数。

下面对上述各项进行了更详细的说明。

请以 Markdown 格式书写您的内容。如需基本的句法规则参考,请点击此处。您可以在公式中使用 MathJax 符号(有关限制,请参见上文)。

编写有关代码的内容

在文字中使用以下内容时,请在其前后加上反引号:

  • 参数名称(例如,inputxtensor
  • 返回张量的名称(例如,outputidxout
  • 数据类型(例如,int32floatuint8
  • 文字中引用的其他操作名称(例如,list_diff()shuffle()
  • 类名称(例如,Tensor - 当实际上指的是 Tensor 对象时;如果您只是一般性说明操作会对张量、图或其他操作做些什么,则不要将首字母大写,也不要使用反引号)
  • 文件名(例如,image_ops.py/path-to-your-data/xml/example-name
  • 数学表达式或条件(例如,-1-input.dims() <= dim <= input.dims()

在示例代码和伪代码示例前后加上三个反引号。当您想要显示操作会返回什么时,请使用 ==>,而不要使用单个等号。例如:

```
# 'input' is a tensor of shape [2, 3, 5]
(tf.expand_dims(input, 0)) ==> [1, 2, 3, 5]
```

如果您要提供 Python 代码示例,请添加 Python 样式标签,以确保实现正确的句法规则突出显示方式:

```python
# some Python code
```

关于在 Markdown 中为代码示例使用反引号的两点注意事项:

  1. 如有必要,您可以将反引号用于 Python 以外的美观打印语言。如需完整的语言列表,请点击此处
  2. Markdown 还允许缩进四个空格,来指明是代码示例。不过,请不要在缩进四个空格的同时使用反引号。两者只能使用其一。

张量维度

当一般性地谈论张量时,请不要将 tensor 这个单词的首字母大写。当谈论作为参数提供给操作或由操作返回的具体对象时,则应将 Tensor 这个单词的首字母大写,并在其前后添加反引号,因为您讨论的是 Tensor 对象。

除非确实是在谈论 Tensors 对象,否则不要使用 Tensors 这个单词来说明多个 Tensor 对象。最好是说“Tensor 对象列表”。

使用术语“维度”来表示张量的大小。如果您需要具体说明张量的大小,请遵循以下规范:

  • 将标量称为“0-D 张量”
  • 将矢量称为“1-D 张量”
  • 将矩阵称为“2-D 张量”
  • 将三维或三维以上的张量称为 3-D 张量或 n-D 张量。只有在有意义的情况下才使用“秩”(rank) 这个单词,但请尽量使用“维度”代替。切勿使用“阶”(order) 这个单词来说明张量的大小。

使用“形状”这个单词详细说明张量的维度,并用带有反引号的方括号形式显示形状。例如:

If `input` is a 3-D tensor with shape `[3, 4, 3]`, this operation
returns a 3-D tensor with shape `[6, 8, 6]`.

使用 C++ 定义的操作

所有使用 C++ 定义(并且可以通过其他语言访问)的操作都必须在记入文档时包含 REGISTER_OP 声明。系统会处理 C++ 文件中的文档字符串,以便自动添加一些关于输入类型、输出类型、属性类型以及默认值方面的信息。

例如:

REGISTER_OP("PngDecode")
  .Input("contents: string")
  .Attr("channels: int = 0")
  .Output("image: uint8")
  .Doc(R"doc(
Decodes the contents of a PNG file into a uint8 tensor.

contents: PNG file contents.
channels: Number of color channels, or 0 to autodetect based on the input.
  Must be 0 for autodetect, 1 for grayscale, 3 for RGB, or 4 for RGBA.
  If the input has a different number of channels, it will be transformed
  accordingly.
image:= A 3-D uint8 tensor of shape `[height, width, channels]`.
  If `channels` is 0, the last dimension is determined
  from the png contents.
)doc");

这部分 Markdown 的结果:

### tf.image.png_decode(contents, channels=None, name=None) {#png_decode}

Decodes the contents of a PNG file into a uint8 tensor.

#### Args:

*  **contents**: A string Tensor. PNG file contents.
*  **channels**: An optional int. Defaults to 0.
   Number of color channels, or 0 to autodetect based on the input.
   Must be 0 for autodetect, 1 for grayscale, 3 for RGB, or 4 for RGBA.  If the
   input has a different number of channels, it will be transformed accordingly.
*  **name**: A name for the operation (optional).

#### Returns:
A 3-D uint8 tensor of shape `[height, width, channels]`.  If `channels` is
0, the last dimension is determined from the png contents.

许多参数说明都是自动添加的。具体来说,文档生成器会自动添加所有输入、属性和输出的名称与类型。在以上示例中,contents: A string Tensor. 是自动添加的。您应撰写一些额外的文字,以自然地衔接在这些说明之后。

对于输入和输出,您可以在撰写的额外文字之前添加一个等号,以防止自动添加名称和类型。在以上示例中,对于名为 image 的输出,其描述以 = 开头,以防止在文字 A 3-D uint8 Tensor... 之前添加 A uint8 Tensor. 您不能以这种方式防止添加属性的名称、类型和默认值,因此在撰写文字时请务必谨慎。

使用 Python 定义的操作

如果操作是在 python/ops/*.py 文件中定义的,那么您需要为所有参数和输出张量(返回的张量)提供文字。文档生成器不会自动为使用 Python 定义的操作生成任何文字,因此您写的是什么,得到的就是什么。

您应遵守通常的 Python 文档字符串规范,不过在文档字符串中您应使用 Markdown。

下面是一个简单的示例:

def foo(x, y, name="bar"):
  """Computes foo.

  Given two 1-D tensors `x` and `y`, this operation computes the foo.

  Example:

  ```
  # x is [1, 1]
  # y is [2, 2]
  tf.foo(x, y) ==> [3, 3]
  ```
  Args:
    x: A `Tensor` of type `int32`.
    y: A `Tensor` of type `int32`.
    name: A name for the operation (optional).

  Returns:
    A `Tensor` of type `int32` that is the foo of `x` and `y`.

  Raises:
    ValueError: If `x` or `y` are not of type `int32`.
  """

文档字符串部分的说明

这一部分详细介绍了文档字符串中的各个元素。

简短说明相应操作做些什么

例如:

Concatenates tensors.
Flips an image horizontally from left to right.
Computes the Levenshtein distance between two sequences.
Saves a list of tensors to a file.
Extracts a slice from a tensor.

简短说明将参数传递给相应操作后会发生什么

例如:

Given a tensor input of numerical type, this operation returns a tensor of
the same type and size with values reversed along dimension `seq_dim`. A
vector `seq_lengths` determines which elements are reversed for each index
within dimension 0 (usually the batch dimension).

This operation returns a tensor of type `dtype` and dimensions `shape`, with
all elements set to zero.

展示相应操作的示例

好的代码示例既要简短,又要易于理解,通常包含简短的代码段,以阐明示例所展示的内容。当操作用于操控张量的形状时,包含关于前后对比的示例通常会非常有用。

squeeze() 操作有一个非常好的伪代码示例:

# 't' is a tensor of shape [1, 2, 1, 3, 1, 1]
shape(squeeze(t)) ==> [2, 3]

tile() 操作提供了一个非常好的说明文字示例:

For example, tiling `[a, b, c, d]` by `[2]` produces `[a b c d a b c d]`.

使用 Python 展示代码示例通常很有帮助。切勿将它们放在 C++ 操作文件中,并避免将它们放在 Python 操作文档中。建议尽可能将代码示例放在 API 指南中。如果不行,请将它们添加到调用操作构造函数的模块文档字符串或类文档字符串中。

以下是 api_guides/python/math_ops.md 中的模块文档字符串内的一个示例:

## Segmentation

TensorFlow provides several operations that you can use to perform common
math computations on tensor segments.
...
In particular, a segmentation of a matrix tensor is a mapping of rows to
segments.

For example:

```python
c = tf.constant([[1,2,3,4], [-1,-2,-3,-4], [5,6,7,8]])
tf.segment_sum(c, tf.constant([0, 0, 1]))
  ==>  [[0 0 0 0]
        [5 6 7 8]]
```

要求、注意事项、重要备注

例如:

This operation requires that: `-1-input.dims() <= dim <= input.dims()`
Note: This tensor will produce an error if evaluated. Its value must
be fed using the `feed_dict` optional argument to `Session.run()`,
`Tensor.eval()`, or `Operation.run()`.

参数和输出张量(返回的张量)的说明。

说明要简明扼要。不必在参数部分说明相应操作是如何运作的。

注明操作是否对输入或输出张量的维度有严格限制。请注意,对于 C++ 操作,系统会自动添加张量的类型,即“一个…类型…张量“或”类型为 {…类型列表…} 之一的张量”。在这种情况下,如果操作对维度有限制,则可以添加“必须是 4-D”等文字,或者在说明之前添加 =(以阻止添加张量类型),并写一些类似于“4-D 浮动张量”的文字。

例如,下面列出了两种将 C++ 操作的图片参数记入文档的方式(注意“=”号):

image: Must be 4-D. The image to resize.
image:= A 4-D `float` tensor. The image to resize.

在文档中,这些将如下所示呈现给 Markdown

image: A `float` Tensor. Must be 4-D. The image to resize.
image: A 4-D `float` Tensor. The image to resize.

可选参数说明(“属性”)

文档生成器始终会说明每个属性的类型及其默认值(如果有的话)。这不能用等号来代替,因为在 C++ 和 Python 生成的文档中,说明有很大差别。

撰写任何额外的属性说明,使其自然地衔接在类型和默认值之后。会先显示类型和默认值,随后是额外的说明。因此,完整的句子是最好的。

以下是 image_ops.cc 中的一个例子:

REGISTER_OP("DecodePng")
    .Input("contents: string")
    .Attr("channels: int = 0")
    .Attr("dtype: {uint8, uint16} = DT_UINT8")
    .Output("image: dtype")
    .SetShapeFn(DecodeImageShapeFn)
    .Doc(R"doc(
Decode a PNG-encoded image to a uint8 or uint16 tensor.

The attr `channels` indicates the desired number of color channels for the
decoded image.

Accepted values are:

*   0: Use the number of channels in the PNG-encoded image.
*   1: output a grayscale image.
*   3: output an RGB image.
*   4: output an RGBA image.

If needed, the PNG-encoded image is transformed to match the requested
number of color channels.

contents: 0-D.  The PNG-encoded image.
channels: Number of color channels for the decoded image.
image: 3-D with shape `[height, width, channels]`.
)doc");

这会在 api_docs/python/tf/image/decode_png.md 中生成以下 Args 部分:

#### Args:

* **`contents`**: A `Tensor` of type `string`. 0-D.  The PNG-encoded
  image.
* **`channels`**: An optional `int`. Defaults to `0`. Number of color
  channels for the decoded image.
* **`dtype`**: An optional <a href="../api_docs/python/tf/DType"><code>tf.DType</code></a> from: `tf.uint8,
  tf.uint16`. Defaults to `tf.uint 8`.
* **`name`**: A name for the operation (optional).