个性化阅读
专注于IT技术分析

NLP的歌词分析和R的机器学习

本文概述

这是一个由三部分组成的系列教程的一部分, 在该系列教程中, 你将使用R来执行传奇艺术家Prince的音乐歌词案例研究中的各种分析任务。这三个教程涵盖以下内容:

  • 第一部分:文本挖掘和探索性分析
  • 第二部分:使用NLP进行情感分析和主题建模
  • 第三部分:使用机器学习进行预测分析
nlp R

介绍

音乐歌词可能代表了艺术家的观点, 但流行歌曲却揭示了社会想要听到的东西。歌词分析并非易事。由于它的结构通常与散文不同, 因此在假设和分析技术的独特区分选择上需要谨慎。音乐歌词渗透到我们的生活中, 并以微妙的普遍性影响我们的思想。预测歌词的概念开始流行起来, 并且作为研究论文和研究生论文的主题越来越普遍。本案例研究仅涉及此新兴主题的一些内容。

王子:艺术家

为了庆祝Prince留下的鼓舞人心的多样化作品, 你将探索他歌词中有时显而易见但往往隐藏的消息。但是, 你不必喜欢Prince的音乐就可以欣赏他对全球许多流派的发展所产生的影响。 《滚石杂志》将《王子》列为有史以来第18名最佳歌曲作者, 仅次于鲍勃·迪伦, 约翰·列侬, 保罗·西蒙, 乔尼·米切尔和史蒂夫·旺德。随着预测”命中歌曲”的可能性越来越接近现实, 歌词分析正逐渐进入数据科学界。

普林斯(Prince)是个充满音乐的人-一位多产的词曲作者, 吉他, 键盘和鼓的演奏家, 还是放克, 摇滚, 节奏布鲁斯和流行音乐的首席架构师, 尽管他的音乐不拘一格。 -乔恩·帕雷莱斯(纽约时报)

在本系列教程的第1部分中, 你将使用整洁的文本框架在一组歌词上利用文本挖掘技术。整洁的数据集具有特定的结构, 其中每个变量是一列, 每个观察值是一行, 每种类型的观察单位是表格。清理和整理数据集后, 你将在描述Prince歌词的不同方面的同时创建描述性统计数据和探索性可视化效果。

先决条件

本教程系列的第1部分需要对整洁的数据有基本的了解-特别是诸如dplyr进行数据转换, ggplot2进行可视化的程序包, 以及最初来自magrittr程序包的%>%管道运算符。每个教程都将描述你可以用于分析的工具, 但可能不会在每个步骤中都详细介绍。你会注意到, 通常使用%>%运算符组合几个步骤。由于这也是一个案例研究, 因此请务必记住, 你所做的所有推论都是纯粹的观察性结论;因此, 相关性并不意味着因果关系。

提示:关于所用工具的背景, 有两个很好的资源:Garrett Grolemund和Hadley Wickham撰写的R for Data Science。 Julia Silge和David Robinson的《与R一起进行文本挖掘》。

第二部分和第三部分

在第二部分的单独教程中, 你将介绍情感分析和主题建模, 以捕获Prince音乐中的整体情绪和主题以及它们在社会视角中的应用。你将使用情感词典, 评估二进制和类别情感, 绘制随时间变化的趋势, 并查看n-gram和单词关联。你还将使用自然语言处理(NLP)和聚类技术(例如潜在狄利克雷分配(LDA)和K-Means)来梳理歌词中的主题。

在第三部分的另一篇教程中, 你将使用探索性的结果来帮助你全面了解工作, 以帮助预测歌曲发行的十年时间, 更有趣的是, 歌曲是否会根据其歌词进入Billboard排行榜。你将使用决策树(rpart和C50), K最近邻(类)和朴素贝叶斯(e1071)等机器学习工具来生成可以接受文本的分类器。

这三个部分将使用相同的Prince歌词, 发行年份和Billboard图表位置数据集。本研究中的技术也可以应用于许多其他类型的文本。实际上, 标准散文的结果更容易解释, 因为一般来说, 歌词通常是设计成带有间接信息和细微差别的。

总之, 有很多不同的方法来分析歌词。这些教程涵盖了下图中以红色突出显示的内容。请注意, 此图形只是非常”模糊”的图片的高级表示。不, 我不是说图片本身!事实是, 建模和机器学习技术的不同方面现在已经模糊, 并且不一定适合单个框, 如下所示。因此, 在观看图像时戴上一些3D眼镜可能会更有意义!

使用的工具

根据此处显示的图形改编

目标

除了学习和练习新技能外, 本教程还旨在解决有关歌词分析概念的基本问题。最近的研究表明, 流行音乐中的”抒情诗”可能正在下降。一些研究甚至表明, 第一首热门歌曲中使用的单词与美国3年级小学生的阅读水平一致。是否有可能利用文本挖掘, NLP, 机器学习和其他数据科学方法来对此类话题发表见解?是否可以根据歌曲的接受程度来确定对社会有吸引力的主题?仅凭抒情分析就可以预测一首歌是否做得好吗?在本教程的第一篇中, 你将探讨作为探索性练习的Prince音乐的抒情复杂性。

问题

在潜入之前, 请考虑一下你想发现的内容。有什么问题值得关注?你将首先对数据集进行分析。它是什么样子的?有几首歌?歌词的结构如何?需要进行多少清洁和打毛?有什么事实?频率一词是什么, 为什么如此重要?从技术角度来看, 你想了解和准备用于情感分析, NLP和机器学习模型的数据。

音乐长期以来一直是与大众交流的有效方式, 而歌词在传递这种交流中起着重要作用。然而, 研究歌词在[社会]的福祉中所扮演的角色的机会却未被充分利用。 -帕特里夏·福克斯(Patricia Fox)

数据

一种流行的获取数据以进行文本挖掘的方法是使用rvest软件包从网络上抓取内容。我能够从各个站点抓取Billboard Chart信息和Prince歌词, 并将它们加入歌曲标题中。由于标题的命名约定不一致, 因此涉及到一些争执。然后, 我做出主观决定, 删除所有不是原始版本的歌曲, 即重新混音, 扩展版本, 俱乐部混音, 翻拍等。为了避免重复, 我还删除了包含他的热门歌曲的历史专辑的专辑。我做了一些小小的清理, 并将结果保存到一个可用于本教程的csv文件中。

