Ubuntu14.04での自分的環境構築をちゃんと書く

概要

先日なんとなくgnome3を使ってみようと思い、色々やっているうちにデスクトップが死んだ。 復旧するより入れなおしたほうが早い感じだったので、昔書いたものを見ながらインストールしようと思ったが、あまりに適当なことしか書いておらず絶望した。 今後のために、クリーンインストールされたubuntu14.04から、ちゃんと自分の設定を復元できるように書いておく。

インストールするものは以下とその他細かい諸々。

  • cuda
  • chainer
  • TensorFlow
  • Emacs

細かいこと色々

$ LANG=C xdg-user-dirs-gtk-update
$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get install freeglut3-dev build-essential libx11-dev libxmu-dev  libxi-dev libgl1-mesa-glx libglu1-mesa libglu1-mesa-dev linux-headers-`uname -r`
$ sudo apt-get install zsh
$ chsh -s /usr/bin/zsh
$ sudo apt-get install git
$ sudo apt-get install gimp ipython ipython-notebook

cudaのインストール

nvidiaのサイトから自分にあったグラフィックボードのドライバとcudaをダウンロードする。 TensorFlowは7.0をインストールしろと言っているので、最新版の7.5ではなく、7.0をダウンロードする。

まだよくわかってないけど、こちら にやれと書いてあるので、ブラックリストに登録をする。たぶんドライバが干渉するのを防ぐんだと思う。

$ sudo nano /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
$ sudo update-initramfs -u
$ sudo reboot

再起動したらCUIに切り替えて以下を実行する。

$ sudo service lightdm stop
$ chmod +x NVIDIA-Linux-x86_64-352.63.run
$ sudo ./NVIDIA-Linux-x86_64-352.63.run
$ chmod +x cuda_7.0.28_linux.run
$ sudo ./cuda_7.0.28_linux.run

cudaの方でグラフィックドライバをインストールするかと聞かれるが、別途入れているのでnoを選択する。 ちなみにyesを選択すると、黒画面に白カーソルが表示されるだけで操作できず、しかもCUIにも入れないという悲しい事態になった。

$ sudo service lightdm start

ここで正しくGUIが表示されなかったら諦める。

.zshrcに以下を追記して、CUDAにパスを通す。

export CUDA_HOME=/usr/local/cuda-7.0
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${CUDA_HOME}/lib64
export PATH=$PATH:${CUDA_HOME}/bin

chainerのインストール

とりあえずpipを入れる。最近はget-pip.pyを実行して入れるらしい。 こちらを参考にした。

chainerを入れるときにhdf5がなんとかかんとかと言われたが、こちら を見てごにょごにょするとうまくいった。 実行したのは以下。

$ curl -kL https://raw.github.com/pypa/pip/master/contrib/get-pip.py | sudo python
$ sudo apt-get install python-dev
$ sudo pip install -U cython
$ sudo apt-get install libblas-dev liblapack-dev gfortran
$ sudo apt-get install g++
$ sudo pip install  numpy scipy
$ sudo apt-get install libhdf5-dev
$ sudo pip install chainer

ついでに使いそうなものを色々入れる

$ sudo pip install sklearn
$ sudo pip install pandas
$ sudo pip install xgboost

Emacsのインストール

とりあえずgit hubから自分の設定ファイルを落としてくる。

$ ssh-keygen

できた id_rsa.pub をgithubの自分のレポジトリに登録して、以下を実行。

$ git clone git@github.com:kskkwn/.emacs.d.git

依存しているものを色々入れる。

$ sudo apt-get install cmigemo pyflakes ruby
$ sudo pip install jedi epc autopep8

Emacsをインストールする。24.4用に設定ファイルを作っているので、面倒なことを避けるためにバージョンは固定。

$ sudo apt-get install build-essential
$ sudo apt-get build-dep emacs24
$ wget http://ftp.jaist.ac.jp/pub/GNU/emacs/emacs-24.4.tar.gz
$ tar -xf emacs-24.4.tar.gz
$ ./configure
$ make
$ sudo make install

ちゃんとできていればここまでで、いつものEmacsが起動するはず。 前に書いたやつからjedi入れたりtabbar入れたりhelm入れたりして、だいぶ変わっているし、またなんか書こう。

TensorFlow

基本的に公式サイト の言うとおりに実行すればいい。

GPU使いたいので、まずはcuDNNをインストールする。 cuDNNはNVIDIAのDeveloperサイトに登録しないとダウンロードできない。 ユーザー登録の際に色々聞かれるけど、「Tensor Flow使いたいねん。しゃす。」ぐらいのことを英語で書いて、数時間待ったらあっさり登録できた。これも最新版でないものをダウンロードする。

$ tar xvzf cudnn-6.5-linux-x64-v2.tgz
$ sudo cp cudnn-6.5-linux-x64-v2/cudnn.h /usr/local/cuda-7.0/include
$ sudo cp cudnn-6.5-linux-x64-v2/libcudnn* /usr/local/cuda-7.0/lib64

続いてTensorFlowのインストール。

$ sudo pip install --upgrade https://www.tensorflow.org/versions/master/get_started/os_setup.htmlhttps://storage.googleapis.com/tensorflow/linux/gpu/tensorflow-0.6.0-cp27-none-linux_x86_64.whl

やってみるとこんな感じのメッセージが表示された。

>>> import tensorflow
I tensorflow/stream_executor/dso_loader.cc:101] successfully opened CUDA library libcublas.so.7.0 locally
I tensorflow/stream_executor/dso_loader.cc:101] successfully opened CUDA library libcudnn.so.6.5 locally
I tensorflow/stream_executor/dso_loader.cc:101] successfully opened CUDA library libcufft.so.7.0 locally
I tensorflow/stream_executor/dso_loader.cc:101] successfully opened CUDA library libcuda.so locally
I tensorflow/stream_executor/dso_loader.cc:101] successfully opened CUDA library libcurand.so.7.0 locally

successfullyって言ってるし、大丈夫やろう。

今後の課題

  • pythonの仮想環境の構築 正直みんな何のためにやっているのかよくわからないので、今はやってないけど、そのうちやったほうがいいのかもしれない。
  • TeX環境の構築 家でTeX書くことないしなぁと思っていれていない。
  • C++環境の構築 ここ半年ぐらいC++書いてない。できれば今後も書きたくない。

参考

python_tips_備忘録

はじめに

コードを書いていると、「あ、これ前もググって実装したやつだ」ということがよくある。 しかもどうやったか全く覚えていない。悲しい。 あげく検索ワードすらわからないときが頻繁にある。悲しい。

