Lyricize: приложение Flask для создания текстов с использованием цепей Маркова

Lyricize: приложение Flask для создания текстов с использованием цепей Маркова

Новые кодеры are always://www.reddit.com/r/learnpython/comments/ul1b8/any_good_projects_for_beginners/[looking] http://www.reddit.com/r/learnpython/comments/1a9yie/worked_with_python_for_almost_a_year_now_how_to/www. http reddit.com/r/learnpython/comments/1iqv3c/please_help_me_prepare_a_roadmap_as_to_what_i/[new] http://www.reddit.com/r/learnpython/comments/1czbfw/kinda_lostwhat_to_do_next/wellprojects Мало того, что ваш собственный side project не только лучший способ получить практический опыт, но и если вы хотите сделать переход от увлечение профессией, тогда побочные проекты - отличный способ начать создавать портфолио работ.

От идеи к MVP

В этом посте мы проработаем процесс запуска (минимум) MVP, от первоначальной концепции идеи до разделяемого прототипа. К концу вы создадите свою собственную версию http://lyricize.herokuapp.com [Lyricize], небольшого приложения, которое использует тексты песен артиста или группы для создания «новых» текстов с похожим звучанием на основе вероятностей. Вместо того, чтобы представлять типичное учебное пособие «Вот как воспроизвести весь этот код», мы пройдем пошаговый процесс, чтобы показать, что на самом деле вовлечено в процесс мышления и творчества.

_ Обратите внимание, что это не обязательно о создании следующего запуска убийцы; мы просто ищем проект, который может быть 1) интересной возможностью для обучения и 2) делиться с другими. _

*Прежде чем мы начнем, взгляните на образец http://lyricize.herokuapp.com [приложение], чтобы увидеть, что вы будете создавать.* По сути, вы можете создавать новые тексты на основе текстов конкретного исполнителя с использованием цепей Маркова. Например, попробуйте поискать «Боб Дилан» и измените количество строк на три. Довольно круто, правда? Я только что выполнил тот же поиск, в результате которого:
*Вон все, что ты обещал, и лодки* + *Я готов к ущелью* + *Я крюк*

Так глубоко. В любом случае, давайте начнем …​

«» «»»

Найти интересующую тему

Итак, шаг 1: найдите тему, о которой вы хотите узнать больше. Следующее приложение было вдохновлено старым заданием college (по общему признанию, не самым распространенным источником вдохновения), которое использует Маркова цепочки для генерации «реально выглядящего» текста с использованием тела образца текста. Марковские модели возникают в all сценариев всех видов. (Вскоре мы углубимся в то, что такое марковская модель.) Идея генерации текста на основе вероятностей показалась мне особенно интересной; в частности, мне было интересно, что произойдет, если вы будете использовать текст песни в качестве образца текста для создания «новой» песни…

В интернет! Быстрый поиск в сети показывает несколько марковских сайтов с лирическим генератором, но не совсем то, что я имею в виду. Кроме того, отключение чужого законченного кода не очень эффективный способ узнать, как на самом деле работают генераторы Маркова; давайте строить свои собственные.

Итак …​ как работают генераторы Маркова? По сути, цепь Маркова генерируется из некоторого текста на основе частоты встречаемости определенных шаблонов. В качестве примера рассмотрим следующую строку в качестве образца текста:

bobby

Из этого текста мы построим простейшую возможную марковскую модель, которая является марковской моделью order 0, как способ предсказать вероятность появления любой конкретной буквы. Это простая таблица частот:

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

Тем не менее, это довольно плохая языковая модель; Помимо того, как часто буквы встречаются в целом, мы также хотим посмотреть, как часто встречается конкретная буква с учетом предыдущей буквы. Поскольку мы зависим от одной предыдущей буквы, это марковская модель порядка 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 должна начинаться с измерения частоты каждой буквы, которая появляется после двухбуквенной строки «bo» и т. д. Увеличивая порядок, мы получаем модель, которая начинает больше походить на реальный язык; например, марковская модель 5-го порядка, которой был дан большой выборочный ввод, включая слово «python», с большой вероятностью следовала бы за строкой «pytho» с «n», тогда как модель гораздо более низкого порядка могла бы придумать несколько креативных слов.

Начать разработку

