Fake Plastic Trees

音楽とシステム,及び機械学習との関わりについてのサーベイや実装など.https://twitter.com/iwa_sheep

Spotify Web APIから収集した楽曲のメル周波数スペクトログラムを用いたジャンル推定と特定次元空間へのマッピング

f:id:tty_tkhs_ml:20191201170609p:plain

マリーゴールドあいみょん)のメル周波数スペクトログラムを可視化した様子

1、概要

 パーソルプロセス&テクノロジー株式会社でデータサイエンティストをやっていますいわしと申します.普段は自然言語処理をメインとしていますが,今回は音声処理(モデルはCNN)について記述します.

*この記事は弊社のQIitaにおけるアドベントカレンダーの1日目の記事として記載しています.是非他の記事もチェックしてみてください.

 

 インターネットの発達と共に,人と音楽の関わり方は大きく変わってきました.今はインターネットを通じて,大量の音楽に瞬時にアクセスすることができます.しかしその結果,自分の好みの楽曲を世界中の楽曲の海から発掘する作業は,レコード屋で盤を掘る作業と比較して極めて労力のいるものとなってしまいました.なので,自分が好きになるかもしれない楽曲を推薦してくれるモデルを作成することにしました.

 この記事では,Spotify APIを用いて楽曲情報を収集し,楽曲のもつ特徴量から楽曲同士の距離を測定し,一方の曲に似ている他方の曲を推薦するという実験の記録として執筆しています.

 

 

2,モチベーション  - 潜在的知識の発見

 例えば,自身と似ているユーザーがよく聞いている曲や,曲のジャンルが似ているもの,○○のフォロワーなどの曲をシステムがレコメンドしてきた時,僕らは大体それらを気にいるかもしれません.ただ往々にしてそういう曲は既に知っているか,好きだけど,今好きな曲の方が好きだから別に聞くまでも無い,みたいなものが多いと個人の体験として感じます.

 なので,「必ず気に入る」という精度の高いレコメンドよりも,従来では候補として出なかった,思いも寄らない曲(潜在的につながっている曲)がレコメンドできたら,面白いのではと考え,今回検証しています.

*2007年に情報処理学会にポストされた「情報の価値化・知識化技術の実現へ向けて : 3.テキストマイニングによる潜在的知識の発見支援」の中で潜在的知識の発見についての歴史や,その課題について述べられているので,ぜひ読んでみてください.

 

3,提案手法

 まず楽曲については,Spotifyが提供する,Spotify Web APIを利用して収集します,

 Spotifyとは,音楽のサブスクリプション型配信サービスです.基本無料で使えますが,課金することにより,利用できる機能が増え,様々な制限が解除されます.

 Spotify Web APIとは,Spotifyで配信している音楽の情報をRestで取得できる仕組みです.そこから取得できる情報は豊富にあり,音楽のタイトル,曲の長さ,テンポなどに始まり,Spotify社が独自に解析しているTrack Feature(躍りやすさ,アコースティックぽさ,など)も収集できます.これらについて詳細は,よく纏まっている下記を参照ください.

 

blog.exploratory.io

 

  そもそもとして,楽曲の推薦手法の歴史は長いです(既存手法の詳細については,本記事はサーベイでは無いので取り扱いません).一般的なよくある推薦手法としては,アイテムベースレコメンドや,協調フィルタリングが挙げられます.

www.slideshare.net

 

  しかし上記手法は適用できるシチュエーションと,適用しにくいシチュエーションがあります.

 例えば内容ベース(アイテムベース)で行う場合は,必ずアイテムの情報(特徴量)が必要です.今回の場合でいうと,曲の情報が必要となります.これがどのようにシチュエーションを限定するかというと,例えば内容ベースの推薦を行う際に,Spotify固有の特徴量を用いる場合,Spotify以外の楽曲の推薦は原理原則できなくなります(厳密にいうと本当にSpotify固有なものだけが欠損するので,不可能では無いが精度は大きく落ちる).

 次に協調フィルタリングでやる場合は,必ずユーザーの情報が必要です.すなわち,新規ユーザーなど,まだあまり曲数を視聴していないユーザーへのレコメンドは,あまり妥当では無い結果が出る可能性があります.

 上記を補うための施策ももちろんありますが,今回はまず内容ベースでの推薦手法を考えます.さらに,Spotifyの特徴量は用いずに,楽曲そのものの波形を用いることにより,Spotifyに登録されていない楽曲でも,楽曲そのもののデータがあればこの手法を適用できるようにします

 

