暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Globalization Design: 语言识别

万力王 2020-05-16
430
在国际化的路途坎坷,语言是其中非常大的一个挑战,尤其想把海量的内容多语言化。在这篇,我们来讨论下如何识别文本语言来满足常见的一些需求。其实很简单。

一、字符和字符集

我们平时看到的所有文本类的信息,都是由一个个字符组成的。而计算机存储的字符,本质上就是一个个数字,我们可以称其为“编码”,比如这么一串字符:
"Graviton is a bad guy in S.H.I.E.L.D. Season 5."
这句话中出现的大写字母“S”用16进制数字0x53存储;小写字母s用16进制数字0x73存储;数字“5”用0x35存储;标点号“.”用0x2E存储。所以,我们其实预先“约定”好了,让每个字符都能有一个数字代号,这不难实现。但问题来了,“约定”并不是法律,不是全世界所有人都可以或愿意遵守,那就会出现同一个数字,在不同的地方使用,代表的符号不一样,这也就是为什么我们常常会遇到一个文本文件,有时候打开会出现几个看不懂的乱码的原因。
所有,当我们处理一个文档的时候,需要事先知道这个文档是按照何种“约定”来编码存储的,就好比一本字典,我们称之为“字符集”。最常见的就是ASCII(American Standard Code for Information Interchange,美国信息互换标准代码)字符集,一共有128个字符,包括了0到9的数字,大小写各26个英语字母,还有一些常用的标点符号,涵盖了我们键盘上的大多数字符。
对于使用英语的过来来说,单一个基于ASCII字符集的组合就够用了。但像汉语这样,常用汉字有3000多个,还有近10万个非常用汉字,所以就还有GB2312字符集(6763个汉字),GBK字符集(21003个汉字,包括中日韩汉字),BIG5字符集(台湾地区繁体中文标准字符集,13053个汉字),GB18030字符集(27484汉字,包括中日韩汉字)。这里需要注意,日本和韩国还是会有用到汉字。
光汉字就那么多,更不用说全世界其他地方的语言,那有没有一个涵盖所有字符的字符集呢?答案当然是有的,就是Unicode(国际标准字符集),这里涵盖了世界上所有的字符编码。通过它,我们可以做最基本的语言识别。通过这个地址:http://www.unicode.org/charts/,可以查询到具体每个字符的编码,建议收藏。

二、通过字符编码范围反查语言

