hojichar.filters.document_filters
1import json 2import logging 3import pathlib 4import re 5import string 6import time 7import unicodedata 8from collections import Counter 9from itertools import groupby 10from os import PathLike 11from typing import Any, Dict, List, Optional, Union 12 13import numpy as np 14 15import hojichar 16from hojichar.core.filter_interface import Filter 17from hojichar.core.models import Document, Token 18 19try: 20 import emoji 21 from fugashi import Tagger # type: ignore 22 23 is_loaded_extras = True 24except ImportError: 25 is_loaded_extras = False 26 27BASE_PATH = pathlib.Path(hojichar.__path__[0]) 28logger = logging.getLogger(__name__) 29 30 31class ExampleHojiChar(Filter): 32 """基本的なフィルタの実装例です. 末尾に'<hojichar>'を追加します.""" 33 34 def apply(self, document: Document) -> Document: 35 """ 36 >>> ExampleHojiChar()("hello, world") 37 'hello, world<hojichar>' 38 """ 39 document.text += "<hojichar>" 40 return document 41 42 43class ExampleDiscardDocumentContainKeyword(Filter): 44 """特定のキーワードを持つドキュメントを破棄するようなフィルタの実装例です.""" 45 46 def __init__(self, keyword: str, *args: Any, **kwargs: Any) -> None: 47 super().__init__(*args, **kwargs) 48 self.keyword = keyword 49 50 def apply(self, document: Document) -> Document: 51 """ 52 >>> ExampleDiscardDocumentContainKeyword("バカ").apply(Document("あいつはバカだ")).is_rejected 53 True 54 """ 55 if self.keyword in document.text: 56 document.is_rejected = True 57 return document 58 59 60class Identity(Filter): 61 """何も変化を加えないフィルタです. テスト・デバッグに用いられます.""" 62 63 def apply(self, document: Document) -> Document: 64 return document 65 66 67class DiscardAll(Filter): 68 """ 69 すべてのドキュメントを破棄するフィルタです. 70 テスト・デバッグに用いられます. 71 """ 72 73 def apply(self, document: Document) -> Document: 74 document.is_rejected = True 75 return document 76 77 78class ApplyDiscard(Filter): 79 """ 80 上流フィルタで破棄された`Document`を空文字列にします. 81 82 `Document.is_rejected=True` の ドキュメントは無視されるため, 83 このフィルタを `Compose` のコンストラクタに渡しても動作しません. 84 このフィルタは主に`Compose` 内部や, `discard_filtered=False` を指定 85 したデバッグ時などに利用されます. 86 """ 87 88 def __init__(self, *args: Any, **kwargs: Any) -> None: 89 super().__init__(*args, **kwargs) 90 91 def apply(self, document: Document) -> Document: 92 """ 93 >>> ApplyDiscard().apply(Document(text="hello", is_rejected=True)).text 94 '' 95 """ 96 if document.is_rejected: 97 document.text = "" 98 99 return document 100 101 102class Sleep(Filter): 103 """ 104 デバッグ用のフィルタです. 指定秒スリープします. 105 """ 106 107 def __init__(self, time: float = 1.0, *args: Any, **kwargs: Any) -> None: 108 super().__init__(*args, **kwargs) 109 self.time = time 110 111 def apply(self, document: Document) -> Document: 112 """ 113 >>> Sleep(0.1)('hello') # After 0.1 seconds, 114 'hello' 115 """ 116 time.sleep(self.time) 117 return document 118 119 120class DocumentNormalizer(Filter): 121 """ 122 Unicode の正規化をします. 123 """ 124 125 def __init__(self, *args: Any, **kwargs: Any) -> None: 126 super().__init__(*args, **kwargs) 127 128 def apply(self, document: Document) -> Document: 129 document.text = unicodedata.normalize("NFKC", document.text) 130 return document 131 132 133class JSONLoader(Filter): 134 """ 135 テキストを Json として解釈し, `key` で指定した要素を文字列として 136 doument に格納します.デフォルトの `key` は 'text' です. 137 138 Json の読み込み, あるいは `key` の読み込みに失敗した際には例外を送出します. 139 これらを無視する場合は, `ignore=True` にします. その際, 読み込みに失敗 140 したドキュメントは破棄されます. 141 """ 142 143 def __init__( 144 self, 145 key: str = "text", 146 ignore: bool = False, 147 extra_keys: Optional[List[str]] = None, 148 *args: Any, 149 **kwargs: Any, 150 ) -> None: 151 super().__init__(*args, **kwargs) 152 self.key = key 153 self.ignore = ignore 154 self.extra_keys = extra_keys 155 156 def apply(self, document: Document) -> Document: 157 """ 158 >>> JSONLoader()( '{"text": "hello, world", "words": 2}' ) 159 'hello, world' 160 161 >>> JSONLoader()( '{"text": hello, world ....' ) # Broken JSON 162 Traceback (most recent call last): 163 ... 164 json.decoder.JSONDecodeError: Expecting value: line 1 column 10 (char 9) 165 166 >>> JSONLoader()( '{"words": 2}' ) 167 Traceback (most recent call last): 168 ... 169 KeyError: 'text' 170 171 >>> JSONLoader(ignore=True).apply(Document('{"text": hello, world ....' )).is_rejected 172 True 173 """ 174 try: 175 data = json.loads(document.text) 176 document.text = str(data[self.key]) 177 if self.extra_keys is not None: 178 document.extras = {key: data[key] for key in self.extra_keys if key in data} 179 except Exception as e: 180 logger.error(f"Failed to parsing in JSONLoader. Input document: \n{document.text}") 181 if self.ignore: 182 document.is_rejected = True 183 return document 184 else: 185 raise e 186 187 return document 188 189 190class JSONDumper(Filter): 191 """ 192 Document.text の文字列を json に変換します. 193 必要に応じ Document のメタデータを付与します. これはドキュメントの破棄事由が含まれ、偽陽性の分析に有効です。 194 デフォルトで `skip_rejected` が `False` にセットされており、Document の破棄フラグにかかわらず 195 処理されます。 196 """ 197 198 def __init__( 199 self, 200 dump_reason: bool = False, 201 p: float = 1, 202 skip_rejected: bool = False, 203 export_extras: bool = False, 204 *args: Any, 205 **kwargs: Any, 206 ) -> None: 207 """ 208 Args: 209 dump_reason (bool, optional): `is_rejected`, `reason` エントリをダンプします. Defaults to False. 210 p (float, optional): Apply probability. Defaults to 1. 211 skip_rejected (bool, optional): 破棄済みサンプルを排除しません. 212 """ 213 super().__init__(p, skip_rejected, *args, **kwargs) 214 self.dump_reason = dump_reason 215 self.export_extras = export_extras 216 217 def apply(self, document: Document) -> Document: 218 """ 219 >>> JSONDumper()("hojichar") 220 '{"text": "hojichar"}' 221 """ 222 text = document.text 223 if self.dump_reason: 224 if self.export_extras: 225 document.text = json.dumps( 226 { 227 "text": text, 228 "is_rejected": document.is_rejected, 229 "reason": document.reject_reason, 230 "extras": document.extras, 231 }, 232 ensure_ascii=False, 233 ) 234 else: 235 document.text = json.dumps( 236 { 237 "text": text, 238 "is_rejected": document.is_rejected, 239 "reason": document.reject_reason, 240 }, 241 ensure_ascii=False, 242 ) 243 else: 244 if self.export_extras: 245 document.text = json.dumps( 246 { 247 "text": text, 248 "extras": document.extras, 249 }, 250 ensure_ascii=False, 251 ) 252 else: 253 document.text = json.dumps({"text": text}, ensure_ascii=False) 254 return document 255 256 257class DocumentLengthFilter(Filter): 258 """ 259 `min_doc_len`, `max_doc_len` で指定した上限・下限の範囲内にないドキュメントを破棄します. 260 デフォルトでは 200字 以上 50000字以内のテキストが受理されます. 261 """ 262 263 def __init__( 264 self, 265 min_doc_len: Optional[int] = None, 266 max_doc_len: Optional[int] = None, 267 *args: Any, 268 **kwargs: Any, 269 ) -> None: 270 super().__init__(*args, **kwargs) 271 272 self.min_doc_len = min_doc_len 273 self.max_doc_len = max_doc_len 274 275 def apply(self, doc: Document) -> Document: 276 """ 277 >>> DocumentLengthFilter(min_doc_len=5).apply(Document("1234")).is_rejected 278 True 279 """ 280 doc_len = len(doc.text) 281 if self.min_doc_len is not None: 282 if doc_len < self.min_doc_len: 283 doc.is_rejected = True 284 if self.max_doc_len is not None: 285 if self.max_doc_len < doc_len: 286 doc.is_rejected = True 287 return doc 288 289 290class NgWordsFilterJa(Filter): 291 """ 292 日本語のNGワード(および不適切語)を含む文書を破棄します. 293 `dict_path` で指定したファイルから, キーワードのリストを得ます. 294 ファイルは単語が改行で羅列されたテキストファイルです. 295 296 `ignore_confused` を `True` にすると, 297 偽陽性を軽減するために, カタカナのNGワードは前後にカタカナが無い場合のみNG判定されます. 298 デフォルト値は `False` です. 299 """ 300 301 def __init__( 302 self, 303 dict_path: Union[str, PathLike], 304 ignore_confused: bool = False, 305 *args: Any, 306 **kwargs: Any, 307 ) -> None: 308 super().__init__(*args, **kwargs) 309 310 with open(dict_path, encoding="utf-8") as fp: 311 ng_words = fp.read().split("\n") 312 ng_words = [w.strip() for w in ng_words if not len(w) == 0] 313 314 if ignore_confused: 315 words_katakana = [] 316 words_not_katakana = [] 317 for w in ng_words: 318 if re.fullmatch(r"[ァ-ヴー]+", w): 319 words_katakana.append(re.escape(w)) 320 else: 321 words_not_katakana.append(re.escape(w)) 322 katakana_pat = "|".join(words_katakana) 323 katakana_pat = rf"(?<![ァ-ヴー])({katakana_pat})(?![ァ-ヴー])" 324 pat = "|".join(words_not_katakana) + "|" + katakana_pat 325 self.keyword_pat = re.compile(pat) 326 else: 327 ng_words = [re.escape(w) for w in ng_words] 328 pat = "|".join(ng_words) 329 self.keyword_pat = re.compile(pat) 330 331 def apply(self, doc: Document) -> Document: 332 regex_match = self.keyword_pat.search(doc.text) 333 if regex_match: 334 doc.is_rejected = True 335 self.matched_text = regex_match.group() 336 self.matched_text_neighbor = doc.text[ 337 regex_match.start() - 20 : regex_match.end() + 20 338 ] 339 340 return doc 341 342 343class NgWordsFilterEn(Filter): 344 """ 345 英語のNGワード(および不適切語)を含む文書を破棄します. 346 `dict_path` で指定したファイルから, キーワードのリストを得ます. 347 ファイルは単語が改行で羅列されたテキストファイルです. 348 """ 349 350 def __init__(self, dict_path: Union[str, PathLike], *args: Any, **kwargs: Any) -> None: 351 super().__init__(*args, **kwargs) 352 353 with open(dict_path, encoding="utf-8") as fp: 354 ng_words = fp.read().split("\n") 355 ng_words = [re.escape(w.strip()) for w in ng_words if not len(w) == 0] 356 pat = "|".join(ng_words) 357 # 英語のパターンにマッチするようにしている, \s[単語]\s や [単語]. [単語], などにマッチ. 358 self.keyword_pat = re.compile(rf"(?:^| )({pat})(?:( |,|\.)|$)", re.IGNORECASE) 359 360 def apply(self, doc: Document) -> Document: 361 if self.keyword_pat.search(doc.text): 362 doc.is_rejected = True 363 return doc 364 365 366class DiscardAdultContentJa(NgWordsFilterJa): 367 """ 368 日本語のアダルトキーワード(および不適切語)を含む文書を破棄します. 369 `dict_path` で指定したファイルから, キーワードのリストを得ます. 370 ファイルは単語が改行で羅列されたテキストファイルです. 371 デフォルトの`dict_path` は /hojichar/dict/adult_keywords_ja.txt です. 372 """ 373 374 def __init__( 375 self, 376 dict_path: Union[str, PathLike] = BASE_PATH / "dict/adult_keywords_ja.txt", 377 *args: Any, 378 **kwargs: Any, 379 ) -> None: 380 super().__init__(dict_path, *args, **kwargs) 381 382 def apply(self, doc: Document) -> Document: 383 """ 384 >>> DiscardAdultContentJa().apply(Document("<TEST_STRING_OF_ADULT_KEYWORD>")).is_rejected 385 True 386 387 >>> DiscardAdultContentJa().apply(Document("ほうじ茶")).is_rejected 388 False 389 390 挙動は正しいが誤検知しているケース. 他にも, サック in リュックサック, 391 >>> DiscardAdultContentJa().apply(Document("アスパラガス")).is_rejected \ 392 # Matching with NG keyword "アス" 393 True 394 """ 395 return super().apply(doc) 396 397 398class DiscardAdultContentEn(NgWordsFilterEn): 399 """ 400 英語のアダルトキーワード(および不適切語)を含む文書を破棄します. 401 `dict_path` で指定したファイルから, キーワードのリストを得ます. 402 ファイルは単語が改行で羅列されたテキストファイルです. 403 デフォルトの`dict_path` は /hojichar/dict/adult_keywords_en.txt です. 404 """ 405 406 def __init__( 407 self, 408 dict_path: Union[str, PathLike] = BASE_PATH / "dict/adult_keywords_en.txt", 409 *args: Any, 410 **kwargs: Any, 411 ) -> None: 412 super().__init__(dict_path, *args, **kwargs) 413 414 def apply(self, doc: Document) -> Document: 415 """ 416 >>> DiscardAdultContentEn().apply(Document("<TEST_STRING_OF_ADULT_KEYWORD>")).is_rejected 417 True 418 419 >>> DiscardAdultContentEn().apply(Document("hojichar")).is_rejected 420 False 421 """ 422 return super().apply(doc) 423 424 425class DiscardDiscriminationContentJa(NgWordsFilterJa): 426 """ 427 日本語の差別キーワード(および不適切語)を含む文書を破棄します. 428 `dict_path` で指定したファイルから, キーワードのリストを得ます. 429 ファイルは単語が改行で羅列されたテキストファイルです. 430 デフォルトの`dict_path` は /hojichar/dict/discrimination_keywords_ja.txt です. 431 """ 432 433 def __init__( 434 self, 435 dict_path: Union[str, PathLike] = BASE_PATH / "dict/discrimination_keywords_ja.txt", 436 *args: Any, 437 **kwargs: Any, 438 ): 439 super().__init__(dict_path, *args, **kwargs) 440 441 def apply(self, doc: Document) -> Document: 442 """ 443 >>> DiscardDiscriminationContentJa().\ 444 apply(Document("<TEST_STRING_OF_DISCRIMINATION_KEYWORD>")).is_rejected 445 True 446 447 >>> DiscardDiscriminationContentJa().apply(Document("ほうじ茶")).is_rejected 448 False 449 """ 450 return super().apply(doc) 451 452 453class DiscardViolenceContentJa(NgWordsFilterJa): 454 """ 455 日本語の暴力・脅迫を示唆するキーワードを含む文書を破棄します. 456 `dict_path` で指定したファイルから, キーワードのリストを得ます. 457 ファイルは単語が改行で羅列されたテキストファイルです. 458 デフォルトの`dict_path` は /hojichar/dict/violence_keywords_ja.txt です. 459 """ 460 461 def __init__( 462 self, 463 dict_path: Union[str, PathLike] = BASE_PATH / "dict/violence_keywords_ja.txt", 464 *args: Any, 465 **kwargs: Any, 466 ) -> None: 467 super().__init__(dict_path, *args, **kwargs) 468 469 def apply(self, doc: Document) -> Document: 470 """ 471 >>> DiscardViolenceContentJa()\ 472 .apply(Document("<TEST_STRING_OF_VIOLENCE_KEYWORD>")).is_rejected 473 True 474 475 >>> DiscardViolenceContentJa().apply(Document("ほうじ茶")).is_rejected 476 False 477 """ 478 return super().apply(doc) 479 480 481class DiscardBBSComments(Filter): 482 """ 483 正規表現 "BBS Pattern" に `max_allow_num` 回よりたくさんマッチする文書を破棄します. 484 `max_allow_num` のデフォルト値は14です. 485 正規表現 "BBS Pattern" は下記のリンクで検証可能です. 486 https://regex101.com/r/ybQvL2/1 487 """ 488 489 def __init__(self, max_allowed_num: int = 14, *args: Any, **kwargs: Any) -> None: 490 super().__init__(*args, **kwargs) 491 492 self.max_allowed_num = max_allowed_num 493 self.keyword_pat = re.compile( 494 r"\d{4}[年\.\-\/][\ ]*\d{1,2}[月\.\-\/][\ ]*\d{1,2}[日]*|コメント|SOLD OUT|レビュー|投稿|ページ|\([月火水木金土日]\)|質問|\d+話|楽天市場|-" # noqa 495 ) 496 497 def apply(self, doc: Document) -> Document: 498 """ 499 >>> DiscardBBSComments().apply(Document("楽天市場 質問 投稿 コメント レビュー "*3)).is_rejected 500 True 501 502 >>> DiscardBBSComments().apply(Document("鏡餅")).is_rejected 503 False 504 """ 505 bbs_factor = self.keyword_pat.findall(doc.text) 506 if len(bbs_factor) > self.max_allowed_num: 507 doc.is_rejected = True 508 return doc 509 510 511class DiscardAds(Filter): 512 """ 513 主に広告キーワードを`max_allow_num`より多く含む文書を破棄します. 514 デフォルトで`max_allow_num` は14です. 515 `dict_path` で指定したファイルから, 広告キーワードのリストを得ます. 516 ファイルは単語が改行で羅列されたテキストファイルです. 517 デフォルトの`dict_path` は /hojichar/dict/advertisement_keywords_ja.txt です. 518 """ 519 520 def __init__( 521 self, 522 dict_path: Union[str, PathLike] = BASE_PATH / "dict/advertisement_keywords_ja.txt", 523 max_allowed_num: int = 14, 524 *args: Any, 525 **kwargs: Any, 526 ): 527 super().__init__(*args, **kwargs) 528 529 self.max_allow_num = max_allowed_num 530 with open(dict_path, encoding="utf-8") as fp: 531 ng_words = fp.read().split("\n") 532 ng_words = [re.escape(w.strip()) for w in ng_words if not len(w) == 0] 533 pat = r"|".join(ng_words) 534 self.keyword_pat = re.compile(pat) 535 536 def apply(self, doc: Document) -> Document: 537 """ 538 >>> DiscardAds().apply(Document("お問い合わせください 営業時間 よくある質問"*5)).is_rejected 539 True 540 541 >>> DiscardAds().apply(Document("おはよう")).is_rejected 542 False 543 """ 544 ads_factor = self.keyword_pat.findall(doc.text) 545 if len(ads_factor) > self.max_allow_num: 546 doc.is_rejected = True 547 return doc 548 549 550class AcceptJapanese(Filter): 551 """ 552 日本語でないドキュメントを破棄します. 日本語判定は次の手順で行われます. 553 1. テキストを左から`lookup_size` (デフォルトで50字) 参照し, 554 ひらがな・カタカナが存在すれば日本語と判定する. 555 """ 556 557 def __init__(self, lookup_size: int = 50, *args: Any, **kwargs: Any) -> None: 558 super().__init__(*args, **kwargs) 559 560 self.lookup_size = lookup_size 561 self.hiragana_katakana_pat = re.compile(r"[ぁ-んァ-ン]") 562 563 def apply(self, doc: Document) -> Document: 564 """ 565 >>> AcceptJapanese().apply(Document("This is English document")).is_rejected 566 True 567 568 >>> AcceptJapanese().apply(Document("a"*50 + "あ")).is_rejected 569 True 570 571 >>> AcceptJapanese().apply(Document("ほうじ茶")).is_rejected 572 False 573 """ 574 if not self.hiragana_katakana_pat.search(doc.text[: self.lookup_size]): 575 doc.is_rejected = True 576 return doc 577 578 579class DiscardRareKuten(Filter): 580 """ 581 日本語でないドキュメントを破棄します. 日本語判定は次の手順で行われます 582 ドキュメントを句点"。"で区切り, 平均文長が 583 `max_avarage_sentence_length` より長い場合は破棄します. 584 `max_avarage_sentence_length` のデフォルト値は100です. 585 このフィルタは, 文章中の句点の割合が少なすぎるドキュメントを破棄します. 586 """ 587 588 def __init__(self, max_average_sentence_length: int = 100, *args: Any, **kwargs: Any) -> None: 589 super().__init__(*args, **kwargs) 590 591 self.max_average_sentence_length = max_average_sentence_length 592 self.kuten_pat = re.compile(r"。") 593 594 def apply(self, doc: Document) -> Document: 595 """ 596 >>> DiscardRareKuten(max_average_sentence_length=4).apply(Document("おはよ。")).is_rejected 597 False 598 >>> DiscardRareKuten(max_average_sentence_length=4).apply(Document("おはよう。")).is_rejected 599 True 600 """ 601 kuten_lst = self.kuten_pat.findall(doc.text) 602 min_kuten_num = len(doc.text) / self.max_average_sentence_length 603 if len(kuten_lst) < min_kuten_num: 604 doc.is_rejected = True 605 return doc 606 607 608class HeaderFooterTagsRemover(Filter): 609 """ 610 ドキュメントの冒頭・末尾のトークンを調査し, ヘッダー・フッダー的な 611 タグが存在していた場合, そのトークンを除去します. 612 613 このフィルタを通す前に, 事前にセンテンスレベルにトーカナイズしておいてください. 614 このフィルタでは Document.token にのみ変更が加えられるので, 出力前 あるいは 下流フィルタで 615 Document.text に変更を加える前にトークンをマージしておいてください. 616 """ 617 618 def __init__( 619 self, 620 dict_path: Union[str, PathLike] = BASE_PATH / "dict/header_footer_keywords_ja.txt", 621 *args: Any, 622 **kwargs: Any, 623 ) -> None: 624 super().__init__(*args, **kwargs) 625 626 with open(dict_path) as fp: 627 keywords = fp.read().split("\n") 628 keywords = [re.escape(w.strip()) for w in keywords if not len(w) == 0] 629 self.keyword_pat = re.compile(r"|".join(keywords)) 630 631 def apply(self, doc: Document) -> Document: 632 if len(doc.tokens) == 0: 633 return doc 634 635 lookup_size = 0 636 if 1 <= len(doc.tokens) < 4: 637 lookup_size = 1 638 elif 4 <= len(doc.tokens) < 6: 639 lookup_size = 2 640 elif 6 <= len(doc.tokens): 641 lookup_size = 3 642 643 for i in range(lookup_size): 644 if self.should_drop_token(doc.tokens[i]): 645 doc.tokens[i].is_rejected = True 646 if self.should_drop_token(doc.tokens[-(i + 1)]): 647 doc.tokens[i].is_rejected = True 648 649 return doc 650 651 def should_drop_token(self, token: Token) -> bool: 652 """ 653 >>> HeaderFooterTagsRemover().should_drop_token(Token("<TEST_STRING_OF_KEYWORD>")) 654 True 655 656 >>> HeaderFooterTagsRemover().should_drop_token(Token("ほうじ茶")) 657 False 658 659 Comment. 660 Original legacy code removed a pattern r"« _ | Main | _ »" . 661 In the pattern, "|" is not escaped, so **ANY** string was eliminated. 662 It seems unintended behavior, so I fix this. 663 """ 664 if self.keyword_pat.match(token.text): 665 return True 666 else: 667 return False 668 669 670class MaskPersonalInformation(Filter): 671 """ 672 ドキュメントに含まれる電話番号・電子メールアドレスを一部マスキングします. 673 """ 674 675 def __init__(self, *args: Any, **kwargs: Any) -> None: 676 super().__init__(*args, **kwargs) 677 678 self.phone_pat = re.compile( 679 r"((0|\+\d{1,3}[- ]?)(\d{2}[- ]?\d{4}[- ]?|\d[- ]?\d{4}[- ]?|\d{2}[- ]?\d{3}[- ]?|\d{3}[- ]?\d{2}[- ]?|\d{4}[- ]?\d{1}[- ]?))\d{4}" # noqa 680 ) 681 self.email_pat = re.compile( 682 r"[a-zA-Z0-9!#$%&'*+\-/=?^_`{|}~.]+@[A-Za-z0-9!#$%&'*+\-/=?^_`{|}~.]+(\.[A-Za-z0-9\-]+)" # noqa 683 ) 684 685 def apply(self, doc: Document) -> Document: 686 """ 687 >>> MaskPersonalInformation()('06-1234-5678') 688 '06-1234-XXXX' 689 >>> MaskPersonalInformation()('075-123-4567') 690 '075-123-XXXX' 691 >>> MaskPersonalInformation()('0166-12-3456') 692 '0166-12-XXXX' 693 >>> MaskPersonalInformation()('09808-1-2345') 694 '09808-1-XXXX' 695 >>> MaskPersonalInformation()('090-1234-5678') 696 '090-1234-XXXX' 697 >>> MaskPersonalInformation()('0751234567') 698 '075123XXXX' 699 >>> MaskPersonalInformation()('08012345678') 700 '0801234XXXX' 701 >>> MaskPersonalInformation()('連絡は075-123-4567 まで') 702 '連絡は075-123-XXXX まで' 703 >>> MaskPersonalInformation()('+81-80-1234-5678') 704 '+81-80-1234-XXXX' 705 >>> MaskPersonalInformation()('+818012345678') 706 '+81801234XXXX' 707 >>> MaskPersonalInformation()('hogehoge@example.com') 708 'xxxx@yyy.com' 709 >>> MaskPersonalInformation()('何かあれば hogehoge@example.ne.jp まで連絡') 710 '何かあれば xxxx@yyy.jp まで連絡' 711 """ 712 text = self.phone_pat.sub(r"\1XXXX", doc.text) 713 text = self.email_pat.sub(r"xxxx@yyy\1", text) 714 doc.text = text 715 return doc 716 717 718class DiscardTooManyNouns(Filter): 719 """ 720 [!CAUTION] This filter requires `fugashi` package. Please install it 721 by `pip install 'hojichar[all]'`. 722 723 A filter that removes document with too many nouns in Japanese i.e., 724 documents such as advertisement, word salad, etc ... 725 """ 726 727 def __init__(self, threshold: float = 0.80, *args: Any, **kwargs: Any) -> None: 728 """ 729 Args: 730 threshold: document whose noun ratio is higher than this value will be discarded 731 *args: 732 **kwargs: 733 """ 734 super().__init__(*args, **kwargs) 735 assert ( 736 is_loaded_extras 737 ), "fugashi is required for this filter. Try pip install 'hojichar[all]'" 738 739 self.threshold = threshold 740 self.tagger = Tagger("-Owakati") 741 assert ( 742 "unidic" in self.tagger.dictionary_info[0]["filename"] 743 ), "MeCab dictionary must be unidic" 744 745 def apply(self, doc: Document) -> Document: 746 """ 747 >>> DiscardTooManyNouns().apply(Document("自然言語処理大好き!")).is_rejected 748 False 749 >>> DiscardTooManyNouns().apply(Document("リンゴ・オレンジ・ミカン・バナナ セール中")).is_rejected 750 True 751 >>> DiscardTooManyNouns().apply(Document("今日の仙台朝市ではリンゴがセール中")).is_rejected 752 False 753 """ 754 # remove "補助記号" from part-of-speech statistics 755 # because they often decrease the noun ratio, 756 # e.g., the sentence "リンゴ・オレンジ・バナナ・" has 補助記号 ratio of 0.5 757 # however, we don't want such sentence 758 pos_count = Counter( 759 w.feature.pos1 for w in self.tagger(doc.text) if w.feature.pos1 != "補助記号" 760 ) 761 try: 762 noun_ratio = pos_count["名詞"] / sum(pos_count.values()) 763 except ZeroDivisionError: 764 noun_ratio = 0.0 765 if noun_ratio >= self.threshold: 766 doc.is_rejected = True 767 return doc 768 769 770class CharRepetitionRatioFilter(Filter): 771 """ 772 文字Ngramの重なり率(文書中で高頻度文字Ngramが占める割合)を計算して, 重なりの大きいものを除去します. 773 名詞の連続からなるような広告テキストを取り除くのに有効です. 774 775 実装は, BigScience で採用されていた前処理を参考にしています. 776 元実装: https://github.com/bigscience-workshop/data-preparation/blob/9d0588419073cc5bf0fb92b58f37f2a1016572c3/preprocessing/training/01b_oscar_cleaning_and_filtering/filtering.py#L425-L453 # noqa: E501 777 778 「高頻度文字Ngram」は、sqrt(ユニークなNgramの総数)によって求めていますが, 779 これは文書長の影響を軽減するためだとされています. 780 781 掲示板のテキストが引っかかりやすい傾向があります. 782 13: 名無しさん@実況で競馬板アウト 2019/08/18(日) 15:28:46.10 ID:eBvZg8h+0 783 的なものが高頻度で登場するため、文字Ngramの重なり率も高くなってしまう 784 """ 785 786 def __init__( 787 self, threshold: float = 0.33, ngram_size: int = 5, *args: Any, **kwargs: Any 788 ) -> None: 789 """ 790 791 Args: 792 threshold: document with character repetition ratio higher than this value will be discarded 793 ngram_size: character ngram size. Larger value will decrease the false positive of long documents 794 *args: 795 **kwargs: 796 """ # noqa: E501 797 798 super().__init__(*args, **kwargs) 799 self.threshold = threshold 800 self.ngram_size = ngram_size 801 802 def apply(self, doc: Document) -> Document: 803 ratio = self.compute_character_repetition_ratio(doc.text, self.ngram_size) 804 if ratio >= self.threshold: 805 doc.is_rejected = True 806 return doc 807 808 @staticmethod 809 def compute_character_repetition_ratio( 810 document: str, character_repetition_length: int 811 ) -> float: 812 def get_freq_character_ngrams(document: str, n: int) -> Dict[str, int]: 813 character_ngrams: List[str] = [ 814 document[i : i + n] for i in range(len(document) - n + 1) 815 ] 816 freq_character_ngrams_dict: Dict[str, int] = {} 817 for character_ngram in character_ngrams: 818 freq_character_ngrams_dict[character_ngram] = ( 819 freq_character_ngrams_dict.get(character_ngram, 0) + 1 820 ) 821 return freq_character_ngrams_dict 822 823 freq_character_ngrams_dict = get_freq_character_ngrams( 824 document, character_repetition_length 825 ) 826 if len(freq_character_ngrams_dict) == 0: 827 return 0.0 828 freq_character_ngrams: List[int] = list(freq_character_ngrams_dict.values()) 829 freq_character_ngrams = sorted(freq_character_ngrams, reverse=True) 830 val_one = len([el for el in freq_character_ngrams if el == 1]) 831 num_rep_character_ngrams = min( 832 int(np.sqrt(len(freq_character_ngrams))), 833 len(freq_character_ngrams) - val_one, 834 ) 835 character_repetition_ratio = sum(freq_character_ngrams[:num_rep_character_ngrams]) / sum( 836 freq_character_ngrams 837 ) 838 return character_repetition_ratio 839 840 841class WordRepetitionRatioFilter(Filter): 842 """ 843 [!CAUTION] This filter requires `fugashi` package. Please install it 844 by `pip install 'hojichar[all]'`. 845 846 単語Ngramの重なり率(文書中で重複する単語Ngramが占める割合)を計算して、重なりの大きいものを弾くためのフィルタ. 847 BigScienceで採用されていた前処理を参考にしている. 848 849 名詞が連打されているような広告テキストを取り除くのに有効な様子 850 まともな文書がたまたま2回繰り返されている場合もあり、これを取り除いて良いのかは分からない 851 例: 852 "ウェブ\n本文: ニコンの上昇率16%超える、今3月期は経常76%の大幅増益見込む(ニコン) 2013年05月10日[minkabu PRESS] - みんなの株式 (みんかぶ)\n2013/05/10(10:57) 853 ニコン<7731.T>が急騰、寄り付き直後に前日比355円高の2537円まで買い上げ 854 られ、上昇率は16%を超えた。外国為替市場で円が1ドル100円台、1ユーロ131円台に入るなど急速に円安が進み、輸出株が軒並み高になる 855 なか、9日取引終了後に発表した前年3月期決算で、今3月期は2ケタ近い増収で大幅増益を見込んだことが買い気を強めさせた。連結売上 856 高は前期比9.8%増の1兆1100億円、経常利益75.8%増の850億円を予想。前期は半導体、電子部品の低迷が足かせになり、2ケタ増収ながら 857 経常46%の大幅減益になったが、レンズ交換式デジタルカメラの拡大や液晶ディスプレイの回復で収益が急回復する。ニコンの株価は10時 858 56分現在2491円(△309円)出所:株経通信(株式会社みんかぶ)\n2013/05/10 - ニコン(7731) の関連ニュース。 ニコン<7731.T>が急騰、寄 859 り付き直後に前日比355円高の2537円まで買い上げられ、上昇率は16%を超えた。外国為替市場で円が1ドル100円台、1ユーロ131円台に入 860 るなど急速に円安が進み、輸出株が軒並み高になるなか、9日取引終了後に発表した前年3月期決算で、今3月期は2ケタ近い増収で大幅増 861 益を見込んだことが買い気を強めさせた。連結売上高は前期比9.8%増の1兆1100億円、経常利益75.8%増の850億円を予想。前期は半導体、 862 電子部品の低迷が足かせになり、2ケタ増収ながら経常46%の大幅減益になったが、レンズ交換式デジタルカメラの拡大や液晶ディスプレ 863 イの回復で収益が急回" 864 """ # noqa: E501 865 866 def __init__( 867 self, threshold: float = 0.40, ngram_size: int = 7, *args: Any, **kwargs: Any 868 ) -> None: 869 """ 870 871 Args: 872 threshold: document whose character repetition ratio is higher than this value will be discarded 873 ngram_size: character ngram size. Larger value will decrease the false positive of long documents 874 *args: 875 **kwargs: 876 """ # noqa: E501 877 super().__init__(*args, **kwargs) 878 assert ( 879 is_loaded_extras 880 ), "fugashi is required for this filter. Try pip install 'hojichar[all]'" 881 882 self.threshold = threshold 883 self.ngram_size = ngram_size 884 self.tagger = Tagger("-Owakati") 885 886 def apply(self, doc: Document) -> Document: 887 ratio = self.compute_word_repetition_ratio(doc.text, self.ngram_size) 888 if ratio >= self.threshold: 889 doc.is_rejected = True 890 return doc 891 892 def compute_word_repetition_ratio(self, document: str, word_repetition_length: int) -> float: 893 def get_freq_word_ngrams(document: str, n: int) -> Dict[str, int]: 894 # tokenizing given document 895 words = [w.surface for w in self.tagger(document)] 896 word_ngrams = [" ".join(words[i : i + n]) for i in range(len(words) - n + 1)] 897 freq_word_ngrams: Dict[str, int] = {} 898 for word_ngram in word_ngrams: 899 freq_word_ngrams[word_ngram] = freq_word_ngrams.get(word_ngram, 0) + 1 900 return freq_word_ngrams 901 902 freq_word_ngrams_dict = get_freq_word_ngrams(document, word_repetition_length) 903 if len(freq_word_ngrams_dict) == 0: 904 return 0 905 freq_word_ngrams = list(freq_word_ngrams_dict.values()) 906 word_repetition_ratio = sum(freq for freq in freq_word_ngrams if freq > 1) / sum( 907 freq_word_ngrams 908 ) 909 910 return word_repetition_ratio 911 912 913class DiscardTooManySpecialToken(Filter): 914 """ 915 [!CAUTION] This filter requires `emoji` package. Please install it 916 by `pip install 'hojichar[all]'`. 917 918 句読点を含む記号、空白、絵文字、その他特殊な文字を一定の割合以上含むような文書を取り除くためのフィルタ 919 元実装: BigScience https://github.com/bigscience-workshop/data-preparation/blob/9d0588419073cc5bf0fb92b58f37f2a1016572c3/preprocessing/training/01b_oscar_cleaning_and_filtering/parameters_filtering.py#L5-L16 # noqa: E501 920 """ 921 922 def __init__(self, threshold: float = 0.4, *args: Any, **kwargs: Any) -> None: 923 """ 924 925 Args: 926 threshold: document whose special token ratio is higher than this value will be discarded 927 *args: 928 **kwargs: 929 """ # noqa: E501 930 super().__init__(*args, **kwargs) 931 932 # digits are not regarded as special tokens 933 # otherwise many false positives are made, i.e., good documents discarded 934 main_special_characters = string.punctuation + string.whitespace # + string.digits 935 other_special_characters = ( 936 " ’“”–▬…✦�£•€«»°·═" 937 "×士^˘⇓()§″′´¿−±∈¢ø‚„½¼¾¹²³―⁃,ˌ¸‹›ʺˈʻ¦‐⠀‰ ‑≤≥‖" 938 "◆●■►▼▲▴∆▻¡★☆✱ːº。¯˜¥ɪ≈†:⁄♡✓⊕․.⋅÷1‟;،、¨ाাी्े◦˚" 939 "゜ʼ≖ʼ¤℃√!?【】‿∞➤~πه۩☛₨➩☻๑٪♥ıॽ《‘©﴿٬?▷Г♫∟™ª₪®「—❖" 940 "」﴾》�" 941 ) 942 943 en_emoji = emoji.EMOJI_DATA.keys() 944 945 special_characters_default = set(main_special_characters + other_special_characters) 946 special_characters_default.update(en_emoji) 947 self.special_characters = special_characters_default 948 949 self.threshold = threshold 950 951 def _compute_special_characters_ratio(self, text: str) -> float: 952 if len(text) == 0: 953 return 0 954 955 special_characters_ratio = len( 956 [char for char in text if char in self.special_characters] 957 ) / len(text) 958 return special_characters_ratio 959 960 def apply(self, doc: Document) -> Document: 961 special_characters_ratio = self._compute_special_characters_ratio(doc.text) 962 963 if special_characters_ratio > self.threshold: 964 doc.is_rejected = True 965 return doc 966 967 968class SingleCharacterRepetitionFilter(Filter): 969 """ 970 単一文字が大量に繰り返されているような文書を取り除くためのフィルタ 971 そのような文書はノイズである可能性が高いため 972 参考: BigScienceプロジェクトによると、oscarデータセットの中にバックスラッシュだけを2M個含むような文書が含まれていたらしい 973 https://github.com/bigscience-workshop/bigscience/blob/master/train/tr8-104B-wide/chronicles.md#2m-backslash-only-samples-in-our-dataset # noqa: E501 974 """ 975 976 def __init__( 977 self, 978 threshold: int = 200, 979 *args: Any, 980 **kwargs: Any, 981 ) -> None: 982 """ 983 Args: 984 threshold: The document is removed if character is repeated for this value or more 985 *args: 986 **kwargs: 987 """ 988 super().__init__(*args, **kwargs) 989 self.threshold = threshold 990 991 def _is_repeat_contained(self, text: str) -> bool: 992 groups = groupby(text) 993 is_repeat_contained = any(sum(1 for _ in group) >= self.threshold for _, group in groups) 994 return is_repeat_contained 995 996 def apply(self, doc: Document) -> Document: 997 if self._is_repeat_contained(doc.text): 998 doc.is_rejected = True 999 return doc 1000 1001 1002class DiscardTooManyEndingEllipsis(Filter): 1003 """ 1004 ellipsisで終わるような行が大量に含まれるような文書を取り除くためのフィルタです. 1005 ellipsisとしては ... と … を用いている 1006 同様のフィルタが RedPajama v2で用いられています. 1007 1008 例として, 以下のような文書を検知します. 1009 ``` 1010 ペアーズは女性、という驚愕の過食が出ているのをごアラサーですか。時代から付... 1011 バツイチアラフォー 婚活ち女性の特徴と子持な付... 1012 ``` 1013 1014 デフォルトではしきい値を0.7としているが, これはC4から0.1%を削るような設定であり、 1015 precisionを重視した設定です. 1016 """ 1017 1018 def __init__( 1019 self, 1020 threshold: float = 0.7, 1021 *args: Any, 1022 **kwargs: Any, 1023 ) -> None: 1024 """ 1025 Args: 1026 threshold: The document is removed if ratio of lines ending with ellipsis is higher than this value 1027 *args: 1028 **kwargs: 1029 """ # noqa: E501 1030 super().__init__(*args, **kwargs) 1031 self.threshold = threshold 1032 self.ellipsis_pattern = re.compile(r"(\.{3}|…)\n") # matches ...\n and …\n 1033 1034 def apply(self, doc: Document) -> Document: 1035 ellipsis_count = len(self.ellipsis_pattern.findall(doc.text)) 1036 newline_count = max(doc.text.count("\n"), 1) # avoid zero division 1037 ellipsis_ratio = ellipsis_count / newline_count 1038 1039 if ellipsis_ratio > self.threshold: 1040 doc.is_rejected = True 1041 return doc 1042 1043 1044class DiscardTooShortLines(Filter): 1045 """ 1046 短い行を大量に含む文書を捨てるためのフィルタです. 1047 1048 メニューバーやパンくずリストのような要素を大量に含む文書を取り除くのに有効です. 1049 """ 1050 1051 def __init__(self, threshold: float = 0.5, *args: Any, **kwargs: Any) -> None: 1052 """ 1053 Args: 1054 threshold: The document is removed if the ratio of short (<10 chars) lines are more than this value. 1055 *args: 1056 **kwargs: 1057 """ # noqa: E501 1058 super().__init__(*args, **kwargs) 1059 self.threshold = threshold 1060 # この値は適当に決め打ち 1061 self.minimum_line_length = 10 1062 1063 def apply(self, doc: Document) -> Document: 1064 lines = [len(x) for x in doc.text.split("\n")] 1065 short_lines = [x for x in lines if x <= self.minimum_line_length] 1066 if (len(short_lines) / len(lines)) > self.threshold: 1067 doc.is_rejected = True 1068 return doc
32class ExampleHojiChar(Filter): 33 """基本的なフィルタの実装例です. 末尾に'<hojichar>'を追加します.""" 34 35 def apply(self, document: Document) -> Document: 36 """ 37 >>> ExampleHojiChar()("hello, world") 38 'hello, world<hojichar>' 39 """ 40 document.text += "<hojichar>" 41 return document
基本的なフィルタの実装例です. 末尾に'
35 def apply(self, document: Document) -> Document: 36 """ 37 >>> ExampleHojiChar()("hello, world") 38 'hello, world<hojichar>' 39 """ 40 document.text += "<hojichar>" 41 return document
>>> ExampleHojiChar()("hello, world")
'hello, world<hojichar>'
Inherited Members
44class ExampleDiscardDocumentContainKeyword(Filter): 45 """特定のキーワードを持つドキュメントを破棄するようなフィルタの実装例です.""" 46 47 def __init__(self, keyword: str, *args: Any, **kwargs: Any) -> None: 48 super().__init__(*args, **kwargs) 49 self.keyword = keyword 50 51 def apply(self, document: Document) -> Document: 52 """ 53 >>> ExampleDiscardDocumentContainKeyword("バカ").apply(Document("あいつはバカだ")).is_rejected 54 True 55 """ 56 if self.keyword in document.text: 57 document.is_rejected = True 58 return document
特定のキーワードを持つドキュメントを破棄するようなフィルタの実装例です.
47 def __init__(self, keyword: str, *args: Any, **kwargs: Any) -> None: 48 super().__init__(*args, **kwargs) 49 self.keyword = keyword
Parameters
p : float, optional Probability that this filter will be applied. Default=1
51 def apply(self, document: Document) -> Document: 52 """ 53 >>> ExampleDiscardDocumentContainKeyword("バカ").apply(Document("あいつはバカだ")).is_rejected 54 True 55 """ 56 if self.keyword in document.text: 57 document.is_rejected = True 58 return document
>>> ExampleDiscardDocumentContainKeyword("バカ").apply(Document("あいつはバカだ")).is_rejected
True
Inherited Members
61class Identity(Filter): 62 """何も変化を加えないフィルタです. テスト・デバッグに用いられます.""" 63 64 def apply(self, document: Document) -> Document: 65 return document
何も変化を加えないフィルタです. テスト・デバッグに用いられます.
Definition of filter behavior.
In this method, the filter will modify document.text
, or
set document.is_rejected = True
to discard the document.
Do not define a filter that changes both document.text
and document.token
Parameters
document : Document Input document
Returns
Document Processed Document
Inherited Members
68class DiscardAll(Filter): 69 """ 70 すべてのドキュメントを破棄するフィルタです. 71 テスト・デバッグに用いられます. 72 """ 73 74 def apply(self, document: Document) -> Document: 75 document.is_rejected = True 76 return document
すべてのドキュメントを破棄するフィルタです. テスト・デバッグに用いられます.
74 def apply(self, document: Document) -> Document: 75 document.is_rejected = True 76 return document
Definition of filter behavior.
In this method, the filter will modify document.text
, or
set document.is_rejected = True
to discard the document.
Do not define a filter that changes both document.text
and document.token
Parameters
document : Document Input document
Returns
Document Processed Document
Inherited Members
79class ApplyDiscard(Filter): 80 """ 81 上流フィルタで破棄された`Document`を空文字列にします. 82 83 `Document.is_rejected=True` の ドキュメントは無視されるため, 84 このフィルタを `Compose` のコンストラクタに渡しても動作しません. 85 このフィルタは主に`Compose` 内部や, `discard_filtered=False` を指定 86 したデバッグ時などに利用されます. 87 """ 88 89 def __init__(self, *args: Any, **kwargs: Any) -> None: 90 super().__init__(*args, **kwargs) 91 92 def apply(self, document: Document) -> Document: 93 """ 94 >>> ApplyDiscard().apply(Document(text="hello", is_rejected=True)).text 95 '' 96 """ 97 if document.is_rejected: 98 document.text = "" 99 100 return document
上流フィルタで破棄されたDocument
を空文字列にします.
Document.is_rejected=True
の ドキュメントは無視されるため,
このフィルタを Compose
のコンストラクタに渡しても動作しません.
このフィルタは主にCompose
内部や, discard_filtered=False
を指定
したデバッグ時などに利用されます.
Parameters
p : float, optional Probability that this filter will be applied. Default=1
92 def apply(self, document: Document) -> Document: 93 """ 94 >>> ApplyDiscard().apply(Document(text="hello", is_rejected=True)).text 95 '' 96 """ 97 if document.is_rejected: 98 document.text = "" 99 100 return document
>>> ApplyDiscard().apply(Document(text="hello", is_rejected=True)).text
''
Inherited Members
103class Sleep(Filter): 104 """ 105 デバッグ用のフィルタです. 指定秒スリープします. 106 """ 107 108 def __init__(self, time: float = 1.0, *args: Any, **kwargs: Any) -> None: 109 super().__init__(*args, **kwargs) 110 self.time = time 111 112 def apply(self, document: Document) -> Document: 113 """ 114 >>> Sleep(0.1)('hello') # After 0.1 seconds, 115 'hello' 116 """ 117 time.sleep(self.time) 118 return document
デバッグ用のフィルタです. 指定秒スリープします.
108 def __init__(self, time: float = 1.0, *args: Any, **kwargs: Any) -> None: 109 super().__init__(*args, **kwargs) 110 self.time = time
Parameters
p : float, optional Probability that this filter will be applied. Default=1
112 def apply(self, document: Document) -> Document: 113 """ 114 >>> Sleep(0.1)('hello') # After 0.1 seconds, 115 'hello' 116 """ 117 time.sleep(self.time) 118 return document
>>> Sleep(0.1)('hello') # After 0.1 seconds,
'hello'
Inherited Members
121class DocumentNormalizer(Filter): 122 """ 123 Unicode の正規化をします. 124 """ 125 126 def __init__(self, *args: Any, **kwargs: Any) -> None: 127 super().__init__(*args, **kwargs) 128 129 def apply(self, document: Document) -> Document: 130 document.text = unicodedata.normalize("NFKC", document.text) 131 return document
Unicode の正規化をします.
Parameters
p : float, optional Probability that this filter will be applied. Default=1
129 def apply(self, document: Document) -> Document: 130 document.text = unicodedata.normalize("NFKC", document.text) 131 return document
Definition of filter behavior.
In this method, the filter will modify document.text
, or
set document.is_rejected = True
to discard the document.
Do not define a filter that changes both document.text
and document.token
Parameters
document : Document Input document
Returns
Document Processed Document
Inherited Members
134class JSONLoader(Filter): 135 """ 136 テキストを Json として解釈し, `key` で指定した要素を文字列として 137 doument に格納します.デフォルトの `key` は 'text' です. 138 139 Json の読み込み, あるいは `key` の読み込みに失敗した際には例外を送出します. 140 これらを無視する場合は, `ignore=True` にします. その際, 読み込みに失敗 141 したドキュメントは破棄されます. 142 """ 143 144 def __init__( 145 self, 146 key: str = "text", 147 ignore: bool = False, 148 extra_keys: Optional[List[str]] = None, 149 *args: Any, 150 **kwargs: Any, 151 ) -> None: 152 super().__init__(*args, **kwargs) 153 self.key = key 154 self.ignore = ignore 155 self.extra_keys = extra_keys 156 157 def apply(self, document: Document) -> Document: 158 """ 159 >>> JSONLoader()( '{"text": "hello, world", "words": 2}' ) 160 'hello, world' 161 162 >>> JSONLoader()( '{"text": hello, world ....' ) # Broken JSON 163 Traceback (most recent call last): 164 ... 165 json.decoder.JSONDecodeError: Expecting value: line 1 column 10 (char 9) 166 167 >>> JSONLoader()( '{"words": 2}' ) 168 Traceback (most recent call last): 169 ... 170 KeyError: 'text' 171 172 >>> JSONLoader(ignore=True).apply(Document('{"text": hello, world ....' )).is_rejected 173 True 174 """ 175 try: 176 data = json.loads(document.text) 177 document.text = str(data[self.key]) 178 if self.extra_keys is not None: 179 document.extras = {key: data[key] for key in self.extra_keys if key in data} 180 except Exception as e: 181 logger.error(f"Failed to parsing in JSONLoader. Input document: \n{document.text}") 182 if self.ignore: 183 document.is_rejected = True 184 return document 185 else: 186 raise e 187 188 return document
テキストを Json として解釈し, key
で指定した要素を文字列として
doument に格納します.デフォルトの key
は 'text' です.
Json の読み込み, あるいは key
の読み込みに失敗した際には例外を送出します.
これらを無視する場合は, ignore=True
にします. その際, 読み込みに失敗
したドキュメントは破棄されます.
144 def __init__( 145 self, 146 key: str = "text", 147 ignore: bool = False, 148 extra_keys: Optional[List[str]] = None, 149 *args: Any, 150 **kwargs: Any, 151 ) -> None: 152 super().__init__(*args, **kwargs) 153 self.key = key 154 self.ignore = ignore 155 self.extra_keys = extra_keys
Parameters
p : float, optional Probability that this filter will be applied. Default=1
157 def apply(self, document: Document) -> Document: 158 """ 159 >>> JSONLoader()( '{"text": "hello, world", "words": 2}' ) 160 'hello, world' 161 162 >>> JSONLoader()( '{"text": hello, world ....' ) # Broken JSON 163 Traceback (most recent call last): 164 ... 165 json.decoder.JSONDecodeError: Expecting value: line 1 column 10 (char 9) 166 167 >>> JSONLoader()( '{"words": 2}' ) 168 Traceback (most recent call last): 169 ... 170 KeyError: 'text' 171 172 >>> JSONLoader(ignore=True).apply(Document('{"text": hello, world ....' )).is_rejected 173 True 174 """ 175 try: 176 data = json.loads(document.text) 177 document.text = str(data[self.key]) 178 if self.extra_keys is not None: 179 document.extras = {key: data[key] for key in self.extra_keys if key in data} 180 except Exception as e: 181 logger.error(f"Failed to parsing in JSONLoader. Input document: \n{document.text}") 182 if self.ignore: 183 document.is_rejected = True 184 return document 185 else: 186 raise e 187 188 return document
>>> JSONLoader()( '{"text": "hello, world", "words": 2}' )
'hello, world'
>>> JSONLoader()( '{"text": hello, world ....' ) # Broken JSON
Traceback (most recent call last):
...
json.decoder.JSONDecodeError: Expecting value: line 1 column 10 (char 9)
>>> JSONLoader()( '{"words": 2}' )
Traceback (most recent call last):
...
KeyError: 'text'
>>> JSONLoader(ignore=True).apply(Document('{"text": hello, world ....' )).is_rejected
True
Inherited Members
191class JSONDumper(Filter): 192 """ 193 Document.text の文字列を json に変換します. 194 必要に応じ Document のメタデータを付与します. これはドキュメントの破棄事由が含まれ、偽陽性の分析に有効です。 195 デフォルトで `skip_rejected` が `False` にセットされており、Document の破棄フラグにかかわらず 196 処理されます。 197 """ 198 199 def __init__( 200 self, 201 dump_reason: bool = False, 202 p: float = 1, 203 skip_rejected: bool = False, 204 export_extras: bool = False, 205 *args: Any, 206 **kwargs: Any, 207 ) -> None: 208 """ 209 Args: 210 dump_reason (bool, optional): `is_rejected`, `reason` エントリをダンプします. Defaults to False. 211 p (float, optional): Apply probability. Defaults to 1. 212 skip_rejected (bool, optional): 破棄済みサンプルを排除しません. 213 """ 214 super().__init__(p, skip_rejected, *args, **kwargs) 215 self.dump_reason = dump_reason 216 self.export_extras = export_extras 217 218 def apply(self, document: Document) -> Document: 219 """ 220 >>> JSONDumper()("hojichar") 221 '{"text": "hojichar"}' 222 """ 223 text = document.text 224 if self.dump_reason: 225 if self.export_extras: 226 document.text = json.dumps( 227 { 228 "text": text, 229 "is_rejected": document.is_rejected, 230 "reason": document.reject_reason, 231 "extras": document.extras, 232 }, 233 ensure_ascii=False, 234 ) 235 else: 236 document.text = json.dumps( 237 { 238 "text": text, 239 "is_rejected": document.is_rejected, 240 "reason": document.reject_reason, 241 }, 242 ensure_ascii=False, 243 ) 244 else: 245 if self.export_extras: 246 document.text = json.dumps( 247 { 248 "text": text, 249 "extras": document.extras, 250 }, 251 ensure_ascii=False, 252 ) 253 else: 254 document.text = json.dumps({"text": text}, ensure_ascii=False) 255 return document
Document.text の文字列を json に変換します.
必要に応じ Document のメタデータを付与します. これはドキュメントの破棄事由が含まれ、偽陽性の分析に有効です。
デフォルトで skip_rejected
が False
にセットされており、Document の破棄フラグにかかわらず
処理されます。
199 def __init__( 200 self, 201 dump_reason: bool = False, 202 p: float = 1, 203 skip_rejected: bool = False, 204 export_extras: bool = False, 205 *args: Any, 206 **kwargs: Any, 207 ) -> None: 208 """ 209 Args: 210 dump_reason (bool, optional): `is_rejected`, `reason` エントリをダンプします. Defaults to False. 211 p (float, optional): Apply probability. Defaults to 1. 212 skip_rejected (bool, optional): 破棄済みサンプルを排除しません. 213 """ 214 super().__init__(p, skip_rejected, *args, **kwargs) 215 self.dump_reason = dump_reason 216 self.export_extras = export_extras
Args:
dump_reason (bool, optional): is_rejected
, reason
エントリをダンプします. Defaults to False.
p (float, optional): Apply probability. Defaults to 1.
skip_rejected (bool, optional): 破棄済みサンプルを排除しません.
218 def apply(self, document: Document) -> Document: 219 """ 220 >>> JSONDumper()("hojichar") 221 '{"text": "hojichar"}' 222 """ 223 text = document.text 224 if self.dump_reason: 225 if self.export_extras: 226 document.text = json.dumps( 227 { 228 "text": text, 229 "is_rejected": document.is_rejected, 230 "reason": document.reject_reason, 231 "extras": document.extras, 232 }, 233 ensure_ascii=False, 234 ) 235 else: 236 document.text = json.dumps( 237 { 238 "text": text, 239 "is_rejected": document.is_rejected, 240 "reason": document.reject_reason, 241 }, 242 ensure_ascii=False, 243 ) 244 else: 245 if self.export_extras: 246 document.text = json.dumps( 247 { 248 "text": text, 249 "extras": document.extras, 250 }, 251 ensure_ascii=False, 252 ) 253 else: 254 document.text = json.dumps({"text": text}, ensure_ascii=False) 255 return document
>>> JSONDumper()("hojichar")
'{"text": "hojichar"}'
Inherited Members
258class DocumentLengthFilter(Filter): 259 """ 260 `min_doc_len`, `max_doc_len` で指定した上限・下限の範囲内にないドキュメントを破棄します. 261 デフォルトでは 200字 以上 50000字以内のテキストが受理されます. 262 """ 263 264 def __init__( 265 self, 266 min_doc_len: Optional[int] = None, 267 max_doc_len: Optional[int] = None, 268 *args: Any, 269 **kwargs: Any, 270 ) -> None: 271 super().__init__(*args, **kwargs) 272 273 self.min_doc_len = min_doc_len 274 self.max_doc_len = max_doc_len 275 276 def apply(self, doc: Document) -> Document: 277 """ 278 >>> DocumentLengthFilter(min_doc_len=5).apply(Document("1234")).is_rejected 279 True 280 """ 281 doc_len = len(doc.text) 282 if self.min_doc_len is not None: 283 if doc_len < self.min_doc_len: 284 doc.is_rejected = True 285 if self.max_doc_len is not None: 286 if self.max_doc_len < doc_len: 287 doc.is_rejected = True 288 return doc
min_doc_len
, max_doc_len
で指定した上限・下限の範囲内にないドキュメントを破棄します.
デフォルトでは 200字 以上 50000字以内のテキストが受理されます.
264 def __init__( 265 self, 266 min_doc_len: Optional[int] = None, 267 max_doc_len: Optional[int] = None, 268 *args: Any, 269 **kwargs: Any, 270 ) -> None: 271 super().__init__(*args, **kwargs) 272 273 self.min_doc_len = min_doc_len 274 self.max_doc_len = max_doc_len
Parameters
p : float, optional Probability that this filter will be applied. Default=1
276 def apply(self, doc: Document) -> Document: 277 """ 278 >>> DocumentLengthFilter(min_doc_len=5).apply(Document("1234")).is_rejected 279 True 280 """ 281 doc_len = len(doc.text) 282 if self.min_doc_len is not None: 283 if doc_len < self.min_doc_len: 284 doc.is_rejected = True 285 if self.max_doc_len is not None: 286 if self.max_doc_len < doc_len: 287 doc.is_rejected = True 288 return doc
>>> DocumentLengthFilter(min_doc_len=5).apply(Document("1234")).is_rejected
True
Inherited Members
291class NgWordsFilterJa(Filter): 292 """ 293 日本語のNGワード(および不適切語)を含む文書を破棄します. 294 `dict_path` で指定したファイルから, キーワードのリストを得ます. 295 ファイルは単語が改行で羅列されたテキストファイルです. 296 297 `ignore_confused` を `True` にすると, 298 偽陽性を軽減するために, カタカナのNGワードは前後にカタカナが無い場合のみNG判定されます. 299 デフォルト値は `False` です. 300 """ 301 302 def __init__( 303 self, 304 dict_path: Union[str, PathLike], 305 ignore_confused: bool = False, 306 *args: Any, 307 **kwargs: Any, 308 ) -> None: 309 super().__init__(*args, **kwargs) 310 311 with open(dict_path, encoding="utf-8") as fp: 312 ng_words = fp.read().split("\n") 313 ng_words = [w.strip() for w in ng_words if not len(w) == 0] 314 315 if ignore_confused: 316 words_katakana = [] 317 words_not_katakana = [] 318 for w in ng_words: 319 if re.fullmatch(r"[ァ-ヴー]+", w): 320 words_katakana.append(re.escape(w)) 321 else: 322 words_not_katakana.append(re.escape(w)) 323 katakana_pat = "|".join(words_katakana) 324 katakana_pat = rf"(?<![ァ-ヴー])({katakana_pat})(?![ァ-ヴー])" 325 pat = "|".join(words_not_katakana) + "|" + katakana_pat 326 self.keyword_pat = re.compile(pat) 327 else: 328 ng_words = [re.escape(w) for w in ng_words] 329 pat = "|".join(ng_words) 330 self.keyword_pat = re.compile(pat) 331 332 def apply(self, doc: Document) -> Document: 333 regex_match = self.keyword_pat.search(doc.text) 334 if regex_match: 335 doc.is_rejected = True 336 self.matched_text = regex_match.group() 337 self.matched_text_neighbor = doc.text[ 338 regex_match.start() - 20 : regex_match.end() + 20 339 ] 340 341 return doc
日本語のNGワード(および不適切語)を含む文書を破棄します.
dict_path
で指定したファイルから, キーワードのリストを得ます.
ファイルは単語が改行で羅列されたテキストファイルです.
ignore_confused
を True
にすると,
偽陽性を軽減するために, カタカナのNGワードは前後にカタカナが無い場合のみNG判定されます.
デフォルト値は False
です.
302 def __init__( 303 self, 304 dict_path: Union[str, PathLike], 305 ignore_confused: bool = False, 306 *args: Any, 307 **kwargs: Any, 308 ) -> None: 309 super().__init__(*args, **kwargs) 310 311 with open(dict_path, encoding="utf-8") as fp: 312 ng_words = fp.read().split("\n") 313 ng_words = [w.strip() for w in ng_words if not len(w) == 0] 314 315 if ignore_confused: 316 words_katakana = [] 317 words_not_katakana = [] 318 for w in ng_words: 319 if re.fullmatch(r"[ァ-ヴー]+", w): 320 words_katakana.append(re.escape(w)) 321 else: 322 words_not_katakana.append(re.escape(w)) 323 katakana_pat = "|".join(words_katakana) 324 katakana_pat = rf"(?<![ァ-ヴー])({katakana_pat})(?![ァ-ヴー])" 325 pat = "|".join(words_not_katakana) + "|" + katakana_pat 326 self.keyword_pat = re.compile(pat) 327 else: 328 ng_words = [re.escape(w) for w in ng_words] 329 pat = "|".join(ng_words) 330 self.keyword_pat = re.compile(pat)
Parameters
p : float, optional Probability that this filter will be applied. Default=1
332 def apply(self, doc: Document) -> Document: 333 regex_match = self.keyword_pat.search(doc.text) 334 if regex_match: 335 doc.is_rejected = True 336 self.matched_text = regex_match.group() 337 self.matched_text_neighbor = doc.text[ 338 regex_match.start() - 20 : regex_match.end() + 20 339 ] 340 341 return doc
Definition of filter behavior.
In this method, the filter will modify document.text
, or
set document.is_rejected = True
to discard the document.
Do not define a filter that changes both document.text
and document.token
Parameters
document : Document Input document
Returns
Document Processed Document
Inherited Members
344class NgWordsFilterEn(Filter): 345 """ 346 英語のNGワード(および不適切語)を含む文書を破棄します. 347 `dict_path` で指定したファイルから, キーワードのリストを得ます. 348 ファイルは単語が改行で羅列されたテキストファイルです. 349 """ 350 351 def __init__(self, dict_path: Union[str, PathLike], *args: Any, **kwargs: Any) -> None: 352 super().__init__(*args, **kwargs) 353 354 with open(dict_path, encoding="utf-8") as fp: 355 ng_words = fp.read().split("\n") 356 ng_words = [re.escape(w.strip()) for w in ng_words if not len(w) == 0] 357 pat = "|".join(ng_words) 358 # 英語のパターンにマッチするようにしている, \s[単語]\s や [単語]. [単語], などにマッチ. 359 self.keyword_pat = re.compile(rf"(?:^| )({pat})(?:( |,|\.)|$)", re.IGNORECASE) 360 361 def apply(self, doc: Document) -> Document: 362 if self.keyword_pat.search(doc.text): 363 doc.is_rejected = True 364 return doc
英語のNGワード(および不適切語)を含む文書を破棄します.
dict_path
で指定したファイルから, キーワードのリストを得ます.
ファイルは単語が改行で羅列されたテキストファイルです.
351 def __init__(self, dict_path: Union[str, PathLike], *args: Any, **kwargs: Any) -> None: 352 super().__init__(*args, **kwargs) 353 354 with open(dict_path, encoding="utf-8") as fp: 355 ng_words = fp.read().split("\n") 356 ng_words = [re.escape(w.strip()) for w in ng_words if not len(w) == 0] 357 pat = "|".join(ng_words) 358 # 英語のパターンにマッチするようにしている, \s[単語]\s や [単語]. [単語], などにマッチ. 359 self.keyword_pat = re.compile(rf"(?:^| )({pat})(?:( |,|\.)|$)", re.IGNORECASE)
Parameters
p : float, optional Probability that this filter will be applied. Default=1
361 def apply(self, doc: Document) -> Document: 362 if self.keyword_pat.search(doc.text): 363 doc.is_rejected = True 364 return doc
Definition of filter behavior.
In this method, the filter will modify document.text
, or
set document.is_rejected = True
to discard the document.
Do not define a filter that changes both document.text
and document.token
Parameters
document : Document Input document
Returns
Document Processed Document
Inherited Members
367class DiscardAdultContentJa(NgWordsFilterJa): 368 """ 369 日本語のアダルトキーワード(および不適切語)を含む文書を破棄します. 370 `dict_path` で指定したファイルから, キーワードのリストを得ます. 371 ファイルは単語が改行で羅列されたテキストファイルです. 372 デフォルトの`dict_path` は /hojichar/dict/adult_keywords_ja.txt です. 373 """ 374 375 def __init__( 376 self, 377 dict_path: Union[str, PathLike] = BASE_PATH / "dict/adult_keywords_ja.txt", 378 *args: Any, 379 **kwargs: Any, 380 ) -> None: 381 super().__init__(dict_path, *args, **kwargs) 382 383 def apply(self, doc: Document) -> Document: 384 """ 385 >>> DiscardAdultContentJa().apply(Document("<TEST_STRING_OF_ADULT_KEYWORD>")).is_rejected 386 True 387 388 >>> DiscardAdultContentJa().apply(Document("ほうじ茶")).is_rejected 389 False 390 391 挙動は正しいが誤検知しているケース. 他にも, サック in リュックサック, 392 >>> DiscardAdultContentJa().apply(Document("アスパラガス")).is_rejected \ 393 # Matching with NG keyword "アス" 394 True 395 """ 396 return super().apply(doc)
日本語のアダルトキーワード(および不適切語)を含む文書を破棄します.
dict_path
で指定したファイルから, キーワードのリストを得ます.
ファイルは単語が改行で羅列されたテキストファイルです.
デフォルトのdict_path
は /hojichar/dict/adult_keywords_ja.txt です.
375 def __init__( 376 self, 377 dict_path: Union[str, PathLike] = BASE_PATH / "dict/adult_keywords_ja.txt", 378 *args: Any, 379 **kwargs: Any, 380 ) -> None: 381 super().__init__(dict_path, *args, **kwargs)
Parameters
p : float, optional Probability that this filter will be applied. Default=1
383 def apply(self, doc: Document) -> Document: 384 """ 385 >>> DiscardAdultContentJa().apply(Document("<TEST_STRING_OF_ADULT_KEYWORD>")).is_rejected 386 True 387 388 >>> DiscardAdultContentJa().apply(Document("ほうじ茶")).is_rejected 389 False 390 391 挙動は正しいが誤検知しているケース. 他にも, サック in リュックサック, 392 >>> DiscardAdultContentJa().apply(Document("アスパラガス")).is_rejected \ 393 # Matching with NG keyword "アス" 394 True 395 """ 396 return super().apply(doc)
>>> DiscardAdultContentJa().apply(Document("<TEST_STRING_OF_ADULT_KEYWORD>")).is_rejected
True
>>> DiscardAdultContentJa().apply(Document("ほうじ茶")).is_rejected
False
挙動は正しいが誤検知しているケース. 他にも, サック in リュックサック,
>>> DiscardAdultContentJa().apply(Document("アスパラガス")).is_rejected # Matching with NG keyword "アス"
True
Inherited Members
399class DiscardAdultContentEn(NgWordsFilterEn): 400 """ 401 英語のアダルトキーワード(および不適切語)を含む文書を破棄します. 402 `dict_path` で指定したファイルから, キーワードのリストを得ます. 403 ファイルは単語が改行で羅列されたテキストファイルです. 404 デフォルトの`dict_path` は /hojichar/dict/adult_keywords_en.txt です. 405 """ 406 407 def __init__( 408 self, 409 dict_path: Union[str, PathLike] = BASE_PATH / "dict/adult_keywords_en.txt", 410 *args: Any, 411 **kwargs: Any, 412 ) -> None: 413 super().__init__(dict_path, *args, **kwargs) 414 415 def apply(self, doc: Document) -> Document: 416 """ 417 >>> DiscardAdultContentEn().apply(Document("<TEST_STRING_OF_ADULT_KEYWORD>")).is_rejected 418 True 419 420 >>> DiscardAdultContentEn().apply(Document("hojichar")).is_rejected 421 False 422 """ 423 return super().apply(doc)
英語のアダルトキーワード(および不適切語)を含む文書を破棄します.
dict_path
で指定したファイルから, キーワードのリストを得ます.
ファイルは単語が改行で羅列されたテキストファイルです.
デフォルトのdict_path
は /hojichar/dict/adult_keywords_en.txt です.
407 def __init__( 408 self, 409 dict_path: Union[str, PathLike] = BASE_PATH / "dict/adult_keywords_en.txt", 410 *args: Any, 411 **kwargs: Any, 412 ) -> None: 413 super().__init__(dict_path, *args, **kwargs)
Parameters
p : float, optional Probability that this filter will be applied. Default=1
415 def apply(self, doc: Document) -> Document: 416 """ 417 >>> DiscardAdultContentEn().apply(Document("<TEST_STRING_OF_ADULT_KEYWORD>")).is_rejected 418 True 419 420 >>> DiscardAdultContentEn().apply(Document("hojichar")).is_rejected 421 False 422 """ 423 return super().apply(doc)
>>> DiscardAdultContentEn().apply(Document("<TEST_STRING_OF_ADULT_KEYWORD>")).is_rejected
True
>>> DiscardAdultContentEn().apply(Document("hojichar")).is_rejected
False
Inherited Members
426class DiscardDiscriminationContentJa(NgWordsFilterJa): 427 """ 428 日本語の差別キーワード(および不適切語)を含む文書を破棄します. 429 `dict_path` で指定したファイルから, キーワードのリストを得ます. 430 ファイルは単語が改行で羅列されたテキストファイルです. 431 デフォルトの`dict_path` は /hojichar/dict/discrimination_keywords_ja.txt です. 432 """ 433 434 def __init__( 435 self, 436 dict_path: Union[str, PathLike] = BASE_PATH / "dict/discrimination_keywords_ja.txt", 437 *args: Any, 438 **kwargs: Any, 439 ): 440 super().__init__(dict_path, *args, **kwargs) 441 442 def apply(self, doc: Document) -> Document: 443 """ 444 >>> DiscardDiscriminationContentJa().\ 445 apply(Document("<TEST_STRING_OF_DISCRIMINATION_KEYWORD>")).is_rejected 446 True 447 448 >>> DiscardDiscriminationContentJa().apply(Document("ほうじ茶")).is_rejected 449 False 450 """ 451 return super().apply(doc)
日本語の差別キーワード(および不適切語)を含む文書を破棄します.
dict_path
で指定したファイルから, キーワードのリストを得ます.
ファイルは単語が改行で羅列されたテキストファイルです.
デフォルトのdict_path
は /hojichar/dict/discrimination_keywords_ja.txt です.
434 def __init__( 435 self, 436 dict_path: Union[str, PathLike] = BASE_PATH / "dict/discrimination_keywords_ja.txt", 437 *args: Any, 438 **kwargs: Any, 439 ): 440 super().__init__(dict_path, *args, **kwargs)
Parameters
p : float, optional Probability that this filter will be applied. Default=1
442 def apply(self, doc: Document) -> Document: 443 """ 444 >>> DiscardDiscriminationContentJa().\ 445 apply(Document("<TEST_STRING_OF_DISCRIMINATION_KEYWORD>")).is_rejected 446 True 447 448 >>> DiscardDiscriminationContentJa().apply(Document("ほうじ茶")).is_rejected 449 False 450 """ 451 return super().apply(doc)
>>> DiscardDiscriminationContentJa(). apply(Document("<TEST_STRING_OF_DISCRIMINATION_KEYWORD>")).is_rejected
True
>>> DiscardDiscriminationContentJa().apply(Document("ほうじ茶")).is_rejected
False
Inherited Members
454class DiscardViolenceContentJa(NgWordsFilterJa): 455 """ 456 日本語の暴力・脅迫を示唆するキーワードを含む文書を破棄します. 457 `dict_path` で指定したファイルから, キーワードのリストを得ます. 458 ファイルは単語が改行で羅列されたテキストファイルです. 459 デフォルトの`dict_path` は /hojichar/dict/violence_keywords_ja.txt です. 460 """ 461 462 def __init__( 463 self, 464 dict_path: Union[str, PathLike] = BASE_PATH / "dict/violence_keywords_ja.txt", 465 *args: Any, 466 **kwargs: Any, 467 ) -> None: 468 super().__init__(dict_path, *args, **kwargs) 469 470 def apply(self, doc: Document) -> Document: 471 """ 472 >>> DiscardViolenceContentJa()\ 473 .apply(Document("<TEST_STRING_OF_VIOLENCE_KEYWORD>")).is_rejected 474 True 475 476 >>> DiscardViolenceContentJa().apply(Document("ほうじ茶")).is_rejected 477 False 478 """ 479 return super().apply(doc)
日本語の暴力・脅迫を示唆するキーワードを含む文書を破棄します.
dict_path
で指定したファイルから, キーワードのリストを得ます.
ファイルは単語が改行で羅列されたテキストファイルです.
デフォルトのdict_path
は /hojichar/dict/violence_keywords_ja.txt です.
462 def __init__( 463 self, 464 dict_path: Union[str, PathLike] = BASE_PATH / "dict/violence_keywords_ja.txt", 465 *args: Any, 466 **kwargs: Any, 467 ) -> None: 468 super().__init__(dict_path, *args, **kwargs)
Parameters
p : float, optional Probability that this filter will be applied. Default=1
470 def apply(self, doc: Document) -> Document: 471 """ 472 >>> DiscardViolenceContentJa()\ 473 .apply(Document("<TEST_STRING_OF_VIOLENCE_KEYWORD>")).is_rejected 474 True 475 476 >>> DiscardViolenceContentJa().apply(Document("ほうじ茶")).is_rejected 477 False 478 """ 479 return super().apply(doc)
>>> DiscardViolenceContentJa() .apply(Document("<TEST_STRING_OF_VIOLENCE_KEYWORD>")).is_rejected
True
>>> DiscardViolenceContentJa().apply(Document("ほうじ茶")).is_rejected
False
Inherited Members
482class DiscardBBSComments(Filter): 483 """ 484 正規表現 "BBS Pattern" に `max_allow_num` 回よりたくさんマッチする文書を破棄します. 485 `max_allow_num` のデフォルト値は14です. 486 正規表現 "BBS Pattern" は下記のリンクで検証可能です. 487 https://regex101.com/r/ybQvL2/1 488 """ 489 490 def __init__(self, max_allowed_num: int = 14, *args: Any, **kwargs: Any) -> None: 491 super().__init__(*args, **kwargs) 492 493 self.max_allowed_num = max_allowed_num 494 self.keyword_pat = re.compile( 495 r"\d{4}[年\.\-\/][\ ]*\d{1,2}[月\.\-\/][\ ]*\d{1,2}[日]*|コメント|SOLD OUT|レビュー|投稿|ページ|\([月火水木金土日]\)|質問|\d+話|楽天市場|-" # noqa 496 ) 497 498 def apply(self, doc: Document) -> Document: 499 """ 500 >>> DiscardBBSComments().apply(Document("楽天市場 質問 投稿 コメント レビュー "*3)).is_rejected 501 True 502 503 >>> DiscardBBSComments().apply(Document("鏡餅")).is_rejected 504 False 505 """ 506 bbs_factor = self.keyword_pat.findall(doc.text) 507 if len(bbs_factor) > self.max_allowed_num: 508 doc.is_rejected = True 509 return doc
正規表現 "BBS Pattern" に max_allow_num
回よりたくさんマッチする文書を破棄します.
max_allow_num
のデフォルト値は14です.
正規表現 "BBS Pattern" は下記のリンクで検証可能です.
https://regex101.com/r/ybQvL2/1
490 def __init__(self, max_allowed_num: int = 14, *args: Any, **kwargs: Any) -> None: 491 super().__init__(*args, **kwargs) 492 493 self.max_allowed_num = max_allowed_num 494 self.keyword_pat = re.compile( 495 r"\d{4}[年\.\-\/][\ ]*\d{1,2}[月\.\-\/][\ ]*\d{1,2}[日]*|コメント|SOLD OUT|レビュー|投稿|ページ|\([月火水木金土日]\)|質問|\d+話|楽天市場|-" # noqa 496 )
Parameters
p : float, optional Probability that this filter will be applied. Default=1
498 def apply(self, doc: Document) -> Document: 499 """ 500 >>> DiscardBBSComments().apply(Document("楽天市場 質問 投稿 コメント レビュー "*3)).is_rejected 501 True 502 503 >>> DiscardBBSComments().apply(Document("鏡餅")).is_rejected 504 False 505 """ 506 bbs_factor = self.keyword_pat.findall(doc.text) 507 if len(bbs_factor) > self.max_allowed_num: 508 doc.is_rejected = True 509 return doc
>>> DiscardBBSComments().apply(Document("楽天市場 質問 投稿 コメント レビュー "*3)).is_rejected
True
>>> DiscardBBSComments().apply(Document("鏡餅")).is_rejected
False
Inherited Members
512class DiscardAds(Filter): 513 """ 514 主に広告キーワードを`max_allow_num`より多く含む文書を破棄します. 515 デフォルトで`max_allow_num` は14です. 516 `dict_path` で指定したファイルから, 広告キーワードのリストを得ます. 517 ファイルは単語が改行で羅列されたテキストファイルです. 518 デフォルトの`dict_path` は /hojichar/dict/advertisement_keywords_ja.txt です. 519 """ 520 521 def __init__( 522 self, 523 dict_path: Union[str, PathLike] = BASE_PATH / "dict/advertisement_keywords_ja.txt", 524 max_allowed_num: int = 14, 525 *args: Any, 526 **kwargs: Any, 527 ): 528 super().__init__(*args, **kwargs) 529 530 self.max_allow_num = max_allowed_num 531 with open(dict_path, encoding="utf-8") as fp: 532 ng_words = fp.read().split("\n") 533 ng_words = [re.escape(w.strip()) for w in ng_words if not len(w) == 0] 534 pat = r"|".join(ng_words) 535 self.keyword_pat = re.compile(pat) 536 537 def apply(self, doc: Document) -> Document: 538 """ 539 >>> DiscardAds().apply(Document("お問い合わせください 営業時間 よくある質問"*5)).is_rejected 540 True 541 542 >>> DiscardAds().apply(Document("おはよう")).is_rejected 543 False 544 """ 545 ads_factor = self.keyword_pat.findall(doc.text) 546 if len(ads_factor) > self.max_allow_num: 547 doc.is_rejected = True 548 return doc
主に広告キーワードをmax_allow_num
より多く含む文書を破棄します.
デフォルトでmax_allow_num
は14です.
dict_path
で指定したファイルから, 広告キーワードのリストを得ます.
ファイルは単語が改行で羅列されたテキストファイルです.
デフォルトのdict_path
は /hojichar/dict/advertisement_keywords_ja.txt です.
521 def __init__( 522 self, 523 dict_path: Union[str, PathLike] = BASE_PATH / "dict/advertisement_keywords_ja.txt", 524 max_allowed_num: int = 14, 525 *args: Any, 526 **kwargs: Any, 527 ): 528 super().__init__(*args, **kwargs) 529 530 self.max_allow_num = max_allowed_num 531 with open(dict_path, encoding="utf-8") as fp: 532 ng_words = fp.read().split("\n") 533 ng_words = [re.escape(w.strip()) for w in ng_words if not len(w) == 0] 534 pat = r"|".join(ng_words) 535 self.keyword_pat = re.compile(pat)
Parameters
p : float, optional Probability that this filter will be applied. Default=1
537 def apply(self, doc: Document) -> Document: 538 """ 539 >>> DiscardAds().apply(Document("お問い合わせください 営業時間 よくある質問"*5)).is_rejected 540 True 541 542 >>> DiscardAds().apply(Document("おはよう")).is_rejected 543 False 544 """ 545 ads_factor = self.keyword_pat.findall(doc.text) 546 if len(ads_factor) > self.max_allow_num: 547 doc.is_rejected = True 548 return doc
>>> DiscardAds().apply(Document("お問い合わせください 営業時間 よくある質問"*5)).is_rejected
True
>>> DiscardAds().apply(Document("おはよう")).is_rejected
False
Inherited Members
551class AcceptJapanese(Filter): 552 """ 553 日本語でないドキュメントを破棄します. 日本語判定は次の手順で行われます. 554 1. テキストを左から`lookup_size` (デフォルトで50字) 参照し, 555 ひらがな・カタカナが存在すれば日本語と判定する. 556 """ 557 558 def __init__(self, lookup_size: int = 50, *args: Any, **kwargs: Any) -> None: 559 super().__init__(*args, **kwargs) 560 561 self.lookup_size = lookup_size 562 self.hiragana_katakana_pat = re.compile(r"[ぁ-んァ-ン]") 563 564 def apply(self, doc: Document) -> Document: 565 """ 566 >>> AcceptJapanese().apply(Document("This is English document")).is_rejected 567 True 568 569 >>> AcceptJapanese().apply(Document("a"*50 + "あ")).is_rejected 570 True 571 572 >>> AcceptJapanese().apply(Document("ほうじ茶")).is_rejected 573 False 574 """ 575 if not self.hiragana_katakana_pat.search(doc.text[: self.lookup_size]): 576 doc.is_rejected = True 577 return doc
日本語でないドキュメントを破棄します. 日本語判定は次の手順で行われます.
1. テキストを左からlookup_size
(デフォルトで50字) 参照し,
ひらがな・カタカナが存在すれば日本語と判定する.
558 def __init__(self, lookup_size: int = 50, *args: Any, **kwargs: Any) -> None: 559 super().__init__(*args, **kwargs) 560 561 self.lookup_size = lookup_size 562 self.hiragana_katakana_pat = re.compile(r"[ぁ-んァ-ン]")
Parameters
p : float, optional Probability that this filter will be applied. Default=1
564 def apply(self, doc: Document) -> Document: 565 """ 566 >>> AcceptJapanese().apply(Document("This is English document")).is_rejected 567 True 568 569 >>> AcceptJapanese().apply(Document("a"*50 + "あ")).is_rejected 570 True 571 572 >>> AcceptJapanese().apply(Document("ほうじ茶")).is_rejected 573 False 574 """ 575 if not self.hiragana_katakana_pat.search(doc.text[: self.lookup_size]): 576 doc.is_rejected = True 577 return doc
>>> AcceptJapanese().apply(Document("This is English document")).is_rejected
True
>>> AcceptJapanese().apply(Document("a"*50 + "あ")).is_rejected
True
>>> AcceptJapanese().apply(Document("ほうじ茶")).is_rejected
False
Inherited Members
580class DiscardRareKuten(Filter): 581 """ 582 日本語でないドキュメントを破棄します. 日本語判定は次の手順で行われます 583 ドキュメントを句点"。"で区切り, 平均文長が 584 `max_avarage_sentence_length` より長い場合は破棄します. 585 `max_avarage_sentence_length` のデフォルト値は100です. 586 このフィルタは, 文章中の句点の割合が少なすぎるドキュメントを破棄します. 587 """ 588 589 def __init__(self, max_average_sentence_length: int = 100, *args: Any, **kwargs: Any) -> None: 590 super().__init__(*args, **kwargs) 591 592 self.max_average_sentence_length = max_average_sentence_length 593 self.kuten_pat = re.compile(r"。") 594 595 def apply(self, doc: Document) -> Document: 596 """ 597 >>> DiscardRareKuten(max_average_sentence_length=4).apply(Document("おはよ。")).is_rejected 598 False 599 >>> DiscardRareKuten(max_average_sentence_length=4).apply(Document("おはよう。")).is_rejected 600 True 601 """ 602 kuten_lst = self.kuten_pat.findall(doc.text) 603 min_kuten_num = len(doc.text) / self.max_average_sentence_length 604 if len(kuten_lst) < min_kuten_num: 605 doc.is_rejected = True 606 return doc
日本語でないドキュメントを破棄します. 日本語判定は次の手順で行われます
ドキュメントを句点"。"で区切り, 平均文長が
max_avarage_sentence_length
より長い場合は破棄します.
max_avarage_sentence_length
のデフォルト値は100です.
このフィルタは, 文章中の句点の割合が少なすぎるドキュメントを破棄します.
589 def __init__(self, max_average_sentence_length: int = 100, *args: Any, **kwargs: Any) -> None: 590 super().__init__(*args, **kwargs) 591 592 self.max_average_sentence_length = max_average_sentence_length 593 self.kuten_pat = re.compile(r"。")
Parameters
p : float, optional Probability that this filter will be applied. Default=1
595 def apply(self, doc: Document) -> Document: 596 """ 597 >>> DiscardRareKuten(max_average_sentence_length=4).apply(Document("おはよ。")).is_rejected 598 False 599 >>> DiscardRareKuten(max_average_sentence_length=4).apply(Document("おはよう。")).is_rejected 600 True 601 """ 602 kuten_lst = self.kuten_pat.findall(doc.text) 603 min_kuten_num = len(doc.text) / self.max_average_sentence_length 604 if len(kuten_lst) < min_kuten_num: 605 doc.is_rejected = True 606 return doc
>>> DiscardRareKuten(max_average_sentence_length=4).apply(Document("おはよ。")).is_rejected
False
>>> DiscardRareKuten(max_average_sentence_length=4).apply(Document("おはよう。")).is_rejected
True
Inherited Members
671class MaskPersonalInformation(Filter): 672 """ 673 ドキュメントに含まれる電話番号・電子メールアドレスを一部マスキングします. 674 """ 675 676 def __init__(self, *args: Any, **kwargs: Any) -> None: 677 super().__init__(*args, **kwargs) 678 679 self.phone_pat = re.compile( 680 r"((0|\+\d{1,3}[- ]?)(\d{2}[- ]?\d{4}[- ]?|\d[- ]?\d{4}[- ]?|\d{2}[- ]?\d{3}[- ]?|\d{3}[- ]?\d{2}[- ]?|\d{4}[- ]?\d{1}[- ]?))\d{4}" # noqa 681 ) 682 self.email_pat = re.compile( 683 r"[a-zA-Z0-9!#$%&'*+\-/=?^_`{|}~.]+@[A-Za-z0-9!#$%&'*+\-/=?^_`{|}~.]+(\.[A-Za-z0-9\-]+)" # noqa 684 ) 685 686 def apply(self, doc: Document) -> Document: 687 """ 688 >>> MaskPersonalInformation()('06-1234-5678') 689 '06-1234-XXXX' 690 >>> MaskPersonalInformation()('075-123-4567') 691 '075-123-XXXX' 692 >>> MaskPersonalInformation()('0166-12-3456') 693 '0166-12-XXXX' 694 >>> MaskPersonalInformation()('09808-1-2345') 695 '09808-1-XXXX' 696 >>> MaskPersonalInformation()('090-1234-5678') 697 '090-1234-XXXX' 698 >>> MaskPersonalInformation()('0751234567') 699 '075123XXXX' 700 >>> MaskPersonalInformation()('08012345678') 701 '0801234XXXX' 702 >>> MaskPersonalInformation()('連絡は075-123-4567 まで') 703 '連絡は075-123-XXXX まで' 704 >>> MaskPersonalInformation()('+81-80-1234-5678') 705 '+81-80-1234-XXXX' 706 >>> MaskPersonalInformation()('+818012345678') 707 '+81801234XXXX' 708 >>> MaskPersonalInformation()('hogehoge@example.com') 709 'xxxx@yyy.com' 710 >>> MaskPersonalInformation()('何かあれば hogehoge@example.ne.jp まで連絡') 711 '何かあれば xxxx@yyy.jp まで連絡' 712 """ 713 text = self.phone_pat.sub(r"\1XXXX", doc.text) 714 text = self.email_pat.sub(r"xxxx@yyy\1", text) 715 doc.text = text 716 return doc
ドキュメントに含まれる電話番号・電子メールアドレスを一部マスキングします.
676 def __init__(self, *args: Any, **kwargs: Any) -> None: 677 super().__init__(*args, **kwargs) 678 679 self.phone_pat = re.compile( 680 r"((0|\+\d{1,3}[- ]?)(\d{2}[- ]?\d{4}[- ]?|\d[- ]?\d{4}[- ]?|\d{2}[- ]?\d{3}[- ]?|\d{3}[- ]?\d{2}[- ]?|\d{4}[- ]?\d{1}[- ]?))\d{4}" # noqa 681 ) 682 self.email_pat = re.compile( 683 r"[a-zA-Z0-9!#$%&'*+\-/=?^_`{|}~.]+@[A-Za-z0-9!#$%&'*+\-/=?^_`{|}~.]+(\.[A-Za-z0-9\-]+)" # noqa 684 )
Parameters
p : float, optional Probability that this filter will be applied. Default=1
686 def apply(self, doc: Document) -> Document: 687 """ 688 >>> MaskPersonalInformation()('06-1234-5678') 689 '06-1234-XXXX' 690 >>> MaskPersonalInformation()('075-123-4567') 691 '075-123-XXXX' 692 >>> MaskPersonalInformation()('0166-12-3456') 693 '0166-12-XXXX' 694 >>> MaskPersonalInformation()('09808-1-2345') 695 '09808-1-XXXX' 696 >>> MaskPersonalInformation()('090-1234-5678') 697 '090-1234-XXXX' 698 >>> MaskPersonalInformation()('0751234567') 699 '075123XXXX' 700 >>> MaskPersonalInformation()('08012345678') 701 '0801234XXXX' 702 >>> MaskPersonalInformation()('連絡は075-123-4567 まで') 703 '連絡は075-123-XXXX まで' 704 >>> MaskPersonalInformation()('+81-80-1234-5678') 705 '+81-80-1234-XXXX' 706 >>> MaskPersonalInformation()('+818012345678') 707 '+81801234XXXX' 708 >>> MaskPersonalInformation()('hogehoge@example.com') 709 'xxxx@yyy.com' 710 >>> MaskPersonalInformation()('何かあれば hogehoge@example.ne.jp まで連絡') 711 '何かあれば xxxx@yyy.jp まで連絡' 712 """ 713 text = self.phone_pat.sub(r"\1XXXX", doc.text) 714 text = self.email_pat.sub(r"xxxx@yyy\1", text) 715 doc.text = text 716 return doc
>>> MaskPersonalInformation()('06-1234-5678')
'06-1234-XXXX'
>>> MaskPersonalInformation()('075-123-4567')
'075-123-XXXX'
>>> MaskPersonalInformation()('0166-12-3456')
'0166-12-XXXX'
>>> MaskPersonalInformation()('09808-1-2345')
'09808-1-XXXX'
>>> MaskPersonalInformation()('090-1234-5678')
'090-1234-XXXX'
>>> MaskPersonalInformation()('0751234567')
'075123XXXX'
>>> MaskPersonalInformation()('08012345678')
'0801234XXXX'
>>> MaskPersonalInformation()('連絡は075-123-4567 まで')
'連絡は075-123-XXXX まで'
>>> MaskPersonalInformation()('+81-80-1234-5678')
'+81-80-1234-XXXX'
>>> MaskPersonalInformation()('+818012345678')
'+81801234XXXX'
>>> MaskPersonalInformation()('hogehoge@example.com')
'xxxx@yyy.com'
>>> MaskPersonalInformation()('何かあれば hogehoge@example.ne.jp まで連絡')
'何かあれば xxxx@yyy.jp まで連絡'
Inherited Members
719class DiscardTooManyNouns(Filter): 720 """ 721 [!CAUTION] This filter requires `fugashi` package. Please install it 722 by `pip install 'hojichar[all]'`. 723 724 A filter that removes document with too many nouns in Japanese i.e., 725 documents such as advertisement, word salad, etc ... 726 """ 727 728 def __init__(self, threshold: float = 0.80, *args: Any, **kwargs: Any) -> None: 729 """ 730 Args: 731 threshold: document whose noun ratio is higher than this value will be discarded 732 *args: 733 **kwargs: 734 """ 735 super().__init__(*args, **kwargs) 736 assert ( 737 is_loaded_extras 738 ), "fugashi is required for this filter. Try pip install 'hojichar[all]'" 739 740 self.threshold = threshold 741 self.tagger = Tagger("-Owakati") 742 assert ( 743 "unidic" in self.tagger.dictionary_info[0]["filename"] 744 ), "MeCab dictionary must be unidic" 745 746 def apply(self, doc: Document) -> Document: 747 """ 748 >>> DiscardTooManyNouns().apply(Document("自然言語処理大好き!")).is_rejected 749 False 750 >>> DiscardTooManyNouns().apply(Document("リンゴ・オレンジ・ミカン・バナナ セール中")).is_rejected 751 True 752 >>> DiscardTooManyNouns().apply(Document("今日の仙台朝市ではリンゴがセール中")).is_rejected 753 False 754 """ 755 # remove "補助記号" from part-of-speech statistics 756 # because they often decrease the noun ratio, 757 # e.g., the sentence "リンゴ・オレンジ・バナナ・" has 補助記号 ratio of 0.5 758 # however, we don't want such sentence 759 pos_count = Counter( 760 w.feature.pos1 for w in self.tagger(doc.text) if w.feature.pos1 != "補助記号" 761 ) 762 try: 763 noun_ratio = pos_count["名詞"] / sum(pos_count.values()) 764 except ZeroDivisionError: 765 noun_ratio = 0.0 766 if noun_ratio >= self.threshold: 767 doc.is_rejected = True 768 return doc
[!CAUTION] This filter requires fugashi
package. Please install it
by pip install 'hojichar[all]'
.
A filter that removes document with too many nouns in Japanese i.e., documents such as advertisement, word salad, etc ...
728 def __init__(self, threshold: float = 0.80, *args: Any, **kwargs: Any) -> None: 729 """ 730 Args: 731 threshold: document whose noun ratio is higher than this value will be discarded 732 *args: 733 **kwargs: 734 """ 735 super().__init__(*args, **kwargs) 736 assert ( 737 is_loaded_extras 738 ), "fugashi is required for this filter. Try pip install 'hojichar[all]'" 739 740 self.threshold = threshold 741 self.tagger = Tagger("-Owakati") 742 assert ( 743 "unidic" in self.tagger.dictionary_info[0]["filename"] 744 ), "MeCab dictionary must be unidic"
Args: threshold: document whose noun ratio is higher than this value will be discarded args: *kwargs:
746 def apply(self, doc: Document) -> Document: 747 """ 748 >>> DiscardTooManyNouns().apply(Document("自然言語処理大好き!")).is_rejected 749 False 750 >>> DiscardTooManyNouns().apply(Document("リンゴ・オレンジ・ミカン・バナナ セール中")).is_rejected 751 True 752 >>> DiscardTooManyNouns().apply(Document("今日の仙台朝市ではリンゴがセール中")).is_rejected 753 False 754 """ 755 # remove "補助記号" from part-of-speech statistics 756 # because they often decrease the noun ratio, 757 # e.g., the sentence "リンゴ・オレンジ・バナナ・" has 補助記号 ratio of 0.5 758 # however, we don't want such sentence 759 pos_count = Counter( 760 w.feature.pos1 for w in self.tagger(doc.text) if w.feature.pos1 != "補助記号" 761 ) 762 try: 763 noun_ratio = pos_count["名詞"] / sum(pos_count.values()) 764 except ZeroDivisionError: 765 noun_ratio = 0.0 766 if noun_ratio >= self.threshold: 767 doc.is_rejected = True 768 return doc
>>> DiscardTooManyNouns().apply(Document("自然言語処理大好き!")).is_rejected
False
>>> DiscardTooManyNouns().apply(Document("リンゴ・オレンジ・ミカン・バナナ セール中")).is_rejected
True
>>> DiscardTooManyNouns().apply(Document("今日の仙台朝市ではリンゴがセール中")).is_rejected
False
Inherited Members
771class CharRepetitionRatioFilter(Filter): 772 """ 773 文字Ngramの重なり率(文書中で高頻度文字Ngramが占める割合)を計算して, 重なりの大きいものを除去します. 774 名詞の連続からなるような広告テキストを取り除くのに有効です. 775 776 実装は, BigScience で採用されていた前処理を参考にしています. 777 元実装: https://github.com/bigscience-workshop/data-preparation/blob/9d0588419073cc5bf0fb92b58f37f2a1016572c3/preprocessing/training/01b_oscar_cleaning_and_filtering/filtering.py#L425-L453 # noqa: E501 778 779 「高頻度文字Ngram」は、sqrt(ユニークなNgramの総数)によって求めていますが, 780 これは文書長の影響を軽減するためだとされています. 781 782 掲示板のテキストが引っかかりやすい傾向があります. 783 13: 名無しさん@実況で競馬板アウト 2019/08/18(日) 15:28:46.10 ID:eBvZg8h+0 784 的なものが高頻度で登場するため、文字Ngramの重なり率も高くなってしまう 785 """ 786 787 def __init__( 788 self, threshold: float = 0.33, ngram_size: int = 5, *args: Any, **kwargs: Any 789 ) -> None: 790 """ 791 792 Args: 793 threshold: document with character repetition ratio higher than this value will be discarded 794 ngram_size: character ngram size. Larger value will decrease the false positive of long documents 795 *args: 796 **kwargs: 797 """ # noqa: E501 798 799 super().__init__(*args, **kwargs) 800 self.threshold = threshold 801 self.ngram_size = ngram_size 802 803 def apply(self, doc: Document) -> Document: 804 ratio = self.compute_character_repetition_ratio(doc.text, self.ngram_size) 805 if ratio >= self.threshold: 806 doc.is_rejected = True 807 return doc 808 809 @staticmethod 810 def compute_character_repetition_ratio( 811 document: str, character_repetition_length: int 812 ) -> float: 813 def get_freq_character_ngrams(document: str, n: int) -> Dict[str, int]: 814 character_ngrams: List[str] = [ 815 document[i : i + n] for i in range(len(document) - n + 1) 816 ] 817 freq_character_ngrams_dict: Dict[str, int] = {} 818 for character_ngram in character_ngrams: 819 freq_character_ngrams_dict[character_ngram] = ( 820 freq_character_ngrams_dict.get(character_ngram, 0) + 1 821 ) 822 return freq_character_ngrams_dict 823 824 freq_character_ngrams_dict = get_freq_character_ngrams( 825 document, character_repetition_length 826 ) 827 if len(freq_character_ngrams_dict) == 0: 828 return 0.0 829 freq_character_ngrams: List[int] = list(freq_character_ngrams_dict.values()) 830 freq_character_ngrams = sorted(freq_character_ngrams, reverse=True) 831 val_one = len([el for el in freq_character_ngrams if el == 1]) 832 num_rep_character_ngrams = min( 833 int(np.sqrt(len(freq_character_ngrams))), 834 len(freq_character_ngrams) - val_one, 835 ) 836 character_repetition_ratio = sum(freq_character_ngrams[:num_rep_character_ngrams]) / sum( 837 freq_character_ngrams 838 ) 839 return character_repetition_ratio
文字Ngramの重なり率(文書中で高頻度文字Ngramが占める割合)を計算して, 重なりの大きいものを除去します. 名詞の連続からなるような広告テキストを取り除くのに有効です.
実装は, BigScience で採用されていた前処理を参考にしています. 元実装: https://github.com/bigscience-workshop/data-preparation/blob/9d0588419073cc5bf0fb92b58f37f2a1016572c3/preprocessing/training/01b_oscar_cleaning_and_filtering/filtering.py#L425-L453 # noqa: E501
「高頻度文字Ngram」は、sqrt(ユニークなNgramの総数)によって求めていますが, これは文書長の影響を軽減するためだとされています.
掲示板のテキストが引っかかりやすい傾向があります. 13: 名無しさん@実況で競馬板アウト 2019/08/18(日) 15:28:46.10 ID:eBvZg8h+0 的なものが高頻度で登場するため、文字Ngramの重なり率も高くなってしまう
787 def __init__( 788 self, threshold: float = 0.33, ngram_size: int = 5, *args: Any, **kwargs: Any 789 ) -> None: 790 """ 791 792 Args: 793 threshold: document with character repetition ratio higher than this value will be discarded 794 ngram_size: character ngram size. Larger value will decrease the false positive of long documents 795 *args: 796 **kwargs: 797 """ # noqa: E501 798 799 super().__init__(*args, **kwargs) 800 self.threshold = threshold 801 self.ngram_size = ngram_size
Args: threshold: document with character repetition ratio higher than this value will be discarded ngram_size: character ngram size. Larger value will decrease the false positive of long documents args: *kwargs:
803 def apply(self, doc: Document) -> Document: 804 ratio = self.compute_character_repetition_ratio(doc.text, self.ngram_size) 805 if ratio >= self.threshold: 806 doc.is_rejected = True 807 return doc
Definition of filter behavior.
In this method, the filter will modify document.text
, or
set document.is_rejected = True
to discard the document.
Do not define a filter that changes both document.text
and document.token
Parameters
document : Document Input document
Returns
Document Processed Document
809 @staticmethod 810 def compute_character_repetition_ratio( 811 document: str, character_repetition_length: int 812 ) -> float: 813 def get_freq_character_ngrams(document: str, n: int) -> Dict[str, int]: 814 character_ngrams: List[str] = [ 815 document[i : i + n] for i in range(len(document) - n + 1) 816 ] 817 freq_character_ngrams_dict: Dict[str, int] = {} 818 for character_ngram in character_ngrams: 819 freq_character_ngrams_dict[character_ngram] = ( 820 freq_character_ngrams_dict.get(character_ngram, 0) + 1 821 ) 822 return freq_character_ngrams_dict 823 824 freq_character_ngrams_dict = get_freq_character_ngrams( 825 document, character_repetition_length 826 ) 827 if len(freq_character_ngrams_dict) == 0: 828 return 0.0 829 freq_character_ngrams: List[int] = list(freq_character_ngrams_dict.values()) 830 freq_character_ngrams = sorted(freq_character_ngrams, reverse=True) 831 val_one = len([el for el in freq_character_ngrams if el == 1]) 832 num_rep_character_ngrams = min( 833 int(np.sqrt(len(freq_character_ngrams))), 834 len(freq_character_ngrams) - val_one, 835 ) 836 character_repetition_ratio = sum(freq_character_ngrams[:num_rep_character_ngrams]) / sum( 837 freq_character_ngrams 838 ) 839 return character_repetition_ratio
Inherited Members
842class WordRepetitionRatioFilter(Filter): 843 """ 844 [!CAUTION] This filter requires `fugashi` package. Please install it 845 by `pip install 'hojichar[all]'`. 846 847 単語Ngramの重なり率(文書中で重複する単語Ngramが占める割合)を計算して、重なりの大きいものを弾くためのフィルタ. 848 BigScienceで採用されていた前処理を参考にしている. 849 850 名詞が連打されているような広告テキストを取り除くのに有効な様子 851 まともな文書がたまたま2回繰り返されている場合もあり、これを取り除いて良いのかは分からない 852 例: 853 "ウェブ\n本文: ニコンの上昇率16%超える、今3月期は経常76%の大幅増益見込む(ニコン) 2013年05月10日[minkabu PRESS] - みんなの株式 (みんかぶ)\n2013/05/10(10:57) 854 ニコン<7731.T>が急騰、寄り付き直後に前日比355円高の2537円まで買い上げ 855 られ、上昇率は16%を超えた。外国為替市場で円が1ドル100円台、1ユーロ131円台に入るなど急速に円安が進み、輸出株が軒並み高になる 856 なか、9日取引終了後に発表した前年3月期決算で、今3月期は2ケタ近い増収で大幅増益を見込んだことが買い気を強めさせた。連結売上 857 高は前期比9.8%増の1兆1100億円、経常利益75.8%増の850億円を予想。前期は半導体、電子部品の低迷が足かせになり、2ケタ増収ながら 858 経常46%の大幅減益になったが、レンズ交換式デジタルカメラの拡大や液晶ディスプレイの回復で収益が急回復する。ニコンの株価は10時 859 56分現在2491円(△309円)出所:株経通信(株式会社みんかぶ)\n2013/05/10 - ニコン(7731) の関連ニュース。 ニコン<7731.T>が急騰、寄 860 り付き直後に前日比355円高の2537円まで買い上げられ、上昇率は16%を超えた。外国為替市場で円が1ドル100円台、1ユーロ131円台に入 861 るなど急速に円安が進み、輸出株が軒並み高になるなか、9日取引終了後に発表した前年3月期決算で、今3月期は2ケタ近い増収で大幅増 862 益を見込んだことが買い気を強めさせた。連結売上高は前期比9.8%増の1兆1100億円、経常利益75.8%増の850億円を予想。前期は半導体、 863 電子部品の低迷が足かせになり、2ケタ増収ながら経常46%の大幅減益になったが、レンズ交換式デジタルカメラの拡大や液晶ディスプレ 864 イの回復で収益が急回" 865 """ # noqa: E501 866 867 def __init__( 868 self, threshold: float = 0.40, ngram_size: int = 7, *args: Any, **kwargs: Any 869 ) -> None: 870 """ 871 872 Args: 873 threshold: document whose character repetition ratio is higher than this value will be discarded 874 ngram_size: character ngram size. Larger value will decrease the false positive of long documents 875 *args: 876 **kwargs: 877 """ # noqa: E501 878 super().__init__(*args, **kwargs) 879 assert ( 880 is_loaded_extras 881 ), "fugashi is required for this filter. Try pip install 'hojichar[all]'" 882 883 self.threshold = threshold 884 self.ngram_size = ngram_size 885 self.tagger = Tagger("-Owakati") 886 887 def apply(self, doc: Document) -> Document: 888 ratio = self.compute_word_repetition_ratio(doc.text, self.ngram_size) 889 if ratio >= self.threshold: 890 doc.is_rejected = True 891 return doc 892 893 def compute_word_repetition_ratio(self, document: str, word_repetition_length: int) -> float: 894 def get_freq_word_ngrams(document: str, n: int) -> Dict[str, int]: 895 # tokenizing given document 896 words = [w.surface for w in self.tagger(document)] 897 word_ngrams = [" ".join(words[i : i + n]) for i in range(len(words) - n + 1)] 898 freq_word_ngrams: Dict[str, int] = {} 899 for word_ngram in word_ngrams: 900 freq_word_ngrams[word_ngram] = freq_word_ngrams.get(word_ngram, 0) + 1 901 return freq_word_ngrams 902 903 freq_word_ngrams_dict = get_freq_word_ngrams(document, word_repetition_length) 904 if len(freq_word_ngrams_dict) == 0: 905 return 0 906 freq_word_ngrams = list(freq_word_ngrams_dict.values()) 907 word_repetition_ratio = sum(freq for freq in freq_word_ngrams if freq > 1) / sum( 908 freq_word_ngrams 909 ) 910 911 return word_repetition_ratio
[!CAUTION] This filter requires fugashi
package. Please install it
by pip install 'hojichar[all]'
.
単語Ngramの重なり率(文書中で重複する単語Ngramが占める割合)を計算して、重なりの大きいものを弾くためのフィルタ.
BigScienceで採用されていた前処理を参考にしている.
名詞が連打されているような広告テキストを取り除くのに有効な様子
まともな文書がたまたま2回繰り返されている場合もあり、これを取り除いて良いのかは分からない
例:
"ウェブ
本文: ニコンの上昇率16%超える、今3月期は経常76%の大幅増益見込む(ニコン) 2013年05月10日[minkabu PRESS] - みんなの株式 (みんかぶ) 2013/05/10(10:57) ニコン<7731.T>が急騰、寄り付き直後に前日比355円高の2537円まで買い上げ られ、上昇率は16%を超えた。外国為替市場で円が1ドル100円台、1ユーロ131円台に入るなど急速に円安が進み、輸出株が軒並み高になる なか、9日取引終了後に発表した前年3月期決算で、今3月期は2ケタ近い増収で大幅増益を見込んだことが買い気を強めさせた。連結売上 高は前期比9.8%増の1兆1100億円、経常利益75.8%増の850億円を予想。前期は半導体、電子部品の低迷が足かせになり、2ケタ増収ながら 経常46%の大幅減益になったが、レンズ交換式デジタルカメラの拡大や液晶ディスプレイの回復で収益が急回復する。ニコンの株価は10時 56分現在2491円(△309円)出所:株経通信(株式会社みんかぶ) 2013/05/10 - ニコン(7731) の関連ニュース。 ニコン<7731.T>が急騰、寄 り付き直後に前日比355円高の2537円まで買い上げられ、上昇率は16%を超えた。外国為替市場で円が1ドル100円台、1ユーロ131円台に入 るなど急速に円安が進み、輸出株が軒並み高になるなか、9日取引終了後に発表した前年3月期決算で、今3月期は2ケタ近い増収で大幅増 益を見込んだことが買い気を強めさせた。連結売上高は前期比9.8%増の1兆1100億円、経常利益75.8%増の850億円を予想。前期は半導体、 電子部品の低迷が足かせになり、2ケタ増収ながら経常46%の大幅減益になったが、レンズ交換式デジタルカメラの拡大や液晶ディスプレ イの回復で収益が急回"
867 def __init__( 868 self, threshold: float = 0.40, ngram_size: int = 7, *args: Any, **kwargs: Any 869 ) -> None: 870 """ 871 872 Args: 873 threshold: document whose character repetition ratio is higher than this value will be discarded 874 ngram_size: character ngram size. Larger value will decrease the false positive of long documents 875 *args: 876 **kwargs: 877 """ # noqa: E501 878 super().__init__(*args, **kwargs) 879 assert ( 880 is_loaded_extras 881 ), "fugashi is required for this filter. Try pip install 'hojichar[all]'" 882 883 self.threshold = threshold 884 self.ngram_size = ngram_size 885 self.tagger = Tagger("-Owakati")
Args: threshold: document whose character repetition ratio is higher than this value will be discarded ngram_size: character ngram size. Larger value will decrease the false positive of long documents args: *kwargs:
887 def apply(self, doc: Document) -> Document: 888 ratio = self.compute_word_repetition_ratio(doc.text, self.ngram_size) 889 if ratio >= self.threshold: 890 doc.is_rejected = True 891 return doc
Definition of filter behavior.
In this method, the filter will modify document.text
, or
set document.is_rejected = True
to discard the document.
Do not define a filter that changes both document.text
and document.token
Parameters
document : Document Input document
Returns
Document Processed Document
893 def compute_word_repetition_ratio(self, document: str, word_repetition_length: int) -> float: 894 def get_freq_word_ngrams(document: str, n: int) -> Dict[str, int]: 895 # tokenizing given document 896 words = [w.surface for w in self.tagger(document)] 897 word_ngrams = [" ".join(words[i : i + n]) for i in range(len(words) - n + 1)] 898 freq_word_ngrams: Dict[str, int] = {} 899 for word_ngram in word_ngrams: 900 freq_word_ngrams[word_ngram] = freq_word_ngrams.get(word_ngram, 0) + 1 901 return freq_word_ngrams 902 903 freq_word_ngrams_dict = get_freq_word_ngrams(document, word_repetition_length) 904 if len(freq_word_ngrams_dict) == 0: 905 return 0 906 freq_word_ngrams = list(freq_word_ngrams_dict.values()) 907 word_repetition_ratio = sum(freq for freq in freq_word_ngrams if freq > 1) / sum( 908 freq_word_ngrams 909 ) 910 911 return word_repetition_ratio
Inherited Members
914class DiscardTooManySpecialToken(Filter): 915 """ 916 [!CAUTION] This filter requires `emoji` package. Please install it 917 by `pip install 'hojichar[all]'`. 918 919 句読点を含む記号、空白、絵文字、その他特殊な文字を一定の割合以上含むような文書を取り除くためのフィルタ 920 元実装: BigScience https://github.com/bigscience-workshop/data-preparation/blob/9d0588419073cc5bf0fb92b58f37f2a1016572c3/preprocessing/training/01b_oscar_cleaning_and_filtering/parameters_filtering.py#L5-L16 # noqa: E501 921 """ 922 923 def __init__(self, threshold: float = 0.4, *args: Any, **kwargs: Any) -> None: 924 """ 925 926 Args: 927 threshold: document whose special token ratio is higher than this value will be discarded 928 *args: 929 **kwargs: 930 """ # noqa: E501 931 super().__init__(*args, **kwargs) 932 933 # digits are not regarded as special tokens 934 # otherwise many false positives are made, i.e., good documents discarded 935 main_special_characters = string.punctuation + string.whitespace # + string.digits 936 other_special_characters = ( 937 " ’“”–▬…✦�£•€«»°·═" 938 "×士^˘⇓()§″′´¿−±∈¢ø‚„½¼¾¹²³―⁃,ˌ¸‹›ʺˈʻ¦‐⠀‰ ‑≤≥‖" 939 "◆●■►▼▲▴∆▻¡★☆✱ːº。¯˜¥ɪ≈†:⁄♡✓⊕․.⋅÷1‟;،、¨ाাी्े◦˚" 940 "゜ʼ≖ʼ¤℃√!?【】‿∞➤~πه۩☛₨➩☻๑٪♥ıॽ《‘©﴿٬?▷Г♫∟™ª₪®「—❖" 941 "」﴾》�" 942 ) 943 944 en_emoji = emoji.EMOJI_DATA.keys() 945 946 special_characters_default = set(main_special_characters + other_special_characters) 947 special_characters_default.update(en_emoji) 948 self.special_characters = special_characters_default 949 950 self.threshold = threshold 951 952 def _compute_special_characters_ratio(self, text: str) -> float: 953 if len(text) == 0: 954 return 0 955 956 special_characters_ratio = len( 957 [char for char in text if char in self.special_characters] 958 ) / len(text) 959 return special_characters_ratio 960 961 def apply(self, doc: Document) -> Document: 962 special_characters_ratio = self._compute_special_characters_ratio(doc.text) 963 964 if special_characters_ratio > self.threshold: 965 doc.is_rejected = True 966 return doc
[!CAUTION] This filter requires emoji
package. Please install it
by pip install 'hojichar[all]'
.
句読点を含む記号、空白、絵文字、その他特殊な文字を一定の割合以上含むような文書を取り除くためのフィルタ 元実装: BigScience https://github.com/bigscience-workshop/data-preparation/blob/9d0588419073cc5bf0fb92b58f37f2a1016572c3/preprocessing/training/01b_oscar_cleaning_and_filtering/parameters_filtering.py#L5-L16 # noqa: E501
923 def __init__(self, threshold: float = 0.4, *args: Any, **kwargs: Any) -> None: 924 """ 925 926 Args: 927 threshold: document whose special token ratio is higher than this value will be discarded 928 *args: 929 **kwargs: 930 """ # noqa: E501 931 super().__init__(*args, **kwargs) 932 933 # digits are not regarded as special tokens 934 # otherwise many false positives are made, i.e., good documents discarded 935 main_special_characters = string.punctuation + string.whitespace # + string.digits 936 other_special_characters = ( 937 " ’“”–▬…✦�£•€«»°·═" 938 "×士^˘⇓()§″′´¿−±∈¢ø‚„½¼¾¹²³―⁃,ˌ¸‹›ʺˈʻ¦‐⠀‰ ‑≤≥‖" 939 "◆●■►▼▲▴∆▻¡★☆✱ːº。¯˜¥ɪ≈†:⁄♡✓⊕․.⋅÷1‟;،、¨ाাी्े◦˚" 940 "゜ʼ≖ʼ¤℃√!?【】‿∞➤~πه۩☛₨➩☻๑٪♥ıॽ《‘©﴿٬?▷Г♫∟™ª₪®「—❖" 941 "」﴾》�" 942 ) 943 944 en_emoji = emoji.EMOJI_DATA.keys() 945 946 special_characters_default = set(main_special_characters + other_special_characters) 947 special_characters_default.update(en_emoji) 948 self.special_characters = special_characters_default 949 950 self.threshold = threshold
Args: threshold: document whose special token ratio is higher than this value will be discarded args: *kwargs:
961 def apply(self, doc: Document) -> Document: 962 special_characters_ratio = self._compute_special_characters_ratio(doc.text) 963 964 if special_characters_ratio > self.threshold: 965 doc.is_rejected = True 966 return doc
Definition of filter behavior.
In this method, the filter will modify document.text
, or
set document.is_rejected = True
to discard the document.
Do not define a filter that changes both document.text
and document.token
Parameters
document : Document Input document
Returns
Document Processed Document
Inherited Members
969class SingleCharacterRepetitionFilter(Filter): 970 """ 971 単一文字が大量に繰り返されているような文書を取り除くためのフィルタ 972 そのような文書はノイズである可能性が高いため 973 参考: BigScienceプロジェクトによると、oscarデータセットの中にバックスラッシュだけを2M個含むような文書が含まれていたらしい 974 https://github.com/bigscience-workshop/bigscience/blob/master/train/tr8-104B-wide/chronicles.md#2m-backslash-only-samples-in-our-dataset # noqa: E501 975 """ 976 977 def __init__( 978 self, 979 threshold: int = 200, 980 *args: Any, 981 **kwargs: Any, 982 ) -> None: 983 """ 984 Args: 985 threshold: The document is removed if character is repeated for this value or more 986 *args: 987 **kwargs: 988 """ 989 super().__init__(*args, **kwargs) 990 self.threshold = threshold 991 992 def _is_repeat_contained(self, text: str) -> bool: 993 groups = groupby(text) 994 is_repeat_contained = any(sum(1 for _ in group) >= self.threshold for _, group in groups) 995 return is_repeat_contained 996 997 def apply(self, doc: Document) -> Document: 998 if self._is_repeat_contained(doc.text): 999 doc.is_rejected = True 1000 return doc
単一文字が大量に繰り返されているような文書を取り除くためのフィルタ そのような文書はノイズである可能性が高いため 参考: BigScienceプロジェクトによると、oscarデータセットの中にバックスラッシュだけを2M個含むような文書が含まれていたらしい https://github.com/bigscience-workshop/bigscience/blob/master/train/tr8-104B-wide/chronicles.md#2m-backslash-only-samples-in-our-dataset # noqa: E501
977 def __init__( 978 self, 979 threshold: int = 200, 980 *args: Any, 981 **kwargs: Any, 982 ) -> None: 983 """ 984 Args: 985 threshold: The document is removed if character is repeated for this value or more 986 *args: 987 **kwargs: 988 """ 989 super().__init__(*args, **kwargs) 990 self.threshold = threshold
Args: threshold: The document is removed if character is repeated for this value or more args: *kwargs:
997 def apply(self, doc: Document) -> Document: 998 if self._is_repeat_contained(doc.text): 999 doc.is_rejected = True 1000 return doc
Definition of filter behavior.
In this method, the filter will modify document.text
, or
set document.is_rejected = True
to discard the document.
Do not define a filter that changes both document.text
and document.token
Parameters
document : Document Input document
Returns
Document Processed Document
Inherited Members
1003class DiscardTooManyEndingEllipsis(Filter): 1004 """ 1005 ellipsisで終わるような行が大量に含まれるような文書を取り除くためのフィルタです. 1006 ellipsisとしては ... と … を用いている 1007 同様のフィルタが RedPajama v2で用いられています. 1008 1009 例として, 以下のような文書を検知します. 1010 ``` 1011 ペアーズは女性、という驚愕の過食が出ているのをごアラサーですか。時代から付... 1012 バツイチアラフォー 婚活ち女性の特徴と子持な付... 1013 ``` 1014 1015 デフォルトではしきい値を0.7としているが, これはC4から0.1%を削るような設定であり、 1016 precisionを重視した設定です. 1017 """ 1018 1019 def __init__( 1020 self, 1021 threshold: float = 0.7, 1022 *args: Any, 1023 **kwargs: Any, 1024 ) -> None: 1025 """ 1026 Args: 1027 threshold: The document is removed if ratio of lines ending with ellipsis is higher than this value 1028 *args: 1029 **kwargs: 1030 """ # noqa: E501 1031 super().__init__(*args, **kwargs) 1032 self.threshold = threshold 1033 self.ellipsis_pattern = re.compile(r"(\.{3}|…)\n") # matches ...\n and …\n 1034 1035 def apply(self, doc: Document) -> Document: 1036 ellipsis_count = len(self.ellipsis_pattern.findall(doc.text)) 1037 newline_count = max(doc.text.count("\n"), 1) # avoid zero division 1038 ellipsis_ratio = ellipsis_count / newline_count 1039 1040 if ellipsis_ratio > self.threshold: 1041 doc.is_rejected = True 1042 return doc
ellipsisで終わるような行が大量に含まれるような文書を取り除くためのフィルタです. ellipsisとしては ... と … を用いている 同様のフィルタが RedPajama v2で用いられています.
例として, 以下のような文書を検知します.
ペアーズは女性、という驚愕の過食が出ているのをごアラサーですか。時代から付...
バツイチアラフォー 婚活ち女性の特徴と子持な付...
デフォルトではしきい値を0.7としているが, これはC4から0.1%を削るような設定であり、 precisionを重視した設定です.
1019 def __init__( 1020 self, 1021 threshold: float = 0.7, 1022 *args: Any, 1023 **kwargs: Any, 1024 ) -> None: 1025 """ 1026 Args: 1027 threshold: The document is removed if ratio of lines ending with ellipsis is higher than this value 1028 *args: 1029 **kwargs: 1030 """ # noqa: E501 1031 super().__init__(*args, **kwargs) 1032 self.threshold = threshold 1033 self.ellipsis_pattern = re.compile(r"(\.{3}|…)\n") # matches ...\n and …\n
Args: threshold: The document is removed if ratio of lines ending with ellipsis is higher than this value args: *kwargs:
1035 def apply(self, doc: Document) -> Document: 1036 ellipsis_count = len(self.ellipsis_pattern.findall(doc.text)) 1037 newline_count = max(doc.text.count("\n"), 1) # avoid zero division 1038 ellipsis_ratio = ellipsis_count / newline_count 1039 1040 if ellipsis_ratio > self.threshold: 1041 doc.is_rejected = True 1042 return doc
Definition of filter behavior.
In this method, the filter will modify document.text
, or
set document.is_rejected = True
to discard the document.
Do not define a filter that changes both document.text
and document.token
Parameters
document : Document Input document
Returns
Document Processed Document
Inherited Members
1045class DiscardTooShortLines(Filter): 1046 """ 1047 短い行を大量に含む文書を捨てるためのフィルタです. 1048 1049 メニューバーやパンくずリストのような要素を大量に含む文書を取り除くのに有効です. 1050 """ 1051 1052 def __init__(self, threshold: float = 0.5, *args: Any, **kwargs: Any) -> None: 1053 """ 1054 Args: 1055 threshold: The document is removed if the ratio of short (<10 chars) lines are more than this value. 1056 *args: 1057 **kwargs: 1058 """ # noqa: E501 1059 super().__init__(*args, **kwargs) 1060 self.threshold = threshold 1061 # この値は適当に決め打ち 1062 self.minimum_line_length = 10 1063 1064 def apply(self, doc: Document) -> Document: 1065 lines = [len(x) for x in doc.text.split("\n")] 1066 short_lines = [x for x in lines if x <= self.minimum_line_length] 1067 if (len(short_lines) / len(lines)) > self.threshold: 1068 doc.is_rejected = True 1069 return doc
短い行を大量に含む文書を捨てるためのフィルタです.
メニューバーやパンくずリストのような要素を大量に含む文書を取り除くのに有効です.
1052 def __init__(self, threshold: float = 0.5, *args: Any, **kwargs: Any) -> None: 1053 """ 1054 Args: 1055 threshold: The document is removed if the ratio of short (<10 chars) lines are more than this value. 1056 *args: 1057 **kwargs: 1058 """ # noqa: E501 1059 super().__init__(*args, **kwargs) 1060 self.threshold = threshold 1061 # この値は適当に決め打ち 1062 self.minimum_line_length = 10
Args: threshold: The document is removed if the ratio of short (<10 chars) lines are more than this value. args: *kwargs:
1064 def apply(self, doc: Document) -> Document: 1065 lines = [len(x) for x in doc.text.split("\n")] 1066 short_lines = [x for x in lines if x <= self.minimum_line_length] 1067 if (len(short_lines) / len(lines)) > self.threshold: 1068 doc.is_rejected = True 1069 return doc
Definition of filter behavior.
In this method, the filter will modify document.text
, or
set document.is_rejected = True
to discard the document.
Do not define a filter that changes both document.text
and document.token
Parameters
document : Document Input document
Returns
Document Processed Document