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

子育てしながらエンジニアしたい

現在 2 歳女の子の子育て中エンジニア。子育てとエンジニアを両立するにはどうすれば良いのか模索中。

読書録 - Python クローリング & スクレイピング

Python データサイエンス

今年から Python によるデータサイエンスの勉強をしています。
データサイエンスというからには、まずはデータを集めなければいけない、ということで
Amazon で評判のよかった以下の本を購入して、読んでみました。

全体を通して感じたこと

この本の「おわりに」にも書いてある一言を痛感しました。

何も持たない個人であっても巨人の肩に乗って技術の力で世界を良くしていける、
プログラミングの面白さが詰まった分野です。

この本では、いろんなライブラリーを駆使してクローリング & スクレイピングするテクニックが書かれています。
今まで C 言語で組み込みソフトをゴリゴリ書いていた人間にとっては、

「こんな複雑なことが、これだけでできちゃうんですか...」

という衝撃を受けるとともに、まさに「巨人の肩に乗って」をしないと、
これからはやっていけないんだなぁとしみじみ思ってしまいました。

こんな人におすすめ

私のように、クローリングやスクレイピングに新しく挑戦する人にとってはとても良い書籍だと思います。
Python 標準ライブラリ、サードパーティライブラリの使い方がとても丁寧に説明されています。
また「どうやったらできる」だけでなく、相手先のサイトに迷惑をかけないための方法もきちんと解説されており、
その書き方にはとても好感が持てました。

ただし、あらかじめ Python の文法についてはある程度知っておく必要があると思います。
とはいっても深い知識が必要なわけではないので、Python スクリプトを書いたことがあるよ~という方なら
読んでいけるのではないかなと思います。

というわけで、広くおすすめできる書籍です。

注意

この本の Appendix に Vagrant による仮想環境の構築方法が書かれています。
どういうわけか自分の環境 (Windows 10 Pro) では、Vagrant 起動時にエラーが出て、共有ディレクトリが
ちゃんと使えないという不具合がありました。
Vagrant のバージョンと Virtual Box のバージョンが合わなかったのかもしれません。

かなり長時間ハマってしまい、結局は Vagrant を使わずに普通に Ubuntu をインストールしたほうが早かったので、
これから環境構築する方はご注意いただければ。

この本を読んで、どう活用する?

ベタですけど株価予測とかに使っていきたいと思います。
ヘボい投資状況についても、そのうちブログで公開予定...

Python スクリプトを exe ファイル化したい

Python スクリプトを exe ファイル化したい

Python の良いところにはマルチプラットフォームで動くというのがありますが、
良くない、というか仕方ないところは、そのための実行環境を入れないといけません。
自分で使う分には良いですが、人に使わせるとなると、そこがハードルになります。
そのハードルを取り除くため、Windows 環境で動く exe ファイルにする方法を調べました。

方法

調べるといくつか方法があるようです。

ざっと情報を調べてみると、こんな一長一短があるようです。

py2exe PyInstaller cx_Freeze 備考
Python 3.5 対応 × py2exe は Python 3.4 まで
処理時間 × 参考記事
プラットフォーム × py2exe は Windows 向けのみ
シングル exe ファイル × cx_Freezeは 1 ファイルにまとめられない

処理時間とシングル exe ファイルができることから、py2exe を選びたかったのですが、
Python 3.5 に非対応だったので諦め...
ということで PyInstaller と cx_Freezeを試してみることにしました。

変換する Python スクリプト

tkinter GUI アプリケーションを変換したかったので、とりあえず簡単なテストアプリを作りました。
ファイルダイアログで選択したファイルパスを出力するだけです。


PyInstaller

導入

pip install で導入します。

$ pip install pyinstaller

変換

シングル exe ファイルで、コンソール画面を出さないようにしてみました。
といってもそれぞれ -F, -w オプションをつけるだけです。

オプション一覧

$ pyinstaller -F -w tkinter2exe.py

これだけで、dist ディレクトリの下に tkinter2exe.exe ができました。

起動

ちゃんと起動しました。
が遅い!!
感覚で言うと 4 秒くらいかかるような感じです。
ちなみに Python として起動したときは 1 秒もかかりません。
なおファイルサイズは 8.47 MB になりました。

cx_Freeze

結果的には、こちらはうまくいかずでした...

導入

pip install で導入します。

$ pip install cx_Freeze

変換

ここ を参考に以下のようなコマンドを叩きました。

$ cxfreeze tkinter2exe.py --target-dir dist

これでは cxfreeze が起動せず。
以下のようにちょっと修正。

