Archives

Pythonのロギング

pythonの公式ドキュメントはどうにも不自由な日本語なのでまとめ直し

Loggingモジュールのイメージ

Loggerオブジェクト

loggerオブジェクトを各モジュールに明示する( logging.getLogger(“hogehoge”) )ことで、ログを収集する。

loggerは名前で管理され、違うモジュールでも同じ名前を指定すると同じloggerになる。

木構造

loggerは階層構造を持ち、名前をピリオドで分割することで表現する。

Python パッケージ名前空間におけるモジュール名は __name__ で取得できるので、


logging.getLogger(__name__)

を使ってloggerをモジュール単位で構成することが推奨される。

loggerとhandlerを組み合わせて君だけの最強のloggingを手に入れろ!

loggerがログを収集するものだとすれば、handlerはログを吐き出すオブジェクトである。

loggerとhandlerは多対多で紐付けることが可能である。

標準出力用のhandlerとログファイル出力用のhandlerを用意してどちらもルートロガーに紐付ければ、そのアプリケーションの全loggerの出力を標準出力&ログファイルに出力できる。

 logging

複数モジュールでstdoutとファイルにログを吐き出すサンプル

  • 構成例

# 実行ファイル
cook_sukiyaki.py
  
# 設定ファイル
settings.py
  
# モジュール群
Models
  |- Nabe.py
  |- Cooker.py
  |- SukiyakiGuzai.py
  |- Soup.py
  
# サブモジュール群
AussieBeef.py        # SukiyakiGuzai.pyでimport
Gas.py                     # Cooker.pyでimport
   :
   :
  • settings.py

#!/usr/bin/env python
# coding: utf-8
import os
import argparse
import logging
import logging.handlers
  
  
# デバッグか否かをコマンドラインオプションで指定
parser = argparse.ArgumentParser(description="Sukiyaki tsukuruyo!")
parser.add_argument("-d", "--debug", action="store_true", help="Debug mode.")
parser.add_argument("-b", "--beef", type=str, default="aussie", choices=["aussie", "koube"], help="Beef choice.")
# 引数のparse
args = parser.parse_args()
 
 
if args.debug:
    LOG_LEVEL = logging.DEBUG
else:
    LOG_LEVEL = logging.INFO
