⛰️

第5回 【Python】まだ見ぬアクティビティーを求めてアソビュー !Doc2Vecモデルチューニング

2022/11/07に公開

1. 概要

この記事は第4回の続きです。

https://zenn.dev/tokoroteen/articles/28ad4b0c2ec178/

第4回ではこれまで集めてきたデータを使って、Doc2Vecでモデル構築を行いました。今回はそのモデルをチューニングしつつ、アクティビティーのレコメンド機能を実装していきます。

完成したアプリはこちら↓

https://lifac.herokuapp.com/

例えば「空を飛びたい」とき(検索後に海要素を足して、山要素を引くこともできます!!)

例えば「動物に癒されたい」とき

2. 前回のモデルの確認

まずは前回作ったモデルでアクティビティーのレコメンド機能を作ってみます。

import numpy as np
import pandas as pd
import MeCab
from gensim.models.doc2vec import Doc2Vec, TaggedDocument

#モデルをロード
model = Doc2Vec.load('asoview.model')

#前回も使った関数
tagger = MeCab.Tagger('-Owakati -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd')
def tokenize_ja(text):
    node = tagger.parseToNode(text)
    while node:
        if node.feature.split(',')[0] in ["名詞","形容詞","形容動詞","動詞"]:#分かち書きで取得する品詞を指定
            yield node.surface.lower() #単語を出力
        node = node.next

def tokenize(content):
    token = [token for token in tokenize_ja(content)]
    return token

infer_vector()というメゾットで引数とした文書のベクトルを推測できるので、クエリに対してinfer_vector()を生成し、それと類似度の高いアクティビティーを調べてみます。

doc = tokenize('リラックスしたい') #['リラックス', 'し']
infer = model.infer_vector(doc_words=doc) #inferベクトルの生成
model.dv.most_similar(infer) #inferベクトルと似ているもの
[('野沢温泉スポーツ公園', 0.845043420791626),
 ('RetreatDive(リトリートダイブ)', 0.8392682671546936),
 ('ピースフリーマリン', 0.8303577899932861),
 ('リブマックスリゾート安芸 宮浜温泉', 0.8288807272911072),
 ('PRO SHOP LODGE', 0.8255318999290466),
 ('きものレンタルwargo(ワーゴ) 東京浅草店', 0.8246656060218811),
 ('ハピピランド\u3000ヒロロ弘前', 0.8239638209342957),
 ('Chisato Porcelarts Salon(チサト・ポーセラーツ・サロン)', 0.8234217166900635),
 ('カイトマリンスポーツ', 0.8213975429534912),
 ('GTダイバーズ沖永良部島', 0.8155992031097412)]

トップに出てきたのは野沢温泉スポーツ公園という施設でした。

野沢温泉スポーツ公園では、全長652m・標高差122mのジップ・スカイライドを保有。スリルあり爽快感ありの非日常体験、野沢温泉の大自然の中をさわやかな風を全身に浴びて体験する空中散歩をご提供しています!まるで鳥になった快感、目の前に広がる緑豊かな大自然を楽しんでいただくよう、安心・安全にご案内します!
出典:https://www.asoview.com/base/153252/

他の結果もいい感じとは言い難いです、、、
クエリをいくつか用意して一気に確認してみます。

queries = ['山で思いっきりはしゃぎたい', 'ストレスを解消したい', 'とにかくリラックスして体を休めたい', '空を飛びたい', '美味しいものをおなかいっぱい食べたい', '非日常感あふれる体験をしたい', 'カップルの記念日']
for i in queries:
    print(i)
    doc = tokenize(i)
    infer = model.infer_vector(doc_words=doc)
    print(model.dv.most_similar(infer)[:3])
    print('='*20)