4,データの取得

 まず初めに,ソースコードは全て公開しているので下記に共有します.Plotlyでの作図が正しく表示されるGoogle Colaboratoryでの閲覧を推奨します.また,今回は実際にGoogle Colaboratoryで解析を行ったため,Google Colaboratoryのノートブックをフォークすればそのまま実行できます.

 

colab.research.google.com

 

 

github.com

 

 データの取得について,下記の条件で行いました.

Spotifyの全データの解析は計算資源的に困難であるため,データを特定の条件で絞りました.

・ジャンルを,‘acoustic’, ‘ambient’, 'anime', ‘edm’, ‘hip-hop’, ‘j-dance’, ‘j-idol’, ‘j-pop’, ‘j-rock’, ‘techno’, ‘trip-hop’の11種類に絞ります.

・ターゲットのマーケットプレイスを,日本に限定します.

・各ジャンルから,プレビュー用のmp3が存在するもののみを100曲ずつ収集.すなわち,11ジャンル*100曲 = 1100曲を収集,解析対象とします.

 

 言語はPythonで行いました,SpotipyというSpotify Web APIをラッパーしたライブラリを用いると,極めてシンプルに実装できるのでお勧めです.以下に実装の一部を紹介します.特に難易度の高い実装はありません.構造的に実装できます.

# 全てのジャンルの曲を取得
for genre in tqdm(target_genres):

  # バッチサイズ分取得(一度に大量に取得すると,429のリクエスト過多エラーになるので注意)
  for i in range(itter):
    # 曲情報を.ジャンルでフィルターして取得
    tracks = spotify.search(q="genre:{}".format(genre) ,limit=GET_TRACK_BATCH_NUM, offset=i * GET_TRACK_BATCH_NUM, market='JP')['tracks']

    # 取得した楽曲の数だけループ
    for item in tracks['items']:
      trackId = item['id']
      track = spotify.track(trackId)
      track_features = spotify.audio_features(trackId)[0]

      # プレビューURLが存在しない(楽曲のmp3が取得できない)場合は,再帰的に取得処理を実行
      if track["preview_url"] == None:
        print("\n{} - {}'s preview mp3 file dosen't exist.".format(track["artists"][0]['name'], track["name"]))
        maxoffset += 1
        maxoffset = getTrackDataRecursive(genre, maxoffset)
      else:
        # save track data
        trackData = getTrackData(trackId, track, track_features)
        all_tracks.append(trackData)
        saveTrackData(trackId, trackData)

print('\nget {} tracks'.format(len(all_tracks)))

 

5,曲の"近さ"の測り方

 曲と曲の近さの測り方及びマッピングについては,2種類を比較検証することにしました.

  1. Spotifyの曲特徴量を用いた高次元でのマッピング 
  2. ジャンル推定モデルをCNNで作成し,CNNの最終層の出力を特徴量として高次元でマッピング

 1については すでに試されている方も多い,いわゆるベターなアプローチです.距離のメトリクスにはコサイン類似度を利用します.

 2については,楽曲のプレビュー(mp3)データをメル周波数スペクトログラムに変換し,画像認識のマルチラベルタスクとしてジャンル推定で学習し,その学習したモデルのForward時の最終層の出力を楽曲の特徴量とするものです.

 なんでそんなことをするかについてですが,まず冒頭で述べたように推薦アルゴリズムSpotify依存にしないように,楽曲の音の情報だけで解析をしたかったのが,一番大きな理由です.

 音を機械学習で解析する手法はたくさんありますが,今回は音をメル周波数スペクトログラムに変換し,画像解析のタスクに持ち込む手法を用います.これは後述するFreesound Audio Taggingの上位ソリューションでも用いられている手法です.音をメル周波数スペクトログラムに変換して音自体の特徴量を抽出し,その後周辺情報を畳み込むことによって時間軸や周波数帯の微小な誤差にロバストなモデルを作ろう,という考え方です(そもそも画像タスクの方が研究的に精度が高く出ている傾向があるため,他にも時系列データを画像タスクで解いたりと,いろんなデータを画像タスクに持ち込むことがあります).