由于第一部分专注于文本挖掘, 因此我没有在本教程中包含该代码, 但是如果你想继续学习, 可以在此处下载该数据集。

加载库

#most of the libraries needed
library(dplyr) #data manipulation
library(ggplot2) #visualizations
library(gridExtra) #viewing multiple plots together
library(tidytext) #text mining
library(wordcloud2) #creative visualizations

读入数据

有几种方法可以从csv文件中读取数据, 但是我选择使用read.csv()来加载带有歌词, 发行年份和Billboard图表位置的数据框。请注意, 默认情况下, R将所有字符串转换为因子。这可能会导致下游问题, 但是你可以通过将stringsAsFactors参数设置为FALSE来解决。现在来检查数据…

prince_orig <- read.csv("prince_raw_data.csv", stringsAsFactors = FALSE)

你可以使用names()函数查看数据框中的列:

names(prince_orig)
##  [1] "X"            "text"         "artist"       "song"        
##  [5] "year"         "album"        "Release.Date" "US.Pop"      
##  [9] "US.R.B"       "CA"           "UK"           "IR"          
## [13] "NL"           "DE"           "AT"           "FR"          
## [17] "JP"           "AU"           "NZ"           "peak"

因为我创建了此文件, 所以我知道X只是行号, 而文本是实际的歌词。其他需要的字段包括歌曲, 年份和峰值(在公告牌图表上显示其位置)。 US.Pop和US.R.B是美国的峰值图表位置(Pop和R&B图表), 因此也请保持在该位置, 并暂时删除所有其他字段。

为此, 请获取原始数据集prince_orig, 然后使用%>%将其通过管道传递到select()中。这样, 你可以从左到右读取代码。

另外, 请注意select()允许你一步重命名所有列。因此, 继续将文本设置为歌词, 然后使用” _”而不是”。”将US列重命名为tidyverse样式。然后将结果存储在prince中, 你将在整个教程中使用该结果。 dplyr提供了一个名为glimpse()的函数, 使你可以轻松地在转置视图中查看数据。

prince <- prince_orig %>% 
  select(lyrics = text, song, year, album, peak, us_pop = US.Pop, us_rnb = US.R.B)

glimpse(prince[139, ])
Observations: 1
Variables: 7
$ lyrics <chr> "I just can't believe all the things people say, controversy\nAm I ...
$ song   <chr> "controversy"
$ year   <int> 1981
$ album  <chr> "Controversy"
$ peak   <int> 3
$ us_pop <chr> "70"
$ us_rnb <chr> "3"

第一个明显的问题是, 有多少个观测值和列?

dim(prince)
[1] 824   7

使用dim()函数, 你看到有7列和824个观察值。每个观察都是一首歌。就像我说的…多产!

查看其中一首歌曲的歌词列, 你可以看到它们的结构。

str(prince[139, ]$lyrics, nchar.max = 300)
 chr "I just can't believe all the things people say, controversy\nAm I Black or White? Am I straight or gay? Controversy\nDo I believe in God? Do I believe in me? Controversy\nControversy, controversy\nI can't understand human curiosity, controversy\nWas it good for you? Was I what you w"| __truncated__

有很多清理工作的机会, 所以让我们开始吧。

数据调理

基本清洁

你可以使用多种方法来调节数据。一种选择是使用tm文本挖掘程序包将数据帧转换为语料库和文档术语矩阵, 然后使用tm_map()函数进行清理, 但是本教程现在将坚持基础知识并使用gsub()和apply()函数来完成肮脏的工作。

首先, 通过使用gsub()创建一个处理大多数情况的小函数来消除那些令人讨厌的收缩, 然后将该函数应用于所有歌词。

# function to expand contractions in an English-language source
fix.contractions <- function(doc) {
  # "won't" is a special case as it does not expand to "wo not"
  doc <- gsub("won't", "will not", doc)
  doc <- gsub("can't", "can not", doc)
  doc <- gsub("n't", " not", doc)
  doc <- gsub("'ll", " will", doc)
  doc <- gsub("'re", " are", doc)
  doc <- gsub("'ve", " have", doc)
  doc <- gsub("'m", " am", doc)
  doc <- gsub("'d", " would", doc)
  # 's could be 'is' or could be possessive: it has no expansion
  doc <- gsub("'s", "", doc)
  return(doc)
}

# fix (expand) contractions
prince$lyrics <- sapply(prince$lyrics, fix.contractions)

你还会注意到使文本混乱的特殊字符。你可以使用gsub()函数和简单的正则表达式删除它们。请注意, 执行此步骤之前扩大收缩至关重要!

# function to remove special characters
removeSpecialChars <- function(x) gsub("[^a-zA-Z0-9 ]", " ", x)
# remove special characters
prince$lyrics <- sapply(prince$lyrics, removeSpecialChars)

为了保持一致, 请继续使用方便的tolower()函数将所有内容转换为小写。

# convert everything to lower case
prince$lyrics <- sapply(prince$lyrics, tolower)

现在检查歌词会显示原始原始文本的漂亮, 清晰的版本。

str(prince[139, ]$lyrics, nchar.max = 300)
chr "i just can not believe all the things people say  controversy am i black or white

调节数据以进行文本挖掘的另一个常见步骤称为词干提取, 或将单词分解为词根含义。这本身就是一个主题, 以后可以讨论。现在, 看一下王子数据帧的摘要。

#get facts about the full dataset
summary(prince)
    lyrics              song                year         album          
 Length:824         Length:824         Min.   :1978   Length:824        
 Class :character   Class :character   1st Qu.:1989   Class :character  
 Mode  :character   Mode  :character   Median :1996   Mode  :character  
                                       Mean   :1995                     
                                       3rd Qu.:1999                     
                                       Max.   :2015                     
                                       NA's   :495                      
      peak          us_pop             us_rnb         
 Min.   : 0.00   Length:824         Length:824        
 1st Qu.: 2.00   Class :character   Class :character  
 Median : 7.00   Mode  :character   Mode  :character  
 Mean   :15.48                                        
 3rd Qu.:19.00                                        
 Max.   :88.00                                        
 NA's   :751

