コンピュータサイエンス系勉強ノート

計算機科学に限らず日々学んだことを色々まとめていきます

Selenium使ってみた(Python)

最近スクレイピングを始めたのですが,javascriptが使われている場合はブラウザ経由でないと操作ができないことを知ったのでseleniumを試してみることにしました.Pythonからのseleniumインストールは非常に簡単で,

pip install selenium

で一発でいけます.まずはseleniumからChromeを起動してみます.

#encoding:utf-8
from selenium import webdriver

browser = webdriver.Chrome(executable_path='Chromedriverのパス')
browser.get('http://seleniumhq.org/')

Chromeを起動するためにはChromeDriverとよばれるChromeを操作するためのドライバーが必要となります.ドライバーがない場合以下のサイトからダウンロードしてください.
Downloads - ChromeDriver - WebDriver for Chrome
コード中にダウンロードしたドライバのパスを入力します.私の環境ではドライバのパスを指定してあげないと以下の様なエラーがでました.

selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executable needs to be in PATH

ニコニコ動画へのログイン

練習がてらニコニコ動画seleniumを使って自動的にログインしてみます.

#encoding:utf-8
from selenium import webdriver

browser = webdriver.Chrome(executable_path='Chromedriverのパス')
browser.get('ニコニコ動画のログインページのURL')
browser.find_element_by_id("input__mailtel").send_keys("メールアドレス")
browser.find_element_by_id("input__password").send_keys("パスワード")
browser.find_element_by_id("login__submit").click()

Chromeの代わりにPhantomJSを使う

ChromeだといちいちGUIが立ち上がるため重いです.PhantomJSをドライバに指定するとGUIが立ち上がらなくて済みます.以下のサイトからダウンロードして,
Download | PhantomJS
以下のコードでPhantomJSでログインできます.

#encoding:utf-8
from selenium import webdriver

browser = webdriver.PhantomJS(executable_path="PhantomJSのパス")
browser.get('ニコニコ動画のログインページのURL')
browser.find_element_by_id("input__mailtel").send_keys("メールアドレス")
browser.find_element_by_id("input__password").send_keys("パスワード")
browser.find_element_by_id("login__submit").click()

今後の課題

最終的にはseleniumを使用してニコニコ動画のコメントを抽出したいです..だれか方法知ってたら教えてくだしあ

janomeで形態素解析してみた(Python)

Pythonで使える主な日本語形態素解析ライブラリにはMecabがありますが,導入までには色々と手間がかかります.(私だけかもしれませんが・・)
実はpip install一発でインストールできる日本語形態素解析ライブラリがあります.それがjanomeです.janomeMecabと同じように形態素解析が可能で,出力結果もMecabと同じような感じです.手っ取り早く形態素解析をしたい方にはお勧めなライブラリです.インストール方法は開発者さんのサイトに載ってる通りです.

開発者さんサイト
moco(beta)'s backup: 辞書内包/Pure Python実装の形態素解析器 Janome を公開しました

使い方も非常に簡単で,

#encoding:utf-8
from janome.tokenizer import Tokenizer

t = Tokenizer()
for token in t.tokenize(u"すもももももももものうち"):
    print(token.surface + " " + token.part_of_speech)
すもも 名詞,一般,*,*
も 助詞,係助詞,*,*
もも 名詞,一般,*,*
も 助詞,係助詞,*,*
もも 名詞,一般,*,*
の 助詞,連体化,*,*
うち 名詞,非自立,副詞可能,*

で単語とその品詞が表示されます.品詞の方もsplitメソッドで分割できます.pip install一発でインストールできたので非常に重宝しているライブラリのひとつです.

Pythonでテンプレートマッチング実装

Pythonでテンプレートマッチングを実装しました.類似度尺度には,正規化相互相関(NCC)・相互相関係数(ZNCC)・差の二乗和(SSD)・差の絶対和(SAD)の四種類を使用しました.

使用データ

画像は以下の画像を使用.
f:id:clientver2:20151112001032j:plain

マッチング対象画像

f:id:clientver2:20151112001034j:plain

パターン画像

正規化相互相関(NCC)

マッチングをかける画像と,パターン画像の輝度値をベクトルとみなしてコサイン類似度を求める感じ

def NCC(image1, image2):
    vec1, vec2 = image1.reshape(-1), image2.reshape(-1)
    numer = np.dot(vec1, vec2.T)
    denom = np.sqrt(np.sum(vec1 ** 2)) / np.sqrt(np.sum(vec2 ** 2))
    if denom == 0: return 0
    return numer / denom