pao2.hatenablog.com

 

 ちなみに,このアイディアは,Qosmo社のAI DJ Projectの手法とほぼ同様です(CNNのネットワーク等異なる部分は大きくあると思いますが,アプローチの考え方が同様,という意味です.)

 下記を参考ください.先行研究へのリンクもあるので大変参考になります.

naotokui.net

 

 今回のCNNのネットワークは下記の通りです.こちらはKaggleで開催された,Freesound Audio Tagging 2019コンペティションで,mhiro2さんが公開してくださったカーネルのモデルです.私も実際にコンペティションに参加しメダルを獲得できましたが,このカーネルをベースとして派生させていったので,大変お世話になったものでもあります.

www.kaggle.com

 

計算グラフ

 上の図で言うと,最終層のバッチ正則化層の出力値を用います.

 実装のポイントだけ紹介します.Pytorchにおける最終層(中間層)へのアクセスは,モデル作成時に組み込めば簡単に実現できます.

 

class Classifier(nn.Module):
    def __init__(self, num_classes):
        super().__init__()
        
        self.conv = nn.Sequential(
            ConvBlock(in_channels=3, out_channels=64),
            ConvBlock(in_channels=64, out_channels=128),
            ConvBlock(in_channels=128, out_channels=256),
            ConvBlock(in_channels=256, out_channels=512),
        )
        
        self.fc = nn.Sequential(
            nn.Dropout(0.2),
            nn.Linear(512, 128),
            nn.PReLU(),
            nn.BatchNorm1d(128),
            nn.Dropout(0.1),
        )

        self.linear = nn.Sequential(
            nn.Linear(128, num_classes),
        )

    def forward(self, x):
        x = self.conv(x)
        x = torch.mean(x, dim=3)
        x, _ = torch.max(x, dim=2)
        lastlayer_output = self.fc(x) # 最終層の出力を退避
        output = self.linear(lastlayer_output)
        return lastlayer_output, output

  上記の通り,最後の線形出力だけSequentialから切り離し,バッチ正則化の値をリターンしています.あとはその値をインデックス順に並び替えて保存すれば良いです.

 

6,結果1 - 収集したデータのSimple EDA

1,ジャンルごとのカウント

全てのジャンルでちょうど100曲ずつ収集しています.

genre count
2,ユニークカウント

ジャンルごとのユニークアーティスト

ジャンルごとのユニークなアルバム数

3,ジャンルごとのトラック特徴量の分布

ジャンルごとに,dancebillity(踊れる曲っぽさ)などの分布が異なることがわかります.ただし前述している通り,標本数が確定的に少ないことと,取得できた楽曲に偏りがある(ユニーク数が少なめ)であるため,母集団の分布とは異なる可能性が高いです.あくまで標本分布として捉えています.

acoustic

ambient

anime

EDM

hip-hop

j-dance

j-idol

j-pop

j-rock

techno

trip-hop
4,楽曲のメル周波数スペクトログラムの可視化

 メル周波数スペクトログラムについては下記を参照してください.楽曲のアナログな波形をまずデジタルでサンプリングし,それらをフーリエ変換 -> 対数 -> 逆フーリエ変換したものです.

qiita.com

あいみょん - マリーゴールドのメル周波数スペクトログラムの可視化

 

 

