Yahoo! Japan Developer Network : 日本語形態素解析サービスの利用

/*
 * Yahoo! Japan ディベロッパーネットワーク: テキスト解析:形態素解析サービスの利用
 * http://developer.yahoo.co.jp/webapi/jlp/ma/v1/parse.html
 * 2015.11.18
 */
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.io.InputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import javax.xml.xpath.*;

import java.util.ArrayList;

public class YahooMorph {
  private String appid;
  private final String path = "http://jlp.yahooapis.jp/MAService/V1/parse?";

  // 形態素 (morpheme)クラス
  public class Morpheme {
    private String surface;
    private String reading;
    private String pos;
    private String baseform;
    
    public Morpheme(String surface, String reading, String pos, String baseform){
      this.surface = surface;
      this.reading = reading;
      this.pos = pos;
      this.baseform = baseform;
    }
    
    // getter
    public String getSurface(){ return surface; }
    public String getReading(){ return reading; }
    public String getPos(){ return pos; }
    public String getBaseform(){ return baseform; }
  }
  
  public YahooMorph(String appid){
    this.appid = appid;
  }
  
  public ArrayList<Morpheme> analyse(String text){
    // 形態素を格納するリスト
    ArrayList<Morpheme> morph_list = new ArrayList<Morpheme>();
    
    try{
      // リクエスト URL およびその引数については下記を参照
      // http://developer.yahoo.co.jp/webapi/jlp/ma/v1/parse.html
      // (注)日本語文字列は予め URL encoding しなければならない。
      URL url = new URL(path + "appid=" + appid + "&results=ma&response=surface,reading,pos,baseform&sentence=" + URLEncoder.encode(text, "utf-8"));
      
      // Yahoo! Developer Web service とのコネクションを張る
      HttpURLConnection http = (HttpURLConnection)url.openConnection();
      http.setRequestMethod("GET"); // GET メソッドによるアクセス
      http.connect();
      
      // サーバから返される XML を処理するための document builder の設定
      InputStream input = url.openStream();
      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
      DocumentBuilder db = dbf.newDocumentBuilder();
      Document doc = db.parse(input);

      // XML の解析(parsing)に XPath を用いる。
      // XPath の解説は下記サイトが詳しい。
      // http://www.techscore.com/tech/XML/XPath/index.html
      XPathFactory factory = XPathFactory.newInstance();
      XPath xpath = factory.newXPath();
      
      // 表層形 (surface), 読み (reading), 品詞 (pos),基本形 (baseform)の取り出し
      NodeList surfaces = (NodeList)xpath.evaluate("//ma_result/word_list/word/surface/text()", doc, XPathConstants.NODESET);
      NodeList readings = (NodeList)xpath.evaluate("//ma_result/word_list/word/reading/text()", doc, XPathConstants.NODESET);
      NodeList poses = (NodeList)xpath.evaluate("//ma_result/word_list/word/pos/text()", doc, XPathConstants.NODESET);
      NodeList baseforms = (NodeList)xpath.evaluate("//ma_result/word_list/word/baseform/text()", doc, XPathConstants.NODESET);

      // デバッグ用の出力
      /*
      for(int i=0; i < surfaces.getLength(); i++){
        System.out.println(surfaces.item(i).getNodeValue());
      }
      
      for(int i=0; i < readings.getLength(); i++){
        System.out.println(readings.item(i).getNodeValue());
      }

      for(int i=0; i < poses.getLength(); i++){
        System.out.println(poses.item(i).getNodeValue());
      }

      for(int i=0; i < baseforms.getLength(); i++){
        System.out.println(baseforms.item(i).getNodeValue());
      }
      */

      // 各形態素の表層形 (surface), 読み (reading), 品詞 (pos),基本形 (baseform)を
      // 形態素解析リストへ格納
      for(int i=0; i < surfaces.getLength(); i++){
        morph_list.add(new Morpheme(surfaces.item(i).getNodeValue(),
            readings.item(i).getNodeValue(),
            poses.item(i).getNodeValue(),
            baseforms.item(i).getNodeValue()));
      }
      
      http.disconnect(); // コネクションの切断
      
    } catch(Exception e){
      System.out.println(e);
      System.exit(1);
    }
    
    return morph_list;
  }
  
