東北大学の言語処理100本ノックを解く(1章~3章)

スポンサーリンク
自然言語処理
スポンサーリンク

東北大学知能情報科学講座の自然言語処理学分野の乾・鈴木研究室の新人教育に使われている言語処理100本ノックを解いてみます。現東京工業大学の岡崎教授が作った自然言語の問題集で、2023年2月現在、一般に公開されており、解くだけで自然言語処理の基本がわかってしまうというとても優れものです。いろいろ調べながらやってみましたが、なかなか難易度が高くて骨が折れます。こういうのを研究室で受け継いでいくのってうらやましいです。4章以降はさらに難易度があがるみたいですが、そちらは追々やります。

下の回答は模範解答でもなんでもなく、単なる私の学習記録用に残したものなので、正解かどうかは保証しません。

第1章: 準備運動

プログラミングの基礎的な問題ですが、自然言語処理の前処理で使いそうな小技満載です。

00. 文字列の逆順

s = "stressed"
s[::-1]
'desserts'

01. 「パタトクカシーー」

s="パタトクカシーー"
l = [1,3,5,7]

"".join([s[num-1] for num in l])
'パトカー'

02. 「パトカー」+「タクシー」=「パタトクカシーー」

s1,s2 = "パトカー","タクシー"
"".join([s1[i]+s2[i] for i in range(len(s1))])
'パタトクカシーー'

03. 円周率

s = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
words_list = s.replace(",","").replace(".","").split()
[len(word) for word in word_list]
[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9]

04. 元素記号

s = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."
words_list = s.replace(".","").split()
words_dict = {}
singles = [1,5,6,7,8,9,15,16,19]
for num in range(len(words_list)):
    if num+1 in singles:
        words_dict[num+1] = words_list[num][0]
    else:
        words_dict[num+1] = words_list[num][0:2]
words_dict
{1: 'H',
 2: 'He',
 3: 'Li',
 4: 'Be',
 5: 'B',
 6: 'C',
 7: 'N',
 8: 'O',
 9: 'F',
 10: 'Ne',
 11: 'Na',
 12: 'Mi',
 13: 'Al',
 14: 'Si',
 15: 'P',
 16: 'S',
 17: 'Cl',
 18: 'Ar',
 19: 'K',
 20: 'Ca'}

05. n-gram

def words_bi_gram(s):
    words_list = s.split()
    result = []
    for i in range(len(words_list)-1):
        result.append(" ".join([words_list[i],words_list[i+1]]))
    return result

def char_bi_gram(s):
    words = s.replace(" ","")
    result = []
    for i in range(len(words)-1):
        result.append(words[i:i+2])
    return result

sequence = "I am an NLPer"
print("words: ", words_bi_gram(sequence))
print("chars: ", char_bi_gram(sequence))
words:  ['I am', 'am an', 'an NLPer']
char:  ['Ia', 'am', 'ma', 'an', 'nN', 'NL', 'LP', 'Pe', 'er']

06. 集合

def char_bi_gram(s):
    words = s.replace(" ","")
    result = []
    for i in range(len(words)-1):
        result.append(words[i:i+2])
    return result

word1 = "paraparaparadise"
word2 = "paragraph"
X = set(char_bi_gram(word1))
Y = set(char_bi_gram(word2))
print("X: ", X)
print("Y: ", Y)
print("和集合: ", X|Y)
print("積集合: ", X&Y)
print("差集合(X-Y): ", X-Y)
print("差集合(Y-X): ", Y-X)
print("'se' in X: ", 'se' in X)
print("'se' in Y: ", 'se' in Y)
X:  {'ar', 'ad', 'pa', 'ap', 'ra', 'se', 'is', 'di'}
Y:  {'ag', 'ar', 'gr', 'ph', 'pa', 'ap', 'ra'}
和集合:  {'ag', 'ar', 'gr', 'ph', 'ad', 'pa', 'ap', 'ra', 'se', 'is', 'di'}
積集合:  {'ra', 'ar', 'ap', 'pa'}
差集合(X-Y):  {'ad', 'se', 'di', 'is'}
差集合(Y-X):  {'ag', 'gr', 'ph'}
'se' in X:  True
'se' in Y:  False

07. テンプレートによる文生成

x, y, z = 12, "気温", 22.4
print("{}時の{}は{}".format(int(x),y,float(z)))
12時の気温は22.4

08. 暗号文

# 暗号化と復号化は同じ関数
def encryption(s):
    s = list(s)
    for i in range(len(s)):
        if 97 <= ord(s[i]) <= 122:
            s[i] = chr(219-ord(s[i]))
    return "".join(s)
    
s= "I have a pen."
encryption(s)
'I szev z kvm.'
s2 = "I szev z kvm."
encryption(s2)
'I have a pen.'

09. Typoglycemia

import random

def typoglycemia(sequence):
    words_list = sequence.split()
    if len(words_list) <= 4:
        result = words_list
    else:
        head = words_list[0]
        end = words_list[-1]
        middle = words_list[1:-1]
        rand_list = [num for num in range(len(middle))]
        random.shuffle(rand_list)
        result = [head] + [middle[num] for num in rand_list] + [end]
    
    return " ".join(result)

s = "I couldn’t believe that I could actually understand what I was reading : the phenomenal power of the human mind ."
typoglycemia(s)
'I was mind actually could that of phenomenal I what : couldn’t believe human I the power the understand reading .'

第2章: UNIXコマンド

この章は問題に使うコマンドがヒントとして書いてあるので、調べながらそのコマンドの使い方を学びました。実行Ubuntudで行っています。

10. 行数のカウント

$ wc -l popular-names.txt
2780 popular-names.txt

11. タブをスペースに置換

$ cat popular-names.txt | tr "\t" " " > popular-names-new.txt

12. 1列目をcol1.txtに,2列目をcol2.txtに保存

$ cut -f 1 -d " " popular-names-new.txt >col1.txt
$ cut -f 2 -d " " popular-names-new.txt >col2.txt

13. col1.txtとcol2.txtをマージ

$ cat col1.txt > col_merge.txt col2.txt

14. 先頭からN行を出力

$ head -n 5 popular-names.txt
Mary    F       7065    1880
Anna    F       2604    1880
Emma    F       2003    1880
Elizabeth       F       1939    1880
Minnie  F       1746    1880

15. 末尾のN行を出力

$ tail -n 3 popular-names.txt
Lucas   M       12585   2018
Mason   M       12435   2018
Logan   M       12352   2018

16. ファイルをN分割する

$ split -n 5 -d popular-names.txt sep

17. 1列目の文字列の異なり

$ cut -f 1 popular-names.txt | sort | uniq

18. 各行を3コラム目の数値の降順にソート

$ sort -k 3nr -t " " popular-names-new.txt

19. 各行の1コラム目の文字列の出現頻度を求め,出現頻度の高い順に並べる

$ cut -f 1 -d " " popular-names-new.txt | sort | uniq -c | sort -r

第3章: 正規表現

ダウンロードしたデータを見ながらゴリゴリと書きました。

20. JSONデータの読み込み

import json

# 1行ずつ読み込んで、json形式で取り出してリストに入れる
articles = []
with open('jawiki-country.json', 'r', encoding="utf-8") as f:
    for article in f:
        articles.append(json.loads(article))
len(articles)
248
# イギリスのtextの冒頭200文字を表示
for i in range(len(articles)):
    if articles[i]['title']=='イギリス':
        uk_text = articles[i]['text']
        print(uk_text[:200])
        break
{{redirect|UK}}
{{redirect|英国|春秋時代の諸侯国|英 (春秋)}}
{{Otheruses|ヨーロッパの国|長崎県・熊本県の郷土料理|いぎりす}}
{{基礎情報 国
|略名  =イギリス
|日本語国名 = グレートブリテン及び北アイルランド連合王国
|公式国名 = {{lang|en|United Kingdom of Great Britain and Norther

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

import re
search_text = '\[\[Category.+\]\]'
result = re.findall(search_text, uk_text)
print(result)
['[[Category:イギリス|*]]', '[[Category:イギリス連邦加盟国]]', '[[Category:英連邦王国|*]]', '[[Category:G8加盟国]]', '[[Category:欧州連合加盟国|元]]', '[[Category:海洋国家]]', '[[Category:現存する君主国]]', '[[Category:島国]]', '[[Category:1801年に成立した国家・領域]]']

22. カテゴリ名の抽出

for i in range(len(result)):
    print(re.search("(?<=Category:)(.*)(?=\]\])",result[i]).group())
イギリス|*
イギリス連邦加盟国
英連邦王国|*
G8加盟国
欧州連合加盟国|元
海洋国家
現存する君主国
島国
1801年に成立した国家・領域

23. セクション構造

for i in range(len(uk_text_list)):
    if re.search("(\=\=)(.*)(\=\=)",uk_text_list[i]):
        section = re.search("(\=\=)(.*)(\=\=)",uk_text_list[i]).group()
        level = section.count("=")//2-1
        print(section.replace("=",""), level, end= ", ")
print()
国名 1, 歴史 1, 地理 1, 主要都市 2, 気候 2, 政治 1, 元首 2, 法 2, 内政 2, 地方行政区分 2, 外交・軍事 2, 経済 1, 鉱業 2, 農業 2, 貿易 2, 不動産 2, エネルギー政策 2, 通貨 2, 企業 2, 通信 3, 交通 1, 道路 2, 鉄道 2, 海運 2, 航空 2, 科学技術 1, 国民 1, 言語 2, 宗教 2, 婚姻 2, 移住 2, 教育 2, 医療 2, 文化 1, 食文化 2, 文学 2, 哲学 2, 音楽 2, ポピュラー音楽 3, 映画 2, コメディ 2, 国花 2, 世界遺産 2, 祝祭日 2, スポーツ 2, サッカー 3, クリケット 3, 競馬 3, モータースポーツ 3, 野球 3,  カーリング  3,  自転車競技  3, 脚注 1, 関連項目 1, 外部リンク 1, 

24. ファイル参照の抽出

search_text = '(?<=ファイル:)(.*?)(?=\|)'
result = re.findall(search_text, uk_text)
print(result)
['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", 'London.bankofengland.arp.jpg', 'Battle of Waterloo 1815.PNG', 'Uk topo en.jpg', 'BenNevis2005.jpg', 'Population density UK 2011 census.png', '2019 Greenwich Peninsula & Canary Wharf.jpg', 'Birmingham Skyline from Edgbaston Cricket Ground crop.jpg', 'Leeds CBD at night.jpg', 'Glasgow and the Clyde from the air (geograph 4665720).jpg', 'Palace of Westminster, London - Feb 2007.jpg', 'Scotland Parliament Holyrood.jpg', 'Donald Trump and Theresa May (33998675310) (cropped).jpg', 'Soldiers Trooping the Colour, 16th June 2007.jpg', 'City of London skyline from London City Hall - Oct 2008.jpg', 'Oil platform in the North SeaPros.jpg', 'Eurostar at St Pancras Jan 2008.jpg', 'Heathrow Terminal 5C Iwelumo-1.jpg', 'Airbus A380-841 G-XLEB British Airways (10424102995).jpg', 'UKpop.svg', 'Anglospeak.svg', "Royal Aberdeen Children's Hospital.jpg", 'CHANDOS3.jpg', 'The Fabs.JPG', 'Wembley Stadium, illuminated.jpg']

25. テンプレートの抽出

results = re.search(r'(?<=基礎情報)(.*)(?=\n\}\}\n)', uk_text, re.DOTALL).group().split('\n|')[1:]
uk_dict = {}
for result in results:
    key = re.search('(.*?)(?=\=)', result).group().replace(" ","")
    value = re.search('(?<=\=)(.*)', result).group().lstrip()
    uk_dict[key] = value    
uk_dict
{'略名': 'イギリス',
 '日本語国名': 'グレートブリテン及び北アイルランド連合王国',
 '公式国名': '{{lang|en|United Kingdom of Great Britain and Northern Ireland}}<ref>英語以外での正式国名:<br />',
 '国旗画像': 'Flag of the United Kingdom.svg',
 '国章画像': '[[ファイル:Royal Coat of Arms of the United Kingdom.svg|85px|イギリスの国章]]',
 '国章リンク': '([[イギリスの国章|国章]])',
 '標語': '{{lang|fr|[[Dieu et mon droit]]}}<br />([[フランス語]]:[[Dieu et mon droit|神と我が権利]])',
 '国歌': "[[女王陛下万歳|{{lang|en|God Save the Queen}}]]{{en icon}}<br />''神よ女王を護り賜え''<br />{{center|[[ファイル:United States Navy Band - God Save the Queen.ogg]]}}",
 '地図画像': 'Europe-UK.svg',
 '位置画像': 'United Kingdom (+overseas territories) in the World (+Antarctica claims).svg',
 '公用語': '[[英語]]',
 '首都': '[[ロンドン]](事実上)',
 '最大都市': 'ロンドン',
以下略 }

26. 強調マークアップの除去

results = re.search(r'(?<=基礎情報)(.*)(?=\n\}\}\n)', uk_text, re.DOTALL).group().split('\n|')[1:]
uk_dict = {}
for result in results:
    key = re.search('(.*?)(?=\=)', result).group().replace(" ","")
    value = re.search('(?<=\=)(.*)', result).group().lstrip()
    value = re.sub("'''''","",value)
    value = re.sub("'''","",value)
    value = re.sub("''","",value)
    uk_dict[key] = value    
uk_dict

27. 内部リンクの除去

results = re.search(r'(?<=基礎情報)(.*)(?=\n\}\}\n)', uk_text, re.DOTALL).group().split('\n|')[1:]
uk_dict = {}
for result in results:
    key = re.search('(.*?)(?=\=)', result).group().replace(" ","")
    value = re.search('(?<=\=)(.*)', result).group().lstrip()
    value = re.sub("'''''","",value)
    value = re.sub("'''","",value)
    value = re.sub("''","",value)
    value = re.sub('(\{\{)(.*?)(http)(.*?)(\}\})','',value)
    value = re.sub('(\[http)(.*?)(\])','',value)
    uk_dict[key] = value    
uk_dict

28. MediaWikiマークアップの除去

results = re.search(r'(?<=基礎情報)(.*)(?=\n\}\}\n)', uk_text, re.DOTALL).group().split('\n|')[1:]
uk_dict = {}
for result in results:
    key = re.search('(.*?)(?=\=)', result).group().replace(" ","")
    value = re.search('(?<=\=)(.*)', result).group().lstrip()
    value = re.sub("'''''","",value)
    value = re.sub("'''","",value)
    value = re.sub("''","",value)
    value = re.sub('(\<ref)(.*)(\/\>)','',value)
    value = re.sub('(\<ref)(.*)(\/ref\>)','',value)
    value = re.sub('\<br\s+\/\>','',value)
    value = re.sub("\[\[","",value)
    value = re.sub("\]\]","",value)
    uk_dict[key] = value    
uk_dict
{'略名': 'イギリス',
 '日本語国名': 'グレートブリテン及び北アイルランド連合王国',
 '公式国名': '{{lang|en|United Kingdom of Great Britain and Northern Ireland}}',
 '国旗画像': 'Flag of the United Kingdom.svg',
 '国章画像': 'ファイル:Royal Coat of Arms of the United Kingdom.svg|85px|イギリスの国章',
 '国章リンク': '(イギリスの国章|国章)',
 '標語': '{{lang|fr|Dieu et mon droit}}(フランス語:Dieu et mon droit|神と我が権利)',
 '国歌': '女王陛下万歳|{{lang|en|God Save the Queen}}{{en icon}}神よ女王を護り賜え{{center|ファイル:United States Navy Band - God Save the Queen.ogg}}',
 '地図画像': 'Europe-UK.svg',
 '位置画像': 'United Kingdom (+overseas territories) in the World (+Antarctica claims).svg',
 '公用語': '英語',
 '首都': 'ロンドン(事実上)',
 '最大都市': 'ロンドン',
以下略 }

29. 国旗画像のURLを取得する

import requests
S = requests.Session()
URL = "https://ja.wikipedia.org/w/api.php"
PARAMS = {
    "action": "query",
    "format": "json",
    "prop": "imageinfo",
    "titles": "ファイル:"+uk_dict['国旗画像'], 
    "iiprop": "url"
}
R = S.get(url=URL, params=PARAMS)
DATA = R.json()
PAGES = DATA["query"]["pages"]
for k, v in PAGES.items():
    print(v["imageinfo"][0]["url"])
https://upload.wikimedia.org/wikipedia/commons/8/83/Flag_of_the_United_Kingdom_%283-5%29.svg
https://upload.wikimedia.org/wikipedia/commons/8/83/Flag_of_the_United_Kingdom_%283-5%29.svg

コメント