$ python C:\Users\[Username]\Anaconda3\Scripts\cxfreeze tkinter2exe.py --target-dir dist

これでとりあえず cxfreeze は動くようになったのですが TCLが見つからないというエラーが出ます。
環境によるようなのですが、Stack overflow の情報をもとに環境変数を追加しました。

$ set TCL_LIBRARY=C:\Users\[Username]\Anaconda3\tcl\tcl8.6\
$ set TK_LIBRARY=C:\Users\[Username]\Anaconda3\tcl\tk8.6\

これで改めて

$ python C:\Users\[Username]\Anaconda3\Scripts\cxfreeze tkinter2exe.py --target-dir dist

こうして、dist フォルダの中に tkinter2exe.exe ができた。

起動

さて起動、、と思ったら起動せず...
setup.py を作ってみたり、いろいろやってみたものの、うまくいきませんでした。

まとめ

本当は py2exe, PyInstaller, cx_Freezeの比較までいきたかったけれど、
現時点では PyInstaller しか使えませんでした。
どうも Python の環境 (自分は Anaconda) にもだいぶ依存するようなので、
exe 化はまだまだハマりどころがいろいろあるようです。

Web ページから iOS アプリを起動したい - Custom URL scheme

やりたいこと

Web ブラウジングをしていて、ボタンやリンクを押すとアプリが起動する機能があります。
それを iOS で実現したい!ということで、その方法を調べました。
なお、ただ起動するだけでなく、起動時の URL も取得したかったので、その方法も含みます。

実現する方法

調べた感じでは、2 つの方法があるようです。

後者の Universal Links は iOS 9 で導入された機能のようで、UX 的にはこちらのほうが良さそうです。
ただ試すのにいろいろ制約 (SSL 接続必須とか、独自ドメインとか) があったので、今回は Custom URL scheme をやってみることにしました。
事情により Swift/Objective-C の両方でやる必要があったので、両方のやり方を紹介します。

Custom URL scheme

Custom URL scheme とは

ディープリンクとは、アプリの特定の画面に遷移(遷移先のアプリ側で実装が必要です)させることのできるリンクのことです。 iOSでは、 cm-app:// のような、アプリ固有の Custom URL Scheme を実装することで、これを実現できます。

ディープリンク(Custom URL Scheme)でアプリを起動する より

ウェブのリンクは通常 http:// とか https:// とかで始まります。
これをアプリ固有の myapp:// とか hogehoge:// とか (これを Custom URL scheme と呼ぶらしい) にすることで、この scheme を持ったアプリがインストールされていれば起動されます。

注意点としては、以下のようなことがあげられます。

  • 同じ scheme を持っているアプリがあった場合、どちらが起動するかわからない
  • scheme がなかったらエラーになる

後者に関してはいろいろ対策を考えられている方もいらっしゃるようです。

今回はここまで踏み込まず、アプリの起動と、そのときの URL の取得までを行います。

アプリに Custom URL scheme を設定する

これは非常に簡単です。
プロジェクトの Info -> URL Types を開き、+ ボタンを押します。
すると下記のようになるので、ここの URL Schemes に使いたい scheme 名を入力します。

f:id:edosha:20170502162802p:plain

ここでは sctest としました。

リンクを作成する

これもとっても簡単です。
html に以下のように記述すれば OK です。

<a href="sctest://">Launch!!</a>

iOS 側でこのリンクをクリックすると、以下のような画面になります。

f:id:edosha:20170502163340p:plain

これだけで、アプリの起動までは完了です。

起動時の URL を取得する

ただ今回はこれだけではなく、起動時の URL をアプリ側で取得したかったのです。
それによってアプリの動作を変えるってことも簡単にできるので。
たとえば html ファイルのリンクを以下のようにして、ユーザ名やパスワード等をアプリに渡すことを考えます。

<a href="sctest://user:password@192.168.100.100:5555/#fragment">Launch!!</a>

Swift/Objective-C それぞれやり方が微妙に違ったので、どちらも紹介します。

Swift

AppDelegate.swift ファイル内に AppDelegate クラスがあります。
そのクラスのどこかに以下のような記述を入れます。

func application(_ application: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
    print("scheme: \(url.scheme!)")
    print("user: \(url.user!)")
    print("password: \(url.password!)")
    print("host: \(url.host!)")
    print("port: \(url.port!)")
    print("fragment: \(url.fragment!)")
        
    return true
}

こうすると Xcode の出力部に以下のように出力されます。

f:id:edosha:20170502165800p:plain

なお url から取れる情報は、Apple の開発者ページが参考になります。
NSURL