これ以上、そのような悲しいことを起こさないために、たまに使う細かい処理について書いていく。 間違っている部分や黒魔術的な部分もあるかもしれないので注意。 随時追加するつもり。 番号は参考サイトを示している。サンプルコードについて非常に参考にした。

for文が正しく終了したときに、処理を実行する。(for...else) [1]

正常終了するとelseというのが、なんとなく慣れない。いちいちフラグとかたてなくてもいい。

for i in range(10):
    print i
else:
    print "hoge"
0
1
2
3
4
5
6
7
8
9
hoge
for i in range(10):
    print i
    break
else:
    print "hoge"
0

コマンドライン引数を処理する。(argparser, click) [2]、[3]

コマンドライン引数を処理するモジュールとして、argparserとclickがある。まずはargpaeser。

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-n', '--n_iter', default=1000, type=int, help="num_iteration")
parser.add_argument('-g', '--gpu', action='store_true')
args = parser.parse_args()

print args
print args.n_iter
print args.gpu
$ python temp.py -n 10 --gpu
Namespace(gpu=True, n_iter=10)
10
True

続いてclick。最近知ったので、使い方がまだ良くわかっていないが、こちらのほうがコンパクトに書けて便利な気がする。

import click

@click.command()
@click.argument("name")
@click.option("-n", "--num_iter", type=int, help="num iteration")
def main(name, num_iter):
    for i in range(num_iter):
        msg = "hello %s %d" % (name, i)
        print msg

if __name__ == '__main__':
    main()
$ python temp2.py world -n 10
hello world 0
hello world 1
hello world 2
hello world 3
hello world 4
hello world 5
hello world 6
hello world 7
hello world 8
hello world 9

ファイル名に時間を埋め込む 。(datetime)[4]

結果の出力など、うっかり上書きしないようにするときによく使う。

from datetime import datetime
filename = "results/" + datetime.now().strftime("%Y%m%d_%H%M%S") + ".csv"
print filename
results/20151130_225340.csv

ファイルを開く。(with open... as f)

普通にopenだけで開くと後でcloseしないといけない。忘れるので、withを使う。 自作のクラスもこういうものに対応させておくと後で便利かも。

with open("temp.csv") as f:
    for i in f:
        print i

プログラムの進行状況などをprintするときに改行せず上書きする。 (sys.stdout.write)

ループの中で、print i などと書くと、いちいち改行されて鬱陶しい。 以下のコードでいい感じに進行状況を表示できる。

import sys
import time
for i in range(100):
    sys.stdout.write("\riter=%d" % i)
    sys.stdout.flush()
    time.sleep(0.1)
iter=99

ファイル名で検索してリストを取得する。(glob)

paramsディレクトリ内に存在する全てのパラメータファイルを読んでいって、順番に実行するとか、resultsディレクトリ以下の全てのファイルの平均値をとるとか、やりたいときに使う。

import glob

print glob.glob("./*")
print glob.glob("./*.py")
['./tips.org', './temp.csv', './Untitled.ipynb', './temp1.py', './temp2.py', './temp10.py']
['./temp1.py', './temp2.py', './temp10.py']

文字中に含まれる数字でソート。[5]

ファイル名などに数字が付いている時、普通にsortメソッドを使うと、2の前に10が来たりしてしまう。はじめから%05dとかで書いておけば不要。

import re
files = glob.glob("./*.py")
temp = [(re.search("[0-9]+", x).group(), x) for x in files]
temp.sort(cmp = lambda x, y: cmp(int(x[0]), int(y[0])))
print  [x[1] for x in temp]
['./temp1.py', './temp2.py', './temp10.py']

別のディレクトリのファイルをインポートする。(sys.path.append)

昔作ったコードを再利用したいとき、コピーしてきてもいいけど、これでもOK。

import sys
sys.path.append("../kaggle/mnist/")
import nn
net = nn.Perceptron(10,2)

ある条件を満たす数列を生成する。(リスト内包表記、ジェネレータ)

リスト内包表記をごちゃごちゃすると色々できる。 あまり使いすぎるとわかりにくいので、ほどほどに使う。

x = [i**2 for i in range(10) if i%2==0]
print x
[0, 4, 16, 36, 64]

ジェネレータでもいける。yieldは遅延評価なので、1つ値を生成するコストが大きいかつ、いくつ必要かわからないときとかいいかもしれない。

def generator():
    counter = 0
    while(True):
        yield counter**2
        counter += 2
for i in generator():
    print i
    if i>=64:
        break
0
4
16
36
64

学習データからランダムなミニバッチを作る。( np.random.permutation(N) ) [6]、[7]

SGDとかやるときに使う。

    def train(self, inputs, labels, tests):
        N = len(inputs)
        inputs = np.array(inputs).astype(np.float32)
        labels = np.array(labels).astype(np.int32)
        tests = np.array(tests).astype(np.float32)

        for epoch in range(self.num_epoch):
            print "epoch: %d" % epoch
            perm = np.random.permutation(N)
            for i in range(0, N, self.batchsize):
                x_batch = cuda.to_gpu(inputs[perm[i:i + self.batchsize]])
                y_batch = cuda.to_gpu(labels[perm[i:i + self.batchsize]])

KFold Cross Validation(sklearn.cross_validation.KFold) [8]

一瞬自分でつくろうかと思ったけど、探してみたらやっぱりあったscikit_learn。 自分で書くコードは最小限にする方針からこれを使う。

kf = cross_validation.KFold(len(all_vec), n_folds=10, shuffle=True)
for train_index, test_index in kf:
    test_vec = all_vec[test_index]
    test_label = labels[test_index]
    train_vec = all_vec[train_index]
    train_label = labels[train_index]

特徴ベクトル(の一部)を正規化する。(sklearn.preprocessing.MinMaxScaler) [9]

sklearnに正規化の関数があるので、それを使う。1ofKの変数とかは正規化しなくてもいいと思うので、一部だけやる例。

min_max_scaler = sklearn.preprocessing.MinMaxScaler()
no_normalize_index = 8
min_max_scaler.fit(train_vec[:, no_normalize_index:])
test_vec[:, no_normalize_index:] = min_max_scaler.transform(test_vec[:, no_normalize_index:])
train_vec[:, no_normalize_index:] = min_max_scaler.transform(train_vec[:, no_normalize_index:])

bool型の掛け算順

以前、一度バグを産んだ原因コード。演算子の優先度は、(符号としての) - > * > (引き算としての)- なので、こういうことをすると、一見引き算の順序を変えただけなのに、結果がかわる。これからはおとなしくastypeでキャストしようと心に誓った。

import numpy as np
bool_list = np.array([True, False])

