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