File size: 4,734 Bytes
29f25be | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 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 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | """Streaming readers for local FineWeb2 and Tatoeba files."""
import argparse
import json
from contextlib import closing
from dataclasses import dataclass
from itertools import islice
from pathlib import Path
from typing import Iterator
import pyarrow.parquet as pq
ROOT = Path(__file__).resolve().parents[3]
@dataclass(frozen=True)
class Document:
source: str
source_id: str
text: str
source_file: str
row_index: int
metadata: dict
@property
def doc_id(self) -> str:
return f"{self.source}:{self.source_id}"
def relative_path(path: Path) -> str:
return path.resolve().relative_to(ROOT).as_posix()
def iter_fineweb(
path: Path, batch_size: int = 1024
) -> Iterator[Document]:
path = Path(path)
if batch_size < 1:
raise ValueError("batch_size 必须大于 0。")
with pq.ParquetFile(path) as parquet:
names = parquet.schema_arrow.names
if not {"text", "id"}.issubset(names):
raise ValueError(f"{path.name}: 缺少 text 或 id 字段。")
row_index = 0
for batch in parquet.iter_batches(batch_size=batch_size):
for row in batch.to_pylist():
source_id = row["id"]
text = row["text"]
if not isinstance(source_id, str) or not source_id:
raise ValueError(
f"{path.name}, row {row_index}: id 无效。"
)
if not isinstance(text, str):
raise ValueError(
f"{path.name}, row {row_index}: text 无效。"
)
yield Document(
source="fineweb",
source_id=source_id,
text=text,
source_file=relative_path(path),
row_index=row_index,
metadata={
key: value
for key, value in row.items()
if key not in {"text", "id"}
},
)
row_index += 1
def iter_tatoeba(path: Path) -> Iterator[Document]:
path = Path(path)
with path.open(encoding="utf-8-sig") as stream:
for row_index, line in enumerate(stream):
# Preserve literal quotes in the sentence text.
parts = line.rstrip("\r\n").split("\t", 2)
if len(parts) != 3:
raise ValueError(
f"{path.name}, row {row_index}: TSV 格式错误。"
)
source_id, language, text = parts
if not source_id:
raise ValueError(
f"{path.name}, row {row_index}: id 为空。"
)
if language != "jpn":
raise ValueError(
f"{path.name}, row {row_index}: "
f"预期 jpn,实际为 {language!r}。"
)
yield Document(
source="tatoeba",
source_id=source_id,
text=text,
source_file=relative_path(path),
row_index=row_index,
metadata={"language": language},
)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--limit", type=int, default=2)
args = parser.parse_args()
if args.limit < 1:
parser.error("--limit 必须大于 0")
fineweb_files = sorted(
(ROOT / "datasets/fineweb-2-edu-japanese").glob("*.parquet")
)
if not fineweb_files:
raise FileNotFoundError("没有找到 FineWeb2 parquet。")
inputs = [
(path, iter_fineweb) for path in fineweb_files
]
inputs.append((
ROOT / "datasets/Tatoeba/jpn_sentences.tsv",
iter_tatoeba,
))
preview_documents = 0
for path, reader in inputs:
with closing(reader(path)) as documents:
for document in islice(documents, args.limit):
print(json.dumps({
"doc_id": document.doc_id,
"source": document.source,
"source_file": document.source_file,
"row_index": document.row_index,
"characters": len(document.text),
"preview": document.text[:100],
}, ensure_ascii=False))
preview_documents += 1
print(json.dumps({
"input_files": len(inputs),
"preview_documents": preview_documents,
}, ensure_ascii=False))
if __name__ == "__main__":
main() |