print [0,0] - bool_list * 0.5
print -bool_list * (0.5) + [0,0]

print - bool_list.astype(int) * 0.5 + [0,0]
[-0.5  0. ]
[ 0.   0.5]
[-0.5  0. ]

クラスタリングなどの結果をscatterでプロットする(plt.scatter)

だいたい特徴量的にx, yを管理しているので、特徴ベクトル行列のrow, colを入れ替えてプロットする。 colorの変数に適当に突っ込むとよしなにやってくれる。 ついでにPCAもする。

from sklearn.decomposition import PCA
pca = PCA(n_components=2)
pcaed_data = pca.fit_transform(data[:,1:]/255.0)
pcaed_data = zip(*pcaed_data)
plt.scatter(pcaed_data[0], pcaed_data[1], marker=".",c=data[:,0], linewidths=0, alpha=0.7)

csvファイルからデータを読み込む。(pandas)

普通にopenでファイルを開いてもいいけど、pandasでやると欠損値とかの扱いがかなり楽。

import pandas as pd
data = pd.read_csv("train.csv")

data = data[~data.Cabin.isnull()] #"Cabin"がNanでないものだけを抽出する。
print data
     PassengerId  Survived  Pclass  \
1              2         1       1
3              4         1       1
6              7         0       1
10            11         1       3
..           ...       ...     ...
879          880         1       1
887          888         1       1
889          890         1       1

                                                  Name     Sex   Age  SibSp  \
