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

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

新しいコーダーhttp://www.reddit.com/r/learnpython/comments/xjlsh/i_just_finished_codeacademys_python_course/[are] always http://www.reddit.com/r/learnpython/comments/ul1b8/any_good_projects_for_beginners/[looking] for http://www。 reddit.com/r/learnpython/comments/1iqv3c/please_help_me_prepare_a_roadmap_as_to_what_i/[new] projects-あるべきです! 独自のhttps://realpython.com/intermediate-python-project-ideas/#web[side project]を実践的な経験を得るための最良の方法にするだけでなく、職業への趣味、そしてサイドプロジェクトは、仕事のポートフォリオを構築するための素晴らしい方法です。

アイデアからMVPへ

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

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

*サンプルhttp://lyricize.herokuapp.com [app]を見て開始する前に、作成するものを確認してください。*基本的に、マルコフチェーンを使用して、特定のアーティストの歌詞に基づいて新しい歌詞を生成できます。 たとえば、「Bob Dylan」を検索して、行数を3に変更してみてください。 かなりクールですよね? 同じ検索を実行したところ、次の結果になりました。

  • Yonderはすべてを約束し、ボートを立てます * 私は峡谷の準備ができています * 私はフックです *

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

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

したがって、ステップ1:詳細を知りたいトピックを見つけます。 次のアプリは、マルコフを使用する古いhttp://www.cs.princeton.edu/courses/archive/fall13/cos126/assignments/markov.html [カレッジの割り当て](確かにインスピレーションの最も一般的なソースではない)に触発されましたサンプルテキストの本文を指定して、「リアルな」テキストを生成するチェーン。 マルコフモデルは、http://en.wikipedia.org/wiki/Markov_chain#Applications [あらゆる種類のシナリオ]に登場します。 (マルコフモデルの詳細については、後ほど詳しく説明します。)確率ベースのテキスト生成のアイデアは特に興味深いと感じました。具体的には、歌の歌詞をサンプルテキストとして使用して「新しい」歌詞を生成するとどうなるのかと思いました。

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

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

bobby

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

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

ただし、これはかなり悪い言語モデルです。全体的に文字が出現する頻度に加えて、特定の文字が_前の文字が与えられた場合の_出現頻度も調べたいと思います。 以前の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 `で ` model `辞書に ` fragment `辞書を追加しました合計 ` next_letter +`頻度の辞書を保持します。

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

>>>

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

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

次に、モデルとフラグメントを指定して、モデルの確率を考慮して適切な次の文字を決定する「+ getNextCharacter()」メソッドでこの「 model +」を使用します。

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は、https://realpython.com/python-jsonを使用して、無料のhttps://realpython.com/api-integration-in-python/[RESTful API]を提供しています/[JSON]は、最大150文字の歌詞を返します。 これは、特にほとんどの曲の繰り返しを考えると、ユースケースに最適です。サンプルだけで十分な歌詞を収集する必要はありません。 APIにアクセスできるように、http://www.lyricsnmusic.com/api_keys/new [新しいキー]を入手してください。

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の親切な魂は、すでにhttps://github.com/MaxWagner/PyMarkovChain [実装済み]の基本的な単語ベースのマルコフチェーンを持ち、さらにhttps://pypi.python.org/pypi/PyMarkovChain/[ PyPI]。 codeをざっと見てみると、このモデルは次数0のみであるように見えます。 これはおそらく、私たち自身で構築するのに十分な速さだったでしょうが、高次のモデルはかなり多くの作業をするかもしれません。 とりあえず、他の人のあらかじめパッケージ化されたホイールを使ってみましょう。単語全体を使用している場合、少なくとも注文0モデルはビョークのようには聞こえません。

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

フラスコを追加

通常のルーチンに従って、https://realpython.com/python-virtual-environments-a-primer/[仮想環境を起動]-まだ行っていない場合! これがなじみのあるプロセスでない場合は、https://realpython.com/python-web-applications-with-flask-part-i/#toc_5 [以前の投稿]のいくつかを調べて、設定方法を学習してください。アップ。

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

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

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

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

次に、アプリを作成します。 簡単にするために、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テンプレートのコンテンツをロードすることです。 基本的なフォームにしましょう。

<html>
 <body>
  <form action="#" method="post" class="lyrics">
    Artist or band name: <input name="artist" type="text"/><br/>
    Number of lines:
    <select name="lines">
      {% for n in range(1,11) %}
        <option value="{{n}}">{{n}}</option>
      {% endfor %}
    </select>
    <br/><br/>
    <input class="button" type="submit" value="Lyricize">
  </form>
 </body>
</html>

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

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

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

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

<html>
 <body>
  <div align="center" style="padding-top:20px;">
   <h2>
   {% for line in result %}
     {{ line }}<br/>
   {% endfor %}
   </h2>
   <h3>{{ artist }}</h3>
   <br/>
   <form action="{{ url_for('index') }}">
    <input type="submit" value="Do it again!"/>
   </form>
  </div>
 </body>
</html>

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

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

<form action="{{ url_for('lyrics') }}" method="post" class="lyrics">

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

@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リクエストを受け取り、提供された*アーティスト*と*行*の数を解析します-まだ歌詞を生成しておらず、テンプレートに結果のダミーリストを与えているだけです。 また、必要な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キーは、コードに表示されないはずです。結局のところ、このコードを他の人と共有したいと思うでしょう。 代わりに、この値を変数として保持する別のファイルを作成しましょう。

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

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

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

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

from pymarkovchain import MarkovChain

そして、APIから lyrics 応答を受け取った後、MarkovChainを作成し、歌詞データを読み込んで、文章のリストを生成します。

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でホストしましょう。これらの最小限の要件のために、無料でそれを行うことができます。 そのためには、コードを少し調整する必要があります。 まず、https://devcenter.heroku.com/articles/procfile [Procfile]を追加して、Herokuにアプリの提供方法を​​指示します。

$ 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

この展開プロセスの詳細については、https://devcenter.heroku.com/articles/getting-started-with-python [Heroku docs]を参照してください。 HerokuでAPI_KEY変数を追加してください:

$ heroku config:set API_KEY=[youractualapikeygoeshere]

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

結論と次のステップ

このコンテンツが気に入ったら、Web開発を学ぶためのhttps://realpython.com [現在のコース]や、より高度なテクニックをカバーする最新のhttp://kck.st/1b0tz6I[Kickstarter]に興味があるかもしれません。 または-アプリhttp://lyricize.herokuapp.com [こちら]をいじってみてください。

可能な次のステップ:

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

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

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

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

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

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

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

  • 結果をhttp://tts-api.com/[text-to-speech]エンジンに投げ、別のマルコフモデルを使用してピッチパターンを変化させることを学習し、ビートに設定します。すぐにチャートのトップになります!