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.     ]] 

    这样就不会出现问题了~

  • 相关阅读:
    使用 Spring data redis 结合 Spring cache 缓存数据配置
    Spring Web Flow 笔记
    Linux 定时实行一次任务命令
    css js 优化工具
    arch Failed to load module "intel"
    go 冒泡排序
    go (break goto continue)
    VirtualBox,Kernel driver not installed (rc=-1908)
    go运算符
    go iota
  • 原文地址:https://www.cnblogs.com/zklidd/p/6106774.html
Copyright © 2011-2022 走看看