Lyricize:Markovチェーンを使用して歌詞を作成するFlaskアプリ

Lyricize:Markovチェーンを使用して歌詞を作成するFlaskアプリ

新しいコーダーarealwayslookingfornewprojects-同様にそうあるべきです! 自分のside projectを実際に体験するための最良の方法にするだけでなく、趣味から職業への移行を検討している場合は、サイドプロジェクトが構築を開始するための優れた方法です。仕事のポートフォリオ。

アイデアからMVPへ

この投稿では、アイデアの最初の構想から共有可能なプロトタイプまで、(最低限の)MVPを開始するプロセスを進めます。 最後に、独自のバージョンのLyricizeを作成します。これは、アーティストまたはバンドの歌詞を使用して、確率に基づいて「新しい」類似した歌詞を生成する小さなアプリです。 典型的な「このコードをすべて複製する方法」のチュートリアルを紹介する代わりに、プロセスを段階的に説明して、思考プロセスと作成に関与するactuallyを示します。

これは必ずしも次のキラースタートアップの構築に関するものではないことに注意してください。 1)楽しい学習の機会、2)他の人と共有できるプロジェクトを探しています。

Before we start take a look at the sample app to see what you’ll be creating.基本的に、マルコフ連鎖を使用して、特定のアーティストの歌詞に基づいて新しい歌詞を生成できます。 たとえば、「Bob Dylan」を検索して、行数を3に変更してみてください。 かなりクールですよね? 同じ検索を実行したところ、次の結果になりました。

ヨンダーはあなたの約束をすべて守り、ボート
峡谷の準備ができました
私はフックです

とても深い。 とにかく始めましょう…


興味のあるトピックを見つける

したがって、ステップ1:詳細を知りたいトピックを見つけます。 次のアプリは、マルコフ連鎖を使用してサンプルテキストの本文を指定して「リアルな」テキストを生成する古いcollege assignment(確かに最も一般的なインスピレーションのソースではありません)に触発されました。 マルコフモデルはall sorts of scenariosで発生します。 (マルコフモデルの詳細については、後ほど詳しく説明します。)確率ベースのテキスト生成のアイデアは特に興味深いと感じました。具体的には、歌の歌詞をサンプルテキストとして使用して「新しい」歌詞を生成するとどうなるのかと思いました。

インターネットへ! クイックWeb検索では、マルコフベースの歌詞生成サイトがいくつか表示されますが、私が思い描いているものとはまったく異なります。 その上、他の誰かの完成したコードをプラグインすることは、マルコフジェネレーターが実際にどのように機能するかを学ぶための非常に効果的な方法ではありません。独自に構築しましょう。

それでは…マルコフジェネレータはどのように機能しますか? 基本的に、マルコフ連鎖は、特定のパターンの発生頻度に基づいてテキストから生成されます。 例として、サンプルテキストとして次の文字列を考えます。

bobby

特定の文字が発生する可能性を予測する方法として、このテキストから可能な限り単純なマルコフモデルを構築します。これはorder 0のマルコフモデルです。 これは簡単な頻度表です。

b: 3/5
o: 1/5
y: 1/5

ただし、これはかなり悪い言語モデルです。文字が全体的に発生する頻度に加えて、特定の文字がgiven the previous letter発生する頻度も調べたいと思います。 以前の1つの手紙に依存しているため、これは次数1のマルコフモデルです。

given "b":
  "b" is next: 1/3
  "o" is next: 1/3
  "y" is next: 1/3
given "o":
  "b" is next: 1
given "y":
  [terminates]: 1

ここから、高次のマルコフモデルを想像できます。次数2モデルは、2文字の文字列「bo」などの後に出現する各文字の頻度を測定することから始まります。 次数を増やすことで、実際の言語のように見えるモデルが得られます。たとえば、「python」という単語を含む多くのサンプル入力が与えられた5次のマルコフモデルは、「pytho」という文字列の後に「n」が付いている可能性が非常に高いのに対し、はるかに低い次数のモデルはいくつかの創造的な言葉。

開発を開始

マルコフモデルの大まかな近似をどのように構築しますか? 基本的に、上で概要を示した高次モデルの構造は、辞書の辞書です。 さまざまな単語フラグメント(つまり、「bo」)をキーとして持つmodel辞書を想像できます。 これらの各フラグメントは、順番に辞書を指し、それらの内部辞書はそれぞれの次の文字(「y」)をキーとして保持し、それぞれの頻度を値として保持します。

