Python
doc2vec

漫画を人に勧めるときために漫画足し算をやってみたかった話(途中経過)

この記事は「ロボP(東海大学チャレンジセンター ユニークプロジェクト) Advent Calendar 2018」の6日目の記事です。

開発途中なのと初心者なので、温かい目で見てくれると幸いです。正直Doc2Vecのアルゴリズムを理解していません

この案はノリで考えたので多めに見てください🙇

あまり、難しい話はしません


ことの発端

はてブを見てた時、「【poke2vec】word2vecをポケモンのパーティに適用してみた」を読んで「これ他にも応用できるのでは?🤔」と思ったのがきっかけ。参考資料

さらに、「AIが見つけた、埋もれたQiita良記事100選」で記事の分類を可能にしていることを知りました。


やりたいことと仮説

ある日、漫画のシュタインズ・ゲート(ループもの)と、東京卍リベンジャーズ(ループもの)を読んだ友人から「シュタインズ・ゲートと東京卍リベンジャーズが面白かったから、それと似たやつ見たい!」って言われたらどうしますか?私なら、漫画で言うならはっぴぃヱンドとオール・ユー・ニード・イズ・キル、アニメで言うならぼくだけがいない街とリゼロから始める異世界生活、ひぐらしのなく頃にを勧めますが、友人が「もう見た」のなら、どうしましょうか?

そこで考えたのが、漫画の足し算です。漫画+漫画=類似の漫画

正直、ループものが好きなのでGoogleで「漫画 ループもの」で検索するのですが、明らかに他のサイトの模倣などがあり悩んでいました。ノイズレスサーチでもいいのですが、ここは思い切って作ってみることにしました。

図1.png

↑最初にアイデアが思いついたころの脳内


考え方

なんとか実現できないかと学習するデータを何にするか考えていました。で思いついたのが、「Amazonの漫画紹介文」。-> Doc2Vecで記事の分類ができるので、「Amazonの漫画の紹介文」も分類できるのではないか?

例えば、シュタインズ・ゲート


発売直後、ネットの口コミで話題となり大ヒットとなった5pb.×ニトロプラスのタッグが贈る想定科学ADV『STEINS;GATE』。

そのメインルートのコミカライズが満を持しての登場!!

漫画を担当するのは「ヒのカグツチ」シリーズでも知られる実力派の新鋭・さらちよみ!

主人公の岡部倫太郎(通称:オカリン)は未だ厨二病から抜け出せない大学生。

「未来ガシェット研究室」というメンバーわずか3人だけのサークルで、ヘンテコ発明品を作る日々を送っている。

そんなある日、偶然にも過去へ電子メールを送れる機械、すなわち「タイムマシン」を発明してしまった!

タイムリープ実験を繰り返すうち、いくつもの要因が重なりあい起こった、全世界を巻き込む大事件!

そして、オカリンたちの選ぶ“未来への選択”とは――!?


ジャンルを決定する単語「タイムマシーン」「タイムリープ」

東京卍リベンジャーズ


『新宿スワン』の和久井健、一世一代の最新作!! 今度は不良が、タイムリープ!? 最愛の人を救うため、ダメフリーター・花垣タケミチが中学時代に戻り、関東最凶不良集団の頂点を目指す!!

花垣タケミチは、中学時代に付き合っていた人生唯一の彼女・橘ヒナタが、悪党連合“東京卍會”に殺されたことをニュースで知る。壁の薄いボロアパートに住み、バイト先では6歳年下の店長からバカ扱い。極めつけはドーテー……。そんなどん底人生まっただ中のある日、12年前の中学時代にタイムリープする!! ヒナタを救うため、逃げ続けた人生を変えるため、ダメフリーター・タケミチが、関東最凶不良軍団の頂点を目指す!!


ジャンルを決定する単語「不良」「タイムリープ」

このように人が見たら、「両者ともSF漫画でループものだ」と分かるはずです。これを機械学習で学習させます。

先に言いますが、Doc2Vecを用いる手法では手ごたえを感じませんでした。


機械学習について

時間がなかったのでDoc2Vecだけですが、他のアルゴリズムの結果を合わせて精度を上げていくのもいいと思います。

機械学習の良い手段として、ASIN(Amazonの商品識別値)とそのASINのジャンルの配列を2つセット(機械学習において正解ラベルといいます)にして、答えが分かった状態で学習させますが、今回は違います。Doc2Vecで正解ラベルのない学習(いわゆる教師無し学習)をします。簡単に言うと、「複数の商品の中で頻出の部分を学習させ、分類を行う」という感じです。「タイムリープ」という単語を使っている漫画は「タイムリープ」グループに属し、「サッカー」は「サッカー」グループに属することを想定しています。

