文本数字化难题
图像天然是数字,文字却是离散符号,它该如何变成神经网络能接受的输入?
神经网络只接受数字
神经网络的每一层,本质上是一连串的乘法与加法:把输入的数字与权重相乘、求和, 再送进激活函数。因此无论要处理什么数据,第一个前提都不变,那就是 输入必须是数字 。
处理图像时,这个前提天然成立。一张图片本身就是由像素亮度组成的网格, 每个像素是一个 到 的整数; 把它整理成张量,就能直接参与矩阵运算、直接做反向传播,中间不需要任何额外转换。
可是换成文字,问题立刻出现。一句中文,比如 我爱自然语言处理 , 在程序里只是一串字符(character),没有任何可以相乘、相加的数值。 如果直接把这串字符送进一个线性层,程序会因为「拿到的不是数字」而直接报错, 根本无法开始计算。
「数字化」不是随便编号
最直接的想法是:给每个字编一个号,比如「我」是 1、「爱」是 2…… 把句子变成一串整数,不就成了数字吗?这条路是对的起点,但远远不够。 一个能真正服务于神经网络的文本数字化方案,要同时满足两个要求:
- 切得合适 :先要决定「以什么为单位」来切分文本, 是按字、按词,还是别的粒度?切得太碎或太粗,后面都不好处理。
- 编得有意义 :最终给到神经网络的数字表示, 最好能体现出「词与词之间的关系」,意思相近的词,数字表示也应该相近。 单纯按出现顺序编号做不到这一点(编号 1 和 2 相邻,并不代表这两个词意思相近)。
这两个要求,把文本数字化拆成了一条清晰的流水线。
三步流水线
走完这三步,一段文字就变成了一串「带语义的数字向量」,可以送进神经网络。 前两步解决「切分与编号」,第三步解决「让数字携带语义」。
什么是 token
流水线的第一个关键词是 token 。token 指的是 文本被切分后的最小处理单元 。一个 token 可以是一个词、一个字、 一个标点符号,也可以是后面会讲到的「子词」(一个词的片段)。
一旦确定了切分方式,任何一段文本,在计算机眼里就不再是「一长串字符」, 而是 「一串 token 组成的序列」 。后续所有处理(编号、转成向量、 送进网络)都以 token 为单位进行。
切分的第一个难点:中文词与词之间没有天然分隔
既然要把文本切成 token,那「在哪里切」就成了第一个要回答的问题。 对英文来说,这件事看起来很轻松:单词之间天然有空格, 按空格(再处理一下标点)就能把句子切成一个个单词。
中文却没有这种便利。 我爱自然语言处理 这串字之间没有任何分隔符,词与词的边界藏在语义里,需要靠算法去判断, 到底该切成「我 / 爱 / 自然 / 语言 / 处理」,还是「我 / 爱 / 自然语言 / 处理」, 甚至「我 / 爱 / 自然语言处理」?不同的切法都说得通。 对中文这类没有空格的语言,切分本身就是一个需要专门解决的问题。
同一句话,两种切分粒度
除了「在哪切」,还有「切多细」的选择。以下是对同一句中文, 分别 按词 和 按字 切分得到的两种 token 序列:
按词切,序列短、每个 token 含义更完整,但「词」的种类极多; 按字切,序列长、token 种类少,但单个字的语义更弱。 粒度的选择会带来一连串后果,这正是切分要仔细权衡的地方。
下面这个小工具可以亲手试一试:输入任意一句话,在「按字 / 按词」之间切换, 观察切出来的 token 序列怎么变。
接下来的问题
到这里,文本数字化的整体路线已经清楚了: 先把文本切成 token,再给 token 编号,最后把编号变成带语义的向量 。 但每一步都还藏着具体的麻烦:
- 最朴素的分词到底怎么做?怎么把一串 token 整理成一张可查的「词表」?
- 按词切「词太多」、按字切「字太碎」,有没有折中的切分粒度?
- 就算给每个词都编了号,整数 5 和 8 之间的「大小」也并不代表词义, 怎么才能让数字真正携带语义?
这些问题会被逐一拆开。先从最基础的一步开始:怎样把文本切成 token, 并为它们建立一张词表。