読者です 読者をやめる 読者になる 読者になる

ジオマーリン

仕様を公開してアドバイスを受けながら geomerlin.com を構築していくスタイル。このシステムではデータサイエンスと人工知能技術により地政学リスクを算出しています。

geomerlin.comのシステム全体図を公開

 ちょっとエンジニア向けのページになります。

仕様を公開する意図は

①アドバイスを受けたいこと

②ユーザーを巻き込んだ開発にすること

です

f:id:geomerlin-com:20170410221117p:plain

 

よくありそうな図ですね。

クローリング→自然言語処理→DB保存→ユーザーのリクエストに対してリスポンス

 

次に細かく書きます。

①クローラ

  • 普通にurllibを使用。pyextracontent(python2)をpython3から呼び出し
  • 加えてbingNewsAPIから国ごとの情報を取得
  • RSSのデータベース(sqlite3)を元にクロール先のurlを決定

自然言語処理

③サーバーサイド(AWS

④クライアントサイド

  • 可視化(特に地球儀・エフェクト)をD3で処理。
  • リクエストはAjax
  • いくつかUIをbootstrap
  • タップイベントとかはjquery
  • スマホアプリはMonaca+Inappbrowserプラグイン

python3からpython2呼び出しを最速で

人それぞれの事情でpython3からどうしてもpython2のプログラムを実行したい時、するべきことを簡単に書く

 

やることは

python2コード(p2.py)を書く

→②python3コードの中でsubprocessを書く

以上

 

では具体的に以上の2つを実践する

python2コード(p2.py)を書く

import sys

#最初にpython3から標準でテキストを受け取る
got_line = sys.stdin.readline()
got_line = got_line.strip()

#ここでやりたい処理を行う(出力は文字列)
result = doingSomething(got_line)

sys.stdout.write(result.encode("utf-8")

python3コードの中でsubprocessを書く

def inputSomethingToPython2(input_text):

p = subprocess.Popen("python p2.py", shell=True,stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

#ここでpyton2に文字列を渡す
stdout_data, stderr_data = p.communicate(input_text.encode("utf-8"),timeout=20)
return stdout_data.decode("utf-8")

 

 

 

 

chainerで文書分類(ニュースをカテゴリ分け) 訓練後を中心

chainerはディープラーニングもできるNNライブラリ

ディープラーニングということは入力と出力のサンプル(教師データ)を用意すれば分類問題は絶対自動化できる。

ただ、入力と出力の型が違うとコードも違ってくるから、「でぃーぷらーにんぐをやれ!」と一言では済ませられない。

 

chainerはトレーニング→実用なのだが、実用の部分がドキュメント・ブログともに少ないので、訓練後を丁寧にやっていきたい。トレーニングまでは

Chainerでフィードフォワードニューラルネットワークを実装して文書分類する - Qiita

を参照してほしい。

まず最低限のchainer用語を紹介

l1,l2,l3 →入力から第一層目に変換、一層目から二層目、二層目から三層目へ変換する関数

・optimizer →最適化のためのクラス

・model→学習済みニューラルネットワーク

 

ここでこの記事の結論を先に言うことにする

l3の出力をnp.argmaxすれば分類を出力できます。

 

では前述のありがたい記事にしたがって書いていく。

想定する教師データ: [[文字リスト],番号] ×n行 :[[[この、学校、バカ、ばっかり],0],[[けど、俺、は、すごい],1]] 

 

【Chainer】畳み込みニューラルネットワークによる文書分類 - Qiita をコピペして動かしますが、2点を変更

①インポートライブラリとload_data

# coding: utf-8
import numpy as np
from sklearn.cross_validation import train_test_split
from collections import defaultdict
import six
import sys
import json
import chainer
import chainer.links as L
from chainer import optimizers, cuda, serializers
import chainer.functions as F
import argparse
from gensim import corpora, matutils
import pickle


def load_data():

source = []
target = []

document_list = [] #各行に一文書. 文書内の要素は単語
with open("teacher.json", "r") as fi:
dataset = json.load(fi)

for l in dataset:
#ラベルと単語列を分ける

label = int(l[1]) #ラベル
target.append(label)
document_list.append(l[0]) #単語分割して文書リストに追加

#単語辞書を作成
for document in document_list:
tmp = dictionary.doc2bow(document) #文書をBoW表現
vec = list(matutils.corpus2dense([tmp], num_terms=len(dictionary)).T[0])
source.append(vec)

dataset = {}
dataset['target'] = np.array(target)
dataset['source'] = np.array(source)
print("vocab size:", len(dictionary.items()))
return dataset, dictionary

load_dataを少々変更。

この関数は教師データをchainerにぶち込める形に変換する。

 

②データ保存コードを変更。pickleのほうが何かと便利で使いやすい

#modelとoptimizerを保存
print('save the model')
#serializers.save_npz('pn_classifier_ffnn.model', model)
#print('save the optimizer')
#serializers.save_npz('pn_classifier_ffnn.state', optimizer)

with open('classifier_ffnn3.pickle',"wb") as fi:
pickle.dump(model,fi)
with open('dictionary3.pickle',"wb") as fi:
pickle.dump(dictionary,fi)

 

ではトレーニングを終えた後のことについて

別ファイルで次のコードを書きます。

import chainer
import chainer.functions as F
from gensim import corpora, matutils
import pickle

####################
#ファイル読み込み
####################
with open("classifier_ffnn3.pickle", "rb") as fi:
Model = pickle.load(fi)

with open("dictionary3.pickle","rb") as fi:
Dictionary = pickle.load(fi)


#NNによる分類関数
def fwd(model,dictionary,x):
x = dictionary.doc2bow(x) #文書をBoW表現
vec = list(matutils.corpus2dense([x], num_terms=len(dictionary)).T[0])
x = np.array([vec]).astype(np.float32)
x =chainer.Variable(x)
h1 = F.sigmoid(model.l1(x))
h2 = model.l2(h1)
h3 = model.l3(h2)
return np.argmax(h3.data)#F.softmax(h2)

####################
#関数用意
####################
def label_news(word_list):
return fwd(Model,Dictionary,word_list)

pickleで保存したNNとディクショナリを復活。

fwd関数で文字リストx(["ムズい","試験","タルい"]など)を最後の引数で受け取り、教師と同じように分類して返す。ラッパーがlabel_news関数になっている。

結論はl3の出力をnp.argmaxすれば分類できます。

 

このコードは http://geomerlin.com のニュース分類に使っています。

//訓練データ作るのにネカフェで修行僧になってました:D

 

Stanford Corenlp をPythonで使う2(実用的なコーディング、便利な関数の作成)

 前回(Stanford Corenlp をPythonで使う1)ではソフトの紹介を行いましたが、続いて実際に僕が使い続けている際に、どういうコードを書けば良いのかを書かせてもらいます。

 

 まず、重要な点として

 1:出力がディクショナリという変更可能(ミュータブル)なオブジェクトを中心に扱っているため、その利点を活かすべきだということ

 2:出力されたデータはあまりにもネストが多くて扱いにくいということ

 

 故に

 coref,dependencyのデータを単語データ(sentence["words"]の要素)にあるディクショナリに格納しネストを減らすべき

 と言う結論に達します。全部単語データにぶち込んで分かりやすくしておき、後々のコーディングに備えましょう。

 まずcorefから。

 

def net_coref(all_data):

   for coref in all_data["coref"]:

       for item in coref:

           "各corefのリストは最後の要素が、最初に文で登場した具体的な表現である"

   "故に最後の要素を参照先として参照元の単語データに格納する"

           gov = item[-1] #これが最後のデータ

 

   "最後以外のデータを順に処理する"

           for index in range(len(item))[:-1]:

               i  = item[index]

               start = i[-2] #始まりの場所

               end = i[-1] #終わりの場所

        "i[1]は「何文目か」、i[-1]は「何語目から始まるか」,i[-2]は「終わるか」"

               for word in all_data['sentences'][i[1]]["words"][start:end]:

                   word[1]['coref'] = tuple(gov)


##続き編集中

 

Stanford Corenlp をPythonで使う1(インストール〜ダンプ)

Stanford Corenlp (http://stanfordnlp.github.io/CoreNLP/)は自然言語処理の汎用ツールで、品詞の特定、構文木・依存関係の決定、固有名詞解析、共参照の特定(例えば”This is my friend ,Asami.She is from U.S.”ならthis=my friend=she=Asamiの関係を特定)などをやってくれる。素人が使うツールとしては最強!であるがデフォルトで3GBのメモリをアロケートするので注意。

 

まず、pythonでラップする対象である元のjavaプログラムをダウンロード

curl -L -O http://nlp.stanford.edu/software/stanford-corenlp-full-2014-08-27.zip

アクセス制限のエラー出たら、sudoってください。

 

解凍(僕の場合は/usr/local/libが空いてるのでここにしておく)

unzip ./stanford-corenlp-full-2014-08-27.zip -d /usr/local/lib/

 

次にpipでラッパーをインストール

sudo pip3 install corenlp-python

 

ではもう使えます

import corenlp
parser = corenlp.StanfordCoreNLP()
json_data = parser.parse(解析する英文)

エラーが出る場合は一番下へ

 

結果について説明しておきます。出力はjsonなのでjson.loadsかevalして下さい。

かなりひどいネスト構造になっています。
大まかには全体データ > 文章データ(sentences) > 単語データ(words)のネストです。

最初は大きいディクショナリは全部の文章の解析結果でキーはcoref とsentencesです。
corefの値のリストには共参照がセットになっています。
(それぞれの共参照はだいたい最後の要素が最初にでてきた表現です)

sentencesの値はそれぞれの文ごとの解析結果です。
このリストの中はディクショナリが格納されていて、キーはtext,dependencies,wordsです。
さらにwordsの値にはそれぞれの単語のデータが入っています。

wordsのkeyは固有名詞解析のNamedEntityTag,原型のLemma,品詞のPartOfSpeechからなっています。

 

文章データにあるdependenciesは依存関係を表していますが、これは依存関係の表現を知らないと使いようがありません。このドキュメントに使い方が書いてあります。

http://nlp.stanford.edu/software/dependencies_manual.pdf

 

・エラーが出る場合

次のようなエラーが出ましたか?

corenlp.corenlp.ProcessError: 'CoreNLP process terminates abnormally while parsing'

だいたいメモリ不足か自然言語とは思えない文字が混入してるかどちらかですね。

後者の場合は除去して下さい。前者の場合は、Java仮想マシンに3GBもアロケートさせるのをやめましょう。つぎのファイルにアクセスして下さい

pythonなんとか/site-packages/corenlp/corenlp.py

ここで

class StanfordCoreNLP:

    (中略)

    def __init__(self, corenlp_path=DIRECTORY, memory="3g"

のところの3gを1gか2gにしましょう。これでパーサーがメモリ飲み放題のパーティーを開いて怒られるのを防ぎます。ただし、精度は下がるでしょうから、長い文章の共参照はあきらめましょう。

他にも/site-packages/corenlp/default.propertiesの

annotators = tokenize, ssplit, pos, lemma, ner, parse, dcorefを減らすと機能が減る代わりに負担も減ります。