また、Word2Vecアルゴリズムというのもあり、単語の意味の類似を学習してくれます。Doc2VecはWord2Vecを内包しているので、この理屈ならば「サッカー」は「スポーツ」「ラグビー」「野球」。学習させるときは単語と意味をセットにして学習させます。同じスポーツの意味?が含まれているなら近しいと判断します。(また一文字版のChar2Vecもあります。)

もし結果が良くなかったら別の方法として、「まんが王国」などのサイトからジャンルを正解ラベルとして学習させることも考えていました


やったこと

まず、データ集めです。漫画の全収集を目指しました。これが大変なんですわぁ。AmazonのAPIである、Product Advertising API (PA-API)を使い、「コミック」(ブラウズノードに含まれる、漫画のASIN(Amzonで扱われる商品ID)やその他の情報を取得しました。

ブラウズノード名はシュタインズ-ゲートの登録情報の欄にあります。

無題.png

また、PA-APIのBrowseNodeLookupを使えば階層をたどれます。下の画像は前に調べたブラウズノード名とIDです。

20181129_014230.jpg

注:ブラウズノードは新しくできたり削除されたりするのでDBの保存は非推奨されている。

ボーイズラブが隔離されていますね...。コミックスに一緒にすると検索しずらいからでしょうか?

ac96614b384e686bd7d600291d433f07.png

ボーイズラブを探しやすくはなっているのかな?


PA-APIによるリクエスト

ItemSearchを使い、「コミック」ブラウズノードを含む漫画を取得しました。ItemPage:?(1~10)を指定することで最大100冊まで取得できます。(1つのItemPageに10冊の情報)リファレンスにはItempageは400までって書いてあるのに指定するとエラーがでる。リファレンスとは?

ItemSearchで使用したリクエストパラメータ

リクエストパラメータ名
使用した理由

BrowseNode
「コミックス」のブラウズノードを含む本を取得する為

ItemPage
元々指定しなくとも、1回のリクエストで10個の本の情報が返ってくるが、 ImatePage(1-10の値を指定)を使うと最大100個の本を ItemPageを除いた同じリクエストパラメータで取得できる為 (APIリファレンスには「有効な値: 1~400までの整数」って書いてあるのになぁ...)

Power
下で説明

SearchIndex
Booksを指定する本のみ取得でき、さらに検索が速くなるため

Sort
salesrankを指定。売れている順に並び替える為

ResponseGroup
Largeを指定するとタイトルや価格、出版社名、商品のurl、作者、商品説明、関連商品、画像のurl、本の言語

Powerについて

Key
Valueと理由

binding
Booksを指定

during
検索する範囲を指定。最小で月まで。日は出来ない。例during:"11-2005"

language
日本語を指定

title
1巻を取得する為「1」を指定

検索期間がひと月までしか指定できないのが難点でした。 どうしても100冊を超えてしまい、すべての漫画の情報を取得できませんでした。そこで、1巻だけに絞って取得した所、何とか100冊以内に収まる月ができました。どうしても100冊以上になるときがあるので、Sortでランキング上位を取得しています。


サーバやプログラミング

言語はPythonです。Webサーバは常務で一番使われているという理由からDjangoにしました。RDBMSは並列処理するならPostgreSQLですが、その予定はないのでSQLite。Djangoのその他設定もそのままです。DBに保存したらCVS変換して機械学習処理を行います。


PA-APIのリクエスト制限

リクエスト制限より、リクエスト上限は変更されるので、取り合え4秒ごとにリクエストしています。(本当は、累計して効率よくやるべき?)

PythonでWeb APIを扱う時にretryライブラリを使用しました。指定したエラーを受けとったらもう一度繰り返してくれます。**2000年から2017年の1巻のみの漫画を取得しました。1日で4年分取得できたのでそんなに掛かりませんでしたが、最初はサーバがエラーを出してループが多かったのでSlackのAPIでSlackに取得確認とエラーメッセージを投稿して、ログを確認できるようにしていました。

921632964b390733bde1d6a48ed891f2.png

エラーが出たら、Ctrl+Cを押してプログラムを修正してました。修正が大分終わったので1日放置してたら、画像の取得で404エラーをはいて、5分ごとに6時間くらいループしていました。(投稿しすぎて無料枠超え過去投稿がすべて消えましたw。素直にLine APIを使いましょう)


コードのあれこれ


Doc2Vecの為の分かち書き&ノイズ除去(ノーマライズ)

文章の学習がしやすいように、記号や空白を削除したり、「一」の漢数字を「1」に直したり、同じ意味を持たせています。また、分かち書きした単語の中から名詞、動詞、形容詞だけに限定しています。こうすることで「の」や「です」を除くことができます。

def conver_for_doc2vec(con):

con=unicodedata.normalize("NFKC", con)
# 「日本語 名詞,一般,*,*,*,*,日本語,ニホンゴ,ニホンゴ」の形式にする
result = tagger.parseToNode(con)
word_hinshi=["名詞","動詞","形容詞"]
# ▼ ● ( ) ( ), 、 . 。?! / : : 「 」 < >
no_mozis=["▼","●","(",")","(",")","、",",",".","。","?","?","!","!","/",":",":",";",";","「","」","<",">"]
result_content=""
while result:
feature=result.feature.split(",")
for fea in feature:
if fea in word_hinshi:
surface=str(result.surface)
surface=surface.lower()
is_no_mozis=[surface.find(no_mozi) for no_mozi in no_mozis]

if 0 not in is_no_mozis:
result_content+=surface+" "
result = result.next
# 最後の1文字(空白)を削除
result_content=result_content[:-1]
# 1 話を削除
result_content=re.sub("[0-9]+ 話","",result_content)
# 一 話 を削除
result_content=re.sub("[一二三四五六七八九十壱弐参拾百千万萬億兆〇]+ 話","",result_content)
# 1 巻を削除
result_content=re.sub("[0-9]+ 巻","",result_content)
# 一 巻を削除
result_content=re.sub("[一二三四五六七八九十壱弐参拾百千万萬億兆〇]+ 巻","",result_content)

return result_content

Before


ここは木ノ葉隠れの里。忍術学校の問題児、ナルトは今日もイタズラ三昧!! そんなナルトのでっかい夢は歴代の勇者、火影の名を受けついで、先代を越える忍者になることだ。だがナルトには出生の秘密が…!?


After


ここ 木 葉隠れ 里 忍術 学校 問題児 ナルト 今日 イタズラ 三昧 ナルト でっかい 夢 歴代 勇者 火影 名 受け ついで 先代 越える 忍者 なる こと ナルト 出生 秘密


まだまだ、やることはあると思いますが現状は上記処理です。本当はジャンルを抽出したいんだけどなぁ(´・ω・`)


結果を出力

DBから出力させたCVSを読み込む。

# 読み込みとindexを指定

df_traning_before=pd.read_csv("manga_trainng.csv",index_col=0)
df_traning=df_traning_before.copy()

CVSは以下の通り

index
0

asin
Amazonの商品ID

author
作者

binding
本の種類。ここではコミック

content
商品の説明

detailpageurl
商品のurl

imagepath
画像のurl。

language
言語。Powerで日本語を指定しているので日本語のみ

price
価格。たまに1円のコミックがある

publicationdate
出版日

publisher
出版社

reviewstar
Amazonのあの星

reviewsurl
レビューコメントのurl

salesrank
ランキング。変動する可能性大

similarproducts
「この商品をチェックした人はこんな商品もチェックしています」の本

title
本のタイトル

Doc2Vecを作るための前加工処理を参考にして加工


Doc2Vecのモデルを作るためには、学習用のデータとして、

「文章の名前 (タイトルやタグ): 文章のテキストを単語リスト化したもの」の形(TaggedDocument形式)に記事を加工する


#タグドキュメントのリストを作る

TaggedDocument_list=[]
for index, row in df_traning.iterrows():
asin=row[1]
title=row[15]
content=row[4]
TaggedDocument_pair = TaggedDocument(words=[content], tags=[asin])
TaggedDocument_list.append(TaggedDocument_pair)

この時点で13687冊のデータがあることを確認

3bed8fd7bec810d714f61ce232552ed9.png

学習させます。13秒かかりました。

%%time

Doc2VecModel = Doc2Vec(documents=TaggedDocument_list, vector_size=150, alpha=0.0015, sample=1e-4, min_count=10, workers=4, epochs=50)
Doc2VecModel.save('simple30_Doc2VecModel_150.model')

「シュタインズ・ゲート」と「東京卍リベンジャーズ」のASINをpositiveに指定して予測(本来は学習に使ったデータで予測することはよろしくないですが、大目に見て下さい)

model = Doc2VecModel

most_manga=model.docvecs.most_similar(positive=["484013328X","4063959384"],topn=20)
for manga_list in most_manga:
asin=manga_list[0]
print(asin,manga_list[1],df_traning.content[df_traning.asin==asin])

出力結果

順位
タイトル
%

1
乱飛乱外(1)(シリウスコミックス)
0.3196205496788025

2
もどって!まもって!ロリポップ(1)(なかよしコミックス)
0.30429574847221375

3
BAMBOO BLADE 1巻 (デジタル版ヤングガンガンコミックス)
0.294271856546402

4
CB感。REBORN(1)(ビッグコミックス)
0.27727600932121277

5
7センチ!もう1つの世界(3)もう1つの世界(3)
0.27715590596199036

6
姉妹ってこ! : 1 (アクションコミックス)
0.27431291341781616

7
ヴァムピール特別編 KING AND BARON+(1) (アフタヌーンコミックス)
0.2731931507587433

8
闇の守り人 1 (Nemuki+コミックス)
0.2677374482154846

9
和太鼓†ガールズ : 1 和太鼓 ガールズ (アクションコミックス)
0.2677217721939087

10
一週間フレンズ。 1巻 (デジタル版ガンガンコミックスJOKER)
0.26640552282333374

11
ギャルごはん【期間限定無料版】 1 (ヤングアニマルコミックス)
0.26609137654304504

12
一週間フレンズ。 4巻 (デジタル版ガンガンコミックスJOKER)
0.26047495007514954

13
ハートのダイヤ 1 (りぼんマスコットコミックスDIGITAL)
0.2594802975654602

14
玲瓏館健在なりや1巻 (HARTA COMIX)
0.2583160400390625

15
ぼっちな僕らの恋愛事情 1巻 (まんがタイムKRコミックス)
0.2581280469894409

16
ぴりふわつーん 1巻 (芳文社コミックス)
0.255959689617157

17
駆除人(1) (角川コミックス・エース)
0.2533213794231415

18
黄門さま~助さんの憂鬱~ 1 (ヤングジャンプコミックスDIGITAL)
0.2512657940387726

19
機動戦士ガンダム エコール・デュ・シエル(1) (角川コミックス・エース)
0.24728567898273468

20
無間地獄 : 1 (アクションコミックス)
0.24138006567955017

かすってすらいない...


考察

商品説明(漫画の説明)を取得して気づいたのですが、


 東京の空の玄関、“ビッグウイング(新東京空港)”。そこで、働く新人受付の吉川久美子を通して様々な人間模様を描く!!▼第1話/翼をください▼第2話/サイドウォーク▼第3話/休憩▼第4話/チケット▼第5話/社員旅行▼第6話/人生の価値▼第7話/ステージ▼第8話/携帯電話 ●登場人物/吉川久美子(“ビッグウイング”の受付=ツアーデスクの新人。困っている人を見るとほっておけない性格)、江頭主任(久美子の上司。久美子のことを厳しく、かつ優しく見守っている) ●あらすじ/吉川久美子は新東京空港、通称ビッグウイングの受付=ツアーデスクで働く新人。その久美子が担当する受付にひとりのお婆ちゃんがやって来た。搭乗手続の時間が過ぎていたためにカウンターで断られてしまったらしい。孫の結婚式に出席したいというお婆ちゃんをどうしても飛行機に乗せてあげたいと考えた久美子は、搭乗手続きを無理やり済ませ、お婆ちゃんを連れて搭乗口へ急ぐ。なんとか搭乗には間に合ったものの、出発を遅らせてしまう(第1話)。


↑話のタイトルがある。(補足:「●あらすじ」以降を取得できれば〇)

や、


ぴたテン 1巻


↑本のタイトルと巻のみ

など学習の対象にならないような説明文がありました。このような分かりやすいノイズを除去するのは勿論、細かい部分のノイズの除去をしらければいけない為、現状は進捗がありません(´・ω・`)そもそも、仮説があっているのかどうかも心配になってきていますね...

上で


もし結果が良くなかったら別の方法として、「まんが王国」などのサイトからジャンルを正解ラベルとして学習させることも考えていました


と答えましたがよくよく考えたらその通りで、正解ラベルを使って学習させた方が、分類しやすそうです。現状のDoc2Vecのアルゴリズムでは「文章の長さ」や「単語の並び順」で分類(クラスタリング)されているのでループものの特徴を学習できていないです。予想以上に、漫画の説明文のパターンが多かったり、異様に他とは違う10文字以下の説明文だったりいわゆる、「外れ値」が多かったです。

やる気がでたらやる

以上