実装は下記のページを参考にしました。
とても詳しく書いてあるのでおすすめです。
ディープリンク(Custom URL Scheme)でアプリを起動する

Objective-C

AppDelegate.m ファイルのどこかに以下のような記述を入れます。

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
    NSLog(@"scheme: %@", url.scheme);
    NSLog(@"user: %@", url.user);
    NSLog(@"password: %@", url.password);
    NSLog(@"host: %@", url.host);
    NSLog(@"port: %@", url.port);
    NSLog(@"fragment: %@", url.fragment);
    
    return YES;
}

こうすると、ターミナルのログに以下のように出力されます。

f:id:edosha:20170502170242p:plain

Swift と同じですが、url から取れる情報は、Apple の開発者ページが参考になります。
NSURL

ログの確認は以下のページを参考にしました。
iOS Simulatorのシステムログを確認する

さいごに

思ったよりも簡単にできてしまいました。
いや〜、iOS アプリすごいですね...

Xcode で Emacs キーバインドを使いたい - Hammerspoon を使う

Hello, Xcode

仕事で iOS アプリを作ることになりました。
当然ながら開発環境は Xcode になります。
今までで最も長い組み込み開発では、Emacs を使ってきました。
Python 開発は、Spyder を使っているものの、keyhac というソフトウェアを使うことで Emacsキーバインドにしていました。

というわけで XcodeEmacs キーバインドで使いたい!というのが今回の趣旨です。

キーマップの変更手段

El Capitan 以前は Karabiner というソフトを使って、キーマップを入れ替えるのが定番だったようです。
しかし macOS Sierra 以降では使えないとのこと...!!
後継のKarabiner Elements というソフトウェアもリリースされていますが、残念ながら Modifier (Ctrl とか Shift とか) を使ったキーリマップができないようです。

Windows で愛用している keyhac を使ってみましたが、いろいろうまくいかず断念。。

そして候補にあがってきたのが Hammerspoon です。

Hammerspoon

Hammerspoon とは

This is a tool for powerful automation of OS X. At its core, Hammerspoon is just a bridge between the operating system and a Lua scripting engine. What gives Hammerspoon its power is a set of extensions that expose specific pieces of system functionality, to the user.

Hammerspoon

基本的には Mac の自動化ツールだそうです。
Lua スクリプトで動作を記述し、それを OS に渡すというブリッジの役目をします。
API Document を見るとかなりいろいろできるのですが、今回はキーリマップのためだけに使います。

Hammerspoon のインストール

公式ページ から zip ファイルをダウンロードします。
Hammerspoon.app をアプリケーションディレクトリにコピーすれば OK です。

なお初回起動のときに、Hammerspoon への制御許可を求められます。
システム環境設定 -> セキュリティとプライバシー -> アクセシビリティで Hammerspoon にチェックを入れれば OK です。

f:id:edosha:20170501150426p:plain

キーバインドの実現

こちら の記事をかなり参考にさせていただきました。
というかコア部分はほとんど上記のスクリプトで、一部追加しただけです。

キーバインド

このスクリプトでは Xcode を開いているときに以下のキーバインドを実現します。

  • Ctrl + Space
    • Mark set
  • Ctrl + W
    • Cut
  • Alt + W
    • Copy
  • Ctrl + Y
    • Paste
  • Ctrl + X, Ctrl + F
    • Open quickly
  • Ctrl + X, Ctrl + S
    • Save file
  • Ctrl + X, N
    • 次のタブへ
  • Ctrl + X, P
    • 前のタブへ
  • Ctrl + X, K
    • タブを閉じる
  • Alt + Shift + ,
    • ファイルの先頭へ移動
  • Alt + Shift + .
    • ファイルの末尾へ移動
  • Ctrl + /
    • Undo
  • Ctrl + '
  • Ctrl + S
    • Search
  • Ctrl + K
    • Kill line
  • Alt + V
    • Page up
スクリプト

実際のスクリプトこれ です。

ちなみに私は Lua スクリプトの文法を全く知りませんが、スクリプトの改変はなんとかできました。
直感的だと思うので、見ればわかるかなと思います。


おわりに

Xcode での Emacs キーバインドができて、かなり快適になりました。
Hammerspoon 自体は応用範囲が広そうなので、もっといろいろできそうです。

Kaggle 初挑戦: タイタニック号の生存予測その 3 - ニューラルネットワークによる予測 -

Kaggle: タイタニック号生存予測シリーズ

ニューラルネットワークとは

ニューラルネットワークとは、脳神経系をモデルにした情報処理システムのことです。学習能力を持ち、必要とされる機能を、提示されるサンプルに基づき自動形成することができます。文字認識や、音声認識など、コンピュータが苦手とされている処理に対して有効です。