山で思いっきりはしゃぎたい
[('ヤマノオト', 0.7556900382041931), ('GREEN MOUNT(グリーンマウント)', 0.7556573152542114), ('森の国', 0.7545734643936157)]
====================
ストレスを解消したい
[('海猫', 0.9106647372245789), ('トムソーヤマリンショップ', 0.910165548324585), ('ピースフリーマリン', 0.9088289737701416)]
====================
とにかくリラックスして体を休めたい
[('海人石垣島(マリンちゅ)', 0.9215481281280518), ('海猫', 0.9207642674446106), ('株式会社むさしのくらふと', 0.9170313477516174)]
====================
空を飛びたい
[('株式会社むさしのくらふと', 0.9283918738365173), ('海人石垣島(マリンちゅ)', 0.9174885749816895), ('キッズユーエスランド 熊本八代店', 0.9089061617851257)]
====================
美味しいものをおなかいっぱい食べたい
[('海人石垣島(マリンちゅ)', 0.935479462146759), ('Marine activity NATURA(マリン アクティビティ ナトゥーラ)', 0.9015001654624939), ('SEA WORK(シーワーク)', 0.9001250863075256)]
====================
非日常感あふれる体験をしたい
[('TAKU DIVE 石垣島', 0.8821085095405579), ('海猫', 0.8778867721557617), ('梨花和服 祇園店', 0.8758808374404907)]
====================
カップルの記念日
[("Kid's US.LAND\u3000イオン加古川店", 0.8514808416366577), ('SEA WORK(シーワーク)', 0.8326671123504639), ('海人石垣島(マリンちゅ)', 0.8318099975585938)]
====================

マリンちゅ激推しくんになってしまいました。

今度はinfer_vectorではなく、クエリの単語ごとに単語vectorを生成し、それらと近いアクティビティーを探してみます。

queries = ['山で思いっきりはしゃぎたい', 'ストレスを解消したい', 'とにかくリラックスして体を休めたい', '空を飛びたい', '美味しいものをおなかいっぱい食べたい', '非日常感あふれる体験をしたい', 'カップルの記念日']
for i in queries:
    print(i)
    doc = tokenize(i)
    word_vec = model.wv[doc]
    print(model.dv.most_similar(word_vec)[:3])
    print('='*20)
山で思いっきりはしゃぎたい
[('RocaRoca(ロカロカ)アクティビティ', 0.5573384761810303), ('フォレストアドベンチャー奥神鍋', 0.5123566389083862), ('大石林山', 0.5011543035507202)]
====================
ストレスを解消したい
[('筑紫野スポーツランド', 0.47226324677467346), ('砂丘YOGA®', 0.4182570278644562), ('T&Hボクサ・フィットネス', 0.39894649386405945)]
====================
とにかくリラックスして体を休めたい
[('癒しの里 名張の湯', 0.48462826013565063), ('さわやかクールバードえびす', 0.483079195022583), ('宮の街道温泉江戸遊', 0.46548011898994446)]
====================
空を飛びたい
[('KPS那須高原パラグライダースクール', 0.7040980458259583), ('湘南スカイスポーツスクール', 0.6983148455619812), ('奄美パラグライダーガイドサービスHappysky(ハッピースカイ)', 0.6967601180076599)]
====================
美味しいものをおなかいっぱい食べたい
[('松阪農業公園ベルファーム', 0.5463957786560059), ('伊勢志摩みやげセンター王将\u3000伊勢店', 0.49008798599243164), ('軽井沢ガーデンファームいちご園', 0.48717954754829407)]
====================
非日常感あふれる体験をしたい
[('無心窯陶芸教室', 0.41576093435287476), ('陶芸サロンHealingClay', 0.38910138607025146), ('ラ・ファミーユ中角', 0.3703257739543915)]
====================
カップルの記念日
[('彫金工房輝風', 0.4607360363006592), ('工房スミス札幌店', 0.45361363887786865), ('MATEI(マテイ)', 0.44840505719184875)]
====================

こちらの方がいい感じの結果が出力されるようになりました!
Doc2Vecなので足し算引き算もやってみます。

「空を飛びたい」ー「山」

pos_vec = model.wv[tokenize('空を飛びたい')]
neg_vec = model.wv[tokenize('山')]
model.dv.most_similar(positive=pos_vec, negative=neg_vec)[0]
('スカイ朝霧 パラグライダー・カヌースクール', 0.5966419577598572)

「リラックスしたい」ー「温泉」

pos_vec = model.wv[tokenize('リラックスしたい')]
neg_vec = model.wv[tokenize('温泉')]
model.dv.most_similar(positive=pos_vec, negative=neg_vec)[0]
('une graine(アングレンヌ)', 0.32486122846603394)

手作りキャンドル/ドライフラワーアレンジメントの施設でした。

「那須どうぶつ王国」ー「ライオン」+「イルカ」

model.dv.most_similar(positive=[model.dv['那須どうぶつ王国'],model.wv['イルカ']],negative=model.wv['ライオン'])[0]
('伊勢夫婦岩ふれあい水族館シーパラダイス(伊勢シーパラダイス)', 0.7413780689239502)

