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

カタカタブログ

SIerで働くITエンジニアがカタカタした記録を残す技術ブログ。Java, Oracle Database, Linuxが中心です。たまにRuby on Railsなども。

Raspberry Pi 2とSoftEtherを使って自宅VPNを構築してみた

Linux ネットワーク Raspberry Pi

これまでヤマハやBUFFALOの市販のルータを使って自宅VPNを構築してきた。
totech.hateblo.jp
totech.hateblo.jp
しかし、先日BUFFALOのルータを買い換えたところ、PPTPサーバ機能がなくてVPNサーバとして使えなかったため、
Raspberry Pi 2を使ってソフトウェアVPNサーバを構築してみようと思い立った。

そこで、今回はSoftEther(ソフトイーサ)というソフトウェアVPNを使って自宅VPNを構築する。
SoftEtherクラウド上でVPNの経路を中継する仕組みのため、VPNサーバとVPNクライアント間で直接セッションを貼るのではなく、間にVPN Azureというクラウドサービスを使う。これは筑波大学が研究用途で公開している無料のサービスで、カタログスペック上はopenvpnよりも帯域や使いやすさで上回っているらしい。
また、クラウドサービスを利用することで、VPN機能のルータはもちろんグローバルIPアドレスすら不要となる。

よって、今回はRaspberry Pi 2上にSoftEtherのVPNサーバをインストールし、VPN Azureを使って中継し、Windows 7からSoftEtherのVPNクライアントを使って自宅LANにVPNに接続する。

基本的には以下の公式マニュアルを参照して構築した。
https://ja.softether.org/4-docs/1-manual/7/7.3

Raspberry PiにSoftEther VPN Serverインストール

まずはRaspberry Pi にVPNサーバを構築する。
SoftEtherのVPNサーバインストーラは以下からダウンロードできる。
http://www.softether.org/5-download
現時点の最新版である softether-vpnserver-v4.21-9613-beta-2016.04.24-linux-arm_eabi-32bit.tar.gz をダウンロードした。

f:id:osn_th:20160806181937p:plain

以下、Raspberry Pi上でインストールする作業をrootユーザで行う。
まず、ダウンロードしたインストーラを展開する。

root@raspberrypi:~# pwd
/root
root@raspberrypi:~# ls -l softether-vpnserver-v4.21-9613-beta-2016.04.24-linux-arm_eabi-32bit.tar.gz
-rw-r--r-- 1 root root 5646405  8月  4  2016 softether-vpnserver-v4.21-9613-beta-2016.04.24-linux-arm_eabi-32bit.tar.gz

root@raspberrypi:~# tar xzvf softether-vpnserver-v4.21-9613-beta-2016.04.24-linux-arm_eabi-32bit.tar.gz
vpnserver/
vpnserver/Makefile
vpnserver/.install.sh
vpnserver/ReadMeFirst_License.txt
vpnserver/Authors.txt
vpnserver/ReadMeFirst_Important_Notices_ja.txt
vpnserver/ReadMeFirst_Important_Notices_en.txt
vpnserver/ReadMeFirst_Important_Notices_cn.txt
vpnserver/code/
vpnserver/code/vpnserver.a
vpnserver/code/vpncmd.a
vpnserver/lib/
vpnserver/lib/libcharset.a
vpnserver/lib/libcrypto.a
vpnserver/lib/libedit.a
vpnserver/lib/libiconv.a
vpnserver/lib/libncurses.a
vpnserver/lib/libssl.a
vpnserver/lib/libz.a
vpnserver/lib/License.txt
vpnserver/hamcore.se2

実行可能ファイルを作成するため、コンパイルする。
途中でライセンスに関する確認が行われるので、回答する。

root@raspberrypi:~# cd vpnserver/
root@raspberrypi:~/vpnserver# make
--------------------------------------------------------------------

SoftEther VPN Server (Ver 4.21, Build 9613, ARM EABI) for Linux Install Utility
Copyright (c) SoftEther Project at University of Tsukuba, Japan. All Rights Reserved.

--------------------------------------------------------------------


Do you want to read the License Agreement for this software ?

 1. Yes
 2. No

Please choose one of above number:
1

(・・・略)
Did you read and understand the License Agreement ?
(If you couldn't read above text, Please read 'ReadMeFirst_License.txt'
 file with any text editor.)

 1. Yes
 2. No

Please choose one of above number:
1


Did you agree the License Agreement ?

1. Agree
2. Do Not Agree

Please choose one of above number:
1

make[1]: Entering directory '/root/vpnserver'
Preparing SoftEther VPN Server...
ranlib lib/libcharset.a
ranlib lib/libcrypto.a
ranlib lib/libedit.a
ranlib lib/libiconv.a
ranlib lib/libncurses.a
ranlib lib/libssl.a
ranlib lib/libz.a
ranlib code/vpnserver.a
gcc code/vpnserver.a -O2 -fsigned-char -lm -ldl -lrt -Wl,--no-warn-mismatch -lpthread -L./ lib/libssl.a lib/libcrypto.a lib/libiconv.a lib/libcharset.a lib/libedit.a lib/libncurses.a lib/libz.a -o vpnserver
ranlib code/vpncmd.a
gcc code/vpncmd.a -O2 -fsigned-char -lm -ldl -lrt -Wl,--no-warn-mismatch -lpthread -L./ lib/libssl.a lib/libcrypto.a lib/libiconv.a lib/libcharset.a lib/libedit.a lib/libncurses.a lib/libz.a -o vpncmd
./vpncmd /tool /cmd:Check
vpncmd コマンド - SoftEther VPN コマンドライン管理ユーティリティ
SoftEther VPN コマンドライン管理ユーティリティ (vpncmd コマンド)
Version 4.21 Build 9613   (Japanese)
Compiled 2016/04/24 16:39:47 by yagi at pc30
Copyright (c) SoftEther VPN Project. All Rights Reserved.

VPN Tools を起動しました。HELP と入力すると、使用できるコマンド一覧が表示できます。

VPN Tools>Check
Check コマンド - SoftEther VPN の動作が可能かどうかチェックする
---------------------------------------------------
SoftEther VPN 動作環境チェックツール

Copyright (c) SoftEther VPN Project.
All Rights Reserved.

この動作環境チェックツールを実行したシステムがテストに合格した場合は、SoftEther VPN ソフトウェアが動作する可能性が 高いです。チェックにはしばらく時間がかかる場合があります。そのままお待ちください...

'カーネル系' のチェック中...
              [合格] ○
'メモリ操作系' のチェック中...
              [合格] ○
'ANSI / Unicode 文字列処理系' のチェック中...
              [合格] ○
'ファイルシステム' のチェック中...
              [合格] ○
'スレッド処理システム' のチェック中...
              [合格] ○
'ネットワークシステム' のチェック中...
              [合格] ○

すべてのチェックに合格しました。このシステム上で SoftEther VPN Server / Bridge が正しく動作する可能性が高いと思われます。

コマンドは正常に終了しました。


--------------------------------------------------------------------
The preparation of SoftEther VPN Server is completed !


*** How to switch the display language of the SoftEther VPN Server Service ***
SoftEther VPN Server supports the following languages:
  - Japanese
  - English
  - Simplified Chinese

You can choose your prefered language of SoftEther VPN Server at any time.
To switch the current language, open and edit the 'lang.config' file.


*** How to start the SoftEther VPN Server Service ***

Please execute './vpnserver start' to run the SoftEther VPN Server Background Service.
And please execute './vpncmd' to run the SoftEther VPN Command-Line Utility to configure SoftEther VPN Server.

Of course, you can use the VPN Server Manager GUI Application for Windows / Mac OS X on the other Windows / Mac OS X computers in order to configure the SoftEther VPN Server remotely.


*** For Windows users ***
You can download the SoftEther VPN Server Manager for Windows
from the http://www.softether-download.com/ web site.
This manager application helps you to completely and easily manage the VPN server services running in remote hosts.


*** For Mac OS X users ***
In April 2016 we released the SoftEther VPN Server Manager for Mac OS X.
You can download it from the http://www.softether-download.com/ web site.
VPN Server Manager for Mac OS X works perfectly as same as the traditional Windows versions. It helps you to completely and easily manage the VPN server services running in remote hosts.

--------------------------------------------------------------------

make[1]: Leaving directory '/root/vpnserver'

正常に終了したら、VPN Serverのプログラムが生成されるので、それをvpnserverディレクトリごと/user/localに移動する。

root@raspberrypi:~/vpnserver# cd ..
root@raspberrypi:~# ls
softether-vpnserver-v4.21-9613-beta-2016.04.24-linux-arm_eabi-32bit.tar.gz  vpnserver
root@raspberrypi:~# mv vpnserver/ /usr/local/
root@raspberrypi:~# ls -l /usr/local/vpnserver/
合計 8784
-rwxrwxrwx 1 root root    1881  4月 24 08:11 Authors.txt
-rwxrwxrwx 1 root root    2859  4月 24 08:11 Makefile
-rwxrwxrwx 1 root root   30801  4月 24 08:11 ReadMeFirst_Important_Notices_cn.txt
-rwxrwxrwx 1 root root   36297  4月 24 08:11 ReadMeFirst_Important_Notices_en.txt
-rwxrwxrwx 1 root root   50695  4月 24 08:11 ReadMeFirst_Important_Notices_ja.txt
-rwxrwxrwx 1 root root   58932  4月 24 08:11 ReadMeFirst_License.txt
drwx------ 2 root root    4096  8月  4 07:39 chain_certs
drwxrwxrwx 2 root root    4096  8月  4 07:38 code
-rwxrwxrwx 1 root root 1295363  4月 24 08:11 hamcore.se2
-rw------- 1 root root     867  8月  4 07:38 lang.config
drwxrwxrwx 2 root root    4096  8月  4 07:38 lib
-rwxr-xr-x 1 root root 3742580  8月  4 07:38 vpncmd
-rwxr-xr-x 1 root root 3742656  8月  4 07:38 vpnserver

パーミッションを変えておく。

root@raspberrypi:~# cd /usr/local/vpnserver/
root@raspberrypi:/usr/local/vpnserver# chmod 600 *
root@raspberrypi:/usr/local/vpnserver# chmod 700 vpncmd
root@raspberrypi:/usr/local/vpnserver# chmod 700 vpnserver
root@raspberrypi:/usr/local/vpnserver# ls -l
合計 8784
-rw------- 1 root root    1881  4月 24 08:11 Authors.txt
-rw------- 1 root root    2859  4月 24 08:11 Makefile
-rw------- 1 root root   30801  4月 24 08:11 ReadMeFirst_Important_Notices_cn.txt
-rw------- 1 root root   36297  4月 24 08:11 ReadMeFirst_Important_Notices_en.txt
-rw------- 1 root root   50695  4月 24 08:11 ReadMeFirst_Important_Notices_ja.txt
-rw------- 1 root root   58932  4月 24 08:11 ReadMeFirst_License.txt
drw------- 2 root root    4096  8月  4 07:39 chain_certs
drw------- 2 root root    4096  8月  4 07:38 code
-rw------- 1 root root 1295363  4月 24 08:11 hamcore.se2
-rw------- 1 root root     867  8月  4 07:38 lang.config
drw------- 2 root root    4096  8月  4 07:38 lib
-rwx------ 1 root root 3742580  8月  4 07:38 vpncmd
-rwx------ 1 root root 3742656  8月  4 07:38 vpnserver

vpncmdというコマンドツールで、VPNサーバの動作チェックが行えるので、念のため行っておく。ちなみに上で実行可能ファイルを生成した時によく見ると同様のチェックがすでに行われている。

root@raspberrypi:/usr/local/vpnserver# ./vpncmd
vpncmd コマンド - SoftEther VPN コマンドライン管理ユーティリティ
SoftEther VPN コマンドライン管理ユーティリティ (vpncmd コマンド)
Version 4.21 Build 9613   (Japanese)
Compiled 2016/04/24 16:39:47 by yagi at pc30
Copyright (c) SoftEther VPN Project. All Rights Reserved.

