More than 1 year has passed since last update.

この記事は 「さだまさし x IT」 というテーマのITアドベントカレンダー7日目の投稿です。

さだまさしが歌った450曲を形態素解析して5調と7調の歌詞を収集します。それを組み合わせれば さだまさし川柳 を詠むプログラムが完成します。

さだまさし川柳の自動生成結果

  • いなりずし 器用でもない 愛がいい
  • あ、どうも 届かない恋 通り過ぎ
  • 下手くそだ 僕が初めて して貰い
  • 死んでゆく お代わり出来る 膝小僧
  • 卒業 若いのに髪 今はなく
  • 幸せと 素敵な彼が 離さない
  • 君らしく すてきな仲間 淋しい目
  • うずくまる きたねえ字だな 書いた人
  • 好きだった 生命なくし 春のこと
  • 悲しみが いつでも君を プログラム
  • 花となり おめでとうって 実が実る
  • じじとばば なるものらしい 生きている
  • 夢がある 手に入らない 窓の外
  • 脱いだあと 息子が僕の 憧れて
  • 帰れない お袋はまだ 港を見
  • 散る恋も 出来る小さな おせっかい
  • 私には 何もなくただ そばにいる
  • 好きだった 甘い手紙を 日曜日
  • 信じたん あなたが逝った 馬券買え
  • 愛すると 白い煙を 超えて耐え
  • 僕のせい 君の瞳に 生まれてる
  • アノ娘ノ 別れの理由 銃弾
  • 軽く言う いつでも君を 友達に
  • 生きてゆく ひとり暮らしの 旅に出る
  • 坂道を 精一杯に 風が舞う
  • 鍵をかけ 京成上野 並ぶ君
  • 勝ったぞと 若き旅人 置き手紙
  • あがりです 旅の途中 しあわせで

生成アルゴリズム

スクリーンショット 2015-12-04 17.51.48.png

450曲歌詞URL生成ツール

lyric.py
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
from bs4 import BeautifulSoup
import requests

# uta-netのさだまさし先生の曲一欄URL
urls = [
    'http://www.uta-net.com/artist/1399/0/1/',
    'http://www.uta-net.com/artist/1399/0/2/',
    'http://www.uta-net.com/artist/1399/0/3/',
]


class LyricSheet(object):
    """
    歌詞
    """
    def __init__(self, title, song_id):
        self.title = title
        self.song_id = song_id

    def __repr__(self):
        return str(self.song_id) + ':' + self.title

    @property
    def url(self):
        _base = 'http://www.uta-net.com/user/phplib/svg/showkasi.php?ID={}'
        return _base.format(str(self.song_id))

    def write_file(self):
        with open("./data/main.txt", 'a') as text:
            response = requests.get(self.url)
            assert response.status_code == 200
            text.write(response.text)


def generate_lyrics(url):
    response = requests.get(url)
    assert response.status_code == 200
    soup = BeautifulSoup(response.text)
    songs = []

    for td in soup.tbody.find_all("td"):
        if td.a:
            _url = td.a['href']
            if 'song' in _url:
                _song_id = _url.replace('/', '').replace('song', '')
                songs.append(LyricSheet(td.a.text, _song_id))
    return songs

# 曲一覧ページのパース
lyrics = []
for url in urls:
    lyrics += generate_lyrics(url)

# URLの出力
for lyric in lyrics:
    print lyric.url

さだまさし川柳10万句生成ツール

# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
from bs4 import BeautifulSoup
from janome.tokenizer import Tokenizer
import random