7,結果2 - 特定次元空間へのマッピング

まずはSpotify楽曲特徴量を用いて,t-sneで2次元へ削減して2次元空間へのマッピングを行った結果.

Spotify楽曲特徴量を用いて,2次元空間へのマッピングを行った結果.

 

次に,CNNの最終層を最終出力を特徴量として,同様にt-sneで2次元へ削減して2次元空間へのマッピングを行った結果.

CNNの最終層の出力を用いた場合

 Spotifyの特徴量と比較して,ジャンルごとのまとまりがよくなったような印象.


8,結果3 - 楽曲推薦

 最後に,曲同士を高次元のままコサイン類似度の類似度TOP10を「おすすめ曲」として推薦してみます.

 

 まず初めに,Spotify特徴量を使用して,あいみょんの「マリーゴールド」に似ている曲を探索してみました.なお,特徴量にはジャンルは入れていません*

 今回用いた特徴量は,下記の通りです.キーもあえて入れませんでした.キー(主音)が特徴量に入るのはノイズになると考えたためです(キーが違うけど似ている曲は多くあると考えたためです.)

  • “danceability” - 踊れる曲っぽさ
  • “energy”- エナジーっぽさ
  • “speechiness”- 囁きっぽさ
  • “acousticness”- アコースティックぽさ
  • “instrumentalness”- インストゥルメンタルっぽさ
  • “liveness”- ライブっぽさ
  • “valence”- ポジティブさ
  • loudness_norm”- やかましさを正規化した値(0 ~ 1)
  • “tempo_norm”- テンポを正規化した値(0 ~ 1)

*しかし,ジャンルを入れていないから,ジャンルのリークが無いとは言い切れませんSpotify上のアルゴリズムブラックボックスである以上,今回用いた「dancability」などを算出する際にジャンルの特徴量を使用しているかどうかがわからないためです.

あいみょん - マリーゴールドに似ている曲(Spotify特徴量使用)

 結果を見るとわかるのですが,同一曲でありながら異track_idがあるものが複数散見されます.これは,同一曲でありながら別のアルバム(例:シングルと,アルバムなど)に収録されている場合,track_idが分かれる仕様であるからのようです.今回はあえて排除しなかったが,タスクによっては曲の名寄せが必要になるので注意するべきでしょう.

 なお結果については概ね人間が想像しうる範囲の印象です.データ的にもやはり特徴量は似ていて,かつテンポも似ている,耳障りの良いj-popが集まっているようです.

 

 続いて,CNNを用いたものから同様の問いを投げてみます.

CNNを用いた楽曲の推薦

 思ったより結果が変わったことに,私自身も結構驚きました.

 まず初めに一番面白いなと思ったのは,Spotify特徴量と比較して,女性のアーティストのみになったことです.音の特徴量をCNNにかけているので,女性の声のトーンをCNNが見ているのでは無いか,との印象を受けます.

blog.brainpad.co.jp

 

 次に興味深かったのは,アイドル系が多く出たことです.個人的な意見ばかりで本当の恐縮なのですが,あいみょんを聞く層と,アイドルを聞く層はどれほど被っているのでしょうか.これはまた別の課題としてWebマイニングの共起度から測ってみたいものですが,それほど被っていない場合,あいみょんをよく聞く人に,アイドルの曲をレコメンドするのはなかなか面白いのでは,と思った次第です.

 似ている曲をプレイリストにしました.是非聞いてみてください.

 

8,最後に

今回の検証の課題は下記の通りです.

  • CNNでのアプローチはSpotify特徴量とは結果が異なり,興味深いものがあった.
  • プレビューファイル(mp3)は曲の一部を切り取った30秒ファイルなので,曲の全体では無い.切り取った部分によるバイアスを排除できてい無い
  • 取得した曲数が少ないため,もっと曲数を増やしてより母集団に近似した結果を出す必要がある

次回は今回作成した曲同士のデータをGraphデータに変換し,3D空間に作図してWeb公開しようと思います.

 

以上です.