Class: VivlioStarter::CLI::IndexCommands::IndexCandidateExtractor
- Inherits:
-
Object
- Object
- VivlioStarter::CLI::IndexCommands::IndexCandidateExtractor
- Defined in:
- lib/vivlio_starter/cli/index/index_candidate_extractor.rb
Overview
索引候補語自動抽出クラス
Constant Summary collapse
- TERM_CHAR =
定義文から語を切り出すときに、語の一部として許す文字。
素の
.で 20 文字を取ると、文の途中から機械的に切り出すことになり 「た章のみです。 は本の目次(章立て)」のような文の断片**が候補になる。 実測(本書 27 章)では候補 4,053 件のうち 1,359 件がこの類で、 スコア分布と順位を歪め、candidate_pool を上げても本物が出てこない原因だった。/.-は許す——PDF/X-1aTerminal.app10-20行目のような 正当な語を巻き込まないため。 %r{[^\s。、!?…「」『』()()\[\]{}#*|`>~::;;,,\\]}- DEFINITION_PATTERNS =
定義パターン(「〜とは」「〜について」など)
[ /(#{TERM_CHAR}{2,20})とは[、,]?[^。]*(?:である|です|を意味|を指|という)/, /(#{TERM_CHAR}{2,20})(?:について|に関して)(?:は|の)/, /(#{TERM_CHAR}{2,20})(?:を|が)(?:定義|説明|解説)/, /「(#{TERM_CHAR}{2,20})」(?:とは|は|について)/, /(#{TERM_CHAR}{2,20})(?:の概念|の定義|の意味)/ ].freeze
- JUNK_TERM_PATTERN =
語として成立しない文字列。定義文の切り出しや名詞連続に混ざる残骸を落とす。 記法の断片(
###MATTR**Linux|画像)、句読点をまたいだ文、 記号で始まる・終わる語が対象。 /[。、!?\r\n\t#*|`>~\[\]()()「」『』【】]|:{3}|\A[[:space:]\-.::]|[[:space:]\-.::]\z/- SINGLE_NOUN_MIN_LENGTH =
MeCab が 1 語と認識する複合語(「相対性理論」など)を拾う下限。
名詞連続の経路は 2 語以上を対象にするため、MeCab の辞書に 1 語として 載っている専門用語が丸ごと漏れていた(「特殊相対性理論」は 「特殊」+「相対性理論」の 2 語なので拾えるのに、「相対性理論」単体は漏れる)。 短い単独名詞まで拾うと「本」「方法」「場合」で埋まるため長さで絞る。
5- TECHNICAL_TERM_PATTERNS =
専門用語パターン(カタカナ語、英字語など)
[ /[ァ-ヶー]{3,}/, # カタカナ3文字以上 /[A-Z][a-zA-Z]{2,}/, # 英語の単語 /[A-Z]{2,}/ # 略語(HTML, CSS など) ].freeze
Instance Attribute Summary collapse
-
#documents ⇒ Object
readonly
Returns the value of attribute documents.
-
#scoring ⇒ Object
readonly
Returns the value of attribute scoring.
-
#term_contexts ⇒ Object
readonly
Returns the value of attribute term_contexts.
Instance Method Summary collapse
-
#all_candidates ⇒ Object
全ての候補語を取得.
-
#extract_from_chapters!(chapters) ⇒ Object
全章を解析して索引候補を抽出.
-
#initialize ⇒ IndexCandidateExtractor
constructor
A new instance of IndexCandidateExtractor.
-
#score_terms(terms) ⇒ Hash{String => Float}
辞書に登録済みの用語へ、候補と同じ式でスコアを与える。.
-
#term_scores ⇒ Object
用語 → スコア。算出そのものは ScoringEngine が持つ(重みの二重管理を作らない)。.
Constructor Details
#initialize ⇒ IndexCandidateExtractor
Returns a new instance of IndexCandidateExtractor.
75 76 77 78 79 80 81 |
# File 'lib/vivlio_starter/cli/index/index_candidate_extractor.rb', line 75 def initialize @documents = {} @scoring = ScoringEngine.new @term_contexts = Hash.new { |h, k| h[k] = [] } @yomi_inferrer = YomiInferrer.new @context_width = load_context_width end |
Instance Attribute Details
#documents ⇒ Object (readonly)
Returns the value of attribute documents.
67 68 69 |
# File 'lib/vivlio_starter/cli/index/index_candidate_extractor.rb', line 67 def documents @documents end |
#scoring ⇒ Object (readonly)
Returns the value of attribute scoring.
67 68 69 |
# File 'lib/vivlio_starter/cli/index/index_candidate_extractor.rb', line 67 def scoring @scoring end |
#term_contexts ⇒ Object (readonly)
Returns the value of attribute term_contexts.
67 68 69 |
# File 'lib/vivlio_starter/cli/index/index_candidate_extractor.rb', line 67 def term_contexts @term_contexts end |
Instance Method Details
#all_candidates ⇒ Object
全ての候補語を取得
70 |
# File 'lib/vivlio_starter/cli/index/index_candidate_extractor.rb', line 70 def all_candidates = @scoring.terms |
#extract_from_chapters!(chapters) ⇒ Object
全章を解析して索引候補を抽出
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/vivlio_starter/cli/index/index_candidate_extractor.rb', line 85 def extract_from_chapters!(chapters) Common.log_action('索引候補の自動抽出を開始します...') # ドキュメントを読み込み (contents/ 配下のみ) chapters.each do |chapter| md_file = File.join(Common::CONTENTS_DIR, "#{chapter}.md") unless File.exist?(md_file) Common.log_warn("索引候補抽出: contents/ に #{chapter}.md が見つからないためスキップします") next end content = File.read(md_file, encoding: 'utf-8') @documents[chapter] = content end # 各種抽出を実行 extract_definition_patterns! extract_technical_terms! extract_noun_sequences! if @yomi_inferrer.available? # TF-IDF スコアリング calculate_tfidf_scores! Common.log_success("#{@scoring.terms.size} 件の候補語を抽出しました") end |
#score_terms(terms) ⇒ Hash{String => Float}
辞書に登録済みの用語へ、候補と同じ式でスコアを与える。
帯の判定(推奨候補・見直し候補)は登録語と未登録候補を同じ土俵で 並べて決めるので、候補として抽出されなかった語——手動マークアップや ライブラリ取込——にも順位が要る。
候補側と揃わない点が 1 つある: 定義パターンと名詞連続の性質は 本文走査で付くものなので、ここでは判定しない(語の形から分かる :technical だけ付ける)。そのぶん控えめなスコアになるため、 手動マークアップ由来の語は見直し候補へ出さない(呼び出し側の責務)。
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/vivlio_starter/cli/index/index_candidate_extractor.rb', line 125 def score_terms(terms) return {} if @documents.empty? engine = ScoringEngine.new doc_count = @documents.size contents = @documents.values terms.each do |entry| name = entry['term'].to_s next if name.empty? tf = 0 df = 0 pattern = term_regexp(entry) contents.each do |content| n = content.scan(pattern).size next if n.zero? tf += n df += 1 end # 1 回も出てこない語は記録しない。observe は tf を見て黙るが mark は # 語の綴りだけで通るため、性質ボーナスだけのスコアが残っていた。 # すると出現ゼロの語が「スコア: 15.0」と表示され、レビューで # 「[原稿に出現しません]」に振り分けられない(死語が生きて見える)。 next if tf.zero? engine.mark(name, :technical) if TECHNICAL_TERM_PATTERNS.any? { name.match?(it) } engine.observe(name, tf:, df:, doc_count:) end engine.scores end |
#term_scores ⇒ Object
用語 → スコア。算出そのものは ScoringEngine が持つ(重みの二重管理を作らない)。
73 |
# File 'lib/vivlio_starter/cli/index/index_candidate_extractor.rb', line 73 def term_scores = @term_scores ||= @scoring.scores |