在泰国文档理解算法研究中关于泰文字符的探索和insights

140 阅读7分钟

最近着手开发一个基于多模态大模型的泰国文档关键信息提取算法,在前期进行数据分析和tokenization实验的过程中遇到一些比较有趣的问题,这是在做中文或者英文相关任务中是没有遇到过的。

众所周知,像中文字符或者英文字母,无论笔画有多少、无论构成多么复杂,一个字符其长度就是1,但是泰文不一样,主要跟泰语的书写系统和字符的组合方式有关,所以记录一下。

事情的起因是这样的,当我在对比模型预测结果跟真实标注结果时(比如泰文中姓名或者地址),发现两个字符串一模一样,可是却又不相等,所以尝试一个个字符对比将不同的字符和索引打印出来,通过索引获取的字符好像原串中又看不到,所以引起极大的探索欲望。 Image

比如下面这个例子,a和b看上去很像吧,但是却不相同,所以将不同的字符和所在索引打印出来,然后取得这个字符。

Image

这是结果:

Image

上面出现的那个字符很奇怪,我的a和b中没有啊,只是某个字符的组成部分,然后再通过索引和分片方式打印出来看看:

Image

网上搜了才知道,泰文(Thai)与英语等拉丁字母语言不同,它是一种音节型语言,由多个组件构成单个字符或音节,具体来说,泰语是一种合成型(或称为字母组合型)语言,这意味着一些字符在视觉上是由多个符号或符号的组合构成的。

泰文的一个可见字符可能由多个 Unicode 码点(code points)组成,这就导致 len() 计算的长度可能大于 1,在 Python 中,len() 计算的是 Unicode 码点的数量,而泰文的一个可见字符可能由多个码点组成,泰文的元音和声调符号在 Unicode 中属于组合字符,它们不会单独占据一个字母位置,但在Python的 len() 计算中,每个 Unicode 码点都算一个单位。

Image

例如:

len("ก") → 1(单个辅音)
len("กิ") → 2(辅音 ก + 元音 ิ)
len("กิ่") → 3(辅音 ก + 元音 ิ + 声调符号 ่)

所以进一步了解了泰文的书写系统和字符的组合方式。
泰语由 44 个辅音字母 和 15 个元音符号 组成,但元音在拼写时可以分布在辅音的上、下、左、右。因此,需要包括:

  • 泰文辅音(เช่น ก, ข, ค, ง)
  • 泰文元音(เช่น า, ิ, ึ, ื, ะ, เ, โ)
  • 声调符号(่, ้, ๊, ๋)
  • 独立元音(อ)
  • 其他特殊字符(ๆ, ฯ, ี, ำ)

完整的常用泰文字符集可以参考 Unicode 范围:

  • 辅音:U+0E01 - U+0E2E
  • 元音 & 变音符号:U+0E30 - U+0E3A
  • 声调符号:U+0E48 - U+0E4E
  • 泰文数字:U+0E50 - U+0E59(如果需要识别泰文数字)
  1. 基本字母和附加符号
    在泰文中,基础字母(例如:ม)可以与附加符号(如声调符号、元音符号、复合辅音)结合,形成一个复杂字符或音节。例如,ม ("m") 和附加的元音符号或声调符号(如 ู 和 ่)组合后仍然是同一个音节:มู่ 由两个符号组成:(字母 "m")和 ู่(由两部分组成的元音符号和声调符号)
  2. 元音符号
    泰语的元音通常不是独立字符,而是与辅音字符结合来表达音节。 元音可以出现在辅音字符的前面、后面、上方或下方,甚至嵌在字符内部。 例如:ม ("m") 字母可以与不同的元音符号(如 ู、ิ、ื 等)组合来形成不同的音节。
  3. 复合辅音
    有时一个音节由多个辅音组合而成,这些辅音可以是并列的或结合在一起发音。 例如,泰语有一些复合辅音(例如: ทร),它是由两个字母并组成的。
  4. 声调符号
    泰语是一个有声调的语言,不同的声调会改变单词的意思。声调通常通过附加符号表示,这些符号可以出现在基础字符的上方或下方。一个泰语字符可能需要与这些附加的声调符号结合,才能表达正确的发音和含义。