サンプルテキストとマルコフモデルの順序を取り込むgenerateModel()メソッドを作成することから始めて、次の辞書の辞書を返します。

def generateModel(text, order):
    model = {}
    for i in range(0, len(text) - order):
        fragment = text[i:i+order]
        next_letter = text[i+order]
        if fragment not in model:
            model[fragment] = {}
        if next_letter not in model[fragment]:
            model[fragment][next_letter] = 1
        else:
            model[fragment][next_letter] += 1
    return model

使用可能なすべてのテキストをループし、最後の使用可能な完全なフラグメント+次の文字まで、文字列の終わりからはみ出さないように、fragment辞書を各fragmentは、合計next_letter頻度の辞書を保持しています。

この関数をPythonシェルにコピーして試してください:

>>>

>>> generateModel("bobby", 1)
{'b': {'y': 1, 'b': 1, 'o': 1}, 'o': {'b': 1}}

そうするだろう! 相対確率の代わりに頻度のカウントがありますが、それで作業できます。 100%の確率に追加するために各辞書を正規化する必要はありません。

次に、このmodelgetNextCharacter()メソッドで使用して、モデルとフラグメントが与えられた場合に、モデルの確率が与えられた場合に適切な次の文字を決定します。

from random import choice
def getNextCharacter(model, fragment):
    letters = []
    for letter in model[fragment].keys():
        for times in range(0, model[fragment][letter]):
            letters.append(letter)
    return choice(letters)

これは最も効率的なセットアップではありませんが、今は簡単に構築して動作します。 フラグメント後の合計出現頻度を考慮して、文字のリストを作成し、そのリストからランダムに選択しました。

残っているのは、指定された長さのテキストを実際に生成する3番目のメソッドでこれら2つのメソッドを使用することです。 これを行うには、新しい文字を追加しながら、構築中の現在のテキストフラグメントを追跡する必要があります。

def generateText(text, order, length):
    model = generateModel(text, order)

    currentFragment = text[0:order]
    output = ""
    for i in range(0, length-order):
        newCharacter = getNextCharacter(model, currentFragment)
        output += newCharacter
        currentFragment = currentFragment[1:] + newCharacter
    print output

これをマルコフ順序を取り、引数としてテキスト長を出力する完全に実行可能なスクリプトにしましょう。

from random import choice
import sys

def generateModel(text, order):
    model = {}
    for i in range(0, len(text) - order):
        fragment = text[i:i+order]
        next_letter = text[i+order]
        if fragment not in model:
            model[fragment] = {}
        if next_letter not in model[fragment]:
            model[fragment][next_letter] = 1
        else:
            model[fragment][next_letter] += 1
    return model

def getNextCharacter(model, fragment):
    letters = []
    for letter in model[fragment].keys():
        for times in range(0, model[fragment][letter]):
            letters.append(letter)
    return choice(letters)

def generateText(text, order, length):
    model = generateModel(text, order)
    currentFragment = text[0:order]
    output = ""
    for i in range(0, length-order):
        newCharacter = getNextCharacter(model, currentFragment)
        output += newCharacter
        currentFragment = currentFragment[1:] + newCharacter
    print output

text = "some sample text"
if __name__ == "__main__":
    generateText(text, int(sys.argv[1]), int(sys.argv[2]))

とりあえず、コピーして貼り付けたアラニスモリゼットの歌詞に基づいて、コードに文字列を直接挿入する非常に科学的な方法でサンプルテキストを生成します。

Test

スクリプトを保存して旋回させます。

$ python markov.py 2 100
I wounts
You ho's humortel whime
 mateend I wass
How by Lover
$ python markov.py 4 100
stress you to cosmic tears
All they've cracked you (honestly) at the filler in to like raise
$ python markov.py 6 100
tress you place the wheel from me
Please be philosophical
Please be tapped into my house

まあ、それはただ貴重でした。 最後の2つのトライアルは、彼女の歌詞をきちんと代表しています(ただし、順序2の最初のサンプルはビョークに似ています)。 これらの結果は、コードをすばやくスケッチするのに十分な励みになるため、このことを実際のプロジェクトに変えましょう。

次の反復