vpncmd プログラムを使って以下のことができます。

1. VPN Server または VPN Bridge の管理
2. VPN Client の管理
3. VPN Tools コマンドの使用 (証明書作成や通信速度測定)

1 - 3 を選択: 3

VPN Tools を起動しました。HELP と入力すると、使用できるコマンド一覧が表示できます。

VPN Tools>help
下記の 6 個のコマンドが使用できます:
 About         - バージョン情報の表示
 Check         - SoftEther VPN の動作が可能かどうかチェックする
 MakeCert      - 新しい X.509 証明書と秘密鍵の作成 (1024 bit)
 MakeCert2048  - 新しい X.509 証明書と秘密鍵の作成 (2048 bit)
 TrafficClient - 通信スループット測定ツールクライアントの実行
 TrafficServer - 通信スループット測定ツールサーバーの実行

それぞれのコマンドの使用方法については、"コマンド名 ?" と入力するとヘルプが表示されます。
コマンドは正常に終了しました。

VPN Tools>check
Check コマンド - SoftEther VPN の動作が可能かどうかチェックする
---------------------------------------------------
SoftEther VPN 動作環境チェックツール

Copyright (c) SoftEther VPN Project.
All Rights Reserved.

この動作環境チェックツールを実行したシステムがテストに合格した場合は、SoftEther VPN ソフトウェアが動作する可能性が 高いです。チェックにはしばらく時間がかかる場合があります。そのままお待ちください...

'カーネル系' のチェック中...
              [合格] ○
'メモリ操作系' のチェック中...
              [合格] ○
'ANSI / Unicode 文字列処理系' のチェック中...
              [合格] ○
'ファイルシステム' のチェック中...
              [合格] ○
'スレッド処理システム' のチェック中...
              [合格] ○
'ネットワークシステム' のチェック中...
              [合格] ○

すべてのチェックに合格しました。このシステム上で SoftEther VPN Server / Bridge が正しく動作する可能性が高いと思われます。

コマンドは正常に終了しました。

VPN Tools>exit

すべてのチェックに合格していることを確認する。

ここから、VPNサーバを運用しやすくするため、スタートアップスクリプトに登録する。

root@raspberrypi:/usr/local/vpnserver# touch /etc/init.d/vpnserver
root@raspberrypi:/usr/local/vpnserver# vi /etc/init.d/vpnserver

公式マニュアル通り、以下の内容を記載する。

#!/bin/sh
# chkconfig: 2345 99 01
# description: SoftEther VPN Server
DAEMON=/usr/local/vpnserver/vpnserver
LOCK=/var/lock/subsys/vpnserver
test -x $DAEMON || exit 0
case "$1" in
start)
$DAEMON start
touch $LOCK
;;
stop)
$DAEMON stop
rm $LOCK
;;
restart)
$DAEMON stop
sleep 3
$DAEMON start
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
esac
exit 0

パーミッションも適切に設定する。

root@raspberrypi:/usr/local/vpnserver# chmod 755 /etc/init.d/vpnserver

chkconfigに登録する。当環境ではchkconfigが入っていなかったのでapt-getでインストールから行った。

root@raspberrypi:~# apt-get install chkconfig
root@raspberrypi:~# chkconfig vpnserver on
insserv: warning: script 'vpnserver' missing LSB tags and overrides

警告が出るが、以下を見る限り大した問題ではなさそうなので、ここでは無視する。
http://www.sssg.org/blogs/hiro345/archives/16237.html

root@raspberrypi:~# chkconfig vpnserver --list
vpnserver                 0:off  1:off  2:on   3:on   4:on   5:on   6:off

これで以下のコマンドでVPNサーバの起動・停止・ステータス確認が正常に行えるようになった。

# service vpnserver start
# service vpnserver stop
# service vpnserver status

vpnserverを起動させておく。

root@raspberrypi:~# service vpnserver start

これでRaspberry PiにSoftEther VPN Serverのインストールが完了した。

Windows 7マシンからSoftEther VPN Serverの設定

Raspberry Pi にインストールしたVPN Serverの設定を行う。
サーバ上で直接vpncmdを使ってもできるのだが、Win/Mac用のGUIの管理用ツールがあるので、各種設定はこちらから行う。
そのため、ここからはWindowsマシンを使って作業する。今回は後にVPNクライアントととしても使うことになるWindows 7端末を利用する。

VPNサーバと同じダウンロードページから softether-vpnserver_vpnbridge-v4.21-9613-beta-2016.04.24-windows-x86_x64-intel.exe をダウンロードする。
http://www.softether.org/5-download
f:id:osn_th:20160806181923p:plain

インストーラを展開し、実行する。
SoftEther VPN サーバー管理マネージャを選択し、次へ
f:id:osn_th:20160806182010p:plain
あとは、ライセンスやインストール先を指定するだけで、基本的に指示通りに「次へ」を選んでいくだけでインストールが進む。
完了し、管理マネージャを起動すると以下のようなGUIのツールが起動する。
「新しい接続設定」をクリックし、さきほどRaspberry Pi にインストールしたSoftEther VPN Serverの設定を行っていく。
f:id:osn_th:20160806181950p:plain
接続設定名はなんでもよいが、今回はraspi-vpnとした。なお、この先いろいろなところで各種設定の名前を入力するが、今回は全てraspi-vpnとしている。
ホスト名はVPN ServerをインストールしたサーバのIPアドレスもしくはホスト名を指定する。
f:id:osn_th:20160806181947p:plain
設定が作成されたので、「接続」する。
f:id:osn_th:20160806181955p:plain
初回ログイン時に管理パスワードの設定をする。
以降は管理ツールにアクセス時にそのパスワードを入力する(ちなみにこれはVPNユーザのパスワードとはまた別で、それはあとで別に設定する)。
f:id:osn_th:20160806181941p:plain
リモートアクセス VPNサーバーにチェックを入れ、次へ
f:id:osn_th:20160806181933p:plain
はい
f:id:osn_th:20160806182012p:plain
仮想HUB名もraspi-vpnにしておく
f:id:osn_th:20160806181958p:plain
固有のダイナミックDNSが割り当てられる。変更も可能なようだが、今回は特に変更せずにそのまま利用する。
f:id:osn_th:20160806182018p:plain
今回はVPN Azureを使ってクラウド経由のVPNを作るため、「VPN Azureを有効にする」にチェックを入れる。
ここで、上のダイナミックDNSに基づいた「現在のVPN Azureホスト名」が実際にVPNクライアントから接続するときのホスト名となるので、控えておいた上で、OK。
f:id:osn_th:20160806182021p:plain
続いてVPNユーザーを作成する。
f:id:osn_th:20160806182004p:plain
f:id:osn_th:20160806182014p:plain
最後に、VPN経由でLANにアクセスするために、ローカルブリッジ設定をする。
この設定を入れないとVPNでつないだときにリンクローカルアドレスが割り当てられてしまい、LAN内のサーバと通信ができない。
この設定を入れることで、VPNセッションをつないだときにLAN内のDHCPサーバ(ルータ)からIPアドレスが割り当てられる。
f:id:osn_th:20160806182034p:plain
仮想HUBに「物理的な既存のLANカードとのブリッジ接続」を選んで、「ローカルブリッジを追加」する。
f:id:osn_th:20160806181930p:plain
追加されたローカルブリッジが動作中になる

f:id:osn_th:20160806181921p:plain
以上で、VPN Server側の設定が完了した!

SoftEther VPN Clientセットアップ

VPNサーバ側のセットアップが完了したので、あとはVPNクライアントをセットアップして実際に接続してみる。
SoftEther VPN Clientもこれまでと同じようにダウンロードする。
f:id:osn_th:20160806182006p:plain
SoftEther VPN Clientをインストールする。
f:id:osn_th:20160806181952p:plain
クライアント側も指示どおりに「次へ」を選んでいくだけで、特に迷うことなくインストールできる。
VPNクライアントが起動したら、「新しい接続設定の作成」を選択する。
f:id:osn_th:20160806182000p:plain
はい
f:id:osn_th:20160806181943p:plain
ここでもraspi-vpnとする。
f:id:osn_th:20160806181927p:plain
つくられたraspi-vpn仮想LANカードをクリック
f:id:osn_th:20160806182024p:plain
さきほど作ったVPNサーバ側のホスト名やVPNユーザ情報など各種設定を入力し、OK。
f:id:osn_th:20160806182027p:plain

ここまででVPNサーバ側およびクライアント側の全設定が完了した。

VPN接続テスト

セットアップしたVPNが正しく動作するか検証する。
ここまで作業は自宅LAN内のルータの無線LANを使ってインターネットに出ていたが、正しくVPN経由で自宅LANにアクセスできることを確認するため、ここからはモバイルルータにネットワークを切り替えて作業する。

作成したVPNクライアントの接続設定を右クリックし、「接続」を選択する。
f:id:osn_th:20160806182031p:plain
VPN上のDHCPサーバからIPアドレスが取得される。
f:id:osn_th:20160806181945p:plain
IPアドレスが割り当てられ、接続完了となればOK。
f:id:osn_th:20160806182037p:plain

ipconfigを打つと、raspi-vpn仮想ネットワークカードに自宅LANのIPアドレスが割り当てられていることが確認できる。

>ipconfig

Windows IP 構成


イーサネット アダプター raspi-vpn - VPN Client:

   接続固有の DNS サフィックス . . . :
   リンクローカル IPv6 アドレス. . . . : xxxx::xxxx:xxxx:xxxx:xxxx%60
   IPv4 アドレス . . . . . . . . . . : 192.168.11.17
   サブネット マスク . . . . . . . . : 255.255.255.0
   デフォルト ゲートウェイ . . . . . : 192.168.11.1

Raspberry Pi 2 (192.168.11.11)にもアクセスできる。

>ping 192.168.11.11

192.168.11.11 に ping を送信しています 32 バイトのデータ:
192.168.11.11 からの応答: バイト数 =32 時間 =1ms TTL=64
192.168.11.11 からの応答: バイト数 =32 時間 =1ms TTL=64
192.168.11.11 からの応答: バイト数 =32 時間 =1ms TTL=64
192.168.11.11 からの応答: バイト数 =32 時間 =1ms TTL=64

192.168.11.11 の ping 統計:
    パケット数: 送信 = 4、受信 = 4、損失 = 0 (0% の損失)、
ラウンド トリップの概算時間 (ミリ秒):
    最小 = 1ms、最大 = 1ms、平均 = 1ms

sshもOK。

>ssh pi@192.168.11.11
pi@192.168.11.11's password:

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sat Aug  6 01:20:30 2016 from 192.168.11.4
pi@raspberrypi:~ $

sshでRaspberry Piにログインもできた。
正しくVPNが構築できているよう!

まとめ

今回はSoftEtherのVPN Azureを使って自宅VPNを構築した。
Raspberry PiをSoftEther VPN Server、Windows 7マシンをSoftEther VPN Clientとして環境構築、設定、疎通まで確認できた。
それほど難しい手順はなく(VPNやネットワークの基礎知識がないと辛いが)、管理ツールも使いやすいのでかなり便利だと思う。クラウドVPNを使うことでグローバルIPアドレスが不要になるなので、自宅ルータにVPN機能がなくても普通のマシンされあればVPN Server化できるので、構築のための敷居はかなり低いと感じた。

以上!

apt-get install時に「 パッケージ 'wireless-tools' のファイル一覧ファイルに最後の改行がありません」エラー

Linux Raspberry Pi

久しぶりにRaspberry Piを触った。
Raspberry Pi 2にapt-getでchkconfigを入れようとしたときによくわからないエラーが出たので、対策をメモ。

エラーと対応方法

apt-getでchkconfigを入れようとしたときに、wireless-toolsパッケージ周りでエラーが発生し、インストールに失敗するという問題が発生。

# apt-get install chkconfig
(・・・略)
dpkg: 復旧不可能な致命的なエラーです。中止します:
パッケージ 'wireless-tools' のファイル一覧ファイルに最後の改行がありません
E: Sub-process /usr/bin/dpkg returned an error code (2)

