Работа, проделанная в рамках этого проекта, была для ключевого проекта Udacity Data Scientist Nanodegree. Цель состояла в том, чтобы продемонстрировать свое понимание процессов науки о данных с помощью интересующего меня проекта. Этот проект собирается классифицировать песни по категориям, таким как рок или хип-хоп, используя предопределенные функции, созданные исследовательской группой The Echo Nest. Я выбрал этот проект не только потому, что мне нравится музыка, но и из-за растущего применения обработки сигналов на основе машинного обучения в других отраслях (например, в производстве, автомобилестроении и т. д.), которые я нахожу очень похожими на процессы текущего проекта. Обычно описательные функции получаются из сигналов и могут использоваться для кластеризации или классификации записей для автоматизации инженерных процессов.

Постановка задачи

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

Метрики

Чтобы измерить, насколько хорошо наша модель предсказывает классы, разумно рассчитать точность, полноту и оценку F-1.

Точность = (TP)/(TP+FP) Когда использовать: Точность — хороший выбор в наших показателях, когда мы хотим быть очень уверенными в нашем прогнозе.

Отзыв = (TP)/(TP+FN) Когда использовать: Отзыв — хороший выбор в наших показателях, когда мы хотим зафиксировать как можно больше положительных результатов.

Оценка F1 = 2 x (точность x полнота)/(точность+отзыв) когда использовать: Оценка F1 является хорошим выбором, если мы хотим выбрать модель с высокой точностью и высокой полнотой.

Кроме того, я использовал кривые ROC (рабочие характеристики приемника)-AUC (площадь под кривой) для анализа производительности моделей. ROC представляет собой кривую вероятности, а AUC представляет собой степень или меру разделимости. Он говорит, насколько модель способна различать классы.

Исследование и визуализация данных

Два файла, которые используются в этом проекте, содержат метаданные трека и производные значения, созданные исследовательской группой The Echo Nest. Первый находится в формате файла csv, а второй — в формате json. После чтения файлов мы можем изучить их столбцы и значения, распечатав только первые несколько строк, а также создав описательную статистику.

В файле метаданных дорожки колонка жанра_top указывает тип песни; будь то рок или хип-хоп. Помимо этого можно найти информацию, например. о композиторе, дате записи и продолжительности трека.

В файле echonest мы можем проверить количество и тип подготовленных производных значений. Все функции имеют диапазон от 0 до 1, только темп имеет диапазон значений от 29 до 250, что на два порядка выше.

Объединив два файла через track_id, мы можем присвоить жанру_top трека его производные значения.

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
# Read in track metadata 
tracks = pd.read_csv('datasets/fma-rock-vs-hiphop.csv')
# Read in track metrics 
echonest_metrics = pd.read_json('datasets/echonest-metrics.json',precise_float=True)
# Merge the relevant columns of tracks and echonest_metrics
echo_tracks = echonest_metrics.merge(tracks[['track_id','genre_top']], on = 'track_id')
echo_tracks.describe()

С помощью гистограмм мы можем анализировать форму распределения значений, пики и перекрытие значений двух классов.

Только глядя на графики распределения, невозможно различить значения двух типов песен. Значения имеют широкое перекрытие. Возможно, танцевальность — единственная, у которой искажены значения влево для рока и вправо для хип-хопа. Что касается других особенностей, то оба класса либо имеют два пика, либо очень плоские.

echo_tracks_melt = echo_tracks[['acousticness', 'danceability', 'energy','instrumentalness', 'liveness', 'speechiness', 'valence','genre_top']].melt(id_vars='genre_top')
g = sns.displot(
    data=echo_tracks_melt,
    x='value', 
    hue='genre_top', 
    kind='hist', 
    fill=True,
    col='variable',
    bins=100
)

Функция темпа была визуализирована отдельно из-за измененного диапазона значений.

tempo_distplot = sns.displot(data=echo_tracks, x='tempo', hue='genre_top', kind='hist', height=5, aspect=1.5)

Как правило, следует избегать сильных корреляций между переменными, чтобы упростить модель и упростить ее интерпретацию (со многими функциями мы рискуем переобучиться), кроме того, наше время вычислений может быть значительно ускорено.

