最近Web小説が人気でいろいろ書籍化されたりアニメ化したりしています
今期のアニメでは『ダンジョンに出会いを求めるのは間違っているだろうか』が放送されていておすすめです(アニメに合わせてKindle版の1,2巻が値下げされています)
ダンジョンに出会いを求めるのは間違っているだろうか (GA文庫)
- 作者: 大森藤ノ,ヤスダスズヒト
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2013/01/16
- メディア: 文庫
- 購入: 1人 クリック: 50回
- この商品を含むブログ (32件) を見る
ダンジョンに出会いを求めるのは間違っているだろうか外伝 ソード・オラトリア (GA文庫)
- 作者: 大森藤ノ,はいむらきよたか,ヤスダスズヒト
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2014/01/15
- メディア: 文庫
- この商品を含むブログ (5件) を見る
sucrose.hatenablog.com
pixivの小説ランキングは男性向けを探すのは難しいのが既知の問題として知られています[要出典]
一応男女別人気ランキングとして「男子に人気」と「女子に人気」のランキングがありますが、分け方がてきとーです
男子に人気ランキングを見ればわかりますが男子に人気のトップ10がすべて刀剣乱舞なのでたぶんだいたい腐向けです
ちなみにトップ50のうち刀剣乱舞以外のものは7件しかありませんでした
ランキングの小説が男性向けかどうかを自動で判定できないか、機械学習を使って簡単に試して遊んでみました
なお「男性向け」「女性向け」という言葉をゆるふわに使ってますがご容赦ください
データの収集
「男子に人気」と「女子に人気」のランキングを、それぞれ男女のラベル付きの教師データとみなして利用します
なぜか過去のランキングはないのでデータは男女100件ずつしかありません(イラストの方には過去ランキングがあるのに
特徴量
小説の情報のうち使えるのは、本文、タイトル、タグ、キャプション、作者などいろいろとあります。
今回は本文とタグのそれぞれの頻度をTFIDFで重み付けしてベクトルとして学習してみました
結果
残念なことに(?)本文を使うよりもタグを使ったほうが性能がよかったです(メタデータはコンテンツそのものの情報を使うよりも分類に役立つことがよくあります)
タグ
小説ごとにどのタグが含まれていたかのベクトルを作ってSVMで学習しました
データが少なかったのと、めんどくさかったのでクローズドな評価です(訓練データそのものを分類して評価する)
「女子に人気」のランキングの小説は9割ぐらいが正しく判定できていましたが、「男子に人気」ランキングの小説は5,6割ぐらいしか正解できませんでした
「男子に人気」ランキングの分類が多く失敗しているのは、「男子に人気」ランキングにも女性向け作品が多いという観察結果と合致しています。
この結果はまあまあ女性向け作品を分けることができていると考えるべきでしょう
分類への影響が大きかった、重みの絶対値の大きなタグを確認すると特徴的な作品やキャラのタグをちゃんと学習できているように見えます(というか刀剣乱舞強すぎです!)
男性向け | 女性向け |
---|---|
やはり俺の青春ラブコメはまちがっている | 刀剣乱腐 |
ラブライブ | 刀剣乱舞小説100users入り |
比企谷八幡 | 刀剣乱腐小説100users入り |
naruto | 腐向け |
サスサク | 女審神者 |
ナルヒナ | ブラック本丸 |
雪ノ下雪乃 | 刀剣乱夢 |
東條希 | 刀剣乱舞 |
艦これ | とうらぶちゃんねる |
naruto小説50users入り | 混合小説100users入り |
ちなみにタグの文字n-gramを使って学習したら「100users」の部分文字列や「腐」などの重みの絶対値が大きくておもしろかったです
pixivの小説は女性ユーザーが多いので、上の重みの絶対値の大きなタグみてもわかるように「100users」などの人数が大きなタグがついているだけで女性の確率が高くなるという現象があります
教師データが意図した通りの基準で分けられているかってのは難しい問題です
男女で分けたかったのに、人気の大小の判定機ができたりするかもしれません
まとめ
そこそこ男性向けと女性向けに分類できました(作品で判別しているだけなのでオリジナルでは難しそう
本当はセリフ内かどうかを考慮したり長さとか品詞ごとの頻度とかを入れたり特徴ベクトルを工夫したほうがよいと思います
男子に人気の分類結果が女子向けと判定されるのが多めだったので自分の人力で男子に人気ランキングを分類してみました
刀剣乱舞などを除くと100作品中4,50件ぐらいしか男性向けと思われるものはありませんでした
結論としては、男性向けのWeb小説を読みたい人はpixivだと難しいので、オリジナルなら小説家になろうや二次創作ならハーメルンで読みましょう()
おまけ - 各小説サイトごとのイメージ
以下主観で特徴を列挙しました
- 小説家になろう
- 最大の(?)Web小説サイトで基本オリジナル系。ファンタジー、チート、成り上がり、ハーレム、勘違い系などいろいろと独特の特徴があります。
- pixiv 小説
- イラスト投稿サイトとして有名なpixivの小説版。主に女性ユーザーが多くランキングは腐女子向けの二次創作小説がほとんど。
- ハーメルン
- 二次創作小説投稿サイト。主に男性向け(?)
- Arcadia SS投稿掲示板
- 昔からあるサイトです。オリジナルと二次創作どっちもありますが、最近は投稿数が少なめ
ソースコード
タグで適当に分類する版(sklearnとかpyqueryなどのライブラリを使っています)
# -*- coding: utf-8 -*- import re import time from pyquery import PyQuery import codecs from sklearn.feature_extraction.text import CountVectorizer from sklearn.feature_extraction.text import TfidfTransformer from sklearn.svm import LinearSVC from sklearn.cross_validation import cross_val_score from sklearn.metrics import confusion_matrix from sklearn.metrics import classification_report import numpy as np def read_tag(url): pattern_id = re.compile(r'id=(\d+)') q = PyQuery(url) tags = [] for elem in q.find('.ranking-item'): tags.append(PyQuery(elem).find('.tags a[class!=tag-icon]').text()) time.sleep(1) return tags pattern_id = re.compile(r'id=(\d+)') #データの取得 female_tag = [] female_tag += read_tag('http://www.pixiv.net/novel/ranking.php?mode=female&p=1') female_tag += read_tag('http://www.pixiv.net/novel/ranking.php?mode=female&p=2') male_tag = [] male_tag += read_tag('http://www.pixiv.net/novel/ranking.php?mode=male&p=1') male_tag += read_tag('http://www.pixiv.net/novel/ranking.php?mode=male&p=2') #特徴ベクトル化 vectorizer = CountVectorizer(min_df=3, ngram_range=(1, 1)) data = vectorizer.fit_transform(female_tag + male_tag) tfidf = TfidfTransformer() data = tfidf.fit_transform(data) print u'パラメータを変えながら適当にクロスバリデーション(精度)' for i in xrange(-10, 6): model = LinearSVC(C = 4**i, loss='l1') scores = cross_val_score(model, data, [1] * len(female_tag) + [-1] * len(male_tag), cv = 20) print i, scores.mean() model = LinearSVC(C = 4**(-5), loss='l1') model.fit(data, [1] * len(female_tag) + [-1] * len(male_tag)) print u'男性向け女性向けそれぞれの重みの大きな特徴量' print ', '.join(np.array(vectorizer.get_feature_names())[np.argsort(model.coef_[0])[:10]]) print ', '.join(np.array(vectorizer.get_feature_names())[np.argsort(model.coef_[0])[-10:][::-1]]) print print 'confusion_matrix' print confusion_matrix([1] * len(female_tag) + [-1] * len(male_tag), model.predict(data)) print 'classification_report' print classification_report([1] * len(female_tag) + [-1] * len(male_tag), model.predict(data)) print print u'訓練データ自体を予測した結果' print model.predict(data) print u'ルーキーランキングを予測した結果' print model.decision_function(vectorizer.transform(read_tag('http://www.pixiv.net/novel/ranking.php?mode=rookie')))