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

Для Spotify не было бы неразумным найти мне плейлист-мэшап Элтона Джона и Билли Джоэла. Почему? Они оба умеют сочинять эпические фортепианные баллады.

Хотя в этой статье я не буду вдаваться в подробности того, как работают такие сервисы, как Spotify (или насколько Элтон и Билли настолько хороши), я буду расскажу, как классифицировать музыку по жанрам с помощью R. Почему? Потому что (1) наука о данных — это круто, (2) музыка еще круче и (3) мне нужно пройти это руководство, чтобы выполнить требования моего курса интеллектуального анализа данных. Ура! Давайте начнем.

Данные

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

Шаг 0: Предварительная обработка

Во-первых, давайте загрузим все необходимые нам библиотеки, а затем загрузим данные и предварительно обработаем их. Давайте обязательно удалим все столбцы, которые не являются полезными функциями, и заменим любые факторы (помимо прогнозируемого фактора «Жанр») на числовые, чтобы упростить ситуацию.

library(dplyr)
library(caTools)
library(caret)
library(class)
library(mlbench)
library(glmnet)
library(nnet)
library(DescTools)
library(rpart)
# Read in the file, which is tab-delimited
data.full <- read.delim(file.path(data.dir, "songDb.tsv"), header=TRUE, sep="\t")
# Remove columns that don't serve as features (the Spotify URI, The track reference, the full URL, etc.)
data <- subset(data.full, select= - c(Uri, Ref_Track, URL_features, Type, ID, Name))
# Identify each song by its name (by changing the row names to song names)
rownames(data) <- make.names(data.full$Name, unique = TRUE)
# Ensure the time signature is numeric, rather than a factor
# Not sure if the best decision, but prevents time_signature var from having too many factor levels
data$time_signature <- as.numeric(data$time_signature)
# Tempo should also be numeric
data$Tempo <- as.numeric(data$Tempo)

Шаг 1. Создайте более управляемое подмножество

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

# Get much smaller subset of genres
genres <- list("canadianpop", "electronica", "rock", "modernblues", "r&b", "polishblackmetal", "videogamemusic", "irishfolk", "koreanpop", "hiphop")
#genres <- droplevels(sample(unique(data$Genre), 10))
data.sub <- data[data$Genre %in% genres,]
data.sub$Genre <- droplevels(data.sub$Genre)

Вау, никогда не знал, что польский блэк-метал так популярен.

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

get_train_test <- function(split_ratio, data) {
  results <- list()
  
  split.index <- sample.split(seq_len(nrow(data)), split_ratio)
  
  results$data.train <- data[split.index, ]
  results$data.test <- data[!split.index, ]
  
  results$X.train <- results$data.train %>% select(-Genre) %>% as.matrix()
  results$Y.train <- results$data.train$Genre
  
  results$X.test <- results$data.test %>% select(-Genre) %>% as.matrix()
  results$Y.test <- results$data.test$Genre
  return(results)
}

Хорошо — теперь мы, наконец, готовы приступить к обучению и тестированию наших моделей.

Шаг 2: классифицируйте все песни в подмножестве

K ближайших соседей

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

# Knn 
set.seed(101)
# Create an empty data frame to store the predictions and the actual labels
classifications <- data.frame(pred=factor(), actual=factor())
# Use K-fold cross validation
K=5
for(k in 1:K) {
  # shuffle the data
  res <- get_train_test(0.8, data.sub)
fit.knn <- knn(train=res$X.train, test=res$X.test, cl=res$Y.train)
  classifications <- rbind(classifications, data.frame(pred=fit.knn,   actual=res$Y.test))
}
confusionMatrix(classifications$pred, classifications$actual)

Ик. Точность 43% хуже, чем случайность.

Что может быть причиной этой проблемы?

Что ж, как вы можете видеть из результатов, несколько жанров более неясны (опять же, польский блэк-метал) и/или имеют лишь небольшое количество песен в данном наборе данных. Следовательно, алгоритм KNN найдет очень мало соседей этих жанров при попытке классифицировать любую заданную точку. Поэтому логично, что точность классификации и другие статистические данные будут низкими, поскольку KNN принимает решение на основе популярности лейбла. Если у нас есть только 5 близлежащих лейблов (жанров) для просмотра, и каждый из них отличается (из-за низкой доли песен в каждом из близлежащих жанров), то, по сути, это жеребьевка для присвоения предсказанного лейбла.

Наивный байесовский метод

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

set.seed(102)
res <- get_train_test(0.8, data.sub)
# Use cross validation
train.control <- trainControl(method="cv", number=5)
# Naive Bayes
fit.nb <- train(x=res$X.train, y=res$Y.train, trControl=train.control, method="nb")
Y.pred.nb <- predict(fit.nb, newdata = res$X.test, type="raw")
confusionMatrix(Y.pred.nb, res$Y.test)

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

Уф! Опять же, это довольно плохая точность. Давайте перейдем к нашему последнему классификатору, прежде чем обсуждать, почему.

Дерева решений

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

# Decision Tree
set.seed(103)
fit.dtree <- train(Genre~., data=res$data.train, method = "rpart", parms = list(split = "information"),trControl=train.control)
Y.pred.dtree <- predict(fit.dtree, newdata=data.frame(res$X.test), type="raw")
confusionMatrix(Y.pred.dtree, res$Y.test)

Опять плохая точность. Теперь давайте обсудим, почему.

Почему плохие результаты?

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

Как мы могли бы сделать лучше?

  • Используйте API Spotify для сбора набора данных с лучшим балансом классов и более популярными жанрами.
  • Масштабировать данные
  • Изучите другие классификаторы
  • Используйте Python (моя предвзятость показывает)

Источники, изобилие

[1] https://stats.stackexchange.com/questions/318968/knn-and-k-folding-in-r

[2] https://stackoverflow.com/questions/50151471/splitting-a-data-frame-into-training-and-testing-sets-in-r

[3] https://stackoverflow.com/questions/17200114/how-to-split-data-into-training-testing-sets-using-sample-function

[4] https://stackoverflow.com/questions/8273313/sample-random-rows-in-dataframe

[5] https://stackoverflow.com/questions/5234117/how-to-drop-columns-by-name-in-a-data-frame

[6] https://stackoverflow.com/questions/33470373/applying-k-fold-cross-validation-model-using-caret-package

[7] https://stats.stackexchange.com/questions/187595/clustering-with-categorical-and-numeric-data

[8] https://cran.r-project.org/web/packages/ggfortify/vignettes/plot_pca.html

[9] https://www.biostars.org/p/62988/

[10] https://medium.com/latinxinai/discovering-descriptive-music-genres-using-k-means-clustering-d19bdea5e443

[11] http://www.sthda.com/english/wiki/ggfortify-extension-to-ggplot2-to-handle-some-popular-packages-r-software-and-data-visualization

[12] https://www.statmethods.net/advstats/cluster.html

[13] https://medium.com/@samshadwell/organizing-spotify-saved-songs-with-machine-learning-eb765446a25d

[14] http://dataaspirant.com/2017/02/03/decision-tree-classifier-implementation-in-r/

[15] http://www.prognoz.com/blog/platform/benefits-of-decision-trees-in-solving-predictive-analytics-problems/

[16] http://uc-r.github.io/naive_bayes