apt-get update, upgradeをやってから再実行するも変わらず。

wireless-toolsのファイル一覧ファイルがおかしいということなのでファイルの中身を確認してみる。

# file /var/lib/dpkg/info/wireless-tools.list
/var/lib/dpkg/info/wireless-tools.list: PNG image data, 38 x 25, 8-bit/color RGBA, non-interlaced

なぜかPNG画像ファイルになっている。。
普通は以下のようにテキストファイルのはず。つまり、wireless-tools.listが破損してしまっているよう。

# file /var/lib/dpkg/info/*.list | head
/var/lib/dpkg/info/acl.list:                 ASCII text
/var/lib/dpkg/info/adduser.list:               ASCII text
/var/lib/dpkg/info/adwaita-icon-theme.list:          ASCII text
/var/lib/dpkg/info/alacarte.list:               ASCII text
/var/lib/dpkg/info/alsa-base.list:              ASCII text
/var/lib/dpkg/info/alsa-utils.list:              ASCII text
/var/lib/dpkg/info/apt-utils.list:              ASCII text
/var/lib/dpkg/info/apt.list:                 ASCII text
/var/lib/dpkg/info/aptitude-common.list:           ASCII text
/var/lib/dpkg/info/aptitude.list:               ASCII text
# head /var/lib/dpkg/info/acl.list
/.
/usr
/usr/bin
/usr/share
/usr/share/man
/usr/share/man/man1
/usr/share/man/man1/chacl.1.gz
/usr/share/man/man1/getfacl.1.gz
/usr/share/man/man1/setfacl.1.gz
/usr/share/man/man5

正しいwireless-tools.listを探す。適当にググると以下にそれっぽいものを発見。
信頼性はまったくもって怪しいが、どうせ自宅利用のRaspberry Piなのでこれで試してみる。
https://github.com/volumio/RootFS-RaspberryPI/blob/master/var/lib/dpkg/info/wireless-tools.list

# cd/var/lib/dpkg/info
# mv wireless-tools.list wireless-tools.list.org
# viwireless-tools.list

wireless-tools.listの中身は以下のようになった。

/.
/sbin
/sbin/iwgetid
/sbin/iwlist
/sbin/iwevent
/sbin/iwspy
/sbin/iwconfig
/sbin/iwpriv
/etc
/etc/network
/etc/network/if-pre-up.d
/etc/network/if-pre-up.d/wireless-tools
/etc/network/if-post-down.d
/etc/network/if-post-down.d/wireless-tools
/usr
/usr/share
/usr/share/doc/wireless-tools
/usr/share/doc/wireless-tools/changelog.Debian.gz
/usr/share/doc/wireless-tools/HOTPLUG-UDEV.txt.gz
/usr/share/doc/wireless-tools/PCMCIA.txt.gz
/usr/share/doc/wireless-tools/README.Debian
/usr/share/doc/wireless-tools/copyright
/usr/share/doc/wireless-tools/DISTRIBUTIONS.txt.gz
/usr/share/doc/wireless-tools/changelog.gz
/usr/share/doc/wireless-tools/README.gz
/usr/share/man
/usr/share/man/cs
/usr/share/man/cs/man8
/usr/share/man/cs/man8/iwspy.8.gz
/usr/share/man/cs/man8/iwpriv.8.gz
/usr/share/man/cs/man8/iwconfig.8.gz
/usr/share/man/cs/man8/iwevent.8.gz
/usr/share/man/cs/man8/iwgetid.8.gz
/usr/share/man/cs/man8/iwlist.8.gz
/usr/share/man/cs/man7
/usr/share/man/cs/man7/wireless.7.gz
/usr/share/man/fr.UTF-8
/usr/share/man/fr.UTF-8/man8
/usr/share/man/fr.UTF-8/man8/iwspy.8.gz
/usr/share/man/fr.UTF-8/man8/iwpriv.8.gz
/usr/share/man/fr.UTF-8/man8/iwconfig.8.gz
/usr/share/man/fr.UTF-8/man8/iwevent.8.gz
/usr/share/man/fr.UTF-8/man8/iwgetid.8.gz
/usr/share/man/fr.UTF-8/man8/iwlist.8.gz
/usr/share/man/fr.UTF-8/man7
/usr/share/man/fr.UTF-8/man7/wireless.7.gz
/usr/share/man/man8
/usr/share/man/man8/iwspy.8.gz
/usr/share/man/man8/iwpriv.8.gz
/usr/share/man/man8/iwconfig.8.gz
/usr/share/man/man8/iwevent.8.gz
/usr/share/man/man8/iwgetid.8.gz
/usr/share/man/man8/iwlist.8.gz
/usr/share/man/fr.ISO8859-1
/usr/share/man/fr.ISO8859-1/man8
/usr/share/man/fr.ISO8859-1/man8/iwspy.8.gz
/usr/share/man/fr.ISO8859-1/man8/iwpriv.8.gz
/usr/share/man/fr.ISO8859-1/man8/iwconfig.8.gz
/usr/share/man/fr.ISO8859-1/man8/iwevent.8.gz
/usr/share/man/fr.ISO8859-1/man8/iwgetid.8.gz
/usr/share/man/fr.ISO8859-1/man8/iwlist.8.gz
/usr/share/man/fr.ISO8859-1/man7
/usr/share/man/fr.ISO8859-1/man7/wireless.7.gz
/usr/share/man/man7
/usr/share/man/man7/wireless.7.gz

これでapt-get installを再実行する。

# apt-get install chkconfig
E: dpkg は中断されました。問題を修正するには 'dpkg --configure -a' を手動で実行する必要があります。

ファイルを手で直接いじったせいか、実行できなかったので、指示されたとおりのコマンドを実行する。

# dpkg --configure -a

再実行

# apt-get install chkconfig
(・・・略)
(データベースを読み込んでいます ... 現在 118819 個のファイルとディレクトリがインストールされています。)
.../chkconfig_11.4.54.60.1debian1_all.deb を展開する準備をしています ...
chkconfig (11.4.54.60.1debian1) を展開しています...
man-db (2.7.0.2-5) のトリガを処理しています ...
chkconfig (11.4.54.60.1debian1) を設定しています ...

今度は正常に完了した!

# which chkconfig
/sbin/chkconfig

ちゃんとchkconfigがインストールされた。

まとめ

なぜかapt-getであるパッケージのファイル一覧ファイルが壊れてしまったため、
そのパッケージに依存するパッケージのインストールに失敗してしまう、という問題が発生した。
ファイル一覧ファイル(xxx.list)を適当に拾ってきたものだが、差し替えたところ、正常にインストールできるようになった。

以上!

【ベイズ統計】HMC(ハミルトニアン・モンテカルロ)法をRで理解する

統計 R

ベイズ統計に関する以下の本を読んだ。

基礎からのベイズ統計学: ハミルトニアンモンテカルロ法による実践的入門

基礎からのベイズ統計学: ハミルトニアンモンテカルロ法による実践的入門

この本のゴールはHMC法(ハミルトニアン・モンテカルロ法)を習得することで、Rのサンプルコードも付いているのだが、理解を深めるためにRコードを実装しながらHMC法を動かしてみた。Rコードはサンプルコードの実装を読み解きながら、自分で理解しやすいようにときほぐすように実装してみた。
HMC法自体の解説はこの書籍の5章「ハミルトニアンモンテカルロ法」のほうに詳しく説明されているので、本記事で
は割愛。

リープフロッグ法

まずリープフロッグ法を実装する。
リープフロッグ法はある座標にいる物体に適当な運動量を加えて1ステップ移動させるという操作を
複数回の細かい移動を繰り返すことで、その物体の移動後の座標をシミュレーションするというもの。
移動回数(=移動時間)も与えているので、一定時間後の座標と運動量がステップごとに更新される。
計算のため、座標の勾配(つまり、運動量の時間微分)の関数を与えている。

# リープフロッグ法
# r: 運動量
# z: 座標
# D: 運動量の時間微分(勾配)の関数
# e: 細かさの精度
# L: 移動回数(=移動時間)
leapfrog <- function(r, z, D, e, L) {
  leapfrog.step <- function(r, z, e){
    r2 <-- e * D(z) / 2
    z2 <- z + e * r2
    r2 <- r2 - e * D(z2) / 2
    list(r=r2, z=z2) # 1回の移動後の運動量と座標
  }
  leapfrog.result <- list(r=r, z=z)
  for(i in 1:L) {
    leapfrog.result <- leapfrog.step(leapfrog.result$r, leapfrog.result$z, e)
  }
  leapfrog.result
}

leapfrog.stepがリープフロッグ法の中での”細かい”移動一回分を表していて、それをL回繰り返すことで1ステップの移動を実現する、というもの。
移動のたびに座標と運動量が更新され、最終的な座標と運動量を結果として返す。

HMC(ハミルトニアン・モンテカルロ法)

HMC法は物理学のアナロジーで、物理空間上をハミルトニアン(運動エネルギー + 位置エネルギー)が一定である、という制約を満たすような移動を複数ステップ繰り返すというもの。
リープフロッグ法はHMC法の中での1ステップの移動を実現するために用いる。
ここで、物理空間を確率分布とみなすと、母数は座標となり、HMC法で得られた各ステップの座標はその確率分布を満たすランダムサンプリングの結果だとみなすことができる。
ただしステップが収束するまではこの分布を表現していないため、破棄する必要がある。この期間をバーンイン期間という。

# ハミルトニアンモンテカルロ
# N: サンプリングする乱数の数(HMC法で移動するステップ数)
# ini: 母数ベクトルの初期値(初期座標)
# E: 対数尤度関数のマイナス(物理空間=サンプリングしたい確率分布上の位置エネルギーに相当する関数。計算しやすくするために対数をとり、確率の”高さ”を力学的な空間の”低さ”に対応づけるためにマイナス1倍する)
# D: 対数尤度関数の微分のマイナス(物理空間=サンプリングしたい確率分布の勾配。上と同じように対数化、マイナス1倍。この関数はリープフロッグ法で使う)
# L: 移動回数
# e: リープフロッグ法の細かさの精度
hmc <- function(N, ini, E, D, L=100, e=0.01){
  # ハミルトニアン
  # r: 運動量ベクトル
  # z: 座標ベクトル
  # E: 座標ベクトルから位置エネルギーを算出する関数(hmcのEと同じ)
  H <- function(r, z, E=E) {
    sum(r^2)/2 + E(z) # sum(r^2)/2: 運動エネルギー, E(z): 位置エネルギー
  }

  d <- length(ini) # 母数ベクトルの次元
  z <- matrix(0,N,d) # 母数の推定値(Nステップ後の座標に相当)
  r <- matrix(0,N,d) # Nステップ後の運動量に相当
  z[1,] <- ini # iniは母数ベクトルの初期値(1ステップ目の座標に相当)
  co <- 1 # 採択数(最初は採択)
  for(i in 2:N) { 
    r[i-1,] <- rnorm(d) # 運動量は独立なd個の標準正規乱数
    h <- H(r[i-1,], z[i-1,], E) # i-1ステップ後のハミルトニアン
    leapfrog.result <- leapfrog(r[i-1,], z[i-1,], D=D, e=e, L=L) # リープフロッグ法による1ステップ移動
    r2 <- leapfrog.result$r # 移動後の運動量
    z2 <- leapfrog.result$z # 移動後の座標
    h2 <- H(r2, z2, E) # 移動後のハミルトニアン
    # 移動前後でハミルトニアンの保存精度が十分高ければiステップ後の値として採択
    if (runif(1) < exp(h - h2)) {
      z[i,] <- z2
      co <- co + 1
    } else {
      # 採択されなければ留まる
      z[i,] <- z[i-1,] 
    }
  } 
  ac <- co / N # 採択率
  list(z=z, r=r, ac=ac)
}