Функция Pandas corr () используется для вычисления коэффициента корреляции Пирсона по умолчанию, который исследует линейную зависимость функций. Тепловая карта — это хороший метод визуализации для представления корреляционной матрицы, указывающий — в случае выбранной нами цветовой палитры — высокую положительную корреляцию (в случае, если значения признаков движутся в одном направлении) с более темным красным цветом, в то время как высокую отрицательную корреляцию (в случае признаков значения движутся в противоположном направлении) с более темным синим цветом. Светло-красные и светло-голубые значения показывают низкую корреляцию.

Достаточно показать только часть ниже диагонали матрицы, так как значения зеркальны.

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

# Calculate pairwise-correlation
matrix = echo_tracks.corr()
# Create a mask
mask = np.triu(np.ones_like(matrix, dtype=bool))
# Create a custom diverging palette
cmap = sns.diverging_palette(250, 15, s=75, l=40,
                            n=9, center="light", as_cmap=True)
plt.figure(figsize=(16, 12))
sns.heatmap(matrix, mask=mask, center=0, annot=True,
            fmt='.2f', square=True, cmap=cmap)
plt.show()

Используя функцию countplot Seaborn, можно узнать количество помеченных данных, что является полезной информацией при создании моделей машинного обучения. Мы должны позаботиться о том, чтобы иметь сбалансированные данные для достижения хорошей производительности модели.
Ниже видно, что песни с ярлыком «Рок» встречаются в 4 раза чаще, чем песни с ярлыком «Хип-хоп». Нам нужно подумать о стратегии, чтобы справиться с этой ситуацией.
Но сначала давайте оставим все как есть, не балансируя набор данных, и посмотрим, каковы будут результаты.

ax = sns.countplot(x="genre_top", data=echo_tracks)

Создание обучающих и тестовых наборов

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

# Import train_test_split function 
from sklearn.model_selection import train_test_split
# Create features by dropping track_id and genre_top
features = echo_tracks.drop(columns=['track_id', 'genre_top'], axis=1).values
# Create labels using genre_top
labels = echo_tracks['genre_top'].values
# Split our data into train and test features and labels
train_features, test_features, train_labels, test_labels = train_test_split(features, labels, random_state=0)

Масштабирование данных

Мы не хотим, чтобы одна функция преобладала над другой, создавая предвзятость в нашей модели. Вместо этого мы хотели бы, чтобы наша модель одинаково обрабатывала значения функций, чтобы извлечь из них все важное значение. Чтобы избежать доминирования функций, мы должны нормализовать наши функции обучения и тестирования.
Есть несколько способов добиться этого, но наиболее популярным является стандартизация, при которой все функции имеют среднее значение 0 и стандартное отклонение 1.

Обратите внимание, что мы используем fit_transform() для обучающих функций, а transform() для тестовых данных.

Всем известно, что мы используем функцию transform() для наших тестовых данных и метод fit_transform() для наших обучающих данных. Однако настоящий вопрос заключается в следующем: «Зачем мы это делаем?»

Обучающие данные масштабируются, и их параметры масштабирования изучаются путем применения fit_transform() к обучающим данным. Модель, которую мы создали в этом случае, обнаружит среднее значение и дисперсию характеристик в обучающем наборе. Затем мы масштабируем наши тестовые данные, используя эти новые изученные параметры.

Среднее значение и дисперсия, определенные на основе наших обучающих данных, можно использовать для изменения наших тестовых данных с помощью метода преобразования. Поэтому мы можем преобразовать наши тестовые данные, используя параметры, полученные нашей моделью из обучающего набора.

