読者です 読者をやめる 読者になる 読者になる

鹿児島ハードチル同好会

情報学部の大学生です。深層学習(Tensorflow)とかUbuntuとか音楽とかガジェットに興味があります。バンドもしてたりする

【Twitter API】西野カナっぽい歌詞を自動生成するbotをつくった【形態素解析】

勉強 python

形態素解析西野カナbot

f:id:fuchami:20170216063410p:plain



大学で自然言語処理に関する講義を受講しました。
最終課題としてTwitter APIを用いて自動ツイートをするbotの作成を課されたので、自動で西野カナっぽい歌詞を生成するbotを作成。

データを収集する

とりあえず、西野カナの歌詞を大量に収集しなければいけません。
Twitter APIを用いるということで、歌詞もTwitter上から集めることに。
西野カナ歌詞botなるものがいくつも存在するためその中からこちらのアカウントを使用させて頂きました。

西野カナ 歌詞bot (@kanayan_lyrics) | Twitter
西野カナ歌詞bot (@kana_lyrics) | Twitter


データ収集コード

実際にツイートから歌詞を収集していきます。1回のツイート取得数は200ツイートまでなのですが、オプションパラメータであるmax_idを指定することによってそれより前のツイートを繰り返し取得していきます。

#-*- encoding:utf-8 -*-
import requests_oauthlib
import requests
import json
import sys, os

#ツイートの取得
def get_tw(account):
    session = requests_oauthlib.OAuth1Session(
        "XXXXXXXXXXXXXXXXXXX", #Consumer Key
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", #Consumer Secret
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", #Access Token
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" #Access Token Secret    
    )
    url = 'https://api.twitter.com/1.1/statuses/user_timeline.json'
    res = session.get(url, params = {'screen_name': account, 'count':200,'include_rts':False})
    maxid = 0
    i = 0
    f = open("data.txt", "a")
    #ツイートを取得
    while True:
        res_text = json.loads(res.text)
        for r in res_text:
            if maxid > r['id'] or maxid == 0:
                maxid = r['id']
            tw = r['text'].encode('utf-8')
            f.write(tw)
            i = i +1
        if  500<= i:
            break
        res =  session.get(url, params = {'screen_name': account, 'count':200,'include_rts':False, 'max_id': r['id']-1})
    #APIの呼び出し・ステータスコード判定
    if res.status_code != 200:
        #正常終了しなければエラー表示
        print ("Twitter API Error: %d" % res.status_code)
        sys.exit(1)
    f.close()
    return 0
#main関数
def main():
    #ツイートを取得する
    get_tw("kanayan_lyrics")
    get_tw("kana_lyrics")
    print("ツイートを収集しました")
    return 0
if __name__ == "__main__":
    sys.exit(main())

収集結果

こんな感じで収集できました。だいぶ重複しているとは思いますが、文字数にして104020文字、5304行西野カナの歌詞を取得することができました。
なかなかカオスです。

f:id:fuchami:20170216055850p:plain



歌詞を生成する

分かち書き

歌詞の生成の前に、日本語を分かち書きする必要があります。
英語は単語ごとにスペースが区切られてますが日本語はそんなことがないため単語ごと(正確には形態素ごと)に分ける必要があります。
MeCabっていう形態素解析エンジンがあるのでこれを使用。

歌詞生成 マルコフ連鎖 (N-gramモデル)

さて、いよいよ歌詞を生成します。
今回はマルコフ連鎖を用いて歌詞を生成していきます。
マルコフ連鎖に関してはニコニコ大百科が非常に分かりやすいので一読することをおすすめします。
マルコフ連鎖とは (マルコフレンサとは) [単語記事] - ニコニコ大百科

今回は連鎖数3で作成。あまり増やしすぎるとオリジナルの歌詞そのままを生成してしまう(ような気がした)ので。

歌詞生成コード

#-*- encoding:utf-8 -*-

from requests_oauthlib import OAuth1Session
import requests
import json
import sys, os, re
import MeCab
import random

#Mecabによってわかちがきを行う
def wakati(text):
    #余計な文字列を除去
    text=re.sub(r'https?://[\w/:%#\$&\?\(\)~\.=\+\-…]+', "", text)
    text=re.sub('RT', "", text)
    text=re.sub('#kana_nishino', "", text)
    text=re.sub('/', "", text  )
    text=re.sub('#西野カナ', "", text)
    t = MeCab.Tagger("-Owakati")
    m = t.parse(text)
    result = m.rstrip(" \n").split(" ")
    return result

#連鎖数3のマルコフ連鎖にて文章生成
def create_tw(wordlist):
    markov = {}
    w1 = ""
    w2 = ""
    w3 = ""
    endword = ["。", "!", "?"]
    for word in wordlist:
        if w1 and w2 and w3:
            if(w1, w2, w3) not in markov:
                markov[(w1, w2, w3)] = []
            markov[(w1, w2, w3)].append(word)
        w1, w2, w3 = w2, w3, word
        
    count = 0
    sentence = ""
    w1, w2, w3 = random.choice(markov.keys())
    while count < len(wordlist):
        tmp = random.choice(markov[w1, w2, w3])
        #句読点などの区切りがついたら文章作成を終了
        if tmp in endword:
            break
        sentence += tmp
        w1, w2, w3 = w2, w3, tmp
        count += 1
        if count > 20:
            break
    return sentence

#main関数
def main():
    CK = 'XXXXXXXXXXXXXXXXXXXXXXXXX'                             
    CS = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'         
    AT = 'XXXXXXXXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' 
    AS = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
   
    filename = "data.txt"
    src = open(filename, "r").read()
    # ツイート投稿用のURL
    url = "https://api.twitter.com/1.1/statuses/update.json"
    #わかち書き
    wordlist = wakati(src)
    #文の作成
    tw = create_tw(wordlist)
    # ツイート本文
    params = {"status": tw}
    # OAuth認証で POST method で投稿
    twitter = OAuth1Session(CK, CS, AT, AS)
    req = twitter.post(url, params = params)
    # レスポンスを確認
    if req.status_code == 200:
        print ("posted tweet:" + tw)
    else:
        print ("Error: %d" % req.status_code)    
    return 0

if __name__ == "__main__":
    sys.exit(main())
    

生成結果

上手く出来たものをいくつか紹介。
なかなか西野カナの雰囲気のが出てると思います。




失敗例

英単語が何故か単語ごとにスペースがなく見づらくなってしまいました。
いろいろやってみものの結局挫折。
pytnoで日本語と英語を扱うのはなかなかキツいっす。