曲の推論アプリ作ってみた

別サイトに投稿した記事シリーズです。
# FastAPIで推論APIを作ってみた: Spotifyの楽曲特徴量を活用した推奨システム構築

## 概要
最近、ハッカソンに参加してFastAPIを使って開発してきました！
その中で、「ML API（機械学習の推論API）はどうやって作るのだろう？」という疑問が浮かびました。
そこで、FastAPIを使用して推論アプリを作成することに挑戦してみました。

## このプロジェクトのゴール
この記事のゴールは、SpotifyAPIを使って楽曲の特徴量を取得し、それらをデータベースに保存して、最終的に機械学習モデル（みたなもの）で推論を行うAPIを作成することです。手順はとてもシンプルなので、ML APIに興味がある方はぜひ試してみてください。

## プロジェクトのコード
実際に作成したコードはGitHubに公開していますので、興味のある方は以下のリンクからご覧ください。
https://github.com/HikaruAkashi7/music_recommend


## 処理の流れ
処理の流れは以下の通りです。

1. SpotifyAPIを使用して、楽曲の特徴量を取得する。
2. 取得したデータをデータベースに保存する。
3. データベースに保存された楽曲データを元に推論を行うAPIを構築する。

以下、各ステップの詳細について説明します。

---

## 1. SpotifyAPIを使用して、楽曲の特徴量を取得する

### 1.1 SpotifyAPIの認証設定
SpotifyAPIを使用するには、まずSpotifyの開発者アカウントを取得し、クライアントIDとクライアントシークレットを取得する必要があります。この情報を使ってSpotifyAPIに接続し、指定したプレイリストや楽曲の特徴量を取得します。

### 1.2 楽曲の特徴量取得
特徴量の取得には、SpotipyというPythonライブラリを使用しました。例えば、以下の特徴量が取得できます。

- ダンスしやすさ（danceability）
- アコースティック度合い（acousticness）
- エネルギー（energy）
- インストゥルメンタル度合い（instrumentalness）
- ライブ感（liveness）
- 音量（loudness）
- スピーチ度合い（speechiness）
- テンポ（tempo）
- 楽曲の明るさ（valence）

```python
# SpotifyAPIを使って楽曲の特徴量を取得する例
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials

# クライアント認証
sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials(client_id="YOUR_CLIENT_ID", client_secret="YOUR_CLIENT_SECRET"))

# プレイリストから楽曲の特徴量を取得
results = sp.playlist_tracks("プレイリストのID")
tracks = results['items']

# 取得した特徴量を処理
for track in tracks:
    track_info = track['track']
    features = sp.audio_features(track_info['id'])[0]
    print(features)
```

---

## 2. データベースに保存する

### 2.1 データベース設定
楽曲の特徴量を取得したら、それをデータベースに保存します。今回はPostgreSQLを使用しました。FastAPIとPsycopg2を使って簡単に接続できます。

### 2.2 データベースにデータを保存するコード
以下の例では、取得した特徴量を`tracks_features`というテーブルに挿入しています。

```python
import psycopg2

# データベースに接続
def connect_to_db():
    conn = psycopg2.connect(
        host="localhost",
        database="your_db",
        user="your_user",
        password="your_password"
    )
    return conn

# データを保存する関数
def save_to_db(data):
    conn = connect_to_db()
    cursor = conn.cursor()
    cursor.execute(
        """
        INSERT INTO tracks_features (track_id, danceability, energy, valence, ...)
        VALUES (%s, %s, %s, %s, ...)
        """,
        (data['id'], data['danceability'], data['energy'], data['valence'], ...)
    )
    conn.commit()
    cursor.close()
    conn.close()
```

---

## 3. 実際に推論してみる

### 3.1 推論APIの設計
次に、保存したデータを使って推論を行います。今回はシンプルな距離計算（ユークリッド距離など）で推奨楽曲を選んでいますが、将来的にはより高度なモデルに置き換えることも考えられます。推論APIは、ユーザーの回答をもとに最適な楽曲を選択する役割を担っています。

### 3.2 推論アルゴリズムの詳細

推論APIの中核を担うアルゴリズムは、ユーザーの音楽に関する嗜好とデータベースに保存された楽曲の特徴量との間の類似性を計算し、その類似度が最も高い楽曲を推奨するものです。このアルゴリズムは以下の手順で動作します。