村上研究室 ニューラルネットワーク より

いわゆる人工知能の一つのアプローチです。
実は大学時代 (もう10年前か...)、人工知能の一分野である、強化学習というのを研究していました。
当時は実装が大変で、かなり挫折してました...
それが今は Python という言語に強力なライブラリがあるおかげで、とても簡単に実装できるようになっていました。
Python で作るニューラルネットワークの本「ゼロから作る Deep Learning」を買ったので、
せっかくなのでタイタニック号生存予測をニューラルネットワークでやることにしました。

ゼロから作る Deep Learning

Amazon でも大評判のこの本、読んでみました。
たしかに説明もわかりやすく、かつ Python の実装もちゃんと書かれているので、とても理解しやすいです。
また、素晴らしいのはコードが Github で公開されていることです。

Gitlab - ゼロから作るdeep learning

MITライセンスで自由に使えるとのことで、ありがたいです。
今回はこのソースからニューラルネットワークを使わせてもらうことにしました。

データの加工

前回の記事のように、まずはデータを加工します。

edosha.hatenablog.jp

前回の記事では試行錯誤しながらデータを加工しているので、これをまとめました。

# coding: utf-8
import pandas as pd
"""Titanic データの前処理をする

同じディレクトリに train.csv, test.csv を置いて使う。

Available functions:
    wrangle_data - データを加工する。返り値は train_df, test_df
                   (pandas のデータフレーム)
"""

def wrangle_data ():
    # Preprocess training data
    train_df = pd.read_csv ('train.csv')
    test_df = pd.read_csv ('test.csv')
    
    train_df['Title'] = train_df.Name.str.extract(' ([A-Za-z]+)\.', expand=False)
    test_df['Title'] = test_df.Name.str.extract(' ([A-Za-z]+)\.', expand=False)
    
    # train_df
    title_list = train_df['Title'].sort_values (inplace=False).unique ()
    pclass_list = train_df['Pclass'].sort_values (inplace=False).unique ()
    for title in title_list:
        for pclass in pclass_list:
            guess_df = train_df[(train_df['Title'] == title) & (train_df['Pclass'] == pclass)]['Age'].dropna ()
            train_df.loc[(train_df['Age'].isnull()) & (train_df['Title'] == title) & (train_df['Pclass'] == pclass), 'Age'] = guess_df.median ()
            
    # test_df
    title_list_t = test_df['Title'].sort_values (inplace=False).unique ()
    pclass_list_t = test_df['Pclass'].sort_values (inplace=False).unique ()
    for title in title_list_t:
        for pclass in pclass_list_t:
            guess_df = test_df[(test_df['Title'] == title) & (test_df['Pclass'] == pclass)]['Age'].dropna ()
            test_df.loc[(test_df['Age'].isnull()) & (test_df['Title'] == title) & (test_df['Pclass'] == pclass), 'Age'] = guess_df.median ()
            
    # test_df 'Title' == 'Ms' の補完
    guess_df = train_df[train_df['Title'] == 'Ms']['Age'].dropna ()
    test_df.loc[test_df['Age'].isnull(), 'Age'] = guess_df.median ()
       
    # train_df 'Embarked' の補完
    freq_port = train_df.Embarked.dropna().mode()[0]
    train_df['Embarked'] = train_df['Embarked'].fillna (freq_port)
    
    # test_df 'Fare' の補完
    guess_df = test_df[test_df['Pclass'] == 3].dropna()
    test_df.loc[test_df['Fare'].isnull(), 'Fare'] = guess_df['Fare'].median()
    
    df_list = [train_df, test_df]
    for df in df_list:
        df.loc[df['Age'] <= 8, 'Age'] = 0
        df.loc[(df['Age'] > 8) & (df['Age'] <= 16), 'Age'] = 1
        df.loc[(df['Age'] > 16) & (df['Age'] <= 32), 'Age'] = 2
        df.loc[(df['Age'] > 32) & (df['Age'] <= 48), 'Age'] = 3
        df.loc[(df['Age'] > 48) & (df['Age'] <= 64), 'Age'] = 4
        df.loc[(df['Age'] > 64) & (df['Age'] <= 80), 'Age'] = 5
        df['Fellow'] = df['SibSp'] + df['Parch']
        df['IsAlone'] = 0
        df.loc[df['Fellow'] == 0, 'IsAlone'] = 1
        df['Sex'] = df['Sex'].map ({'female': 0, 'male': 1}).astype (int)
        df['Embarked'] = df['Embarked'].map ({'C': 0, 'Q': 1, 'S': 2}).astype (int)
        df.loc[df['Fare'] <= 7.91, 'Fare'] = 0
        df.loc[(df['Fare'] > 7.91) & (df['Fare'] <= 14.454), 'Fare'] = 1
        df.loc[(df['Fare'] > 14.454) & (df['Fare'] <= 31), 'Fare']   = 2
        df.loc[df['Fare'] > 31, 'Fare'] = 3
        df['Fare'] = df['Fare'].astype(int)
        
    train_df = train_df.drop (['PassengerId', 'Ticket', 'Cabin', 'Name', 'Title', 'SibSp', 'Parch', 'Fellow'], axis=1)
    test_df = test_df.drop (['Ticket', 'Cabin', 'Name', 'Title', 'SibSp', 'Parch', 'Fellow'], axis=1)
    
    return train_df, test_df