def cut_word(count):
    """
    ファイルからカタカナ読みしたときの文字数:count だけ切り取って返却

    例)
    input - 5
    output - ["アイウエオ", "カキクケコ", "サシスセソ"]
    """
    _tmp = []
    result = []
    path = './data/main.txt'
    f = open(path, "r")

    for body in f:
        soup = BeautifulSoup(body)
        for t in soup.find_all('text'):
            if t is None:
                continue

            for token in tokenizer.tokenize(t.text):
                # 空白ならリセット
                if '空白' in token.part_of_speech:
                    _tmp = []

                # _tmpの先頭は名詞、形容詞、動詞のみ
                if len(_tmp) == 0:
                    if '助動詞' in token.part_of_speech:
                        continue

                    if '接尾' in token.part_of_speech:
                        continue

                    if '非自立' in token.part_of_speech:
                        continue

                    if '数' in token.part_of_speech:
                        continue

                    if '名詞' in token.part_of_speech or '形容詞' in token.part_of_speech or '動詞' in token.part_of_speech:
                        pass
                    else:
                        continue

                _tmp.append(token)
                # カタカナ読みした時の読み
                reading = ''.join([_token.reading for _token in _tmp])
                if len(reading) == count:
                    s = ''.join([_token.surface for _token in _tmp])
                    if '¥' not in s:
                        # debug
                        # result.append(s + '    :' + ''.join([_.surface + _.part_of_speech for _ in _tmp]))
                        result.append(s)
                    _tmp = []
                elif len(reading) > count:
                    # リセット
                    _tmp = []
        f.close()
        return result

tokenizer = Tokenizer()

word_seven = cut_word(7)
word_five = cut_word(5)

for x in xrange(100000):
    print random.choice(word_five), random.choice(word_seven), random.choice(word_five)

print len(word_five), len(word_seven)

未解決問題: 良い川柳順でソートできない

10万句の川柳を生成した結果、たとえば 言えばいい 味噌汁になれ 幸福だ草笛は 君の瞳の カレンダー のような馬鹿げた川柳が大量に生成されてしまいました。人が見ればすぐ判別できるのですが具体的な良い川柳の定義を明文化せよとなると、なかなか言語化できず難しい問題です。

英文学部卒の友人に聞いてみた

詳しい人に相談してみました。英文学部出身の友人に句をみせたところ、係り受けが成立していない句が多いと指摘されました。今後の課題として係り受け解析を行い成立している/いないで点数を付けることで、より良い句を抽出するフィルターが開発出来そうです。

良いさだまさし川柳とは

俳句と川柳を適切に区別できる人は希少です。詠んだ俳句を 「よい川柳ですね」 と褒められた笑い話は枚挙に暇がありません。今回は良いさだまさし川柳の抽出ロジックを、歴史が長く研究が進んでいる現代俳句を参考に考えてみました。

フランス文学者の桑原武夫先生は岩波書店の雑誌『世界』1946年11月号に掲載した 「第二芸術 ―現代俳句について―」 論文にて俳句を「第二芸術」として他の芸術と区別するべきと論じ、当時の俳壇に大きな論争を引き起こしました(第二芸術論争)。俳壇側の人間の視点だと、おフランスかぶれの学者が俳句は他の芸術より芸術性が低いとディスったけど、当時の有名俳人は上手く反論出来ず、学者に弁では敵わないと匙を投げてしまったお話です。

桑原は 「芸術とはなんぞや」 という問いに 「人間の心に感動を与えるもの。そして、芸術は深く考えることに意味がある」 と答えています。また 「作者の経験が鑑賞者のうちに再生産」されなければ芸術の意味がない とも述べています。つまり感動の共有です。良いさだまさし川柳には作者と鑑賞者の間で感動の共有が必要不可欠であると考えました。読後ありありと情景が思い浮かべられる句を抽出しました。一方単なる言葉の羅列となっている句は排除しています。

単なる言葉の羅列になっていたため排除した句

  • 生きてゆく 痛み切なさ 虫歯がキラリン
  • ほらそこに 尋ね訪ねて かすみ草
  • あと一歩 夢を諦め 山ざくら
  • 晴れたらオン・ザ・ロック 際だっていて なるららら
  • ため息で 町のざわめき 流れ星
  • 僕の顔 男はやはり みな悪い

さいごに

さだまさし先生の曲は、なぜか心の琴線に触れると言われています。40年間の長きに渡り国民に愛される理由の中に、表現が優れているため歌を聞いた鑑賞者の心のうちに情景を思い浮かべさせ、感動を共有する手法に優れている点があるのではないでしょうか。

さいごに10万句のさだまさし川柳を添付します。良い川柳を抽出するロジックのモデル化とプログラムは後続の研究者に任せたいと思います。
- 10万句のさだまさし川柳(100k_haiku.txt.zip)

気に入った句をつぶやいて頂けると今後の参考と助けになります。
本日はご清聴ありがとうございました。