最初にHというハミルトニアンを算出する関数を定義している。ハミルトニアンは運動エネルギーと位置エネルギーの和であり、運動エネルギーは(質量を1とみなすと)sum(r^2)/2の式で表現できる。
後の処理で、1ステップ後にこのハミルトニアンが移動前後で大きく変化していないかを確認することで、精度を高めている。

計算に使う入れ物(変数)を用意した後は、N回のリープフロッグによるステップ移動を繰り返す。
初期座標は入力として与えられ、移動量を表現する運動量は標準正規乱数から求められる。つまり、まったくランダムな力を加えて、それをリープフロッグ法によって移動後の座標を求める。

次に、移動前の座標と運動から算出したハミルトニアンと、移動後の座標と運動量から算出したハミルトニアンを比較する。
ここで、この変化が非常に少なければハミルトニアンの保存の精度が高く、このステップの移動は信頼性が高いとして採択される。採択されればその移動は採用されるし、採択されなければそのまま留まることになる。
こうして指定された回数分のステップが終わると、座標と運動量と採択率を結果として返す。

正規分布をサンプリングしてみる

HMC法が実装できたので、練習として書籍の例題と同じ平均170, 分散49 (σ=7)の正規分布の推定をN=100000, L=100, e=0.01として行ってみる。
正規分布の母数は平均と標準偏差の二つなので、母数は以下のようになり、これが物理空間上の座標に相当する。
f:id:osn_th:20160728133347p:plain
この2次元の確率分布の空間上の移動をシミュレートすることになる。

対数尤度関数、対数尤度関数の微分を計算

HMCで計算するためにDEに対応する関数を計算しておく。
今回は正規分布が対象なので、正規関数のlogをとってマイナス1倍したものがD
それをμとσ^2でそれぞれ微分し、logをとってマイナス1倍した2つの関数がEとなる。

ここの関数定義は書籍のものをそのまま利用した。
thetaは母数である平均μと分散σ^2の二つの要素を持つベクトル。

#正規分布モデル
#データの用意
set.seed(1234)
n<-100
x<-round(rnorm(n,170,7.0))
x
n<-length(x)

# 対数尤度関数のマイナス
lognorm <- function(theta){
  mu<-theta[1]
  sigma2<-theta[2]
  return(((n*log(sigma2)/(-2))-(sum((x-mu)^2)/(2*sigma2)))*(-1))
}
# 対数尤度関数の微分のマイナス
dmu <- function(theta) {
  mu<-theta[1]
  sigma2<-theta[2]
  return(sum(x-mu)/sigma2)
}
dsigma2 <- function(theta){
  mu<-theta[1]
  sigma2<-theta[2]
  return((-1*n)/(2*sigma2) + sum((x-mu)^2)/(2*sigma2*sigma2))
}
Dlognorm <- function(theta){
  return(c(dmu(theta),dsigma2(theta))*(-1))
}

正規分布の推定

以下のように推定する。初期座標は適当に、μ=168, σ^2= 49としたが、これは最終的には同じような値に収束するので値の選び方はあまり関係がない(はず)。

hmcを実行する。

# HMC実行
fit <- hmc(N=100000,ini=c(168,49),E=lognorm,D=Dlognorm,L=100,epsi=0.01)

z <- fit$z # 母数の推定値
print(fit$ac); # 採用率 => 1
zzz<-c(1001:100000) # バーンイン期間を取り除いた期間

hmc関数の計算は結構時間がかかって、MacBookAirで実行したところ3分程度かかった。

ヒストグラムを描いてみる。
まず、平均μについて確認する。

hist(z[zzz,1],breaks = 50,freq =TRUE)

f:id:osn_th:20160728133350p:plain
MAP推定値(最頻値)を求めると、168.9となっている。
最尤推定値が170なので少々左にずれているが、ほぼ近しい値となっている。

> rev(sort(table(round(z[zzz,1],1))))[1]
168.9 
 5586

続いて、分散σ^2を確認する。

hist(z[zzz,2],breaks = 50,freq =TRUE)

f:id:osn_th:20160728133352p:plain
MAP推定値(最頻値)を求めると、48.8となっている。
最尤推定値が49なので、ほぼ近しい値となっている。

> rev(sort(table(round(z[zzz,2],1))))[1]
48.8
 599  

平均と分散の値から、かなり正確に正規分布をシミュレートできているよう。

最後に、収束性を確認するためにトレースラインを確認しておく。

# 正規分布モデルのトレースライン
options(scipen=100)
par(mfrow=c(2,1))
plot(z[,1],type="l",cex.axis=1.5,xlab="t",ylab="μ",cex.lab=1.5)
plot(z[,2],type="l",cex.axis=1.5,xlab="t",ylab="σ2",cex.lab=1.5)
par(mfrow=c(1,1))

f:id:osn_th:20160728133354p:plain
ほとんど全体的に同じ幅の長さで推移していて、十分に収束しているよう。

まとめ

HMC法をRで実装し、正規分布のサンプリングを行ってみた。ほとんど教科書をなぞってみただけだが、自分で理解しながら再実装しつつ、計算したりすることでHMCの理解がより深まったように思う。
アルゴリズム自体はそれほど複雑ではないので実装は簡単だが、ちゃんと背景を理解していないと結果を理解するのが難しいと思う。
ただHMCは正規分布から10万個程度のサンプリングであっても結構遅いように感じた。

以上!

※参考にした本

基礎からのベイズ統計学: ハミルトニアンモンテカルロ法による実践的入門

基礎からのベイズ統計学: ハミルトニアンモンテカルロ法による実践的入門

Cloudera Managerのチャート機能でリソース使用状況を可視化する

Hadoop Cloudera Manager

前回、Hadoopのパフォーマンスを計測するベンチマークの使い方を見た。
totech.hateblo.jp

通常、パフォーマンス計測時には裏でdstatやsarのようなサーバのリソース使用状況を取得するツールを走らせておくが、これらはログとして保管したり報告資料を作る上では便利だが、計測中にリソースを眺める分にはやや見づらい。
そこで、Cloudera Managerのチャート機能を使えば、リアルタイムにリソース使用状況をグラフ化できてとても見やすくなる。
チャート機能はtsqueryというSQLのようなクエリを登録することで取得対象のリソースを定義したり、where句で取得対象を絞ったりできる。

以下に基本的な設定内容を以下にまとめる。

リソース tsquery
CPU select cpu_percent
Load Average select load_1
メモリ select physical_memory_used
スワップ select swap_used
ネットワークIn select bytes_receive_rate
ネットワークOut select bytes_transmit_rate
ディスクread select read_bytes_rate where roleType = DATANODE
ディスクwrite select write_bytes_rate where roleType = DATANODE

これで以下のようなグラフができる。
f:id:osn_th:20160720102332p:plain

tsqueryを使えば、その他にもさまざまな指標が取得できる。取得可能なメトリクスの一覧は以下のClouderaのドキュメントにのっているのでこれを参考にいろいろ組み合わせられる。
http://www.cloudera.com/documentation/enterprise/5-3-x/topics/cm_metrics.html

以上!

Hadoop関連書籍(過去に読んだ本)

Hadoop 第3版

Hadoop 第3版

Hadoop徹底入門 第2版 オープンソース分散処理環境の構築

Hadoop徹底入門 第2版 オープンソース分散処理環境の構築

Hadoopのベンチマーク計測サンプル(TeraSort, TestDFSIO)を動かしてみる

Hadoop

構築したHadoopクラスタの性能がどの程度なのかを知る上で、共通で標準的な計測手順があると便利だと思い調べてみたところ、Hadoopに標準でベンチマークを計測するスクリプトが用意されているようだったので、これを使ってみる。
サンプルはたくさんあるようだが、ベンチマークとして一般的に使えそうなものにTeraSortとTestDFSIOという2つがあった。
TeraSortはその名の通り、大量データのソート速度を図るもの。ソートなので主にCPUやメモリをベンチマークの対象としている。
もう一つのTestDFSIOは分散ファイルシステムのIOということで、HDFSへの書き込み/読み込みを図る。つまりディスクをベンチマークを対象としている。
今回はこのふたつのサンプルを実行させてみることにする。

なお、Hadoop環境はCDH 5.5がインストールされたHadoop環境で実施した。

TeraSort

TeraSortを実行するためには3ステップの手順を実行する。

  1. TeraGen: ソート用の入力データ生成
  2. TeraSort: ソート処理実行
  3. TeraValidate: ソートされた結果の検証

この3ステップのプログラムはhadoop-examples.jarというHadoop標準のjarとして用意されているので、まずはこれをhadoop jarで呼び出してみる。

$ hadoop jar /usr/lib/hadoop-0.20-mapreduce/hadoop-examples.jar
An example program must be given as the first argument.
Valid program names are:
 aggregatewordcount: An Aggregate based map/reduce program that counts the words in the input files.
 aggregatewordhist: An Aggregate based map/reduce program that computes the histogram of the words in the input files.
 bbp: A map/reduce program that uses Bailey-Borwein-Plouffe to compute exact digits of Pi.
 dbcount: An example job that count the pageview counts from a database.
 distbbp: A map/reduce program that uses a BBP-type formula to compute exact bits of Pi.
 grep: A map/reduce program that counts the matches of a regex in the input.
 join: A job that effects a join over sorted, equally partitioned datasets
 multifilewc: A job that counts words from several files.
 pentomino: A map/reduce tile laying program to find solutions to pentomino problems.
 pi: A map/reduce program that estimates Pi using a quasi-Monte Carlo method.
 randomtextwriter: A map/reduce program that writes 10GB of random textual data per node.
 randomwriter: A map/reduce program that writes 10GB of random data per node.
 secondarysort: An example defining a secondary sort to the reduce.
 sort: A map/reduce program that sorts the data written by the random writer.
 sudoku: A sudoku solver.
 teragen: Generate data for the terasort ★
 terasort: Run the terasort ★
 teravalidate: Checking results of terasort ★
 wordcount: A map/reduce program that counts the words in the input files.
 wordmean: A map/reduce program that counts the average length of the words in the input files.
 wordmedian: A map/reduce program that counts the median length of the words in the input files.
 wordstandarddeviation: A map/reduce program that counts the standard deviation of the length of the words in the input files.

hadoop-examplesは今回のTeraSort以外にもWordCountやモンテカルロによる円周率算出(pi)などさまざまなプログラムが用意されていることが分かる。

TeraGen

TeraGenはTeraSortのための入力データを生成する。HDFS上の作業ディレクトリを/user/mapred/benchmarks/terasortとし、これが作成済みであるとする。
このとき、以下のコマンドでTeraGenが実行できる。

$ hadoop jar/usr/lib/hadoop-0.20-mapreduce/hadoop-examples.jar teragen 10000/user/mapred/benchmarks/terasort/input

第1引数はteragenと指定する。
第2引数は生成データ行を数値で指定する。1行あたり100バイトとなるので、10000を指定した場合は1,000,000バイト、つまり1MBのファイルを生成する。
第3引数はデータの出力先のパスを指定する。

このコマンドを実行するとデータ生成のMapReduceジョブが実行される。
完了すると、指定したパスに以下のようにデータが生成される。

$ hadoop fs -ls /user/mapred/benchmarks/terasort/input
Found 3 items
-rw-r--r-- 1 mapredsupergroup     0 2016-07-19 15:51 /user/mapred/benchmarks/terasort/input/_SUCCESS
-rw-r--r-- 1 mapred supergroup  500000 2016-07-19 15:51 /user/mapred/benchmarks/terasort/input/part-m-00000
-rw-r--r-- 1 mapred supergroup  500000 2016-07-19 15:51 /user/mapred/benchmarks/terasort/input/part-m-00001

TeraSort

続いて生成したデータを実際にソートする。
以下のコマンドでTeraSortを実行する。

$ hadoop jar/usr/lib/hadoop-0.20-mapreduce/hadoop-examples.jar terasort /user/mapred/benchmarks/terasort/input/user/mapred/benchmarks/terasort/output

第1引数はterasortと指定する。
第2引数はソート対象の入力データパス、つまりTeraGenの出力先を指定する。
第3引数はソート結果を出力するパスを指定する。