[ссылка: https://towardsdatascience.com/what-and-why-behind-fit-transform-vs-transform-in-scikit-learn-78f915cf96fe]

# Import the StandardScaler
from sklearn.preprocessing import StandardScaler
# creating s standar scaler object
scaler = StandardScaler()
# Scale train_features and test_features
scaled_train_features = scaler.fit_transform(train_features)
scaled_test_features = scaler.transform(test_features)

Модели классификации зданий

Будут сравниваться три алгоритма машинного обучения:
- Случайный лес,
- Логистическая регрессия,
- Дерево повышения градиента.

Во-первых, объекты модели создаются с фиксированным random_state, чтобы получить детерминированное поведение во время подбора.
Затем модели снабжаются масштабированными функциями поезда и метками поезда.
Наконец, метки прогнозируются на основе масштабированных тестовых признаков.

from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import GradientBoostingClassifier
rf = RandomForestClassifier(random_state=0)
lr = LogisticRegression(random_state=0)
gb = GradientBoostingClassifier(random_state=0)
rf.fit(scaled_train_features,train_labels)
lr.fit(scaled_train_features,train_labels)
gb.fit(scaled_train_features,train_labels)
pred_labels_rf = rf.predict(scaled_test_features)
pred_labels_lr = lr.predict(scaled_test_features)
pred_labels_gb = gb.predict(scaled_test_features)

Сравнить модели

Функция Classification_report используется для анализа производительности моделей. Рассчитываются значения точности, отзыва и F1-оценки, кроме того, строятся кривые ROC_AUC.

Из отчетов о классификации мы видим, что все три модели уже имеют очень высокие значения показателей (более 90% для точности, полноты и F1-показателя) для рок-меток, но они намного ниже для хип-хопа.

Кривые ROC_AUC показывают, что метки Rock модели Random Forest создают наибольшую площадь под кривой ROC, что означает, что она имеет самую высокую производительность. Модель Gradient Boosting Tree немного остается ниже, в то время как модель логистической регрессии имеет самую низкую (но все же неплохую) производительность.

from sklearn.metrics import classification_report, plot_roc_curve
from sklearn import metrics
class_report_rf = classification_report(test_labels, pred_labels_rf)
class_report_lr = classification_report(test_labels, pred_labels_lr)
class_report_gb = classification_report(test_labels, pred_labels_gb)
print("Random Forest: \n", class_report_rf)
print("Logistic Regression: \n", class_report_lr)
print("Gradient Boosting Tree: \n", class_report_gb)

plot_roc_curve(rf, scaled_test_features, test_labels)
plt.title('ROC_AUC curve for Random Forest model')
plt.show()
plot_roc_curve(lr, scaled_test_features, test_labels)
plt.title('ROC_AUC curves for Logistic regression model')
plt.show()
plot_roc_curve(gb, scaled_test_features, test_labels)
plt.title('ROC_AUC curves for Gradient Boosting Tree model')
plt.show()

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

Давайте сначала попробуем сделать случайную передискретизацию на хип-хоп лейблах!

Решение несбалансированного набора данных

Создав RandomOverSampler с помощью библиотеки imblearn, мы можем подогнать наш набор данных, чтобы получить одинаковое число для всех меток классов. После того, как нам снова придется разделить набор данных для обучения и тестирования, наконец, повторив процедуру обучения модели, затем мы предскажем метки из тестового набора, можно будет проанализировать новые результаты.

Удивительно, что модель Random Forest может почти идеально идентифицировать рок и хип-хоп песни, о чем также доказывают отличные кривые ROC! Дерево повышения градиента также стало очень хорошим, и даже модель логистической регрессии имеет очень приличную одинаково хорошую производительность для обеих меток.

Можем ли мы достичь еще большей производительности? Другим решением является использование методов сокращения признаков, таких как PCA. Давайте посмотрим, сможем ли мы улучшить даже нашу модель логистической регрессии.

from imblearn.over_sampling import RandomOverSampler
oversample = RandomOverSampler(sampling_strategy='minority')
features_over, labels_over = oversample.fit_resample(features, labels)
train_features, test_features, train_labels, test_labels = train_test_split(features_over, labels_over)
scaled_train_features = scaler.fit_transform(train_features)
scaled_test_features = scaler.transform(test_features)
rf = RandomForestClassifier(random_state=0)
lr = LogisticRegression(random_state=0)
gb = GradientBoostingClassifier(random_state=0)
rf.fit(scaled_train_features,train_labels)
lr.fit(scaled_train_features,train_labels)
gb.fit(scaled_train_features,train_labels)
pred_labels_rf = rf.predict(scaled_test_features)
pred_labels_lr = lr.predict(scaled_test_features)
pred_labels_gb = gb.predict(scaled_test_features)
class_report_rf = classification_report(test_labels, pred_labels_rf)
class_report_lr = classification_report(test_labels, pred_labels_lr)
class_report_gb = classification_report(test_labels, pred_labels_gb)
print("Random Forest: \n", class_report_rf)
print("Logistic Regression: \n", class_report_lr)
print("Gradient Boosting Tree: \n", class_report_gb)

plot_roc_curve(rf, scaled_test_features, test_labels)
plt.title('ROC_AUC curve for Random Forest model')
plt.show()
plot_roc_curve(lr, scaled_test_features, test_labels)
plt.title('ROC_AUC curves for Logistic regression model')
plt.show()
plot_roc_curve(gb, scaled_test_features, test_labels)
plt.title('ROC_AUC curves for Gradient Boosting Tree model')
plt.show()

Анализ главных компонентов

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

Мы получили столько компонентов PCA, сколько функций у нас есть. Из гистограмм видно, что первая составляющая объясняет около 25% различий между музыкальными жанрами, но, к сожалению, мы не видим четкого изгиба, где мы должны определить предел для лучших составляющих.
Таким образом, это полезно. для построения кумулятивных объясненных отклонений, а также с помощью ступенчатого графика. В качестве общей практики можно сказать, что следует брать все компоненты ниже предела, при котором совокупная сумма достигает 85%. Следуя этому правилу, в нашем случае идеальными являются шесть компонентов.

# Import  PCA class
from sklearn.decomposition import PCA
pca = PCA()
pca.fit(scaled_train_features)
explained_variance = pca.explained_variance_ratio_
# cumulative explained variance
cum_explained_variance = np.cumsum(explained_variance)
plt.bar(range(0,len(explained_variance)), explained_variance, alpha=0.5, align='center', label='Individual explained variance')
plt.step(range(0,len(cum_explained_variance)), cum_explained_variance, where='mid',label='Cumulative explained variance')
plt.ylabel('Explained variance ratio')
plt.xlabel('Principal component index')
plt.legend(loc='best')
plt.tight_layout()
plt.show()

Создание новых моделей с использованием функций PCA

Как мы видели ранее, шесть — идеальное число для компонентов, поэтому мы установили n_component=6 в параметре при создании объекта PCA.

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

pca = PCA(n_components=6, random_state=0)
# Fit and transform the scaled training features using pca
train_pca = pca.fit_transform(scaled_train_features)
# Fit and transform the scaled test features using pca
test_pca = pca.transform(scaled_test_features)
rf = RandomForestClassifier(random_state=0)
lr = LogisticRegression(random_state=0)
gb = GradientBoostingClassifier(random_state=0)
rf.fit(train_pca,train_labels)
lr.fit(train_pca,train_labels)
gb.fit(train_pca,train_labels)
pred_labels_rf = rf.predict(test_pca)
pred_labels_lr = lr.predict(test_pca)
pred_labels_gb = gb.predict(test_pca)
class_report_rf = classification_report(test_labels, pred_labels_rf)
class_report_lr = classification_report(test_labels, pred_labels_lr)
class_report_gb = classification_report(test_labels, pred_labels_gb)
print("Random Forest: \n", class_report_rf)
print("Logistic Regression: \n", class_report_lr)
print("Gradient Boosting Tree: \n", class_report_gb)

Краткое содержание

Цель проекта состояла в том, чтобы создать программное решение, которое может отличать жанры песен — например, в данном конкретном случае песни рок и хип-хоп — друг от друга, используя производные функции.

Производные функции (8 функций были созданы для каждой песни) были созданы исследовательской группой The Echo Nest.

Данные признаков были проанализированы путем построения гистограмм, создания корреляционной матрицы и сводной статистики. Счетная диаграмма вектора меток показала, что в наборе данных гораздо больше рок-песен, чем хип-хоп.

Не было обнаружено сильной корреляции между функциями, что полезно для модели машинного обучения.

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

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

Затем для решения этой проблемы была применена балансировка данных, которая оказалась успешной. Вся модель значительно улучшилась, и Random Forest (который и без того был очень хорош) теперь умеет отлично различать рок и хип-хоп песни.

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

Перспектива

Чтобы иметь более достоверные маркированные данные для приложения, мы могли бы попросить клиентов добавить отзыв, действительно ли рекомендуемая песня была той, которую они хотели иметь; например правильно ли система рекомендовала рок-песню пользователям, на которых были нацелены рок-песни.

Подтверждение

Я очень благодарен исследовательской группе The Echo Nest за доступность их набора данных.

Репозиторий на гитхабе

https://github.com/Kristan88/Udacity_DS_nanodegree_capstone.git