この結果は個人的にめちゃくちゃ満足です!!動物園からライオンを引いて、イルカを足すと水族館になりました!

3. パラメータを変えてみる

想像以上にいい感じのモデルで逆に困っていますが、学習の際のパラメータを変えてみます。

windowによる変化

windowは左右何個の単語まで考慮するかを決めることができます。(デフォルトはwindow=5)

window=1

model = Doc2Vec(window=1)
model.build_vocab(taggeddocument)
model.train(taggeddocument, total_examples=model.corpus_count, epochs=model.epochs)

queries = ['山で思いっきりはしゃぎたい', 'ストレスを解消したい', 'とにかくリラックスして体を休めたい', '空を飛びたい', '美味しいものをおなかいっぱい食べたい', '非日常感あふれる体験をしたい', 'カップルの記念日']
for i in queries:
    print(i)
    doc = tokenize(i)
    word_vec = model.wv[doc]
    print(model.dv.most_similar(word_vec)[:3])
    print('='*20)
山で思いっきりはしゃぎたい
[('Nipa yoga(ニパヨガ)', 0.6043249368667603), ('RocaRoca(ロカロカ)アクティビティ', 0.6040046215057373), ('KURA-RUN OUTDOORS', 0.5976560115814209)]
====================
ストレスを解消したい
[('Resound(リサウンド)', 0.5740888714790344), ('JOHNNY STYLE 銀座店', 0.5551772117614746), ('JOHNNY STYLE 恵比寿店', 0.5483916401863098)]
====================
とにかくリラックスして体を休めたい
[('ドライヘッドスパ専門店ヘッドミント', 0.5579400062561035), ('La vie en Rose 南森町店', 0.5525122880935669), ('癒しの里 名張の湯', 0.5520180463790894)]
====================
空を飛びたい
[('伊吹山パラグライダースクール', 0.6111437082290649), ('スカイエンジェル', 0.6041603088378906), ('三保の松原パラグライダー体験', 0.6028056144714355)]
====================
美味しいものをおなかいっぱい食べたい
[('古民家カフェ\u3000岡野ファーム', 0.6257206201553345), ('伊勢志摩みやげセンター王将\u3000松阪店', 0.598393976688385), ('八ヶ岳エナジーファーム\u3000いちご畑としいたけの森', 0.5901493430137634)]
====================
非日常感あふれる体験をしたい
[('スカイダイブ北海道', 0.6004854440689087), ('ファームリゾートONJUKU(オンジュク)', 0.6002585887908936), ('翔笑璃', 0.5995974540710449)]
====================
カップルの記念日
[('Team\u3000Adventure(チーム\u3000アドベンチャー)', 0.5526367425918579), ('屋久島メッセンジャー', 0.5498412847518921), ('あとりえboss&Nonko(ボスアンドノンコ)', 0.5234155654907227)]
====================

window=10

model = Doc2Vec(window=10)
model.build_vocab(taggeddocument)
model.train(taggeddocument, total_examples=model.corpus_count, epochs=model.epochs)
[('RocaRoca(ロカロカ)アクティビティ', 0.4813646674156189), ('大石林山', 0.4802303612232208), ('富士漫遊社', 0.4595384895801544)]
====================
ストレスを解消したい
[('筑紫野スポーツランド', 0.4924512505531311), ('白馬マウンテンクロス', 0.41904202103614807), ('フォレストアドベンチャー・湯の山', 0.38855403661727905)]
====================
とにかくリラックスして体を休めたい
[('さわやかクールバードえびす', 0.47929099202156067), ('喜盛の湯', 0.4791018068790436), ('スパロイヤル川口', 0.46811315417289734)]
====================
空を飛びたい
[('スカイ朝霧 パラグライダー・カヌースクール', 0.7342751622200012), ('ライトドリーム', 0.7297751903533936), ('KPS那須高原パラグライダースクール', 0.7292381525039673)]
====================
美味しいものをおなかいっぱい食べたい
[('福田フルーツパーク', 0.5193634629249573), ('松阪農業公園ベルファーム', 0.5180649757385254), ('宿沢フルーツ農園本店', 0.4595303535461426)]
====================
非日常感あふれる体験をしたい
[('ニライナホリデイズ', 0.3319607973098755), ('陶芸サロンHealingClay', 0.3239280879497528), ('無心窯陶芸教室', 0.3155190646648407)]
====================
カップルの記念日
[('彫金工房輝風', 0.4435161054134369), ('iddo art jewelry 門司', 0.4213113486766815), ('MATEI(マテイ)', 0.41991835832595825)]
====================