コマンドを実行するとソートを行うMapReduceジョブが実行される。
TeraSortはベンチマークスクリプトだが、計測はやってくれないので、このときに処理時間やCPU、メモリ等のリソース使用状況は自分で取得する必要がある。
リソースの取り方はdstat等のOSコマンドで各ノードのリソース使用状況を確認したり、Cloudera Managerがあるならリソース画面で確認してもよい。
ジョブが完了すると、以下のように結果が出力されている。

$ hadoop fs -ls /user/mapred/benchmarks/terasort/output
Found 3 items
-rw-r--r-- 1 mapred supergroup     0 2016-07-19 15:58 /user/mapred/benchmarks/terasort/output/_SUCCESS
-rw-r--r-- 10 mapred supergroup     0 2016-07-19 15:57 /user/mapred/benchmarks/terasort/output/_partition.lst
-rw-r--r-- 1 mapred supergroup  1000000 2016-07-19 15:58 /user/mapred/benchmarks/terasort/output/part-r-00000

TeraValidate

TeraSortによるソート処理が正しく行われたかを判定する処理。
TeraSortの出力結果に対し、以下のようなコマンドを実行することで検証が行える。

$ hadoop jar/usr/lib/hadoop-0.20-mapreduce/hadoop-examples.jar teravalidate/user/mapred/benchmarks/terasort/output /user/mapred/benchmarks/terasort/validate

第1引数はteravalidateと指定する。
第2引数は検証対象の入力データパス、つまりTeraSortのソート結果出力先を指定する。
第3引数は検証結果を出力するパスを指定する。

これを実行すると、以下のように結果が出力される。

$ hadoop fs -ls /user/mapred/benchmarks/terasort/validate
Found 2 items
-rw-r--r-- 1 mapred supergroup     0 2016-07-19 16:01 /user/mapred/benchmarks/terasort/validate/_SUCCESS
-rw-r--r-- 1 mapred supergroup    22 2016-07-19 16:01 /user/mapred/benchmarks/terasort/validate/part-r-00000

出力されたファイルの中身を確認する。

$ hadoop fs -cat /user/mapred/benchmarks/terasort/validate/part-r-00000
checksum    139abefd74b2

このようにchecksumのみが出力され、errorが出力されていなければソートは正しく行われたと判断できる。
ソートが正しくない場合は、以下のようにerrorが結果に含まれる。

checksum    365ed3f3e1
error misorder in part-m-00000 between 6a 97 43 59 ea ab 3a 59 4d 99 and 63 b6 04 4b 8e 78 91 14 83 73
error misorder in part-m-00000 between 88 2a 02 c3 15 36 2b 60 76 5f and 5c 90 ab 38 ae 52 89 62 15 d7
(略)
error misorder in part-m-00001 between 6e 45 fb 3d 1c 2c fd d1 cd 57 and 3c b4 46 e3 07 0c 3d 50 d0 19
error bad key partitioning:
 file part-m-00000:begin key 4a 69 6d 47 72 61 79 52 49 50
 file part-m-00000:end key 2c b7 a1 bb 93 62 af 20 a4 d9

TestDFSIO

TestDFSIOはHDFS上にデータを書き込み、読み込みしてIOを計測するためのベンチマークスクリプト。書き込みと読み込みでそれぞれ独立した処理になっている。
実行ファイルは以下で、TeraSortで使ったjarとは異なるので注意。

$ hadoop jar /usr/lib/hadoop-mapreduce/hadoop-mapreduce-client-jobclient-tests.jar
An example program must be given as the first argument.
Valid program names are:
 DFSCIOTest: Distributed i/o benchmark of libhdfs.
 DistributedFSCheck: Distributed checkup of the file system consistency.
 JHLogAnalyzer: Job History Log analyzer.
 MRReliabilityTest: A program that tests the reliability of the MR framework by injecting faults/failures
 SliveTest: HDFS Stress Test and Live Data Verification.
 TestDFSIO: Distributed i/o benchmark. ★
 fail: a job that always fails
 filebench: Benchmark SequenceFile(Input|Output)Format (block,record compressed and uncompressed), Text(Input|Output)Format (compressed and uncompressed)
 largesorter: Large-Sort tester
 loadgen: Generic map/reduce load generator
 mapredtest: A map/reduce test check.
 minicluster: Single process HDFS and MR cluster.
 mrbench: A map/reduce benchmark that can create many small jobs
 nnbench: A benchmark that stresses the namenode.
 sleep: A job that sleeps at each map and reduce task.
 testbigmapoutput: A map/reduce program that works on a very big non-splittable file and does identity map/reduce
 testfilesystem: A test for FileSystem read/write.
 testmapredsort: A map/reduce program that validates the map-reduce framework's sort.
 testsequencefile: A test for flat files of binary key value pairs.
 testsequencefileinputformat: A test for sequence file input format.
 testtextinputformat: A test for text input format.
 threadedmapbench: A map/reduce benchmark that compares the performance of maps with multiple spills over maps with 1 spill

TestDFSIOを引数なしで実行するとUsageが見れる。

$ hadoop jar /usr/lib/hadoop-mapreduce/hadoop-mapreduce-client-jobclient-tests.jar TestDFSIO
16/07/19 17:46:10 INFO fs.TestDFSIO: TestDFSIO.1.7
Missing arguments.
Usage: TestDFSIO [genericOptions] -read [-random | -backward | -skip [-skipSize Size]] | -write | -append | -clean [-compression codecClassName] [-nrFiles N] [-size Size[B|KB|MB|GB|TB]] [-resFile resultFileName] [-bufferSize Bytes]

書き込み

1MBファイルを10個、つまり10MBの書き込みを実行するコマンドは以下のように実行する。

$ hadoop jar/usr/lib/hadoop-mapreduce/hadoop-mapreduce-client-jobclient-tests.jar TestDFSIO \
-D test.build.data=/user/mapred/benchmarks/TestDFSIO \
-write -nrFiles 10 -fileSize 1MB

-Dオプションでtest.build.dataプロパティに計測用のデータ書き込み先パスを指定する。
-writeオプションで書き込みであることを指定し、-nrFilesでファイル数、-fileSizeでファイルごとのサイズを指定する。

これを実行するとMapReduceジョブが開始されるが、TeraSortと違い、ジョブの最後にベンチマーク値が標準出力される。

(・・・略)
16/07/19 16:16:37 INFO fs.TestDFSIO: ----- TestDFSIO ----- : write
16/07/19 16:16:37 INFO fs.TestDFSIO:      Date & time: Tue Jul 19 16:16:37 JST 2016
16/07/19 16:16:37 INFO fs.TestDFSIO:    Number of files: 10
16/07/19 16:16:37 INFO fs.TestDFSIO: Total MBytes processed: 10.0
16/07/19 16:16:37 INFO fs.TestDFSIO:   Throughput mb/sec: 1.196888090963495
16/07/19 16:16:37 INFO fs.TestDFSIO: Average IO rate mb/sec: 1.5878732204437256
16/07/19 16:16:37 INFO fs.TestDFSIO: IO rate std deviation: 1.2619301662658684
16/07/19 16:16:37 INFO fs.TestDFSIO:  Test exec time sec: 51.268
16/07/19 16:16:37 INFO fs.TestDFSIO:

スループットやI/O、処理時間などが分かる。

読み込み

書き込みと同じように、ただオプションを-writeから-readに変えるだけでよい。

$ hadoop jar/usr/lib/hadoop-mapreduce/hadoop-mapreduce-client-jobclient-tests.jar TestDFSIO \
-D test.build.data=/user/mapred/benchmarks/TestDFSIO \
-read -nrFiles 10 -fileSize 1MB

こちらも同じように結果が標準出力される。

(・・・略)
16/07/19 16:20:28 INFO fs.TestDFSIO: ----- TestDFSIO ----- : read
16/07/19 16:20:28 INFO fs.TestDFSIO:      Date & time: Tue Jul 19 16:20:28 JST 2016
16/07/19 16:20:28 INFO fs.TestDFSIO:    Number of files: 10
16/07/19 16:20:28 INFO fs.TestDFSIO: Total MBytes processed: 10.0
16/07/19 16:20:28 INFO fs.TestDFSIO:   Throughput mb/sec: 74.6268656716418
16/07/19 16:20:28 INFO fs.TestDFSIO: Average IO rate mb/sec: 164.8916778564453
16/07/19 16:20:28 INFO fs.TestDFSIO: IO rate std deviation: 148.3374932963216
16/07/19 16:20:28 INFO fs.TestDFSIO:  Test exec time sec: 48.181
16/07/19 16:20:28 INFO fs.TestDFSIO:

クリーン

書き込みや読み込みで生成したデータは削除されないので、計測が終わった場合ややり直したい場合はクリーンコマンドを実行するとよい。
パラメータは以下のように実行する。

$ hadoop jar/usr/lib/hadoop-mapreduce/hadoop-mapreduce-client-jobclient-tests.jar TestDFSIO \
-D test.build.data=/user/mapred/benchmarks/TestDFSIO \
-clean

これで以下のように出力される。

(・・・略)
16/07/19 16:23:49 INFO fs.TestDFSIO: TestDFSIO.1.7
16/07/19 16:23:49 INFO fs.TestDFSIO: nrFiles = 1
16/07/19 16:23:49 INFO fs.TestDFSIO: nrBytes (MB) = 1.0
16/07/19 16:23:49 INFO fs.TestDFSIO: bufferSize = 1000000
16/07/19 16:23:49 INFO fs.TestDFSIO: baseDir = /user/mapred/benchmarks/TestDFSIO
16/07/19 16:23:50 INFO fs.TestDFSIO: Cleaning up test files

クリーンはtest.build.dataオプションで指定したディレクトリが削除される。