如你所见, 有37年的歌曲, 而排行榜最低的歌曲(存在于数据集中)位于第88位。你还可以看到, 年份和峰值都有很多NA。由于你将要进行不同类型的分析, 因此将整个数据集保留在王子数据框中, 并在需要时进行过滤。

添加一些字段

由于你的目标问题之一是跨时间查找歌曲趋势, 并且数据集包含各个发行年份, 因此你可以创建存储段并将这些年份分组为几十年。使用dplyr的mutate()动词创建新的October字段。创建存储桶的一种方法是利用ifelse()和%in%运算符按年份进行过滤并将歌曲分类为数十年。然后将结果存储回王子(本质上是添加一个新字段)。

#create the decade column
prince <- prince %>%
  mutate(decade = 
           ifelse(prince$year %in% 1978:1979, "1970s", ifelse(prince$year %in% 1980:1989, "1980s", ifelse(prince$year %in% 1990:1999, "1990s", ifelse(prince$year %in% 2000:2009, "2000s", ifelse(prince$year %in% 2010:2015, "2010s", "NA"))))))

你可以对chart_level执行相同的操作, 它表示一首歌曲是否在前10名, 前100名中达到最高峰, 或未达到榜首(即未知)。这些是互斥的, 因此前100名不包括前10首歌曲。

#create the chart level column
prince <- prince %>%
  mutate(chart_level = 
           ifelse(prince$peak %in% 1:10, "Top 10", ifelse(prince$peak %in% 11:100, "Top 100", "Uncharted")))

另外, 创建一个称为charted的二进制字段, 指示一首歌曲是否达到Billboard图表(即流行歌曲)。使用write.csv()保存以供以后的教程使用。

#create binary field called charted showing if a song hit the charts at all
prince <- prince %>%
  mutate(charted = 
           ifelse(prince$peak %in% 1:100, "Charted", "Uncharted"))

#save the new dataset to .csv for use in later tutorials
write.csv(prince, file = "prince_new.csv")

描述性统计

为了自定义图形, 我喜欢创建唯一的颜色列表以保持视觉效果的一致性。网上有很多地方可以通过如下所示的十六进制代码表示来获得不同的颜色。如果你对图形有偏好, 还可以创建自己的主题, 并在需要时将其应用于ggplot()。

#define some colors to use throughout
my_colors <- c("#E69F00", "#56B4E9", "#009E73", "#CC79A7", "#D55E00")

theme_lyrics <- function() 
{
  theme(plot.title = element_text(hjust = 0.5), axis.text.x = element_blank(), axis.ticks = element_blank(), panel.grid.major = element_blank(), panel.grid.minor = element_blank(), legend.position = "none")
}

在进行文本挖掘之前, 首先要对歌曲中的数据内容有一个基本的了解。现在将是一个很好的时机, 以了解Prince每十年发行了多少首歌曲。提醒一下, 他的职业生涯始于1978年, 一直持续到2015年(如上面的summary()统计数据所示)。但是, 由于我们现在正在查看趋势, 并且数据集中的年份有大量空白值, 因此你需要过滤掉第一张图的所有发布年份NA。

歌曲统计

使用dplyr的filter(), group_by()和summarise()函数, 你可以按十年分组, 然后计算歌曲数。函数n()是可用于将summarise()用于分组数据的几个聚合函数之一。然后使用ggplot()和geom_bar()创建条形图, 并用图表类别填充条形图。

prince %>%
  filter(decade != "NA") %>%
  group_by(decade, charted) %>%
  summarise(number_of_songs = n()) %>%
  ggplot() + 
  geom_bar(aes(x = decade, y = number_of_songs, fill = charted), stat = "identity")  +
  theme(plot.title = element_text(hjust = 0.5), legend.title = element_blank(), panel.grid.minor = element_blank()) +
  ggtitle("Released Songs") +
  labs(x = NULL, y = "Song Count")
NLP的歌词分析和R的机器学习3

这清楚地表明了他最活跃的十年是1990年代。

现在, 使用chart_level创建一个相似的图形。

请记住同时使用group_by()十年和chart_level, 以便你可以看到趋势。

在此图中, 你将只查看图表中的歌曲, 因此使用peak> 0过滤掉所有其他内容。使用n()将group_by对象通过sum()计算出歌曲的数量。将其存储在变量中时, 可以将其通过管道传递给ggplot()以获得简单的条形图。

charted_songs_over_time <- prince %>%
  filter(peak > 0) %>%
  group_by(decade, chart_level) %>%
  summarise(number_of_songs = n())

charted_songs_over_time %>% 
  ggplot() + 
  geom_bar(aes(x = decade, y = number_of_songs, fill = chart_level), stat = "identity") +
  theme(plot.title = element_text(hjust = 0.5), legend.title = element_blank(), panel.grid.minor = element_blank()) +
  labs(x = NULL, y = "Song Count") +
  ggtitle("Charted Songs")
NLP的歌词分析和R的机器学习4

见解

请注意, 在Prince的所有录制歌曲中, 多数歌曲进入了前10名。但是更有趣的是, 他创作新歌最多的十年是1990年代, 而更受欢迎的唱片出现在1980年代。为什么会这样呢?当你浏览有关文本挖掘的各个部分时, 请记住此问题。

为了将整个数据集的功能用于歌词分析, 你可以删除对图表级别和发行年份的引用, 并挖掘更多的歌曲。看一看:

#look at the full data set at your disposal
prince %>%
  group_by(decade, chart_level) %>%
  summarise(number_of_songs = n()) %>%
  ggplot() +
  geom_bar(aes(x = decade, y = number_of_songs, fill = chart_level), stat = "identity")  +
  theme(plot.title = element_text(hjust = 0.5), legend.title = element_blank(), panel.grid.minor = element_blank()) +
  labs(x = NULL, y = "Song Count") +
  ggtitle("All Songs in Data")
NLP的歌词分析和R的机器学习5