  public static void main(String[] args) {
    // Application ID の取得方法は下記を参照:
    // http://developer.yahoo.co.jp/start/
    String appid = "xxxx";

    YahooMorph yahooMorph = new YahooMorph(appid);

    // 形態素解析(その1)
    String text = "明日,晴れることを願っている。";
    ArrayList<Morpheme> result = yahooMorph.analyse(text);
    System.out.println("=== Example 1 ===");
    for(int i=0; i < result.size(); i++){
      Morpheme m = result.get(i);
      System.out.println(m.getSurface()
          + "\t" + m.getReading()
          + "\t" + m.getPos()
          + "\t" + m.getBaseform());
    }

    // 形態素解析(その2)
    text = "JavaもC言語もどちらも大切でしょう。";
    result = yahooMorph.analyse(text);
    System.out.println("=== Example 2===");
    for(int i=0; i < result.size(); i++){
      Morpheme m = result.get(i);
      System.out.println(m.getSurface()
          + "\t" + m.getReading()
          + "\t" + m.getPos()
          + "\t" + m.getBaseform());
    }
  }
}

EMアルゴリズムによるGMMパラメータの推定

トピックモデルでは EM アルゴリズムを用いるのが一般的なのに,EM の理屈を理解できていない。
そこで,GMM (Gaussian Mixture Model) のパラメータ推定を対象とし,
Simon J.D. Prince, Computer vision: models, learning and inference (2012)
で勉強しつつの,
http://nbviewer.ipython.org/github/tritemio/notebooks/blob/master/Mixture_Model_Fitting.ipynb
に掲載されていたコードを打ち込んでみた。

#coding: utf-8

'''
下記サイトに掲載されていたコード
http://nbviewer.ipython.org/github/tritemio/notebooks/blob/master/Mixture_Model_Fitting.ipynb
'''

import numpy as np
from numpy import random
from scipy.optimize import minimize, show_options
import matplotlib.mlab as mlab

np.random.seed(1)

# 2つの正規分布を重み (0.3, 0.7) で混合する
N=1000
a=0.3
s1 = random.normal(0, 0.08, size=N*a)
s2 = random.normal(0.6, 0.12, size=N*(1-a))
s = np.concatenate([s1, s2])

'''
import matplotlib.pyplot as plt
plt.hist(s, bins=20)
plt.show()
'''

# matplotlib.mlab.normpdf を用いて,混合確率密度を得る
def pdf_model(x, p):
    mu1, sig1, mu2, sig2, pi_1 = p
    return pi_1 * mlab.normpdf(x, mu1, sig1) + (1-pi_1) * mlab.normpdf(x, mu2, sig2)

# 繰返し回数を固定(本来は値が収束するまで反復)
max_iter = 100

# パラメータの初期設定(テキトー?)
p0 = np.array([-0.2, 0.2, 0.8, 0.2, 0.5])
mu1, sig1, mu2, sig2, pi_1 = p0
mu = np.array([mu1, mu2])
sig = np.array([sig1, sig2])
pi_ = np.array([pi_1, 1-pi_1])

gamma = np.zeros((2, s.size))  # 2 * サンプル数のゼロ行列
N_ = np.zeros(2)   # サイズ 2 のゼロベクトル
p_new = p0

# ここから EM ループ...
counter = 0
converged = False
while not converged:
    # Compute the responsibility function and new parameters
    for k in [0, 1]:
        # E-step
        gamma[k,:] = pi_[k] * mlab.normpdf(s, mu[k], sig[k]) / pdf_model(s, p_new)

        # M-step
        N_[k] = 1. * gamma[k].sum()
        mu[k] = sum(gamma[k] * s) / N_[k]
        sig[k] = np.sqrt(sum(gamma[k] * (s - mu[k])**2) / N_[k])
        pi_[k] = N_[k] / s.size
    p_new = [mu[0], sig[0], mu[1], sig[1], pi_[0]]
    # assert abs(N_.sum() - N) / float(N) < 1e-6
    # assert abs(pi_.sum() - 1) < 1e-6
    counter += 1
    converged = counter >= max_iter

print "parameters : ", p_new

実行結果を以下に示す。

parameters :  [0.0063749972625032486, 0.076249720004177929, 0.60298724972290296, 0.11940589580298418, 0.30040038558122012]

サンプル生成の際に仮定した密度関数のパラメータを推定できているようだ。

次に scikit learn を用いた実装を試した。

import numpy as np
from numpy import random
from sklearn import mixture

np.random.seed(1)

# 2つの正規分布を重み (0.3, 0.7) で混合する
N=1000
a=0.3
s1 = random.normal(0, 0.08, size=N*a)
s2 = random.normal(0.6, 0.12, size=N*(1-a))
s = np.concatenate([s1, s2])

#s = np.vstack(s)

