zoukankan      html  css  js  c++  java
  • Torch-RNN运行过程中的坑 [2](Lua的string sub函数,读取中文失败,乱码?)

    0、踩坑背景

    仍然是torch-rnn/LanguageModel.lua文件中的一些问题,仍然是这个狗血的LM:encode_string函数:

    function LM:encode_string(s)
      local encoded = torch.LongTensor(#s)
      for i = 1, #s do
        local token = s:sub(i, i)
        local idx = self.token_to_idx[token]
        assert(idx ~= nil, 'Got invalid idx')
        encoded[i] = idx
      end
      return encoded
    end

    上篇文章Torch-RNN运行过程中的坑 [1](读取Lua非空table,size为0)就在这部分测试table是不是空,填了个坑。

    在填坑过程中,发现for...in pair table打印出的字典是没有问题的,但是上述encode_string函数的token竟然是乱码,每次从字典table中命中不了,每次返回的都是nil。

    查了console编码,是没有问题的(UTF-8)。这是咋回事哩?

    1、Lua中的string sub函数切分中文

    我们捋一下整个工程,

    第一步使用preprocess.py对文本文件转为json和hdf5文件,值得一提的是,json中使用Unicode存储字典文件;

    第二步train.lua中对json和hdf5文件进行训练;

    第三步sample.lua中对checkpoint和start_text进行抽样。

    由于是对start_text进行character拆分时乱码,我们肯定首先对encode_string函数中的s:sub(i, i)函数进行debug。

    这个sub函数首先在lua console中看看它到底返回的是啥:

    > s1='a1b2c3'
    
    > print(s1:sub(1,1))
    a  --lua中从1开始计数,和java substring和python slice不同的是,start/end index它都包含
    > print(s1:sub(1,2))
    a1
    > print(s1:sub(3,6))
    b2c3
    
    > print(s1:sub(3,7))
    b2c3  --lua还是比较智能,越界也没有报错,比java更脚本化一些
    > print(s1:sub(0,7))
    a1b2c3 
    
    > print(s1:sub(-1,7))
    3 --脚本语言都有的负数下标

    大概有个了解,就是java中的substring嘛,有些细节不一样。真的是这样吗?见下:

    > s2='1a新华'
    > print(s2:sub(1,2))
    1a  --前俩字母,正常
    > print(s2:sub(1,3))
    1a  --WTF?这就尴尬了,我的汉字呢
    > print(s2:sub(1,4))
    1a  --???
    > print(s2:sub(1,5))
    1a新  --???一个“新”字占了3个字节
    > print(s2:sub(3,5))
    新
    > print(s2:sub(3,6))
    新
    > print(s2:sub(3,7))
    新
    > print(s2:sub(3,8))
    新华  --???“华”字也是占了三个字节
    > print(s2:sub(3,9))
    新华
    > print(string.len(s2))
    8  --这。。。正常length 4的是8?

    这“1a新华”四个字的length Lua给我输出个8,让我这个java选手情何以堪。。。。

    好奇害死猫,尝试在python console下,也输出的是8,看来此处必有蹊跷。嗯。

    2、各种字符编码的区别与联系?

    一个汉字占了3个字符,学过计组的同学醒醒了,当年老师敲黑板的重点就是这个。。。

    这里首先抛出ruanyf十年前写的一篇的浅显易懂的文章 《字符编码笔记:ASCII,Unicode和UTF-8》。太通俗易懂了,我这里简要搬运下。。。

    总结一下考点:

    首先,ASCII码是1byte,8bit,最多可以表示2^8=256种字符,表示英文字符绰绰有余。

    但是,以中文为代表的各国优秀博大精深语言(文中说汉字有10W+),渐渐走进了国际化道路,当年定制ASCII码的同志们发现1byte根本表示不了这么多字符。

    于是,我国1980年推出了简体中文GB2312(1995年推出的GBK的前身),两个字节byte表示一个汉字。

    再后来,1990年,各国都开发了各自的编码,这时候一个伟大的联盟组织、非营利机构统一码联盟承担了Unicode(万国码、国际码、统一码、单一码)的标准制定,该机构致力于让Unicode方案替换既有的字符编码方案。每个国家的字符都有对应的编码区间,终于实现了同一个世界,同一套编码。。。

    但是Unicode还是有些小瑕疵,没有大量推广使用,这时候UTF-8左右Unicode的一种实现方式,闪亮登场。

    文中还有一些大小端、BIG-5、UTF-8 BOM方面的考点,感兴趣的同学可以点链接。

    3、有点跑题,咱们的问题怎么解决,切分中文string乱码?

    如果单纯的string全都是中文,那还好办了,直接三个三个切分就成,如果混合字符串呢?例如“1a新华”这样的。

    这时候就要对编码区间进行判断了。

    function LM:encode_string(s)
      local encoded = torch.LongTensor(#s)
      local i = 1
      -- cause 中文在UTF8中存储占位3个字节,而英文仍然是1个字节[ascii]
      for token in s.gmatch(s, "[%z1-127194-244][128-191]*") do
        local idx = self.token_to_idx[token]
        assert(idx ~= nil, 'Got invalid idx')
        encoded[i] = idx
        i = i+1
      end
      return encoded
    end

    按照博文 lua 含中文的字符串处理--分离字符、计算字符数、截取指定长度 中对regex的解释是:

    1. --[[  
    2.     UTF8的编码规则:  
    3.     1. 字符的第一个字节范围: 0x00—0x7F(0-127),或者 0xC2—0xF4(194-244); UTF8 是兼容 ascii 的,所以 0~127 就和 ascii 完全一致  
    4.     2. 0xC0, 0xC1,0xF5—0xFF(192, 193 和 245-255)不会出现在UTF8编码中   
    5.     3. 0x80—0xBF(128-191)只会出现在第二个及随后的编码中(针对多字节编码,如汉字)   
    6.     ]] 

    这样就不会出现问题了~

  • 相关阅读:
    C#检查数组是否重复——HashSet
    C#动态生成控件,并添加事件处理,获取控件值并保存
    .net接口交互
    SQL Server 表建Trigger
    SQL Server 表建Trigger
    SQL语句修改not in 变为not exists
    奋战杭电ACM(DAY11)1017
    奋战杭电ACM(DAY11)1016
    奋战杭电ACM(DAY10)1015
    奋战杭电ACM(DAY9)1014
  • 原文地址:https://www.cnblogs.com/zklidd/p/6106774.html
Copyright © 2011-2022 走看看