如你所见, Prince编写了数百首歌曲, 数据中没有发布日期。对于情感分析或探索性分析, 你可以使用所有数据, 但对于随时间变化的趋势, 你可以使用的数据集要少得多。可以-只需记住一点即可。

第一首歌!

对于那些狂热的Prince粉丝, 下面是这些歌曲在排行榜上排名第一的歌曲的快速浏览。 (注意, 你可以使用knitr和kableExtra包中的kable()和kable_styling()以及formattable中的color_tile()来创建格式良好的HTML输出。)

library(knitr) # for dynamic reporting
library(kableExtra) # create a nicely formated HTML table
library(formattable) # for the color_tile function
prince %>%
  filter(peak == "1") %>%
  select(year, song, peak) %>%
  arrange(year) %>%
  mutate(year = color_tile("lightblue", "lightgreen")(year)) %>%
  mutate(peak = color_tile("lightgreen", "lightgreen")(peak)) %>%
  kable("html", escape = FALSE, align = "c", caption = "Prince's No. 1 Songs") %>%
  kable_styling(bootstrap_options = 
                  c("striped", "condensed", "bordered"), full_width = FALSE)
Prince’s No. 1 Songs
song
1979 我想成为你的爱人 1
1984 色情城市 1
1984 紫雨 1
1984 当鸽子哭泣 1
1985 一日游世界 1
1986 1
1988 性爱 1
1989 舞蹈 1
1990 庙里的小偷 1
1991 钻石和珍珠 1
1995 世界上最美丽的女孩 1
2006 3121 1
2007 行星地球 1

文字挖掘

文本挖掘也可以被认为是文本分析。目的是发现可能未知或埋藏在明显事物之下的相关信息。自然语言处理(NLP)是一种用于挖掘文本的方法。它试图通过标记化, 聚类, 提取实体和单词关系, 并使用算法来识别主题并量化主观信息, 从而破译书面语言中的歧义。首先, 你将分解词汇复杂性的概念。

词汇复杂性在不同的上下文中可能意味着不同的事情, 但是目前, 假定可以通过以下这些措施的组合来描述:

  • 词频:每首歌曲的词数
  • 单词长度:文本中单个单词的平均长度
  • 词汇多样性:文本(歌曲词汇)中使用的唯一单词的数量
  • 词汇密度:唯一单词数除以单词总数(重复单词)

整洁的文字格式

要开始分析, 你需要将歌词分解成单个词并开始挖掘见解。此过程称为令牌化。

数据格式和令牌化

请记住, 可以使用多种方法和数据格式来挖掘文本:

  • 语料库:由tm文本挖掘程序包创建的文档的集合
  • 文档术语矩阵:按文档列出语料库中所有单词出现的矩阵, 其中文档为行, 单词为列
  • 整洁的文本:每行一个令牌的表。在本案例研究中, 令牌将是一个单词(或第二部分将讨论的n-gram)。因此, 令牌化是将歌词拆分为令牌的过程。本教程将使用tidytext的unnest_tokens()进行此操作。有关更多详细信息, 请参见tidytext文档。

但是在标记任何内容之前, 还需要清理数据的另一步骤。转录时, 许多歌词都包含诸如” Repeat Chorus”(重复合唱)之类的短语, 或诸如” Bridge”和” Verse”之类的标签。还有许多其他令人讨厌的词可能会使结果混乱。在进行了一些先前的分析之后, 我选择了一些我想摆脱的方法。

以下是需要手动删除的多余单词的列表:

undesirable_words <- c("prince", "chorus", "repeat", "lyrics", "theres", "bridge", "fe0f", "yeah", "baby", "alright", "wanna", "gonna", "chorus", "verse", "whoa", "gotta", "make", "miscellaneous", "2", "4", "ooh", "uurh", "pheromone", "poompoom", "3121", "matic", " ai ", " ca ", " la ", "hey", " na ", " da ", " uh ", " tin ", "  ll", "transcription", "repeats")

要取消标记的嵌套, 请使用已加载的tidytext库。现在, 你可以利用dplyr的功能并将多个步骤结合在一起。

在整洁的文本框架中, 你需要将文本分解成单独的标记(令牌化)并将其转换为整洁的数据结构。为此, 请使用tidytext的unnest_tokens()函数。 unnest_tokens()至少需要两个参数:将在文本未嵌套到其中时创建的输出列名称(在这种情况下为” word”), 以及保存当前文本(即歌词)的输入列。

你可以获取王子数据集并将其通过管道传输到unnest_tokens(), 然后删除停用词。什么是停用词?你对它们都很了解。它们是太普通的词, 可能对我们的结果没有任何意义。有不同的列表可供选择, 但是在这里, 你将使用tidytext包中的词汇stop_words。

使用sample()显示这些停用词的随机列表, 使用head()限制为15个单词。

head(sample(stop_words$word, 15), 15)
 [1] "where"      "sensible"   "except"     "wouldn't"   "normally"   "relatively"
 [7] "has"        "said"       "yet"        "how"        "this"       "available" 
[13] "therein"    "different"  "followed"

因此, 在将歌词标记为单词之后, 可以使用dplyr的anti_join()删除停用词。接下来, 摆脱之前使用dplyr的filter()动词和%in%运算符定义的不需要的单词。然后, 使用distinct()也消除所有重复的记录。最后, 你可以删除少于四个字符的所有单词。这是另一个主观决定, 但是在歌词中, 这些通常是诸如”是和嘿”之类的感叹词。然后将结果存储到prince_words_filtered中。

注意:prince_words_filtered是prince数据帧的整洁文本版本, 没有1)停用词, 2)不良词和3)1-3个字符词, 以供将来参考。你将在部分(但不是全部)分析中使用它。

#unnest and remove stop, undesirable and short words
prince_words_filtered <- prince %>%
  unnest_tokens(word, lyrics) %>%
  anti_join(stop_words) %>%
  distinct() %>%
  filter(!word %in% undesirable_words) %>%
  filter(nchar(word) > 3)

注意stop_words有一个word列, 并且unnest_tokens()函数创建了一个名为word的新列, 因此anti_join()自动连接到列word上。

