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, "[%z\1-\127\194-\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.     ]] 

這樣就不會出現問題了~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM