Download app/fp/charclass.py from Enrell/lain-parser: direct link, hf CLI and curl.
- Browser
- Download file 4.15 kB
-
https://huggingface.co/Enrell/lain-parser/resolve/main/app/fp/charclass.py
- Command line
-
hf download hf://Enrell/lain-parser/app/fp/charclass.py
-
curl -L -o charclass.py https://huggingface.co/Enrell/lain-parser/resolve/main/app/fp/charclass.py
4.15 kB
| """Unicode class features for the character encoder (E2). | |
| A character's identity embedding says nothing about a code point the model has | |
| never seen; its Unicode general category and script do. Every character gets a | |
| (category, script) pair, and the parser adds one learned embedding for each to | |
| the identity embedding, so an unseen separator or bracket still arrives as | |
| "opening punctuation" or "dash". | |
| Class 0 is reserved for positions that are not characters ([CLS], [SEP], | |
| padding). The table is built from this interpreter's `unicodedata` and exported | |
| into the Go artifact as ranges, so both runtimes use the same assignment even | |
| where Unicode versions differ. | |
| """ | |
| import bisect | |
| import unicodedata | |
| import numpy as np | |
| CATEGORIES = ("Lu", "Ll", "Lt", "Lm", "Lo", "Mn", "Mc", "Me", "Nd", "Nl", "No", | |
| "Pc", "Pd", "Ps", "Pe", "Pi", "Pf", "Po", "Sm", "Sc", "Sk", "So", | |
| "Zs", "Zl", "Zp", "Cc", "Cf", "Cs", "Co", "Cn") | |
| # Block ranges, not the Unicode Script property: `unicodedata` does not expose | |
| # scripts, and blocks are what separate the signals a filename cares about | |
| # (fullwidth vs ASCII punctuation, kana vs Han). Unlisted code points are "other". | |
| SCRIPT_BLOCKS = ( | |
| ("latin", ((0x0000, 0x024F), (0x1E00, 0x1EFF), (0x2C60, 0x2C7F), (0xA720, 0xA7FF), | |
| (0xAB30, 0xAB6F))), | |
| ("ipa-modifier", ((0x0250, 0x036F),)), | |
| ("greek", ((0x0370, 0x03FF), (0x1F00, 0x1FFF))), | |
| ("cyrillic", ((0x0400, 0x052F), (0x1C80, 0x1C8F), (0x2DE0, 0x2DFF), (0xA640, 0xA69F))), | |
| ("armenian-hebrew", ((0x0530, 0x05FF),)), | |
| ("arabic", ((0x0600, 0x06FF), (0x0750, 0x077F), (0x08A0, 0x08FF), (0xFB50, 0xFDFF), | |
| (0xFE70, 0xFEFF))), | |
| ("indic", ((0x0900, 0x0DFF),)), | |
| ("southeast-asian", ((0x0E00, 0x0FFF), (0x1000, 0x109F), (0x1780, 0x17FF))), | |
| ("hangul", ((0x1100, 0x11FF), (0x3130, 0x318F), (0xA960, 0xA97F), (0xAC00, 0xD7FF))), | |
| ("general-punctuation", ((0x2000, 0x206F),)), | |
| ("symbols", ((0x2070, 0x2BFF),)), | |
| ("cjk-symbols", ((0x3000, 0x303F), (0x3200, 0x33FF), (0xFE10, 0xFE1F), (0xFE30, 0xFE4F))), | |
| ("hiragana", ((0x3040, 0x309F),)), | |
| ("katakana", ((0x30A0, 0x30FF), (0x31F0, 0x31FF), (0xFF66, 0xFF9F))), | |
| ("han", ((0x2E80, 0x2FDF), (0x3400, 0x4DBF), (0x4E00, 0x9FFF), (0xF900, 0xFAFF), | |
| (0x20000, 0x3FFFF))), | |
| ("fullwidth", ((0xFF00, 0xFF65), (0xFFE0, 0xFFEF))), | |
| ("emoji", ((0x1F000, 0x1FAFF),)), | |
| ) | |
| SCRIPTS = tuple(name for name, _ in SCRIPT_BLOCKS) + ("other",) | |
| N_CATEGORIES, N_SCRIPTS = len(CATEGORIES) + 1, len(SCRIPTS) + 1 | |
| LIMIT = 0x110000 | |
| # Categories that carry structure rather than content; identity dropout hits | |
| # them harder (see Parser.character_embeddings). | |
| STRUCTURAL = frozenset(i + 1 for i, name in enumerate(CATEGORIES) if name[0] in "PSZ") | |
| _TABLES = None | |
| def tables(): | |
| """(category ids, script ids) for every code point, as uint8 arrays.""" | |
| global _TABLES | |
| if _TABLES is None: | |
| category = np.empty(LIMIT, dtype=np.uint8) | |
| index = {name: i + 1 for i, name in enumerate(CATEGORIES)} | |
| for cp in range(LIMIT): | |
| category[cp] = index[unicodedata.category(chr(cp))] | |
| script = np.full(LIMIT, SCRIPTS.index("other") + 1, dtype=np.uint8) | |
| for name, ranges in SCRIPT_BLOCKS: | |
| for a, b in ranges: | |
| script[a:b + 1] = SCRIPTS.index(name) + 1 | |
| _TABLES = category, script | |
| return _TABLES | |
| def classes(char): | |
| category, script = tables() | |
| return int(category[ord(char)]), int(script[ord(char)]) | |
| def ranges(): | |
| """Run-length [start, end, category, script] rows covering 0..0x10FFFF.""" | |
| category, script = tables() | |
| combined = category.astype(np.uint16) << 8 | script | |
| change = np.flatnonzero(np.diff(combined)) + 1 | |
| starts = np.concatenate(([0], change)) | |
| ends = np.concatenate((change - 1, [LIMIT - 1])) | |
| return [[int(a), int(b), int(category[a]), int(script[a])] for a, b in zip(starts, ends)] | |
| def lookup(table, cp): | |
| """Reference implementation of the Go range lookup (for parity tests).""" | |
| position = bisect.bisect_right([row[0] for row in table], cp) - 1 | |
| return table[position][2], table[position][3] | |