示例:มู่ ("mū")  是由以下部分构成的:

  • ม:一个泰文辅音字母,代表 /m/ 音。
  • ู่:由两部分组成的元音符号和声调符号,表示元音的发音和调调。

综上: 泰语是一个以音节为单位的语言,每个音节可以由辅音字母、元音符号和声调符号的组合构成,因此会出现一个字符由多个部分组成的现象。

所以,在构造泰文字符集时,辅音、元音、声调符号是必须的,另外,空格、标点、数字、英文字符可以根据实际需要添加。
这是一个泰文字符集:

thai_chars = [    "ก""ข""ฃ""ค""ฅ""ฆ""ง""จ""ฉ""ช""ซ""ฌ""ญ""ฎ""ฏ""ฐ""ฑ""ฒ""ณ",    "ด""ต""ถ""ท""ธ""น""บ""ป""ผ""ฝ""พ""ฟ""ภ""ม""ย""ร""ล""ว""ศ",    "ษ""ส""ห""ฬ""อ""ฮ""ะ""ั""า""ำ""ิ""ี""ึ""ื""ุ""ู""เ""แ""โ""ใ",    "ไ""ๅ""ๆ""็""่""้""๊""๋""์""ํ""ฺ""ฯ""๐""๑""๒""๓""๔""๕""๖""๗",     "๘""๙"]
# 构造字符到索引的映射
char2idx = {char: idx for idx, char in enumerate(thai_chars)}
idx2char = {idx: char for char, idx in char2idx.items()}
# 可以英文字母和一些特殊字符,根据实际需要即可
thai_extended_chars = thai_chars + list("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ .,!?")
char2idx_extended = {char: idx for idx, char in numerate(thai_extended_chars)}

搞清楚了泰文的构成方法,还需要处理的问题是已有的tokenizer虽然号称支持100多种语言,但是某些泰文字符还是<unk>,也就是在训练集中没有,出现未知字符的问题,所以需要换一个能够处理泰文的tokenizer,这里试了四个:

  • google/byt5-small
  • google/mt5-base
  • sambanovasystems/SambaLingo-Thai-Base
  • airesearch/wangchanberta-base-att-spm-uncased

各有优劣吧。在测试字符串上面试了一下,这个测试字符串包括数字、英文字母大小写、泰文字符和一些特殊字符,基本能够覆盖文档中的字符集。

Image

下面是分别进行的测试,注意不仅要能encode,还要能够decode回去。

1、google/byt5-small
这是Google开发的一种基于字符的 SentencePiece 模型,字符级 SentencePiece。

Image

Image

2、google/mt5-base
Google 的 mT5 训练了一个 支持100+种语言(包括泰文和英文)的 SentencePiece Tokenizer。 Image Image

3、sambanovasystems/SambaLingo-Thai-Base
SambaLingo-Thai-Base 是由 SambaNova Systems 开发的双语(泰语和英语)预训练模型。该模型通过在 Cultura-X 数据集的泰语部分进行训练,处理了 380 亿个标记(tokens),将 Llama-2-7b 模型适配于泰语。SambaLingo-Thai-Base 可用于多种任务,包括文本生成、翻译和对话。 Image Image

4、airesearch/wangchanberta-base-att-spm-uncased
WangchanBERTa 是由泰国 VISTEC-depa 人工智能研究所开发的泰语预训练语言模型,基于 RoBERTa 架构。其中,wangchanberta-base-att-spm-uncased 是该模型的主要版本,专为泰语自然语言处理任务设计。该模型在一个包含 78.5GB 未压缩文本的多样化泰语语料库上进行预训练,语料来源包括社交媒体帖子、新闻文章和其他公开可用的数据集。

使用 SentencePiece 子词分词器,词汇量为 25,000 个子词单元,适应泰语中词与词之间通常不以空格分隔的特点。由于泰语本身不区分大小写,模型在训练时未考虑大小写差异。 Image Image

看到了吧,token--> string 和 id --> token --> string 的结果不一样,因为这个tokenizer不支持大写字母,会变成unk,这从tokens = tokenizer.tokenize(s)看不出来,在将id转换为token的时候就看出来了,也就是tokenizer.convert_ids_to_tokens(ids)

所以得小心不是。