def matching(image1, image2):
    image = image1.astype(np.double)
    pattern = image2.astype(np.double)
    height1, width1 = image.shape
    height2, width2 = pattern.shape
    output = np.zeros(image.shape)

    for i in range(height2 / 2, height1 - height2 / 2):
        for j in range(width2 / 2, width1 - width2 / 2):
            score = NCC(image[i-height2/2:i+height2/2+1, j-width2/2:j+width2/2+1], pattern)
            output[i,j] = score

    output /= np.max(output)
    maxidx = np.unravel_index(output.argmax(), output.shape)

    result = cv2.cvtColor(image1, cv2.COLOR_GRAY2RGB)
    cv2.rectangle(result, (maxidx[1] - width2 / 2, maxidx[0] - height2 / 2), (maxidx[1] + width2, maxidx[0] + height2), (0,255,0), 1)
    cv2.imshow("matching", result)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

相互相関係数(ZNCC)

平均値を引いてます.明るさ変化に対して強くなるようです.

def ZNCC(image1, image2):
    vec1, vec2 = image1.reshape(-1), image2.reshape(-1)
    vec1, vec2 = vec1 - np.mean(vec1), vec2 - np.mean(vec2)
    numer = np.dot(vec1, vec2.T)
    denom = np.sqrt(np.sum(vec1 ** 2)) / np.sqrt(np.sum(vec2 ** 2))
    if denom == 0: return 0
    return numer / denom

def matching(image1, image2):
    image = image1.astype(np.double)
    pattern = image2.astype(np.double)
    height1, width1 = image.shape
    height2, width2 = pattern.shape
    output = np.zeros(image.shape)

    for i in range(height2 / 2, height1 - height2 / 2):
        for j in range(width2 / 2, width1 - width2 / 2):
            score = ZNCC(image[i-height2/2:i+height2/2+1, j-width2/2:j+width2/2+1], pattern)
            output[i,j] = score

    output /= np.max(output)
    maxidx = np.unravel_index(output.argmax(), output.shape)

    result = cv2.cvtColor(image1, cv2.COLOR_GRAY2RGB)
    cv2.rectangle(result, (maxidx[1] - width2 / 2, maxidx[0] - height2 / 2), (maxidx[1] + width2, maxidx[0] + height2), (0,255,0), 1)
    cv2.imshow("matching", result)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

差の二乗和(SSD)

def SSD(image1, image2):
    vec1, vec2 = image1.reshape(-1), image2.reshape(-1)
    return np.sum((vec1 - vec2) ** 2)

def matching(image1, image2):
    image = image1.astype(np.double)
    pattern = image2.astype(np.double)
    height1, width1 = image.shape
    height2, width2 = pattern.shape
    output = np.ones(image.shape) * 10000

    for i in range(height2 / 2, height1 - height2 / 2):
        for j in range(width2 / 2, width1 - width2 / 2):
            score = SSD(image[i-height2/2:i+height2/2+1, j-width2/2:j+width2/2+1], pattern)
            output[i,j] = score

    maxidx = np.unravel_index(output.argmin(), output.shape)

    result = cv2.cvtColor(image1, cv2.COLOR_GRAY2RGB)
    cv2.rectangle(result, (maxidx[1] - width2 / 2, maxidx[0] - height2 / 2), (maxidx[1] + width2, maxidx[0] + height2), (0,255,0), 1)
    cv2.imshow("matching", result)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

差の絶対和(SAD)

def SAD(image1, image2):
    vec1, vec2 = image1.reshape(-1), image2.reshape(-1)
    return np.sum(np.abs(vec1 - vec2))

def matching(image1, image2):
    image = image1.astype(np.double)
    pattern = image2.astype(np.double)
    height1, width1 = image.shape
    height2, width2 = pattern.shape
    output = np.ones(image.shape) * 10000

    for i in range(height2 / 2, height1 - height2 / 2):
        for j in range(width2 / 2, width1 - width2 / 2):
            score = SAD(image[i-height2/2:i+height2/2+1, j-width2/2:j+width2/2+1], pattern)
            output[i,j] = score

    maxidx = np.unravel_index(output.argmin(), output.shape)

    result = cv2.cvtColor(image1, cv2.COLOR_GRAY2RGB)
    cv2.rectangle(result, (maxidx[1] - width2 / 2, maxidx[0] - height2 / 2), (maxidx[1] + width2, maxidx[0] + height2), (0,255,0), 1)
    cv2.imshow("matching", result)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

出力結果

4つともこんな感じで出力されます.矩形の位置も同じになりました.どの方法がいいのかはいまいち分からなかったので,また色々試したいと思います.
f:id:clientver2:20151112003802p:plain

参考文献

中京大学 工学部 橋本学 テンプレートマッチングの魅力
http://isl.sist.chukyo-u.ac.jp/Archives/SSII2013TS-Hashimoto.pdf

中心極限定理を確かめてみる(Python)