最初のハードル:大量の歌詞の取得を自動化する方法は? 1つの選択肢は、歌詞サイトからコンテンツを選択的にスクレイピングすることですが、おそらく低品質の結果を得るために多大な努力が必要になります。さらに、ほとんどの歌詞アグリゲーターの陰気さと音楽業界の厳格さを考えると、潜在的な法的灰色の領域があります。 代わりに、開いているAPIがあるかどうかを確認しましょう。 programmableweb.comを検索すると、実際には14のさまざまな歌詞APIがリストされています。 ただし、これらのリストは常に最新とは限らないため、最新のリストで検索してみましょう。

LYRICSnMUSICは、JSONを使用して最大150文字の歌詞を返す無料のRESTful APIを提供します。 これは、特にほとんどの曲の繰り返しを考えると、ユースケースに最適です。サンプルだけで十分な歌詞を収集する必要はありません。 new keyを取得して、APIにアクセスできるようにします。

APIを試してから、このソースを使いましょう。 ドキュメントに基づいて、次のようなサンプルリクエストを作成できます。

http://api.lyricsnmusic.com/songs?api_key=[YOUR_API_KEY_HERE]&artist=coldplay

ブラウザに吐き出されるJSONの結果は少し読みにくいです。 formatterでそれらを介して、よりよく見てください。 Coldplayの曲に基づいた辞書のリストを正常に取得できたようです。

[
  {
     "title":"Don't Panic",
     "url":"http://www.lyricsnmusic.com/coldplay/don-t-panic-lyrics/4294612",
     "snippet":"Bones sinking like stones \r\nAll that we've fought for \r\nHomes, places we've grown \r\nAll of us are done for \r\n\r\nWe live in a beautiful world \r\nYeah we ...",
     "context":null,
     "viewable":true,
     "instrumental":false,
     "artist":{
        "name":"Coldplay",
        "url":"http://www.lyricsnmusic.com/coldplay"
     }
  },
  {
     "title":"Shiver",
     "url":"http://www.lyricsnmusic.com/coldplay/shiver-lyrics/4294613",
     "snippet":"So I look in your direction\r\nBut you pay me no attention, do you\r\nI know you don't listen to me\r\n'Cause you say you see straight through me, don't you...",
     "context":null,
     "viewable":true,
     "instrumental":false,
     "artist":{
        "name":"Coldplay",
        "url":"http://www.lyricsnmusic.com/coldplay"
     }
  },
  ...
]

応答を制限する方法はありませんが、提供される各「スニペット」にのみ関心があります。これは、このプロジェクトでは問題ないようです。

マルコフジェネレーターを使用した予備実験は教育的なものでしたが、現在のモデルは歌詞を生成するタスクに最適ではありません。 一つには、文字ごとに物事をとるのではなく、おそらく個々の単語をトークンとして使用する必要があります。言語自体をモックしようとするのは楽しいですが、偽の歌詞を生成するために、本物の英語に固執したいと思うでしょう。 しかし、これはややこしく聞こえます。マルコフチェーンがどのように動作するかを理解するのに長い道のりを歩んできました。 この時点で、より多くの学習のために比phor的な車輪を再発明する(優れたコーディングプラクティスになる可能性がある)か、他の人がすでに作成したものを見るという岐路に達します。

私は怠wayな方法を選択し、インナーウェブの検索に戻りました。 GitHubの親切な魂は、すでにimplementedの基本的な単一単語ベースのマルコフ連鎖を持っており、それをPyPIにアップロードしています。 codeをざっと見てみると、このモデルは0次のみであるように見えます。 これはおそらく、私たち自身で構築するのに十分な速さだったでしょうが、高次のモデルはかなり多くの作業をするかもしれません。 とりあえず、他の人のあらかじめパッケージ化されたホイールを使ってみましょう。単語全体を使用している場合、少なくとも注文0モデルはビョークのようには聞こえません。

作成したものを友人や家族と簡単に共有したいので、それをWebアプリケーションに変えるのは理にかなっています。 次に、Webフレームワークを選択します。 個人的には、私はDjangoにはるかに精通していますが、ここではやり過ぎのようです。結局のところ、独自のデータベースさえ必要ありません。 フラスコを試してみましょう。

フラスコを追加

通常のルーチンに従って、fire up a virtual environment-まだ行っていない場合! これがなじみのないプロセスである場合は、previous postsのいくつかを調べて、セットアップ方法を学習してください。