Как бы мы пошли о построении приблизительной аппроксимации марковской модели? По сути, структура, которую мы обрисовали в общих чертах выше с моделями высшего порядка, представляет собой словарь словарей. Вы можете представить себе словарь + model + с различными фрагментами слов (то есть, «bo») в качестве ключей. Каждый из этих фрагментов затем будет указывать на словарь по очереди, причем эти внутренние словари содержат отдельные следующие буквы («у») в качестве ключей, а их соответствующие частоты в качестве значений.

Давайте начнем с создания метода + 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

Мы перебрали весь доступный текст, поднявшись до последнего доступного полного фрагмента + следующей буквы, чтобы не пропустить конец строки, добавив наши словари + фрагмент + в + модель + с каждым + фрагментом + содержит словарь полных + next_letter + частот.

Скопируйте эту функцию в оболочку Python и попробуйте:

>>>

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

Это сработает! У нас есть счетчики частот вместо относительных вероятностей, но мы можем работать с этим; нет причин для нормализации каждого словаря, чтобы добавить к вероятностям 100%.

Теперь давайте используем этот + model + в методе + getNextCharacter () +, который, учитывая модель и фрагмент, определит подходящую следующую букву с учетом вероятностей модели:

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)

Это не самая эффективная установка, но она проста в сборке и работает на данный момент. Мы просто создали список букв, учитывая их общую частоту появления после фрагмента, и выбрали случайным образом из этого списка.

Осталось только использовать эти два метода в третьем методе, который на самом деле будет генерировать текст определенной длины. Для этого нам необходимо отслеживать текущий фрагмент текста, который мы создаем, при добавлении новых символов:

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]))

На данный момент мы сгенерируем образец текста с помощью очень научного метода бросания строки прямо в код на основе некоторых скопированных и вставленных текстов Alanis Morisette.

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 больше похож на Бьорка). Эти результаты достаточно обнадеживающие для быстрого наброска кода, поэтому давайте превратим это в настоящий проект.

Следующая итерация

Первое препятствие: как мы собираемся автоматизировать получение большого количества текстов? Одним из вариантов может быть выборочная очистка контента с сайта с текстами песен, но это звучит как много усилий для получения результатов, возможно, низкого качества, плюс потенциальная легальная серая область, учитывая теневые оттенки большинства агрегаторов текстов и драконовство музыкальной индустрии. Вместо этого, давайте посмотрим, есть ли открытые API. Направляясь на поиск по programmableweb.com, мы на самом деле находим 14 различных API текстов песен в списке. Эти списки не всегда самые свежие, поэтому давайте посмотрим по последним в списке.

LYRICSnMUSIC предлагает бесплатный RESTful API с использованием JSON, чтобы вернуть до 150 символов песни. Это звучит идеально для нашего сценария использования, особенно учитывая повторение большинства песен; нет необходимости собирать полную лирику, когда подойдет только образец. Перейдите по адресу 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"
     }
  },
  ...
]

Ограничить ответ невозможно, но мы заинтересованы только в каждом предоставленном «фрагменте», который отлично подходит для этого проекта.

Наши предварительные эксперименты с марковскими генераторами были познавательными, но наша текущая модель не совсем подходит для задачи генерации текстов. Во-первых, нам, вероятно, следует использовать отдельные слова в качестве токенов, а не брать вещи символ за символом; забавно пытаться издеваться над самим языком, но для создания фальшивых текстов мы хотим придерживаться настоящего английского языка. Это звучит хитрее, и мы проделали долгий путь к пониманию работы цепей Маркова, что и было первоначальной целью этого упражнения. На этом этапе мы достигаем перекрестка: заново изобретаем метафорическое колесо для большего обучения (это может быть отличной практикой кодирования) или видим, что еще уже создали другие.

Я выбрал ленивый выход и направился назад, чтобы обыскать внутренние сети. Добрая душа на GitHub уже имеет implemented базовую цепочку Маркова, основанную на одном слове, и даже загрузила ее на PyPI. Быстро пройдя по code, вы увидите, что эта модель имеет только порядок 0. Это, вероятно, было бы достаточно быстро, чтобы построить самостоятельно, в то время как модель более высокого порядка могла бы быть значительно больше работы. А пока пойдем с чьим-то заранее упакованным колесом; по крайней мере, модель порядка 0 не будет звучать как Бьорк, если мы будем использовать целые слова.

