【言語処理100本ノック解いてみた】 第3章:正規表現 No.20~No.24

プログラミング

はじめに

今回は第3章の問題の前半部分を解いていきたいと思います.
本章では主に正規表現を扱っています.
自然言語処理のデータ収集においては綺麗なデータが集まることはあまりないため,このように正規表現を用いてほしい情報を的にれられるようにしておく必要があります.
また,本章はGoogle Colaboraroryでの実行を想定したコードになっていますので,コマンドを試しながら進めたい方は環境を揃えるようによろしくお願いします.
では,早速解いていきたいと思います.

20. JSONデータの読み込み

利用データの説明
Wikipediaの記事を以下のフォーマットで書き出したファイルjawiki-country.json.gzがある.
  • 1行に1記事の情報がJSON形式で格納される
  • 各行には記事名が”title”キーに,記事本文が”text”キーの辞書オブジェクトに格納され,そのオブジェクトがJSON形式で書き出される
  • ファイル全体はgzipで圧縮される
    問題文:Wikipedia記事のJSONファイルを読み込み,「イギリス」に関する記事本文を表示せよ.問題21-29では,ここで抽出した記事本文に対して実行せよ.
    コマンド
    #Wikiデータのダウンロードとunzipによる解凍
    !wget https://nlp100.github.io/data/jawiki-country.json.gz
    !gunzip jawiki-country.json.gz
    import json
    filepath = "/content/jawiki-country.json"
    # ファイルを開く
    with open(filepath) as f:
        for line in f:
            json_data = json.loads(line)
            if json_data["title"] == "イギリス":
                uk_text = json_data["text"]
    print(uk_text)
    #出力
    {{redirect|UK}}
    {{redirect|英国|春秋時代の諸侯国|英 (春秋)}}
    {{Otheruses|ヨーロッパの国|長崎県・熊本県の郷土料理|いぎりす}}
    {{基礎情報 国
    |略名  =イギリス
       ・
       ・
       ・
    [[Category:現存する君主国]]
    [[Category:島国]]
    [[Category:1801年に成立した国家・領域]]
    
    解説

    json形式でデータを読み込んでいます.
    jsonの内部はまずtitleとtextに分かれています.このtitleの中身がイギリスとなっている時のtextを取得すればイギリスの記事の本文を取得したことになります.
    よって,json_data[“title”]==”イギリス”でイギリスの内容の記事化を判断し,uk_text = json_data[“text”]で本文を取得しています.
    この後の問題ではこのuk_textを使って問題を解いていきます.

    21. カテゴリ名を含む行を抽出

    問題文: 記事中でカテゴリ名を宣言している行を抽出せよ.
    コマンド
    import re
    category_text = re.findall(r'(\[\[Category:.+\]\])',uk_text)
    category_text
    #出力
    ['[[Category:イギリス|*]]',
     '[[Category:イギリス連邦加盟国]]',
     '[[Category:英連邦王国|*]]',
     '[[Category:G8加盟国]]',
     '[[Category:欧州連合加盟国|元]]',
     '[[Category:海洋国家]]',
     '[[Category:現存する君主国]]',
     '[[Category:島国]]',
     '[[Category:1801年に成立した国家・領域]]']
    
    解説

    カテゴリ名を宣言している行の特徴として[[Category:〇〇]]という形になっているのがわかります.
    この文字列を抜き出したいので,re.findallを使って文字列の抽出をします.re.findall(抽出方法,対象文章)の形式で与えることでマッチした文字列を全て抽出することができます.抽出方法部分に関してそれぞれの意味合いを軽く説明していきます.

    \ : 特殊文字を認識させるために使います.”?”や”.”などのような文字列は正規表現を操作するために使われているので,それらを単純な文字列として扱いたい時に使います.
    . :  任意の文字列を指します.今回の場合”Category:”以降の部分は行毎に異なるのでそこの部分をちゃんと抽出できるようにするために使用しています.
    + :  直前の文字列の一回以上の繰り返しを指します.今回の場合,”.+”となっているので任意の文字列の繰り返しを指しています.

    22. カテゴリ名の抽出

    問題文:記事のカテゴリ名を(行単位ではなく名前で)抽出せよ.
    コマンド
    import re
    category = re.findall(r'\[\[Category:(.+)\]\]', uk_text)
    print(category)
    for i in range(len(category)):
        category[i] = re.sub(r'\|\*', "", category[i])
    print(category)
    #出力
    ['イギリス|*', 'イギリス連邦加盟国', '英連邦王国|*', 'G8加盟国', '欧州連合加盟国|元', '海洋国家', '現存する君主国', '島国', '1801年に成立した国家・領域']
    ['イギリス', 'イギリス連邦加盟国', '英連邦王国', 'G8加盟国', '欧州連合加盟国|元', '海洋国家', '現存する君主国', '島国', '1801年に成立した国家・領域']
    
    解説

    先ほどの抽出方法では余計なかっこや”Category:”という文字列が含まれてしまっていました.
    まずは,それらの余計な部分を取り除くために,findallでマッチする部分を変更していきます.
    “(.+)”とすることでグループ化し,[[Category:〇〇]]の〇〇部分を抽出してくれます.
    ただ,これだけではまだ一部”|*”のようなゴミが含まれてしまっているので,これらをre.sub(“除去対象”,”除去後の処理”,”対象文書”)によって取り除きます.

    23. セクション構造

    問題文:記事中に含まれるセクション名とそのレベル(例えば”== セクション名 ==”なら1)を表示せよ.
    コマンド
    section_list = re.findall(r'^=+.+=+$', uk_text, re.MULTILINE)
    print(section_list[:5])
    result = [(re.sub(r'=+', "", section), len(re.findall(r'=', section))//2) for section in section_list]
    print(result[:5])
    #出力
    ['==国名==', '==歴史==', '==地理==', '===主要都市===', '===気候===']
    [('国名', 2), ('歴史', 2), ('地理', 2), ('主要都市', 3), ('気候', 3)]
    
    解説

    =○○=となっている部分を抜き出してくる問題です.
    まず,1行目で”=”に挟まれている文字列を丸ごと持ってきます.
    また,re.findall内の正規表現はそれぞれ下記の意味を持ちます.

    ^ : 文頭の文字列.今回の問題ではそれぞれのセクションの行の先頭は”=”で固定です.また,文中の”=”を誤って拾ってこないようにするためにつけています.
    $ : 文末の文字列.上記と同様の理由でつけています.

    この際に,re.MULTILINEを使うことで1行ずつに対して文字列の先頭と終了判断をすることができます.
    2行目までで抽出してきた文字列にはまだ”=”が残っており,まだ “=”の数を数えていません.
    3行目でre.sub()による”=”の除去と半ば無理やりre.findall()で”=”の数を数えるということをしています.

    24. ファイル参照の抽出

    問題文:記事から参照されているメディアファイルをすべて抜き出せ.
    コマンド
    media_row = re.findall(r'\[\[ファイル:.+\]\]', uk_text)
    print(media_row[0])
    print("--"*20)
    media_list = re.findall(r'\[\[ファイル:(.+?\..+?)\|',uk_text)
    for media in media_list:
        print(media)
    #出力
    [[ファイル:Royal Coat of Arms of the United Kingdom.svg|85px|イギリスの国章]]
    ----------------------------------------
    Royal Coat of Arms of the United Kingdom.svg
    Descriptio Prime Tabulae Europae.jpg
    Lenepveu, Jeanne d'Arc au siège d'Orléans.jpg
            ・
            ・
            ・
    Wembley Stadium, illuminated.jpg
    
    解説

    メディアファイルを抜き出す問題ですが,まずは,対象の文章の中のどこにメディアファイルがあるか探しましょう.
    出力の1行目のように今回の場合[[ファイル:〇〇]]という形式でメディアファイルが存在しているようです.
    この文字列から拡張子部分までを正規表現で抽出します.新しい正規表現の説明は下記の通りです.

    +? : 文字列の最小マッチを指します.

    今回の場合,”ファイル:”から”|”までを単純に抽出しようとすると”|”が複数あり,”+?”を使わないと最後に出てきた”|”までの文字列をマッチさせてしまいます.
    この”+?”の使い方を知っていれば,あとは()で文字列をグループ化し,拡張子までと拡張子部分を正確に抜き出す正規表現の操作になります.

    最後に

    今回はNLP100本ノックの第3章を解いてみました.
    解説が長くなってしまうので,前後編に分けて解説していきます.
    ぜひ後編の方もご参考ください.

    次の記事

    前の記事

    コメント

    タイトルとURLをコピーしました