logger = logging.getLogger("") # root loggerに対して設定することで、これ以後使うloggerすべてに共通の設定が適用される。
formatter = logging.Formatter(
    fmt="%(asctime)s:[%(name)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
logger.setLevel(LOG_LEVEL)
 
# stdout出力
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(formatter)
stream_handler.setLevel(LOG_LEVEL)
logger.addHandler(stream_handler)
 
# ログ出力
file_handler = logging.handlers.RotatingFileHandler(
    os.path.join(os.path.abspath(os.path.dirname(__file__)), "log", "app.log"),
    maxBytes=50000000, backupCount=5)
file_handler.setFormatter(formatter)
file_handler.setLevel(LOG_LEVEL)
logger.addHandler(file_handler)
  
BEEF = args.beef
  • cook_sukiyaki.py

#!/usr/bin/env python
# coding: utf-8
import settings
  
from Nabe import Nabe
from Cooker import Cooker
from SukiyakiGuzai import SukiyakiGuzai
from Soup import Soup
  
from logging import getLogger
# __name__でloggerを指定することで、pythonの階層構造と同じようにloggerの名前が付与される。
logger = getLogger(__name__)
  
def cook_sukiyaki(nabe, cooker, guzai, soup):
   logger.info("Sukiyaki tsukuruyo!")
   logger.debug("Beef: " + guzai.beef)
     :
     :
  
if __name__ == "__main__":
    nabe = Nabe()
    cooker = Cooker()
    guzai = SukiyakiGuzai(beef=settings.BEEF)
    soup = Soup()
  • モジュール、サブモジュールでのloggingでの呼び出し(例: Nabe.py)

#!/usr/bin/env python
# coding: utf-8
  
# これを書くだけでモジュールごとにloggerが作られる。
from logging import getLogger
logger = getLogger(__name__) 
  
class Nabe:
    pass
  • log例

2016-07-04 18:26:51:[cook_sukiyaki.py] Sukiyaki tsukuruyo!
2016-07-04 18:26:56:[cook_sukiyaki.py] Beef: aussie
2016-07-04 18:27:00:[Models.Nabe] Nabe oitayo!

ハマりがちな落とし穴

なんか実行してもstdoutに表示されないんだけど

参考

Pythonのテスト環境

pythonのテスト環境についてちょっと調べてみたのでまとめ

py.test vs nose vs unittest

だいたい人気のあるtesting frameworkはこの3つ。

testing framework
説明
py.test テストの自動探索/実行機能などがあり、簡単に使える。黒魔術に頼っていたためにnoseというクローンが作られ、人気の座を奪われた。
nose 一番人気。python組み込みのunittest上に構築されているのも特徴。
unittest Pythonの組込みモジュール。xUnit型。unittest 2ではテストの自動探索機能などが追加されている。

歴史的には

  1. 2000年: Kent BeckがJUnitをリリース
  2. 2001年: xUnitの1つとしてunittestがPython 2.1に組み込まれる。
  3. ???: py.test リリース。この時点でテストの自動探索/実行機能を装備していたかは不明。
  4. ???: py.test 0.8の時点でメタプログラミングだらけの「黒魔術」が原因?でnoseがcloneとして作られる。noseが最初からunittest上に作られていたかは不明。
  5. 2010年: unittest2 リリース。自動探索機能を搭載。

など、各々便利な機能を取り込んで発展してきたもよう。

  • 活発に使われていること
  • 出自が「コードの汚さへの反発」というポジティブな目的があること
  • 組込みモジュールのunittestのテストを実行できるという柔軟さ

から、noseを使うのが良いと思う。

リポジトリ内での使い方

Jupyter notebookのレンダリングを行う公式webアプリであるnbviewerでの使い方が綺麗( https://github.com/jupyter/nbviewer/blob/master/tasks.py )。

travis-ci + invoke (Pythonのタスクランナー) + nose

という構成。

ここでは、

  1. travis ciを使用(.travis.ymlを実行)

    1. .travis.ymlでinvokeを使用して環境のセットアップやtestコマンドを外部ファイル化(tasks.py)

    2. pipのパッケージはrequirements.txtとして外部ファイル化。
    3. テストに必要なパッケージのインストールはrequirements-dev.txtとして分離。
  2. invokeでtasks.pyを実行

  3. tasks.pyの内部でnosetestsでテストを実行

という流れで自動テストを実行している。(なお、jupyter notebookのレポジトリではtravis ciだけでなくcircle ciも使っている)

このような構成とすることで、以下のメリットが得られる:

  • invokeでテストの一括実行が出来るようにすることで、ciサービス(ローカルでの手動実行も含め)間の差異を環境設定ファイルだけにとどめている。
  • テストだけでない環境構築等のタスクもinvokeとしてモジュール化できる。

構成サンプル


project_root
  |- app # アプリのディレクトリ
       |- tests
            |- test_*.py # テストコード
  |- .travis.yml
  |- tasks.py
  |- requirements.txt
  |- requirements-dev.txt
  |- Dockerfileなど
  • .travis.yml

language: python
 
python:
- '2.7'
- '3.3'
- '3.4'
 
before_install:
# テストに必要な環境を整える
- sudo apt-get update
- pip install -r requirements-dev.txt
 
 
install:
# アプリケーションに必要なパッケージをインストール
- pip install -r requirements.txt
 
 
script:
# テストの実行
- invoke test
  • tasks.py

import invoke
 
@invoke.task
def test(ctx):
    ctx.run("nosetests -v")
  • requirements-dev.txt

invoke
nose 
  • app/tests/test_example.py (例)

#!/usr/bin/env python
 
def test_numbers_3_4():
    assert 3 * 4 == 12

参考

TensorFlowをec2のgpuインスタンスで実行するまでの手順

Python、データ分析界隈はTensorFlowの登場でにわかに沸き立っていますが、実はTensorFlowはデフォルトでサポートしているgpuに制限があり、Nvidia Compute Capability 3.5 以上のgpuしか使えません。そして大体のひとが使うであろうawsのgpuインスタンスの GRID K520 のNvidia Compute Capabilityは3.0です。ドラゴンボールだと一般人以下です。つまりゴミ以下なのでTensorFlowは使えません。

もちろん既にGitHubでもissueが立っており、公式に対応してくれています。

https://github.com/tensorflow/tensorflow/issues/25

configure時に

TF_UNOFFICIAL_SETTING=1 ./configure

とすればNvidia Compute Capability選択させたるで!とのこと。やったね!

ってbuild必要なんかい!というわけで環境構築を行います。なお、途中でcuDNNが必要になりますが、cuDNNのダウンロードはnvdiaに登録後、1日ぐらい審査に時間がかかるので、早めに申請することをおすすめします。

前準備

awsのリージョンはオレゴンとかの田舎にしましょう。安いので。Ubuntu 14.04のインスタンスを起動します。


# localeをtokyoに変更
sudo cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
sudo locale-gen ja_JP.UTF-8

# rootになる
sudo su -

# 各種ツールをインストール。
# linux-image-extra-virtualのインストールでは、"install the package maintainer's version"を選択する。(cudaのインストールに必要。)
apt-get update && apt-get install -y build-essential linux-image-extra-virtual wget git default-jdk zip zlib1g-dev swig

# cudaをインストールする前に既存のドライバ(nouveau)を無効化する
vi /etc/modprobe.d/blacklist-nouveau.conf
# 下記を書いて保存 ----------------------
blacklist nouveau
blacklist lbm-nouveau
options nouveau modeset=0
alias nouveau off
alias lbm-nouveau off
# ---------------------------------------

echo options nouveau modeset=0 | sudo tee -a /etc/modprobe.d/nouveau-kms.conf
update-initramfs -u
reboot


# 再起動後、anaconda pythonをインストール
sudo su -
wget --quiet https://repo.continuum.io/archive/Anaconda-2.3.0-Linux-x86_64.sh && /bin/bash Anaconda-2.3.0-Linux-x86_64.sh -b -p /opt/conda

# pathの設定
vi /etc/profile.d/path.sh
# ---------------------------------------
export PATH=/opt/conda/bin:$PATH
# ---------------------------------------

# path設定を読み込む
source /etc/profile.d/path.sh
# default encodingをutf-8にする。(TensorFlowには関係ないけど、やっといた方が後々助かる)
vi /opt/conda/lib/python2.7/sitecustomize.py
# ---------------------------------------
import sys
sys.setdefaultencoding('utf-8')
# ---------------------------------------

# CUDA 7.0のインストール (7.5は未サポート)
apt-get install -y linux-source linux-headers-$(uname -r)
export CUDA_MAJOR=7.0
export CUDA_VERSION=7.0.28
export CUDA_MAJOR_U=7_0
cd /tmp && wget http://developer.download.nvidia.com/compute/cuda/${CUDA_MAJOR_U}/Prod/local_installers/cuda_${CUDA_VERSION}_linux.run && chmod +x cuda_*_linux.run && ./cuda_*_linux.run -extract=`pwd` &&   ./NVIDIA-Linux-x86_64-*.run -s
modprobe nvidia
./cuda-linux64-rel-*.run
./cuda-samples-linux-*.run

# 確認 (これをしないと/dev/以下にデバイスファイルが出てこない)
cd /usr/local/cuda/samples/1_Utilities/deviceQuery
make
./deviceQuery
# ls /dev/ すると、nvdia系のデバイスが見えるようになっている。

# cudnn 6.5
# cudnn-6.5-linux-x64-v2.tgzはnvdiaのサイトに登録して自分でダウンロードしてくる
tar xvzf cudnn-6.5-linux-x64-v2.tgz 
cp cudnn-6.5-linux-x64-v2/cudnn.h /usr/local/cuda/include
cp cudnn-6.5-linux-x64-v2/libcudnn* /usr/local/cuda/lib64
reboot

# 再起動後、buildに必要なbazelをインストール
git clone https://github.com/bazelbuild/bazel.git
cd bazel
# バージョンは0.1.0のみサポート
git checkout tags/0.1.0
./compile.sh
sudo cp output/bazel /usr/bin

TensorFlowのインストール


# install tensorflow
cd ~/
# submoduleとしてprotocol buffersを使うので、オプションが必要
git clone --recurse-submodules https://github.com/tensorflow/tensorflow
cd tensorflow

# configureで、Nvidia Compute Capability に3.0を許可する。
TF_UNOFFICIAL_SETTING=1 ./configure

# 以下のような出力が出るので、書いたとおりに入力する
Do you wish to build TensorFlow with GPU support? [y/n]  -> y エンター
GPU support will be enabled for TensorFlow

Please specify the location where CUDA 7.0 toolkit is installed. Refer to README.md for more details. [Default is /usr/local/cuda]: -> エンター
Please specify the location where CUDNN 6.5 V2 library is installed. Refer to README.md for more details. [Default is /usr/local/cuda]: -> エンター

WARNING: You are configuring unofficial settings in TensorFlow. Because some external libraries are not backward compatible, these settings are largely untested and unsupported. 

Please specify a list of comma-separated Cuda compute capabilities you want to build with.
You can find the compute capability of your device at: https://developer.nvidia.com/cuda-gpus.
Please note that each additional compute capability significantly increases your build time and binary size.
[Default is: "3.5,5.2"]: -> 3.0 エンター
Setting up Cuda include
Setting up Cuda lib64
Setting up Cuda bin
Setting up Cuda nvvm
# ---------------------------------------

# ビルド時のデフォルトのincludeディレクトリは /usr/include/python2.7 になっているので、
# includeディレクトリをanaconda pythonのものに変更 (ソースを変更するのでも良いと思う)
sudo ln -s /opt/conda/include/python2.7 /usr/include/python2.7
sudo ln -s /opt/conda/lib/python2.7/site-packages/numpy/core/include/numpy /opt/conda/include/python2.7/numpy

# cuda, cnnのライブラリをシンボリックリンク (pipのパッケージを作成するときに必要。)
# バッドノウハウっぽいのでもっとスマートなやり方があれば知りたい
sudo ln -s /usr/local/cuda/lib64/* /opt/conda/lib

# build
bazel build -c opt --config=cuda //tensorflow/cc:tutorials_example_trainer

# テスト実行
# エラーが出なければおk
/usr/local/cuda/samples/1_Utilities/deviceQuery/deviceQuery
bazel-bin/tensorflow/cc/tutorials_example_trainer --use_gpu


# pipパッケージをbuild
sudo /opt/conda/bin/pip install --upgrade pip
sudo /opt/conda/bin/pip install wheel

bazel build -c opt --config=cuda //tensorflow/tools/pip_package:build_pip_package
bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/tensorflow_pkg
sudo /opt/conda/bin/pip install /tmp/tensorflow_pkg/tensorflow-0.5.0-py2-none-any.whl

# pythonからの動作テスト(mnist)
cd tensorflow/models/image/mnist
python convolutional.py

こんな感じでなんとかインストールして実行出来ました。
マイami化しておくと、次に使う時は便利です。
早くpipのパッケージでもec2サポートしてくれれば良いのですが。。。

ほんとに速くなるのか

Python – TensorFlow 畳み込みニューラルネットワークで手書き認識率99.2%の分類器を構築 – Qiita

この記事で使われているCNNのサンプルを、gpuバージョンとそうでないバージョン(どちらもg2.2xlarge)で実行したところ、

gpuアリ: 10分ぐらい

gpuナシ: 50分ぐらい

で学習終了しました。最初に上げたGitHub issueでは、「そもそもawsのgpuインスタンスは速くない」みたいな話が出てました。まあこんなところかなという気がします。

画像処理で見るグッゲンハイム ヘルシンキ

(追記: どの応募案がどのクラスに分類されたかの結果を貼り忘れていました。CSVファイルをダウンロード)

2014年の建築界隈の主要トピックスの一つであるGuggenheim Helsinkiのデザインコンペティションは1715の応募数を集め、2002年の大エジプト博物館コンペの応募数1557を凌ぐ史上最大規模の建築デザインコンペとなったようです。(参考)

建築デザインの経済的求心力がビルバオ・グッゲンハイムで明らかになったことが、人々の関心を建築に集めたことは間違いないでしょう。

応募案はhttp://designguggenheimhelsinki.org/stageonegallery/view/ にアクセスすれば一覧出来ますが、とにかく多すぎる。審査員もうんざりしたことでしょう。僕は傾向を見たいだけなので、適当に画像処理技術を使ってクラスタリングしてみました。

審査員も適当にザッピングして評価してるよ。きっと。建築の歴史はメディアの歴史でどうこうむにゃむにゃ言うつもりはありません。

素材集め

http://designguggenheimhelsinki.org/stageonegallery/view/にアクセスしてctrl+sでhtmlと画像ファイルをダウンロードします。なぜかファイナリストの画像だけダウンロードできなかったので、ファイナリスト6人のエントリーIDごとに”http://a5.designguggenheimhelsinki.org/GH-1128435973-partC1.jpg”にアクセスして画像をダウンロードします。

これで150×150ピクセルの1715個の画像が集まりました。

処理系の準備

解析に使うのはもちろんPython。なぜなら彼もまた特別な存在だからです。

Anacondaをインストールすればだいたい必要なもの(numpyとかpandasとか)はインストールされます。

あとは追加で画像処理ライブラリのpillow(PIL)をインストールしましょう。コマンドプロンプトから以下をタイプ。

easy_install pillow

手法

教師ありの画像認識的な分類(食べ物の画像かどうか)の例はいくつかあったのですが、教師なしで応募案の画像をクラスタリングする、という感じの例はありませんでした。

なので、画像を読み込んで特徴ベクトル化するところまでは教師あり分類の例にしたがって、特徴ベクトル群をk-meansでクラスタリングしてみます。

参考にしたのは以下。

まず、1715個の画像を読み込んで、150×150のRGB値のデータをRandomized PCAで2次元まで減らしてプロットしてみます。

pca_feature

 

あんまりうまいこと分かれている感じはしませんが、まあそんなもんでしょう。

では、今度は20次元にしたデータをscikit-learnのk-meansで8つのクラスタに分けてみます。

クラス0: 216案, ファイナリスト1(GH-76091181)

0

青空の遠景にひっそりとたたずむ画像がほとんどです。あっ、コンペ要旨の割と最初らへんに出てくる画像ですねコレ。堂々とした正攻法が好印象です。

ここに属するファイナリストはGH-76091181。色にこだわりを感じます。

76091181

クラス1: 181案, ファイナリスト0

1

光と影のコントラストを強調したパースが多いです。この建築のここがいいっしょ!?という強い意志とフェティシズムを感じます。しかし周辺との関係はわかりづらいかも。

クラス2: 284案, ファイナリスト1(GH-121371443)

2

 

全体的に薄暗く、描線の多い絵 or 雲の存在感のあるマジックアワー、といったところ。雲の画像の方はコンペ要旨の最後らへんの画像です。

ファイナリストはGH-121371443。美しいです。
121371443

クラス3: 186案, ファイナリスト0

3

クラス2よりもさらに時間は進み、夜との境目~夜。個人的にはこれぐらいの時刻の写真が好きです。

クラス4: 250案, ファイナリスト3(GH-04380895, GH-1128435973, GH-5631681770)

4

 

全体的に低めな彩度や霞のかかった画像。エッジの効いた彫刻のような存在感ではなく、曖昧な視界にヌッと現れる不気味な存在感、という感じ。

ファイナリストはGH-04380895, GH-1128435973, GH-5631681770と3つも属しています。

04380895 5631681770 1128435973

 

 

 

 

 

カッチリした形で勝負するのでないところが、ヘルシンキという文脈に合うのかもしれません。

クラス5: 271案, ファイナリスト0

5

 

さらに白い。白いが昼間の画像が多い感じです。ヘルシンキの天気を考慮したもののその文脈での美しさを見つけられなかった、という感じでしょうか。

クラス6: 210案, ファイナリスト1(GH-5059206475)

6

白すぎだろ!空が無いものだったり背景が白の図面などが属します。

ファイナリストはGH-5059206475

5059206475

 

うーん白い。白いっていうかなんだこれは。

クラス7: 117案, ファイナリスト0

7

上下余白のある画像です。そういうの分類しにくいからマジ勘弁です。

ここまででクラス4が強いことが分かりました。彩度は低めで、かと言って低すぎず、明度も高すぎず、そしてなにより光っていること。超大事なようです。

ではクラス4をさらに3つぐらいに分割してみましょう。

クラス4-0: 76案, ファイナリスト1(GH-5631681770)

4-0

クラス4-1: 59案, ファイナリスト0

4-1

クラス4-2: 115案, ファイナリスト2(GH-04380895, GH-1128435973)

4-2

やはり霧か…

僕の持論として、美しい画像 = 一つに統一された傾向を持っている画像という法則があるのですが、霧の画像というのは、全体に一つのフィルタをかけるようなものなので統一感が出しやすいのですね。他に統一感のある画像としては、夕焼け(色相)、落ち葉(ベクトル)、木漏れ日(色相、コントラスト)などがありますね。

とにかくここで結論として言えることは、ヘルシンキのコンペでは

  • 霧を使うこと
  • 建築を光らせること
  • 暗すぎてもだめ

という部分を守ると当選確率が上がるかも、という結果論です。これで誰か僕を建築批評シンポジウムか何かに呼んでくれるでしょう(ワクワク)。

一応最後にコードを載せておきます。


#-*- encoding: utf-8 -*-

from PIL import Image
import numpy as np
import os
import pandas as pd
import pylab as plt
import shutil
from sklearn.decomposition import RandomizedPCA
from sklearn.cluster import KMeans


STANDARD_SIZE = (150, 150)


def img_to_matrix(filename, verbose=False):
    """
    load img file as a np.ndarray
    """

    img = Image.open(filename)
    if verbose:
        print('changing size from %s to %s' % (str(img.size), str(STANDARD_SIZE)))
    img = img.resize(STANDARD_SIZE)
    imgArray = np.asarray(img)

    return imgArray

def flatten_image(img):
    """
    takes in an (m, n) numpy array and flattens it 
    into an array of shape (1, m * n)
    """

    s = img.shape[0] * img.shape[1] * img.shape[2]
    img_wide = img.reshape(1, s)
    return img_wide[0]

def plot_in_2_dimensions(data):
    """
    plot in 2 dimensions. (Reshape by RandomizedPCA)
    """

    pca = RandomizedPCA(n_components=2)
    X = pca.fit_transform(data)

    df = pd.DataFrame({"x": X[:, 0], "y": X[:, 1], "name": images})
    colors = ['red', 'yellow']
    plt.scatter(df['x'], df['y'], c=colors[0], label=images)

    # labels
    # for image, x, y in zip(images, X[:,0], X[:,1]):
    #     plt.annotate(image, xy = (x, y), xytext = (-20, 20),
    #     textcoords = 'offset points', ha = 'right', va = 'bottom',
    #     bbox = dict(boxstyle = 'round,pad=0.5', fc = 'black', alpha = 0.5))

    plt.savefig('pca_feature.png')
    plt.show()

def main():
    img_dir = 'images/'
    images = [img_dir + f for f in os.listdir(img_dir)]

    data = []
    for image in images:
        img = img_to_matrix(image)
        img = flatten_image(img)
        data.append(img)

    data = np.array(data)

    # plot_in_2_dimensions(data)

    # k-means clustering
    pca = RandomizedPCA(n_components=20)
    X = pca.fit_transform(data)
    km = KMeans(n_clusters=3)
    labels = km.fit_predict(X)


    for i, image in enumerate(images):
        print(image[7:] + "," + str(labels[i]))
        shutil.copyfile(image, "clustered/" + str(labels[i]) + image[6:])



if __name__ == '__main__':
    main()

 

OpenStreetMapのwayデータをcsvにしてgephiでnode positionも付けて表示する

タイトルのまんま。まずはOpenStreetMap.orgに行って目的の場所を表示する。

8f4601311e11b61b68c6ef306014d238-1024x511

画面上の「エクスポート」ボタンをクリックしてエクスポート画面を呼び出したら、「エクスポート」でデータのダウンロードが開始される。範囲が広すぎるとダウンロードされないので、そういうときはエクスポートボタン下のOverpass APIとかからダウンロードする。OpenStreetMap JapanはUIが違ってエクスポート方法はよくわからない。

これでダウンロードされるファイルは「.osm」と拡張子のついた(もしくは拡張子の無い)ファイルだが、実態はXMLである。

OSM XML – OpenStreetMap Wiki

データは「ノード」とその連続する集合である「ウェイ」、ノードやウェイの集合である「リレーション」からなる。簡単に言えば点と線とグループといったところ。道の情報を取り出したいので「ウェイ」の部分を”始点ノード,終点ノード”のcsvファイルに直したい。またノードやウェイ、リレーションには図のような感じで<tag k=”ラベル(key)” v=”値”>のように特徴が記述されている。これらも必要なものは取り出したい。

スクリーンショット_122413_031316_AM

という訳でXMLをパースしてそれを取り出すスクリプトを書いた。

https://gist.github.com/Drunkar/8102028#file-osm_extractway-py

https://gist.github.com/Drunkar/8102055#file-osm_extractnode-py

例えばosm_extractWay.pyを実行すると、ファイル名は?と聞かれるので同じフォルダに置いてあるosmファイルの名前を入力する。そしたら、ウェイに定義されてるすべてのkeyが表示されるので、表示したいものだけをカンマ区切りで指定。

スクリーンショット_122413_021210_AM

「ええのんか?」と聞かれるのでy。そしたら*_edges.csvが出力される。

スクリーンショット_122413_021821_AM

それでここからはそれをgephiで表示させる話。gephiでは「始点ノードID,終点ノードID」という形のcsvファイルでグラフをインポートできる(辺が登場する回数がそのまま重みになる)ので、srcとdstの列だけを残す。ラベルの1行目は消す。それでgephiにインポートすると以下の感じになる。

スクリーンショット_122413_022716_AM

「僕の知ってる烏丸四条と違う…」

というのも座標がランダムで与えられているので当たり前。しかしgephiはノードに座標を与えるのがちょっと面倒くさい。まず、「データ工房」でデータのエクスポートを行う。ノードテーブルと辺テーブルのどちらもエクスポートする。*_edges.csv[Nodes].csvみたいなファイルで出力されているノードテーブルに、osm_extractNode.pyで出力したノード情報の「lat」(緯度)と「lng」(経度)の列を追加。

スクリーンショット_122413_023902_AM

(latとlngは描画を見やすくするため5000倍に再マッピングしている。)

nodeリストに登場しているけれどedgeの端点としては出てこない点ももちろんあるのでそれは消す。

ここまで出来たらようやく表示することができる。gephiで新規プロジェクトを作成。いきなり「データ工房」から「ノードテーブル」をインポートする。必ずノードテーブルから。「lat」と「lng」を「float」で取り込みます。

続いて”karasuma.osm_edgelist [Edges].csv”を辺のテーブルとして取り込みます。
取り込めたらlatとlngの値をノードの位置として設定します。これにはgephiのプラグインの”Data Laboratory Helper”が必要。

Gephi forums ・ View topic – [TODO] Preset x and y coordinates

プラグインをインストールしたら、「データ工房」>「作業を継続」>「Set standart column」を選択

スクリーンショット_122413_024706_AM「Column」を「lat」に、「Copy to」を「Y-coordinate」にして「Set column」をクリック。すると「OK」という表示が出ます。出ない場合は「lat」列とnodeの数が合ってません。同様に「lng」を「X-coodinate」にセット。最後に「OK」をクリック。

そして「概観」に戻ると・・・

スクリーンショット_122413_030103_AM

 

おおおお。(なんか一部アレだけど)それっぽい。試しに地図の画像となんとなく合わせてみると、

karasumaGousei

 

いい感じ。しかしOpenStreetMapはGPSのプロットデータなのでノードがやたらと多い。試しに下の図で示すぐらいもっと大きな範囲でやってみたところ、*_edges.csvのデータ量が30GB近くになってしまった。スクリーンショット_121813_085638_PM

ノードは交差点だけでいいから、隣接ノードとの角度からノードを減らすようにする必要がありそう。それはまた今度

 

Pythonでプロセスを監視して終了したらgmailを使ってメール送信する

ps ax | grep hogehoge | grep -v grep

に”hogehoge”が含まれてるかを確認して無かったらgmailを送信する。

# -*- coding: utf-8 -*-
import commands
import sendGmail
from datetime import datetime
from datetime import timedelta

if __name__ == '__main__':
    arg = ""
    while len(arg) == 0:
        arg = raw_input("input substring of process name: ")

    INTERVAL   = timedelta(seconds=5)
    from_addr = "<FROM_ADDR>@gmail.com"
    passwd = "<PASS>"
    to_addr = "<TO_ADDR>@gmail.com"
    title = arg + " end."
    body = ""

    previous = datetime.now()
    while True:
        if datetime.now() - previous > INTERVAL:
            result = commands.getoutput("ps ax | grep "+ arg + " | grep -v grep")
            previous = datetime.now()
            if arg not in result:
                    msg = sendGmail.create_message(from_addr, to_addr, title, body)
                    sendGmail.send_via_gmail(from_addr, to_addr, passwd, msg)
                    break;

sendGmail.pyは以下のページまんまです。

独学Linux | Python スクリプトでGmail経由のメールを送信する方法

#!/usr/bin/python
# -*- coding: utf-8 -*-
import smtplib
from email.MIMEText import MIMEText
from email.Header import Header
from email.Utils import formatdate

def create_message(from_addr, to_addr, subject, body):
    msg = MIMEText(body)
    msg['Subject'] = subject
    msg['From'] = from_addr
    msg['To'] = to_addr
    msg['Date'] = formatdate()
    return msg

def send_via_gmail(from_addr, to_addr, passwd, msg):
    s = smtplib.SMTP('smtp.gmail.com', 587)
    s.ehlo()
    s.starttls()
    s.ehlo()
    s.login(from_addr, passwd)
    s.sendmail(from_addr, [to_addr], msg.as_string())
    s.close()

if __name__ == '__main__':
    from_addr = "<FROM_ADDR>@gmail.com"
    passwd = "<PASS>"
    to_addr = "<TO_ADDR>@gmail.com"
    title = "title"
    body = "hongyaaa"
    msg = create_message(from_addr, to_addr, title, body)
    send_via_gmail(from_addr, to_addr, passwd, msg)

プロセス監視のツールとかありそうだけどよく分からん…目指せじょうよわ脱却

arduinoで植物と話したい

11173216365_c5e227b830_c

我が家には友人からもらった大事な幸福の木[Massangeana]があります。5年も一つ屋根の下で暮らしてきたのだからそろそろ会話とかできてもいいんじゃないかな。

田中浩也さんの研究室でそういう研究があることを以前から知っていました。

植物をインターフェースとしたデジタルメディア開発

植物の生体電位を計測することで、植物が環境に対してどのように反応するのかを明らかにする研究です。

生体電位とは、 「生体の生命維持活動に関わる情報伝達によって生じる電気信号」ですって。なにそれこわい。

『植物生体電位とコミュニケーション』という本によれば、日光やらに対して反応する細胞膜中の受容たんぱく質がというのがあって、それが反応すると例えばH+やらのイオンを細胞膜外に運び出す程度の能力「H+ポンプ」が発動し、細胞膜内の電位が変化する。そして、細胞同士はシンプラスト構造というやつで電気的につながっているので、冒険野郎膜外バーは連鎖していくようです。

これは「環境応答」というやつで、他にも植物の代謝にともなう電位の変化とかもあります。

つまり、植物生体電位を計測出来れば、「澄ました顔してても電位は正直だなァ(ゲス顔)」みたいな感じで、植物と少しだけ仲良くなれるかもしれません。ちなみに植物生体電位の研究は以前からあるんですが、前述の田中浩也研究室の研究は「良い電位の計測場所」に関して特許を取得してて、OH…さすがKEIO…という気持ちになったことは秘密です。

さて、ではどうやって生体電位を測るのかですが、もっとも良い方法はガラス微小電極にようにものすごく細かい電極を使って細胞膜の内外にダイレクトアタックすることです。しかしもちろんそんな技術はありません。もうひとつの方法は、とにかく植物に電極をブッ挿したり、葉の両側を皿型電極で挟むことです(葉面電位と呼ばれます)。精度は格段に落ちますが、非破壊で生育しながらの計測が容易であることから、こうした計測も行われているようです。

偶然家にアルミ板があったのでそれを導線にビニールテープでくっつけて、電位をarduinoのアナログ入力で測りましょう。そして、できるだけ高い精度で計測をしたいので、オペアンプを使って入力を増幅しています。あと同時に生育環境についてのデータもほしいので、温度・湿度・明るさを計測しています。haisen

 

オペアンプは京都のマルツパーツ館で、湿度・温度センサーは千石電商のWEBショップ(http://www.sengoku.co.jp/mod/sgk_cart/detail.php?code=EEHD-04YE)で、Cdsセルは「Arduinoをはじめようキット」についてきたものを使っています。つなぎ方は、DHT22は

Cdsセルは

を参考にしました。オペアンプは、『電子工作の素』を読んでつなぎました。「非反転増幅回路」というやつです。ググればきっと出てきますが、オペアンプでの出力が、入力の1+R2/R1倍になります。オペアンプ無しで数日間計測した結果、5Vを1023として、大体最高でも350ぐらいの電位しか計測されなかったので、電位を3倍にするためにR1:R2 = 1:2にしています。10nFのコンデンサはパスコンといって、ノイズに対するクッションのようなものです。

葉面にはこんなふうにしてアルミ板をくっつけてます。

Camera 360

 

裏側。

Camera 3605秒ごとにデータを計測、arduinoからシリアル通信で出力して、パソコン側で受けます。パソコン側ではPythonで、pyserialというライブラリを使ってデータを受け取って、CSVで保存しています。Pythonは便利で、1日たったら新しいファイルに書き込み始めるということができます。コードはgithubで

しかしこれで何が出来るかというと結構微妙です。オペアンプ追加前のログでは雷の日に異常値を計測したりして(たぶん落雷があった)、おおすげぇとはなったのですが。部屋の温度・湿度・明るさのログの方が将来的に役立ちそうな気がします。

tumblrでタグ検索してreblogしてくれるやつ作った

みなさま充実したたんぶらいふを送っていらっしゃいますでしょうか。

「なんかオサレWEB臭がしてイヤ」「コンテンツを作れない人がドヤ顔しててイヤ」

なるほど素敵なたんぶらいふを送られていてなによりです。

ちなみに私はサイドバーに表示してある通り「せかいらぶ」という名でやっておりますが99%ぐらいリブログコンテンツです。関係ないけどtumblrってGoogle Analytics効かなくない?それともほんとに毎日0人なのだろうか…。

まあせっかくみんながみんなこぞって良い画像(やエロい画像)をあっちにこっちにたんぶりんぐしているわけですから、何かしら自分の役に立てたいわけです。というわけで、タグ検索して勝手にリブログしてくれるスクリプトをPythonで書きました。githubでどうぞ

参考にしたのは

です。上記の記事ではタグ検索について書いてないのでやってみた、という感じ。APIはなんかタグ検索に対しての扱いがぞんざいで、ああみんなタグ付けないんですね…。という感じが伝わってきました。APIを使うには上記の記事に従ってアクセストークンやらを取得する必要があります。

さてそんなこんなで作ってみました。

tumblr.com/explore にもある人気のタグでtumblrの代名詞とも言っていいgifをとりあえず、あとは適当に思いついたやつでやってみた。画像ポスト限定で。

重複防止は画像のURLでやってます。これだと重複リブログは防げるものの、同じソースから複数の人がそれぞれ引用ポストをおこなった場合の差異を判定できない。まあ仕方ないか、というところ。むしろ人気のある画像ソースサイトが分かります。

建築批評空間を可視化してみた

最近Pythonにはまっています。

ついでに機械学習の勉強にもはまっています。

こりゃあ建築批評空間を可視化するしかあるまい。

公開されている建築批評といえば「『10+1』データベース」ですね。

なんとなく見た感じ、アドレスは

スクリーンショット_13_02_23_19_26

こんな感じになってるので、適当に番号を増やしてったところ、どうやら1549まであるみたい。

というわけで、全ページを次のシェルスクリプトで取得します

#
var1=http://db.10plus1.jp/backnumber/article/articleid/
var2=.html

for ((i=1;i<1550;i++))
do wget -O $i$var2 $var1$i/
done;

この1549の中にも歯抜けになってる番号が100ありました。

それで、記事によっては

スクリーンショット_13_02_23_19_06

こんな具合に公開されてないものもあります。全1449記事のうち、公開数:721、未公開数:728 でした。約半数が公開されているようです。

さて、いろいろやってみたいことはあるのですが、まずは簡単に記事同士の関係をみてみようと考えました。

記事を見てみると、各記事の右側には著者名が並び、その記事の掲載号情報があり、以降は文中で言及されるキーワードが解説されていることが分かります。

スクリーンショット_13_02_23_19_10-2

 

この著者としての登場回数とか、誰の記事で誰がよく言及されるとかを調べると面白そうです。というわけで、htmlソースから該当部分をPythonでBeautifulSoupを使って抜き出し、pymongoを使ってmongodbにブッ込みました。

なんでmongodbかというと、あとでheroku上でなんか作ろうかなとか思ってるからです。今回はやりませんが。

スクリーンショット_13_02_23_19_22

 

当該情報はannotationクラスのdiv内にあるので、

# -*- coding: utf-8 -*-
import sys
import codecs
import re
import pymongo
from BeautifulSoup import BeautifulSoup

sys.stdin  = codecs.getreader('utf-8')(sys.stdin)
sys.stdout = codecs.getwriter('utf-8')(sys.stdout)

R_END_AUTHOR = re.compile(r'『10+1』')

def getNavigableStrings(soup):
  if isinstance(soup, BeautifulSoup.NavigableString):
    if type(soup) not in (BeautifulSoup.Comment,
      BeautifulSoup.Declaration) and soup.strip():
      yield soup
  elif soup.name not in ('script', 'style'):
    for c in soup.contents:
      for g in getNavigableStrings(c):
        yield g

def parseAuthorAndKeyword(num, col):
    filename = 'html/'+str(num)+'.html'
    f = open(filename, 'r')
    data1 = f.read()
    f.close()
    soup = BeautifulSoup(data1)
    enc = soup.originalEncoding
    # print "Encoding : %s" % enc
    # print unicode(soup.prettify(), enc)

    author  = []
    keyword = []
    authorFlag = True
    # author, keyword
    for table in soup('div', {'class':'annotation'}):
        # get content name
        name = table.findAll('h5')[0].next.next.string.encode('utf-8')
        if authorFlag:
            if R_END_AUTHOR.match(name):
                authorFlag = False
                continue
            splitted = name.split('(')
            author.append(splitted[0])
        else:
            splitted = name.split('(')
            keyword.append(splitted[0])

    # article
    # locked
    articleFlag = -1
    # limited
    for article in soup('div', {'class':'articleLimitedUnitBox'}):
        articleFlag = 0
    # published
    for article in soup('div', {'class':'articleUnitBox'}):
        articleFlag = 1

    doc = { 'author': author, 'keyword': keyword, \
            'number': num, 'published': articleFlag }
    col.insert(doc)

def main():
    conn = pymongo.Connection()
    db = conn['10+1']
    col = db['authors']

    for i in range(1, 1550):
        print i
        parseAuthorAndKeyword(i, col)

    conn.disconnect()

if __name__ == '__main__':
    main()

こんな具合で抜き出します。

関数parseAuthorAndKeywordで抜き出しを行なっています。BeautifulSoupで当該タグまで下り、table.findAll(‘h5’)[0].next.next.string.encode(‘utf-8’)で抜き出しています。名前にリンクが付いている人と付いてない人とがいたので、どっちでもいけるようちょっと汚くなっています。

それと、だいたい名前は「磯崎新(イソザキ・アラタ)」みたいな形になってて、カッコの中はいらないので、splitted = name.split(‘(’)でカッコの始まりで分割し、splitted[0]で前半部分だけ保存してます。

pymongoについてはググれば分かります。

次は、著者として登場するすべての人物について、その人の記事中であるキーワードが右に解説されていたら著者→キーワードとなる辺を持つようなグラフ(隣接辺リスト)をつくります。辺の重みはその記事数です。

さて、いよいよ可視化です。可視化にはcytoscapeを使おうと思ってやってみたんですが、web上で見るのに不便だなと思ったので、もうちょっとイケてるGephiを使って、web上での閲覧はgexf-jsという素晴らしいライブラリを使わせてもらうことにしました。

cytoscapeで読み込む隣接辺リストはsource, target, weightを一行とするcsvファイルでいいのですが(この辺は井庭崇先生のブログがわかりやすい)、Gephiの場合はsource, targetを一行とするcsvファイルです。

重みについてはすべての辺の重みを1として、同じsource, targetの辺の数がその辺の重みとなります。

# -*- coding: utf-8 -*-
import sys
import codecs
import pymongo

sys.stdin  = codecs.getreader('utf-8')(sys.stdin)
sys.stdout = codecs.getwriter('utf-8')(sys.stdout)

convert_tuples = [
 (u'\u00a6',u'\u007c'),#broken bar=>vertical bar
 (u'\u2014',u'\u2015'),#horizontal bar=>em dash
 (u'\u2225',u'\u2016'),#parallel to=>double vertical line
 (u'\uff0d',u'\u2212'),#minus sign=>fullwidth hyphen minus
 (u'\uff5e',u'\u301c'),#fullwidth tilde=>wave dash
 (u'\uffe0',u'\u00a2'),#fullwidth cent sign=>cent sign
 (u'\uffe1',u'\u00a3'),#fullwidth pound sign=>pound sign
 (u'\uffe2',u'\u00ac'),#fullwidth not sign=>not sign
]

def unsafe2safe(string):
    for unsafe, safe in convert_tuples:
        string = string.replace(unsafe, safe)
    return string

def frequencyOfItemsInKey(col, key):
    """
    return dic of all items and its frequency appeared in key.
    {key1:val1, key2:val2, ...}
    """
    dic = {}
    # for all documents
    for doc in col.find():
        # for each key
        for i in  range(len(doc[key])):
            buff = {doc[key][i]: \
                    col.find({key: doc[key][i]}).count()};
            dic.update(buff)

    return dic

def frequencyOfSecondInFirst(col, firstKey, first, secondKey):
    """
    return frequency of keyword in author's text.
    {first: {second1:val1, second2:val1, ...}}
    """
    buff = {}
    # for all documents of which firstKey is first
    for doc in col.find({firstKey:first}):
        # for each secondKey
        for i in  range(len(doc[secondKey])):
            buff2 = {doc[secondKey][i]: \
                     col.find({firstKey: first, \
                               secondKey: doc[secondKey][i]}).count()
                    }
            buff.update(buff2)
    dic = {first: buff}

    return dic

def writeDicRankToTSV(dic, filename):
    """write dic to TSV as a ranking of the value."""
    f = codecs.open(filename, 'w', 'utf-8')
    for key, val in sorted(dic.items(), key=lambda x:x[1], reverse=True):
        if key == '\n': continue
        buff = str(key)+'\t'+str(val)+'\n'
        f.write(buff)
    f.close()

def makeASetOfKey(col, key):
    """make a key set."""
    keys = []
    for doc in col.find():
        for i in  range(len(doc[key])):
            keys.append(doc[key][i])
    keys = set(keys)
    return keys

def writeNetworkAsEdgeList(network, filename):
    """for cytoscape."""
    f = codecs.open(filename, 'w', 'shift-jis')
    for person in network.keys():
        for keyword in network[person].keys():
            buff = person+','+ \
                   keyword+','+str(network[person][keyword])+'\n'
            buff = unsafe2safe(buff)
            f.write(buff)
    f.close()

def writeNetworkAsEdgeListGephi(network, filename):
    """for gephi."""
    f = codecs.open(filename, 'w', 'utf-8')
    for person in network.keys():
        for keyword in network[person].keys():
            # repeat for edge weight
            for i in range(network[person][keyword]):
                buff = person+','+keyword+'\n'
                f.write(buff)
    f.close()

def main():
    # connect with mongodb
    conn = pymongo.Connection()
    db = conn['10+1']
    col = db['authors']

    # make ranking of authors and keywords
    dic = frequencyOfItemsInKey(col, 'author')
    writeDicRankToTSV(dic, 'authorRank.txt')
    dic = frequencyOfItemsInKey(col, 'keyword')
    writeDicRankToTSV(dic, 'keywordRank.txt')

    # make a reference network
    authors = makeASetOfKey(col, 'author')
    dic = {}
    # for all authors
    for person in authors:
        buff = frequencyOfSecondInFirst(col, 'author', person, 'keyword')
        dic.update(buff)
    writeNetworkAsEdgeList(dic, 'referenceNetwork.csv')
    writeNetworkAsEdgeListGephi(dic, 'referenceNetworkForGephi.csv')

    # basic information
    numPages = col.count()
    numLocked = col.find({'published': -1}).count()
    print u'全記事数', numPages - numLocked
    print u'未公開記事数', col.find({'published': 0}).count()
    print u'公開済み記事数', col.find({'published': 1}).count()

    # disconnect with mongodb
    conn.disconnect()

if __name__ == '__main__':
    main()

ちなみにcytoscapeはshift-jisでないといけないので、この記事のやり方でutf-8から変換しています。Gephiはutf-8でいける。

さて、まずはそのまんま可視化した場合(画像クリックで開きます)。

スクリーンショット_13_02_23_18_54

 

  • 言及されてる回数が多いほどノードが大きく、
  • 言及数が多い人(多くのキーワードに言及している人)ほど赤く、
  • 文字の大きさは両方を合わせた数が多いほど大きくなっています

あとで言いますが言及数は記事数が多いほど多いですね当然。

では次は次数が1(言及、被言及合わせて1回だけ)のノードを排除し、その上で辺の重みが2以下の辺を排除したグラフです。いわばテンプラスワン批評空間における特権階級たちです。

スクリーンショット_13_02_23_18_53

 

では記事投稿数ランキング上位21人(同率がいるため)です。

五十嵐太郎 72
田中純 55
日埜直彦 47
八束はじめ 38
塚本由晴 34
上野俊哉 32
中谷礼仁 29
篠儀直子 29
内田隆三 29
今村創平 27
吉村靖孝 26
南泰裕 23
石川初 19
磯崎新 19
椹木野衣 18
多木浩二 17
大島哲蔵 16
今井公太郎 16
毛利嘉孝 15
西沢大良 14
貝島桃代 14

椹木野衣さん多いんですね。意外でした。それでは被言及ランキング上位21人です。

ル・コルビュジエ 114
ポストモダン 92
磯崎新 80
レム・コールハース 64
伊東豊雄 54
丹下健三 50
INAX出版 48
ヴァルター・ベンヤミン 47
ルネサンス 47
パサージュ 35
安藤忠雄 35
藤森照信 35
バウハウス 34
原広司 33
メタボリズム 32
現代住宅研究 31
アルゴリズム 30
アーキグラム 29
ミース・ファン・デル・ローエ 28
コーリン・ロウ 28
篠原一男 28

コルビュジェ先生はさすがですね。テンプラスワンがポストモダンの建築批評誌であることがよく分かります。INAX出版はやりよるなという感じですかね。

これからはもうちょっと面白い特徴を見つけてみようと思います。

あと本文抽出もできるのでMeCabを使ってなんかしてみようと思います。まずは多分ナイーブベイズを試すと思われます。