$ hadoop fs -ls /user/mapred/benchmarks/TestDFSIO
ls: `/user/mapred/benchmarks/TestDFSIO': No such file or directory

ベンチマーク値の見方の注意

読み込み・書き込みのベンチマーク値の見方に注意がある。
まず、計測コマンドを実行したあとの結果はHDFS上の以下に出力されている。

$ hadoop fs -cat /user/mapred/benchmarks/TestDFSIO/io_read/part-00000
f:rate 1648916.8
f:sqrate    4.91932768E8
l:size 10485760
l:tasks 10
l:time 134

ここではpart-*という名前のファイルがたくさんあるが、これはそのMapタスクで処理したファイルのサイズや処理時間の合計を表している。
一方、標準出力される方はこれらの集計である。

※参考: 以下のソースおよびメーリングリスト
https://github.com/facebookarchive/hadoop-20/blob/master/src/test/org/apache/hadoop/fs/TestDFSIO.java
http://mail-archives.apache.org/mod_mbox/hadoop-common-user/200901.mbox/%3C496EACE2.2090007@yahoo-inc.com%3E

16/07/19 16:20:28 INFO fs.TestDFSIO: ----- TestDFSIO ----- : read
16/07/19 16:20:28 INFO fs.TestDFSIO:      Date & time: Tue Jul 19 16:20:28 JST 2016
16/07/19 16:20:28 INFO fs.TestDFSIO:    Number of files: 10
16/07/19 16:20:28 INFO fs.TestDFSIO: Total MBytes processed: 10.0
16/07/19 16:20:28 INFO fs.TestDFSIO:   Throughput mb/sec: 74.6268656716418
16/07/19 16:20:28 INFO fs.TestDFSIO: Average IO rate mb/sec: 164.8916778564453
16/07/19 16:20:28 INFO fs.TestDFSIO: IO rate std deviation: 148.3374932963216
16/07/19 16:20:28 INFO fs.TestDFSIO:  Test exec time sec: 48.181

最後のTest exec time secはhadoop jarコマンド全体の実行時間となるため、並列性が考慮され、それによるオーバーヘッドが含まれる。
しかし結果ファイルのpart-*の方はMapタスクごとの時間の合計として算出されている。
標準出力のThroughput mb/secやAverage IO rate mb/secはこれらのpart-*ファイルの値の合計や平均を算出しているため、あくまでMapの平均であり、並列性は考慮されず、Reducerやオーバーヘッドが含まれない結果となるので注意。
要するに、ここでのThroughputは各MapのThroughputの平均値をとっているため、クラスタのスループットとならないことに注意が必要。
そのためクラスタスループットは、Total MBytes processed /Test exec time sec で近似するのがよさそう。
出力ファイルサイズを増やしていくと、Throughput mb/secの値はあるサイズからどんどん値が小さくなっていくが、これはMap数の平均スループットとなるため、頭打ちするのではなくどんどん値が小さくなっていく。
一方、Total MBytes processed /Test exec time sec で近似した値はそのサイズでほぼ頭打ちする数値となる。

まとめ

Hadoopのベンチマーク取得のためのTeraSortとTestDFSIOというふたつの例の動かし方を一通り見た。
ベンチマークの取り方を知っておくと、異なるHadoopクラスタを性能という観点で比較する場合や構築時の動作確認、もしくはノード追加やメモリ増設やクラスタパラメータ変更の効果を測定する場合に便利。

なお性能を見るときはCloudera Managerのリソース画面を見るのが一番見やすいと思う。機会があればこちらも記事にしたい。

(※2016/7/20追記)
Cloudera Managerのリソース画面について記事を書きました。
totech.hateblo.jp


以上!

Hadoop関連書籍(過去に読んだ本)

Hadoop 第3版

Hadoop 第3版

Hadoop徹底入門 第2版 オープンソース分散処理環境の構築

Hadoop徹底入門 第2版 オープンソース分散処理環境の構築

RubyでHadoop Streamingを動かしてみる

Hadoop Ruby

mHadoopでMapReduceジョブを実行するには最近はHiveを使うのが一般的だが、MapReduceを手軽に使うための方法としてHadoop Streamingがある。
これは標準入出力を利用してMapReduceジョブを実行できるというもので、Javaで複雑なコードを実装せずに手軽に試せるので便利。
Hiveばかり使っているとMapReduceジョブのレイヤーがブラックボックスとなってしまうけど、MapperやReducerの動き自体を理解する上でもわかりやすいので、今回はこれを試してみる。

ちなみに環境はCDH 5.5の入った4ノードのクラスタ環境で試す。

Hadoop Streamingを使うときはMapperとReducerのそれぞれの処理を実装する実行可能なファイルを用意する。
ここで、MapperやReducerは入力データを標準入力から受け取り、結果を標準出力するように実装するのがポイントとなる。
そのため、標準入出力さえできればなんだってよいので、perlだろうがシェルスクリプトでもHadoopが動かせる。便利。今回はRubyを使うことにする。

WordCount

分散処理といえばまずはWordCountを実装するのがならわしのようなので、文字数カウント処理をHadoop Streamingを使って実装してみる。
WordCountは入力にスペース区切りの単語が並んだテキストを持ち、それらのテキストごとの出現数を算出する。

Mapperを作る

まずはmapperを作る。

wc_map.rb

#!/usr/bin/ruby
def wc_map(line)
 line.chomp.split.map { |e| [e, 1] }
end

def output(records)
 records.each do |key, value|
  puts "#{key}\t#{value}"
 end
end

while l = STDIN.gets
 output(wc_map(l))
end

標準入力から1行ずつテキストを受け取り、それをスペースで分割しつつ(単語, 1)という配列を作る。
最後にそれらをタブ区切りで標準出力する。

まずは単体で実行してみる。

$ cat sample.txt
aa bb cc
bb cc dd
cc ee ff
$ cat sample.txt | ./wc_map.r
aa   1
bb   1
cc   1
bb   1
cc   1
dd   1
cc   1
ee   1
ff   1

期待どおりの結果がえられたので、次はこれ(実際はソートもされる)を標準入力から受け取り、単語ごとに集計した結果を標準出力するReducerを作る。

Reducerを作る

wc_reduce.rb

#!/usr/bin/ruby
def wc_reduce(line, results)
 key, value = line.chomp.strip.split
 results[key] += value.to_i
end

results = Hash.new(0)
while l = STDIN.gets
 wc_reduce(l, results)
end
p results

標準入力が単語、数値のタブ区切りなのでそれを分割し、単語をキーとするハッシュを作り、バリューの部分を足しあわせていく。
最後にハッシュを標準出力して終わり。

MapperとReducerができたので、これらをパイプで組み合わせて以下のようにテストする。Hadoop Streamingで分散する場合も同じように処理されるので、これが通ればプログラムレベルではHadoop Streamingで動く。

$ cat sample.txt | ./wc_map.rb | sort | ./wc_reduce.rb
{"ff"=>1, "cc"=>3, "ee"=>1, "bb"=>2, "dd"=>1, "aa"=>1}

うまく動いているよう!

Hadoop Streamingで実行

MapperとReducerができたので、これをHadoop Streamingで分散処理させてみる。
以下のようなコマンドで実行する。入力となるサンプルファイルはHDFS上に配置した上でそのパスを指定する。また、-fileで作成したMapperとRedcuerを指定する。

$ hadoop jar CDH-5.5.1-1.cdh5.5.1.p1168.923/lib/hadoop-0.20-mapreduce/contrib/streaming/hadoop-streaming-mr1.jar -input /user/mapred/example/stream/sample.txt -output /user/mapred/example/stream/wc_result -mapper wc_map.rb -reducer wc_reduce.rb -file wc_map.rb wc_reduce.rb
16/06/08 17:13:33 WARN streaming.StreamJob: -file option is deprecated, please use generic option -files instead.
packageJobJar: [wc_map.rb, wc_reduce.rb] [CDH-5.5.2-1.cdh5.5.2.p0.4/jars/hadoop-streaming-2.6.0-cdh5.5.2.jar] /tmp/streamjob8479180127713306959.jar tmpDir=null
16/06/08 17:13:34 INFO client.ConfiguredRMFailoverProxyProvider: Failing over to rm16
16/06/08 17:13:34 INFO mapred.FileInputFormat: Total input paths to process : 1
16/06/08 17:13:35 INFO mapreduce.JobSubmitter: number of splits:2
16/06/08 17:13:35 INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1465349429352_0013
16/06/08 17:13:35 INFO impl.YarnClientImpl: Submitted application application_1465349429352_0013
16/06/08 17:13:35 INFO mapreduce.Job: The url to track the job: http://myhadoop04:8088/proxy/application_1465349429352_0013/
16/06/08 17:13:35 INFO mapreduce.Job: Running job: job_1465349429352_0013
16/06/08 17:13:39 INFO mapreduce.Job: Job job_1465349429352_0013 running in uber mode : false
16/06/08 17:13:39 INFO mapreduce.Job: map 0% reduce 0%
(・・・略)
16/06/08 17:13:48 INFO streaming.StreamJob: Output directory: /user/mapred/example/stream/wc_result

正常にMapReduceジョブとして実行できた。結果はHDFSのoutputオプションで指定したパスに出力されているので内容を確認する。

$ hadoop fs -cat /user/mapred/example/stream/wc_result/part-00000
{"ff"=>1, "cc"=>3, "ee"=>1, "bb"=>2, "dd"=>1, "aa"=>1}

さきほどコマンドラインでやったのと同じく、正しい結果が得られた!

分散データファイル生成

定型的なWordCountをHadoop Streamingで動かすことができた。次は、これを応用してランダムな文字列からなるファイル生成を分散させてやってみる。
こういった処理はHiveとかだとクエリが書きづらいので、Hadoop Streamingでやると簡単に書ける。

Mapperを作る

Hadoopの負荷分散の仕組みは、Mapperからの入力ごとにタスクを切り出して行うため、複数行の入力をあたえる必要がある。
そこで、今回は入力に各行に生成したランダムファイルの行数と行の長さが書かれたデータを想定する。
例えば、以下のような入力を与えると、10文字のランダム文字列が5行というファイルが2つ作られるような仕様とする。

5   10
5   10

Mapperの実装は以下。

gendata_map.rb

#!/usr/bin/ruby

def random_string(length)
 o = ("a".."z").to_a
 (0..length).map { o[rand(o.length)] }.join
end

def gendata_map(size, length)
 size.times do |i|
    puts random_string(length)
 end
end

# STDIN row : <row num> <row length>
STDIN.each_line do |line|
 size, length = line.chomp.split.map(&:to_i)
 size = 5 unless size
 length = 5 unless length
 gendata_map(size, length)
end

指定した長さのランダム文字列を作り、それを行数分だけ繰り返す。
ここでMapperのみでコマンドラインで単体テストを実施しておく。Hadoop StreamingはMapperやReducer単位で単体テストしやすいのがよい。

$ cat test.txt
10 2
10 2
10 2
$ cat test.txt | ruby gendata_map.rb
fkzuvyzib
lsvymrrlz
ecvtpsplx
wawdxfeps
lkstptsva
metunykpj

Mapperは意図どおりに動いているよう。

Reducerは不要

今回の処理はMapperだけで処理が完結しているので、このようなプログラムの場合はReducerは不要となる。

Hadoop Streamingで実行

WordCountのときと同じように実行する。ReducerがないのでReduceタスクは不要となる。
また、今回は行ごとに処理を分割したいので、map数とreducer数をオプションで明示して実行する(3行をそれぞれ別のMapperで処理させたいのでMap数は3とする)。

$ hadoop jar hadoop-0.20-mapreduce/contrib/streaming/hadoop-streaming-mr1.jar \
-D mapred.map.tasks=3 \
-D mapred.reduce.tasks=0 \
-input /user/mapred/example/gendata/input/input.txt -output/user/mapred/example/gendata/output -mapper gendata_map.rb -file gendata_map.rb

正常に処理が完了し、outputディレクトリを確認すると3つのMapが生成したそれぞれのファイルが作成されている。

$ hadoop fs -ls /user/mapred/example/gendata/output | head
Found 10001 items
-rw-r--r-- 3 mapred hadoop     0 2016-06-13 15:59 /user/mapred/example/gendata/output/_SUCCESS
-rw-r--r-- 3 mapred hadoop 20600000 2016-06-13 15:47 /user/mapred/example/gendata/output/part-00000
-rw-r--r-- 3 mapred hadoop 10300000 2016-06-13 15:47 /user/mapred/example/gendata/output/part-00001
-rw-r--r-- 3 mapred hadoop 10300000 2016-06-13 15:47 /user/mapred/example/gendata/output/part-00002

内容は以下のようになっている。

$ hadoop fs -cat /user/mapred/example/gendata/output/part-00000
dcvvxbxyu
fnijosqtz
$ hadoop fs -cat /user/mapred/example/gendata/output/part-00001
ocndfuctp
limwdnvke
$ hadoop fs -cat /user/mapred/example/gendata/output/part-00002
mdkewafhj
bcekxzcns

今回はReducerで集約していないので、最終的な出力結果はMapperそれぞれが出力したファイルが並ぶことになるが、Hive等でテーブルにする場合はこれで問題ない。
大量ランダムデータ生成のような処理をHadoopで分散処理させたい場合はHadoop Streamingを使うと便利な気がする。

まとめ

Hadoop Streamingを、簡単なRubyスクリプトで動かしてみた。MapReduceをJavaで実装するのは面倒だけど、Hiveではクエリが書きづらい、といった場合に、標準入出力のみ意識したコードをかけばHadoopクラスタで分散処理できるため、便利な機能だと思う。
また、Hadoop入門者がMapReduceの動きを理解する上で、コマンドラインとパイプでMapperとReducerそれぞれの処理のイメージがつかめるので、勉強用でもいいと思う。

以上!

Hadoop関連書籍(過去に読んだ本)

Hadoop 第3版

Hadoop 第3版

Hadoop徹底入門 第2版 オープンソース分散処理環境の構築

Hadoop徹底入門 第2版 オープンソース分散処理環境の構築

MacでR+MeCabによる形態素解析をやってみた

R

自然言語処理でおなじみの形態素解析ツールMeCabをMac上のRから使えるようにしてみた。今回は環境構築の手順をメモ。
環境情報は以下。

  • Mac OS X El Capitan (10.11.3)
  • MeCab 0.996
  • R 3.2.3 (2015-12-10)

MeCabインストール

MeCabは、macではbrewで簡単にインストールできる。
まず、brewからmecabを探す。

$ brew search mecab
mecab            mecab-ipadic     mecab-jumandic   mecab-ko         mecab-ko-dic     mecab-unidic
homebrew/php/php53-mecab          homebrew/php/php55-mecab          homebrew/php/php70-mecab
homebrew/php/php54-mecab          homebrew/php/php56-mecab

色々ヒットするが、mecabがMeCab本体のパッケージとなる。dicで終わっているやつはそれぞれが辞書を表していて、形態素解析を行うためには単語をあらかじめこのように辞書ファイルとして登録しておく必要がある。いろいろな機関が辞書を提供しており、用途によって使い分けがあるようだが、今回は標準的なIPAの辞書(ipadic)を使ってみる。
今回はmecabmecab-ipadicの二つをインストールする。

MeCab本体とIPA辞書をインストールする。

$ brew install mecab
$ brew install mecab-ipadic

インストールに成功。mecabコマンドが正しく実行できることを確認。

$ mecab -v
mecab of 0.996

ちなみに辞書がないと以下のエラーになる。

$ mecab
param.cpp(69) [ifs] no such file or directory: /usr/local/lib/mecab/dic/ipadic/dicrc

MeCabを使ってみる

まずRを介さず、直接MeCabを使ってみる。

mecabコマンドを実行すると、対話的に日本語を入力し、形態素解析の結果を表示してくれる。終了するときはCtrl-D。

$ mecab
今日はいい天気だ
今日    名詞,副詞可能,*,*,*,*,今日,キョウ,キョー
は      助詞,係助詞,*,*,*,*,は,ハ,ワ
いい    形容詞,自立,*,*,形容詞・イイ,基本形,いい,イイ,イイ
天気    名詞,一般,*,*,*,*,天気,テンキ,テンキ
だ      助動詞,*,*,*,特殊・ダ,基本形,だ,ダ,ダ
EOS
明日は天気が悪い
明日    名詞,副詞可能,*,*,*,*,明日,アシタ,アシタ
は      助詞,係助詞,*,*,*,*,は,ハ,ワ
天気    名詞,一般,*,*,*,*,天気,テンキ,テンキ
が      助詞,格助詞,一般,*,*,*,が,ガ,ガ
悪い    形容詞,自立,*,*,形容詞・アウオ段,基本形,悪い,ワルイ,ワルイ
EOS

ワンライナーで実行するときはechoをパイプでつなぐ。

$ echo "Rからmecabを使って形態素解析を行ってみたいと思う" | mecab
R       名詞,固有名詞,組織,*,*,*,*
から    助詞,格助詞,一般,*,*,*,から,カラ,カラ
mecab   名詞,一般,*,*,*,*,*
を      助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
使っ    動詞,自立,*,*,五段・ワ行促音便,連用タ接続,使う,ツカッ,ツカッ
て      助詞,接続助詞,*,*,*,*,て,テ,テ
形態素  名詞,一般,*,*,*,*,形態素,ケイタイソ,ケイタイソ
解析    名詞,サ変接続,*,*,*,*,解析,カイセキ,カイセキ
を      助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
行っ    動詞,自立,*,*,五段・ワ行促音便,連用タ接続,行う,オコナッ,オコナッ
て      助詞,接続助詞,*,*,*,*,て,テ,テ
み      動詞,非自立,*,*,一段,連用形,みる,ミ,ミ
たい    助動詞,*,*,*,特殊・タイ,基本形,たい,タイ,タイ
と      助詞,格助詞,引用,*,*,*,と,ト,ト
思う    動詞,自立,*,*,五段・ワ行促音便,基本形,思う,オモウ,オモウ
EOS

さらに別の辞書を使ってみる。UniDic辞書をインストールする。

$ brew install mecab-unidic

辞書は以下のように追加されている。

$ ls /usr/local/lib/mecab/dic
ipadic unidic

インストールしたUniDic辞書に切り替えて実行するには、-dオプションにインストールした辞書のパスを指定する。

$ mecab -d /usr/local/lib/mecab/dic/unidic
今日はいい天気だ
今日    キョー  キョウ  今日    名詞-普通名詞-副詞可能
は      ワ      ハ      は      助詞-係助詞
いい    イー    ヨイ    良い    形容詞-非自立可能       形容詞  連体形-一般
天気    テンキ  テンキ  天気    名詞-普通名詞-一般
だ      ダ      ダ      だ      助動詞  助動詞-ダ       終止形-一般
EOS

先ほどのIPA辞書の結果と比べると、今回の例では単語の分割のされ方は同じだが、フォーマット・得られる情報が違うことが分かる。当然、文によっては分割自体が異なることもあり得る。

RでMeCabを使う

続いてRからMeCabを実行してみる。RMeCabというRパッケージを使うと簡単らしい。
Rを実行し、以下のようにインストールする。

install.packages ("RMeCab", repos = "http://rmecab.jp/R")

RMeCabの使い方は以下を参考
http://www.ic.daito.ac.jp/~mizutani/mining/rmecab_func.html


早速Rの中でMeCabを呼び出してみる。

library(RMeCab)
RMeCabC("今日はいい天気だ")
[[1]]
  名詞 
"今日" 

[[2]]
助詞 
"は" 

[[3]]
形容詞 
"いい" 

[[4]]
  名詞 
"天気" 

[[5]]
助動詞 
  "だ"

Rのlistとして、形態素解析結果が得られた。
また、他にもいくつか機能はあるが、多くはテキストファイルからの入力を前提としているので、テストファイルを用意する。

$ cat test.txt
今日はいい天気です。
明日の天気は雨の予報です。

これをRで以下のように呼び出す。

> RMeCabFreq("mecab/test.txt")
file = mecab/test.txt 
length = 10 
   Term  Info1    Info2 Freq
1  です 助動詞        *    2
2    は   助詞   係助詞    2
3    の   助詞   連体化    2
4  予報   名詞 サ変接続    1
5  天気   名詞     一般    2
6    雨   名詞     一般    1
7  今日   名詞 副詞可能    1
8  明日   名詞 副詞可能    1
9  いい 形容詞     自立    1
10   。   記号     句点    2

単語ごとの出現頻度が表示されている!

まとめ

MacにMeCabをインストールし、コマンドラインおよびRから実行できるところまで検証した。これで様々な自然言語の解析ができるようになったので、いろいろ試してみたい。

以上!

関連記事

R言語とImageMagickでgifアニメを作成

R

R言語で分布などをグラフィック生成される場合に、パラメータを少しずつ変えて微妙に変化するグラフィックをアニメーションさせて見てみたくなるときがよくある。
例えば、カイ二乗分布のような自由度ごとにグラフの形が大きく変わるようなものは、自由度を少しずつ変化させながらグラフの形状をアニメーションさせられるとより分かりやすい。教科書では、紙だとさすがにせいぜい2, 3個程度の代表的な値でのグラフを重ねて描くしかないが、R言語ならば動的なアニメーションを作成することも難しくなさそう。

手っ取り早くアニメーションを表示する方法として、gifファイルがシンプルでよさそう。ということで、今回は2通りの方法でRグラフィックをgifアニメとして作成してみた。

Rのanimationパッケージを使う

Rで作った画像をアニメーションとして作成するには、animationパッケージを使うのが一般的らしい。これを使うと、gifアニメーションだけでなく、HTMLやmpeg4などでの出力も可能らしい。ちなみにgifを作るためにはImageMagickというツールが必要なので、あらかじめインストールしておく。

事前にanimationパッケージをインストールする。

install.packages("animation")

そして、以下の様にsaveGIF関数の中にグラフィックを生成する処理を複数回呼び出す実装をすることで、アニメーションファイルが作成される。

library(animation)
saveGIF({
  ani.options(loop = TRUE)
  for (i in 1:5) {
    .main = paste("カイ二乗分布 自由度=", i, seq="")
    curve(dchisq(x, df=i), xlim=c(0,10), ylim=c(0, 0.3), main=.main)
  }
}, movie.name="chi.gif", interval=0.5)

この方法で、以下のようなgifアニメが作成できる。
f:id:osn_th:20160418085712g:plain

Rで画像を保存して直接ImageMagickを使う

animationパッケージが内部的に行っているのは、画像を出力し、ImageMagick(convertコマンド)で画像を一つのgifアニメーションファイルに変換しているので、同じことをR標準機能とコマンドでも行ってみる。

まず、以下のようにpng関数とdev.off関数でPNG画像ファイルを生成する。

for (i in seq(10, 100, 10)) {
  .main = paste("カイ二乗分布 自由度=", i, seq="")
  .file <- paste("out/chi", sprintf("%03d", i), ".png", sep="")
  png(.file)
  curve(dchisq(x, df=i), xlim=c(0,200), main=.main)
  dev.off()
}

このコードを実行することで、以下のようにoutディレクトリにpngファイルが作成される。

$ ls out
chi010.png chi030.png chi050.png chi070.png chi090.png
chi020.png chi040.png chi060.png chi080.png chi100.png

これをconvertコマンドで一つのgifファイルにまとめる。delayオプションでアニメーション切り替わりの間隔の時間を指定できる(単位は1/100秒なので、50を指定した場合は0.5秒)。

$ convert -delay 50 out/*.png out/chi2.gif
$ ls out/chi2.gif
out/chi2.gif

このコマンドで生成されたgifファイルは以下。
f:id:osn_th:20160416130726g:plain

まとめ

animationパッケージとImageMagickを直接使うことでRが生成したグラフィックをアニメーションさせる二通りの方法を見た。パラメータを少しずつ変えながら動的にグラフィックを表示させることで、よりデータの理解がしやすくなる場面も多そう。

以上

【統計】二項分布をR言語で描画してみた

統計 R

今回は二項分布のヒストグラムをR言語で可視化してみた。二項分布は「コイン投げで表が出る」や、「サイコロを振って1の目が出る」のような事象が一定確率で「成功するか、しないか」の2者択一の試行をそれぞれ独立に繰り返したときに現れる分布(ちなみにこういう試行をベルヌーイ試行という)。パラメータとして、試行回数と事象が成功する確率の2つをとりうるので、今回は様々な値を使って実際にヒストグラムを描いてみることにする。

二項分布の定義

まず二項分布の正確な定義を確認する。成功確率pの試行をn回繰り返したときに、x回成功する確率f(x)は、以下の式で求められる。ただし、x = 0, 1, 2, … , n。
f:id:osn_th:20160415084601p:plain
このf(x)の確率分布が二項分布であり、B(n, p)で表す。

二項分布のヒストグラム

上記の定義を踏まえて、早速Rで実装してみる。まず、成功確率pの試行をn回繰り返したときの成功回数を求める関数を作成し、それを10,000回繰り返したときの分布(ヒストグラム)を描く関数を作成する。

# B(n,p)の二項分布に従う試行をしたときの成功回数
bin <- function(n, p = 0.5) {
  sum(sample(0:1, size = n, replace = TRUE, prob = c(p, 1 - p)))
}

# B(n,p)のヒストグラムを描画
hist_bin <- function(n = 20, p = 0.5) {
  x <- sapply(c(1:10000), function(x) { bin(n, p)} )
  main <- paste("二項分布 B(", n, ", ", p, ")", seq="")
  hist(x, breaks=0:n, freq=FALSE, main=main, ylim=c(0,0.30))
}

まずは確率1/2の試行を20回行う場合の分布を描画する。また、例によってR標準の二項分布の関数を使って正しいグラフを重ねてみる。

hist_bin(20,0.5)
curve(dbinom(x, 20, 0.5), 0, 20, 21, add=TRUE)

f:id:osn_th:20160415085027p:plain
x=10を中心とした、正規分布のように左右対称な形のヒストグラムになった。ちなみに、二項分布の期待値E(X)はnとpの積で求められる。今回の場合だと20*1/2=10となっており、今回の実験結果と矛盾していない。

パラメータを変えながらヒストグラムを描画

続いて、二項分布のパラメータを徐々に変えながらいろいろなヒストグラムを描いてみる。

試行回数を固定し、確率を上げていく

まず、試行回数を20回で固定したまま、確率を0.1から0.9まで0.1ずつ増やしていく。

# B(n=20, p=[0.1,0.9])
for (i in seq(0.1, 0.9, 0.1)) {
  .file <- paste("out/plot", i, ".png", sep="")
  hist_bin(20, i)
}

以下は描画した画像をgifアニメーションにまとめたもの。
f:id:osn_th:20160415084604g:plain
同じ形の山が右から左に流れているのが分かる。

確率を固定し、試行回数を増やしていく

続いて、確率を1/2に固定したまま、試行回数を10~100まで10回ずつ増やしていった場合のヒストグラムを以下のようなコードで描いてみた。

# B(n=[10,100], p=0.5)
for (i in seq(10, 100, 10)) {
  .file <- paste("out/plot", sprintf("%03d", i), ".png", sep="")
  hist_bin(i, 0.5)
}

f:id:osn_th:20160415084609g:plain
高い山が次第に小さくなっていくのが分かる。中心は期待値であるnとpの積になっている。

まとめ

二項分布をパラメータの値を変えながらヒストグラムを描いてみた。このような複数パラメータがある分布は教科書とかだと代表的なものを2,3個程度を重ねて描かれる程度だが、Rを使って連続実行することでパラメータの値を少しずつ変えながら表示できるので、動きが分かりやすい。

以上

参考文献

統計学入門 (基礎統計学)

統計学入門 (基礎統計学)

Rによるやさしい統計学

Rによるやさしい統計学

関連する記事



【機械学習】Rで手書き数字データMNISTをニューラルネットで学習してみた

機械学習 R

今日はR言語を使って機械学習の入門ということで、ニューラルネットを使ってみた。
今回の目標は、Rを使って機械学習の一連のプロセスである、トレーニングデータの学習によるモデル構築と、それを使ったテストデータの評価までを一通りやってみる。なので、手書き数字データの分類という、ごくありふれた例ではあるが、精度を高める、というところは目的としない。

手書き数字データMNISTの準備

MNISTは28*28のグレースケールの手書き数字データで、28*28=784個のピクセルと、その画像が0~9のどの数字を表しているかの正解ラベル1つの合計785次元ベクトルとして提供されているデータで、機械学習による分類器の作成の検証にあたって、よく利用されているらしい。

データはkaggleという機械学習の精度を競うサイトからダウンロードできたので、これを使ってみる。
https://www.kaggle.com/c/digit-recognizer/data

ここからトレーニングデータとテストデータを入手できるが、今回は正解ラベル付きのトレーニングデータのみを利用する。
f:id:osn_th:20160411092843p:plain

RでMNIST手書き数字を可視化してみる

まずはトレーニングデータを可視化してみる。
train.csvはlabel, pixel0, pixel2, … , pixel783 というヘッダを持つcsvファイルの形式になっている。label列は0~9のどの数字であるかの正解ラベルを表し、それ以外のピクセルは28*28のそれぞれの画素に対応する濃淡を表すグレースケールの数字が0~255の間の数値として表現されている。

なので、まず以下のRコードで各行ごとに数字をヒートマップで表示してみる。

# MNISTのトレーニングデータ読み込み
train <- read.csv("MNIST/train.csv")

# MNISTのトレーニングデータを画像表示する
view_train <- function(train, range = 1:20) {
  par(mfrow=c(length(range)/4, 5))
  par(mar=c(0,0,0,0))
  for (i in range) {
    m <- matrix(data.matrix(train[i,-1]), 28, 28)
    image(m[,28:1])
  }
}

# ラベルを表示
view_label <- function(train, range = 1:20) {
  matrix(train[range,"label"], 4, 5, byrow = TRUE)
}

range <- 1:20
view_train(train, range)
view_label(train, range)

これを実行すると、以下の結果が得られる。手書き数字の形が見て取れる。
f:id:osn_th:20160411092847p:plain
この画像と対応する正解ラベルの値も同じ4行5列の行列で表示させてみる。確かに手書き画像とラベルの対応関係は一致していそう。

> view_label(train, range)
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    0    1    4    0
[2,]    0    7    3    5    3
[3,]    8    9    1    3    3
[4,]    1    2    0    7    5

ニューラルネットで学習

今回はニューラルネットワークを使って、ピクセルのデータから正解ラベルを導出するための学習を行ってみる。
Rにはnnetというニューラルネットワークのライブラリがあるので、これを使ってみる。

library(nnet)

# MNISTのトレーニングデータ読み込み
train <- read.csv("MNIST/train.csv")

# 42000件のデータを30000件のトレーニングデータと12000件のテストデータにランダム・サンプリング
training.index <- sample(1:nrow(train), 30000)
mnist.train <- train[training.index,]
mnist.test <- train[-training.index,]

# トレーニングデータをニューラルネットで学習
mnist.nnet <- nnet(label ~ ., size=3, data=mnist.train)

これを実行すると、以下の箇所でエラーになった。

> mnist.nnet <- nnet(label ~ ., size=3, data=mnist.train)
 nnet.default(x, y, w, ...) でエラー: too many (2359) weights

?nnetでヘルプを見ると、MaxNWtsというパラメータで重みの最大値を指定しているが、その値を超えたことが原因のよう。

MaxNWts
The maximum allowable number of weights. 
There is no intrinsic limit in the code, but increasing MaxNWts will probably allow fits that are very slow and time-consuming.

なので、MaxNWtsを4000として再実行する。

> mnist.nnet <- nnet(label ~ ., size=3, data=mnist.train, MaxNWts = 4000)
# weights:  2359
initial  value 756877.345042 
final  value 608029.000000 
converged

今度はエラーにならずに正常に学習できたよう。

続いて、テストデータを学習したモデルを使って予測してみる。

> mnist.result <- predict(mnist.nnet, mnist.test, type="class")
 predict.nnet(mnist.nnet, mnist.test, type = "class") でエラー: 
  inappropriate fit for class

またエラーになった。。エラーメッセージはよく不正確だが、クラス分類のところがまずそう。
なんとなく、元のトレーニングデータの正解ラベルを表すlabel列がintegerなのがまずい気がする。0~9はラベルとして扱いたいのに、数値として回帰分析されているような気がする。。なので、factorにしてみる。

train[,"label"] <- as.factor(train[,"label"])

これで再度学習をやりなおす。

> mnist.nnet <- nnet(label ~ ., size=3, data=mnist.train, MaxNWts = 4000)
# weights:  2395
initial  value 70427.970781 
iter  10 value 56024.007059
iter  20 value 53612.550630
iter  30 value 53358.071108
iter  40 value 53198.640707
iter  50 value 52431.262209
iter  60 value 52263.579201
iter  70 value 51939.286749
iter  80 value 51693.656379
iter  90 value 51588.731655
iter 100 value 51332.028230
final  value 51332.028230 
stopped after 100 iterations

さっきまでと明らかに動きが違う!学習にも結構時間がかかるようになった。
これで再度テストデータを予測してみる。

> mnist.result <- predict(mnist.nnet, mnist.test, type="class")

エラーがでなくなった。成功したっぽい!

最後にテスト結果を出力する。

> table(mnist.result)
mnist.result
   0    1    4    7 
 996 1419 6762 2823

> table(mnist.test$label, mnist.result, dnn = c("Actual", "Predicted"))
      Predicted
Actual    0    1    4    7
     0  891   27   36  186
     1    0 1144  183    6
     2    4   28 1079   44
     3    8   53 1012  139
     4    1    7 1125   50
     5   60   31  438  584
     6   12   15 1152   49
     7    8   12  145 1093
     8    6   96  939  121
     9    6    6  653  551

Actualが実際の正解ラベルの値で、Predictedが今回の学習モデルが推測したラベルである。なんか0,1,4,7としか予測していない。。

もう一度学習

トレーニングデータが少なかったせいか?と思い、今度は42,000件のデータのうち40,000件を使って学習させた後、2,000件のデータでテストすることに。

最終的に以下のRコードを実行。

library(nnet)

# MNISTのトレーニングデータ読み込み
train <- read.csv("MNIST/train.csv")
train[,"label"] <- as.factor(train[,"label"])

# 42000件のデータを40000件のトレーニングデータと2000件のテストデータに分割
training.index <- 1:40000
mnist.train <- train[training.index,]
mnist.test <- train[-training.index,]

# トレーニングデータをニューラルネットで学習
mnist.nnet <- nnet(label ~ ., size=3, data=mnist.train, MaxNWts=4000)

# テストデータを使って評価
mnist.result <- predict(mnist.nnet, mnist.test, type="class")
table(mnist.test$label, mnist.result, dnn = c("Actual", "Predicted"))

# テストデータの正解、予測を表示
range <- 1:20
view_train(mnist.test, range)
view_label(mnist.test, range)
matrix(mnist.result, 4, 5, byrow = TRUE)

結果は以下。さっきよりはマシだが、やはり5, 8, 9の数値が予測結果に一つも出てきていない。

> table(mnist.test$label, mnist.result, dnn = c("Actual", "Predicted"))
      Predicted
Actual   0   1   2   3   4   6   7
     0 180   0   0   1   0  16   0
     1   2 217   1   0   1   0   4
     2   9   8  88  14   1  69   1
     3  99   3  12  50  16   8  10
     4  17   6   0   0 190  10   3
     5 124   1   9   8  11   9   0
     6  16   3   3   0   0 193   1
     7   0   7   2   4  11   0 178
     8 106  12   0   3  43  10   5
     9   9   3   0   1 166   4  22

最後に正答率を算出する。

# 正答率算出
accuracy <- function(actual, predicted) {
  ret = data.frame(actual = actual, predicted = predicted)
  ok = 0
  for (i in 0:9) {
    ok = ok + nrow(ret[ret$actual == i & ret$predicted == i, ])
  }
  ok / length(actual)
}
accuracy(actual = mnist.test$label, predicted = mnist.result)

この結果は以下。

> accuracy(actual = mnist.test$label, predicted = mnist.result)
[1] 0.548

54.8%。。結構低い。。が、最初にも行ったとおり、今回の目的は精度の高い学習モデルを作ることではないので、いったんここまでにする。

まとめ

Rを使ってMNIST手書き数字データをニューラルネットで機械学習し、テストデータを評価して正答率を出す一連の流れをやってみた。ニューラルネットのパラメータはほとんどチューニングせず適当にやっていることもあって、精度は今ひとつだったが、Rを使って学習モデルを作る流れは他の学習アルゴリズムを使ったりパラメータチューニングしても変わらないので、あとはデータの特性や学習結果、アルゴリズムの中身を知りながら精度を高める作業を進めていくのだろうが、今日はここまでとする。

以上!

参考図書

データサイエンティスト養成読本 R活用編 【ビジネスデータ分析の現場で役立つ知識が満載! 】 (Software Design plus)

データサイエンティスト養成読本 R活用編 【ビジネスデータ分析の現場で役立つ知識が満載! 】 (Software Design plus)

  • 作者: 酒巻隆治,里洋平,市川太祐,福島真太朗,安部晃生,和田計也,久本空海,西薗良太
  • 出版社/メーカー: 技術評論社
  • 発売日: 2014/12/12
  • メディア: 大型本
  • この商品を含むブログ (7件) を見る
WEB+DB PRESS Vol.89

WEB+DB PRESS Vol.89

  • 作者: 佐藤歩,泉水翔吾,村田賢太,門田芳典,多賀千夏,奥一穂,伊藤直也,鍛治匠一,中山裕司,高山温,佐藤太一,西尾泰和,中島聡,はまちや2,竹原,青木大祐,WEB+DB PRESS編集部
  • 出版社/メーカー: 技術評論社
  • 発売日: 2015/10/24
  • メディア: 大型本
  • この商品を含むブログを見る

関連記事