现在, 你可以检查新的整洁数据结构的类和维:

class(prince_words_filtered)
[1] "data.frame"
dim(prince_words_filtered)
[1] 36916    10

prince_words_filtered是一个数据帧, 共有36916个字(不是唯一字)和10列。这是一个快照:(我只选择了一个单词, 并限制了它出现在10首歌曲中, 并使用select()按顺序打印出有趣的字段, 并使用knitr再次进行格式化)。这将显示标记化, 未汇总的整洁数据结构。

 prince_words_filtered %>% 
  filter(word == "race") %>%
  select(word, song, year, peak, decade, chart_level, charted) %>%
  arrange() %>%
  top_n(10, song) %>%
  mutate(song = color_tile("lightblue", "lightblue")(song)) %>%
  mutate(word = color_tile("lightgreen", "lightgreen")(word)) %>%
  kable("html", escape = FALSE, align = "c", caption = "Tokenized Format Example") %>%
  kable_styling(bootstrap_options = 
                  c("striped", "condensed", "bordered"), full_width = FALSE)
Tokenized Format Example
song 十年 chart_level 绘制
种族 性爱 1988 1 1980年代 前10名 绘制
种族 我的树 NA NA NA 未知 未知
种族 积极性 1988 NA 1980年代 未知 未知
种族 种族 1994 NA 1990年代 未知 未知
种族 性欲 1981 88 1980年代 前100名 绘制
种族 慢爱 1987 NA 1980年代 未知 未知
种族 我的余生 1999 NA 1990年代 未知 未知
种族 承办单位 NA NA NA 未知 未知
种族 你让我的阳光照耀 NA NA NA 未知 未知
种族 欢迎2老鼠赛跑 NA NA NA 未知 未知

你会看到每一行包含一个单独的单词, 该单词会针对出现在其中的每首歌曲重复播放。

词频

在音乐中, 无论是重复还是稀有, 单个单词的频率都非常重要。两者都会影响整首歌曲本身的记忆力。歌曲作者可能想知道的一个问题是词频与热门歌曲之间是否存在相关性。因此, 现在你想将整齐的格式更进一步, 并获得每首歌曲的单词总数。

要检查Prince歌词中的这种格式, 请创建一个直方图, 以显示在Billboard Charts上每个位置按歌曲分组的单词计数分布。再次取消王子的歌词, 而无需过滤任何单词, 以获取整个歌曲中单词频率的真实计数。再次使用group_by()和summarise()获得计数。然后使用dplyr动词ranging()按计数排序。首先, 查看最高计数, 然后将ggplot()用于直方图。

full_word_count <- prince %>%
  unnest_tokens(word, lyrics) %>%
  group_by(song, chart_level) %>%
  summarise(num_words = n()) %>%
  arrange(desc(num_words)) 

full_word_count[1:10, ] %>%
  ungroup(num_words, song) %>%
  mutate(num_words = color_bar("lightblue")(num_words)) %>%
  mutate(song = color_tile("lightpink", "lightpink")(song)) %>%
  kable("html", escape = FALSE, align = "c", caption = "Songs With Highest Word Count") %>%
  kable_styling(bootstrap_options = 
                  c("striped", "condensed", "bordered"), full_width = FALSE)
Songs With Highest Word Count
song chart_level num_words
强尼 未知 1349
克洛瑞培根皮 未知 1263
推高 未知 1240
外逃开始了 未知 1072
狂野而松散 未知 1031
g头 未知 940
我叫王子 前10名 916
承认我 未知 913
步行 未知 883
紫色混合泳 未知 874
full_word_count %>%
  ggplot() +
    geom_histogram(aes(x = num_words, fill = chart_level )) +
    ylab("Song Count") + 
    xlab("Word Count per Song") +
    ggtitle("Word Count Distribution") +
    theme(plot.title = element_text(hjust = 0.5), legend.title = element_blank(), panel.grid.minor.y = element_blank())
NLP的歌词分析和R的机器学习6

注意它是正确的偏斜。考虑到歌词转录的本质, 我对数据输入错误表示怀疑。因此, 出于好奇, 看看那首800多个单词的前10首歌曲。

full_word_count %>%
  filter(chart_level == 'Top 10' & num_words > 800) %>%
  left_join(prince_orig, by = "song") %>%
  select(Song = song, "Word Count" = num_words, "Peak Position" = peak, "US Pop" = US.Pop, "US R&B" = US.R.B, Canada = CA, Ireland = IR) %>%
  kable("html", escape = FALSE) %>%
  kable_styling(bootstrap_options = c("striped", "condensed", "bordered"))
song 字数 高峰位置 美国流行乐 美国R&B 加拿大 爱尔兰
我叫王子 916 5 36 25 5 5

我进行了一些研究, 发现这首特定的歌曲中有一位客串表演说唱的艺术家。这肯定可以解释!请记住, 该数据集涵盖了全球的Pop和R&B图表, 因此类型和地理位置会极大地影响你的假设。请注意, 美国流行音乐排行榜的排名远低于其他排行榜, 例如在加拿大达到最高峰5。只是要记住一点。

挑战:如果你被启发自己进行音乐分析, 则可能需要查看The Million Song数据集, 该数据集具有将近50, 000个艺术家的50多种功能(例如, 速度, 响度, 可跳舞性等)!在歌词中添加音乐功能可以进行非常全面的分析。

热门词汇

为了对整套歌词中最常用的单词进行简单评估, 可以使用count()和top_n()从干净的, 经过过滤的数据集中获取n个热门单词。然后使用reorder()根据计数对单词进行排序, 并使用dplyr的mutate()动词将有序值重新分配给单词。这使ggplot()可以很好地显示它。

prince_words_filtered %>%
  count(word, sort = TRUE) %>%
  top_n(10) %>%
  ungroup() %>%
  mutate(word = reorder(word, n)) %>%
  ggplot() +
    geom_col(aes(word, n), fill = my_colors[4]) +
    theme(legend.position = "none", plot.title = element_text(hjust = 0.5), panel.grid.major = element_blank()) +
    xlab("") + 
    ylab("Song Count") +
    ggtitle("Most Frequently Used Words in Prince Lyrics") +
    coord_flip()
