それでは毛玉諸君、これにて失敬

日々の精進を備忘録的に綴ります。

【読書感想文】とてつもない数学

読もうとしたきっかけ

最近競プロや機械学習に手を出し、結果分野は違えど全ての理論は数学によって成り立っていると痛感しています。
これまでまともに勉強などしたことありませんでしたが、憧れのIT職に就けたこともあり
つよつよを目指して統計、線形代数、最適化、集合の初級本などを日々取り組んでいます。

しかし如何せん低スペ脳すぎて中々理解が進まず、悶々としながら参考書を眺め、大切な20代の時間を溶かし続けています。。。

あぁ、学問を究めるにおいて人生はなんて儚いのだろう。。。
周りの人は楽しそうに人生を謳歌しているのに、なぜ自分はムツカシイ記号だらけの本をムウムウ唸りながら取り組んでいるのでしょうか。。。

「アアッ!イケナイイケナイ。プログラミングや競プロ、美しい証明に出会った時のワクワク感を取り戻さないと!」

そんなことを考えながら本屋さんをウロウロしていた時に出会ったのがこの本です。

www.amazon.co.jp

感想

面白い!!!
身近な数字の話から始まり、過去の偉大な数学者たちの経歴や数学の芸術性、現代の生活に欠かすことのできない数学の基礎(統計など)等々…多岐に渡る数学分野の話が満載です。

というかまず、著者の経歴が凄いんですよね
「東大→JAXA→音大→指揮者→数学塾」って唯一無二すぎやしませんか?
僕はこの本の中で特に「数学の美しさ」の部分に心惹かれたのですが、音楽を専攻した著者だからこその内容なんだな~と思いました。

個人的に「解が一意に求まる」部分が数学の一番好きな要素です。
思えば、僕は学生時代に化学を専攻し、高機能なポリマーの合成を目指して日々実験を重ねましたが、これは近似解を探す作業に似ています。
「もっと優れた構造があり気がするけど、今はこれがベストかな~」
理論計算などすれば求まりそうですが、もはや数学の分野ですね。
一意に解を求められることに対し、すごく憧れを持っていました。
それが転職の原動力にもなった気がしますが趣旨から逸れてしまうのでここまでにしときます。

数学と音楽の親和性については作中にも記載がありますが、ピタゴラスが鍛冶屋のハンマー音から音律のヒントを得たエピソードや
偉大な作曲家であるチャイコフスキーが残した以下の言葉などがあります。

「もしも数学が美しくなかったら、おそらく数学そのものが生まれてこなかったであろう。
人類の最大の天才たちをこの難解な学問に引き付けるのに、美の他にどんな力があり得ようか」

音声解析には欠かせないフーリエ変換もバリバリ数学ですし、探せばいくらでもありそうです。
早く数式を「美しい」と思える感性を磨いていきたいです。

懸垂曲線、クロソイド、平面充填とペンローズ・タイル、、、新しい知識もたくさん増えました。

数式をほぼ使わずに数学の美味しい部分を凝縮したとても面白い本でした!

メタル楽曲の歌詞をワードクラウドで可視化

「凶星のデストラップ」にハマってるko_ya346です。

bodoge.hoobby.net

大体いつも最初にエイリアンに食べられる雑魚っぷりを発揮しています。

先日connpassのイベントのMusic×Analytics Meetup Vol.2に参加したのですが、

Bump Of Chickenの歌詞分析のLTがすごい面白かったので自分も真似してみました。何番煎じか分かりませんが…

手順は

1.スクレイピングで歌詞を集める

2.ワードクラウドのライブラリに突っ込む

です。

収集する歌詞

自分はヘヴィメタルが好きなのですが、メタルには非常に多くのジャンルが存在し(少なくとも60以上)、それぞれで思想が異なるようです。

ja.wikipedia.org

そこで今回は各ジャンルを代表するバンドを勝手に選び、ワードクラウドで歌詞の頻出単語を可視化してみました。

代表バンド

デスメタル代表

Cannibal Corpse(代表曲 Hammer Smashed Face

一言:ずっと人間食べてる。安心して聴ける高クオリティのデスメタルの重鎮。

ブラックメタル代表

Behemoth(代表曲 demigod

一言:教会燃やしがち。ボーカルは悪魔が憑りついてるみたいな怖い人だけど普段は優しい。ベースがゴツイ。

メロスピ代表

Darkmoor(代表曲 Quest for the Eternal Fame

一言:この人たちのせいでメタルにハマった。2ndと3rdは人類史上最も美しい名盤。ボーカルが変わって変な感じになった。

Rhapsody(代表曲 Emerald Sword

一言:剣と魔法とドラゴン。ルカ(ギター)のギターソロは天才。最近ボーカルと仲直りして嬉しい。

ブルデス代表

Dying Fetus(代表曲 Your Treachery Will Die With You

一言:攻撃力が高い。演奏技術がエグイ。ブルデスなのにメロディが特徴的で聴きやすいという矛盾したバンド。個性が強すぎて似ているバンドが思い当たらない。好き。

パワーメタル代表

Manowar(代表曲 Hail and Kill

一言:筋肉がすごい。ジャケットが少しダサい。

スラッシュメタル代表

Slayer(代表曲 Angel of Death

一言:メタラーが全員好きなバンド(ホントに)。初期のメロ重視の作風も好き。

スクレイピング

各バンドのイメージを何となく掴んで頂いたと思うので早速歌詞を収集しましょう。

こちらのサイトを使用させて頂きました。 www.darklyrics.com

コードはこちら(すごく…汚いです…)

import time
import os
import re
import requests
from bs4 import BeautifulSoup

#アーティスト
artist = "slayer"
input_dir = "./input/"
#ディレクトリが存在しなければ新規作成する
if not os.path.exists(input_dir+artist):
    os.mkdir(input_dir+artist)

url = "http://www.darklyrics.com/" + artist[0] + "/"+artist+".html"
#歌詞が掲載されているページ
base_url = "http://www.darklyrics.com/lyrics/"+artist+"/"

#httpライブラリでWebページを取得
res = requests.get(url)
#ステータスコードが200番台以外ならメッセージを吐いてスクリプト停止
res.raise_for_status

#取得したhtmlから情報を抽出
soup = BeautifulSoup(res.content, "html.parser")
#aタグを取得
sound_list = soup.find_all("strong")

#アルバム名を収集
album_title = []
for i in sound_list:
    #URLに入れれるように変換(改善の余地あり)
    album_title.append(i.get_text().lower().replace(" ", "").replace("...", "").replace("-", "").replace("'", "").replace(":", "").replace(")", "").replace(".", "")[1:-1])

#アルバム毎に歌詞をまとめる
for i in range(len(album_title)):
    print(f"{i+1} album_title {album_title[i]}")

    with open(f"{input_dir+artist}/{album_title[i]}.txt", "w") as f:
        #アルバムのページに移動
        base_url1 = base_url+album_title[i]+".html"
        res1 = requests.get(base_url1)
        res1.raise_for_status
        soup1 = BeautifulSoup(res1.content, "html.parser")

        #アルバム収録曲のリスト
        song_list = []
        for num in range(1, 31):
            song_title = soup1.find("a", {"href":f"#{num}"})
            if song_title:
                song_list.append(song_title.text)
            else:
                break

        #歌詞を抽出
        text = soup1.get_text()
        #歌詞リスト
        lines = [line.strip() for line in text.splitlines()]
        #このサイトでは歌詞の直後に下の文が来るので終点の目安にする
        end_word = "Submits, comments, corrections are welcomed at webmaster@darklyrics.com"

        end_point = lines.index(end_word)

        #歌詞
        lyrics = ""

        #曲の始まりと終わりのインデックスを取得
        index_list = [j for j, x in enumerate(lines) for sl in song_list if x == sl ][len(song_list):] + [end_point]

        for k in range(len(index_list)-1):
            #不要部を削除
            ly = " ".join(lines[index_list[k]+1:index_list[k+1]]).replace("  ", " ").replace("...", " ").lower()
            #記号を削除
            ly = re.sub(r'[<>♪`‘’“”・…_!?!-/:-@[-`{-~]', '', ly)
            lyrics += ly
        #unicodedecodeerror回避のためcp932に変換できない部分を無視する
        lyrics = lyrics.encode("cp932", "ignore").decode("cp932")
        f.write(lyrics)
        #スクレイピング時のマナー
        time.sleep(1)

こちらのサイトではアルバム毎に歌詞が管理されているので、一度全てテキストを取得し、曲名を頼りに歌詞部分を抽出しています(index_listが目安)。

正直歌詞の抽出はすごく手こずりました。もうちょいうまいやり方はないのか?

アルバムタイトルに記号が含まている場合は取り除く必要があります(アドレスに含まれないため)。

本当はフォーク、ペイガンのジャンルも試したかったのですが、大量のエラーが予想されるので怖くて諦めました(キリル文字が多用されてたりする)。

word_cloudを作る

qiita.com

こちらを参考にしました。

txtファイルを放り込むだけなので楽ちんです。

import matplotlib.pyplot as plt
from wordcloud import WordCloud

def create_wordcloud(text, ar, dir):
    #特徴的な単語を見るために、あえて汎用的な単語は省いています
    stop_words = [ u'am', u'is', u'of', u'and', u'the', u'to', u'it', u'for', u'in', u'as', u'or', u'are', u'be', u'this', u'that', u'will', u'there', u'was', u'by', u'from', u'with', u'on', u"an", u"into", u"their", u"you", u"your", u"my", u"his", u"me", u"we", u"im", u"our", u"so", u"they", u"he"]

    wordcloud = WordCloud(background_color="white", width=900, height=500, \
                          stopwords=set(stop_words)).generate(text)

    fig = plt.figure(figsize=(15,12))
    plt.imshow(wordcloud)
    plt.axis("off")
    plt.show()
    fig.savefig(dir + ar + ".png")

#スクレイピングしたアーティストのリスト
artist = ["behemoth", "darkmoor", "manowar", "dyingfetus", "rhapsody", "cannibalcorpse", "slayer"]

for ar in artist:
    print("now", ar)
    input_dir = f"./input/{ar}/"
    output_dir = "./word_cloud/"

    txt_list = [i for i in os.listdir(input_dir) if ".txt" in i ]
    lyrics = ""
    for i in txt_list:
        with open(input_dir + i) as f:
            tmp = f.read()
        lyrics += tmp
    create_wordcloud(lyrics, ar, output_dir)

結果

Cannibal Corpse

f:id:ko_ya346:20200628172019p:plain

blood、dead、killなどらしい単語が目立ちます。

flesh、bone、eye、body、など体に関する単語は他のバンドには見られませんでした。やっぱ人肉ばっかり食べてるからですかね?(バンド名を和訳すると「食人死体」)

Behemoth

f:id:ko_ya346:20200628172014p:plain

godが目立つところからも彼らの音楽性が伺えます。blood、fireなどは儀式で使用するのでしょうか?

DarkMoor

f:id:ko_ya346:20200628172035p:plain

chorusはおそらく歌詞ではなく曲中のコーラス部分を示す言葉のようです。やっつけ仕事なので仕方ないですね…

これまでのバンドと違いlove、dreamなど前向きな単語が多いかと思いきや右上にしっかりdeathの文字。

Rhapsody

f:id:ko_ya346:20200628172104p:plain

nowとallの二大巨頭。「皆の者、今こそ立ち上がるのだ!」的なことでしょうか?

holy、king、ancient、gloryなどは中世の聖騎士が連想されます。

Dying Fetus

f:id:ko_ya346:20200628172039p:plain

no、f〇cking、deadなど強い単語が目立ちます。

歌詞を読むと政治批判などが書かれているので、強烈な単語で否定し続けているのでしょうか。

Manowar

f:id:ko_ya346:20200628172044p:plain

Rhapsodyと同様にall、nowが目立ちます。

右下のheavy metalで彼らのメタル愛が感じられます。

Slayer

f:id:ko_ya346:20200628172106p:plain

god、death、blood、painと世間が抱くメタルのイメージが全部乗っかったような画像になりました。

感想

想像通りというか、全体的にdeathやkillなど死に関するワードが多めなのが面白かったです。曲自体が強烈なので歌詞も強い言葉を載せないと釣り合いが取れないのかな~と感じます。

また、正直これまでメタル楽曲の歌詞に注目したことはほとんどありませんでしたが、各ジャンルの特徴が垣間見えたのが良かったです。

今回は各アーティストの楽曲を全てひっくるめて集計しましたが、アルバム毎などにすると特色が見れてさらに面白いかもしれません。

感情分析を取り入れると歌詞から彼らの心情も得られて楽しいかもしれませんね。

また機会があれば挑戦したいと思います。

3種類の書き方でナップサック問題を解く(Python)

最近「ひぐらしのなく頃に」にハマってます。にぱ~~

無料で視聴させてくれるABEMAには足を向けて寝れません。

ko_ya346です。

今回は動的計画法(DP)の書き方をまとめてみました。

というのも一言にDPと言えど様々な解法(表現?)があるみたいで、

参考サイト毎に書き方が変わっており理解が捗りませんでした。

Atcoderや蟻本を探した結果、とりあえず3種類ほど見つかったのでご紹介します。

問題はこちら。

atcoder.jp

二次元配列

N, W = map(int, input().split())
obj = [list(map(int, input().split())) for _ in range(N)]

dp = [[0 for _ in range(10**5+5)] for _ in range(N+1)]

for i in range(N):
    for j in range(W+1):
        if j < obj[i][0]:
            dp[i+1][j] = dp[i][j]
        else:
            dp[i+1][j] = max(dp[i][j], dp[i][j-obj[i][0]]+obj[i][1])
print(dp[N][W])

多くの参考サイトで紹介されている書き方です。

アイテム1つに対して配列を一つ持ち、各重みに対して価値を最大化します。

一次元配列

N, W = map(int, input().split())
obj = [list(map(int, input().split())) for _ in range(N)]

dp = [0 for _ in range(10**5+5)]

for i in range(N):
    for j in range(W, obj[i][0]-1, -1):
        dp[j] = max(dp[j], dp[j-obj[i][0]]+obj[i][1])

print(dp[W])

直感的に分かりやすい書き方になっています。

2つめのfor文では先ほどと異なりWから1ずつ減らしていますが、こっちの方が若干行数が減らせます。

Numpy

import numpy as np

N, W = map(int, input().split())
dp = np.zeros((N, W+1), dtype='int64')

for i in range(N):
    w, v = map(int , input().split())

    #買わない選択肢を引き継ぐ
    dp[i, :w] = dp[i-1, :w]
    dp[i, w:] = np.fmax(dp[i-1, w:], dp[i-1,:W-w+1]+v)
    print(dp)

print(dp[N-1][W])

二次元配列の表現に近いですが、各重みの価値を一括で更新できるためスッキリと書くことができます。

終わりに

まだ完全に理解しているとは言い難いですが、類題をたくさん解いて理解したいと思います。