Danny commited on
Commit
7cb8aac
·
verified ·
1 Parent(s): 6514fdb

Bilingual humanize-text-model (EN T5-small + ZH Chinese T5-small)

Browse files
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lynote
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ language:
4
+ - en
5
+ - zh
6
+ tags:
7
+ - text2text-generation
8
+ - text-humanizer
9
+ - rewriting
10
+ - writing-assistant
11
+ - bilingual
12
+ - responsible-ai
13
+ pipeline_tag: text2text-generation
14
+ base_model:
15
+ - google-t5/t5-small
16
+ - uer/t5-small-chinese-cluecorpussmall
17
+ inference: true
18
+ widget:
19
+ - text: "It is important to note that this robust solution serves as a testament to our commitment to innovation. Moreover, we leverage cutting-edge technology."
20
+ - text: "值得注意的是,在当今快速发展的时代,我们通过赋能团队来助力企业实现降本增效。"
21
+ ---
22
+
23
+ # Humanize Text Model (Lynote)
24
+
25
+ A lightweight bilingual (English/Chinese) humanizer: two small T5-seq2seq
26
+ checkpoints in one repository that rewrite AI-flavored prose into more
27
+ natural, human-flavored prose.
28
+
29
+ - **`en/`** — fine-tuned `google-t5/t5-small` (English)
30
+ - **`zh/`** — fine-tuned `uer/t5-small-chinese-cluecorpussmall` (Chinese)
31
+
32
+ ## What it does
33
+
34
+ - Removes high-confidence AI clichés and formulaic phrases (e.g.
35
+ *"it is important to note that"*, *"moreover"*, *"值得注意的是"*,
36
+ *"降本增效"*).
37
+ - Keeps already-human prose nearly untouched (identity learning).
38
+ - Preserves numbers, URLs, file paths, code and quoted text (protected with
39
+ `PROTECTED_N` placeholders during generation, restored afterwards).
40
+ - Routes automatically by language (CJK ratio detection).
41
+
42
+ ## What it is NOT
43
+
44
+ This is a **writing-quality aid**, not a tool for evading AI detectors.
45
+ Detector scores are probabilistic, and no humanizer can guarantee that text
46
+ will be classified as human. Please use it responsibly:
47
+ do not use it to misrepresent authorship in academic, legal, or disciplinary
48
+ contexts.
49
+
50
+ ## Quickstart
51
+
52
+ ```bash
53
+ pip install transformers torch
54
+ ```
55
+
56
+ ```python
57
+ from humanize import Humanizer # the wrapper bundled in this repo
58
+
59
+ h = Humanizer() # loads this repo (en/ and zh/ sub-checkpoints)
60
+ print(h.humanize(
61
+ "It is important to note that this robust solution serves as a "
62
+ "testament to our commitment. Moreover, we leverage cutting-edge "
63
+ "technology."
64
+ ))
65
+ print(h.humanize("值得注意的是,我们通过赋能团队来实现降本增效。"))
66
+ ```
67
+
68
+ Raw `transformers` usage (no wrapper):
69
+
70
+ ```python
71
+ from transformers import T5ForConditionalGeneration, T5Tokenizer
72
+
73
+ model = T5ForConditionalGeneration.from_pretrained("Danny-Lynote/humanize-text-model/en")
74
+ tokenizer = T5Tokenizer.from_pretrained("Danny-Lynote/humanize-text-model/en")
75
+ inputs = tokenizer("It is important to note that this is robust.", return_tensors="pt")
76
+ print(tokenizer.decode(model.generate(**inputs, max_length=128)[0], skip_special_tokens=True))
77
+ ```
78
+
79
+ For Chinese use the `zh/` sub-checkpoint with `BertTokenizer`.
80
+
81
+ ## Training data
82
+
83
+ The corpus is generated deterministically from the editing principles of the
84
+ Lynote reference projects (`humanize-text`, `humanize-text-skill`,
85
+ `humanizer-lite`):
86
+
87
+ 1. **AI → human**: formulaic clause combinations rewritten by a conservative
88
+ rule engine,
89
+ 2. **human → human (identity)**: clean prose unchanged, so the model learns
90
+ not to rewrite good text,
91
+ 3. **mixed**: clean prose with one injected cliché that must be removed,
92
+ 4. **protected spans**: examples with URLs, numbers, code and quotes.
93
+
94
+ Reproduce:
95
+
96
+ ```bash
97
+ python scripts/build_dataset.py --out data # 14.7k pairs (en + zh)
98
+ python scripts/train.py --lang en --epochs 3 # -> checkpoints/humanize-text-model/en
99
+ python scripts/train.py --lang zh --epochs 3 # -> checkpoints/humanize-text-model/zh
100
+ python scripts/evaluate.py # benchmark on held-out test
101
+ pytest tests/ # full test suite
102
+ ```
103
+
104
+ ## Evaluation (held-out test, 500 AI->human + all identity/protected pairs)
105
+
106
+ | Metric | Value |
107
+ |---|---|
108
+ | Corpus BLEU vs rule reference | **99.2** |
109
+ | Cliché removal rate | **100.0%** (435/435) |
110
+ | Identity stability (clean prose, n=67) | **73.1%** |
111
+ | Protected-span preservation (n=253) | **96.8%** |
112
+ | Throughput (MPS) | ~218 chars/s |
113
+
114
+ ## Limitations
115
+
116
+ - Trained on synthetic text; real-world inputs may need light post-editing.
117
+ - One checkpoint per language (English / Chinese); other languages are not
118
+ specifically trained.
119
+ - Long inputs are truncated to 256 tokens.
120
+ - It is a conservative editor: it will not add stylistic richness that is
121
+ absent from the source text.
122
+
123
+ ## License
124
+
125
+ MIT. Base models: `google-t5/t5-small` (Apache-2.0) and
126
+ `uer/t5-small-chinese-cluecorpussmall` (Apache-2.0).
en/added_tokens.json ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "<extra_id_0>": 32099,
3
+ "<extra_id_10>": 32089,
4
+ "<extra_id_11>": 32088,
5
+ "<extra_id_12>": 32087,
6
+ "<extra_id_13>": 32086,
7
+ "<extra_id_14>": 32085,
8
+ "<extra_id_15>": 32084,
9
+ "<extra_id_16>": 32083,
10
+ "<extra_id_17>": 32082,
11
+ "<extra_id_18>": 32081,
12
+ "<extra_id_19>": 32080,
13
+ "<extra_id_1>": 32098,
14
+ "<extra_id_20>": 32079,
15
+ "<extra_id_21>": 32078,
16
+ "<extra_id_22>": 32077,
17
+ "<extra_id_23>": 32076,
18
+ "<extra_id_24>": 32075,
19
+ "<extra_id_25>": 32074,
20
+ "<extra_id_26>": 32073,
21
+ "<extra_id_27>": 32072,
22
+ "<extra_id_28>": 32071,
23
+ "<extra_id_29>": 32070,
24
+ "<extra_id_2>": 32097,
25
+ "<extra_id_30>": 32069,
26
+ "<extra_id_31>": 32068,
27
+ "<extra_id_32>": 32067,
28
+ "<extra_id_33>": 32066,
29
+ "<extra_id_34>": 32065,
30
+ "<extra_id_35>": 32064,
31
+ "<extra_id_36>": 32063,
32
+ "<extra_id_37>": 32062,
33
+ "<extra_id_38>": 32061,
34
+ "<extra_id_39>": 32060,
35
+ "<extra_id_3>": 32096,
36
+ "<extra_id_40>": 32059,
37
+ "<extra_id_41>": 32058,
38
+ "<extra_id_42>": 32057,
39
+ "<extra_id_43>": 32056,
40
+ "<extra_id_44>": 32055,
41
+ "<extra_id_45>": 32054,
42
+ "<extra_id_46>": 32053,
43
+ "<extra_id_47>": 32052,
44
+ "<extra_id_48>": 32051,
45
+ "<extra_id_49>": 32050,
46
+ "<extra_id_4>": 32095,
47
+ "<extra_id_50>": 32049,
48
+ "<extra_id_51>": 32048,
49
+ "<extra_id_52>": 32047,
50
+ "<extra_id_53>": 32046,
51
+ "<extra_id_54>": 32045,
52
+ "<extra_id_55>": 32044,
53
+ "<extra_id_56>": 32043,
54
+ "<extra_id_57>": 32042,
55
+ "<extra_id_58>": 32041,
56
+ "<extra_id_59>": 32040,
57
+ "<extra_id_5>": 32094,
58
+ "<extra_id_60>": 32039,
59
+ "<extra_id_61>": 32038,
60
+ "<extra_id_62>": 32037,
61
+ "<extra_id_63>": 32036,
62
+ "<extra_id_64>": 32035,
63
+ "<extra_id_65>": 32034,
64
+ "<extra_id_66>": 32033,
65
+ "<extra_id_67>": 32032,
66
+ "<extra_id_68>": 32031,
67
+ "<extra_id_69>": 32030,
68
+ "<extra_id_6>": 32093,
69
+ "<extra_id_70>": 32029,
70
+ "<extra_id_71>": 32028,
71
+ "<extra_id_72>": 32027,
72
+ "<extra_id_73>": 32026,
73
+ "<extra_id_74>": 32025,
74
+ "<extra_id_75>": 32024,
75
+ "<extra_id_76>": 32023,
76
+ "<extra_id_77>": 32022,
77
+ "<extra_id_78>": 32021,
78
+ "<extra_id_79>": 32020,
79
+ "<extra_id_7>": 32092,
80
+ "<extra_id_80>": 32019,
81
+ "<extra_id_81>": 32018,
82
+ "<extra_id_82>": 32017,
83
+ "<extra_id_83>": 32016,
84
+ "<extra_id_84>": 32015,
85
+ "<extra_id_85>": 32014,
86
+ "<extra_id_86>": 32013,
87
+ "<extra_id_87>": 32012,
88
+ "<extra_id_88>": 32011,
89
+ "<extra_id_89>": 32010,
90
+ "<extra_id_8>": 32091,
91
+ "<extra_id_90>": 32009,
92
+ "<extra_id_91>": 32008,
93
+ "<extra_id_92>": 32007,
94
+ "<extra_id_93>": 32006,
95
+ "<extra_id_94>": 32005,
96
+ "<extra_id_95>": 32004,
97
+ "<extra_id_96>": 32003,
98
+ "<extra_id_97>": 32002,
99
+ "<extra_id_98>": 32001,
100
+ "<extra_id_99>": 32000,
101
+ "<extra_id_9>": 32090
102
+ }
en/config.json ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "T5ForConditionalGeneration"
4
+ ],
5
+ "classifier_dropout": 0.0,
6
+ "d_ff": 2048,
7
+ "d_kv": 64,
8
+ "d_model": 512,
9
+ "decoder_start_token_id": 0,
10
+ "dense_act_fn": "relu",
11
+ "dropout_rate": 0.1,
12
+ "dtype": "float32",
13
+ "eos_token_id": 1,
14
+ "feed_forward_proj": "relu",
15
+ "initializer_factor": 1.0,
16
+ "is_encoder_decoder": true,
17
+ "is_gated_act": false,
18
+ "layer_norm_epsilon": 1e-06,
19
+ "model_type": "t5",
20
+ "n_positions": 512,
21
+ "num_decoder_layers": 6,
22
+ "num_heads": 8,
23
+ "num_layers": 6,
24
+ "output_past": true,
25
+ "pad_token_id": 0,
26
+ "relative_attention_max_distance": 128,
27
+ "relative_attention_num_buckets": 32,
28
+ "task_specific_params": {
29
+ "summarization": {
30
+ "early_stopping": true,
31
+ "length_penalty": 2.0,
32
+ "max_length": 200,
33
+ "min_length": 30,
34
+ "no_repeat_ngram_size": 3,
35
+ "num_beams": 4,
36
+ "prefix": "summarize: "
37
+ },
38
+ "translation_en_to_de": {
39
+ "early_stopping": true,
40
+ "max_length": 300,
41
+ "num_beams": 4,
42
+ "prefix": "translate English to German: "
43
+ },
44
+ "translation_en_to_fr": {
45
+ "early_stopping": true,
46
+ "max_length": 300,
47
+ "num_beams": 4,
48
+ "prefix": "translate English to French: "
49
+ },
50
+ "translation_en_to_ro": {
51
+ "early_stopping": true,
52
+ "max_length": 300,
53
+ "num_beams": 4,
54
+ "prefix": "translate English to Romanian: "
55
+ }
56
+ },
57
+ "transformers_version": "4.57.6",
58
+ "use_cache": true,
59
+ "vocab_size": 32128
60
+ }
en/generation_config.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "_from_model_config": true,
3
+ "decoder_start_token_id": 0,
4
+ "eos_token_id": 1,
5
+ "pad_token_id": 0,
6
+ "transformers_version": "4.57.6"
7
+ }
en/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:13fd26d16fd54619d6b2a3c3e5061451fa9bf5167312f9c47aa074df1c5e80e6
3
+ size 242041896
en/special_tokens_map.json ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "additional_special_tokens": [
3
+ "<extra_id_0>",
4
+ "<extra_id_1>",
5
+ "<extra_id_2>",
6
+ "<extra_id_3>",
7
+ "<extra_id_4>",
8
+ "<extra_id_5>",
9
+ "<extra_id_6>",
10
+ "<extra_id_7>",
11
+ "<extra_id_8>",
12
+ "<extra_id_9>",
13
+ "<extra_id_10>",
14
+ "<extra_id_11>",
15
+ "<extra_id_12>",
16
+ "<extra_id_13>",
17
+ "<extra_id_14>",
18
+ "<extra_id_15>",
19
+ "<extra_id_16>",
20
+ "<extra_id_17>",
21
+ "<extra_id_18>",
22
+ "<extra_id_19>",
23
+ "<extra_id_20>",
24
+ "<extra_id_21>",
25
+ "<extra_id_22>",
26
+ "<extra_id_23>",
27
+ "<extra_id_24>",
28
+ "<extra_id_25>",
29
+ "<extra_id_26>",
30
+ "<extra_id_27>",
31
+ "<extra_id_28>",
32
+ "<extra_id_29>",
33
+ "<extra_id_30>",
34
+ "<extra_id_31>",
35
+ "<extra_id_32>",
36
+ "<extra_id_33>",
37
+ "<extra_id_34>",
38
+ "<extra_id_35>",
39
+ "<extra_id_36>",
40
+ "<extra_id_37>",
41
+ "<extra_id_38>",
42
+ "<extra_id_39>",
43
+ "<extra_id_40>",
44
+ "<extra_id_41>",
45
+ "<extra_id_42>",
46
+ "<extra_id_43>",
47
+ "<extra_id_44>",
48
+ "<extra_id_45>",
49
+ "<extra_id_46>",
50
+ "<extra_id_47>",
51
+ "<extra_id_48>",
52
+ "<extra_id_49>",
53
+ "<extra_id_50>",
54
+ "<extra_id_51>",
55
+ "<extra_id_52>",
56
+ "<extra_id_53>",
57
+ "<extra_id_54>",
58
+ "<extra_id_55>",
59
+ "<extra_id_56>",
60
+ "<extra_id_57>",
61
+ "<extra_id_58>",
62
+ "<extra_id_59>",
63
+ "<extra_id_60>",
64
+ "<extra_id_61>",
65
+ "<extra_id_62>",
66
+ "<extra_id_63>",
67
+ "<extra_id_64>",
68
+ "<extra_id_65>",
69
+ "<extra_id_66>",
70
+ "<extra_id_67>",
71
+ "<extra_id_68>",
72
+ "<extra_id_69>",
73
+ "<extra_id_70>",
74
+ "<extra_id_71>",
75
+ "<extra_id_72>",
76
+ "<extra_id_73>",
77
+ "<extra_id_74>",
78
+ "<extra_id_75>",
79
+ "<extra_id_76>",
80
+ "<extra_id_77>",
81
+ "<extra_id_78>",
82
+ "<extra_id_79>",
83
+ "<extra_id_80>",
84
+ "<extra_id_81>",
85
+ "<extra_id_82>",
86
+ "<extra_id_83>",
87
+ "<extra_id_84>",
88
+ "<extra_id_85>",
89
+ "<extra_id_86>",
90
+ "<extra_id_87>",
91
+ "<extra_id_88>",
92
+ "<extra_id_89>",
93
+ "<extra_id_90>",
94
+ "<extra_id_91>",
95
+ "<extra_id_92>",
96
+ "<extra_id_93>",
97
+ "<extra_id_94>",
98
+ "<extra_id_95>",
99
+ "<extra_id_96>",
100
+ "<extra_id_97>",
101
+ "<extra_id_98>",
102
+ "<extra_id_99>"
103
+ ],
104
+ "eos_token": {
105
+ "content": "</s>",
106
+ "lstrip": false,
107
+ "normalized": false,
108
+ "rstrip": false,
109
+ "single_word": false
110
+ },
111
+ "pad_token": {
112
+ "content": "<pad>",
113
+ "lstrip": false,
114
+ "normalized": false,
115
+ "rstrip": false,
116
+ "single_word": false
117
+ },
118
+ "unk_token": {
119
+ "content": "<unk>",
120
+ "lstrip": false,
121
+ "normalized": false,
122
+ "rstrip": false,
123
+ "single_word": false
124
+ }
125
+ }
en/spiece.model ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d60acb128cf7b7f2536e8f38a5b18a05535c9e14c7a355904270e15b0945ea86
3
+ size 791656
en/tokenizer_config.json ADDED
@@ -0,0 +1,941 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_prefix_space": true,
3
+ "added_tokens_decoder": {
4
+ "0": {
5
+ "content": "<pad>",
6
+ "lstrip": false,
7
+ "normalized": false,
8
+ "rstrip": false,
9
+ "single_word": false,
10
+ "special": true
11
+ },
12
+ "1": {
13
+ "content": "</s>",
14
+ "lstrip": false,
15
+ "normalized": false,
16
+ "rstrip": false,
17
+ "single_word": false,
18
+ "special": true
19
+ },
20
+ "2": {
21
+ "content": "<unk>",
22
+ "lstrip": false,
23
+ "normalized": false,
24
+ "rstrip": false,
25
+ "single_word": false,
26
+ "special": true
27
+ },
28
+ "32000": {
29
+ "content": "<extra_id_99>",
30
+ "lstrip": false,
31
+ "normalized": false,
32
+ "rstrip": false,
33
+ "single_word": false,
34
+ "special": true
35
+ },
36
+ "32001": {
37
+ "content": "<extra_id_98>",
38
+ "lstrip": false,
39
+ "normalized": false,
40
+ "rstrip": false,
41
+ "single_word": false,
42
+ "special": true
43
+ },
44
+ "32002": {
45
+ "content": "<extra_id_97>",
46
+ "lstrip": false,
47
+ "normalized": false,
48
+ "rstrip": false,
49
+ "single_word": false,
50
+ "special": true
51
+ },
52
+ "32003": {
53
+ "content": "<extra_id_96>",
54
+ "lstrip": false,
55
+ "normalized": false,
56
+ "rstrip": false,
57
+ "single_word": false,
58
+ "special": true
59
+ },
60
+ "32004": {
61
+ "content": "<extra_id_95>",
62
+ "lstrip": false,
63
+ "normalized": false,
64
+ "rstrip": false,
65
+ "single_word": false,
66
+ "special": true
67
+ },
68
+ "32005": {
69
+ "content": "<extra_id_94>",
70
+ "lstrip": false,
71
+ "normalized": false,
72
+ "rstrip": false,
73
+ "single_word": false,
74
+ "special": true
75
+ },
76
+ "32006": {
77
+ "content": "<extra_id_93>",
78
+ "lstrip": false,
79
+ "normalized": false,
80
+ "rstrip": false,
81
+ "single_word": false,
82
+ "special": true
83
+ },
84
+ "32007": {
85
+ "content": "<extra_id_92>",
86
+ "lstrip": false,
87
+ "normalized": false,
88
+ "rstrip": false,
89
+ "single_word": false,
90
+ "special": true
91
+ },
92
+ "32008": {
93
+ "content": "<extra_id_91>",
94
+ "lstrip": false,
95
+ "normalized": false,
96
+ "rstrip": false,
97
+ "single_word": false,
98
+ "special": true
99
+ },
100
+ "32009": {
101
+ "content": "<extra_id_90>",
102
+ "lstrip": false,
103
+ "normalized": false,
104
+ "rstrip": false,
105
+ "single_word": false,
106
+ "special": true
107
+ },
108
+ "32010": {
109
+ "content": "<extra_id_89>",
110
+ "lstrip": false,
111
+ "normalized": false,
112
+ "rstrip": false,
113
+ "single_word": false,
114
+ "special": true
115
+ },
116
+ "32011": {
117
+ "content": "<extra_id_88>",
118
+ "lstrip": false,
119
+ "normalized": false,
120
+ "rstrip": false,
121
+ "single_word": false,
122
+ "special": true
123
+ },
124
+ "32012": {
125
+ "content": "<extra_id_87>",
126
+ "lstrip": false,
127
+ "normalized": false,
128
+ "rstrip": false,
129
+ "single_word": false,
130
+ "special": true
131
+ },
132
+ "32013": {
133
+ "content": "<extra_id_86>",
134
+ "lstrip": false,
135
+ "normalized": false,
136
+ "rstrip": false,
137
+ "single_word": false,
138
+ "special": true
139
+ },
140
+ "32014": {
141
+ "content": "<extra_id_85>",
142
+ "lstrip": false,
143
+ "normalized": false,
144
+ "rstrip": false,
145
+ "single_word": false,
146
+ "special": true
147
+ },
148
+ "32015": {
149
+ "content": "<extra_id_84>",
150
+ "lstrip": false,
151
+ "normalized": false,
152
+ "rstrip": false,
153
+ "single_word": false,
154
+ "special": true
155
+ },
156
+ "32016": {
157
+ "content": "<extra_id_83>",
158
+ "lstrip": false,
159
+ "normalized": false,
160
+ "rstrip": false,
161
+ "single_word": false,
162
+ "special": true
163
+ },
164
+ "32017": {
165
+ "content": "<extra_id_82>",
166
+ "lstrip": false,
167
+ "normalized": false,
168
+ "rstrip": false,
169
+ "single_word": false,
170
+ "special": true
171
+ },
172
+ "32018": {
173
+ "content": "<extra_id_81>",
174
+ "lstrip": false,
175
+ "normalized": false,
176
+ "rstrip": false,
177
+ "single_word": false,
178
+ "special": true
179
+ },
180
+ "32019": {
181
+ "content": "<extra_id_80>",
182
+ "lstrip": false,
183
+ "normalized": false,
184
+ "rstrip": false,
185
+ "single_word": false,
186
+ "special": true
187
+ },
188
+ "32020": {
189
+ "content": "<extra_id_79>",
190
+ "lstrip": false,
191
+ "normalized": false,
192
+ "rstrip": false,
193
+ "single_word": false,
194
+ "special": true
195
+ },
196
+ "32021": {
197
+ "content": "<extra_id_78>",
198
+ "lstrip": false,
199
+ "normalized": false,
200
+ "rstrip": false,
201
+ "single_word": false,
202
+ "special": true
203
+ },
204
+ "32022": {
205
+ "content": "<extra_id_77>",
206
+ "lstrip": false,
207
+ "normalized": false,
208
+ "rstrip": false,
209
+ "single_word": false,
210
+ "special": true
211
+ },
212
+ "32023": {
213
+ "content": "<extra_id_76>",
214
+ "lstrip": false,
215
+ "normalized": false,
216
+ "rstrip": false,
217
+ "single_word": false,
218
+ "special": true
219
+ },
220
+ "32024": {
221
+ "content": "<extra_id_75>",
222
+ "lstrip": false,
223
+ "normalized": false,
224
+ "rstrip": false,
225
+ "single_word": false,
226
+ "special": true
227
+ },
228
+ "32025": {
229
+ "content": "<extra_id_74>",
230
+ "lstrip": false,
231
+ "normalized": false,
232
+ "rstrip": false,
233
+ "single_word": false,
234
+ "special": true
235
+ },
236
+ "32026": {
237
+ "content": "<extra_id_73>",
238
+ "lstrip": false,
239
+ "normalized": false,
240
+ "rstrip": false,
241
+ "single_word": false,
242
+ "special": true
243
+ },
244
+ "32027": {
245
+ "content": "<extra_id_72>",
246
+ "lstrip": false,
247
+ "normalized": false,
248
+ "rstrip": false,
249
+ "single_word": false,
250
+ "special": true
251
+ },
252
+ "32028": {
253
+ "content": "<extra_id_71>",
254
+ "lstrip": false,
255
+ "normalized": false,
256
+ "rstrip": false,
257
+ "single_word": false,
258
+ "special": true
259
+ },
260
+ "32029": {
261
+ "content": "<extra_id_70>",
262
+ "lstrip": false,
263
+ "normalized": false,
264
+ "rstrip": false,
265
+ "single_word": false,
266
+ "special": true
267
+ },
268
+ "32030": {
269
+ "content": "<extra_id_69>",
270
+ "lstrip": false,
271
+ "normalized": false,
272
+ "rstrip": false,
273
+ "single_word": false,
274
+ "special": true
275
+ },
276
+ "32031": {
277
+ "content": "<extra_id_68>",
278
+ "lstrip": false,
279
+ "normalized": false,
280
+ "rstrip": false,
281
+ "single_word": false,
282
+ "special": true
283
+ },
284
+ "32032": {
285
+ "content": "<extra_id_67>",
286
+ "lstrip": false,
287
+ "normalized": false,
288
+ "rstrip": false,
289
+ "single_word": false,
290
+ "special": true
291
+ },
292
+ "32033": {
293
+ "content": "<extra_id_66>",
294
+ "lstrip": false,
295
+ "normalized": false,
296
+ "rstrip": false,
297
+ "single_word": false,
298
+ "special": true
299
+ },
300
+ "32034": {
301
+ "content": "<extra_id_65>",
302
+ "lstrip": false,
303
+ "normalized": false,
304
+ "rstrip": false,
305
+ "single_word": false,
306
+ "special": true
307
+ },
308
+ "32035": {
309
+ "content": "<extra_id_64>",
310
+ "lstrip": false,
311
+ "normalized": false,
312
+ "rstrip": false,
313
+ "single_word": false,
314
+ "special": true
315
+ },
316
+ "32036": {
317
+ "content": "<extra_id_63>",
318
+ "lstrip": false,
319
+ "normalized": false,
320
+ "rstrip": false,
321
+ "single_word": false,
322
+ "special": true
323
+ },
324
+ "32037": {
325
+ "content": "<extra_id_62>",
326
+ "lstrip": false,
327
+ "normalized": false,
328
+ "rstrip": false,
329
+ "single_word": false,
330
+ "special": true
331
+ },
332
+ "32038": {
333
+ "content": "<extra_id_61>",
334
+ "lstrip": false,
335
+ "normalized": false,
336
+ "rstrip": false,
337
+ "single_word": false,
338
+ "special": true
339
+ },
340
+ "32039": {
341
+ "content": "<extra_id_60>",
342
+ "lstrip": false,
343
+ "normalized": false,
344
+ "rstrip": false,
345
+ "single_word": false,
346
+ "special": true
347
+ },
348
+ "32040": {
349
+ "content": "<extra_id_59>",
350
+ "lstrip": false,
351
+ "normalized": false,
352
+ "rstrip": false,
353
+ "single_word": false,
354
+ "special": true
355
+ },
356
+ "32041": {
357
+ "content": "<extra_id_58>",
358
+ "lstrip": false,
359
+ "normalized": false,
360
+ "rstrip": false,
361
+ "single_word": false,
362
+ "special": true
363
+ },
364
+ "32042": {
365
+ "content": "<extra_id_57>",
366
+ "lstrip": false,
367
+ "normalized": false,
368
+ "rstrip": false,
369
+ "single_word": false,
370
+ "special": true
371
+ },
372
+ "32043": {
373
+ "content": "<extra_id_56>",
374
+ "lstrip": false,
375
+ "normalized": false,
376
+ "rstrip": false,
377
+ "single_word": false,
378
+ "special": true
379
+ },
380
+ "32044": {
381
+ "content": "<extra_id_55>",
382
+ "lstrip": false,
383
+ "normalized": false,
384
+ "rstrip": false,
385
+ "single_word": false,
386
+ "special": true
387
+ },
388
+ "32045": {
389
+ "content": "<extra_id_54>",
390
+ "lstrip": false,
391
+ "normalized": false,
392
+ "rstrip": false,
393
+ "single_word": false,
394
+ "special": true
395
+ },
396
+ "32046": {
397
+ "content": "<extra_id_53>",
398
+ "lstrip": false,
399
+ "normalized": false,
400
+ "rstrip": false,
401
+ "single_word": false,
402
+ "special": true
403
+ },
404
+ "32047": {
405
+ "content": "<extra_id_52>",
406
+ "lstrip": false,
407
+ "normalized": false,
408
+ "rstrip": false,
409
+ "single_word": false,
410
+ "special": true
411
+ },
412
+ "32048": {
413
+ "content": "<extra_id_51>",
414
+ "lstrip": false,
415
+ "normalized": false,
416
+ "rstrip": false,
417
+ "single_word": false,
418
+ "special": true
419
+ },
420
+ "32049": {
421
+ "content": "<extra_id_50>",
422
+ "lstrip": false,
423
+ "normalized": false,
424
+ "rstrip": false,
425
+ "single_word": false,
426
+ "special": true
427
+ },
428
+ "32050": {
429
+ "content": "<extra_id_49>",
430
+ "lstrip": false,
431
+ "normalized": false,
432
+ "rstrip": false,
433
+ "single_word": false,
434
+ "special": true
435
+ },
436
+ "32051": {
437
+ "content": "<extra_id_48>",
438
+ "lstrip": false,
439
+ "normalized": false,
440
+ "rstrip": false,
441
+ "single_word": false,
442
+ "special": true
443
+ },
444
+ "32052": {
445
+ "content": "<extra_id_47>",
446
+ "lstrip": false,
447
+ "normalized": false,
448
+ "rstrip": false,
449
+ "single_word": false,
450
+ "special": true
451
+ },
452
+ "32053": {
453
+ "content": "<extra_id_46>",
454
+ "lstrip": false,
455
+ "normalized": false,
456
+ "rstrip": false,
457
+ "single_word": false,
458
+ "special": true
459
+ },
460
+ "32054": {
461
+ "content": "<extra_id_45>",
462
+ "lstrip": false,
463
+ "normalized": false,
464
+ "rstrip": false,
465
+ "single_word": false,
466
+ "special": true
467
+ },
468
+ "32055": {
469
+ "content": "<extra_id_44>",
470
+ "lstrip": false,
471
+ "normalized": false,
472
+ "rstrip": false,
473
+ "single_word": false,
474
+ "special": true
475
+ },
476
+ "32056": {
477
+ "content": "<extra_id_43>",
478
+ "lstrip": false,
479
+ "normalized": false,
480
+ "rstrip": false,
481
+ "single_word": false,
482
+ "special": true
483
+ },
484
+ "32057": {
485
+ "content": "<extra_id_42>",
486
+ "lstrip": false,
487
+ "normalized": false,
488
+ "rstrip": false,
489
+ "single_word": false,
490
+ "special": true
491
+ },
492
+ "32058": {
493
+ "content": "<extra_id_41>",
494
+ "lstrip": false,
495
+ "normalized": false,
496
+ "rstrip": false,
497
+ "single_word": false,
498
+ "special": true
499
+ },
500
+ "32059": {
501
+ "content": "<extra_id_40>",
502
+ "lstrip": false,
503
+ "normalized": false,
504
+ "rstrip": false,
505
+ "single_word": false,
506
+ "special": true
507
+ },
508
+ "32060": {
509
+ "content": "<extra_id_39>",
510
+ "lstrip": false,
511
+ "normalized": false,
512
+ "rstrip": false,
513
+ "single_word": false,
514
+ "special": true
515
+ },
516
+ "32061": {
517
+ "content": "<extra_id_38>",
518
+ "lstrip": false,
519
+ "normalized": false,
520
+ "rstrip": false,
521
+ "single_word": false,
522
+ "special": true
523
+ },
524
+ "32062": {
525
+ "content": "<extra_id_37>",
526
+ "lstrip": false,
527
+ "normalized": false,
528
+ "rstrip": false,
529
+ "single_word": false,
530
+ "special": true
531
+ },
532
+ "32063": {
533
+ "content": "<extra_id_36>",
534
+ "lstrip": false,
535
+ "normalized": false,
536
+ "rstrip": false,
537
+ "single_word": false,
538
+ "special": true
539
+ },
540
+ "32064": {
541
+ "content": "<extra_id_35>",
542
+ "lstrip": false,
543
+ "normalized": false,
544
+ "rstrip": false,
545
+ "single_word": false,
546
+ "special": true
547
+ },
548
+ "32065": {
549
+ "content": "<extra_id_34>",
550
+ "lstrip": false,
551
+ "normalized": false,
552
+ "rstrip": false,
553
+ "single_word": false,
554
+ "special": true
555
+ },
556
+ "32066": {
557
+ "content": "<extra_id_33>",
558
+ "lstrip": false,
559
+ "normalized": false,
560
+ "rstrip": false,
561
+ "single_word": false,
562
+ "special": true
563
+ },
564
+ "32067": {
565
+ "content": "<extra_id_32>",
566
+ "lstrip": false,
567
+ "normalized": false,
568
+ "rstrip": false,
569
+ "single_word": false,
570
+ "special": true
571
+ },
572
+ "32068": {
573
+ "content": "<extra_id_31>",
574
+ "lstrip": false,
575
+ "normalized": false,
576
+ "rstrip": false,
577
+ "single_word": false,
578
+ "special": true
579
+ },
580
+ "32069": {
581
+ "content": "<extra_id_30>",
582
+ "lstrip": false,
583
+ "normalized": false,
584
+ "rstrip": false,
585
+ "single_word": false,
586
+ "special": true
587
+ },
588
+ "32070": {
589
+ "content": "<extra_id_29>",
590
+ "lstrip": false,
591
+ "normalized": false,
592
+ "rstrip": false,
593
+ "single_word": false,
594
+ "special": true
595
+ },
596
+ "32071": {
597
+ "content": "<extra_id_28>",
598
+ "lstrip": false,
599
+ "normalized": false,
600
+ "rstrip": false,
601
+ "single_word": false,
602
+ "special": true
603
+ },
604
+ "32072": {
605
+ "content": "<extra_id_27>",
606
+ "lstrip": false,
607
+ "normalized": false,
608
+ "rstrip": false,
609
+ "single_word": false,
610
+ "special": true
611
+ },
612
+ "32073": {
613
+ "content": "<extra_id_26>",
614
+ "lstrip": false,
615
+ "normalized": false,
616
+ "rstrip": false,
617
+ "single_word": false,
618
+ "special": true
619
+ },
620
+ "32074": {
621
+ "content": "<extra_id_25>",
622
+ "lstrip": false,
623
+ "normalized": false,
624
+ "rstrip": false,
625
+ "single_word": false,
626
+ "special": true
627
+ },
628
+ "32075": {
629
+ "content": "<extra_id_24>",
630
+ "lstrip": false,
631
+ "normalized": false,
632
+ "rstrip": false,
633
+ "single_word": false,
634
+ "special": true
635
+ },
636
+ "32076": {
637
+ "content": "<extra_id_23>",
638
+ "lstrip": false,
639
+ "normalized": false,
640
+ "rstrip": false,
641
+ "single_word": false,
642
+ "special": true
643
+ },
644
+ "32077": {
645
+ "content": "<extra_id_22>",
646
+ "lstrip": false,
647
+ "normalized": false,
648
+ "rstrip": false,
649
+ "single_word": false,
650
+ "special": true
651
+ },
652
+ "32078": {
653
+ "content": "<extra_id_21>",
654
+ "lstrip": false,
655
+ "normalized": false,
656
+ "rstrip": false,
657
+ "single_word": false,
658
+ "special": true
659
+ },
660
+ "32079": {
661
+ "content": "<extra_id_20>",
662
+ "lstrip": false,
663
+ "normalized": false,
664
+ "rstrip": false,
665
+ "single_word": false,
666
+ "special": true
667
+ },
668
+ "32080": {
669
+ "content": "<extra_id_19>",
670
+ "lstrip": false,
671
+ "normalized": false,
672
+ "rstrip": false,
673
+ "single_word": false,
674
+ "special": true
675
+ },
676
+ "32081": {
677
+ "content": "<extra_id_18>",
678
+ "lstrip": false,
679
+ "normalized": false,
680
+ "rstrip": false,
681
+ "single_word": false,
682
+ "special": true
683
+ },
684
+ "32082": {
685
+ "content": "<extra_id_17>",
686
+ "lstrip": false,
687
+ "normalized": false,
688
+ "rstrip": false,
689
+ "single_word": false,
690
+ "special": true
691
+ },
692
+ "32083": {
693
+ "content": "<extra_id_16>",
694
+ "lstrip": false,
695
+ "normalized": false,
696
+ "rstrip": false,
697
+ "single_word": false,
698
+ "special": true
699
+ },
700
+ "32084": {
701
+ "content": "<extra_id_15>",
702
+ "lstrip": false,
703
+ "normalized": false,
704
+ "rstrip": false,
705
+ "single_word": false,
706
+ "special": true
707
+ },
708
+ "32085": {
709
+ "content": "<extra_id_14>",
710
+ "lstrip": false,
711
+ "normalized": false,
712
+ "rstrip": false,
713
+ "single_word": false,
714
+ "special": true
715
+ },
716
+ "32086": {
717
+ "content": "<extra_id_13>",
718
+ "lstrip": false,
719
+ "normalized": false,
720
+ "rstrip": false,
721
+ "single_word": false,
722
+ "special": true
723
+ },
724
+ "32087": {
725
+ "content": "<extra_id_12>",
726
+ "lstrip": false,
727
+ "normalized": false,
728
+ "rstrip": false,
729
+ "single_word": false,
730
+ "special": true
731
+ },
732
+ "32088": {
733
+ "content": "<extra_id_11>",
734
+ "lstrip": false,
735
+ "normalized": false,
736
+ "rstrip": false,
737
+ "single_word": false,
738
+ "special": true
739
+ },
740
+ "32089": {
741
+ "content": "<extra_id_10>",
742
+ "lstrip": false,
743
+ "normalized": false,
744
+ "rstrip": false,
745
+ "single_word": false,
746
+ "special": true
747
+ },
748
+ "32090": {
749
+ "content": "<extra_id_9>",
750
+ "lstrip": false,
751
+ "normalized": false,
752
+ "rstrip": false,
753
+ "single_word": false,
754
+ "special": true
755
+ },
756
+ "32091": {
757
+ "content": "<extra_id_8>",
758
+ "lstrip": false,
759
+ "normalized": false,
760
+ "rstrip": false,
761
+ "single_word": false,
762
+ "special": true
763
+ },
764
+ "32092": {
765
+ "content": "<extra_id_7>",
766
+ "lstrip": false,
767
+ "normalized": false,
768
+ "rstrip": false,
769
+ "single_word": false,
770
+ "special": true
771
+ },
772
+ "32093": {
773
+ "content": "<extra_id_6>",
774
+ "lstrip": false,
775
+ "normalized": false,
776
+ "rstrip": false,
777
+ "single_word": false,
778
+ "special": true
779
+ },
780
+ "32094": {
781
+ "content": "<extra_id_5>",
782
+ "lstrip": false,
783
+ "normalized": false,
784
+ "rstrip": false,
785
+ "single_word": false,
786
+ "special": true
787
+ },
788
+ "32095": {
789
+ "content": "<extra_id_4>",
790
+ "lstrip": false,
791
+ "normalized": false,
792
+ "rstrip": false,
793
+ "single_word": false,
794
+ "special": true
795
+ },
796
+ "32096": {
797
+ "content": "<extra_id_3>",
798
+ "lstrip": false,
799
+ "normalized": false,
800
+ "rstrip": false,
801
+ "single_word": false,
802
+ "special": true
803
+ },
804
+ "32097": {
805
+ "content": "<extra_id_2>",
806
+ "lstrip": false,
807
+ "normalized": false,
808
+ "rstrip": false,
809
+ "single_word": false,
810
+ "special": true
811
+ },
812
+ "32098": {
813
+ "content": "<extra_id_1>",
814
+ "lstrip": false,
815
+ "normalized": false,
816
+ "rstrip": false,
817
+ "single_word": false,
818
+ "special": true
819
+ },
820
+ "32099": {
821
+ "content": "<extra_id_0>",
822
+ "lstrip": false,
823
+ "normalized": false,
824
+ "rstrip": false,
825
+ "single_word": false,
826
+ "special": true
827
+ }
828
+ },
829
+ "additional_special_tokens": [
830
+ "<extra_id_0>",
831
+ "<extra_id_1>",
832
+ "<extra_id_2>",
833
+ "<extra_id_3>",
834
+ "<extra_id_4>",
835
+ "<extra_id_5>",
836
+ "<extra_id_6>",
837
+ "<extra_id_7>",
838
+ "<extra_id_8>",
839
+ "<extra_id_9>",
840
+ "<extra_id_10>",
841
+ "<extra_id_11>",
842
+ "<extra_id_12>",
843
+ "<extra_id_13>",
844
+ "<extra_id_14>",
845
+ "<extra_id_15>",
846
+ "<extra_id_16>",
847
+ "<extra_id_17>",
848
+ "<extra_id_18>",
849
+ "<extra_id_19>",
850
+ "<extra_id_20>",
851
+ "<extra_id_21>",
852
+ "<extra_id_22>",
853
+ "<extra_id_23>",
854
+ "<extra_id_24>",
855
+ "<extra_id_25>",
856
+ "<extra_id_26>",
857
+ "<extra_id_27>",
858
+ "<extra_id_28>",
859
+ "<extra_id_29>",
860
+ "<extra_id_30>",
861
+ "<extra_id_31>",
862
+ "<extra_id_32>",
863
+ "<extra_id_33>",
864
+ "<extra_id_34>",
865
+ "<extra_id_35>",
866
+ "<extra_id_36>",
867
+ "<extra_id_37>",
868
+ "<extra_id_38>",
869
+ "<extra_id_39>",
870
+ "<extra_id_40>",
871
+ "<extra_id_41>",
872
+ "<extra_id_42>",
873
+ "<extra_id_43>",
874
+ "<extra_id_44>",
875
+ "<extra_id_45>",
876
+ "<extra_id_46>",
877
+ "<extra_id_47>",
878
+ "<extra_id_48>",
879
+ "<extra_id_49>",
880
+ "<extra_id_50>",
881
+ "<extra_id_51>",
882
+ "<extra_id_52>",
883
+ "<extra_id_53>",
884
+ "<extra_id_54>",
885
+ "<extra_id_55>",
886
+ "<extra_id_56>",
887
+ "<extra_id_57>",
888
+ "<extra_id_58>",
889
+ "<extra_id_59>",
890
+ "<extra_id_60>",
891
+ "<extra_id_61>",
892
+ "<extra_id_62>",
893
+ "<extra_id_63>",
894
+ "<extra_id_64>",
895
+ "<extra_id_65>",
896
+ "<extra_id_66>",
897
+ "<extra_id_67>",
898
+ "<extra_id_68>",
899
+ "<extra_id_69>",
900
+ "<extra_id_70>",
901
+ "<extra_id_71>",
902
+ "<extra_id_72>",
903
+ "<extra_id_73>",
904
+ "<extra_id_74>",
905
+ "<extra_id_75>",
906
+ "<extra_id_76>",
907
+ "<extra_id_77>",
908
+ "<extra_id_78>",
909
+ "<extra_id_79>",
910
+ "<extra_id_80>",
911
+ "<extra_id_81>",
912
+ "<extra_id_82>",
913
+ "<extra_id_83>",
914
+ "<extra_id_84>",
915
+ "<extra_id_85>",
916
+ "<extra_id_86>",
917
+ "<extra_id_87>",
918
+ "<extra_id_88>",
919
+ "<extra_id_89>",
920
+ "<extra_id_90>",
921
+ "<extra_id_91>",
922
+ "<extra_id_92>",
923
+ "<extra_id_93>",
924
+ "<extra_id_94>",
925
+ "<extra_id_95>",
926
+ "<extra_id_96>",
927
+ "<extra_id_97>",
928
+ "<extra_id_98>",
929
+ "<extra_id_99>"
930
+ ],
931
+ "clean_up_tokenization_spaces": true,
932
+ "eos_token": "</s>",
933
+ "extra_ids": 100,
934
+ "extra_special_tokens": {},
935
+ "legacy": true,
936
+ "model_max_length": 512,
937
+ "pad_token": "<pad>",
938
+ "sp_model_kwargs": {},
939
+ "tokenizer_class": "T5Tokenizer",
940
+ "unk_token": "<unk>"
941
+ }
humanize.py ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """humanize-text-model inference wrapper (bilingual: English + Chinese).
2
+
3
+ Loads the Lynote bilingual humanizer — two small seq2seq checkpoints in one
4
+ repo (``en/`` T5-small, ``zh/`` Chinese T5-small) — and routes each input to
5
+ the right language model. URLs, numbers, paths, code and quoted strings are
6
+ protected with tokenizer-friendly placeholders before generation and restored
7
+ afterwards.
8
+
9
+ Usage:
10
+ from humanize import Humanizer
11
+ h = Humanizer() # loads "Danny-Lynote/humanize-text-model" from the Hub
12
+ print(h("It is important to note that this robust solution serves as a testament to our commitment."))
13
+ """
14
+
15
+ import argparse
16
+ import os
17
+ import re
18
+ import sys
19
+ from typing import List, Optional
20
+
21
+ import torch
22
+ from transformers import BertTokenizer, T5ForConditionalGeneration, T5Tokenizer
23
+
24
+ DEFAULT_MODEL = "Danny-Lynote/humanize-text-model"
25
+ MAX_CHARS = 4_000
26
+ MAX_TOKENS = 256
27
+ DEVICE = "mps" if torch.backends.mps.is_available() else (
28
+ "cuda" if torch.cuda.is_available() else "cpu"
29
+ )
30
+
31
+ PROTECTED_PATTERN = re.compile(
32
+ r"(`[^`]+`|https?://[^\s\u4e00-\u9fff,。!?;:、]+|(?:[A-Za-z]:)?(?:[/\\][\w.\-]+)+|"
33
+ r"\b\d+(?:\.\d+)?(?:%|[A-Za-z]+)?\b|\"[^\"]*\"|'[^']*')"
34
+ )
35
+ CJK_RATIO = re.compile(r"[\u4e00-\u9fff]")
36
+
37
+
38
+ def protect(text: str, language: Optional[str] = None):
39
+ """Replace protected spans with language-aware placeholders:
40
+ EN ``PROTECTED_n``, ZH ``【保护n】`` (both tokenizer-friendly)."""
41
+ protected = []
42
+ zh = language == "zh" or (language is None and is_chinese(text))
43
+
44
+ def replace(match):
45
+ token = f"【保护{len(protected)}】" if zh else f"PROTECTED_{len(protected)}"
46
+ protected.append(match.group(0))
47
+ return token
48
+
49
+ return PROTECTED_PATTERN.sub(replace, text), protected
50
+
51
+
52
+ def restore(text: str, protected: List[str]) -> str:
53
+ """Restore placeholders (either format, tolerant of inserted spaces)."""
54
+ for index, value in enumerate(protected):
55
+ text = re.sub(rf"PROTECTED_{index}\b", lambda m: value, text)
56
+ text = re.sub(
57
+ rf"【\s*保\s*护\s*{index}\s*】", lambda m: value, text
58
+ )
59
+ return text
60
+
61
+
62
+ def is_chinese(text: str, threshold: float = 0.05) -> bool:
63
+ total = len(re.sub(r"\s+", "", text))
64
+ if total == 0:
65
+ return False
66
+ return len(CJK_RATIO.findall(text)) / total > threshold
67
+
68
+
69
+ class Humanizer:
70
+ def __init__(
71
+ self,
72
+ model_id: str = DEFAULT_MODEL,
73
+ device: Optional[str] = None,
74
+ cache_dir: Optional[str] = None,
75
+ local_files_only: bool = False,
76
+ hf_token: Optional[str] = None,
77
+ lazy: bool = True,
78
+ ):
79
+ """model_id is a repo (or local dir) containing ``en/`` and ``zh/``
80
+ subdirectories with separate checkpoints. With ``lazy=True`` (default)
81
+ a language model is only loaded when first needed."""
82
+ self.model_id = model_id
83
+ self.device = torch.device(device or DEVICE)
84
+ self._models = {}
85
+ self._tokenizers = {}
86
+ self._cache_dir = cache_dir
87
+ self._local_only = local_files_only
88
+ self._token = hf_token
89
+ if not lazy:
90
+ self._get("en")
91
+ self._get("zh")
92
+
93
+ def _load(self, language: str, model: T5ForConditionalGeneration, tokenizer: T5Tokenizer):
94
+ self._models[language] = model
95
+ self._tokenizers[language] = tokenizer
96
+
97
+ def _get(self, language: str):
98
+ if language not in self._models:
99
+ sub = f"{self.model_id}/{language}"
100
+ model = T5ForConditionalGeneration.from_pretrained(
101
+ sub, cache_dir=self._cache_dir,
102
+ local_files_only=self._local_only, token=self._token,
103
+ )
104
+ tokenizer_cls = BertTokenizer if language == "zh" else T5Tokenizer
105
+ tokenizer = tokenizer_cls.from_pretrained(
106
+ sub, cache_dir=self._cache_dir,
107
+ local_files_only=self._local_only, token=self._token,
108
+ )
109
+ model.to(self.device).eval()
110
+ self._load(language, model, tokenizer)
111
+ return self._models[language], self._tokenizers[language]
112
+
113
+ def humanize(self, text: str, num_beams: int = 3, do_sample: bool = False) -> str:
114
+ text = (text or "").strip()
115
+ if not text:
116
+ raise ValueError("Input text is empty.")
117
+ if len(text) > MAX_CHARS:
118
+ raise ValueError(f"Input exceeds {MAX_CHARS:,} characters.")
119
+ return self.humanize_batch([text], num_beams=num_beams, do_sample=do_sample)[0]
120
+
121
+ def humanize_batch(
122
+ self,
123
+ texts: List[str],
124
+ num_beams: int = 3,
125
+ do_sample: bool = False,
126
+ batch_size: int = 8,
127
+ ) -> List[str]:
128
+ """Humanize a list of texts. URLs, numbers, paths, code and quotes are
129
+ protected per-text with placeholders and restored after generation."""
130
+ masked = []
131
+ protected_all = []
132
+ for t in texts:
133
+ masked_text, protected = protect(t)
134
+ masked.append(masked_text)
135
+ protected_all.append(protected)
136
+ outputs: List[str] = []
137
+ grouped: dict = {}
138
+ for i, t in enumerate(masked):
139
+ grouped.setdefault("zh" if is_chinese(t) else "en", []).append(i)
140
+ for language, indices in grouped.items():
141
+ model, tokenizer = self._get(language)
142
+ for start in range(0, len(indices), batch_size):
143
+ chunk_idx = indices[start : start + batch_size]
144
+ enc = tokenizer(
145
+ [masked[i] for i in chunk_idx],
146
+ max_length=MAX_TOKENS,
147
+ truncation=True,
148
+ padding=True,
149
+ return_tensors="pt",
150
+ )
151
+ if "token_type_ids" in enc:
152
+ enc.pop("token_type_ids")
153
+ enc = enc.to(self.device)
154
+ with torch.no_grad():
155
+ gen_kwargs = dict(
156
+ num_beams=num_beams,
157
+ do_sample=do_sample,
158
+ early_stopping=True,
159
+ )
160
+ if language == "zh":
161
+ # Small Chinese T5 degenerates on long sequences: cap
162
+ # length and block exact 4-gram repetition (EN does not
163
+ # need these).
164
+ gen_kwargs["max_length"] = 40
165
+ gen_kwargs["no_repeat_ngram_size"] = 4
166
+ else:
167
+ gen_kwargs["max_length"] = MAX_TOKENS
168
+ gen = model.generate(**enc, **gen_kwargs)
169
+ decoded = tokenizer.batch_decode(gen, skip_special_tokens=True)
170
+ for j, text in zip(chunk_idx, decoded):
171
+ outputs.append((j, text))
172
+ outputs.sort(key=lambda x: x[0])
173
+
174
+ return [
175
+ restore(self._polish(out), protected)
176
+ for out, protected in zip([o[1] for o in outputs], protected_all)
177
+ ]
178
+
179
+ @staticmethod
180
+ def _polish(text: str) -> str:
181
+ text = re.sub(r"^\s*(?:extra\d+\s*[,,。\s]*)+", "", text)
182
+ text = re.sub(r"(?<=[\u4e00-\u9fff])\s+(?=[\u4e00-\u9fff,。;:!?])", "", text)
183
+ text = re.sub(r"(?<=[,。;:!?])\s+(?=[\u4e00-\u9fff])", "", text)
184
+ text = re.sub(r"[ \t]{2,}", " ", text)
185
+ text = re.sub(r"\s+([,.;:!?,。;:!?])", r"\1", text)
186
+ text = re.sub(r"[,;]\s*\.", ".", text)
187
+ text = re.sub(r"\.\s*[,;]", ".", text)
188
+ text = re.sub(r"[,,]\s*(?=[。!?.!?])", "", text)
189
+ text = re.sub(r"\.\s+([a-z])", lambda m: ". " + m.group(1).upper(), text)
190
+ return text.strip(" ,")
191
+
192
+
193
+ def main():
194
+ parser = argparse.ArgumentParser(description="Bilingual text humanizer (T5-small EN + Chinese T5-small)")
195
+ parser.add_argument("--text", help="text to humanize")
196
+ parser.add_argument("--input", help="path to a text file")
197
+ parser.add_argument("--output", help="write result to a file")
198
+ parser.add_argument("--model", default=DEFAULT_MODEL, help="model id or local dir")
199
+ parser.add_argument("--beams", type=int, default=3)
200
+ parser.add_argument("--list-models", action="store_true", help="print model ids and exit")
201
+ args = parser.parse_args()
202
+
203
+ if args.list_models:
204
+ print(DEFAULT_MODEL)
205
+ return
206
+
207
+ if args.text and args.input:
208
+ parser.error("provide only one of --text / --input")
209
+ source = args.text or (open(args.input, encoding="utf-8").read() if args.input else None)
210
+ if not source:
211
+ parser.error("provide --text or --input")
212
+
213
+ humanizer = Humanizer(model_id=args.model)
214
+ result = humanizer.humanize(source, num_beams=args.beams)
215
+ if args.output:
216
+ with open(args.output, "w", encoding="utf-8") as f:
217
+ f.write(result)
218
+ print(f"written to {args.output}")
219
+ else:
220
+ print(result)
221
+
222
+
223
+ if __name__ == "__main__":
224
+ main()
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ transformers>=4.46
2
+ torch>=2.2
3
+ sacrebleu>=2.4
4
+ pytest>=8.0
scripts/build_dataset.py ADDED
@@ -0,0 +1,335 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Build the synthetic bilingual training corpus for the humanize model.
2
+
3
+ Methodology
4
+ -----------
5
+ Supervision comes from the conservative rule engine in ``src/rules.py``
6
+ (derived from the lynote-ai reference projects). We generate:
7
+
8
+ 1. **AI -> human**: formulaic clause combinations rewritten by the rules,
9
+ 2. **Human -> human (identity)**: clean prose unchanged, so the model learns
10
+ not to rewrite already-human text,
11
+ 3. **Mixed**: clean prose with one injected AI cliché that must be removed,
12
+ 4. **Protected spans**: examples containing numbers, URLs and quoted text.
13
+
14
+ Every input maps to exactly one deterministic target (one-to-one), which
15
+ keeps the training signal consistent. Outputs are seeded JSONL files with
16
+ fields ``input_text``, ``output_text``, ``lang``.
17
+ """
18
+
19
+ import argparse
20
+ import json
21
+ import random
22
+ import re
23
+ import sys
24
+ from pathlib import Path
25
+
26
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
27
+ from src.rules import EN_PHRASES, ZH_PHRASES, humanize_rules, protect
28
+
29
+ # --------------------------------------------------------------------------
30
+ # Seed prose (curated, human-flavored).
31
+ # --------------------------------------------------------------------------
32
+
33
+ EN_SEEDS = [
34
+ "I walked the dog this morning and the sky was grey and low.",
35
+ "Halfway through the park it started raining, which I hadn't expected, so we ran home.",
36
+ "The dog seemed to enjoy it more than me.",
37
+ "When we got back I made coffee and watched the rain run down the window.",
38
+ "Thanks for sending the report over, the numbers look good.",
39
+ "Could you double-check the retention section before we share it?",
40
+ "Let's grab coffee this week and talk through next steps.",
41
+ "The conference was exhausting, but a couple of the sessions were genuinely useful.",
42
+ "I came home with a notebook full of scribbles and a dozen open browser tabs.",
43
+ "The sky cleared by evening and we went for a long walk.",
44
+ "I've been testing the new tool for a few days and it mostly works.",
45
+ "The docs are clear enough, though the install steps could be shorter.",
46
+ "We tried the new approach on a small project and it held up fine.",
47
+ "My laptop died in the middle of the meeting, which was awkward.",
48
+ "She asked me to review her draft and I found a few typos.",
49
+ "The traffic was bad this morning, so I took the subway instead.",
50
+ "We spent the afternoon fixing bugs and ordering pizza.",
51
+ "I think the plan works, but we should test it with real users first.",
52
+ "The garden looks great after the rain.",
53
+ "He replied faster than I expected, which was a nice surprise.",
54
+ "The new hire seems sharp and asks good questions.",
55
+ "I read the whole chapter on the train and highlighted half of it.",
56
+ "We moved the deadline to Friday so nobody has to work the weekend.",
57
+ "The cafe near the office makes a decent flat white.",
58
+ "My phone died, so I missed your call, sorry about that.",
59
+ "She asked me to review her draft and I found a few typos. It was a nice read.",
60
+ "I think the plan works, but we should test it with real users first. We can start next week.",
61
+ ]
62
+
63
+ ZH_SEEDS = [
64
+ "今天早上我带狗去公园,出门时天阴阴的。",
65
+ "走到一半忽然下起雨,我们只好跑回家,狗倒是挺高兴。",
66
+ "回来以后我煮了杯咖啡,坐在窗边看了会儿雨。",
67
+ "你要的资料我发你邮箱了,附件里有两个版本。",
68
+ "你看一下哪个合适,有问题随时叫我。",
69
+ "下午我跟老王聊了聊,他觉得可以,但预算要再压一压。",
70
+ "今天加班到九点,回来路上吃了碗牛肉面。",
71
+ "老板认得我,多给了几片牛肉。",
72
+ "猫蹲在门口等我,尾巴翘得老高。",
73
+ "周末去了趟公园,人特别多,树荫下倒是凉快。",
74
+ "家里的路由器坏了,折腾了一晚上也没修好。",
75
+ "这个月房租涨了两百块,心里有点不爽。",
76
+ "我在楼下便利店买了瓶水,顺便取了个快递。",
77
+ "下周开会的内容我还没准备完,今晚得加个班。",
78
+ "邻居家的狗最近晚上老叫,吵得人睡不着。",
79
+ "上次说的那个方案,客户反馈还不错,就是工期太紧。",
80
+ "我今天试着做了道红烧肉,味道还行,就是有点咸。",
81
+ "地铁口新开了一家奶茶店,排队的人特别多。",
82
+ "天气预报说明天降温,出门记得多穿点。",
83
+ "我把旧手机的照片都备份到电脑上了。",
84
+ "新来的同事挺靠谱的,交办的事情都能按时完成。",
85
+ "超市打折的时候我囤了不少纸巾和洗衣液。",
86
+ "这个项目其实不难,主要是沟通起来比较费劲。",
87
+ "楼下的早餐店换了老板,油条没有以前好吃了。",
88
+ "我在网上买了个书架,装了一个小时才装好。",
89
+ "妈妈打电话来说家里的桂花开了,让我回去看看。",
90
+ "这个周末打算去爬山,好久没运动了。",
91
+ "我把会议纪要整理��发到群里了,大家记得看。",
92
+ "打印机又卡纸了,修了半小时才弄好。",
93
+ "晚上的风挺大的,窗户被吹得呼呼响。",
94
+ "上个月的项目结项了,团队一起吃了顿饭庆祝。",
95
+ "小区里的银杏树黄了,特别好看。",
96
+ "我最近在学做菜,最拿手的是西红柿炒蛋。",
97
+ "公交车晚点了二十分钟,差点迟到。",
98
+ "这个月业绩不错,老板说要给大家发奖金。",
99
+ "我把书还回图书馆了,顺便借了两本新的。",
100
+ "朋友推荐了一家火锅店,说是味道特别好。",
101
+ "早上起晚了,来不及吃早饭,现在有点饿。",
102
+ "新买的耳机用着不错,降噪效果明显。",
103
+ "我把房间收拾了一遍,看着舒服多了。",
104
+ ]
105
+
106
+ # --------------------------------------------------------------------------
107
+ # Formulaic clause pools. Combinations of these produce many unique inputs;
108
+ # the rule engine maps each one to a deterministic human-flavored target.
109
+ # --------------------------------------------------------------------------
110
+
111
+ EN_SUBJECTS = [
112
+ "artificial intelligence", "digital transformation", "cloud computing",
113
+ "data analytics", "the new platform", "our organization", "this initiative",
114
+ "remote work", "machine learning", "automation", "the latest release",
115
+ "the training program", "our partnership", "the revised policy",
116
+ ]
117
+
118
+ EN_CLAUSES = [
119
+ "it is important to note that {s} plays a crucial role in business success",
120
+ "it is worth noting that leveraging {s} serves as a testament to our commitment to innovation",
121
+ "our comprehensive approach to {s} fosters collaboration and drives growth",
122
+ "the seamless integration of {s} enhances efficiency and delivers a robust solution",
123
+ "embracing {s} is no longer optional but essential for long-term competitiveness",
124
+ "{s} requires high-quality data to function effectively",
125
+ "our groundbreaking platform leverages cutting-edge technology to provide a seamless experience",
126
+ "the state-of-the-art solution plays a crucial role in improving operational efficiency",
127
+ "it is essential to delve into {s} in order to remain competitive",
128
+ "fostering a holistic approach to {s} is paramount",
129
+ "{s} transforms the way we work in a significant way",
130
+ "this game-changing innovation will revolutionize the industry",
131
+ ]
132
+
133
+ EN_CONNECTORS = ["Moreover,", "Furthermore,", "Additionally,", "In conclusion,"]
134
+
135
+ ZH_SUBJECTS = [
136
+ "数字化转型", "人工智能", "这套方案", "新的平台", "我们的团队",
137
+ "在线办公", "自动化流程", "数据治理", "这次改革", "人才培养",
138
+ ]
139
+
140
+ ZH_CLAUSES = [
141
+ "值得注意的是,{s}发挥着重要作用",
142
+ "在当今快速发展的时代,{s}是企业发展的重要保障",
143
+ "综上所述,我们通过赋能团队来助力企业实现降本增效",
144
+ "这一方案形成了完整的业务闭环,无缝连接各个环节",
145
+ "由此可见,{s}已经成为企业发展的必然趋势",
146
+ "总而言之,企业应当积极拥抱新技术,从而在激烈的竞争中保持优势",
147
+ "{s}对于企业来说至关重要",
148
+ "我们需要不断提升效率,持续优化流程",
149
+ "这一举措实现了闭环管理,进一步提升了整体效率",
150
+ "数字化转型为{s}提供了新的发展机遇",
151
+ "相关部门应该加快制定实施细则,确保各项措施落地见效",
152
+ "这一政策有助于优化资源配置,推动产业转型升级",
153
+ "我们要坚持以用户为中心,不断完善产品服务体系",
154
+ "项目团队克服了诸多困难,最终按期完成了交付任务",
155
+ "该平台整合了线上线下资源,覆盖了多个业务场景",
156
+ "通过数据驱动的方式,我们能够更好地洞察市场变化",
157
+ "本次会议明确了下一阶段的工作重点和发展方向",
158
+ "这套体系有效降低了运营成本,提高了响应速度",
159
+ "从长远来看,{s}将深刻改变行业的竞争格局",
160
+ "各方应加强沟通协作,共同推进相关工作的开展",
161
+ ]
162
+
163
+ PROTECTED_SEEDS = [
164
+ "The license key is ABC-12345 and the server is at https://api.lynote.ai/v1/detect.",
165
+ "The report shows a 23.5% increase in Q3, up from 18.2% last year.",
166
+ "Run `pip install humanize-text==1.4.2` and check /usr/local/bin/humanize.",
167
+ "The budget is $12,400 for this quarter, as noted in \"Project Lynote\".",
168
+ "The API accepts 500 requests per minute at https://hub.example.com and returns a 99.9% success rate.",
169
+ "The server rebooted at 03:47 and restored 1.2 TB of data within 10 minutes.",
170
+ "版本号是 v2.1.3,接口地址是 https://api.lynote.ai,成本下降了 15%。",
171
+ "配置文件放在 /etc/lynote/config.toml,端口是 8000,超时 30 秒。",
172
+ "上个月的收入是 85000 元,比预期多了 12%,主要来自 https://lynote.ai。",
173
+ "服务器在 03:47 重启,恢复了 1.2 TB 的数据,耗时 10 分钟。",
174
+ ]
175
+
176
+
177
+ def _ai_en(rng: random.Random, n: int) -> list[str]:
178
+ out = []
179
+ subjects = EN_SUBJECTS
180
+ while len(out) < n:
181
+ s = rng.choice(subjects)
182
+ k = rng.randint(2, 4)
183
+ clauses = [c.format(s=s) for c in rng.sample(EN_CLAUSES, k)]
184
+ clauses[0] = clauses[0][0].upper() + clauses[0][1:]
185
+ text = clauses[0]
186
+ for c in clauses[1:]:
187
+ text += ". " + rng.choice(EN_CONNECTORS) + " " + c
188
+ text += "."
189
+ if text not in out:
190
+ out.append(text)
191
+ return out
192
+
193
+
194
+ def _ai_zh(rng: random.Random, n: int) -> list[str]:
195
+ out = []
196
+ while len(out) < n:
197
+ s = rng.choice(ZH_SUBJECTS)
198
+ k = rng.randint(2, 4)
199
+ clauses = [c.format(s=s) for c in rng.sample(ZH_CLAUSES, k)]
200
+ text = ",".join(clauses[:-1]) + "," + clauses[-1] + "。" if k > 1 else clauses[0] + "。"
201
+ if text not in out:
202
+ out.append(text)
203
+ return out
204
+
205
+
206
+ def _inject_cliche_en(seed: str) -> str:
207
+ phrase = random.choice(list(EN_PHRASES.keys()))
208
+ seed = seed.rstrip(".")
209
+ return f"{phrase}, {seed}."
210
+
211
+
212
+ def _inject_cliche_zh(seed: str) -> str:
213
+ phrase = random.choice(list(ZH_PHRASES.keys()))
214
+ return f"{phrase},{seed}"
215
+
216
+
217
+ def build(
218
+ seed: int = 42,
219
+ n_en_ai: int = 8_000,
220
+ n_zh_ai: int = 6_000,
221
+ n_mixed: int = 1_000,
222
+ n_protected: int = 300,
223
+ ) -> list[dict]:
224
+ rng = random.Random(seed)
225
+ pairs: list[dict] = []
226
+
227
+ # 1. AI -> human.
228
+ for lang, ai_texts, _seeds in (
229
+ ("en", _ai_en(rng, n_en_ai), EN_SEEDS),
230
+ ("zh", _ai_zh(rng, n_zh_ai), ZH_SEEDS),
231
+ ):
232
+ for ai in ai_texts:
233
+ human = humanize_rules(ai, lang)
234
+ if human == ai:
235
+ continue
236
+ pairs.append({"input_text": ai, "output_text": human, "lang": lang})
237
+
238
+ # 2. Identity pairs.
239
+ for lang, seeds in (("en", EN_SEEDS), ("zh", ZH_SEEDS)):
240
+ for s in seeds:
241
+ pairs.append({"input_text": s, "output_text": s, "lang": lang})
242
+
243
+ # 3. Mixed: clean prose + injected cliche -> cleaned prose.
244
+ for _ in range(n_mixed):
245
+ lang = rng.choice(["en", "zh"])
246
+ seed = rng.choice(EN_SEEDS if lang == "en" else ZH_SEEDS)
247
+ noisy = _inject_cliche_en(seed) if lang == "en" else _inject_cliche_zh(seed)
248
+ cleaned = humanize_rules(noisy, lang)
249
+ if cleaned != noisy:
250
+ pairs.append({"input_text": noisy, "output_text": cleaned, "lang": lang})
251
+
252
+ # 3b. Extra Chinese mixed pairs (identity/copy grounding).
253
+ for _ in range(n_mixed):
254
+ seed = rng.choice(ZH_SEEDS)
255
+ noisy = _inject_cliche_zh(seed)
256
+ cleaned = humanize_rules(noisy, "zh")
257
+ if cleaned != noisy:
258
+ pairs.append({"input_text": noisy, "output_text": cleaned, "lang": "zh"})
259
+
260
+ # 4. Protected spans (contextualized so dedup keeps variety).
261
+ en_prefixes = [
262
+ "It is important to note that ", "It is worth noting that ",
263
+ "Moreover, ", "Additionally, ", "In conclusion, ", "",
264
+ ]
265
+ en_suffixes = [
266
+ " This is a robust and seamless solution.",
267
+ " Overall, this approach is a game-changer.",
268
+ " In conclusion, this matters for our work.",
269
+ " Furthermore, this initiative drives growth.",
270
+ "",
271
+ ]
272
+ zh_prefixes = ["值得注意的是,", "由此可见,", "综上所述,", "在当今快速发展的时代,", ""]
273
+ zh_suffixes = ["这一方案发挥了重要作用。", "这就是完整的业务闭环。", "综上所述,我们需要持续优化。", ""]
274
+ protected_srcs = []
275
+ for seed in PROTECTED_SEEDS:
276
+ if re.search(r"[\u4e00-\u9fff]", seed):
277
+ protected_srcs.extend(
278
+ f"{p}{seed}{s}" for p in zh_prefixes for s in zh_suffixes
279
+ )
280
+ else:
281
+ protected_srcs.extend(
282
+ f"{p}{seed}{s}" for p in en_prefixes for s in en_suffixes
283
+ )
284
+ for src in protected_srcs:
285
+ lang = "zh" if re.search(r"[\u4e00-\u9fff]", src) else "en"
286
+ cleaned = humanize_rules(src, lang)
287
+ if cleaned != src:
288
+ # Raw pair (URLs/numbers visible).
289
+ pairs.append({"input_text": src, "output_text": cleaned, "lang": lang})
290
+ # Masked pair: identical placeholder spans on both sides, matching
291
+ # inference-time protection so the model learns to copy them.
292
+ masked_src, _ = protect(src, lang)
293
+ masked_cleaned, _ = protect(cleaned, lang)
294
+ pairs.append(
295
+ {"input_text": masked_src, "output_text": masked_cleaned, "lang": lang}
296
+ )
297
+
298
+ seen, unique = set(), []
299
+ for p in pairs:
300
+ if p["input_text"] in seen:
301
+ continue
302
+ seen.add(p["input_text"])
303
+ unique.append(p)
304
+ return unique
305
+
306
+
307
+ def main() -> None:
308
+ parser = argparse.ArgumentParser()
309
+ parser.add_argument("--out", default="data", help="output directory")
310
+ parser.add_argument("--seed", type=int, default=42)
311
+ parser.add_argument("--val-size", type=int, default=500)
312
+ parser.add_argument("--test-size", type=int, default=1_000)
313
+ args = parser.parse_args()
314
+
315
+ out = Path(args.out)
316
+ out.mkdir(parents=True, exist_ok=True)
317
+ pairs = build(seed=args.seed)
318
+ rng = random.Random(args.seed)
319
+ rng.shuffle(pairs)
320
+
321
+ test = pairs[: args.test_size]
322
+ val = pairs[args.test_size : args.test_size + args.val_size]
323
+ train = pairs[args.test_size + args.val_size :]
324
+
325
+ for split, items in (("train", train), ("val", val), ("test", test)):
326
+ with open(out / f"{split}.jsonl", "w", encoding="utf-8") as f:
327
+ for p in items:
328
+ f.write(json.dumps(p, ensure_ascii=False) + "\n")
329
+ print(f"{split}: {len(items)} pairs")
330
+
331
+ print("test langs:", sorted({p['lang'] for p in test}))
332
+
333
+
334
+ if __name__ == "__main__":
335
+ main()
scripts/evaluate.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Benchmark the humanizer model.
2
+
3
+ Measures:
4
+ - corpus BLEU (sacrebleu) between outputs and rule references on AI->human
5
+ held-out rows (meaning preservation),
6
+ - AI-cliché removal rate on AI-flavored inputs,
7
+ - identity stability on ALL clean-prose (identity) pairs in the corpus,
8
+ - protected-span preservation on ALL examples containing protected spans,
9
+ - latency (characters per second, MPS/CPU).
10
+ """
11
+
12
+ import argparse
13
+ import json
14
+ import re
15
+ import sys
16
+ import time
17
+ from pathlib import Path
18
+
19
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
20
+
21
+ from humanize import Humanizer, protect
22
+
23
+ EN_CLICHES = [
24
+ "it is important to note that", "it is worth noting that", "moreover",
25
+ "furthermore", "in conclusion", "leverage", "seamless", "robust",
26
+ "testament", "delve", "cutting-edge", "state-of-the-art",
27
+ ]
28
+ ZH_CLICHES = [
29
+ "值得注意的是", "综上所述", "赋能", "降本增效", "闭环", "无缝",
30
+ "由此可见", "总而言之",
31
+ ]
32
+
33
+
34
+ def scan(data_dir: str):
35
+ rows = []
36
+ for split in ("train", "val", "test"):
37
+ with open(Path(data_dir) / f"{split}.jsonl", encoding="utf-8") as f:
38
+ for line in f:
39
+ rows.append(json.loads(line))
40
+ return rows
41
+
42
+
43
+ def main():
44
+ parser = argparse.ArgumentParser()
45
+ parser.add_argument("--model", default="checkpoints/humanize-text-model")
46
+ parser.add_argument("--data", default="data")
47
+ parser.add_argument("--ai-limit", type=int, default=500)
48
+ args = parser.parse_args()
49
+
50
+ from sacrebleu import corpus_bleu
51
+
52
+ humanizer = Humanizer(model_id=args.model)
53
+ rows = scan(args.data)
54
+
55
+ ai_rows = [r for r in rows if r["input_text"] != r["output_text"]][: args.ai_limit]
56
+ identity_rows = [r for r in rows if r["input_text"] == r["output_text"]]
57
+ protected_rows = [
58
+ r for r in rows
59
+ if re.search(r"PROTECTED_\d+", r["input_text"]) or re.search(
60
+ r"https?://|PROTECTED_\d+|\d", r["input_text"]
61
+ )
62
+ ]
63
+ protected_rows = [r for r in protected_rows if "PROTECTED_" in r["input_text"] or "https://" in r["input_text"]]
64
+
65
+ # Deduplicate by input so samples are unique.
66
+ def dedupe(items):
67
+ seen, out = set(), []
68
+ for r in items:
69
+ if r["input_text"] in seen:
70
+ continue
71
+ seen.add(r["input_text"])
72
+ out.append(r)
73
+ return out
74
+
75
+ ai_rows = dedupe(ai_rows)
76
+ identity_rows = dedupe(identity_rows)
77
+ protected_rows = dedupe(protected_rows)
78
+
79
+ print(f"AI->human rows: {len(ai_rows)} | identity rows: {len(identity_rows)} | protected rows: {len(protected_rows)}", flush=True)
80
+
81
+ # 1. AI -> human: BLEU + cliché removal.
82
+ refs, hyps = [], []
83
+ cliche_in = 0
84
+ cliche_removed = 0
85
+ t0 = time.time()
86
+ for n, row in enumerate(ai_rows, 1):
87
+ out = humanizer.humanize(row["input_text"], num_beams=3)
88
+ hyps.append(out)
89
+ refs.append(row["output_text"])
90
+ lower = out.lower()
91
+ hits_in = [c for c in (ZH_CLICHES if row["lang"] == "zh" else EN_CLICHES)
92
+ if c in (row["input_text"] if row["lang"] == "zh" else row["input_text"].lower())]
93
+ hits_out = [c for c in (ZH_CLICHES if row["lang"] == "zh" else EN_CLICHES)
94
+ if c in lower]
95
+ if hits_in:
96
+ cliche_in += 1
97
+ if not hits_out:
98
+ cliche_removed += 1
99
+ if n % 50 == 0:
100
+ print(f" ...ai {n}/{len(ai_rows)}", flush=True)
101
+ bleu = corpus_bleu(hyps, [refs]).score
102
+
103
+ # 2. Identity stability.
104
+ identity_ok = 0
105
+ for n, row in enumerate(identity_rows, 1):
106
+ out = humanizer.humanize(row["input_text"], num_beams=3)
107
+ # Allow light edits, no big drift: use char-level edit ratio.
108
+ ratio = sum(1 for a, b in zip(out, row["input_text"]) if a != b) / max(len(row["input_text"]), 1)
109
+ if ratio <= 0.15:
110
+ identity_ok += 1
111
+ if n % 25 == 0:
112
+ print(f" ...identity {n}/{len(identity_rows)}", flush=True)
113
+
114
+ # 3. Protected spans.
115
+ protected_ok = 0
116
+ protected_total = 0
117
+ failures = []
118
+ for n, row in enumerate(protected_rows, 1):
119
+ masked, spans = protect(row["input_text"])
120
+ out = humanizer.humanize(row["input_text"], num_beams=3)
121
+ for span in spans:
122
+ protected_total += 1
123
+ if span in out:
124
+ protected_ok += 1
125
+ else:
126
+ failures.append((row["lang"], span, out))
127
+ if n % 25 == 0:
128
+ print(f" ...protected {n}/{len(protected_rows)}", flush=True)
129
+
130
+ elapsed = time.time() - t0
131
+ chars = sum(len(r["input_text"]) for r in ai_rows + identity_rows + protected_rows)
132
+
133
+ print("=" * 60)
134
+ print(f"model: {args.model}")
135
+ print(f"corpus BLEU (AI->human, n={len(ai_rows)}): {bleu:.1f}")
136
+ print(f"cliche removal rate: {cliche_removed / max(cliche_in, 1):.1%} ({cliche_removed}/{cliche_in})")
137
+ print(f"identity stability (n={len(identity_rows)}): {identity_ok / max(len(identity_rows), 1):.1%} ({identity_ok}/{len(identity_rows)})")
138
+ print(f"protected-span preservation: {protected_ok}/{protected_total} "
139
+ f"({protected_ok / max(protected_total, 1):.1%})")
140
+ print(f"throughput: {chars / max(elapsed, 0.01):.0f} chars/s")
141
+ if failures:
142
+ print(f"\nprotected failures ({len(failures)}):")
143
+ for lang, span, out in failures[:10]:
144
+ print(f" [{lang}] lost {span!r} in {out[:80]!r}")
145
+
146
+ print("\nsample outputs:")
147
+ for row in ai_rows[:5]:
148
+ print(" IN :", row["input_text"][:100])
149
+ print(" OUT:", humanizer.humanize(row["input_text"], num_beams=3)[:100])
150
+ print()
151
+
152
+
153
+ if __name__ == "__main__":
154
+ main()
scripts/train.py ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Fine-tune T5-small as a text humanizer (one language per run).
2
+
3
+ English uses ``google-t5/t5-small``; Chinese uses a Chinese T5-small
4
+ (``uer/t5-small-chinese-cluecorpussmall``). Supervised pairs come from
5
+ ``scripts/build_dataset.py`` (rule-generated), filtered to one language.
6
+
7
+ MPS notes: avoid per-step host sync; accumulate the loss tensor and sync at
8
+ evaluation. Save a checkpoint at the end of every epoch.
9
+ """
10
+
11
+ import argparse
12
+ import json
13
+ import time
14
+ from pathlib import Path
15
+
16
+ import torch
17
+ from torch.optim import AdamW
18
+ from torch.utils.data import DataLoader, Dataset
19
+ from transformers import BertTokenizer, T5ForConditionalGeneration, T5Tokenizer
20
+
21
+ BASE_MODELS = {
22
+ "en": "google-t5/t5-small",
23
+ "zh": "uer/t5-small-chinese-cluecorpussmall",
24
+ }
25
+
26
+
27
+ class PairsDataset(Dataset):
28
+ def __init__(self, path: Path, tokenizer: T5Tokenizer, max_len: int, lang: str):
29
+ self.pairs = []
30
+ with open(path, encoding="utf-8") as f:
31
+ for line in f:
32
+ row = json.loads(line)
33
+ if row["lang"] != lang:
34
+ continue
35
+ self.pairs.append((row["input_text"], row["output_text"]))
36
+ self.tokenizer = tokenizer
37
+ self.max_len = max_len
38
+
39
+ def __len__(self):
40
+ return len(self.pairs)
41
+
42
+ def __getitem__(self, idx):
43
+ src, tgt = self.pairs[idx]
44
+ enc = self.tokenizer(
45
+ src, max_length=self.max_len, padding="max_length", truncation=True
46
+ )
47
+ dec = self.tokenizer(
48
+ tgt, max_length=self.max_len, padding="max_length", truncation=True
49
+ )
50
+ labels = torch.tensor(dec["input_ids"])
51
+ labels[labels == self.tokenizer.pad_token_id] = -100
52
+ return {
53
+ "input_ids": torch.tensor(enc["input_ids"]),
54
+ "attention_mask": torch.tensor(enc["attention_mask"]),
55
+ "labels": labels,
56
+ }
57
+
58
+
59
+ def decode(ids, tokenizer):
60
+ ids = torch.where(
61
+ (ids == -100) | (ids == tokenizer.pad_token_id),
62
+ torch.tensor(tokenizer.pad_token_id),
63
+ ids,
64
+ )
65
+ return tokenizer.decode(ids, skip_special_tokens=True)
66
+
67
+
68
+ def main():
69
+ parser = argparse.ArgumentParser()
70
+ parser.add_argument("--lang", choices=["en", "zh"], default="en")
71
+ parser.add_argument("--data", default="data")
72
+ parser.add_argument("--out", default=None)
73
+ parser.add_argument("--base-model", default=None)
74
+ parser.add_argument("--max-len", type=int, default=128)
75
+ parser.add_argument("--batch-size", type=int, default=32)
76
+ parser.add_argument("--lr", type=float, default=3e-4)
77
+ parser.add_argument("--epochs", type=int, default=3)
78
+ parser.add_argument("--warmup-steps", type=int, default=100)
79
+ parser.add_argument("--eval-every", type=int, default=200)
80
+ parser.add_argument("--seed", type=int, default=42)
81
+ args = parser.parse_args()
82
+
83
+ base_model = args.base_model or BASE_MODELS[args.lang]
84
+ out_dir = Path(args.out or f"checkpoints/humanize-text-model/{args.lang}")
85
+
86
+ torch.manual_seed(args.seed)
87
+ device = "mps" if torch.backends.mps.is_available() else (
88
+ "cuda" if torch.cuda.is_available() else "cpu"
89
+ )
90
+ print(f"lang={args.lang} base={base_model} device={device}", flush=True)
91
+
92
+ tokenizer = (
93
+ BertTokenizer.from_pretrained(base_model)
94
+ if args.lang == "zh"
95
+ else T5Tokenizer.from_pretrained(base_model)
96
+ )
97
+ if (out_dir / "pytorch_model.bin").exists() or (out_dir / "model.safetensors").exists():
98
+ print(f"resuming from existing checkpoint in {out_dir}", flush=True)
99
+ model = T5ForConditionalGeneration.from_pretrained(out_dir)
100
+ else:
101
+ model = T5ForConditionalGeneration.from_pretrained(base_model)
102
+ model.to(device)
103
+
104
+ train_ds = PairsDataset(Path(args.data) / "train.jsonl", tokenizer, args.max_len, args.lang)
105
+ val_ds = PairsDataset(Path(args.data) / "val.jsonl", tokenizer, args.max_len, args.lang)
106
+ val_samples = [
107
+ json.loads(l) for l in open(Path(args.data) / "val.jsonl", encoding="utf-8")
108
+ if json.loads(l)["lang"] == args.lang
109
+ ][:3]
110
+
111
+ train_loader = DataLoader(train_ds, batch_size=args.batch_size, shuffle=True, num_workers=0)
112
+ optimizer = AdamW(model.parameters(), lr=args.lr)
113
+ total_steps = args.epochs * len(train_loader)
114
+ scheduler = torch.optim.lr_scheduler.LinearLR(
115
+ optimizer, start_factor=1 / (args.warmup_steps + 1), total_iters=args.warmup_steps
116
+ )
117
+
118
+ out_dir.mkdir(parents=True, exist_ok=True)
119
+ step = 0
120
+ t0 = time.time()
121
+
122
+ for epoch in range(1, args.epochs + 1):
123
+ model.train()
124
+ epoch_loss = torch.zeros(())
125
+ for batch in train_loader:
126
+ batch = {k: v.to(device) for k, v in batch.items()}
127
+ loss = model(**batch).loss
128
+ loss.backward()
129
+ optimizer.step()
130
+ scheduler.step()
131
+ optimizer.zero_grad()
132
+ epoch_loss = epoch_loss + loss.detach()
133
+ step += 1
134
+
135
+ if step % 100 == 0:
136
+ print(f"[hb] step {step}/{total_steps} ({time.time() - t0:.0f}s)", flush=True)
137
+
138
+ if step % args.eval_every == 0:
139
+ model.eval()
140
+ vloss = 0.0
141
+ with torch.no_grad():
142
+ for i in range(16):
143
+ vb = val_ds[i]
144
+ vb = {k: v.unsqueeze(0).to(device) for k, v in vb.items()}
145
+ vloss += model(**vb).loss.item()
146
+ avg_train = (epoch_loss / step).item()
147
+ model.train()
148
+ print(
149
+ f"[step {step}] epoch={epoch} train_loss={avg_train:.4f} "
150
+ f"val_loss={vloss / 16:.4f} ({time.time() - t0:.0f}s)",
151
+ flush=True,
152
+ )
153
+ sample = val_samples[0]
154
+ model.eval()
155
+ with torch.no_grad():
156
+ ids = model.generate(
157
+ input_ids=tokenizer(sample["input_text"], return_tensors="pt").input_ids.to(device),
158
+ max_length=args.max_len,
159
+ )
160
+ out = decode(ids[0], tokenizer)
161
+ model.train()
162
+ print(" IN :", sample["input_text"][:90])
163
+ print(" OUT:", out[:90], flush=True)
164
+
165
+ avg = (epoch_loss / len(train_loader)).item()
166
+ print(f"epoch {epoch} done: avg_loss={avg:.4f}", flush=True)
167
+ model.save_pretrained(out_dir)
168
+ tokenizer.save_pretrained(out_dir)
169
+ print(f"checkpoint saved to {out_dir}", flush=True)
170
+
171
+ print(f"final model saved to {out_dir}", flush=True)
172
+
173
+
174
+ if __name__ == "__main__":
175
+ main()
scripts/upload_model.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Upload the humanize-text-model repo to Hugging Face.
2
+
3
+ Repo layout (matches Humanizer's expectations):
4
+ en/ zh/ humanize.py README.md LICENSE requirements.txt scripts/ tests/ src/
5
+ """
6
+ import os
7
+ import shutil
8
+ from pathlib import Path
9
+
10
+ from huggingface_hub import HfApi
11
+
12
+ TOKEN = os.environ["HF_TOKEN"]
13
+ REPO = "Danny-Lynote/humanize-text-model"
14
+ SRC = Path(__file__).resolve().parent.parent
15
+ STAGE = Path("/var/folders/x9/f0cp93qs3qqclss21p96pcgr0000gn/T/opencode/humanize-upload")
16
+
17
+ shutil.rmtree(STAGE, ignore_errors=True)
18
+ STAGE.mkdir(parents=True)
19
+ for name in ["humanize.py", "README.md", "LICENSE", "requirements.txt", "scripts", "tests", "src"]:
20
+ shutil.copytree(SRC / name, STAGE / name) if (SRC / name).is_dir() else shutil.copy2(SRC / name, STAGE / name)
21
+ shutil.copytree(SRC / "checkpoints/humanize-text-model/en", STAGE / "en")
22
+ shutil.copytree(SRC / "checkpoints/humanize-text-model/zh", STAGE / "zh")
23
+ (STAGE / "docs").mkdir(exist_ok=True)
24
+ if (SRC / "docs").exists():
25
+ for f in (SRC / "docs").iterdir():
26
+ if f.is_file():
27
+ shutil.copy2(f, STAGE / "docs" / f.name)
28
+
29
+ api = HfApi(token=TOKEN, endpoint="https://huggingface.co")
30
+ try:
31
+ info = api.repo_info(repo_id=REPO, repo_type="model")
32
+ print("repo exists, sha:", info.sha)
33
+ except Exception:
34
+ api.create_repo(repo_id=REPO, repo_type="model", private=False)
35
+ print("created model repo")
36
+
37
+ api.upload_folder(
38
+ folder_path=str(STAGE),
39
+ repo_id=REPO,
40
+ repo_type="model",
41
+ commit_message="Bilingual humanize-text-model (EN T5-small + ZH Chinese T5-small)",
42
+ )
43
+ print("uploaded. url: https://huggingface.co/" + REPO)
src/rules.py ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Shared rule engine for the humanize-text model.
2
+
3
+ Conservative bilingual editing rules derived from the reference projects:
4
+ - spaces/humanizer-lite (phrase replacements + protected spans)
5
+ - trust-fixes/humanizer.py (multi-method pipeline philosophy)
6
+
7
+ The rules generate the supervised (ai_text -> human_text) pairs used to
8
+ fine-tune the seq2seq model, and can also run as a standalone fallback.
9
+ """
10
+
11
+ import re
12
+
13
+ # --------------------------------------------------------------------------
14
+ # Protected spans: code, URLs, paths, numbers, quoted strings.
15
+ # --------------------------------------------------------------------------
16
+
17
+ PROTECTED_PATTERN = re.compile(
18
+ r"(`[^`]+`|https?://[^\s\u4e00-\u9fff,。!?;:、]+|(?:[A-Za-z]:)?(?:[/\\][\w.\-]+)+|"
19
+ r"\b\d+(?:\.\d+)?(?:%|[A-Za-z]+)?\b|\"[^\"]*\"|'[^']*')"
20
+ )
21
+
22
+
23
+ CJK_RE = re.compile(r"[\u4e00-\u9fff]")
24
+
25
+
26
+ def protect(text: str, lang: str = "auto"):
27
+ """Replace protected spans with language-aware placeholders:
28
+ EN ``PROTECTED_n``, ZH ``【保护n】`` (both tokenizer-friendly)."""
29
+ protected = []
30
+ zh = (
31
+ lang == "zh"
32
+ or (lang == "auto" and len(CJK_RE.findall(text)) > 0)
33
+ )
34
+
35
+ def replace(match):
36
+ token = f"【保护{len(protected)}】" if zh else f"PROTECTED_{len(protected)}"
37
+ protected.append(match.group(0))
38
+ return token
39
+
40
+ return PROTECTED_PATTERN.sub(replace, text), protected
41
+
42
+
43
+ def restore(text: str, protected):
44
+ """Restore placeholders (either format, tolerant of inserted spaces)."""
45
+ for index, value in enumerate(protected):
46
+ text = re.sub(rf"PROTECTED_{index}\b", lambda m: value, text)
47
+ text = re.sub(
48
+ rf"【\s*保\s*护\s*{index}\s*】", lambda m: value, text
49
+ )
50
+ return text
51
+
52
+
53
+ # --------------------------------------------------------------------------
54
+ # Formulaic (AI) phrases, English and Chinese.
55
+ # --------------------------------------------------------------------------
56
+
57
+ EN_PHRASES = {
58
+ "in today's rapidly evolving world": "today",
59
+ "in today's fast-paced world": "now",
60
+ "it is important to note that": "",
61
+ "it is worth noting that": "",
62
+ "it can be seen that": "",
63
+ "serves as a testament to": "shows",
64
+ "a testament to": "proof of",
65
+ "delve into": "look at",
66
+ "leverage": "use",
67
+ "leveraging": "using",
68
+ "seamless": "smooth",
69
+ "robust": "reliable",
70
+ "game-changer": "big improvement",
71
+ "groundbreaking": "new",
72
+ "cutting-edge": "modern",
73
+ "state-of-the-art": "modern",
74
+ "plays a crucial role in": "matters for",
75
+ "plays a crucial role": "matters",
76
+ "in the realm of": "in",
77
+ "in conclusion": "",
78
+ "moreover": "also",
79
+ "furthermore": "also",
80
+ "additionally": "also",
81
+ "it is essential to": "we need to",
82
+ "comprehensive approach": "broad approach",
83
+ "foster": "support",
84
+ "fostering": "supporting",
85
+ "undoubtedly": "",
86
+ "no longer optional but essential": "necessary",
87
+ "revolutionize": "change",
88
+ "revolutionizing": "changing",
89
+ "holistic": "full",
90
+ }
91
+
92
+ ZH_PHRASES = {
93
+ "值得注意的是": "",
94
+ "在当今快速发展的时代": "现在",
95
+ "综上所述": "",
96
+ "总而言之": "",
97
+ "由此可见": "",
98
+ "赋能": "帮助",
99
+ "助力": "帮助",
100
+ "降本增效": "省钱提效",
101
+ "闭环": "完整流程",
102
+ "无缝": "顺畅",
103
+ "发挥着重要作用": "很有用",
104
+ "发挥着至关重要的作用": "很重要",
105
+ "数字化转型": "数字化",
106
+ "必然趋势": "趋势",
107
+ "积极拥抱新技术": "用新技术",
108
+ "不断提升": "提高",
109
+ "持续优化": "改进",
110
+ "实现": "做到",
111
+ "进一步": "",
112
+ }
113
+
114
+ EN_OPENERS = ["Moreover,", "Furthermore,", "Additionally,", "In conclusion,"]
115
+
116
+ EN_EXTRA = {
117
+ "very": "",
118
+ "really": "",
119
+ "extremely": "",
120
+ "highly": "",
121
+ }
122
+
123
+ CONTRACTIONS = {
124
+ "it is": "it's",
125
+ "it will": "it'll",
126
+ "we are": "we're",
127
+ "we will": "we'll",
128
+ "you are": "you're",
129
+ "you will": "you'll",
130
+ "do not": "don't",
131
+ "does not": "doesn't",
132
+ "cannot": "can't",
133
+ "I am": "I'm",
134
+ "I will": "I'll",
135
+ "that is": "that's",
136
+ "there is": "there's",
137
+ "will not": "won't",
138
+ "it is not": "it isn't",
139
+ }
140
+
141
+ ZH_PARTICLES_END = ["了", "吧", "呢"]
142
+ ZH_PARTICLES_START = ["其实", "说实话", "我总觉得"]
143
+
144
+ # --------------------------------------------------------------------------
145
+ # Core rule application (single pass, deterministic).
146
+ # --------------------------------------------------------------------------
147
+
148
+
149
+ def humanize_rules(text: str, lang: str = "auto") -> str:
150
+ """Apply conservative editing rules to a draft. Deterministic."""
151
+ protected_text, protected = protect(text, lang)
152
+
153
+ if lang in ("auto", "en"):
154
+ low = protected_text.lower()
155
+ for phrase, replacement in EN_PHRASES.items():
156
+ protected_text = re.sub(re.escape(phrase), replacement, protected_text, flags=re.I)
157
+ # Only apply opener removals when they start a sentence boundary.
158
+ for opener in EN_OPENERS:
159
+ protected_text = re.sub(
160
+ re.escape(opener) + r"\s*", "", protected_text, flags=re.I
161
+ )
162
+ for word, replacement in EN_EXTRA.items():
163
+ protected_text = re.sub(r"\b" + re.escape(word) + r"\s+", "", protected_text, flags=re.I)
164
+ for phrase, replacement in CONTRACTIONS.items():
165
+ protected_text = re.sub(
166
+ r"\b" + re.escape(phrase) + r"\b", replacement, protected_text, flags=re.I
167
+ )
168
+
169
+ if lang in ("auto", "zh"):
170
+ for phrase, replacement in ZH_PHRASES.items():
171
+ protected_text = protected_text.replace(phrase, replacement)
172
+ protected_text = re.sub(r",{2,}", ",", protected_text)
173
+
174
+ protected_text = re.sub(r"[ \t]{2,}", " ", protected_text)
175
+ protected_text = re.sub(r"\s+([,.;:!?,。;:!?])", r"\1", protected_text)
176
+ protected_text = re.sub(r"[,;]\s*\.", ".", protected_text)
177
+ protected_text = re.sub(r"\.\s*[,;]", ".", protected_text)
178
+ protected_text = re.sub(r"[,,]\s*(?=[。!?.!?])", "", protected_text)
179
+ protected_text = re.sub(r"\.\s+([a-z])", lambda m: ". " + m.group(1).upper(), protected_text)
180
+ protected_text = re.sub(r"([.!?。!?])\s*\1+", r"\1", protected_text)
181
+ protected_text = re.sub(r"\n{3,}", "\n\n", protected_text).strip(" ,,")
182
+ return restore(protected_text, protected)
183
+
184
+
185
+ def zh_add_colloquial(text: str) -> str:
186
+ """Add light colloquial markers (used for data augmentation only)."""
187
+ if re.search(r"[\u4e00-\u9fff]", text):
188
+ text = re.sub(r"[。!?]", lambda m: m.group(0) + "呢", text, count=1)
189
+ text = re.sub(r"^", "其实,", text, count=1)
190
+ text = text.replace("今天", "今天吧")
191
+ text = text.replace("我觉得", "我总觉得")
192
+ return text
193
+
194
+
195
+ def en_add_colloquial(text: str) -> str:
196
+ """Add light colloquial markers (used for data augmentation only)."""
197
+ text = re.sub(r"(^|\. )(I|we|he|she|they)\b", r"\1Honestly, \2", text, count=1)
198
+ text = text.replace("The ", "The ", 1)
199
+ if " quite " not in text:
200
+ text = re.sub(r"(\w+)\.$", r"\1, honestly.", text, count=1)
201
+ return text
tests/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ import pytest
2
+ pytest.importorskip("src.rules")
tests/test_dataset.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tests for the synthetic dataset builder and rule engine."""
2
+
3
+ import json
4
+ import random
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
9
+
10
+ from src.rules import humanize_rules, protect, restore
11
+
12
+ DATA = Path(__file__).resolve().parent.parent / "data"
13
+ EN_PHRASES = {
14
+ "it is important to note that",
15
+ "it is worth noting that",
16
+ "moreover",
17
+ "furthermore",
18
+ "in conclusion",
19
+ "leverage",
20
+ "seamless",
21
+ "robust",
22
+ "delve",
23
+ "testament",
24
+ }
25
+ ZH_PHRASES = {
26
+ "值得注意的是",
27
+ "综上所述",
28
+ "赋能",
29
+ "降本增效",
30
+ "闭环",
31
+ "无缝",
32
+ "由此可见",
33
+ }
34
+
35
+
36
+ def _load(split: str):
37
+ pairs = []
38
+ with open(DATA / f"{split}.jsonl", encoding="utf-8") as f:
39
+ for line in f:
40
+ pairs.append(json.loads(line))
41
+ return pairs
42
+
43
+
44
+ def test_splits_exist_and_nonempty():
45
+ for split in ("train", "val", "test"):
46
+ pairs = _load(split)
47
+ assert len(pairs) > 0, f"{split} is empty"
48
+ assert len(_load("train")) > 10_000
49
+
50
+
51
+ def test_pairs_have_expected_fields():
52
+ for split in ("train", "val", "test"):
53
+ for p in _load(split):
54
+ assert set(p) == {"input_text", "output_text", "lang"}
55
+ assert p["lang"] in {"en", "zh"}
56
+ assert p["input_text"] and p["output_text"]
57
+
58
+
59
+ def test_both_languages_present():
60
+ langs = {p["lang"] for p in _load("train")}
61
+ assert langs == {"en", "zh"}
62
+
63
+
64
+ def test_ai_phrases_removed_in_targets():
65
+ counts = 0
66
+ checked = 0
67
+ for p in _load("train"):
68
+ if "值得注意的是" in p["input_text"] or "it is important to note that" in p["input_text"].lower():
69
+ checked += 1
70
+ target_lower = p["output_text"].lower()
71
+ if p["lang"] == "zh":
72
+ assert "值得注意的是" not in target_lower
73
+ assert "综上所述" not in target_lower
74
+ else:
75
+ assert "it is important to note that" not in target_lower
76
+ assert "it is worth noting that" not in target_lower
77
+ counts += 1
78
+ assert counts > 500
79
+
80
+
81
+ def test_protected_spans_preserved():
82
+ seen = 0
83
+ for p in _load("train") + _load("test"):
84
+ src = p["input_text"]
85
+ if "https://" in src or "ABC-12345" in src or "v2.1.3" in src:
86
+ for token in ("https://api.lynote.ai", "ABC-12345", "v2.1.3"):
87
+ if token in src:
88
+ assert token in p["output_text"], f"lost {token} in {p['output_text']}"
89
+ seen += 1
90
+ assert seen > 10
91
+
92
+
93
+ def test_identity_pairs_unchanged():
94
+ for p in _load("train"):
95
+ if p["input_text"] == p["output_text"]:
96
+ assert p["lang"] in {"en", "zh"}
97
+ return
98
+ raise AssertionError("no identity pairs found")
99
+
100
+
101
+ def test_deterministic_generation():
102
+ """Same seed yields the same corpus (checked on a fixed sample)."""
103
+ src = "In today's rapidly evolving world, it is important to note that this solution is robust. Moreover, it is seamless."
104
+ out1 = humanize_rules(src, "en")
105
+ out2 = humanize_rules(src, "en")
106
+ assert out1 == out2
107
+
108
+
109
+ def test_protect_restore_roundtrip():
110
+ text = "Run `pip install x==1.0` on https://example.com/a/b now — the price is $12.50 and \"quoted\" text."
111
+ masked, protected = protect(text)
112
+ assert masked != text
113
+ assert "PROTECTED_0" in masked
114
+ assert restore(masked, protected) == text
115
+
116
+
117
+ def test_rules_en():
118
+ src = "In today's rapidly evolving world, it is important to note that this robust solution serves as a testament to our commitment. Moreover, we leverage cutting-edge technology for a seamless experience."
119
+ out = humanize_rules(src, "en")
120
+ lower = out.lower()
121
+ assert "it is important to note that" not in lower
122
+ assert "moreover" not in lower
123
+ assert "leverage" not in lower
124
+ assert "seamless" not in lower
125
+ assert "robust" not in lower
126
+ assert "testament" not in lower
127
+ assert out != src
128
+
129
+
130
+ def test_rules_zh():
131
+ src = "值得注意的是,在当今快速发展的时代,我们通过赋能团队来实现降本增效,形成完整闭环,无缝连接各个环节。"
132
+ out = humanize_rules(src, "zh")
133
+ assert "值得注意的是" not in out
134
+ assert "赋能" not in out
135
+ assert "降本增效" not in out
136
+ assert "无缝" not in out
137
+ assert out != src
138
+
139
+
140
+ def test_rules_keep_human_prose_intact():
141
+ clean = "I walked the dog this morning and it started raining halfway through the park."
142
+ out = humanize_rules(clean, "en")
143
+ assert out == clean
144
+
145
+
146
+ def test_dataset_reproducibility_from_seed():
147
+ random.seed(7)
148
+ sample1 = random.sample(_load("test"), 50)
149
+ random.seed(7)
150
+ sample2 = random.sample(_load("test"), 50)
151
+ assert [p["input_text"] for p in sample1] == [p["input_text"] for p in sample2]
tests/test_humanize.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Model inference tests. Require a trained checkpoint or the Hub model.
2
+
3
+ Set HUMANIZE_MODEL (default: checkpoints/humanize-text-model).
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ import pytest
11
+
12
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
13
+
14
+ from humanize import Humanizer, MAX_CHARS, protect, restore
15
+
16
+ MODEL = os.environ.get("HUMANIZE_MODEL", "checkpoints/humanize-text-model")
17
+
18
+
19
+ @pytest.fixture(scope="module")
20
+ def humanizer():
21
+ return Humanizer(model_id=MODEL)
22
+
23
+
24
+ def test_loads_and_returns_text(humanizer):
25
+ out = humanizer.humanize("It is important to note that this is a test.")
26
+ assert isinstance(out, str) and out
27
+
28
+
29
+ def test_removes_ai_cliches_en(humanizer):
30
+ src = (
31
+ "In today's rapidly evolving world, it is important to note that this robust "
32
+ "solution serves as a testament to our commitment to innovation. Moreover, "
33
+ "we leverage cutting-edge technology to deliver a seamless experience."
34
+ )
35
+ out = humanizer.humanize(src)
36
+ lower = out.lower()
37
+ assert "it is important to note that" not in lower
38
+ assert "moreover" not in lower
39
+ assert "leverage" not in lower
40
+ assert "seamless" not in lower
41
+ assert "testament" not in lower
42
+ assert out != src
43
+
44
+
45
+ def test_removes_ai_cliches_zh(humanizer):
46
+ src = "值得注意的是,我们通过赋能团队来助力企业实现降本增效,形成完整的业务闭环。"
47
+ out = humanizer.humanize(src)
48
+ assert "值得注意的是" not in out
49
+ assert "赋能" not in out
50
+ assert "降本增效" not in out
51
+ assert out != src
52
+
53
+
54
+ def test_protects_urls_numbers_quotes(humanizer):
55
+ src = (
56
+ "It is important to note that the server at https://api.lynote.ai/v1/detect "
57
+ "handles 1,200 requests per second with a 99.5% uptime, as stated in "
58
+ '"Project Lynote". Moreover, run `pip install humanize-text==1.4.2`.'
59
+ )
60
+ out = humanizer.humanize(src)
61
+ for token in ("https://api.lynote.ai/v1/detect", "1,200", "99.5%", '"Project Lynote"', "humanize-text==1.4.2"):
62
+ assert token in out, f"protected token lost: {token}"
63
+
64
+
65
+ def test_human_prose_stays_similar(humanizer):
66
+ src = "I walked the dog this morning and it started raining halfway through the park, so we ran home."
67
+ out = humanizer.humanize(src)
68
+ # Identity should be near-preserved: allow light edits but no additions.
69
+ assert abs(len(out.split()) - len(src.split())) <= 4
70
+
71
+
72
+ def test_short_input_and_limits(humanizer):
73
+ with pytest.raises(ValueError):
74
+ humanizer.humanize("")
75
+ with pytest.raises(ValueError):
76
+ humanizer.humanize(" ")
77
+ with pytest.raises(ValueError):
78
+ humanizer.humanize("x" * (MAX_CHARS + 1))
79
+
80
+
81
+ def test_batch_consistency(humanizer):
82
+ texts = [
83
+ "It is worth noting that this robust platform is a game-changer. Moreover, it is seamless.",
84
+ "值得注意的是,我们通过赋能团队来实现降本增效。",
85
+ "I walked the dog this morning and the sky was grey.",
86
+ ]
87
+ batch = humanizer.humanize_batch(texts)
88
+ assert len(batch) == 3
89
+ for out, src in zip(batch, texts):
90
+ assert isinstance(out, str) and out
91
+ single = [humanizer.humanize(t) for t in texts]
92
+ # batch and single should agree exactly (same decode path)
93
+ assert batch == single
94
+
95
+
96
+ def test_protect_restore_roundtrip():
97
+ text = "Run `pip install x==1.0` at https://example.com/a?b=1 — cost is $12.50 and \"Q3\" rose 4%."
98
+ masked, protected = protect(text)
99
+ assert "PROTECTED_0" in masked
100
+ assert restore(masked, protected) == text
101
+
102
+
103
+ def test_is_chinese_routing():
104
+ from humanize import is_chinese
105
+ assert is_chinese("值得注意的是,这是中文测试。")
106
+ assert not is_chinese("It is important to note that this is English.")
107
+ assert not is_chinese("Mixed text with 中文 characters but mostly English words here.")
108
+
109
+
110
+ def test_polish():
111
+ from humanize import Humanizer as H
112
+ assert H._polish("hello , world") == "hello, world"
113
+ assert H._polish("end., next") == "end. Next"
114
+ assert H._polish("a b") == "a b"
zh/config.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "T5ForConditionalGeneration"
4
+ ],
5
+ "classifier_dropout": 0.0,
6
+ "d_ff": 2048,
7
+ "d_kv": 64,
8
+ "d_model": 512,
9
+ "decoder_start_token_id": 101,
10
+ "dense_act_fn": "relu",
11
+ "dropout_rate": 0.1,
12
+ "dtype": "float32",
13
+ "eos_token_id": 1,
14
+ "feed_forward_proj": "relu",
15
+ "initializer_factor": 1.0,
16
+ "is_encoder_decoder": true,
17
+ "is_gated_act": false,
18
+ "layer_norm_epsilon": 1e-06,
19
+ "model_type": "t5",
20
+ "n_positions": 512,
21
+ "num_decoder_layers": 6,
22
+ "num_heads": 8,
23
+ "num_layers": 6,
24
+ "pad_token_id": 0,
25
+ "relative_attention_max_distance": 128,
26
+ "relative_attention_num_buckets": 32,
27
+ "tokenizer_class": "BertTokenizer",
28
+ "transformers_version": "4.57.6",
29
+ "use_cache": true,
30
+ "vocab_size": 21228
31
+ }
zh/generation_config.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "_from_model_config": true,
3
+ "decoder_start_token_id": 101,
4
+ "eos_token_id": 1,
5
+ "pad_token_id": 0,
6
+ "transformers_version": "4.57.6"
7
+ }
zh/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:19c458057db296fd6abe06303a5e7bc92a9806d2d663bf43f1238abd0d021c17
3
+ size 219718696
zh/special_tokens_map.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "cls_token": "[CLS]",
3
+ "mask_token": "[MASK]",
4
+ "pad_token": "[PAD]",
5
+ "sep_token": "[SEP]",
6
+ "unk_token": "[UNK]"
7
+ }
zh/tokenizer_config.json ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "added_tokens_decoder": {
3
+ "0": {
4
+ "content": "[PAD]",
5
+ "lstrip": false,
6
+ "normalized": false,
7
+ "rstrip": false,
8
+ "single_word": false,
9
+ "special": true
10
+ },
11
+ "100": {
12
+ "content": "[UNK]",
13
+ "lstrip": false,
14
+ "normalized": false,
15
+ "rstrip": false,
16
+ "single_word": false,
17
+ "special": true
18
+ },
19
+ "101": {
20
+ "content": "[CLS]",
21
+ "lstrip": false,
22
+ "normalized": false,
23
+ "rstrip": false,
24
+ "single_word": false,
25
+ "special": true
26
+ },
27
+ "102": {
28
+ "content": "[SEP]",
29
+ "lstrip": false,
30
+ "normalized": false,
31
+ "rstrip": false,
32
+ "single_word": false,
33
+ "special": true
34
+ },
35
+ "103": {
36
+ "content": "[MASK]",
37
+ "lstrip": false,
38
+ "normalized": false,
39
+ "rstrip": false,
40
+ "single_word": false,
41
+ "special": true
42
+ }
43
+ },
44
+ "clean_up_tokenization_spaces": true,
45
+ "cls_token": "[CLS]",
46
+ "do_basic_tokenize": true,
47
+ "do_lower_case": false,
48
+ "extra_special_tokens": {},
49
+ "mask_token": "[MASK]",
50
+ "model_max_length": 1000000000000000019884624838656,
51
+ "never_split": null,
52
+ "pad_token": "[PAD]",
53
+ "sep_token": "[SEP]",
54
+ "strip_accents": null,
55
+ "tokenize_chinese_chars": true,
56
+ "tokenizer_class": "BertTokenizer",
57
+ "unk_token": "[UNK]"
58
+ }
zh/vocab.txt ADDED
The diff for this file is too large to render. See raw diff