$ mkdir lyricize
$ cd lyricize
$ virtualenv --no-site-packages venv
$ source venv/bin/activate

また、通常どおり、必要な要件をインストールして、requirements.txtファイルにスローします。

$ pip install PyMarkovChain flask requests
$ pip freeze > requirements.txt

歌詞APIにWebリクエストを送信できるように、requestsライブラリにも追加しました。

次に、アプリを作成します。 簡単にするために、2ページに分割します。メインページには、アーティスト名と生成する歌詞の行数を選択するための基本的なフォームがユーザーに表示され、2番目の「歌詞」ページには結果。 index.htmlテンプレートを使用するapp.pyという名前の最低限のFlaskアプリケーションから始めましょう。

from flask import Flask, render_template

app = Flask(__name__)
app.debug = True

@app.route('/', methods=['GET'])
def index():
    return render_template('index.html')

if __name__ == '__main__':
    app.run()

このアプリがこれまでに行うAllは、index.htmlテンプレートのコンテンツをロードすることです。 基本的なフォームにしましょう。


 
  
Artist or band name:
Number of lines:

このindex.htmlをtemplatesという名前の別のフォルダーに保存して、Flaskが見つけられるようにします。 ここでは、FlaskのJinja2テンプレートを使用して、1から10までの数字を含むループに基づいて「選択」ドロップダウンを作成しています。 他のものを追加する前に、このページを起動して、正しく設定されていることを確認してください。

$ python app.py
* Running on http://127.0.0.1:5000/

これで、ブラウザでhttp://127.0.0.1:5000/にアクセスして、素敵なフォームを見ることができるはずです。

次に、結果ページに何を表示するかを決めて、何を渡す必要があるかを判断します。


 
  

{% for line in result %} {{ line }}
{% endfor %}

{{ artist }}


ここでは、result配列を1行ずつループし、各行を個別に表示しています。 その下に、選択したartistを表示し、ホームページにリンクします。 これをlyrics.htmlとして/ templatesディレクトリに保存します。

また、index.htmlのフォームアクションを更新して、この結果ページを指すようにする必要があります。

次に、結果の歌詞ページのルートを作成します。

@app.route('/lyrics', methods=['POST'])
def lyrics():
    artist = request.form['artist']
    lines = int(request.form['lines'])

    if not artist:
        return redirect(url_for('index'))

    return render_template('lyrics.html', result=['hello', 'world'], artist=artist)

このページはフォームからPOSTリクエストを受け取り、提供されたartistlinesの数を解析します。歌詞はまだ生成されておらず、テンプレートに結果のダミーリストを提供しているだけです。 また、依存している必要なFlask機能(url_forおよびredirect)を追加する必要があります。

from flask import Flask, render_template, url_for, redirect

テストして、まだ何も壊れていないことを確認します。

$ python app.py

素晴らしい、今プロジェクトの本当の肉のために。 lyrics()内で、渡されたアーティストパラメーターに基づいてLYRICSnMUSICから応答を取得します。

# Get a response of sample lyrics from the provided artist
uri = "http://api.lyricsnmusic.com/songs"
params = {
    'api_key': API_KEY,
    'artist': artist,
}
response = requests.get(uri, params=params)
lyric_list = response.json()

リクエストを使用して、パラメーターの辞書を含む特定のURLを取得します:指定されたアーティスト名とAPIキー。 この秘密APIキーは、コードにnotが含まれている必要があります。結局のところ、このコードを他の人と共有したいと思うでしょう。 代わりに、この値を変数として保持する別のファイルを作成しましょう。

$ echo "API_KEY=[youractualapikeygoeshere]" > .env

アプリの上部に次のコードを追加するだけで、Flaskが読み取れる特別な「環境」ファイルを作成しました。

import os
API_KEY = os.environ.get('API_KEY')

そして最後に、マルコフ連鎖機能を追加しましょう。 他の人のパッケージを使用するようになったので、これはかなり簡単になります。 まず、上部にインポートを追加します。

from pymarkovchain import MarkovChain

次に、APIからlyricsの応答を受け取った後、マルコフ連鎖を作成し、歌詞データを読み込んで、文のリストを生成します。

mc = MarkovChain()
mc.generateDatabase(lyrics)

result = []
for line in range(0, lines):
    result.append(mc.generateString())