clf = mixture.GMM(n_components=2, covariance_type='diag')
clf.fit(s)

print "weight : ", clf.weights_
print "mean : ", clf.means_
print "sigma : ", np.sqrt(clf.covars_)

sklearn を用いた場合の実行結果を以下に示す。

weight :  [ 0.69922406  0.30077594]
mean :  [[ 0.60316138] [ 0.00671512]]
sigma :  [[ 0.12332669] [ 0.08305453]]

ほぼ同じ値に落ち着いた。

2次元正規密度関数のヒートマップ

Simon J.D. Prince, Computer vision: models, learning and inference (2012) に頻繁に出てくるヒートマップに刺激されて真似てみた。

#coding: utf-8

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.mlab as mlab

def main():
    x = np.linspace(-4.0, 4.0, 200)
    y = np.linspace(-4.0, 4.0, 200)
    X, Y = np.meshgrid(x, y)

    # 2次元正規分布
    # matplotlib.mlab : MATLAB compatible command
    # matplotlib.mlab.bivariate_normal(X, Y, sigmax=1.0, sigmay=1.0, mux=0.0, muy=0.0, sigmaxy=0.0)

    # spherical covariance
    # Z = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0, 0.0)

    # diagonal covariance
    Z = mlab.bivariate_normal(X, Y, 2.0, 1.0, 0.0, 0.0, 0.0)

    # ヒートマップ
    plt.pcolor(X, Y, Z, cmap=plt.cm.hot)

    plt.colorbar()

    # タイトル
    plt.title('bivariate normal density')

    # ラベル
    plt.xlabel('$x$', size=12)
    plt.ylabel('$y$', size=12)

    plt.show()

if __name__ == '__main__':
    main()

描画されたヒートマップを以下に示す。
f:id:ymuto109:20150410105310p:plain

scipy を用いた Latent semantic indexing

#coding: utf-8

import numpy as np
from scipy import linalg

'''
Latent semantic indexing
'''
def main():
    # P.Baldi et al., 確率モデルによるWebデータ解析法, 森北出版, pp.96-98 に
    # 掲載されている行列を用いる
    X = np.matrix([[1,1,0,0,0,0,0,0,0,0,0],
                   [0,0,0,1,1,0,0,0,0,0,0],
                   [0,0,0,1,1,1,0,0,0,0,0],
                   [0,1,1,1,0,0,0,0,0,0,0],
                   [1,0,0,0,0,1,1,0,0,0,0],
                   [0,0,0,0,0,0,0,1,1,0,0],
                   [0,0,0,0,0,0,0,0,0,1,2],
                   [0,0,0,0,0,0,1,0,0,1,0],
                   [0,0,0,0,0,0,0,0,1,1,0],
                   [0,0,0,0,0,0,0,1,0,0,1]])

    # scipy.linalg.svd
    # http://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.svd.html
    u, sigma, v = linalg.svd(X)

    rank = np.shape(X)[0]
    u = np.matrix(u)
    # linalg.svd() の結果,sigma には特異値のリスト(降順)が返ってくるため
    # linalg.diagsvd を用いて行列に変換する
    # http://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.diagsvd.html
    sigma = np.matrix(linalg.diagsvd(sigma, rank, rank))
    v = np.matrix(v)

    # 特異値の大きい方から2個をとって X を再構成
    z = 2
    u2 = u[:, :z]
    sigma2 = sigma[:z, :z]
    v2 = v[:z, :]
    Xhat = u2 * sigma2 * v2

    print Xhat

    np.savetxt('result.txt', Xhat, fmt='%.02f')
    
if __name__ == '__main__':
    main()

実行結果は以下のとおり:

[[  1.18742376e-01   1.48468092e-01   1.02079902e-01   3.36802551e-01
    2.34722649e-01   2.07746172e-01   9.14487067e-02  -2.08551485e-03
    3.41743214e-03   2.40646404e-02   6.94193966e-04]
 [  2.52738635e-01   3.18786565e-01   2.19455902e-01   7.23753688e-01
    5.04297787e-01   4.44151468e-01   1.87007032e-01  -2.02395303e-02
   -4.72794523e-03   1.21686493e-02  -5.85747967e-02]
 [  3.45097561e-01   4.34173811e-01   2.98781826e-01   9.85492384e-01
    6.86710559e-01   6.05672638e-01   2.58391353e-01  -2.13348466e-02
   -1.66967515e-03   3.21884065e-02  -5.60326060e-02]
 [  2.59749943e-01   3.27600603e-01   2.25520701e-01   7.43758428e-01
    5.18237727e-01   4.56451866e-01   1.92276046e-01  -2.06330486e-02
   -4.73153071e-03   1.29213297e-02  -5.95614097e-02]
 [  1.87913028e-01   2.30024227e-01   1.57670040e-01   5.20783588e-01
    3.63113547e-01   3.25264417e-01   1.58277142e-01   2.47446747e-02
    2.67106562e-02   1.07397837e-01   1.07687758e-01]
 [  6.67965446e-03  -5.34773717e-03  -5.02206960e-03  -1.49947726e-02
   -9.97270298e-03   1.96295375e-03   4.28127228e-02   7.78060127e-02
    5.93812774e-02   1.93945477e-01   2.96197591e-01]
 [  4.62012230e-02  -2.07481947e-02  -2.19752154e-02  -6.34780797e-02
   -4.15028644e-02   2.51041386e-02   2.51467991e-01   4.45785034e-01
    3.40555624e-01   1.11315083e+00   1.69762265e+00]
 [  6.18128209e-02   5.37005262e-02   3.46060061e-02   1.16890844e-01
    8.22848376e-02   9.14040781e-02   1.12458081e-01   1.33074100e-01
    1.03684099e-01   3.44110267e-01   5.10254277e-01]
 [  2.34484339e-02   4.03363860e-03   2.90538891e-04   3.86562148e-03
    3.57508259e-03   2.30780273e-02   8.75820325e-02   1.43408667e-01
    1.09918087e-01   3.60212333e-01   5.46747060e-01]
 [  1.59795054e-02  -1.73708263e-02  -1.56109790e-02  -4.72126530e-02
   -3.16016740e-02   1.44687434e-03   1.15006052e-01   2.12170201e-01
    1.61833403e-01   5.28322325e-01   8.07542678e-01]]

読みづらいから,小数点以下2桁で表示すると次のようになる。

0.12  0.15  0.10  0.34  0.23  0.21  0.09 -0.00  0.00  0.02  0.00
0.25  0.32  0.22  0.72  0.50  0.44  0.19 -0.02 -0.00  0.01 -0.06
0.35  0.43  0.30  0.99  0.69  0.61  0.26 -0.02 -0.00  0.03 -0.06
0.26  0.33  0.23  0.74  0.52  0.46  0.19 -0.02 -0.00  0.01 -0.06
0.19  0.23  0.16  0.52  0.36  0.33  0.16  0.02  0.03  0.11  0.11
0.01 -0.01 -0.01 -0.01 -0.01  0.00  0.04  0.08  0.06  0.19  0.30
0.05 -0.02 -0.02 -0.06 -0.04  0.03  0.25  0.45  0.34  1.11  1.70
0.06  0.05  0.03  0.12  0.08  0.09  0.11  0.13  0.10  0.34  0.51
0.02  0.00  0.00  0.00  0.00  0.02  0.09  0.14  0.11  0.36  0.55
0.02 -0.02 -0.02 -0.05 -0.03  0.00  0.12  0.21  0.16  0.53  0.81

Windows form application でタイマーを使う