大数の法則はある分布からN個のサンプルを拾ってきたときNが無限大になればサンプル平均が期待値に収束するというものでした.それの上位版のような法則に中心極限定理があります.中心極限定理とは簡単に言えば,ある分布からサンプルをN個拾ってくることを繰り返すと,サンプル平均と期待値との差もしくは分散の分布が正規分布に従うという定理です.具体的には,

 W_N = \frac{(X_1 + X_2 + ... + X_N) - Nμ}{\sqrt{N}σ}

のようにサンプルの総和を正規化した確率変数 W_Nに置き換えてあげるとこの確率変数が正規分布に従うそうです.

中心極限定理を確かめる

実際にプログラムを書いて中心極限定理が成立することを確かめてみます.

#encoding:utf-8
import numpy as np
import matplotlib.pyplot as plt
import math

#カイ二乗分布で確かめる
def CentralLimitTheorem_ki():
    N = 10000
    Z = []
    for i in range(N):
        x = np.random.chisquare(2, 500)
        Z.append(np.sum(x) / math.sqrt(N) / np.std(x))
    nbins = 50
    plt.hist(Z, nbins, normed=True)
    plt.show()

#ポワソン分布で確かめる
def CentralLimitTheorem_poisson():
    N = 10000
    Z = []
    for i in range(N):
        x = np.random.poisson(2, 500)
        Z.append(np.sum(x) / math.sqrt(N) / np.std(x))
    nbins = 50
    plt.hist(Z, nbins, normed=True)
    plt.show()

def main():
    CentralLimitTheorem_ki()
    CentralLimitTheorem_poisson()

main()

f:id:clientver2:20151110170248p:plain

f:id:clientver2:20151110170250p:plain

確かにどちらのカイ二乗とポワソンのどちらの分布でも正規分布っぽくなってることが分かります.

大数の法則を確かめてみる(Python)

ご存知の方も多いと思いますが確率統計の分野で大数の法則というのがあります.大数の法則とは簡単に言えば,ある試行をN回繰り返して X_1, X_2,...X_Nのような確率変数が手に入ったとすると,

 Z_N = \frac{X_1 + X_2 + ... + X_N}{N} ・・・(1)

(1式)で求めた確率変数の平均が期待値に収束するというものです.

平均値と期待値の違い

平均値と期待値は別物で,平均値は確率的に揺らぐ値ですが期待値は全ての確率を考慮して重みづけされた揺らがない値です.

この大数の法則は揺らぐ値である平均値が無限回の試行で揺らがない値に収束すると言っています.どういうことなの・・

大数の法則を確かめる

よく分からないのでプログラムを組んで大数の法則を実際に確かめてみました.今回は6面のサイコロを複数回投げた場合を考えてみます.各面が出る確率は1/6で期待値は3.5です.サイコロを何回も投げて出た値の平均が3.5に収束していれば大数の法則が確かめられます.ソースコードは以下の通り.下手糞なコードですいません..

#encoding:utf-8
import random
import matplotlib.pyplot as plt
import numpy as np

#サイコロの期待値
def expected_value():
    prob = 1 / 6.0
    return sum([i * prob for i in range(1, 7)])

#サイコロを複数回振る
def LawOfLargeNumbers():
    iter = np.linspace(1, 5000, dtype=np.int)
    for i in range(10):
        values = []
        for j in iter:
            sum = 0
            for k in range(0, j):
                sum += random.randint(1, 6)
            values.append(sum / float(j))
        plt.plot(iter, values)
    plt.show()

def main():
    print(expected_value())
    LawOfLargeNumbers()

main()

f:id:clientver2:20151110143924p:plain

横軸が試行回数,縦軸が平均値です.10回試して10回とも確かに3.5付近に収束しています.大数の法則は本当の様です.

大数の法則の理屈

独立な試行をN回行って平均をとると分散が1/Nになるため,無限回の試行を行うと分散が0になり平均値が全く揺らがなくなります.即ち期待値に一致します.これを式で書くと,

 V[Z_N] = V[\frac{X_1 + X_2 + ... + X_N}{N}] = \frac{V[X_1 + X_2 + ... + X_N]}{N^2} = \frac{NV[Z_N]}{N^2} = \frac{V[Z_N]}{N}

ということだそうです.確かに分散が1/Nになることが分かります.

MNISTの手書き数字データで主成分分析の練習(Python)

今回はMNISTの手書き数字データを使って数字識別をやってみたいと思います.Pythonではscikit-learn内の関数を呼び出すことで簡単にデータをダウンロードできます.画像サイズは28×28ピクセルです.ソースコードは適当です.

ダウンロード用のコードは以下の通り.

from sklearn.datasets import fetch_mldata

