Wikipediaのテキストで学習済の「Word2Vec学習済モデル」を使った簡単な「近い言葉探し遊び」的なデモを作ってみます。
はじめに
言葉(単語)をうけとって、近い言葉を結果として返すクラスは前に作りました。
これを組み込んで、djangoで簡単なデモページを作ってみます。
Word2Vecモデルを扱うのに必要なパッケージなどは、上記の記事を参照します。
とりあえず仕様というか画面イメージ
初期画面はこんな感じ。
単語を指定して、近い単語を探すのですが、「王+女性」みたいな単語の加算減算もやりたいので、「足す言葉」と「引く言葉」を入力欄にします。
ひとつで計算式を入力するのも考えたのですが、()の処理とかみたいな式の解析のロジックを書くのが面倒(笑)だったので、ごくシンプルにしました。
足す言葉に「王 女性」みたいに入力すると「王+女性」として扱い、引く言葉に「愛」とか入力すると「王+女性-愛」になる・・みたいな感じにします。
入力後に「送信する」ボタンを押すと、下に結果を表示する。
そういう仕様にます。
結果を表示したイメージはこちら。
ソースコードと補足
修正が必要なソースの構成は以下です。
- Word2Vec処理クラス:word2vec.py
- djangoフォーム定義:forms.py
- djangoテンプレート:similar.html
- djangoコントローラ:views.py
- djangoURL変換: urls,py
Word2Vec処理クラス:word2vec.py
Word2Vecモデルを読み込んで、近い単語を返すクラスです。
ほぼ、こちらで作ったものを同じです。
ただ、画面仕様にあわせるため、入力のリストを受け取って、「王+女性」みたいに計算式っぽい文字列にする処理を追加してます。
import gensim class Word2Vec: def __init__(self, dicpath): self.model = gensim.models.Word2Vec.load(dicpath) def get_most_similar(self, plus_list, minus_list): exp_str = '' plus = [] for p in plus_list: if p in self.model.wv.vocab: plus.append(p) minus = [] for m in minus_list: if m in self.model.wv.vocab: minus.append(m) try: if not minus: if not plus: result = [('辞書に存在する単語がありません。', 0.00000000)] else: exp_str = self.__exp_str(plus, '+') result = self.model.most_similar(positive=plus) else: if not plus: exp_str = self.__exp_str(minus, '-') result = self.model.most_similar(negative=minus) else: exp_str = self.__exp_str(plus, '+') + '-' + self.__exp_str(minus, '-') result = self.model.most_similar( positive=plus, negative=minus) except BaseException: result = [('処理中にエラーが発生しました。', 0.00000000)] return result, exp_str def __exp_str(self, p_list, dmtr): st = '' for i, p in enumerate(p_list): if i == 0: st = p else: st = st + str(dmtr) + p return st
補足は特にありません。
djangoフォーム定義:forms.py
テキストボックスを2つ用意します。
from django import forms class UserForm(forms.Form): textplus = forms.CharField( label='足す言葉', widget=forms.TextInput( attrs={ 'id': 'textplus', 'placeholder': '単語を入力。複数の場合は空白で区切る。', })) textminus = forms.CharField( label='引く言葉', required=False, widget=forms.TextInput( attrs={ 'id': 'textminus', 'placeholder': '入力した単語を引き算します。', }))
djangoのformはほぼデフォルト「必須入力」です。
当然、CharFieldもデフォルトで必須入力です。
なので、textminusの方は「required=False」で明示的に必須をはずしてます。
djangoテンプレート:similar.html
表示画面です。
{% extends 'base.html' %} {% load static %} {% load bootstrap4 %} {% load widget_tweaks %} {% block header %} <link rel="stylesheet" href="{% static "css/talk.css" %}"></link> {% endblock %} {% block title %} もっとも似ている言葉を探す {% endblock %} {% block content %} <div class="container"> <form action="" method="post">{% csrf_token %} <h4>似ている言葉を探します(言葉の加減算付き)</h4> <div class="form-group row my-4"> <label class="col-lg-3 col-form-label"><h4>{{form.textplus.label}}</h4></label> <div class="col-lg-7"> {{form.textplus|add_class:"form-control"}} </div> </div> <div class="form-group row my-4"> <label class="col-lg-3 col-form-label"><h4>{{form.textminus.label}}</h4></label> <div class="col-lg-7"> {{form.textminus|add_class:"form-control"}} </div> </div> <div class="form-group row my-4"> <div class="col-lg-3"> <button type="submit" class="btn btn-primary">送信する</button> </div> </div> <hr/> <div id="resultarea"> <div class="form-group row text-center my-2"> {% if expstr %} <h1>計算式:「{{ expstr }}」</h1> {% endif %} </div> <div class="form-group row text-center my-2"> {% if expstr %} <h1>最も近い結果:「{{ result1 }}」</h1> {% endif %} </div> <div class="form-group row text-center my-2"> {% if expstr %} <h1>近さの度合い:「{{ result2 }}」</h1> {% endif %} </div> </div> </div> </form> </div> {% endblock %}
特に凝ったことはしていません。
djangoコントローラ:views.py
上記のHTMLテンプレートに渡す結果として
- 計算式文字列:expstr
- 結果の単語:result1
- 近い確率数値:result2
の3つを定義・値のセットをしています。
from django.shortcuts import render from . import forms from django.template.context_processors import csrf import re import os from . import word2vec as wv def w2v_do(request): word2vec = wv.Word2Vec(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'demos\\ja\\ja.bin')) if request.method == 'POST': # テキストボックスに入力されたメッセージ plusinput = request.POST["textplus"] minusinput = request.POST["textminus"] # 半角・全角空白。一応、カンマとタブも区切りにしてみた pluslist = re.split(r'[\s ,\t++]', plusinput) minuslist = re.split(r'[\s ,\t-ー]', minusinput) results, expstr = word2vec.get_most_similar(pluslist, minuslist) # 描画準備 form = forms.UserForm(label_suffix=':') c = { 'form': form, 'expstr': expstr, 'result1': str(results[0][0]), 'result2': str(results[0][1]) } else: # フォームの初期化 form = forms.UserForm(label_suffix=':') c = {'form': form} c.update(csrf(request)) return render(request, 'similar.html', c)
ポイントです。
まず、views.pyから静的ファイルの学習済モデル「js/ja.bin」を読み込むところです。
クラス単体で処理するだけなら「.\\ja\\ja.bin」のように書けばよいのですが、djangoのviews.pyの中で参照するときは、それではエラーになります。
views.pyと同じフォルダに「ja」フォルダを置き、その下に「ja.bin」を置いた場合の参照方法は
os.path.join(os.path.dirname(os.path.dirname(__file__)), 'demos\\ja\\ja.bin')
になります。
例えば「demos」プロジェクトの場合だと、「os.path.join(os.path.dirname(os.path.dirname(__file__)),」で取得できるパスは以下になります。
views.pyの置き場所は、上記の例だと「demos」の下になるので、ここからの相対パスである「 'demos\\ja\\ja.bin'」を書くことで、静的ファイルが取得できる・・というわけですね。
あと。
入力文字列を受け取って、単語に分解するこの部分。
pluslist = re.split(r'[\s ,\t++]', plusinput)
minuslist = re.split(r'[\s ,\t-ー]', minusinput)
いちおう。
- 半角空白、全角空白、カンマ、タブ、半角+、全角+(足す言葉)
- 半角空白、全角空白、カンマ、タブ、半角-、全角ー(引く言葉)
がデリミタで使えるようにしています。
それくらいですかね。
djangoURL変換: urls,py
最後にURLです。
from django.contrib import admin from django.urls import path from . import views urlpatterns = [ path('demo06/', views.w2v_do, name='similar'), ]
例えば。
http://localhost:8000/demo06 でアクセスする想定です。
views.w2v_do は、views.pyの「w2v_do」関数。
name='similar' が「similar.html」をさすのは、ご存じの通りです。
今回のデモの実行環境とか
今回のデモを動かすには、Word2Vecの学習済モデルが必要です。
学習済モデルは以下からダウンロードして、この記事中で説明した所定の場所にコピーする必要があります。
djangoの環境構築や使い方等は以下などを参考にします。
今回はこんなところで。
ではでは。