今回の検討ではwindowによる変化はあまり感じられません。

epoch数による変化

epoch数を変えてみます。(デフォルトは10)

epoch=1

model.train(taggeddocument, total_examples=model.corpus_count, epochs=1)
山で思いっきりはしゃぎたい
[('横浜スカイクルーズ', 0.9358012080192566), ('MAGIC ISLAND(マジックアイランド)', 0.9324934482574463), ('蒜山ツアーデスク', 0.9272384643554688)]
====================
ストレスを解消したい
[('アマニコガイドサービス', 0.7920987010002136), ('エバーブルー屋久島', 0.7913117408752441), ('Simple marine service(シンプルマリンサービス)', 0.7894357442855835)]
====================
とにかくリラックスして体を休めたい
[('Simple marine service(シンプルマリンサービス)', 0.8138972520828247), ('All Blue(オールブルー)', 0.8033820390701294), ('石垣島 ADVENTURE PiPi(イシガキジマアドベンチャーピピ)', 0.8013563752174377)]
====================
空を飛びたい
[('水辺荘', 0.9459261894226074), ('奄美バギー&ナイトウォッチングガイドサービス', 0.9401012659072876), ('熱海マリンスポーツクラブ', 0.9334551692008972)]
====================
美味しいものをおなかいっぱい食べたい
[('石川トマト農園', 0.9277568459510803), ('松治郎の舗\u3000伊勢おはらい町店', 0.8735381960868835), ('美杉リゾートInaka Tourism(いなかツーリズム)', 0.8695625066757202)]
====================
非日常感あふれる体験をしたい
[('陶芸の丘 想工房', 0.8812295794487), ('カヌーヴィレッジ長瀞', 0.8807774782180786), ('コンセプト', 0.8785371780395508)]
====================
カップルの記念日
[('東京タワーバンジーVR', 0.9225044250488281), ('変身スタジオ艷蓮(Adebasu)', 0.9071975350379944), ('GLASS-LAB(グラスラボ)', 0.9045374393463135)]
====================

学習は8秒で終わりましたが、「とにかくリラックスして体を休めたい」のに海や山に連れ回されてしまいそうです、、、

epoch=100

model.train(taggeddocument, total_examples=model.corpus_count, epochs=100)
山で思いっきりはしゃぎたい
[('招き猫美術館', 0.3547191619873047), ('神戸市立須磨海浜水族園', 0.3358643352985382), ('屋久島フィールド情報センター', 0.3265390694141388)]
====================
ストレスを解消したい
[('白馬マウンテンクロス', 0.3729977011680603), ('JOHNNY STYLE 恵比寿店', 0.34663766622543335), ('CARLSBAD(カールスバッド)', 0.3344053328037262)]
====================
とにかくリラックスして体を休めたい
[('La vie en Rose 南森町店', 0.41961660981178284), ('溝口温泉 喜楽里', 0.4148680567741394), ('さわやかクールバードえびす', 0.3761550486087799)]
====================
空を飛びたい
[('VANスカイスポーツ', 0.4992501735687256), ('エアパークCOOパラグライダースクール', 0.4916755259037018), ('ウエストジャパンアウトドアスクール', 0.4839506149291992)]
====================
美味しいものをおなかいっぱい食べたい
[('心いちご園', 0.43655332922935486), ('いちごはうす西脇', 0.393851637840271), ('Pacha Beach(パチャビーチ)', 0.3371447026729584)]
====================
非日常感あふれる体験をしたい
[('平井焼', 0.4783760905265808), ('ニライナホリデイズ', 0.4316614270210266), ('陶房艸雲窯(トウボウソウウンガマ)', 0.4097640812397003)]
====================
カップルの記念日
[('手作りペアリングのAIGIS表参道店', 0.4420255124568939), ('garden心斎橋', 0.4237858057022095), ('アカネス大阪', 0.3967462480068207)]
====================

こちらは8分かかりました。
「山で思いっきりはしゃぎたい」のに招き猫美術館を紹介されても困るのでepoch数が多ければ言いわけでもなさそうです。

vectorの次元数による変化

vector_sizeによって何次元のベクトルで表記するかをしていできます。(デフォルトは100次元)

vector_size=10

