<jupyter_start><jupyter_text>Сбор датасета<jupyter_code>import os

import matplotlib.pyplot as plt
import matplotlib.style as style
import pandas as pd
import seaborn as sns

from app.artist import ArtistInfo
from app.columns import Emotion, AssignmentColumn
from app.deezer_client import Deezer
from app.ymusic import YMusic
from app.ytoloka import YToloka

%load_ext autoreload

%autoreload 2<jupyter_output>The autoreload extension is already loaded. To reload it, use:
  %reload_ext autoreload<jupyter_text>Описание колонок<jupyter_code>DIR_DATA = 'data/'
FILE_TRACKS = DIR_DATA + 'tracks.csv'

tracks = pd.read_csv(FILE_TRACKS)
tracks.head()<jupyter_output><empty_output><jupyter_text>Список треков можно посмотреть в [моих плейлистах](https://music.yandex.ru/users/nikolay.zakharevich/playlists) на Яндекс.Музыке.Треки сгруппированы по эмоциям: плейлисты называются `emotion-X`, где `X` — эмоция из списка:```comfortablehappyinspirationaljoylonelyfunnynostalgicpassionatequietrelaxedromanticsadnesssoulfulsweetseriousangerwarysurprisefear```Для каждой из эмоций делался поиск по её названию на английском и русском языках.Найденные подборки и пользовательские плейлисты с такими же названиями целиком добавлялись в соответствующий плейлистКолонки, которые берутся из Яндекс.Музыки:* **ym_track_id** — ID трека в ЯМ* **ym_album_id** — ID альбома (нужен для ссылки)* **track_title** — Название трека* **artist_name** — Имя исполнителя* **cover_url** — Ссылка на обложка размером 1000x1000 px* **ym_genre** — Жанр по версии ЯМ* **year** — Год записи* **emotions_maybe** — Эмоции трека (по названию плейлиста)После скачивания добавляются такие колонки:* **audio_path** — Относительный путь к аудиофайлу (mp3-файл с битрейтом 320 kbps)* **lyrics_path** — Относительный путь к тексту* **cover_path** — Относительный путь к обложке (1000x1000 px)Яндекс.Музыка выдаёт только один жанр, а у трека иногда можно выделить несколько.Для этого делается поиск каждого из треков в Deezer, эти жанры тоже сохраняются в отдельную колонку:* **deezer_genres** — Жанры по версии DeezerЧтобы знать распределение треков по странам подключены публичные базы[Musicovery](https://musicovery.com/api/V5/doc/documentation.php),[Jamendo](https://developer.jamendo.com/v3.0/artists/locations) и[Music Story](http://developers.music-story.com/developers/artistc_artists), там ищется исполнитель, егострана добавляется в отдельную колонку* **artist_country** — Страна исполнителя* **artist_sex** — Пол исполнителя* **artist_year** — Год дебюта исполнителяПользователи Яндекс.Музыки, составляя свой плейлист, могли обмануть, либоиметь в виду что-то другое, точно полагаться на эту эмоцию нельзя. Поэтому значения из колонки **emotions_maybe**дополнительно фильтруются в Яндекс.Толоке. Подтверждённые эмоции сохраняются в колонку:* **emotions** — Эмоции трека Загрузка датасетаЧтобы воспользоваться классом для работы с Яндекс.Музыкой [YMusic](https://github.com/NikolayZakharevich/music-processing-cpanel/blob/main/dataset/app/ymusic.py),нужно сгенерировать токен доступа и вставить его в файл окружения `.env`. Можно скопировать [файл с примером](https://github.com/NikolayZakharevich/music-processing-cpanel/blob/main/dataset/.env.example),нужная опция:```TOKEN_YANDEX_MUSIC=```Загрузка основной информации о треках:<jupyter_code>tracks = pd.read_csv(FILE_TRACKS)

ymusic = YMusic()
for emotion in Emotion:
    tracks = ymusic.update_from_emotion_playlists(tracks, emotion)

print(f'Tracks count: {len(tracks)}')
tracks.head(3)<jupyter_output>Tracks count: 6689<jupyter_text>Загрузка жанров из Deezer:<jupyter_code>tracks = pd.read_csv(FILE_TRACKS)

deezer = Deezer()
tracks = deezer.update_genres(tracks)
tracks.head(3)<jupyter_output>Track #0: ym_genre was: rock, deezer_genres found: Rock
Track #1: ym_genre was: rock, deezer_genres found: Pop|Rock
Track #2: ym_genre was: rock, deezer_genres found: Pop|Rock
Track #3: ym_genre was: rock, deezer_genres found: Rock
Track #4: ym_genre was: rock, deezer_genres found: Rock
Track #5: ym_genre was: folkrock, deezer_genres found: Folk
Track #6: ym_genre was: rock, deezer_genres found: Rock
Track #7: ym_genre was: rock, deezer_genres found: Pop|International Pop|Rock
Track #8: ym_genre was: jazz, deezer_genres found: Jazz
Track #9: ym_genre was: soul, deezer_genres found: R&B
Track #10: ym_genre was: rock, deezer_genres found: Rock
Track #11: ym_genre was: foreignbard, deezer_genres found: Rock
Track #12: ym_genre was: rock, deezer_genres found: Rock
Track #13: ym_genre was: rock, deezer_genres found: Alternative|Indie Rock|Rock
Track #14: ym_genre was: allrock, deezer_genres found: Rock
Track #15: ym_genre was: blues, deezer_genres found: Pop
Track #16: ym_genre was: indie, [...]<jupyter_text>Загрузка информации об исполнителях. Для Jamendo нужно создать аккаунт и заполнить эту опцию в `.env````JAMENDO_CLIENT_ID=```У этих API есть лимиты по количеству запросов: в Musicovery считают запросы с одного IP, остальные — запросына акканут. Если упереться в такие лимиты, информация подгружаться не будет, поэтому реализовано кэшированиезапросов<jupyter_code>tracks = pd.read_csv(FILE_TRACKS)

artist_info = ArtistInfo()
tracks = artist_info.update_artists_info(tracks)
tracks.head(3)

tracks.to_csv(FILE_TRACKS, index=False)<jupyter_output>{'the rolling stones': {'artist_name': 'the rolling stones', 'artist_country': 'UK', 'artist_sex': nan, 'artist_year': 0.0, 'requests_cnt': 3}, 'grateful dead': {'artist_name': 'grateful dead', 'artist_country': 'USA', 'artist_sex': nan, 'artist_year': nan, 'requests_cnt': 1}, 'the zombies': {'artist_name': 'the zombies', 'artist_country': nan, 'artist_sex': nan, 'artist_year': nan, 'requests_cnt': 1}, 'creedence clearwater revival': {'artist_name': 'creedence clearwater revival', 'artist_country': 'USA', 'artist_sex': nan, 'artist_year': nan, 'requests_cnt': 1}, 'the cranberries': {'artist_name': 'the cranberries', 'artist_country': 'Argentina', 'artist_sex': nan, 'artist_year': 0.0, 'requests_cnt': 2}, 'simon & garfunkel': {'artist_name': 'sim<NAME>el', 'artist_country': 'USA', 'artist_sex': nan, 'artist_year': nan, 'requests_cnt': 3}, 'fleetwood mac': {'artist_name': 'fleetwood mac', 'artist_country': 'UK', 'artist_sex': nan, 'artist_year': nan, 'requests_cnt': 2}, 'lou reed':[...]<jupyter_text>Разметка по эмоциямСначала нужно создать аккаунт и получить токен для API, его класть также в `.env`, поле:```TOKEN_YANDEX_TOLOKA=```Затем создаётся проект. Описание, инструкция и код для заданий находятся в [соседней папке](https://github.com/NikolayZakharevich/music-processing-cpanel/tree/main/toloka-interface).ID проекта нужно добавить в `.env`:```YANDEX_TOLOKA_PROJECT_ID=```Отбор исполнителей делается с помощью обязательного обучения. Нужно создать пул для обучения,его ID тоже внести в `.env`:```YANDEX_TOLOKA_TRAINING_POOL_ID=```Затем нужно создать основные пулы и загружаем в них задания. Чтобы исполнителям было удобнее,для каждой эмоции создаётся отдельный пул.Это всё уже делается через код, из важных настроек пулов можно отметить:* к заданию допускаются только исполнители, успешно прошедшие обучение (результат — **90%** и выше)* есть правило блокировки: если у пользователя мнение не совпадает с мнениембольшинства на четырех и более вопросах из последних десяти, он лишается права выполнять задания, егоответы не учитываются* на каждой из страниц предусмотрены контрольные вопросы с ответом “Нет”, то есть если всегда выбирать “Да”, также последует блокировка;<jupyter_code>tracks = pd.read_csv(FILE_TRACKS)
def filter_by_emotion(emotion: Emotion):
    return lambda t: emotion.value in t.emotions_maybe.split('|')

FILE_ASSIGNMENTS = DIR_DATA + 'assignments.csv'

if os.path.isfile(FILE_ASSIGNMENTS):
    assignments = pd.read_csv(FILE_ASSIGNMENTS)
else:
    assignments = pd.DataFrame([], columns=[c.value for c in AssignmentColumn])    
    
ytoloka = YToloka()
tracks_unassigned = tracks[~tracks.ym_track_id.isin(assignments.ym_track_id)]

for emotion in Emotion:
    tracks_emotion = tracks_unassigned[tracks_unassigned.apply(filter_by_emotion(emotion), axis=1)]
    ytoloka.upload_tasks(emotion, tracks_emotion)<jupyter_output><empty_output><jupyter_text>После этого нужно запустить пулы (я делал это вручную) и собрать результаты:<jupyter_code>ytoloka = YToloka()
for emotion in Emotion:
    assignments = ytoloka.update_assignments(assignments, emotion)
assignments.to_csv(FILE_ASSIGNMENTS, index=False)
assignments.head()<jupyter_output>Experimental method
Experimental method
Experimental method
Experimental method
Experimental method
Experimental method
Experimental method
Experimental method
Experimental method
Experimental method
Experimental method
Experimental method
Experimental method
Experimental method
Experimental method
Experimental method
Experimental method
Experimental method
Experimental method<jupyter_text>Теперь можно заполнить колонку **emotions**. Туда попадут только эмоции с `rate=1.0`,то есть все выбрали вариант "эмоция указана верно":<jupyter_code>tracks=pd.read_csv(FILE_TRACKS)
print(len(tracks))
tracks = ytoloka.update_emotions(tracks, assignments)
print(len(tracks))
tracks.to_csv(FILE_TRACKS, index=False)
tracks.head()<jupyter_output>8027
8027<jupyter_text>Подготовка данныхМодель для обучения по эмоциям важнее, начнём с неё<jupyter_code>tracks = pd.read_csv(FILE_TRACKS)
tracks_emotions = tracks.dropna(subset=['emotions'])
print(f'Количество треков с метками эмоций: {len(tracks_emotions)}')

tracks_single_emotion = tracks_emotions[tracks_emotions.apply(lambda x: '|' not in x.emotions, axis=1)]
print(f'Количество треков с ровно одной эмоцией: {len(tracks_single_emotion)}')

label_freq = tracks_emotions.emotions.apply(lambda s: str(s).split('|')).explode().value_counts().sort_values(ascending=False)


style.use('fivethirtyeight')
plt.figure(figsize=(12,10))
sns.barplot(y=label_freq.index.values, x=label_freq, order=label_freq.index)
plt.title('Частота эмоций', fontsize=14)
plt.xlabel('')
plt.xticks(fontsize=12)
plt.yticks(fontsize=12)
plt.show()<jupyter_output>Количество треков с метками эмоций: 5537
Количество треков с ровно одной эмоцией: 5274
{'fraction_non_alphanumeric': 0.1045971329708354, 'fraction_numerical': 0.009688581314878892, 'mean_word_length': 7.451127819548872, 'pattern_counts': {'":': 1, '<': 31, '<?xml version=': 0, '>': 31, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '361904', 'license': 'MIT', 'n_tokens_mistral': 4046, 'n_tokens_neox': 4231, 'n_words': 941, 'path': 'cpanel/dataset/Dataset-Collecting.ipynb', 'repo_name': 'NikolayZakharevich/music-processing', 'size': '10121'}