#### 3.2.1 ユーザーの回答に基づくスコアリング
まず、ユーザーが回答した音楽の嗜好を数値スコアに変換します。この変換には、以下のような重みを設定します：

- **はい**: 1
- **どちらかといえばはい**: 0.5
- **どちらともいえない**: 0
- **どちらかといえばいいえ**: -0.5
- **いいえ**: -1

このスコアリングによって、ユーザーの嗜好を数値として扱い、楽曲の特徴量との比較が可能になります。

```python
# ユーザーの回答に基づく重み設定
weights = {
    'はい': 1,
    'どちらかといえばはい': 0.5,
    'どちらともいえない': 0,
    'どちらかといえばいいえ': -0.5,
    'いいえ': -1
}

# ユーザーの回答をスコアリングに変換
user_preference = {feature: weights[user_response[feature]] for feature in user_response}
```

#### 3.2.2 データの前処理とスケーリング
次に、Spotifyから取得した楽曲データをスケーリング（MinMaxScaler）し、標準化します。また、データに欠損値がある場合、SimpleImputerを使用してそれらを補完します。このステップにより、異なるスケールの特徴量を均等に比較することができるようになります。

```python
# 特徴量のスケーリングと欠損値の補完
imputer = SimpleImputer(strategy='constant', fill_value=0)
scaler = MinMaxScaler()
features_array = [
    [
        track['popularity'], track['key'], track['mode'], track['danceability'], track['acousticness'], 
        track['energy'], track['instrumentalness'], track['liveness'], track['loudness'], track['speechiness'], 
        track['tempo'], track['time_signature'], track['valence'], track['duration_ms'] if track['duration_ms'] else 0
    ]
    for track in tracks_features
]

# 補完とスケーリング
features_array = imputer.fit_transform(features_array)
scaled_features = scaler.fit_transform(features_array)
```

#### 3.2.3 コサイン類似度を用いた推論
ユーザーの嗜好と楽曲の特徴量の類似度を計算するために、コサイン類似度を使用します。コサイン類似度は、ベクトル間の角度を元に類似性を測る手法で、値が1に近いほど類似していることを示します。計算には以下の公式を使用します：

$$
\text{Cosine Similarity} = \frac{\text{A} \cdot \text{B}}{\|\text{A}\| \|\text{B}\|}
$$


ここで、Aはユーザーの嗜好をベクトル化したもの、Bは楽曲の特徴量です。この計算により、ユーザーの嗜好に最も近い曲を選び出します。

```python
def calculate_cosine_similarity(track_features, preference_values):
    cos_sim = dot(track_features, preference_values) / (norm(track_features) * norm(preference_values))
    if math.isnan(cos_sim) or math.isinf(cos_sim):
        return 0.0
    return cos_sim
```

#### 3.2.4 推論結果のサニタイズ
推論結果にNaN（非数）やInfinity（無限大）の値が含まれる場合、これらを処理し適切な値に置き換えます。これにより、最終的な推奨結果が信頼性を保つようにしています。

```python
def sanitize_recommendation(recommendation):
    sanitized = {}
    for key, value in recommendation.items():
        if isinstance(value, float) and (math.isnan(value) or math.isinf(value)):
            sanitized[key] = 0.0
        else:
            sanitized[key] = value
    return sanitized
```

#### 3.2.5 最終的な推奨曲の選択
各楽曲のスコアが計算された後、最もスコアが高い曲を推奨します。この結果は、ユーザーの嗜好と楽曲の特徴量が最も一致していることを示します。

```python
# スコアが最も高い曲を選択
best_match = max(tracks_features, key=lambda x: x['score'])
return best_match
```

---

## まとめ
今回のプロジェクトを通して、FastAPIを使ったML APIの構築がとてもシンプルであることを実感しました。コサイン類似度を用いた推論アルゴリズムにより、ユーザーの音楽の嗜好に最もマッチする楽曲を効果的に推奨することができました。今後はさらに高度な機械学習モデルを導入して、推論の精度を高めることも検討しています。機械学習に必要なデータの取り扱いや、APIとしての提供方法に興味のある方には、ぜひ一度試してみていただきたいです。