mnist = fetch_mldata('MNIST original', data_home=".")
data = mnist["data"]
target = mnist["target"]

print(data)
print(target)


主成分分析をかけて主成分50個抜き出し,画像として表示してみます.

from sklearn.datasets import fetch_mldata
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt

mnist = fetch_mldata('MNIST original', data_home=".")
data = mnist["data"]
target = mnist["target"]

pca = PCA(n_components=50)
pca.fit(data)
components = pca.components_

for i in range(5):
    for j in range(10):
        plt.subplot(5, 10, 1 + 10 * i + j)
        plt.imshow(components[1 + 5 * i + j, :].reshape(28, 28))
plt.show()

f:id:clientver2:20151111010927p:plain


各数字ごとの主成分も表示してみます.

from sklearn.datasets import fetch_mldata
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
from sklearn.cross_validation import train_test_split

mnist = fetch_mldata('MNIST original', data_home=".")
data = mnist["data"]
target = mnist["target"]
train, test, train_label, test_label = train_test_split(data, target)

for i in range(10):
    number_data = data[target == i]
    pca = PCA(n_components=5)
    pca.fit(number_data)
    components = pca.components_

    for j in range(5):
        plt.subplot(10, 5, 5 * i + j)
        plt.imshow(components[j, :].reshape(28, 28))
plt.show()

f:id:clientver2:20151111010935p:plain

次に画像データを2次元にまで落として散布図を作成してみます

from sklearn.datasets import fetch_mldata
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
from sklearn.cross_validation import train_test_split

mnist = fetch_mldata('MNIST original', data_home=".")
data = mnist["data"]
target = mnist["target"]
train, test, train_label, test_label = train_test_split(data, target)

pca = PCA(n_components=2)
pca.fit(data)

vectors = pca.transform(train)
plt.plot(vectors[train_label==0, 1], vectors[train_label==0, 0], "o")
plt.plot(vectors[train_label==1, 1], vectors[train_label==1, 0], "o")
plt.plot(vectors[train_label==2, 1], vectors[train_label==2, 0], "o")
plt.plot(vectors[train_label==3, 1], vectors[train_label==3, 0], "o")
plt.plot(vectors[train_label==4, 1], vectors[train_label==4, 0], "o")
plt.plot(vectors[train_label==5, 1], vectors[train_label==5, 0], "o")
plt.plot(vectors[train_label==6, 1], vectors[train_label==6, 0], "o")
plt.plot(vectors[train_label==7, 1], vectors[train_label==7, 0], "o")
plt.plot(vectors[train_label==8, 1], vectors[train_label==8, 0], "o")
plt.plot(vectors[train_label==9, 1], vectors[train_label==9, 0], "o")
plt.show()

f:id:clientver2:20151111010940p:plain

点が重なりまくって意味が不明です.あとplotだらけで超不細工です.

次に訓練データからテストデータの識別ができるか試してみます.やり方は簡単で,各数字毎の訓練データを主成分分析にかけて50個の基底ベクトルを取り出します.この基底ベクトルは50次元の空間を構成してて,この空間に各数字データを射影してあげます.例えば,ある数字の訓練データがN個あるなら50次元のベクトルがN個できます.このN個のベクトルの平均を全ての数字について求めてあげます.今回は0~9の数字なので10個の平均ベクトルが求まりますね.すると入力を50次元の空間に射影してあげると,どの平均ベクトルに近いかが計算できます.一番近い平均ベクトルのラベルをその入力のラベルとします.

import numpy as np
from sklearn.datasets import fetch_mldata
from sklearn.decomposition import PCA
from sklearn.cross_validation import train_test_split

class Identificate_Number(object):
    def __init__(self, data, target, n_components=50):
        self.data = data
        self.target = target
        self.pca = PCA(n_components)
        self.pca.fit(self.data)
        self.vectors = self.pca.transform(self.data)
        self.calc_mean()

    def calc_mean(self):
        means = []
        for i in range(10):
            mean = np.mean(self.vectors[self.target == i], axis=0)
            means.append(mean)
        self.means = np.array(means)

    def identification(self, train_data):
        labels = []
        data = self.pca.transform(train_data)

        for vec in data:
            distance = np.sqrt(np.sum((self.means - vec) ** 2, axis=1))
            labels.append(np.argmin(distance))
        labels = np.array(labels)
        return labels

def main():
    mnist = fetch_mldata('MNIST original', data_home=".")
    data = mnist["data"]
    target = mnist["target"]
    train, test, train_label, test_label = train_test_split(data, target)
    identification = Identificate_Number(train, train_label)
    label = identification.identification(train)
    success = (label == train_label).mean() * 100
    print(success)

main()

このやり方で試したところ大体8割くらいは識別できたようです.