tchbcb commited on
Commit
7b46abe
·
verified ·
1 Parent(s): bcd6e1a

Upload prepare_wiki_data.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. prepare_wiki_data.py +197 -0
prepare_wiki_data.py ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """下载维基百科中文数据 + 分词 + 生成 LAL 训练 .bin 文件.
3
+
4
+ 用法:
5
+ python3 scripts/prepare_wiki_data.py [--n_articles 10000] [--out data/wiki_bpe.bin]
6
+
7
+ 输出格式: LALT 二进制 (与 large_bpe_v3.bin 兼容)
8
+ [magic: 4 bytes = "LALT"]
9
+ [n_samples: int32]
10
+ [n_vocab: int32]
11
+ then n_samples records:
12
+ [n_tokens: int32]
13
+ [token_ids: int32 * n_tokens]
14
+ """
15
+ import os
16
+ import sys
17
+ import struct
18
+ import subprocess
19
+ import argparse
20
+ import time
21
+
22
+ # === 固化配置 ===
23
+ TOKENIZER_MODEL = "tokenizer/chinese_bpe.model"
24
+ WIKI_DUMP_URL = "https://dumps.wikimedia.org/zhwiki/latest/zhwiki-latest-pages-articles-multistream.xml.bz2"
25
+ DEFAULT_OUT = "data/wiki_bpe.bin"
26
+ DEFAULT_N_ARTICLES = 10000 # 先取 1 万篇, 约 500-1000 万 token
27
+
28
+
29
+ def log(msg):
30
+ print(f"[WIKI] {msg}", flush=True)
31
+
32
+
33
+ def download_wiki_dump(local_path, max_articles=None):
34
+ """下载维基百科 dump (流式解压 + 解析, 避免下载整个 2GB+ bz2)."""
35
+ import bz2
36
+ import xml.etree.ElementTree as ET
37
+ import urllib.request
38
+
39
+ log(f"下载+解析维基百科 (最多 {max_articles or '全部'} 篇)...")
40
+
41
+ articles = []
42
+ in_text = False
43
+ in_title = False
44
+ current_title = ""
45
+ current_text = ""
46
+ article_count = 0
47
+
48
+ # 流式下载 + bz2 解压
49
+ req = urllib.request.Request(WIKI_DUMP_URL, headers={"User-Agent": "LAL-Data-Prep/1.0"})
50
+ with urllib.request.urlopen(req) as resp:
51
+ with bz2.open(resp, "rt", encoding="utf-8") as f:
52
+ for line in f:
53
+ if "<title>" in line:
54
+ start = line.index("<title>") + 7
55
+ end = line.index("</title>")
56
+ current_title = line[start:end].strip()
57
+ elif "<text" in line:
58
+ in_text = True
59
+ # 提取 text 标签内的内容
60
+ if ">" in line:
61
+ start = line.index(">") + 1
62
+ current_text = line[start:]
63
+ else:
64
+ current_text = ""
65
+ elif "</text>" in line:
66
+ in_text = False
67
+ end = line.index("</text>")
68
+ current_text += line[:end]
69
+
70
+ # 过滤: 跳过重定向、空页面
71
+ if current_text and not current_text.startswith("#REDIRECT"):
72
+ # 清理 wiki 标记 (简单版)
73
+ text = clean_wiki_text(current_text)
74
+ if len(text) > 100: # 太短的文章跳过
75
+ articles.append(text)
76
+ article_count += 1
77
+ if article_count % 1000 == 0:
78
+ log(f" 已收集 {article_count} 篇文章")
79
+
80
+ current_text = ""
81
+
82
+ if max_articles and article_count >= max_articles:
83
+ break
84
+
85
+ # 处理最后一篇
86
+ if in_text and current_text:
87
+ text = clean_wiki_text(current_text)
88
+ if len(text) > 100:
89
+ articles.append(text)
90
+ article_count += 1
91
+
92
+ log(f"共收集 {len(articles)} 篇文章")
93
+ return articles
94
+
95
+
96
+ def clean_wiki_text(text):
97
+ """简单清理 wiki 标记."""
98
+ import re
99
+ # 去掉 wiki 模板 {{...}}
100
+ text = re.sub(r'\{\{[^}]*\}\}', '', text)
101
+ # 去掉 wiki 链接 [[...]]
102
+ text = re.sub(r'\[\[([^|\]]*\|)?([^\]]*)\]\]', r'\2', text)
103
+ # 去掉 HTML 标签
104
+ text = re.sub(r'<[^>]+>', '', text)
105
+ # 去掉 wiki 标题标记 ==
106
+ text = re.sub(r'^=+\s*([^=]+)\s*=+$', r'\1', text, flags=re.MULTILINE)
107
+ # 去掉引用
108
+ text = re.sub(r'<ref[^>]*>.*?</ref>', '', text, flags=re.DOTALL)
109
+ text = re.sub(r'<ref[^>]*/>', '', text)
110
+ # 去掉多余空行
111
+ text = re.sub(r'\n{3,}', '\n\n', text)
112
+ return text.strip()
113
+
114
+
115
+ def tokenize_with_bpe(texts, tokenizer_model):
116
+ """用 sentencepiece BPE 分词."""
117
+ try:
118
+ import sentencepiece as spm
119
+ except ImportError:
120
+ log("安装 sentencepiece...")
121
+ subprocess.run([sys.executable, "-m", "pip", "install", "-q", "sentencepiece"], check=True)
122
+ import sentencepiece as spm
123
+
124
+ sp = spm.SentenceProcessor()
125
+ sp.Load(tokenizer_model)
126
+
127
+ all_samples = []
128
+ total_tokens = 0
129
+
130
+ for i, text in enumerate(texts):
131
+ # 分词 (每篇文章作为一个 sample)
132
+ tokens = sp.EncodeAsIds(text)
133
+ if len(tokens) > 10: # 太短的跳过
134
+ all_samples.append(tokens)
135
+ total_tokens += len(tokens)
136
+
137
+ if (i + 1) % 1000 == 0:
138
+ log(f" 分词 {i+1}/{len(texts)} 篇, 总 token {total_tokens}")
139
+
140
+ log(f"分词完成: {len(all_samples)} samples, {total_tokens} tokens ({total_tokens/10000:.1f}万)")
141
+ return all_samples
142
+
143
+
144
+ def write_lalt_bin(samples, out_path, n_vocab=32768):
145
+ """写 LALT 二进制格式."""
146
+ log(f"写入 {out_path}...")
147
+ with open(out_path, "wb") as f:
148
+ # header
149
+ f.write(b"LALT")
150
+ f.write(struct.pack("<i", len(samples)))
151
+ f.write(struct.pack("<i", n_vocab))
152
+
153
+ # samples
154
+ for tokens in samples:
155
+ f.write(struct.pack("<i", len(tokens)))
156
+ for tok in tokens:
157
+ f.write(struct.pack("<i", tok))
158
+
159
+ size = os.path.getsize(out_path)
160
+ log(f"完成: {out_path} ({size / 1024 / 1024:.1f} MB)")
161
+
162
+
163
+ def main():
164
+ parser = argparse.ArgumentParser(description="准备维基百科训练数据")
165
+ parser.add_argument("--n_articles", type=int, default=DEFAULT_N_ARTICLES,
166
+ help=f"文章数 (默认 {DEFAULT_N_ARTICLES})")
167
+ parser.add_argument("--out", type=str, default=DEFAULT_OUT,
168
+ help=f"输出路径 (默认 {DEFAULT_OUT})")
169
+ args = parser.parse_args()
170
+
171
+ # 检查 tokenizer
172
+ if not os.path.exists(TOKENIZER_MODEL):
173
+ log(f"[!] tokenizer 不存在: {TOKENIZER_MODEL}")
174
+ sys.exit(1)
175
+
176
+ # 确保输出目录存在
177
+ os.makedirs(os.path.dirname(args.out), exist_ok=True)
178
+
179
+ start = time.time()
180
+
181
+ # 1. 下载 + 解析维基百科
182
+ articles = download_wiki_dump(args.out + ".tmp", max_articles=args.n_articles)
183
+
184
+ # 2. 分词
185
+ samples = tokenize_with_bpe(articles, TOKENIZER_MODEL)
186
+
187
+ # 3. 写 .bin
188
+ write_lalt_bin(samples, args.out)
189
+
190
+ elapsed = time.time() - start
191
+ log(f"总计耗时 {elapsed:.0f}s")
192
+ log(f"数据文件: {args.out}")
193
+ log(f"替换训练数据: cp {args.out} data/large_bpe_v3.bin")
194
+
195
+
196
+ if __name__ == "__main__":
197
+ main()