Поскольку мы хотим легко поделиться своим творением с друзьями и семьей, имеет смысл превратить его в веб-приложение. Теперь, чтобы выбрать веб-фреймворк. Лично я наиболее знаком с Джанго, но здесь это кажется излишним; в конце концов, нам даже не понадобится собственная база данных. Давайте попробуем настой.

Добавить колбу

В соответствии с обычной процедурой fire up виртуальной среды - если вы еще этого не сделали! Если это не знакомый процесс, ознакомьтесь с некоторыми из наших previous posts, чтобы узнать, как их настроить вверх.

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

Также, как обычно, установите необходимые требования и добавьте их в файл require.txt:

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

Мы также добавили в библиотеку requests, чтобы мы могли отправлять веб-запросы в API текстов песен.

Теперь, чтобы сделать приложение. Для простоты, давайте разделим его на две страницы: главная страница представит пользователю базовую форму для выбора имени исполнителя и количество строк текста для генерации, а вторая страница «текст» представит Результаты. Давайте начнем с базового приложения Flask с именем app.py , которое использует шаблон index.html :

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>

Сохраните этот index.html в отдельной папке с именем templates, чтобы Flask мог его найти. Здесь мы используем шаблон Jasja2 Flask для создания выпадающего списка «selection» на основе цикла, охватывающего числа от 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 , отображая каждую строку отдельно. Ниже мы показываем выбранного *исполнителя и ссылку на домашнюю страницу. Сохраните это как 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 из формы, анализируя предоставленный Artist и количество строк - мы еще не генерируем текст, просто предоставляя шаблону фиктивный список результатов. Нам также необходимо добавить необходимые функциональные возможности Flask - + url_for + и + redirect + - на которые мы полагались:

from flask import Flask, render_template, url_for, redirect

Проверьте это, чтобы убедиться, что ничего еще не сломано:

$ python app.py

Отлично, теперь для настоящего мяса проекта. В рамках lyrics () давайте получим ответ от LYRICSnMUSIC на основе нашего переданного параметра Artist:

# 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

И затем, после того, как мы получили ответ lyrics от API, мы просто создаем 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, поскольку (для этих минимальных требований) мы можем сделать это бесплатно. Для этого нам нужно сделать несколько небольших изменений в коде. Сначала добавьте 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)

Нам также пришлось указать хост «0.0.0.0», потому что Flask по умолчанию запускается на локальном компьютере в частном порядке, в то время как мы хотим, чтобы приложение запускалось на Heroku с общедоступного IP-адреса.

Наконец, удалите + 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] для более подробного изложения этого процесса развертывания. Обязательно добавьте свою переменную API_KEY в Heroku:

$ heroku config:set API_KEY=[youractualapikeygoeshere]

И у нас все готово! Время поделиться своим творением с миром - или продолжать взламывать его :)

Заключение и следующие шаги

Если вам понравился этот контент, вас могут заинтересовать наши https://realpython.com [текущие курсы] для изучения веб-разработки или наш новейший Kickstarter, который охватывает более продвинутые методы. Или - просто поиграйте с приложением http://lyricize.herokuapp.com [здесь].

*Возможны следующие шаги:*
  • Этот HTML выглядит так, как будто он был написан в начале 90-х; используйте Bootstrap или просто немного базового CSS для стилизации

  • Добавьте несколько комментариев к коду, прежде чем вы забудете, что он делает! (Это оставлено как упражнение для читателя: о)

  • Абстрагировать код в тексте лирики, чтобы быть отдельными методами (т. Е. Метод для возврата ответов из API лирики и другой отдельный метод для генерации модели Маркова); это сделает код более легким в обслуживании и тестировании по мере роста его размера и сложности

  • Создать марковский генератор, способный использовать более высокие порядки

  • Используйте Flask-WTF для улучшения форм и проверки формы

  • Говоря о том, что сделать его более безопасным! Прямо сейчас кто-то может потенциально отправлять необоснованные POST-запросы, вставлять свой собственный код на страницу или делать DoS-сайт со многими быстро повторяющимися запросами; добавить надежную проверку входных данных и ограничение базовой скорости

  • Добавьте лучшую обработку ошибок; Что делать, если вызов API занимает слишком много времени или по какой-то причине дает сбой?

  • Добавьте результаты в движок text-to-speech, научитесь изменять шаблоны высоты тона, используя другую марковскую модель, и установите ритм; скоро ты будешь на вершине чартов!