目的:一定の時間間隔ごとに特定の処理を行いたい。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace timer
{
    public partial class Form1 : Form
    {
        int value = 0;
        
        public Form1()
        {
            InitializeComponent();

            eventTimer.Interval = 1000; // 1000ミリ秒間隔
            eventTimer.Tick += new System.EventHandler(eventTimer_Tick);
            eventTimer.Enabled = true;

            button1.Text = "Disable timer";
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        private void eventTimer_Tick(object sender, EventArgs e){
            value += 1;
            textBox1.Text = value.ToString(); 
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if(eventTimer.Enabled == true){
                eventTimer.Enabled = false;
                button1.Text = "Enable timer";
            }
            else
            {
                eventTimer.Enabled = true;
                button1.Text = "Disable timer";
            }
        }
    }
}

Kinect のスケルトン情報を取得&表示

ケルトン情報の取得に関するサンプルが沢山あるのだけれど,表示については(pygame の例はあれど)OpenCV のみで対処したサンプルが見つからず。
こんなのでよいのかしら?

import cv2
import numpy as np
import pykinect
from pykinect import nui
import thread
import sys

# Video
def video_frame_ready( frame ):
    if videoDisplay == False: return

    with screenLock:
        video = np.empty( ( 480, 640, 4 ), np.uint8 )
        frame.image.copy_bits( video.ctypes.data )

        if skeletons is not None:
            for index, data in enumerate(skeletons):
                if data.eTrackingState != nui.SkeletonTrackingState.TRACKED: continue

                #get right hand position
                handRightPosition = data.SkeletonPositions[nui.JointId.HandRight]
                hr = nui.SkeletonEngine.skeleton_to_depth_image(handRightPosition, 640, 480)

                #get left hand position
                handLeftPosition = data.SkeletonPositions[nui.JointId.HandLeft]
                hl = nui.SkeletonEngine.skeleton_to_depth_image(handLeftPosition, 640, 480)

                print "(%d, %d)" % (int(hr[0]), int(hr[1]))
                cv2.circle(video, (int(hr[0]), int(hr[1])), 20, (255, 0, 0), thickness=10)
                cv2.circle(video, (int(hl[0]), int(hl[1])), 20, (0, 0, 255), thickness=10)

        cv2.imshow( 'frame', video )

# Depth
def depth_frame_ready( frame ):
    if videoDisplay == True: return

    depth = np.empty( ( 240, 320, 1 ), np.uint16 )
    frame.image.copy_bits( depth.ctypes.data )

    print skeletons
    if skeletons is not None:
        for index, data in enumerate(skeletons):
            if data.eTrackingState != nui.SkeletonTrackingState.TRACKED: continue
            #get head position
            headPosition = data.SkeletonPositions[nui.JointId.Head]
            hp = nui.SkeletonEngine.skeleton_to_depth_image(headPosition, 320, 240)

            cv2.circle(depth, (int(hp[0]), int(hp[1])), 20, (255, 255, 255), thickness=10)

    cv2.imshow( 'frame', depth )

    
def skeleton_frame_ready(frame):
    global skeletons
    skeletons = frame.SkeletonData

if __name__ == '__main__':
    screenLock = thread.allocate()

    videoDisplay = False

    kinect = nui.Runtime()

    skeletons = None

    #skeleton frame ready event handling
    #Reference : http://www.slideshare.net/pycontw/pykinect
    kinect.skeleton_engine.enabled = True

    kinect.skeleton_frame_ready += skeleton_frame_ready
    kinect.video_frame_ready += video_frame_ready
    kinect.depth_frame_ready += depth_frame_ready

    kinect.video_stream.open( nui.ImageStreamType.Video, 2, nui.ImageResolution.Resolution640x480, nui.ImageType.Color )
    kinect.depth_stream.open( nui.ImageStreamType.Depth, 2, nui.ImageResolution.Resolution320x240, nui.ImageType.Depth )

    cv2.namedWindow( 'frame', cv2.WINDOW_AUTOSIZE )

    while True:
        #waitKey() returns ASCII code of the pressed key
        key = cv2.waitKey(33)
        if key == 27: # ESC
            break
        elif key == 118: # 'v'
            print >> sys.stderr, "Video stream activated"
            videoDisplay = True
        elif key == 100: # 'd'
            print >> sys.stderr, "Depth stream activated"
            videoDisplay = False

    cv2.destroyAllWindows()
    kinect.close()

UniDic の能力はいかほど?

UniDic という辞書がありまして,優秀だというので試してみました。


UniDic プロジェクト日本語トップページ - SourceForge.JP

レビューや Twitter 等で使用される口語表現がターゲットであり,所謂,表記ゆれ問題への対処に使えないかと検討している段階です。

  • 例1:このハンバーグ,すげー美味しい。

「すげー」は「凄い」が語彙素であると正しく判定してくれる。

この	コノ	コノ	此の	連体詞		
ハンバーグ	ハンバーグ	ハンバーグ	ハンバーグ-hamburg	名詞-普通名詞-一般		
,			,	補助記号-読点		
すげー	スゲー	スゴイ	凄い	形容詞-一般	形容詞	終止形-一般
美味しい	オイシー	オイシイ	美味しい	形容詞-一般	形容詞終止形-一般
。			。	補助記号-句点		
EOS
  • 例2:このハンバーグ,おいしいぃぃぃ。

「おいしいぃぃぃ」の解釈は難しいようです。

この	コノ	コノ	此の	連体詞		
ハンバーグ	ハンバーグ	ハンバーグ	ハンバーグ-hamburg	名詞-普通名詞-一般		
,			,	補助記号-読点		
おいしい	オイシー	オイシイ	美味しい	形容詞-一般	形容詞終止形-一般
ぃ			ぃ	補助記号-一般		
ぃ			ぃ	補助記号-一般		
ぃ			ぃ	補助記号-一般		
。			。	補助記号-句点