if __name__ == '__main__':
    
    train_df, test_df = wrangle_data ()
    
    print (train_df.describe())
    print (test_df.describe())

ニューラルネットワークによる予測

いよいよ予測をします!
まずは使用するニューラルネットワークのライブラリを Github からダウンロードしてください。
common ディレクトリだけ使うので、それだけダウンロードしても大丈夫です。
そして、以下のようにスクリプトを配置してください。

dir
│   data_wrangle.py
│   test.csv
│   train.csv
│   train_neuralnet.py
│
├───common
│   │   functions.py
│   │   gradient.py
│   │   layers.py
│   │   multi_layer_net.py
│   │   multi_layer_net_extend.py
│   │   optimizer.py
│   │   trainer.py
│   │   util.py

train_neuralnet.py は以下のソースコードになってます。
自分の Github からもダウンロード可能です。

# coding: utf-8

"""titanic の生存予測を、ニューラルネットワークで行う

ニューラルネットワークのサイズは MultiLayerNet の引数で決まる。
"""

import numpy as np
from common.multi_layer_net import MultiLayerNet
import pandas as pd
import matplotlib.pyplot as plt
from common.optimizer import *
import data_wrangle

def change_one_hot_label(X):
    """書籍を参考。正解データを one_hot_label 形式にする"""
    T = np.zeros((X.size, 2))
    for idx, row in enumerate(T):
        row[X[idx]] = 1
        
    return T

# 前処理されたデータの取得
train_df, test_df = data_wrangle.wrangle_data ()

# データを学習物と答えに分ける
x_train = train_df.drop (['Survived'], axis=1).values
t_train = train_df['Survived'].values
t_train = change_one_hot_label (t_train)

# ニューラルネットワークの生成。
# 隠れ層は適当に 100, 100, 100 とした。
network = MultiLayerNet(input_size=6, hidden_size_list=[100, 100, 100], output_size=2, weight_decay_lambda=0.01)
# Optimizer の選択。AdaGrad() は common.optimizer 以下にある。
optimizer = AdaGrad()

iters_num = 1000
train_size = x_train.shape[0]
batch_size = 99

train_loss_list = []
train_acc_list = []

iter_per_epoch = max(train_size / batch_size, 1)

# 学習
for i in range(iters_num):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]
    
    # 勾配
    #grad = network.numerical_gradient(x_batch, t_batch)
    grad = network.gradient(x_batch, t_batch)
    
    # 更新
    optimizer.update(network.params, grad)
    
    loss = network.loss(x_batch, t_batch)
    train_loss_list.append(loss)
    
    if i % iter_per_epoch == 0:
        train_acc = network.accuracy(x_train, t_train)
        train_acc_list.append(train_acc)

# train データでどのように学習されたか図示
plt.plot (train_acc_list)

# テストデータの PassengerId を退避させる
test_df_id = test_df['PassengerId']
test_df = test_df.drop ('PassengerId', axis=1)
x_test = test_df.values

# テストデータの生存予測
output = network.predict (x_test)
output = np.argmax (output, axis=1)

# 生存予測を Kaggle に投稿する形に修正する
output_df = pd.DataFrame (output, columns=['Survived'])
output_df['PassengerId'] = test_df_id
output_df = output_df.ix[:, ['PassengerId', 'Survived']]
output_df.to_csv('test_ans.csv', index=False, encoding='utf-8')

さあ、ついにこれで test_ans.csv を得ました!
これを Kaggle に投稿してみましょう!

結果は....


f:id:edosha:20170429215836p:plain

0.7799!!

いまいち!!(笑)


まだまだ全然修行がたりないようです。
でも、こうやってデータ処理を競うっておもしろいですね。
これからも頑張ります。