我们研究unicode编码会发现,每个语种都相对集中在一个范围内,比如基础拉丁字符(http://www.unicode.org/charts/PDF/U0000.pdf),范围是\u0000~\u007F,

泰语字符(http://www.unicode.org/charts/PDF/U0E00.pdf),范围是\u0E00~\u0E7F。

于是我们可以通过这种形式来反查,某个字符是什么语言。

我们以目前最常见的场景来讨论实践方法:如何从英文站点中找到出现的汉字?我们做了下面这几件事:
  1. 埋点:前端展示的字符内容都会按照事先约定的规范格式记录,并传输存储到HDFS上。最基本的字段包括语言站点(比如en-US),以及内容类型(比如name),内容文本(比如Graviton Weng)。

  2. 查询:正则表达式可以很简单地匹配Unicode字符范围,我曾经在之前的这篇文章《利用HDFS生态做复杂离线数据处理》中提到过。

  3. 报表:把找到的有问题的记录做成T+1报表,同步给相关运营同学。

  4. 修复:运营同学按照报表内容修复。

  5. 监控:我们聚合每天类似问题出现的数量,并观察趋势。

通过上面的几步,我们可以把这个问题的处理形成闭环。

我们扩展下上面的第2点。如下面的代码:
    select * 
    from one_table
    where (name regexp 
    '[\u0E00-\u0E7F\u30A0-\u30FF\u3040-\u309F\uAC00-\uD7AF\u4E00-\u9FFF]') 
    and locale like 'en-%';
    上面这段查询的结果需要满足:
    1. 英语站点

    2. name字段包含泰语字符(\u0E00-\u0E7F)
      或者日语片假名(Katakana)字符(\u30A0-\u30FF)
      或者日语平假名(Hiragana)字符(\u3040-\u309F)
      或者韩语字符(\uAC00-\uD7AF)
      或者所有汉语字符(\u4E00-\u9FFF)

    综合起来,意思就是:查找在所有英语站点下,名字含有泰语、日语字符、韩语字符或者汉语字符的所有记录。是不是很简单。

    三、混合多种语言的输入验证

    作为后台录入系统,我们有时候需要限定用户在输入框中只能输入某种特定的语言。对于这种要求,按照上面的思路,其实等同于判断一段话是否在某几段事先指定的编码范围内。比如,我们要求一段话只能输入英文,那我们只需要用javascript做这样的验证,比如下面这段代码:
      patt = [^\u0000-\u007F]/;
      rsl = !patt.test("My name is Graviton!");
      console.log("content validated:", rsl);
      rsl = !patt.test("My name is Graviton 翁!");
      console.log("content validated:", rsl);

      再高端点,我们可以直接告诉用户那个字符不是英语。

        function validate(content){
        patt = /[^\u0000-\u007F]/;    
        rsl = patt.exec(content);
        if(rsl != null) {
                console.log("Content validation failed, ", rsl[0], "is not English!");
        } else {
        console.log("Content validation passed!");
        }
        }
        validate("My name is Graviton Weng!");
        validate("My name is Graviton 翁!");

        是不是超级简单。

        接下来泼点冷水,假如有个输入框限定输入日语呢?我们刚才有提到,日语里也是会用到汉字的,比如有一家酒店的名字叫“東横INN福岡天神”,这里没有标志性的平假名和片假名,却有汉字和英文字符,像这种场景,由于文字上的混用,技术上,我们就没有办法严格禁止输入这样的文字,只能允许或者给一定提示。

        下面这张图是另外例子,如果日语站下显示下面的这个内容,并不是一个错误,因为它本身就是这么用的。所以绝对限定后台的输入,并不是一个通用彻底的从根源上解决多语言问题的解决方案。当然,从产品设计层面上,我们也可以有所取舍(比如日语站上放弃下面类型的信息输出),从而可以解决主要问题(比如保证要有日文)。


        四、基于分组统计来推定某种语言的概率


        上一段有提到,各种语言的字符会混合在一起,那我们其实可以统计出每种语言字符的占比,从而给出一个的各语言占比分布,给后续处理提供概率上的建议。比如下面这段话:

        浦安の舞浜にあるヒルトン 東京ベイは、東京湾まで歩いてすぐ、東京ディズニーランド®まで車で 3 分です。

        我们可以统计出,汉字有 14个,英文(包括数字和空格)4个,平假名和片假名一个有30个,其他特殊符号5个。所以我们可以得到这么一个分布数据:纯日文字符占比57%,纯汉字占比26%,英文(包括数字和空格)占比8%,于是我们可以推测出,这段话是日文的概率有83%(中文字符也可能用在日文里),中文的概率有26%(纯日文字符不会用在中文里),最终我们可以推定这段话是日文。

        我们可以用下面这段稍微复杂点的hsql在hive中计算得到这样的结果。

          with dx as (
            // 替换成存储多语言内容的表或者字段
          select "浦安の舞浜にあるヒルトン 東京ベイは、東京湾まで歩いてすぐ、東京ディズニーランド®まで車で 3 分です。" as content
          )
          select 
            // 内容字段
          content
            // 每种语言的占比,合成一个用逗号分隔的字符串
          , concat_ws(",", collect_set(concat_ws(":", lang, ratio))) as ratio_list
          from (
          select
          content
          , lang
          , round(total/(sum(total) over (partition by content)),2) as ratio
          from (
          select
          content
          , lang
          , count(1) as total
          from (
          select
                      content
          , tf.c
          , case
          when tf.c regexp '[\u30A0-\u30FF\u3040-\u309F]' then 'jp'
          when tf.c regexp '[\u4E00-\u9FFF]' then 'cn'
          when tf.c regexp '[\u0000-\u007F]' then 'en'
          when tf.c regexp '[\u0E00-\u0E7F]' then 'tai'
          when tf.c regexp '[\uAC00-\uD7AF]' then 'kr'
          else 'other'
          end as lang
          from dx
          lateral view explode(split(dx.content,"")) tf as c
          ) lx
          group by content, lang
          ) gx
          ) fx
          group by content
          输出结果为(特殊标点符号被归为other):
            浦安の......3 分です。en:0.08,other:0.09,jp:0.57,cn:0.26


            五、第三方提供的语言识别API


            上面讨论的其实都是围绕字符集展开的方法,相对比较简单,但是需要了解某种语言对应的字符集范围,在不熟悉某种语言的情况下容易产生遗漏,而且多种语言混合的场景判断比较复杂。

            所以,如果家里有矿(因为要收费),推荐可以使用Google Cloud API的Translation API中的detect_language()方法,这里是python版的文档地址https://googleapis.dev/python/translation/latest/index.html:

            https://googleapis.dev/python/translation/latest/client.html#google.cloud.translate_v2.client.Client.detect_language


            综上,我们讨论完了语言识别的基本方法,这些简单的解决方案,相信能够解决大家绝大多数的问题。如有相关特殊问题,欢迎来私信留言。

            文章转载自万力王,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

            评论