model = Doc2Vec(vector_size=10)
山で思いっきりはしゃぎたい
[('昭和記念公園セグウェイツアー', 0.9359950423240662), ('レイクサイドとうろ', 0.9157453179359436), ('龍宮クルーズ', 0.9150355458259583)]
====================
ストレスを解消したい
[('やまさの軍艦島上陸周遊クルーズ・長崎みなとめぐり遊覧船(世界文化遺産ヘリテージクルーズ)', 0.8449451327323914), ('ボンファイアー(日光カヌー・カヤック体験)', 0.8366055488586426), ('支笏ガイドハウスかのあ', 0.8356747031211853)]
====================
とにかくリラックスして体を休めたい
[('リブマックスリゾート天城湯ヶ島', 0.8387866616249084), ('天然温泉かきつばた', 0.8319327235221863), ('伊勢・船江温泉みたすの湯', 0.8318638801574707)]
====================
空を飛びたい
[('伊勢志摩スカイダイビングクラブ', 0.9471952319145203), ('白馬パラトピア五竜パラグライダースクール', 0.9374573826789856), ('ライトドリーム', 0.9350649118423462)]
====================
美味しいものをおなかいっぱい食べたい
[('いのさんの観光農園', 0.8691805601119995), ('軽井沢ガーデンファームいちご園', 0.8687654733657837), ('宿沢フルーツ農園本店', 0.8655694723129272)]
====================
非日常感あふれる体験をしたい
[('CLUBMAN(クラブマン)', 0.862310528755188), ('はぎまえ698', 0.8317584991455078), ('ブルーウォーターアドベンチャーズ カケロマ', 0.8119956254959106)]
====================
カップルの記念日
[('LAWAKU(ラワク)', 0.7508366107940674), ('ハナマルキ みそ作り体験館', 0.7087247967720032), ('鹿野そば道場', 0.6958708763122559)]
====================

10次元でもそれなりに多様な結果を出せるんだと、ちょっと意外でした。

vector_size=500

model = Doc2Vec(vector_size=500)
山で思いっきりはしゃぎたい
[('RocaRoca(ロカロカ)アクティビティ', 0.5462843775749207), ('CANOE PRO', 0.49989041686058044), ('リトルピークス', 0.4914983808994293)]
====================
ストレスを解消したい
[('筑紫野スポーツランド', 0.4884355068206787), ('白馬マウンテンクロス', 0.4086739420890808), ('天狗温泉浅間山荘', 0.37805309891700745)]
====================
とにかくリラックスして体を休めたい
[('鶴見緑地湯元 水春', 0.46439605951309204), ('癒しの里 名張の湯', 0.46235036849975586), ('ヨーガアムリタ', 0.4453602731227875)]
====================
空を飛びたい
[('KPS那須高原パラグライダースクール', 0.6869857311248779), ('白馬パラトピア五竜パラグライダースクール', 0.6797089576721191), ('スカイエンジェル', 0.6769261956214905)]
====================
美味しいものをおなかいっぱい食べたい
[('松阪農業公園ベルファーム', 0.5387112498283386), ('伊勢志摩みやげセンター王将\u3000伊勢店', 0.45953401923179626), ('宿沢フルーツ農園本店', 0.4589420557022095)]
====================
非日常感あふれる体験をしたい
[('重要文化財\u3000菊屋家住宅', 0.3608725070953369), ('無心窯陶芸教室', 0.355581670999527), ('平井焼', 0.35266488790512085)]
====================
カップルの記念日
[('アカネス 清水店', 0.440045028924942), ('彫金工房輝風', 0.42943423986434937), ('iddo art jewelry 門司', 0.42922601103782654)]
====================

可もなく不可もなくといった感じでしょうか。

4. まとめ

前回作ったモデルのパラメータを変えてモデルをチューニングしてみました。
今回の場合はデフォルト設定でかなりいい感じのモデルができたので、変にいじるよりはデフォルトのままのが良さそうです。

完成したものはこちら↓

https://lifac.herokuapp.com/

連載一覧

【Python】まだ見ぬアクティビティーを求めてアソビュー !スクレイピング

第2回 【Python】まだ見ぬアクティビティーを求めてアソビュー !分析

第3回 【Python】まだ見ぬアクティビティーを求めてアソビュー !口コミ スクレイピング

第4回 【Python】まだ見ぬアクティビティーを求めてアソビュー !機械学習(Doc2Vec)

第5回 【Python】まだ見ぬアクティビティーを求めてアソビュー !Doc2Vecモデルチューニング

Discussion