NLP的歌词分析和R的机器学习7

就像大多数流行音乐一样, 爱情似乎是一个常见的话题。仅凭这些热门词汇我就不会做很多假设, 但是你可以肯定地了解艺术家的观点, 而不仅仅是整个图片。在进行更深入的研究之前, 请花一点时间来获得一些视觉上的乐趣。

词云

词云在许多圈子中都受到不良说唱的影响, 如果你不谨慎使用它们, 可以在不合适的上下文中使用它们。但是, 从本质上讲, 我们都是非常视觉化的生物, 在某些情况下可以获得真正的洞察力。在这里查看Sandy McKee的一些示例。只需将它们与一粒盐一起使用。

但是现在, 让我们来看一个名为wordcloud2的新软件包, 其中包含一些很酷的东西。该软件包为你提供了一个生成html小部件的创意云集。实际上, 你可以将鼠标悬停在一个单词上以查看其出现频率。 (此软件包在rMarkdown中的呈现速度可能很慢, 并且经常对其使用的浏览器有些挑剔。希望会有所改进。)

prince_words_counts <- prince_words_filtered %>%
  count(word, sort = TRUE) 

wordcloud2(prince_words_counts[1:300, ], size = .5)

为了获得更多乐趣, 你可以添加一点数据图…

wordcloud2(prince_words_counts[1:300, ], figPath = "guitar_icon.png", color = "random-dark", size = 1.5)

letterCloud(prince_words_counts[1:300, ], word = "PRINCE", size = 2)
NLP的歌词分析和R的机器学习8
NLP的歌词分析和R的机器学习9

流行语

到目前为止, 你已经查看了所有歌曲中的热门单词。如果按图表级别将它们分解会怎样?在到达排行榜的歌曲中, 某些词是否比未解释的歌曲更普遍?这些被社会视为流行词。

注意下面的代码中使用slice(seq_len(n))来获取每个chart_level中的前n个单词。这与top_n()的工作方式不同, 因此在绘图中使用构面时是一个不错的选择。 (永远记住, 可以使用不同的方法来进行这样的技巧。)你还可以使用row_number()函数来确保可以在图形上以正确的顺序列出单词。 ggplot()默认情况下会按字母顺序对单词进行排序, 这是在绘制图形之前进行排序的好习惯。

popular_words <- prince_words_filtered %>% 
  group_by(chart_level) %>%
  count(word, chart_level, sort = TRUE) %>%
  slice(seq_len(8)) %>%
  ungroup() %>%
  arrange(chart_level, n) %>%
  mutate(row = row_number()) 

popular_words %>%
  ggplot(aes(row, n, fill = chart_level)) +
    geom_col(show.legend = NULL) +
    labs(x = NULL, y = "Song Count") +
    ggtitle("Popular Words by Chart Level") + 
    theme_lyrics() +  
    facet_wrap(~chart_level, scales = "free") +
    scale_x_continuous(  # This handles replacement of row 
      breaks = popular_words$row, # notice need to reuse data frame
      labels = popular_words$word) +
    coord_flip()
NLP的歌词分析和R的机器学习10

你现在从以上收集的见解是什么?

好吧, 整个图表级别的热门词汇非常非常相似。对于我们希望根据歌词预测歌曲是否成功的希望, 这看起来并不好!但是, 你仅触及到了文本挖掘以及最终的NLP和预测建模的可能性。

永恒的话语

音乐中的某些单词被认为是永恒的。永恒的文字随着时间的流逝而持久, 吸引了众多观众。如果你将你的分析分解成几十年, 那么这些话将排在首位。使用过滤, 分组和聚合从Prince的歌词中获取每十年的热门单词, 并查看哪些单词可以被认为是永恒或时髦的。你可以将ggplot()与facet_wrap()结合使用以按十年查看。

timeless_words <- prince_words_filtered %>% 
  filter(decade != 'NA') %>%
  group_by(decade) %>%
  count(word, decade, sort = TRUE) %>%
  slice(seq_len(8)) %>%
  ungroup() %>%
  arrange(decade, n) %>%
  mutate(row = row_number()) 

timeless_words %>%
  ggplot(aes(row, n, fill = decade)) +
    geom_col(show.legend = NULL) +
    labs(x = NULL, y = "Song Count") +
    ggtitle("Timeless Words") + 
    theme_lyrics() +  
    facet_wrap(~decade, scales = "free", ncol = 5) +
    scale_x_continuous(  # This handles replacement of row 
      breaks = timeless_words$row, # notice need to reuse data frame
      labels = timeless_words$word) +
    coord_flip()
NLP的歌词分析和R的机器学习11

显然, 爱, 时间和女孩是永恒的。但是发现时髦单词有多容易呢?真相在世纪之交流行吗?难道仅仅是某些歌曲中的重复性很强?频率实际上确定主题吗?这对歌词的作用方式是否与其他文本挖掘任务(例如分析国情咨文演讲)相同?

字长

词长是词曲创作者一个有趣的话题。单词越长, 押韵和挤成图案的难度就越大。如下所示, 此字长直方图与你期望的一样, 但有一些异常长的例外。

#unnest and remove undesirable words, but leave in stop and short words
prince_word_lengths <- prince %>%
  unnest_tokens(word, lyrics) %>%
  group_by(song, decade) %>%
  distinct() %>%
  filter(!word %in% undesirable_words) %>%
  mutate(word_length = nchar(word)) 

prince_word_lengths %>%
  count(word_length, sort = TRUE) %>%
  ggplot(aes(word_length), binwidth = 10) + 
    geom_histogram(aes(fill = ..count..), breaks = seq(1, 25, by = 2), show.legend = FALSE) + 
    xlab("Word Length") + 
    ylab("Word Count") +
    ggtitle("Word Length Distribution") +
    theme(plot.title = element_text(hjust = 0.5), panel.grid.minor = element_blank())
NLP的歌词分析和R的机器学习12

那些疯狂的长话是什么?我认为这需要一个完全有趣的词云!这是基于字长而不是字频。看看这个:

wc <- prince_word_lengths %>%
  ungroup() %>%
  select(word, word_length) %>%
  distinct() %>%
  arrange(desc(word_length))

wordcloud2(wc[1:300, ], size = .15, minSize = .0005, ellipticity = .3, rotateRatio = 1, fontWeight = "bold")

词汇多样性

文本所具有的词汇越多样化, 其词汇多样性就越高。歌曲词汇表表示一首歌曲中使用了多少个独特单词。可以用多年来每首歌曲的平均唯一单词的简单图表来显示。你将要再次标记原始数据集, 但请保留所有停用词和简短词, 因为你将更多地关注此图中的定量洞察力。


lex_diversity_per_year <- prince %>%
  filter(decade != "NA") %>%
  unnest_tokens(word, lyrics) %>%
  group_by(song, year) %>%
  summarise(lex_diversity = n_distinct(word)) %>%
  arrange(desc(lex_diversity)) 

diversity_plot <- lex_diversity_per_year %>%
  ggplot(aes(year, lex_diversity)) +
    geom_point(color = my_colors[3], alpha = .4, size = 4, position = "jitter") + 
    stat_smooth(color = "black", se = FALSE, method = "lm") +
    geom_smooth(aes(x = year, y = lex_diversity), se = FALSE, color = "blue", lwd = 2) +
    ggtitle("Lexical Diversity") +
    xlab("") + 
    ylab("") +
    scale_color_manual(values = my_colors) +
    theme_classic() + 
    theme_lyrics()

diversity_plot
NLP的歌词分析和R的机器学习13

这是什么意思?在过去的几十年中, Prince的歌词多样性有轻微上升的趋势。这与图表成功有何关系?确实很难说, 但是继续绘制密度和图表历史记录以进行进一步比较。

词汇密度

回想一下, 在本教程中, 词汇密度定义为唯一单词数除以单词总数。这是单词重复的指标, 它是词曲作者的重要工具。随着词汇密度的增加, 重复次数减少。 (注意:这并不意味着顺序重复, 这是另一种写歌技巧。)

看看这些年来王子的词汇密度。对于密度, 最好保留所有单词, 包括停用词。因此, 请从原始数据集开始, 然后嵌套单词。按歌曲和年份分组, 然后使用n_distinct()和n()计算密度。使用geom_smooth()将其通过管道传输到ggplot()中。使用方法=” lm”为线性平滑模型添加一个附加的stat_smooth()。

lex_density_per_year <- prince %>%
  filter(decade != "NA") %>%
  unnest_tokens(word, lyrics) %>%
  group_by(song, year) %>%
  summarise(lex_density = n_distinct(word)/n()) %>%
  arrange(desc(lex_density))

density_plot <- lex_density_per_year %>%
  ggplot(aes(year, lex_density)) + 
    geom_point(color = my_colors[4], alpha = .4, size = 4, position = "jitter") + 
    stat_smooth(color = "black", se = FALSE, method = "lm") +
    geom_smooth(aes(x = year, y = lex_density), se = FALSE, color = "blue", lwd = 2) +
    ggtitle("Lexical Density") + 
    xlab("") + 
    ylab("") +
    scale_color_manual(values = my_colors) +
    theme_classic() + 
    theme_lyrics()

density_plot
NLP的歌词分析和R的机器学习14

为了比较趋势, 请为他的图表历史记录创建一个图表(即达到图表的成功歌曲), 然后将其与多样性和密度进行比较。使用gridExtra中的grid.arrange()可以并排绘制它们。

chart_history <- prince %>%
  filter(peak > 0) %>%
  group_by(year, chart_level) %>%
  summarise(number_of_songs = n()) %>%
  ggplot(aes(year, number_of_songs)) + 
    geom_point(color = my_colors[5], alpha = .4, size = 4, position = "jitter") +
    geom_smooth(aes(x = year, y = number_of_songs), se = FALSE, method = "lm", color = "black" ) +
    geom_smooth(aes(x = year, y = number_of_songs), se = FALSE, color = "blue", lwd = 2) +
   ggtitle("Chart History") +
   xlab("") + 
   ylab("") +
   scale_color_manual(values = my_colors) +
   theme_classic() + 
   theme_lyrics()

grid.arrange(diversity_plot, density_plot, chart_history, ncol = 3)
NLP的歌词分析和R的机器学习15

从以上内容可以看出, 多年来Prince的词汇多样性和密度略有增加。与所有流行音乐相比, 这种趋势如何?一项研究表明, 《流行歌曲》的词法密度下降, 表明重复次数增加(即”更多的单词少说了”)。那不是王子的歌发生的事情。另一项研究表明, 在所有流行音乐中, 多样性(唯一词)随着时间的推移而增加, 这与王子的音乐成正相关。这可能与历史上图表排名中更大的体裁多样性有关。体裁是此数据集中不存在的关键数据。可能是因为流行音乐他的音乐排行榜历史减少, 而R&B / Rap的音乐排行榜历史增加了吗?

挑战:我将让你考虑这些结果, 甚至鼓励你寻找可以自己利用这些技能的不同数据集!请记住, 关联并不意味着因果关系。

特遣部队

到目前为止, 你一直在使用该方法查看整个数据集, 但尚未解决如何量化文档中各个术语对整个集合的重要性。你已经查看了词频, 并删除了停用词, 但这可能不是最复杂的方法。

输入TF-IDF。 TF是”期限频率”。 IDF是”反文档频率”, 它为常用单词赋予较低的权重, 而对于在文本集合中不常用的单词赋予较高的权重。当你将TF和IDF结合使用时, 术语的重要性会根据使用的频率进行调整。 TF-IDF背后的假设是, 除非在许多文档中也出现, 否则应给在文档中更频繁出现的术语赋予更高的权重。该公式可以总结如下:

  • 术语频率(TF):术语在文档中出现的次数
  • 文件频率(DF):包含每个单词的文件数量
  • 反文档频率(IDF)= 1 / DF
  • TF-IDF = TF * IDF