合計すると、app.pyは次のようになります。

from flask import Flask, url_for, redirect, request, render_template
import requests
from pymarkovchain import MarkovChain
import os

API_KEY = os.environ.get('API_KEY')

app = Flask(__name__)
app.debug = True

@app.route('/', methods=['GET'])
def index():
    return render_template('index.html')

@app.route('/lyrics', methods=['POST'])
def lyrics():
    artist = request.form['artist']
    lines = int(request.form['lines'])

    if not artist:
        return redirect(url_for('index'))

    # Get a response of sample lyrics from the artist
    uri = "http://api.lyricsnmusic.com/songs"
    params = {
        'api_key': API_KEY,
        'artist': artist,
    }
    response = requests.get(uri, params=params)
    lyric_list = response.json()

    # Parse results into a long string of lyrics
    lyrics = ''
    for lyric_dict in lyric_list:
        lyrics += lyric_dict['snippet'].replace('...', '') + ' '

    # Generate a Markov model
    mc = MarkovChain()
    mc.generateDatabase(lyrics)

    # Add lines of lyrics
    result = []
    for line in range(0, lines):
        result.append(mc.generateString())

    return render_template('lyrics.html', result=result, artist=artist)

if __name__ == '__main__':
    app.run()

やってみよう! すべてがローカルで動作するはずです。 今、それを世界と共有するために…

Herokuにデプロイする

Herokuでホストしましょう。これらの最小限の要件のために、無料でそれを行うことができます。 そのためには、コードを少し調整する必要があります。 まず、Herokuにアプリの提供方法を​​指示するProcfileを追加します。

$ echo "web: python app.py" > Procfile

次に、Herokuはアプリケーションを実行するランダムなポートを指定するため、上部にポート番号を渡す必要があります。

PORT = int(os.environ.get('PORT', 5000))

app = Flask(__name__)
app.config.from_object(__name__)

そして、アプリが実行されたら、このポートを必ず渡してください

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=PORT)

Flaskはデフォルトでローカルコンピューターでプライベートに実行されるため、ホストを「0.0.0.0」に指定する必要がありましたが、公開されているIP上のHerokuでアプリを実行する必要があります。

最後に、コードからapp.debug=Trueを削除して、問題が発生した場合にユーザーが完全なスタックトレースエラーを確認できないようにします。

gitリポジトリを初期化し(まだ行っていない場合)、新しいHerokuアプリを作成し、そこにコードをプッシュします!

$ git init
$ git add .
$ git commit -m "First commit"
$ heroku create
$ git push heroku master

この展開プロセスのより詳細な概要については、Heroku docsを参照してください。 HerokuでAPI_KEY変数を追加してください:

$ heroku config:set API_KEY=[youractualapikeygoeshere]

そして、私たちはすべて準備完了です! あなたの作品を世界と共有する時間-またはそれをハッキングし続ける:)

結論と次のステップ

このコンテンツが気に入った場合は、Web開発を学習するためのcurrent courses、またはより高度な手法をカバーする最新のKickstarterに興味があるかもしれません。 または-アプリhereをいじってみてください。

可能な次のステップ:

  • このHTMLは、90年代初頭に作成されたように見えます。スタイリングにはBootstrapまたは基本的なCSSを使用します

  • コードの機能を忘れる前に、コードにコメントを追加してください! (これは読者のための演習として残されています:o)

  • 歌詞ルートのコードを個別のメソッド(つまり、歌詞APIからの応答を返すメソッドと、マルコフモデルを生成するための別のメソッド)に抽象化します。これにより、サイズと複雑さが増すにつれて、コードの保守とテストが容易になります。

  • 高次を使用できるマルコフジェネレーターを作成する

  • Flask-WTFを使用して、フォームとフォームの検証を改善します

  • そういえば:より安全に! 現時点では、誰かが不合理なPOSTリクエストを送信したり、自分のコードをページに挿入したり、多くの迅速に繰り返されるリクエストでサイトをDoSしたりする可能性があります。確実な入力検証と基本的なレート制限を追加します

  • より良いエラー処理を追加します。 API呼び出しに時間がかかりすぎる、または何らかの理由で失敗した場合はどうなりますか?

  • 結果をtext-to-speechエンジンにスローし、別のマルコフモデルを使用してピッチパターンを変更する方法を学び、ビートに設定します。すぐにチャートのトップになります!