エロマーケットプレースの研究シリーズ 5: LightGBM で人気コンテンツの特徴を調べる (完）

# 前回

https://qiita.com/c1z/items/90b3eb9c34df8ad25755

MeCab にエロ単語を登録するところまでやった

# 今回は

（前提として、このシリーズで、分析しているエロマーケットプレイスは　**女性向けエロ音声** に特化したものとなっている）

さて前々前回クロールしたアイテムのデータで LightGBM を使って、どういうタイトルや説明文、タグがついたものが多く再生されるのかという問いに答えていきたい

# 早速コードを書こう

```python
import numpy as np
import pandas as pd
import MeCab
import unicodedata
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer
import subprocess
import os
from sklearn.model_selection import train_test_split
import lightgbm as lgb
import matplotlib.pyplot as plt
import re

# Mecab の準備
dicdir = subprocess.run(['mecab-config', '--dicdir'], capture_output=True).stdout.decode('utf-8').strip()
neologd_dir = os.path.join(dicdir, 'mecab-ipadic-neologd')
mecab = MeCab.Tagger(f'-Owakati -d {neologd_dir}')

# クロールしてきた情報を読み込む
df = pd.read_csv('output.csv')

# 180 秒未満の音声はサイト上、検索されないので、除外
df = df[df['duration'] >= 180]

# 説明欄に載ってる Twitter ID を削除
df['description'] = df['description'].apply(lambda description: re.sub(r'@\w+', '', description))

# タイトルの長さと、説明文の長さを features に入れる
df['title_length'] = df['title'].apply(len)
df['description_length'] = df['description'].apply(len)

# MeCab でタイトルと、タグをわかち書き
df['tokenized_title'] = df['title'].fillna('').apply(lambda title: mecab.parse(unicodedata.normalize('NFKC', title)).strip())
df['tokenized_description'] = df['description'].fillna('').apply(lambda description: mecab.parse(unicodedata.normalize('NFKC', description)).strip())

# タグは CountVectorizer で扱いやすいようにスペース区切りにする
df['tokenized_tags'] = df['tags'].fillna('').apply(lambda tags: ' '.join(tags.split(',')))

# エロ音声のカテゴリ
genre_id_map = {
        <サイト特定につながるので省略>
        }

# ジャンルは数字にする
df['genre_id'] = df['genre'].apply(lambda genre: genre_id_map[genre])

# タイトルをベクトルにする
title_vec = CountVectorizer(max_df=1.0, min_df=1, ngram_range=(1, 1))
title_features = title_vec.fit_transform(df['tokenized_title'])

# 説明文をベクトルにする
description_vec = CountVectorizer(max_df=1.0, min_df=1, ngram_range=(1, 1))
description_features = description_vec.fit_transform(df['tokenized_description'])

# タグをベクトルにする
tags_vec = CountVectorizer(max_df=1.0, min_df=1, ngram_range=(1, 1))
tags_features = tags_vec.fit_transform(df['tokenized_tags'])

# 全部のベクトルをガッチャンこして、特徴ベクトルを作る
X = np.c_[
        title_features.toarray(),
        description_features.toarray(),
        tags_features.toarray(),
        df['title_length'].values,
        df['description_length'].values,
        df['duration'].values,
        df['genre_id'].values,
        ]

# 再生数を結果とする
y = df['play_count'].values

# テストデータと学習データに分割
X_train, X_test, y_train, y_eval = train_test_split(X, y, shuffle=True, random_state=3)

# データセットの準備
lgb_train = lgb.Dataset(X_train, y_train, free_raw_data=False)
lgb_test = lgb.Dataset(X_test, y_eval, reference=lgb_train, free_raw_data=False)

# 学習の実行
lgb_results = {}
model = lgb.train({ },
        train_set=lgb_train,
        valid_sets=[lgb_train, lgb_test],
        valid_names=['Train', 'Test'],
        evals_result=lgb_results,
        num_boost_round=1000,
        early_stopping_rounds=20)

# feature_importance で特徴量ランキングを表示する
pd.set_option('display.max_rows', 500)
print(pd.DataFrame(
    model.feature_importance(importance_type='gain'),
    index=np.r_[
        'title:' + title_vec.get_feature_names_out(),
        'description:' + description_vec.get_feature_names_out(),
        'tags:' + tags_vec.get_feature_names_out(),
        ['title_length'],
        ['description_length'],
        ['duration'],
        ['genre_id'],
    ]
    ).sort_values(by=0, ascending=False)[0:200])

# 学習曲線を表示
loss_train = lgb_results['Train']['l2']
loss_test = lgb_results['Test']['l2']
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.set_xlabel('Iteration')
ax1.set_ylabel('logloss')
ax1.plot(loss_train, label='train loss')
ax1.plot(loss_test, label='test loss')
plt.legend()
plt.show()
```

# 実行すると以下のような結果が得られた


```
duration              1.047812e+10
description_length    8.591571e+09
title_length          8.569055e+09
tags:クリ責め             6.155094e+09
tags:言葉責め             4.015460e+09
tags:中出し              3.160956e+09
tags:イケボ              2.796235e+09
tags:ドs               2.596815e+09
tags:オホ声              2.595525e+09
tags:オナサポ             1.811506e+09
tags:オナ指示             1.310070e+09
description:twitter   1.269246e+09
tags:asmr             1.245078e+09
genre_id              1.084064e+09
description:です        9.994625e+08
description:いいね       9.283243e+08
title:クリ              8.415287e+08
tags:くちゅ音             7.376458e+08
description:まし        6.099216e+08
tags:クンニ              5.851214e+08
description:反応        5.634147e+08
description:こんばんは     4.954506e+08
description:淫語        4.520743e+08
tags:淫語               4.100873e+08
title:セックス            4.078265e+08
description:たら        4.017410e+08
tags:セックス             3.978365e+08
description:聞い        3.945767e+08
```

なるほど、このエロマーケットプレイスにおける女性向け音声作品では
- 再生時間は再生するかどうかの判断において重要 (duration)
- 説明文は長い方がいい
- タイトルも長めがいい
- 「クリ責め」というタグがついていることが再生数に寄与する
- 「言葉責め」というタグがついていることが再生数に寄与する
- このサイトにおいては投稿カテゴリーは重要じゃない（サイトのデザインの問題だと思う）

ちなみに description:twitter というのがあるが、これは、投稿者がちゃんとファン用の twitter を作っていて、ちゃんと告知をしているというところが再生数に効いているということだろう

ちなみに学習曲線はこんな感じ

![スクリーンショット 2022-05-25 2.35.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2669624/085288db-1705-2993-2d35-06a4f483f775.png)

# 良い知見を得ました

これからの男性は、ちゃんとイケボでクリ責め、言葉責めをすることが求められるということでしょうか。

# このシリーズは終了です

LightGBM は PC がしょぼくてもさくっと分析できて良いですね。いろんなサイトをこの手法で分析して、どのサイトで何をすれば売れるのかを解析することを趣味としていきたい