因此, 对于在集合中较少的文档中出现的单词, 任何术语的IDF都较高。你可以使用tidytext提供的bind_tf_idf()函数, 使用这种新方法来检查每个图表级别最重要的单词。此函数计算并绑定TF和IDF, 以及TF * IDF乘积。它需要一个整齐的文本数据集作为输入, 每个标记(单词), 每个文档(歌曲)一行。你将在新列中看到结果。

因此, 将原始的Prince数据帧和不必要的标记带到单词上, 删除不需要的单词, 但保留停用词。然后, 使用bind_tf_idf()运行公式并创建新列。

 popular_tfidf_words <- prince %>%
  unnest_tokens(word, lyrics) %>%
  distinct() %>%
  filter(!word %in% undesirable_words) %>%
  filter(nchar(word) > 3) %>%
  count(chart_level, word, sort = TRUE) %>%
  ungroup() %>%
  bind_tf_idf(word, chart_level, n)

head(popular_tfidf_words)
## # A tibble: 6 x 6
##   chart_level  word     n          tf   idf tf_idf
##         <chr> <chr> <int>       <dbl> <dbl>  <dbl>
## 1   Uncharted  that   622 0.010942420     0      0
## 2   Uncharted  your   529 0.009306335     0      0
## 3   Uncharted  what   496 0.008725789     0      0
## 4   Uncharted  will   481 0.008461904     0      0
## 5   Uncharted  this   464 0.008162834     0      0
## 6   Uncharted  know   441 0.007758211     0      0

现在, 你可以看到IDF和TF-IDF对于非常普通的单词为0。 (从技术上讲, IDF项将是1的自然对数, 因此对于这些单词来说将是零。)将你的结果从上方通过管道传送到ranging()中, 然后以tf-idf递减。再添加一些步骤, 你将看到查看Prince歌词中单词的另一种方式。

top_popular_tfidf_words <- popular_tfidf_words %>%
  arrange(desc(tf_idf)) %>%
  mutate(word = factor(word, levels = rev(unique(word)))) %>%
  group_by(chart_level) %>% 
  slice(seq_len(8)) %>%
  ungroup() %>%
  arrange(chart_level, tf_idf) %>%
  mutate(row = row_number())

top_popular_tfidf_words %>%
  ggplot(aes(x = row, tf_idf, fill = chart_level)) +
    geom_col(show.legend = NULL) +
    labs(x = NULL, y = "TF-IDF") + 
    ggtitle("Important Words using TF-IDF by Chart Level") +
    theme_lyrics() +  
    facet_wrap(~chart_level, ncol = 3, scales = "free") +
    scale_x_continuous(  # This handles replacement of row 
      breaks = top_popular_tfidf_words$row, # notice need to reuse data frame
      labels = top_popular_tfidf_words$word) +
    coord_flip()
NLP的歌词分析和R的机器学习16

哇, 使用TF-IDF无疑使我们对潜在的重要单词有了不同的看法。当然, 这种解释纯粹是主观的。注意到任何模式吗?

接下来, 看一下跨时间的TF-IDF:

tfidf_words_decade <- prince %>%
  unnest_tokens(word, lyrics) %>%
  distinct() %>%
  filter(!word %in% undesirable_words & decade != 'NA') %>%
  filter(nchar(word) > 3) %>%
  count(decade, word, sort = TRUE) %>%
  ungroup() %>%
  bind_tf_idf(word, decade, n) %>%
  arrange(desc(tf_idf))

top_tfidf_words_decade <- tfidf_words_decade %>% 
  group_by(decade) %>% 
  slice(seq_len(8)) %>%
  ungroup() %>%
  arrange(decade, tf_idf) %>%
  mutate(row = row_number())

top_tfidf_words_decade %>%
  ggplot(aes(x = row, tf_idf, fill = decade)) +
    geom_col(show.legend = NULL) +
    labs(x = NULL, y = "TF-IDF") + 
    ggtitle("Important Words using TF-IDF by Decade") +
    theme_lyrics() +  
    facet_wrap(~decade, ncol = 3, nrow = 2, scales = "free") +
    scale_x_continuous(  # this handles replacement of row 
      breaks = top_tfidf_words_decade$row, # notice need to reuse data frame
      labels = top_tfidf_words_decade$word) +
    coord_flip()
NLP的歌词分析和R的机器学习17

你现在肯定会获得更深入的洞察力。永恒的词不再盛行。你是否开始看到主题或主题出现?每个小组只说几句话真的有可能吗?在第二篇教程中, 你将带到一个新的高度(第二部分:使用NLP进行情感分析和主题建模)。

使用此方法的快速wordcloud显示了Prince歌词中重要单词的新视角, 并且更加有趣…

wc <- tfidf_words_decade %>%
  arrange(desc(tf_idf)) %>%
  select(word, tf_idf)

wordcloud2(wc[1:300, ], color = "random-dark", minRotation = -pi / 6, maxRotation = -pi / 3, minSize = .002, ellipticity = .3, rotateRatio = 1, size = .2, fontWeight = "bold", gridSize = 1.5 )

总结

在本案例研究中, 你首先仅看一下基础知识就了解了实际数据。在执行了一些条件(例如数据清理和删除无用的单词)之后, 你就开始了歌曲级别的探索性分析。

接下来, 你通过将歌词取消嵌套到标记化的单词中来更深入地研究文本挖掘, 以便你可以查看歌词的复杂性。结果为情感分析和主题建模的下一步提供了重要的见解。

最后, 你使用TF-IDF分析来表示文档中某个单词所涉及的感兴趣结果背后的信息。你可能会认为这是识别音乐主题的好方法, 但实际上只有故事的一半。第二部分介绍了一种称为Latent Dirichlet Allocation(LDA)的无监督学习方法。与数据科学的所有方面一样, 有很多方法可供选择以获取洞察力。在本案例研究的第二部分和第三部分中, 你将介绍更多这些选项。

希望你和我一样兴奋, 继续从探索性分析到情感分析, 主题建模和预测见解的旅程。

感谢你的阅读, 希望在下一个教程中见到你!

赞(0) 打赏
未经允许不得转载:srcmini » NLP的歌词分析和R的机器学习
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!

 

觉得文章有用就打赏一下文章作者

微信扫一扫打赏