1    Cumings, Mrs. John Bradley (Florence Briggs Th...  female  38.0      1
3         Futrelle, Mrs. Jacques Heath (Lily May Peel)  female  35.0      1
6                              McCarthy, Mr. Timothy J    male  54.0      0
10                     Sandstrom, Miss. Marguerite Rut  female   4.0      1
..                                                 ...     ...   ...    ...
879      Potter, Mrs. Thomas Jr (Lily Alexenia Wilson)  female  56.0      0
887                       Graham, Miss. Margaret Edith  female  19.0      0
889                              Behr, Mr. Karl Howell    male  26.0      0

     Parch       Ticket      Fare        Cabin Embarked
1        0     PC 17599   71.2833          C85        C
3        0       113803   53.1000         C123        S
6        0        17463   51.8625          E46        S
10       1      PP 9549   16.7000           G6        S
..     ...          ...       ...          ...      ...
879      1        11767   83.1583          C50        C
887      0       112053   30.0000          B42        S
889      0       111369   30.0000         C148        C

[204 rows x 12 columns]

リストで重複した要素を削除する。(pandas)

例えばIDが重複していて、どちらかの要素がいらないとき、これを見つけて削除する。 オプションで上にあるものを残すか、下にあるものを残すかを選べる。

print data[~data.Cabin.duplicated()]
     PassengerId  Survived  Pclass  \
1              2         1       1
3              4         1       1
6              7         0       1
10            11         1       3
..           ...       ...     ...
879          880         1       1
887          888         1       1
889          890         1       1

                                                  Name     Sex   Age  SibSp  \
1    Cumings, Mrs. John Bradley (Florence Briggs Th...  female  38.0      1
3         Futrelle, Mrs. Jacques Heath (Lily May Peel)  female  35.0      1
6                              McCarthy, Mr. Timothy J    male  54.0      0
10                     Sandstrom, Miss. Marguerite Rut  female   4.0      1
..                                                 ...     ...   ...    ...
879      Potter, Mrs. Thomas Jr (Lily Alexenia Wilson)  female  56.0      0
887                       Graham, Miss. Margaret Edith  female  19.0      0
889                              Behr, Mr. Karl Howell    male  26.0      0

     Parch       Ticket      Fare        Cabin Embarked
1        0     PC 17599   71.2833          C85        C
3        0       113803   53.1000         C123        S
6        0        17463   51.8625          E46        S
10       1      PP 9549   16.7000           G6        S
..     ...          ...       ...          ...      ...
879      1        11767   83.1583          C50        C
887      0       112053   30.0000          B42        S
889      0       111369   30.0000         C148        C

[147 rows x 12 columns]

特定の文字を含むものを取り出す(pandas)

数字は簡単だけど、文字の一部に含まれているというのがよくわからなかった。これもpandasを使えば簡単。

print data[data.Cabin.str.contains("A")]
     PassengerId  Survived  Pclass  \
23            24         1       1
96            97         0       1
174          175         0       1
185          186         0       1
209          210         1       1
284          285         0       1
445          446         1       1
475          476         0       1
556          557         1       1
583          584         0       1
599          600         1       1
630          631         1       1
647          648         1       1
806          807         0       1
867          868         0       1

                                                  Name     Sex  Age  SibSp  \
23                        Sloper, Mr. William Thompson    male   28      0
96                           Goldschmidt, Mr. George B    male   71      0
174                            Smith, Mr. James Clinch    male   56      0
185                              Rood, Mr. Hugh Roscoe    male  NaN      0
209                                   Blank, Mr. Henry    male   40      0
284                         Smith, Mr. Richard William    male  NaN      0
445                          Dodge, Master. Washington    male    4      0
475                        Clifford, Mr. George Quincy    male  NaN      0
556  Duff Gordon, Lady. (Lucille Christiana Sutherl...  female   48      1
583                                Ross, Mr. John Hugo    male   36      0
599       Duff Gordon, Sir. Cosmo Edmund ("Mr Morgan")    male   49      1
630               Barkworth, Mr. Algernon Henry Wilson    male   80      0
647                Simonius-Blumer, Col. Oberst Alfons    male   56      0
806                             Andrews, Mr. Thomas Jr    male   39      0
867               Roebling, Mr. Washington Augustus II    male   31      0

     Parch    Ticket     Fare Cabin Embarked
23       0    113788  35.5000    A6        S
96       0  PC 17754  34.6542    A5        C
174      0     17764  30.6958    A7        C
185      0    113767  50.0000   A32        S
209      0    112277  31.0000   A31        C
284      0    113056  26.0000   A19        S
445      2     33638  81.8583   A34        S
475      0    110465  52.0000   A14        S
556      0     11755  39.6000   A16        C
583      0     13049  40.1250   A10        C
599      0  PC 17485  56.9292   A20        C
630      0     27042  30.0000   A23        S
647      0     13213  35.5000   A26        C
806      0    112050   0.0000   A36        S
867      0  PC 17590  50.4958   A24        S

おわりに

書いたら覚えるだろう、そう思っていた。

参考

  1. for文の終了時の処理(for...else) - 繰り返し - Python入門
  2. argparseを使ってみた - そこはかとなく書くよ。
  3. Python: コマンドラインパーサの Click が便利すぎた - CUBE SUGAR CONTAINER
  4. 現在時刻を取得・ファイル名用に整形 - 週末京都
  5. スーパーIT戦士になりたい!: Python: ファイル名の番号でソートする
  6. Chainerによる多層パーセプトロンの実装 - 人工知能に関する断創録
  7. numpy.random.permutation — NumPy v1.10 Manual
  8. sklearn.cross_validation.KFold — scikit-learn 0.17 documentation
  9. API Reference — scikit-learn 0.17 documentation

kaggle2回目 タイタニック号の生存者予測

概要

kaggle2回目。タイタニック号の乗員乗客の生存者予測。 データを眺めて思いついたことをやってみると、ベースラインは超えられたけれど、今ひとつうまくいかなかった。 今後、続きをやるか、別の課題をやるか、全然違うことをやるかは考え中。

はじめに

kaggle初挑戦ということで、前回手書き文字認識をやった。 課題自体やり尽くされている感じであまりおもしろくないので、今回はタイタニック乗客の生存予測に挑戦することにする。 手法は諸々の事情からニューラルネットワークにした。

どういう課題か

タイタニック号の沈没事故によって、多くの人が亡くなっている。 今回の課題は、この沈没事故によって死亡した人と生き残った人とを分類する問題である。 kaggleから与えられる情報は以下。

  • PassengerId: kaggleが振ったただの連番。役に立たない気がする。
  • PassengerClass: 乗客の等級。1から3まで。金持ちが生き残りそう。
  • Name: 名前。"Mr."とか"Don."とか色々情報がある。うまくやれば家族の情報も抽出できるかも。
  • Sex: 性別。映画では女性、子どもが先に救命ボートに乗ったらしい。関係ありそう。
  • Age: 年齢。同上。
  • Sibsp: 海外に住んでいる兄弟、配偶者の数。なんやこれ
  • Parch: 海外に住んでいる両親、子どもの数。なんやこれ
  • Ticket: チケットの番号。"113803"とか"A/5 21171"とか規則がよくわからない。
  • Fare: 運賃。金持ちが生き残りそう。
  • Cabin: 部屋番号。"C85"みたいな感じ。ぱっと見た感じほとんど欠損している。
  • Embarked: 乗船した港。Cherbourg、Queenstown、Southamptonの3種類。

ぱっとみると、関係ありそうなデータからそうでもないデータまで色々ある。 課題用のものというより、事後処理のときの名簿か何かなのかもしれない。

kaggleから提示されるベースラインは4つ。

  1. Assume All Perished [0.62679] 全員死んだとしたときの精度。
  2. Gender Based Model [0.76555] 女性が全員助かって、男性が全員死んだとしたときの精度。
  3. My First Random Forest [0.77512] Name、Ticket、Cabinのデータ以外を使ってランダムフォレスト。コードも公開。
  4. Gender, Price and Class Based Model [0.77990] 性別、チケットのクラス、チケットの値段の割合に基づいて予測(?)

とりあえず、これら全てのベースラインを超えることを目標とする。

どういうデータか

とりあえず、生データを眺めて以下のことに気づいた。

  • 欠損値が多い。: Ageは結構抜けていて、Cabinにいたっては欠損している方が多い。
  • Fareが0の人がいる。: 欠損しているのか、来賓的なやつなのか。どう扱っていいものか迷う。
  • 同じ名字、部屋番号の人がそれなりにいる。: 家族なら生死を共にしている可能性が高い気がする。

欠損値の補完の方法によって精度が結構変わりそう。 kaggleのコードでは文字列的な情報は使っていないので、欠損値の補完は、単に中央値を用いていた。 補完せずにデータを捨てると、いくつぐらいになるのかを計算してみるとデータ数が891→183になった。 もともと多くはないデータ数をかなり削ることになってしまうので、この案はなし。

とりあえず、色々ヒストグラムを書きながら考えることにする。 まず、データを生存か死亡かで分割する。

import pandas as pd
import pylab as plt

data = pd.read_csv("train.csv").replace("male",0).replace("female",1)
data = data.replace("C",0).replace("Q",1).replace("S",2)
split_data = []
for did_survive in [0,1]:
    split_data.append(data[data.Survived==did_survive])

以下のヒストグラムは全て、青色が死亡した人、緑が生き残った人。 まずはAge。

temp = [i["Age"].dropna() for i in split_data]
plt.hist(temp, histtype="barstacked", bins=16)

思ったよりも5歳以下の子どもが助かる確率が高い。優先的に逃げれたのだろうか。 一方で、5歳から10歳になると突然低くなる。

60歳超えるとだんだん厳しくなると思いきや、75歳以上の人が1人助かっている。 少ない訓練データにこういうのがあると、すごい高齢の人は助かるみたいな謎の学習しそうで怖い。 なんにせよ年齢はかなり使えそう。

f:id:ksknw:20151129151059p:plain

次にPassenger Class。

temp = [i["Pclass"].dropna() for i in split_data]
plt.hist(temp, histtype="barstacked", bins=3)

やはり一等客の生存率が高い。お金の力か。 f:id:ksknw:20151129151101p:plain

続いて性別。

temp = [i["Sex"].dropna() for i in split_data]
plt.hist(temp, histtype="barstacked", bins=3)

男(左)はだいたい死んでいて、女性(右)はかなり助かっている。 救命ボートの数が足りなかったらしいタイタニックにおいて、レディファーストとかやってたんだろうか。 この値もかなり使えそう。 f:id:ksknw:20151129151103p:plain

次に運賃。

temp = [i["Fare"].dropna() for i in split_data]
plt.hist(temp, histtype="barstacked", bins=10)

これもある程度出していたほうが、生存率高くなりそう。 f:id:ksknw:20151129151105p:plain

ここから、役に立つかよくわからないデータ。 一つ目、国外に住む兄弟または配偶者の数

temp = [i["SibSp"].dropna() for i in split_data]
plt.hist(temp, histtype="barstacked", bins=8)

グラフを見る限り、0人よりも1人のほうが生存率高そうなんだけど、この値が何と関係しているのかわからない。 国外に家族がいたほうが、収入が高くなって、高い運賃を払いやすくなるんだろうか。 f:id:ksknw:20151129151107p:plain

次、国外に住む親または子どもの数 一方で、こちらはパッとしない。

temp = [i["Parch"].dropna() for i in split_data]
plt.hist(temp, histtype="barstacked", bins=8)

f:id:ksknw:20151129151109p:plain

乗船した港。 これも意外と役に立ちそうな値。 地域による所得差とか、売られていた部屋の分布が偏っているとかそういうのだろうか。

temp = [i["Embarked"].dropna() for i in split_data]
plt.hist(temp, histtype="barstacked", bins=3)

f:id:ksknw:20151129151112p:plain

My First Neural Network

自分的ベースラインとして、とりあえず、ニューラルネットに突っ込んでみる。 使った特徴量はPassengerClass,Sex,Age,Sibsp,Parch,Fare,Embarked。 欠損値はなんとなく平均値で補完した。 PassengerClassとEmbarkedは1ofKにした。 結果60%ぐらいで、これは先の男女だけで判別したモデルよりも低い。雑魚すぎる。 (実は正規化のところでやらかしていたので普通にミスだった)

欠損値の補完とはなにか

欠損していた場合、モデルにとってニュートラルな信号を入力すべきと思うんだけど、 例えば年齢を欠損していた時、これを平均値もしくは中央値で補完すると、死亡と判定する率が上がりそうな気がする。

調べてみると、欠損値を補完する手法が色々あるらしいが、面倒なので欠損の場所によってモデルを分けることにした。 上記の特徴量に関して、Testデータで欠損している部分を調べると、Ageが欠損しているパターンとFareが欠損しているパターンがあった。 よって3つのモデルを学習させることにする。

  1. Age、Fareが欠損していないデータを学習データにして、同様のデータを予測。
  2. Fareが欠損していないデータを学習データにして、Ageが欠損しているデータを予測。
  3. Ageが欠損していないデータを学習データにして、Fareが欠損しているデータを予測。

3つのネットワークを学習させて結果をsubmitすると、 0.77990でベースラインに並んだ。

f:id:ksknw:20151129150618p:plain

ここまで適当にしていたパラメータ調整をちゃんとやればベースラインを超えられる気がしたので、 パラメータ色々いじってsubmitしまくると、 0.78469になり、無事ベースラインを超えることができた。

f:id:ksknw:20151129150625p:plain

試行回数をふやす

何度かパラメータ調整をしてsubmitを繰り返していると、スコアが結構ブレることに気づく。 また、先ほどのモデルの1.に使う学習データは結構少ないので、精度が本当にでるのかやや不安でもある。 そこで、学習を複数回行い、また、1.のモデルで判別していたデータを2.のモデルでも判別した。 それらの結果の平均を最終的な判別結果とする。

submitすると、0.77990で、うーんという感じ。 結果の分散が減ったぶん、たまたまうまくいくのもなくなっているという印象。

別の特徴量を考える。

このままモデルをいじっていてもジリ貧な気がしたので、別な特徴量を考えることにした。

  • PassengerId: なんとなく削っていたけれども、もしかすると何かあるかもしれないので足す。
  • 欠損値の有無: そもそも欠損しているのはなぜか。死んだからではないのか。
  • Nameの敬称: Mr.とかMs.とかだけだと思っていたら、こちらによるともっと色々あるらしいので、突っ込んでみることにした。

以上を加えてモデルを学習させると 0.76077でいまいちだった。ぐぬぬ

ちなみに現在のコードのだいたい。

#-*- coding: utf-8 -*-
import pandas as pd
import numpy as np
from nn import Perceptron
from chainer import cuda
import sklearn.preprocessing


def to_feature(data):

    emberked = []
    for i in data["Embarked"]:
        temp = np.zeros(3)
        if not np.isnan(i):
            temp.put(i, 1)
        emberked.append(temp)
    feature = emberked

    pclass = []
    for i in data["Pclass"]:
        temp = np.zeros(3)
        if not np.isnan(i):
            temp.put(i - 1, 1)
        pclass.append(temp)
    feature = np.c_[feature, pclass]

    feature = np.c_[feature, data["Age_na"]]
    feature = np.c_[feature, data["Cabin_na"]]

    for keyword in ["Master.", "Col.", "Mrs.",
                    "Ms.", "Miss.", "Rev.",
                    "Mr.",  "Dr.", "Major."]:

        feature = np.c_[feature, data.Name.str.contains(keyword).astype(int)]

    try:
        feature = np.c_[feature, data["Age"]]
    except:
        pass
    try:
        feature = np.c_[feature, data["Fare"]]
    except:
        pass
    try:
        feature = np.c_[feature, data["SibSp"]]
    except:
        pass
    try:
        feature = np.c_[feature, data["Parch"]]
    except:
        pass
    feature = np.c_[feature, data["Sex"]]
    feature = np.c_[feature, data["PassengerId"]]

    return feature


def read_data(data_filename):
    data = pd.read_csv(data_filename).replace("male", 0).replace("female", 1)
    data = data.replace("C", 0).replace("Q", 1).replace("S", 2)
    return data


def add_is_na_columns(data):
    for label in ["Age",  "Cabin"]:
        temp = pd.DataFrame(pd.isnull(data[label]).astype(int))
        temp.columns = [label + "_na"]
        data = pd.concat([data, temp], axis=1)
    return data

if __name__ == '__main__':
    test_data = add_is_na_columns(read_data("test.csv"))
    train_data = add_is_na_columns(read_data("train.csv"))

    label_categories = [['PassengerId', 'Name', 'Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare',  'Embarked', 'Cabin_na', 'Age_na'],
                        ['PassengerId', 'Name', 'Pclass', 'Sex', 'SibSp', 'Parch', 'Fare',  'Embarked', 'Cabin_na', 'Age_na']]

    results = []

    for i in range(1):
        print i
        for label in label_categories:
            temp_train_data = train_data.ix[:, train_data.columns.isin(label + ["Survived"])].dropna()
            temp_test_data = test_data.ix[:, test_data.columns.isin(label)].dropna()

            train_vec = to_feature(temp_train_data)
            test_vec = to_feature(temp_test_data)

            min_max_scaler = sklearn.preprocessing.MinMaxScaler()

            no_normalize_index = 8 + 9
            min_max_scaler.fit(train_vec[:, no_normalize_index:])

            test_vec[:, no_normalize_index:] = min_max_scaler.transform(test_vec[:, no_normalize_index:])
            train_vec[:, no_normalize_index:] = min_max_scaler.transform(train_vec[:, no_normalize_index:])

            nn = Perceptron(len(train_vec[0]), 2, 700, 45, 45)
            results.append([temp_test_data.PassengerId,
                            nn.train(train_vec,
                                     temp_train_data["Survived"],
                                     test_vec)])
            # from sklearn.ensemble import RandomForestClassifier
            # random_forest = RandomForestClassifier()
            # random_forest.fit(train_vec, temp_train_data["Survived"])
            # results.append([temp_test_data.PassengerId,
            #                 random_forest.predict(test_vec)])

    results.append([[1044], [1, 0]])  # Fareが欠損している一人は死んだことにする
    # results.append([[1044],  0])  # Fareが欠損している一人は死んだことにする

    print results
#    f = open("result_random_forest.csv", "w")
    f = open("result_nn_10.csv", "w")
    f.write("PassengerId,Survived\n")

    for result in results:
        for i in zip(*result):
            print i
            f.write("%d,%d\n" % (i[0], np.argmax(i[1])))
#            f.write("%d,%d\n" % (i[0], i[1]))
    f.close()

終わりに

なんか適当にパラメータいじっていたら0.78947になった。 たまたま感ある。 f:id:ksknw:20151129150629p:plain

色々やってみたけれども、なかなか難しい。 特徴量を増やしてみても、よくなるわけでもなく。 というかそもそも、どれぐらいいけるものなのかもわからない。 ランキングの一番上は1.000なので、萎える。

もうちょっとアイデイアがあるので、もしかすると続くかもしれないし、 飽きてきている感があるので、別の課題をやるかもしれない。

参考

kaggleで予測モデルを構築してみた (1) - kaggleって何? - About connecting the dots.

kaggle初挑戦 手書き文字認識

概要

kaggleコンテスト(チュートリアル)に初参加した。 手書きの文字認識を4層のNeural Networkでやってみると、ベンチマーク(ランダムフォレスト)は超えられた。 これからちょくちょくやっていきたい。

はじめに

研修で別の業務をやっているので、退屈している、プログラムが書きたい、機械学習したい。 ということで、データサイエンス的なコンテストであるkaggleに参加することにした。 kaggleは初挑戦かつ色々にわかなので、適当ですが、許してほしい。

kaggleとは

公式サイトはこちら

Wikipediaによると

Kaggleは企業や研究者がデータを投稿し、世界中の統計家やデータ分析家がその最適モデルを競い合う、予測モデリング及び分析手法関連プラットフォーム及びその運営会社である。

要するに、企業とかがデータを提供して、世界中の人でそれを解くコンテストである。 今やってるコンテストを見ると、例えば、降水量の予測とか売上の予想とか色々あって面白そう。 本番のコンテストは賞金がついてるものもあって、出題者が本当に課題を解決したいんだなぁという感じがある。

いきなり本番のコンテストに参加するのは色々ハードルが高いので、とりあえず、チュートリアル的なコンテストをやることにする。 チュートリアル的なコンテストは4つ。

とりあえず、一番簡単そうな手書き数字認識をやることにした。

手書き数字認識(mnist)

今回認識するデータはmnistという大規模手書き数字データ。 画像の大きさは28x28ピクセルで白黒画像である。 今回のコンテストでは42000枚のラベル付きの画像で学習を行い、28000枚のラベルなしの画像を認識させる。 このコンテストはチュートリアルなので、ベンチマークアルゴリズム(ランダムフォレスト)、コード、結果(正答率93.514%)が公開されていた。 とりあえずこれを超える結果を出すこと今回の目標とした。

データ

とりあえず、生データをみる。 たまに人間でもなかなか厳しそうなやつがある。もうちょっと丁寧に書いてほしい。 f:id:ksknw:20151108163057p:plain

まずは何も考えずに、画素データを単にベクトルと思って、PCAしてプロットしてみる。

from sklearn.decomposition import PCA
pca = PCA(n_components=2)
pcaed_data = pca.fit_transform(data[:,1:]/255.0)
pcaed_data = zip(*pcaed_data)

plt.scatter(pcaed_data[0], pcaed_data[1], marker=".", c=data[:,0], linewidths=0, alpha=0.7)
f=plt.gcf()
f.set_size_inches(20.0,20.0)
plt.savefig("pca.png")

f:id:ksknw:20151108163045p:plain この時点ですでに、いくつか分かれているものもあるけど、まあ正直よくわからない。 真面目にやるならもうちょっとデータを眺めているのもいいと思うけど、そこまでやる気はないので適当にささっと判別する。

アルゴリズム選択

教師あり学習、画像判別ということで、パッと思いつくアルゴリズムは以下。

  • ランダムフォレスト
  • SIFTやHOG、HLACなどを計算した後、SVMとか
  • Convolutional Neural Network
  • 多層パーセプトロン

最近CNNを使った際、かなり性能が良かったこともあり、これらの中で、直感的に性能が出そうなものはCNNと思う。 ただ、数字だけの認識なのでCNNまでいかなくても、多層パーセプトロンで何とかなるだろうと思った。 そこで、今回はまず多層パーセプトロンで実装してみて、だめなら、もうちょっと強そうなアルゴリズムを使うことにした。

mnistは有名なデータセットなので、少し調べるだけで、多層パーセプトロンで分類をやっている人はたくさんいた。 これらの記事をありがたく参考にさせてもらいつつ、実装を行う。

こちらのブログ で4層のパーセプトロンで認識を行っていたので、とりあえず、4層のネットワークを組むことにした。

実装

今後の勉強も兼ねて、chainerで実装を行う。 もし4層でイマイチな性能だったときに簡単に層数を増やせそうというのもある。

中間層の数は1000ずつ、活性化関数はReLUにして、Dropoutもすることにした。 chainer使うとこのへんの処理がとても簡単に書けるので良い。

学習方法について、最近こういうの を見て、確率的勾配法いまいちなのではと思っている。 今回は、詳細を知らないけど強そうなAdamを使うことにした。

エポック数は20に設定した。 本当は汎化誤差とか見ながら打ち切りするといいと思う。 4層程度ならgpuはいらないかなと思ってcpuで動くコードにした(あとで後悔した)。

import chainer
import chainer.functions as F
from chainer import optimizers
import numpy as np


class Perceptron(object):

    def __init__(self):
        self.batchsize = 100
        self.num_epoch = 20
        self.num_hiddens = 1000
        self.model = chainer.FunctionSet(
            l1=F.Linear(784, self.num_hiddens),
            l2=F.Linear(self.num_hiddens, self.num_hiddens),
            l3=F.Linear(self.num_hiddens, 10)
        )
        self.optimizer = optimizers.Adam()
        self.optimizer.setup(self.model)

    def forward(self, x_data, y_data=None, train=True):
        x = chainer.Variable(x_data)
        if train == True:
            t = chainer.Variable(y_data)
        h1 = F.dropout(F.relu(self.model.l1(x)), train=train)
        h2 = F.dropout(F.relu(self.model.l2(h1)), train=train)
        y = self.model.l3(h2)
        if train:
            loss = F.softmax_cross_entropy(y, t)
            return loss
        else:
            return y.data

    def train(self, inputs, labels, tests):
        N = len(inputs)
        inputs = np.array(inputs).astype(np.float32)
        labels = np.array(labels).astype(np.int32)
        tests = np.array(tests).astype(np.float32)

        for epoch in range(self.num_epoch):
            print "epoch: %d" % epoch

            perm = np.random.permutation(N)
            sum_loss = 0
            for i in range(0, N, self.batchsize):
                x_batch = np.asarray(inputs[perm[i:i + self.batchsize]])
                y_batch = np.asarray(labels[perm[i:i + self.batchsize]])

                self.optimizer.zero_grads()
                loss = self.forward(x_batch, y_batch)
                loss.backward()
                self.optimizer.update()
                sum_loss += float(loss.data) * len(y_batch)
            print "train mean loss: %f" % (sum_loss / N)
        np.asarray(tests)
        y = self.forward(tests, train=False)
        return y

これをipython notebookから読んで使う。

import pandas as pd
import numpy as np

data = pd.read_csv("train.csv")
data = data.as_matrix()
test_data = pd.read_csv("test.csv")

import nn
perceptron = nn.Perceptron()
test_results = perceptron.train(data[:,1:]/255.0, data[:,0], test_data[:]/255.0)
epoch: 0
train mean loss: 0.314591
epoch: 1
train mean loss: 0.149317
epoch: 2
train mean loss: 0.115880
epoch: 3
train mean loss: 0.101776
epoch: 4
train mean loss: 0.082577
epoch: 5
train mean loss: 0.082045
epoch: 6
train mean loss: 0.074332
epoch: 7
train mean loss: 0.064091
epoch: 8
train mean loss: 0.059199
epoch: 9
train mean loss: 0.061192
epoch: 10
train mean loss: 0.053155
epoch: 11
train mean loss: 0.054124
epoch: 12
train mean loss: 0.050729
epoch: 13
train mean loss: 0.050139
epoch: 14
train mean loss: 0.048307
epoch: 15
train mean loss: 0.045948
epoch: 16
train mean loss: 0.044958
epoch: 17
train mean loss: 0.046211
epoch: 18
train mean loss: 0.040627
epoch: 19
train mean loss: 0.041885

ちゃんと損失が下がっている。 あとは提出用のフォーマットを作成する。

results =  np.argmax(test_results, axis = 1)
import csv
f = open("results.csv", "w")
f.write(('"ImageId","Label"\n'))
for i, result in enumerate(results):
    f.write(('%d,"%d"\n'%(i+1,result)))
f.close()

結果

結果をkaggleのサイトに送信すると、正答率とランキングを教えてくれる。 今回の正答率は98%で、順位は205/830位でした。 f:id:ksknw:20151108162603p:plain

間違ったデータを確認してアルゴリズムを改良したいところだけど、テストデータはラベルがないのでどれ間違ったのかわからない。 ちゃんとやるなら、学習データ分割してvalidationしないとだめそう。

今後

パラメータもデータの正規化も、アルゴリズムもかなり適当だったので、色々もうちょっとちゃんとすれば性能上がると思う。 とはいえ、mnist自体はやり尽くされている感じで、あまりおもしろい感じではないので、次はタイタニックのやつをやってみようと思う。

参考

Kaggle: The Home of Data Science

MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges

Deniz Yuret's Homepage: Alec Radford's animations for optimization algorithms

Chainerによる多層パーセプトロンの実装 - 人工知能に関する断創録

iPython notebookを使おうかと思ったけどやめた話(最近むちゃくちゃ使ってるのでこれは嘘)

概要

iPython notebookというPythonコードと数式、グラフなどを統合的に扱えるものを知ったので、 使ってみたけど、Emacsからは微妙だったので使うのやめようかなと思ってる。 あくまでも小一時時間さわってみた感想なので、もう少し使っていれば別の感想になると思う。 タスクごとにエディタを変えることに抵抗が少ない人にはおすすめ。

iPython

iPythonはPythonの対話型インタプリタの拡張。 普通のPythonの対話型インタプリタbashとするなら、iPythonはzshみたいな感じ。 変数名、オブジェクトのメソッド名など、タブで色々補完してくれるので、便利。 他にもシェルのコマンドが使えたり、実行時間が簡単に測れたり色々ある。 こちら が詳しい。

(え、対話型インタプリタって使うの?って正直思ってるんですが、みんな使ってるんですか?)

iPython notebook

iPython notebookではPythonMarkdown、普通のテキストを書くことができる。 これらはそれぞれ、セルごとにどれを使って記述するか指定することができる。 詳しくはこちら を参考。

実際に使ってみたときの画像を以下に示す。 f:id:ksknw:20151010195152p:plain

Markdownで数式を書いて、その下にPythonのコード、さらに結果のグラフを一つのファイルの中で完結させることができている。

また、画面上にツールバーがあり、記述したPythonのコードをその場で実行し、結果をすぐ下に表示させることができる。

今まで、パラメータを変更した際の結果を管理する方法として、ファイル名やフォルダ名にパラメータを記述するという手段をとっていた。 iPython notebookを使うことで、Markdownでパラメータ(値だけでなく式でもいい)を記述し、グラフを管理することができるかもしれない。

Emacsから使う

確かに一つのファイルの中で、Python、その出力結果、Markdownを管理できるのは魅力的な機能である。 一方で、その機能を使うために、別のエディタ(しかもブラウザ上で動作する)を使うというのは、 Emacs教徒である僕には我慢ならない。 ということで、EmacsからiPython notebookを使う。

EmacsにおけるiPython notebookはein(Emacs iPython notebook)から利用できる。 こちら を参考にした。

実際に使ってみた画像は以下。 f:id:ksknw:20151010194432p:plain

Markdownの数式が表示させることができなかった。

結局使うのをやめた理由

以下の理由から、真面目に使うのはもうちょっと後かなぁと思っている。 とはいえ、数式とプログラムと実行結果を一つにまとめられるというのはとても魅力的なので、 そのうち、もう一度トライしてみようと思う。

また、自分の結果をまとめるというよりは、教育や知識の共有に向いているような印象を持った。 理論的な部分の説明から実際のコードの実装、結果を見ることができて、なおかつ、自分でコードを色々いじって結果をすぐに見れるというのは、理解を助けてくれると思う。

Emacsから扱うためのモード"ein"が使いにくい

einはまだ開発途中なのか、動作が不安定な場面が多い気がした。 また、普段Emacspythonを使うときはautopep8やwhite spaceでコードの体裁を整えている。 これらの機能が正常に動いてくれなかったので、コードがぐちゃぐちゃになってしまった。 演算子の両側のスペースとか手動で打つ気にはまったくならない。 設定をいじればどうにかなるのかもしれない。

そもそもMarkdown書けなかった

実はMarkdown書けなかった。 できればorg-modeの中にコードと実行結果を埋めこみたい。

追記(2016/4/23)

Markdown書けるようになったので最近むっちゃ使ってる。 einは今のところ使っていない。

参考

IPythonの使い方

はじめるiPython notebook

EmacsでiPython notebookを使う(EIN / Emacs iPython notebook)

Ubuntu14.04 のクリーンインストールからchainer-goghを実行して、芸術的な猫を描くまで

概要

Ubuntu14.04をクリーンインストールするところから、CUDAやChainerをインストールした。 chainer-goghが正しく動作することを確認し、芸術的な猫を描いた。

以下のものをインストールする。

  • Ubuntu 14.04
  • cuda-7.5
  • chainer(-cuda-deps)

(2015/12/26 追記)

あまりに適当だったので環境の構築を書き直した。 Ubuntu14.04での自分的環境構築をちゃんと書く - やったことの説明


CUDAのインストール

インストールに関して、主にこちらの記事を参考にした。 クリーンインストールした Ubuntu14.04 に環境構築 (2)

とりあえず、今後のことを考えてディレクトリを英語にする。

LANG=C xdg-user-dirs-gtk-update

色々インストールする。

$ sudo apt-get  update
$ sudo apt-get install freeglut3-dev build-essential libx11-dev libxmu-dev  libxi-dev libgl1-mesa-glx libglu1-mesa libglu1-mesa-dev linux-headers-`uname -r`
$ sudo lshw -short -class display
$ sudo apt-get remove --purge nvidia*
$ mkdir cuda
$ cd cuda/

NVIDIAのサイトからCUDAの最新版とグラボのドライバをダウンロードして色々やる。

$ chmod +x cuda_7.0.28_linux.run
$ chmod +x NVIDIA-Linux-x86_64-352.41.run
$ sudo nano /etc/modprobe.d/blacklist-nouveau.conf
$ echo options nouveau modeset=0 | sudo tee -a /etc/modprobe.d/nouveau-kms.conf
$ sudo update-initramfs -u

再起動して、Ctrl+Alt+F1を押してCUIに入る。 そのままではグラボのドライバをインストールできないので、以下をする。

$ sudo service lightdm stop

グラボのドライバをインストールする。

$ chmod +x NVIDIA-Linux-x86_64-352.41.run

そのままCUDAをインストールする。

$ chmod +x cuda_7.0.28_linux.run

.bashrcなり.zshrcなりに環境変数を書いて、CUDAのインストールは終わり。

chainerのインストール

ささっとchainerをインストールする。Caffeと違って非常に簡単にインストールできた。

$ sudo apt-get install python-pip
$ sudo apt-get install python-dev
$ sudo pip install chainer
$ sudo visudo -> secure pathをコメントアウト
$ sudo pip install chainer-cuda-deps

ここまでで、すでにchainerでプログラムが動く。すごい。 chainerのテストのために、こちら をダウンロードする。解説はこちら。 中身は一般物体認識で学習させたCNNを使って、絵の画風を変換するというもの。 詳細は解説記事の方を見てほしい。すごい。

$ sudo apt-get install git
$ git clone https://github.com/mattya/chainer-gogh.git
$ cd chainer-gogh

vgg model fileもダウンロードしておく。

実行

手元のマシン(GTX-750Ti)では、メモリが足らず、256x256の画像を生成することができなかったので、画像サイズは128x128にした。 入力画像として可愛い猫の画像を使う。 f:id:ksknw:20150920205050p:plain かわいい。

スタイル画像はもともとのものを使わせてもらう。

実行結果

f:id:ksknw:20150920205043p:plain f:id:ksknw:20150920205103p:plain f:id:ksknw:20150920205113p:plain f:id:ksknw:20150920205116p:plain

左がスタイル画像、右が生成画像。 簡単に芸術的な猫を描くことが出来ました。 かわいいね!

参考

git + latexdiff でTeXの原稿を添削してもらいやすくする。

(latexdiffのバージョンが古かったりで微妙だったので書き直した 2018年3月)

はじめに

論文や報告書で,TeXの文章を人に添削してもらう機会は多い. それなりに長い文章を添削してもらうとき,前回の添削からどこをどう訂正したのかわからないと, 何度も読まないといけないだろうし,申し訳ない.

自分が訂正した部分をわかりやすくするために,

などがあると思うが,どれも手動でやらなければならないので,めんどくさい. 画期的なソリューションが必要である.

探してみるとlatexdiffというのがあり,gitから呼ぶと便利らしいので,使ってみる.

latexdiff

latexdiffは2つのTeXファイルの差分をTeXファイルで出力してくれるアプリケーション.

サイトはこちら . 普通にapt-getからインストールできた.

$ sudo apt-get install latexdiff
$ latexdiff --version
Latexmk, John Collins, 1 January 2015. Version 4.41

とりあえず,以下のTeXファイルを適当に作る.

before.tex

\documentclass{jarticle}
\begin{document}
消したところは赤で表示される.

\end{document}

after.tex

\documentclass{jarticle}
\begin{document}
追加したところは青で表示される.

\end{document}

あとは以下のコマンドを叩く.

$ latexdiff -e utf8 -t CFONT before.tex after.tex > diff.tex

文字コードnkf –guess とかで調べる. ちなみに指定しないと,日本語が文字化けしてコンパイルできなかった.

コンパイルするとこんな感じのPDFファイルができる.便利.

f:id:ksknw:20180314221745p:plain

latexdiff-git

このままでも使えなくはないけど,変更前のファイルを残しておかないといけない. めんどくさいし,なによりださい. もともとgit使って管理しているなら,前のコミットをlatexdiffの入力として使える.

$ latexdiff-git -r -t CFONT -e utf8 test.tex

とすれば,差分ファイルが出力される.

直前以外のコミットと現在のファイルの差分を出力したいときは

$ latexdiff-git -r HEAD~1 -e utf8 -t CFONT main.tex

などでリビジョンを指定することもできる.

複数ファイルにまたがるとき

ここまでのやり方だと,inputのようなコマンドを使用しているときに,うまく差分ファイルを生成することができない. こちらによると,flattenというオプションを指定すればいいらしい.

以下のようなファイルで実際にやってみると,ちゃんとdiffファイルが生成された.

$ latexdiff -e utf8 --flatten main.tex main2.tex > diff.tex
\documentclass{jarticle}
\begin{document}
\input{before.tex}

\end{document}

参考にしたサイト