Download scripts/02_model/train_tokenizer_morfessor.py from XiaoyanLi/GooGooLM: direct link, hf CLI and curl.
- Browser
- Download file 14.5 kB
-
https://huggingface.co/XiaoyanLi/GooGooLM/resolve/main/scripts/02_model/train_tokenizer_morfessor.py
- Command line
-
hf download hf://XiaoyanLi/GooGooLM/scripts/02_model/train_tokenizer_morfessor.py
-
curl -L -o train_tokenizer_morfessor.py https://huggingface.co/XiaoyanLi/GooGooLM/resolve/main/scripts/02_model/train_tokenizer_morfessor.py
14.5 kB
| """ | |
| ่ฎญ็ป Morfessor+BPE Tokenizer | |
| ๆ่ทฏ๏ผ | |
| 1. ๅจ่ฎญ็ป่ฏญๆไธ่ฎญ็ป Morfessor๏ผๅญฆไน ่ฑ่ฏญๅฝขๆ็ด ่พน็ | |
| 2. ็จ Morfessor ๅฏน่ฎญ็ปๆๆฌ้ขๅๅ๏ผๅจๅฝขๆ็ด ่พน็ๆๅ ฅ็ฉบๆ ผ๏ผ | |
| 3. ๅจ้ขๅๅๆๆฌไธ่ฎญ็ป BPE๏ผๆถๆไธๆ ๅ BPE tokenizer ๅฎๅ จไธ่ด๏ผ | |
| ๆๆ๏ผBPE ไธไผ่ทจๅฝขๆ็ด ่พน็ๅๅนถ๏ผไฟ็ WUG/Entity Tracking ๆๅฉ็ๅฝขๆไฟกๆฏ | |
| ่พๅ ฅ: data/8_sample_B/train.txt | |
| ่พๅบ: models/tokenizer_morfessor/ | |
| โโโ tokenizer.json (BPE tokenizer, HuggingFace ๆ ผๅผ) | |
| โโโ tokenizer_config.json | |
| โโโ special_tokens_map.json | |
| โโโ morfessor.bin (Morfessor ๆจกๅ๏ผๆจ็ๆถ้่ฆ) | |
| ็จๆณ: | |
| pip install morfessor | |
| python scripts/02_model/train_tokenizer_morfessor.py | |
| python scripts/02_model/train_tokenizer_morfessor.py --input data/8_sample_C/train.txt | |
| """ | |
| import argparse | |
| import re | |
| import sys | |
| from collections import Counter | |
| from pathlib import Path | |
| try: | |
| import morfessor | |
| except ImportError: | |
| print("่ฏทๅ ๅฎ่ฃ : pip install morfessor") | |
| sys.exit(1) | |
| from tokenizers import Tokenizer, Regex | |
| from tokenizers.models import BPE | |
| from tokenizers.trainers import BpeTrainer | |
| from tokenizers.normalizers import Sequence, Prepend, NFKC, Replace | |
| from tokenizers.pre_tokenizers import Sequence as PreSeq, Split, ByteLevel | |
| from tokenizers.processors import TemplateProcessing | |
| from transformers import PreTrainedTokenizerFast | |
| ROOT = Path(__file__).resolve().parent.parent.parent | |
| DEFAULT_INPUT = ROOT / "data/8_sample_B/train.txt" | |
| DEFAULT_OUT = ROOT / "models/tokenizer_morfessor" | |
| SPECIAL_TOKENS = ["<unk>", "<s>", "</s>", "<pad>", "<mask>"] | |
| # Morfessor ่ถ ๅ | |
| CORPUSWEIGHT = 0.01 # ่ถๅฐๅๅ่ถๆฟ่ฟ๏ผ้ป่ฎค~1.0 ๅคชไฟๅฎ๏ผ | |
| MIN_MORPH_LEN = 2 # ๅฝขๆ็ด ๆ็ญ้ฟๅบฆ๏ผ่ฟๆปค s+it / b+and ็ญๅ้ณๆง | |
| MIN_WORD_LEN = 3 # ็ญไบๆญค้ฟๅบฆ็่ฏไธๅๅ | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # Step 1: ่ฎญ็ป Morfessor | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def train_morfessor_model(input_path: Path, corpusweight: float = CORPUSWEIGHT) -> morfessor.BaselineModel: | |
| """ๅจ่ฎญ็ป่ฏญๆไธ่ฎญ็ป Morfessor ๆจกๅใ""" | |
| print(f"Step 1: ่ฎญ็ป Morfessor ๆจกๅ (corpusweight={corpusweight})...") | |
| # ็ป่ฎก่ฏ้ข๏ผๅชๅ็บฏๅญๆฏ่ฏ๏ผๅปๆ ็น๏ผ | |
| word_counts = Counter() | |
| with open(input_path) as f: | |
| for line in f: | |
| for word in line.split(): | |
| clean = word.strip(".,!?;:\"'()-[]{}โฆ""''").lower() | |
| if len(clean) >= 2 and clean.isalpha(): | |
| word_counts[clean] += 1 | |
| print(f" ๅฏไธ่ฏๆฐ: {len(word_counts):,}") | |
| print(f" ๆป่ฏ้ข: {sum(word_counts.values()):,}") | |
| model = morfessor.BaselineModel(corpusweight=corpusweight) | |
| training_data = [(count, word) for word, count in word_counts.items()] | |
| model.load_data(training_data) | |
| model.train_batch() | |
| # ็คบไพ | |
| samples = [ | |
| "unhappiness", "running", "walked", "beautiful", "government", | |
| "internationally", "darkness", "singer", "swimming", "nationalization", | |
| "happiness", "slowly", "governmental", "children", "quickly", | |
| ] | |
| print("\n ๅๅ็คบไพ:") | |
| for w in samples: | |
| segs = model.viterbi_segment(w)[0] | |
| print(f" {w:25s} โ {' + '.join(segs)}") | |
| return model | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # Step 2: ้ขๅๅๆๆฌ | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| _WORD_RE = re.compile(r'^([^a-zA-Z]*?)([a-zA-Z]+)([^a-zA-Z]*)$') | |
| def presegment_word(word: str, morf_model) -> str: | |
| """ๅฏนๅ่ฏ็จ Morfessor ๆพๅฝขๆ็ด ่พน็๏ผๅจ่พน็ๅคๆๅ ฅ็ฉบๆ ผใ | |
| ไฟ็ๅๅงๅคงๅฐๅ๏ผ็จ Morfessor ๅๅๅฐๅ็ๆฌ๏ผ | |
| ๅๆ segment ้ฟๅบฆๆ ๅฐๅๅๅงๅญ็ฌฆใ | |
| ่ฟๆปค่งๅ๏ผๆๆๅฝขๆ็ด ้ฟๅบฆๅฟ ้กป >= MIN_MORPH_LEN๏ผ | |
| ๅฆๅ่งไธบๅ้ณๆง๏ผๅฆ s+it, b+and๏ผ๏ผไฟๆๅ่ฏไธๅใ | |
| """ | |
| m = _WORD_RE.match(word) | |
| if not m: | |
| return word | |
| prefix, core, suffix = m.groups() | |
| if len(core) < MIN_WORD_LEN: | |
| return word | |
| segments = morf_model.viterbi_segment(core.lower())[0] | |
| if len(segments) <= 1: | |
| return word | |
| # ่ฟๆปค๏ผๆๆๅฝขๆ็ด ๅฟ ้กป >= MIN_MORPH_LEN | |
| if not all(len(s) >= MIN_MORPH_LEN for s in segments): | |
| return word | |
| # ๆ segment ้ฟๅบฆไปๅๅง core ไธญๅ็๏ผไฟ็ๅคงๅฐๅ๏ผ | |
| parts = [] | |
| pos = 0 | |
| for seg in segments: | |
| n = len(seg) | |
| parts.append(core[pos:pos + n]) | |
| pos += n | |
| return prefix + ' '.join(parts) + suffix | |
| def presegment_file(input_path: Path, morf_model, output_path: Path) -> Path: | |
| """ๅฏนๆดไธชๆไปถ่ฟ่ก Morfessor ้ขๅๅใ""" | |
| print("\nStep 2: ้ขๅๅ่ฎญ็ปๆๆฌ...") | |
| line_count = 0 | |
| with open(input_path) as fin, open(output_path, 'w') as fout: | |
| for line in fin: | |
| if line.strip(): | |
| words = line.split() | |
| segmented = [presegment_word(w, morf_model) for w in words] | |
| fout.write(' '.join(segmented) + '\n') | |
| else: | |
| fout.write('\n') | |
| line_count += 1 | |
| if line_count % 200000 == 0: | |
| print(f" ๅทฒๅค็ {line_count:,} ่ก...") | |
| print(f" ๆปๅ ฑๅค็ {line_count:,} ่ก") | |
| print(f" ้ขๅๅๆไปถ: {output_path} ({output_path.stat().st_size / 1e6:.1f} MB)") | |
| # ๅฏนๆฏๅ ่ก | |
| print("\n ๅฏนๆฏ็คบไพ:") | |
| shown = 0 | |
| with open(input_path) as f1, open(output_path) as f2: | |
| for orig, seg in zip(f1, f2): | |
| if orig.strip() != seg.strip() and 30 < len(orig.strip()) < 150: | |
| print(f" ๅ: {orig.strip()}") | |
| print(f" ๅ: {seg.strip()}") | |
| print() | |
| shown += 1 | |
| if shown >= 5: | |
| break | |
| return output_path | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # Step 3: ๅจ้ขๅๅๆๆฌไธ่ฎญ็ป BPE | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def build_and_train_bpe(segmented_file: Path, vocab_size: int) -> Tokenizer: | |
| """่ฎญ็ป BPE๏ผๆถๆไธๆ ๅ tokenizer ๅฎๅ จไธ่ดใ""" | |
| print(f"\nStep 3: ่ฎญ็ป BPE (vocab_size={vocab_size})...") | |
| # Normalizer๏ผไธๆ ๅ BPE ไธ่ด๏ผ | |
| normalizer = Sequence([ | |
| Prepend(prepend=" "), | |
| NFKC(), | |
| Replace(Regex(r"\n"), "\n "), | |
| Replace(Regex(r" *\n"), "\n"), | |
| ]) | |
| # Pre-tokenizer๏ผไธๆ ๅ BPE ไธ่ด๏ผ | |
| GPT4_REGEX = ( | |
| r"[^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}]*" | |
| r"[\p{Ll}\p{Lm}\p{Lo}\p{M}]+" | |
| r"|[^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}]+" | |
| r"[\p{Ll}\p{Lm}\p{Lo}\p{M}]*" | |
| r"| ?\p{N}" | |
| r"| ?[^\s\p{L}\p{N}]+[\r\n/]*" | |
| r"|\s*[\r\n]+" | |
| r"|\s+(?!\S)" | |
| r"|\s+" | |
| ) | |
| pre_tokenizer = PreSeq([ | |
| Split(pattern=Regex(GPT4_REGEX), behavior="isolated"), | |
| ByteLevel(add_prefix_space=False, trim_offsets=True, use_regex=False), | |
| Split(pattern=Regex(r".{1,24}"), behavior="isolated"), | |
| ]) | |
| tokenizer = Tokenizer(BPE(unk_token="<unk>")) | |
| tokenizer.normalizer = normalizer | |
| tokenizer.pre_tokenizer = pre_tokenizer | |
| trainer = BpeTrainer( | |
| vocab_size=vocab_size, | |
| special_tokens=SPECIAL_TOKENS, | |
| min_frequency=2, | |
| show_progress=True, | |
| ) | |
| tokenizer.train(files=[str(segmented_file)], trainer=trainer) | |
| print(f" ๅฎ้ vocab size: {tokenizer.get_vocab_size()}") | |
| # Post-processor: ๅฅ้ฆๅ <s> | |
| tokenizer.post_processor = TemplateProcessing( | |
| single="<s> $A", | |
| pair="<s> $A <s> $B", | |
| special_tokens=[("<s>", tokenizer.token_to_id("<s>"))], | |
| ) | |
| # ๆ ก้ช็นๆฎ token ID | |
| expected = {"<unk>": 0, "<s>": 1, "</s>": 2, "<pad>": 3, "<mask>": 4} | |
| for token, eid in expected.items(): | |
| aid = tokenizer.token_to_id(token) | |
| status = "โ " if aid == eid else "โ" | |
| print(f" {status} {token:10s} expected={eid} actual={aid}") | |
| if aid != eid: | |
| raise ValueError(f"็นๆฎ token ID ไธไธ่ด: {token}") | |
| return tokenizer | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # Step 4: ไฟๅญ | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def save_all(tokenizer: Tokenizer, morf_model, output_dir: Path): | |
| """ไฟๅญ BPE tokenizer + Morfessor ๆจกๅใ""" | |
| print(f"\nStep 4: ไฟๅญๅฐ {output_dir}/") | |
| output_dir.mkdir(parents=True, exist_ok=True) | |
| # BPE tokenizer | |
| raw_path = output_dir / "tokenizer.json" | |
| tokenizer.save(str(raw_path)) | |
| fast_tok = PreTrainedTokenizerFast( | |
| tokenizer_file=str(raw_path), | |
| bos_token="<s>", eos_token="</s>", unk_token="<unk>", | |
| sep_token="</s>", pad_token="<pad>", cls_token="<s>", mask_token="<mask>", | |
| ) | |
| fast_tok.save_pretrained(str(output_dir)) | |
| # Morfessor ๆจกๅ | |
| io = morfessor.MorfessorIO() | |
| morf_path = output_dir / "morfessor.bin" | |
| io.write_binary_model_file(str(morf_path), morf_model) | |
| print(f" ๆไปถ: {sorted(f.name for f in output_dir.iterdir())}") | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # Step 5: ้ช่ฏ & ๅฏนๆฏ | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def verify(output_dir: Path, morf_model): | |
| """ๅ ่ฝฝๅนถๅฏนๆฏๆ ๅ BPE vs Morfessor+BPEใ""" | |
| print("\nStep 5: ้ช่ฏ & ๅฏนๆฏ...") | |
| fast_tok = PreTrainedTokenizerFast.from_pretrained(str(output_dir)) | |
| std_path = ROOT / "models/tokenizer/tokenizer.json" | |
| if std_path.exists(): | |
| std_tok = Tokenizer.from_file(str(std_path)) | |
| else: | |
| std_tok = None | |
| tests = [ | |
| "The cat sat on the mat.", | |
| "She was running quickly through the forest.", | |
| "I don't think he's coming today.", | |
| "unhappiness", | |
| "running jumped swimming", | |
| "The ice is cold and the fire is hot.", | |
| "nationalization", | |
| "governmental", | |
| "The children played happily in the garden.", | |
| ] | |
| for t in tests: | |
| # Morfessor+BPE: ๅ ้ขๅๅ | |
| seg_t = ' '.join(presegment_word(w, morf_model) for w in t.split()) | |
| morf_tokens = fast_tok.tokenize(seg_t) | |
| print(f" ๅๆ: {t}") | |
| if seg_t != t: | |
| print(f" ้ขๅ: {seg_t}") | |
| if std_tok: | |
| std_tokens = std_tok.encode(t).tokens | |
| print(f" ๆ ๅBPE ({len(std_tokens):2d}): {std_tokens}") | |
| print(f" Morf+BPE ({len(morf_tokens):2d}): {morf_tokens}") | |
| print() | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # Main | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def main(): | |
| parser = argparse.ArgumentParser(description="่ฎญ็ป Morfessor+BPE Tokenizer") | |
| parser.add_argument("--input", default=str(DEFAULT_INPUT), help="่ฎญ็ปๆไปถ่ทฏๅพ") | |
| parser.add_argument("--output", default=str(DEFAULT_OUT), help="่พๅบ็ฎๅฝ") | |
| parser.add_argument("--vocab_size", default=8192, type=int, help="่ฏ่กจๅคงๅฐ") | |
| parser.add_argument("--corpusweight", default=CORPUSWEIGHT, type=float, | |
| help=f"Morfessor corpusweight, ่ถๅฐ่ถๆฟ่ฟ (้ป่ฎค{CORPUSWEIGHT})") | |
| args = parser.parse_args() | |
| input_path = Path(args.input) | |
| output_dir = Path(args.output) | |
| if not input_path.exists(): | |
| raise FileNotFoundError(f"่ฎญ็ปๆไปถไธๅญๅจ: {input_path}") | |
| print(f"โโโ ่ฎญ็ป Morfessor+BPE Tokenizer โโโ") | |
| print(f" ่พๅ ฅ: {input_path} ({input_path.stat().st_size / 1e6:.1f} MB)") | |
| print(f" ่พๅบ: {output_dir}") | |
| print(f" vocab_size: {args.vocab_size}") | |
| print() | |
| # Step 1: Morfessor | |
| morf_model = train_morfessor_model(input_path, corpusweight=args.corpusweight) | |
| # Step 2: ้ขๅๅ | |
| seg_path = output_dir / "_presegmented_train.txt" | |
| output_dir.mkdir(parents=True, exist_ok=True) | |
| presegment_file(input_path, morf_model, seg_path) | |
| # Step 3: BPE | |
| tokenizer = build_and_train_bpe(seg_path, args.vocab_size) | |
| # Step 4: ไฟๅญ | |
| save_all(tokenizer, morf_model, output_dir) | |
| # Step 5: ้ช่ฏ | |
| verify(output_dir, morf_model) | |
| # ๅ ้ค้ขๅๅไธญ้ดๆไปถ | |
| seg_path.unlink() | |
| print(f" ๅทฒๅ ้คไธญ้ดๆไปถ: {seg_path.name}") | |
| print("\nโโโ ๅฎๆ๏ผโโโ") | |
| if __name__ == "__main__": | |
| main() | |