rhel 8.2不识别unicode_Unicode的文本处理二三事
0x01. 前言
在日常工作的文本處理過程中,經(jīng)常會遇到一些利用unicode特性對文本進行處理的技巧。在這篇文章中則主要對其進行一些匯總和剖析。在開始之前,這里對 unicode 一些鮮為人知的概念做一些介紹。
大部分時候,我們都會只認為Unicode只是對字符的一個數(shù)字編碼,在Python內部,我們可以通過這樣的方式,查看一個文本的 unicode 編碼:
a = "母" a.encode("raw_unicode_escape") # b'u2e9f'但實際上,一個unicode除了其 codepoint 之外,還有很多特殊的屬性,而這些屬性在很多的NLP處理任務的過程中起到幫助的作用。如下圖:
unicode的其他屬性在這里,我推薦使用這個站點來查詢unicode的相關屬性。從上圖可以看出,一個unicode還具備以下常用的屬性:
- Name: 每個Unicode會有一個獨特的名字,后面我們會展示一個根據(jù)名字前綴來識別unicode屬于哪一種語言的技巧。
- Block: 一個連續(xù)的編碼范圍,具體可以參考:Wikipedia - Unicode block
- Plane: 具體可以參考:Wikipedia - Plane (Unicode)
- Script: 每個文字的書寫體系,具體可以參考:Wikipedia - Script(Unicode)。
- Category: 類別,待會會詳細介紹。
0x02. Unicode Range
我們都知道unicode利用一個數(shù)字來表示每個字符。而實際上,每個書寫語言(script)所涉及的文字,都有其獨特的unicode范圍。因此最直接的一個應用就是利用 unicode range 來判定一個字符 or 文本屬于哪一種語言。
在開始之前,我先推薦一個站點:Code Chars。這個站點按照不用的書寫語言和地域進行分類,列舉出每個語言的unicode range。如下圖綠框,其中中文script的名字叫做 Unihan。
Code Chars:不同語言的unicode-range在上面站點可以查詢到,漢字(Han scirpt)包含以下的block,而每個block的 block-range 可以表示為:
- CJK Unified Ideographs: U+4E00–U+9FEF
- CJK Unified Ideographs Extension A: U+3400–U+4DB5
- CJK Unified Ideographs Extension B: U+20000–U+2A6D6
- CJK Unified Ideographs Extension C: U+2A700–U+2B734
- CJK Unified Ideographs Extension D: U+2B740–U+2B81D
- CJK Unified Ideographs Extension E: U+2B820–U+2CEA1
- CJK Unified Ideographs Extension F: U+2CEB0–U+2EBE0
因此,我們可以根據(jù)上述的 unicode-range,開開心心的寫一個判定是否為漢字的正則表達式
HAN_SCRIPT_PAT = re.compile(r'[u4E00-u9FEFu3400-u4DB5u20000-u2A6D6u2A700-u2B734'r'u2B740-u2B81Du2D820-u2CEA1u2CEB0-u2EBE0]' )def is_chinese_char(c):return bool(HAN_SCRIPT_PAT.match(c))然而值得注意的是,這種方法并不算是一種很好的方式。因為不同文字的unicode范圍會有變化。如果只是一次性的搞一波,那也可以考慮一下。
0x03. Unicode 的其他屬性應用
在這一小節(jié),我們主要討論unicode的其他屬性以及 normalize 的問題,主要涉及 Python 中 unicodedata 和 regrex 兩個標準庫。
3.1 字符名字(Name)判斷:
在第一小節(jié)中我們提及到,每個unicode字符都有其獨特的名字。在Python中,我們可以通過這樣的方式來獲取某個unicode字符的名字:
import unicodedata text = "中" print(unicodedata.name(text)) # CJK UNIFIED IDEOGRAPH-4E2D進一步的,我們可以簡單來看下多個unicode的名字特點:從下表可以看到: 對于中文字符,其 Unicode 名字都是以 CJK 開頭; 對于印地語(天成文),其前綴也基本是以 DEVANAGARI 開頭; * 對于表情符號,其名字還包含了表情符號本身的文字描述。這額外的描述也可以在NLP任務過程中作為表情符號的特征進行補充,讓模型能夠更好的理解符號本身。
回到判定字符所屬的語言任務本身,利用Unicode-range判定法會存在范圍變化的問題。那么可以更改為利用名字判斷:
def is_chinese_char(c):return unicodedata.name(c).startswith("CJK")除了利用名字之外,更加規(guī)范的做法應該是直接判斷該unicode的Script屬性(漢字的Script屬于Han)。可惜 unicodedata 這個庫不支持。但是可以用 regrex 庫搞一波:
def is_chinese_char(c):return bool(regrex.match(r"p{script=han}", c))3.2 字符類別(Category)判斷:
在Unicode中,每個字符還會被賦予上Category的屬性,而這個屬性跟語種是無關的。總體而言,Category一共分為 Letter, Mark, Number, Punctuation, Symbol, Seperator, Other 七大類, 而每個類別下面還有進一步的二級分類。在 Python 中,我們可以利用 unicodedata.category 這個庫來獲取這個屬性;
import unicodedatarst = [] for char in "1a天。 ??":rst.append("{}:{}".format(char, unicodedata.category(char)))print(",".join(rst))# 1:Nd,a:Ll,天:Lo,。:Po, :So,?:So,?:Mn更詳細的,我們可以來看看所有Category的類型碼和對應信息類別:
二級Category列表,參考[1]一旦知曉了字符的類別,那么在文本處理過程中就有很多技巧可以應用的上的。例如:
- 利用類別中P開頭的字符,把標點符號全部篩選出來。
- 類別N開頭的是數(shù)字符號,除了常見的阿拉伯數(shù)字,還可以將羅馬數(shù)字、其他語種的數(shù)字體、帶圓圈的數(shù)序序號等也排除出來。
- 利用類別中C類別的字符,可以把文本中一些不可見的控制字符(如"^V, ^I" 或者zero-width的如u200d等字符)給過濾掉:
在這里,我展示一下 tensor2tensor 中計算 BLEU 分數(shù)的時候,用于分詞的函數(shù) bleu_tokenizer:
class UnicodeRegex(object):"""Ad-hoc hack to recognize all punctuation and symbols."""def __init__(self):# 獲取所有的標點符號punctuation = self.property_chars("P")# 標點符號左邊不帶數(shù)字self.nondigit_punct_re = re.compile(r"([^d])([" + punctuation + r"])")# 標點符號右邊不帶數(shù)字self.punct_nondigit_re = re.compile(r"([" + punctuation + r"])([^d])")# 所有的符號集合self.symbol_re = re.compile("([" + self.property_chars("S") + "])")def property_chars(self, prefix):return "".join(six.unichr(x) for x in range(sys.maxunicode)if unicodedata.category(six.unichr(x)).startswith(prefix))uregex = UnicodeRegex()def bleu_tokenize(string):# 粗暴的分割所有除了前后包含數(shù)字的標點符號。string = uregex.nondigit_punct_re.sub(r"1 2 ", string)string = uregex.punct_nondigit_re.sub(r" 1 2", string)# 所有的symbol默認分割string = uregex.symbol_re.sub(r" 1 ", string)return string.split()3.3 對unicode字符進行normalized:
在某些自然語言處理任務的過程中,會遇到一些神奇的靈異現(xiàn)象。 例如兩個單詞 or 字符用肉眼看是完全一模一樣的,但是在計算機中讀取出來卻表示兩者不相等。進一步的,當我們查看這個item的編碼字符的時候,發(fā)現(xiàn)兩者確實也不一樣。那究竟是什么樣的一回事呢??
text_a = "?????" text_b = "??????"print(text_a == text_b) # False print(unicodedata.normalize("NFKD", text_a) == text_b) # True事實上,在Unicode的編碼中,經(jīng)常會有一些特殊字符被編碼成多種 Unicode 形式。例如: 字符 U+00C7 (LATIN CAPITAL LETTER C WITH CEDILLA) 也可以被表示為下面列個字符的組合: U+0043 (LATIN CAPITAL LETTER C) 和 字符U+0327 (COMBINING CEDILLA).
這種情況下多發(fā)于那些需要包含音調的字符體系中(例如印地語、德語、西班牙語等),如以下字符"?"。Unicode體系中,即可以用Compose(組合)的形式U+00C7來表示這個字符。 也可以使用Decompose(分離)分別存儲字符(U+0043)本身和音調(U+0327)本身。
在上面的印地語中,出現(xiàn)問題的主要是因為字符"?",該字符下有一個小點,表示印地語中的一些音調問題(具體參考 Nuqta)。該字符就擁有 Compose 和 Decompose 兩種Unicode表示方法, 因此才會出現(xiàn)上文中字符不等的例子。
在Python中,我們可以利用 unicodedata.normalize 函數(shù)對字符進行標準化。標準化分為兩個方式:
- unicodedata.normalize("NFKC", text): Normal form Composition: 將所有的文本標準化為 Compose 形式。
- unicodedata.normalize("NFKD", text): Normal form Decomposition: 將所有的文本標準化為 Decompose 形式。
更標準的寫法,應該為
import unicodedata def strip_accents(s):return ''.join(c for c in unicodedata.normalize('NFD', s)if unicodedata.category(c) != 'Mn')3.3.1 題外話:
在撰寫本文的時候,我發(fā)現(xiàn)了一些外觀長的一模一樣,并且通過normalize方法也無法歸一化的問題。例如:
a = "?" b = "馬"print(a == b) # False print(a.encode("raw_unicode_escape")) # b'u2ee2' print(b.encode("raw_unicode_escape")) # b'u9a6c' print(unicodedata.normalize("NFKD", a) == b) # False print(unicodedata.normalize("NFKC", a) == b) # False于是我對上述文本中的第一個『馬』進行了一番查詢(正是文章開頭圖片的字符),發(fā)現(xiàn):
- 第一個馬的Category是一個Symbol,也就是說是一個符號。
- 第一個馬的Block屬于Radical-Block,查詢了一下,主要是在漢字中用于偏旁作用的。
那么,如果在實際應用中,應該如何對這兩個字符進行歸一化呢??? 目前我也沒有 idea 。。。。。
0x04. Reference:
- [1]. NLP哪里跑: Unicode相關的一些小知識和工具
- [2]. Python - Unicodedata
總結
以上是生活随笔為你收集整理的rhel 8.2不识别unicode_Unicode的文本处理二三事的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: chart.js 饼图显示百分比_Ech
- 下一篇: 销售找客户的软件_实用的销售客户管理软件