アンサンブル学習 アルゴリズム入門 〜〜決定木その1〜〜
アンサンブル学習とは
アンサンブル学習とは、複数の機械学習モデルを組み合わせて使用するタイプの機械学習アルゴリズムのことです。
Kaggleのようなデータ分析コンペティションでもよく使われているLightGBMなどもアンサンブル学習アルゴリズムの一つです。
この記事のシリーズは2019年6月に出版された「作ってわかる!アンサンブル学習 アルゴリズム入門」の内容に沿っています。
- 作者: 坂本俊之
- 出版社/メーカー: シーアンドアール研究所
- 発売日: 2019/05/28
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
決定木
決定木アルゴリズムは、条件による分岐を根からたどることで、最も条件に合致する葉を検索するというアルゴリズムで、IF-THENルーチンと同じです。
機械学習における決定木は、学習データをもとにして、説明変数からなる条件式をノードとすることで説明変数に対するモデルを含む葉を検索することになります。
枝と葉
決定木アルゴリズムで作成されるモデルは、ある一つのノードが根となり、そのノードから伸びる複数の枝が、次のノードまたは葉を示す構造となっています。
決定木を使用した機械学習では、各ノードには、からなる条件式が入ることになります。そうすると、ある入力に対して決定木をたどっていけば、いずれかの葉にたどり着くことになります。
決定木によるデータの分割
言い換えるとデータセットに含まれるデータは、全ていずれかの葉に属することになり、このことは決定木はデータを葉の数に分割するということを表しています。そしてデータを葉の数に分割し、それぞれの葉でクラス分類や回帰のモデルを適用することで全体としてより望ましいモデルを構築することが決定木アルゴリズムの目的です。
決定木にはそれが単体が機械学習モデルというわけではなく、基本的にはデータの分割に関するアルゴリズムであり、葉となる機械学習モデルと組み合わせて利用されます。
葉の部分で利用できるモデルはデータの部分集合に対して学習と実行行えるモデルであれば何でも良いのですが決定木の葉の部分にあまり複雑なモデルを使用することは一般的には行われません。本記事ではZeroRule(クラス分類であれば「学習時に最も多かったクラス」、回帰であれば「学習した正解データの平均値」)と線形回帰モデルという二つのアルゴリズムをモデルとして使用します 。
木の分割
決定木による分割それ自体も機械学習アルゴリズムの一種であり学習データからモデルを作成します。決定木の学習ではまず深さの浅い決定木を作成しその葉の部分に新しいノードを追加していくことで順次より深さの深い決定木を作成しますこのような葉を新しいノードとしてデータを分割していくことを木の分割と呼びます 。
上記の決定木ではそれぞれのノードでは一度に説明変数内の一つの値しか見ていません。つまりノード内にある条件式は横軸または縦軸のどちらかの値によって処理を振り分けるので図中の全ての分割線は水平または垂直になっています。そのように単純な条件式のみからなるのノードでも、気の深さを深くしてやることで複雑なデータでも分割を可能とするのが決定木アルゴリズムの特徴です。
Metics関数
決定木アルゴリズムでは説明変数からなる条件式で最もよく目的変数を分割できる条件をもとにノードを作成します。ここで「最もよく」目的変数を分割するために目的変数の分割の良さを数値で比較できるスコアが必要になりますが、そのために使用するのが損失関数またはMetrics関数と呼ばれる関数になります(本記事では以後Metics関数と呼びます)。この関数でノード内の条件式を評価し、最もよく目的変数を分割できるように学習を行います。
よく用いられるMetics関数にはいくつか種類があります。
標準偏差
回帰に使用されるMetics関数として標準偏差の合計が挙げられます。これは目的変数を分割する際に、分割後のグループごとの標準偏差を取り、その合計が少ないほどよくデータ分割できているとするものです。
ここで作成するMetics関数は「entropy.py」という名前のファイルに作成して、後で紹介する決定木アルゴリズムでインポートして使用します。
import numpy as np def deviation(y): return y.std()
Gini impurity (ジニ不純物)
クラス分類では目的変数はクラスを表す番号であり、値そのものの比較には意味がないので与えられた目的変数のリスト内がどのくらい単一のクラスからなっているかを評価することになります。
そのために使用されるのがGini impurityです。経済学で使われるジニ係数 (Gini coefficient)とは異なるので注意してください。
をデータに含まれるクラスの個数、をデータがクラスである確率とするとGini impurityは以下の式で表されます。
上記の式をpythonで実装すると下記のようになります。
def gini(y): # compute gini impurity m = y.sum(axis=0) size = y.shape[0] e = (m / size) ** 2 return 1.0 - np.sum(e)
Information gain
クラス分類で使用される metrics 関数としてInformation Gainもあります。
これは情報理論で使用するエントロピーに基づく関数で、分割後の目的変数に含まれる情報量が少なくなるように分割点を求めます。
Information Gainの正しい定義は、元の集合のエントロピーから分割後の集合のエントロピーの重み付き合計を引いた値でその値が大きくなるような分割を目指します。
決定木の場合親ノードから与えられたデータのエントロピーから子ノードに渡すデータのエントロピーの合計を引いたものがノード内の条件式が持っている情報量ということになるので、Information gainはノード内の条件式が持つ情報量を最大化するようにデータを分割するMetics関数とも言えます。
ここでは決定木のノードにノートの一つの分割を考えるので単純に分割後のエントロピーの合計が小さくなるように目的変数を分割します 。
エントロピーは乱雑度とも考えられるので、乱雑度を小さくするように分割する、と考えてもいいかもしれません。
情報量 - Wikipedia
情報理論の基礎~情報量の定義から相対エントロピー、相互情報量まで~ | Logics of Blue
をデータに含まれるクラスの個数、をデータがクラスである確率とするとエントロピーは以下の式で表されます。
上記の式をpythonで実装すると下記のようになります。
def infgain(y): m - y.sum(axis=0) size = y.shape[0] e = [p * np.log2(p / size) / size for p in m if p != 0.0] return -np.sum(e)
scipyでも計算できます。
【python】pythonで情報エントロピーの計算 - 静かなる名辞
DecisionStump
DecisionStumpとはアルゴリズムの評価に使用される最もシンプルな構造をした決定木で、深さが1の決定木のことです。
DecisionStumpにはノードが一つと葉が二つの要素しかありませんが、データの分割と葉によるモデルの結合という決定木アルゴリズムの基礎が含まれており、データの分割アルゴリズムなども評価に用いられます。
またDecisionStumpはアンサンブル学習のアルゴリズムを評価する際のベースにも使用されます。
アルゴリズムを評価する際のベースとしては複雑なモデルを使用してもベースのモデルの性能として良い結果が出ているのか、アンサンブル学習アルゴリズムの性能としていい結果が出ているのかわからないため、最もシンプルな構造をした決定木であるDecisionStumpが使用されます。
木分割の実装
実際にDecisionStumpの実装を行っていきます。
dstump.pyというファイルに下記クラスを実装します。
class DecisionStump: def __init__(self, metric=entropy.gini, leaf=ZeroRule): self.metric = metric # 使用するMetics関数 self.leaf = leaf # 葉のモデル self.left = None # 左の葉のモデルインスタンス self.right = None # 右の葉のモデルインスタンス self.feat_index = 0 # 分割に使用する目的変数の位置を表す self.feat_val = np.nan # 分割に使用する目的変数の値を表す self.score = np.nan # 分割の際のMetics関数の値を表す(今後使用)
左右のインデックスを取得する
上記のDecisionStumpクラス内に決定木アルゴリズムに必要となる機能を実装します。
まず作成するのは、目的変数から取得した値の配列を特定の値より小さなものとそれ以外に分ける関数で「make_split」という名前の関数を作成します。
この関数は1次元の数値からなる配列を与えられた値で分割した際のインデックスを返します 。
def make_split(self, feat, val): # featをval以下と以上で分割するインデックスを返す left, right = [], [] for i, v in enumerate(feat): if v < val: left.append(i) else: right.append(i) return left, right
分割した後のスコアを計算する
次に作成するのは記事上部で作成したMetics関数を使用して、分割した後のスコアを計算する「make_loss」という名前の関数です。
この関数では「self.metric」に代入されているMetics関数でそのスコアの重み付き合計を求めます。
関数の引数は左右に分割した目的変数となります。
def make_loss(self, y1, y2): y1_size = y1.shape[0] y2_size = y2.shape[0] if y1_size == 0 or y2_size == 0: return np.inf totol = y1_size + y2_size m1 = self.metric(y1) * (y1_size / totol) m2 = self.metric(y2) * (y2_size / totol) return m1 + m2
データを左右に分割する
次に作成するのは説明変数と目的変数からデータを左右の枝に振り分ける「split_tree」関数です。
この関数では説明変数内の全ての次元に対して、その中の値でデータを分割した際のスコアを計算します。
そして、そのスコアが最も小さくなる説明変数内の次元の位置と分割値を「self.feat_index」と「self.feat_val」変数に保存しておきます 。
最後に、分割した後の左右の葉に振り分けられるデータのインデックスを返します。
def split_tree(self,x, y): # データを分割して左右の枝に属するインデックスを返す。 self.feat_index = 0 self.feat_val = np.inf score = np.inf left, right = list(range(x.shape[0])), [] # 説明変数内のすべての次元に対して for i in range(x.shape[1]): feat = x[:, i] for val in feat: # 最もよく分割する値を探す l, r = self.make_split(feat, val) loss = self.make_loss(y[l], y[r]) if score > loss: score = loss left = l right = r self.feat_index = i self.feat_val = val self.score = score return left, right
学習部分の実装
DecisionStumpは深さが1の決定木なので、学習のためのコードはデータを左右の端に振り分けてそれぞれの葉の学習を行うだけです。
self.leaf変数に葉となるモデルが入っているので、それを呼び出してインスタンス化しておき、 split_tree()で左右の端に振り分けたデータを学習させます。
また、必ずしも左右に値が振り分けられるとは限らず、どちらか一方の端にのみデータが集中する可能性もあるので if 文でデータの長さをチェックしてから学習を行います。
def fit(self, x, y): # 左右の葉のモデルをインスタンス化 self.left = self.leaf() self.right = self.leaf() # データを左右の葉に振り分ける left, right = self.split_tree(x, y) # 左右の葉を学習 if len(left) > 0: self.left.fit(x[left], y[left]) if len(right) > 0: self.right.fit(x[right], y[right]) return self
推論部分の実装
DecisionStumpの推論は、左右の端へデータを振り分けて、それぞれの葉のモデルの実行結果から最終的な出力を作成します。
データを左右の端に振り分けるのはmake_split()を使用し、左右ともにデータがあれば左右の葉の実行結果をそのインデックスに代入して最終的な結果とします。
また左右のどちらかのみにデータがある場合は左右の葉のいずれかの実行結果が最終的な結果となります 。
def predict(self, x): feat = x[:, self.feat_index] val = self.feat_val l, r = self.make_split(feat, val) z = None if len(l) > 0 and len(r) > 0: left = self.left.predict(x[l]) right = self.right.predict(x[r]) z = np.zeros((x.shape[0], left.shape[1])) z[l] = left z[r] = right elif len(l) > 0: z = self.left.predict(x) elif len(r) > 0: z = self.right.predict(x) return z
DecisionStumpの評価
以上でDecisionStumpの実装ができました。
検証用データに対して学習して評価するコードは下記にあるので参考にしてみてください。
次の記事では今作ったDecisionStumpクラスを利用して深さが1より大きい決定木アルゴリズムを実装していきたいと思います。
Kaggleの網膜コンペで銅メダルをとったので振り返る
Kaggle で開催されたAPTOS 2019 Blindness Detection(網膜コンペ)にソロで参加したのでその振り返りです。
結果は174th/2943 (TOP6%)で銅メダルでした。このコンペで2枚目の銅メダルを獲得し、Kaggle Expertになることができました。
コンペ概要
網膜の画像から糖尿病網膜症の重症度を予測します。
ラベルは0〜4の5段階で数字が大きいほど重症を表しています。
糖尿病網膜症については以下のスライドが分かりやすいです。
評価指標はquadratic weighted kappaです。
またkernel only コンペだったので学習は手元でOKですが推論はkernel上で完結させる必要があり、以下にkernelの制限時間内に推論できるかもこのコンペのポイントでした。
自分の手法
Validation Strategy
2015年にも同様のコンペ (APTOS2015)が行われており、そのデータと現コンペ (APTOS2019)のデータを単純に結合して学習データとしました。discussionでは2015のデータでpretrainして2019のデータでfine-tuningする手法が出ていましたが、めんどくさかったので単純に結合して使いました。
検証データは訓練データと同じラベルの分布になるように分割。最初は5-foldでCVしていましたが、時間がかかるので中盤からはsigle fold で学習させました。
あとの祭りですが、external dataとしてAPTOS2015だけでなくIDRIDやMessidorのような他のデータもあったようでそちらも使うべきだったなあと反省…。
preprosessing
前回コンペの優勝者が使用していた前処理がkernelで公開されていたのでそちらのコードを丸コピして使わせてもらいました。
処理内容は黒の背景部分を自動でcropし、ガウシアンフィルタをかけるというものです。
ガウシアンフィルタの強さを3段階くらい試してその中で最もlocal CVのスコアが高いものを使用しました。
画像サイズは256, 340, 512でいろいろ試したが画像サイズを増やすとoverfitする傾向があったので256 or 340の小さめのサイズでやっていました。
Augmentation
以下を使用しました。
- Flip
- Rotate
- ShiftScaleRotate
- RandomBrightness
- RandomContrast
- RandomGamma
後で上位の解法を見ると相当heavyな拡張をしていたのでもっといろいろ試しても良かったかも。
Models
CNNのモデルはResnet34, Resnet50, SE-ResNext50, Efficient-Netなどいろいろ試しました。
Efficient-Netは最近GoogleがNeural Architecture Searchで発見したモデルの構造で既存モデルより少ないパラメータで精度が良いようです。
実際、上位の解法の多くがEfficient-Netを使用していました。
Efficient-Netはパラメータの数によってB0~B7の7段階あり、自分は学習済みモデルが公開されていたB0~B5まで試してローカルのスコアが最も高かったB3を使用しました。
Training
discussionではロスをMSEとして回帰問題として解いているものが多かったですが、自分は最初あえてCrossEntropyLossを使った分類問題として解いていました。最終的には回帰として解く方法も試しました。
optimizerはAdam+CosineAnnealingを使用。
また、discussionで同じ画像でも異なるラベルがついていることがわかったりとラベル付けが適当な感じあったのでミスラベリング&汎化性能向上のためにLabelSmoothingを試してみました。
Label Smoothing: An ingredient of higher model accuracy
実際LabelSmoothingによってスコアが0.05ほど上がりました。
inference
最終的には以下の4つのモデルのアンサンブルした結果で提出しました。
- Classificationとして解いたEfficient-NetB3で画像サイズを3種類で学習したモデル(size256, 340, 512)
- Regressionとして解いたEfficient-NetB3で画像サイズ320(しきい値はローカルスコアで最適化)
自分のkernelの単純なコードだと制限時間で4回推論するのが限界だったんですが他の解法を見るとさらに多くのアンサンブルをしているものもあったのでここは工夫してもっと短時間で大量に推論してアンサンブルできるようにするべきだったかもしれません。
結果
Public LB: 0.808829, Private LB 0.918672で無事銅メダル圏内に残ることができました。
QWKで0.9超えるってどういう問題なんだ…
上位ソリューションのまとめ
1st place solution
1st place solution summary | Kaggle
- 2015+2019のデータを学習データとして使用。Public LBをvalidationとして信頼した。
- 特別な前処理なく単純なresizeのみ ← !!
- 以下8つのアンサンブル
- 2 x inception_resnet_v2, input size 512
- 2 x inception_v4, input size 512
- 2 x seresnext50, input size 512
- 2 x seresnext101, input size 384
- nn.SmoothL1Loss()
- 最後のプーリング層をGeneralized mean poolingに変更
- pseudo labeing
2nd place solution
4th place solution (2nd after LB cleaning) | Kaggle
- APTOS2015でpretrained
- 以下3つのモデル
- Efficient-Net B3 image size 300
- Efficient-Net B4 image size 460
- Efficient-Net B5 image size 456
- 黒背景をクロップしてサイズを変えるだけの単純な前処理
- a lot of augmentation
- pseudo labeling
- Test Time Augmentation (TTA)
4th place solution
- 黒背景除去&学習データの画像サイズごと3種類に分け、それぞれに対応した前処理
- Effcient-Net B2~B7
- heavy augmentation
- APTOS2015で事前学習、2019で5-fold
- 5 model x 8 TTA
- 前処理を1回しかしないことで推論時間を短縮
5th place solution
7th place solution
10th place solution w/ code [Catalyst, Albumentations] | Kaggle
コードも公開されていました。
GitHub - BloodAxe/Kaggle-2019-Blindness-Detection
- APTOS2015でpretrained、2019でfine-tuning. 外部データとしてidrid&Messidor使用
- SeResNext50, SeResNext101, InceptionV4
- 特殊な回帰として解く
- 最終層でsigmoidを通した4要素のベクトルを予想し合計をとる
- MSE
- Loss function
- focal kappa > soft CE(label smoothing) > plain CE
- Regression: WingLoss
- Ordinal Regression: Huber loss (aka smooth L1 loss) -> Cauchy Loss (?)
- mixtured precision FP16 (Apex)
- pseudo labeling
- RAdam
- 4 model ensumble
反省
上位のソリューションを見るとほとんどがpseudo labeling を使用していました。このコンペはtrainとtestの分布がかなり違ったのでpseudo labelingでtestの情報を与えることが重要だったのだと思います。
まだ自分はコンペでpseudo labelingをしたことがないので次回はしっかり習得したい使ってみたい。
また、Grad-CAMのような手法をつかってデータセットや前処理の手法を分析をしているカーネルがあったりしてとても勉強になりました。今後は自分もそういう分析ができるようになりたいですね。
何はともあれKaggle Expertに上がれてよかったです。
ymicky | Kaggle
次はNIPSコンペ、鉄鋼コンペでやろうと思います。
参考
28thの方も手法を公開してくれています。
speakerdeck.com
docker imageの保存先を変更する
最近自分はdeep learningやデータサイエンスの環境構築も基本的にdockerで行うようにしています。
dockerfileさえあれば他のVMでも簡単に同じような環境が作れるので重宝しています。
しかし、docker imageって何気にサイズがでかいのでストレージの容量がすくない端末やVMだとimageをたくさん作ったときにすぐ容量がなくって辛いです…
そこで今回はdocker imageの保存先を変えてみたいと思います。
docker imageの情報はデフォルトの設定を変えていなければ基本的に /var/lib/docker/
配下に保存されます。
この保存先のパスをマウントされたディスクなどに変更してあげればいいわけです。
docker の環境ファイルの場所を確認
docker info
で環境ファイルの場所を確認できます。
$ docker info Containers: 16 Running: 4 Paused: 0 Stopped: 12 Images: 53 Server Version: 17.12.0-ce Storage Driver: overlay2 Backing Filesystem: extfs Supports d_type: true Native Overlay Diff: true Logging Driver: json-file Cgroup Driver: cgroupfs Plugins: Volume: local Network: bridge host macvlan null overlay Log: awslogs fluentd gcplogs gelf journald json-file logentries splunk syslog Swarm: inactive Runtimes: nvidia runc Default Runtime: runc Init Binary: docker-init containerd version: 89623f28b87a6004d4b785663257362d1658a729 runc version: b2567b37d7b75eb4cf325b77297b140ea686ce8f init version: 949e6fa Security Options: apparmor seccomp Profile: default Kernel Version: 4.13.0-1008-gcp Operating System: Ubuntu 16.04.3 LTS OSType: linux Architecture: x86_64 CPUs: 8 Total Memory: 196.8GiB Name: kirikei-gpu-2p100 ID: Y2TO:YVPN:Q5CC:CE2U:D36N:LAPL:ETKB:2CWC:6FHM:43TH:IWK2:UVTW Docker Root Dir: /var/lib/docker #環境ファイルの保存先 Debug Mode (client): false Debug Mode (server): false Registry: https://index.docker.io/v1/ Labels: Experimental: false Insecure Registries: 127.0.0.0/8 Live Restore Enabled: false
それでは/var/lib/docker/
を覗いてみましょう。
$sudo ls /var/lib/docker builder containerd containers image network overlay2 plugins runtimes swarm tmp trust volumes $sudo ls /var/lib/docker/containers 05bdbbd0ba717e5fa9c1278a36d4cadfd21c086e04cd2bf2f04cb1c80b7cb601 6a5be04d34dfe04c4d20c7085a2a7c79e6293917b1a34e746fb48beb16cab05a 29182f0a853741eae26c35a00acaff15e0ada8654b2c28b07ce5b799b6114a91 74d5762a9ab6a889f3a0742d226ee0dae4bc5bbc3b8a0b44ee89f3f9fd2e4d7c 3da8d78d42aa0be6331a462f3c1b19ad12e98ce84a35c90ef2363e91fa6f8753 84296104a53777287ac81126ea54353d216ac945e3767747fc0100f196cf3d6a 3f826eecd54f3f5c4f4669ca32da74cf6242d98f81b0290a11017db6bb5217e6 8a190f44c0c0b5e58cb842ea34a38bb1b0dae40e0d0c47e7b747fbc2b5414255 422966b08448176ae8b97530a95aaf6d38c0bf17ddfd82f4952c76f6ac1f73cb b26f8795699f8341b37a3baaab099aba3fc0dfbfe4c4603d77a9097b78093cc4 42b231e2bb3069913c23c88cc0184593be3aed21292f4ba5462a69518fe65eb4 c6e9faebbcf89bbeabf0da0e7c2feaaed6beb212df4516abdde15cc1f3e80c4c 54f4ec7e7f2fbb744e47c1d39e89a82a873e0abdff64d6003895e319be02e9a9 f27eadcbbf130ee30dd1fb544a2644b06ca7d2608889543fe0fcaed59ad1587f 59f251808b4b6da9ee26f768cd0e5f1f22071a4e6514edbedf17e337f7829096 f834f94f5b6de0dbc2f1ad0d09ccc28b5d2ba9f387b4b89917e5d9caaadb8b05
このようにcontainerやimageが保存されていることがわかります。
環境ファイル保存先を変更する。
簡単に調べたところ、docker環境の保存先を変更する方法はdockerの設定ファイルをいじったり、docker daemonの起動オプションをつけたりと複数の方法があるようです。
forums.docker.com
stackoverflow.com
今回調べたなかでもっとも簡単にできそうなシンボリックリンクは貼る方法でやりたいと思います。
やりかたの基本は下記リンクの通りです。
1.docker daemonを停止させる
$ /etc/init.d/docker stop
2.docker関連のプロセスが動いていないことを確認
$ ps aux|grep docker
3./var/lib/docker/
の中身を新しい場所に移動させる
$ mv /var/lib/docker /path/to/new/
4.デフォルトの場所にシンボリックリンクを貼る
$ ln -s /path/to/new/docker/ /var/lib/docker
5.docker daemonをスタートさせる
$ /etc/init.d/docker start
6.新しい場所でimageやcontainerが動いていることを確認する
以上です。
それではよいdockerライフを!
pytorchでレイヤーをフリーズさせる
画像分野で深層学習を用いる際はImageNetのような大規模データセットで学習した学習済みモデルを使用して転移学習、ファインチューニングを行うことが一般的です。
このファインチューニングを行う際に、最初の数エポックはモデルの最終層以外の重みを固定し最終層だけ学習、その後に全レイヤーの重みを学習するといったテクニックがあります。
一般的にニューラルネットワークの入力層に近いほど学習データの抽象的な特徴を学習していると言われていますが、
このテクニックを使うことにより、学習初期にいきなり入力層に近いレイヤーの重みが更新されることで抽象的な特徴抽出機能が崩壊することを防ぐ効果があります。
pytorchでは以下のようにパラメータをrequires_grad=False
することによってbackward()
の際に重みが更新されないようにする(freeze)ことができます。
from torchvision.models import resnet34 model = resnet34(pretrained=True) # freeze all layers for param in model.parameters(): param.requires_grad = False
上記の例だと最終層も含めてすべてのレイヤーがfreezeされてしまうので最終層はrequires_grad=True
のままにしておきます。
from torchvision.models import resnet34 model = resnet34(pretrained=True) # freeze layers except last layer for param in model.parameters(): param.requires_grad = False last_layer = list(model.children())[-1] print(f'except last layer: {last_layer}') for param in last_layer.parameters(): param.requires_grad = True
このようにしてやることで最終層だけ除いてレイヤーの重みを固定することができます。
この状態で数エポック学習を回して最終的に全レイヤーを学習する際はrequires_grad=True
に戻してあげればOKです。
from torchvision.models import resnet34 model = resnet34(pretrained=True) # unfreeze all layers for param in model.parameters(): param.requires_grad = True
jenkinsとgithubを連携して自動テストをする
web上にいろいろ情報が錯綜していていろいろハマったので成功したやりかたをメモしておく
やりたいこと
- githubに新しいcommitがpushされるごとにそのソースコードをビルドしてテストする
- テスト結果をgithub上から確認できるようにする
github側の設定
- ビルドしたいレポジトリにwebhookを設定する
- settings -> webook
- pyload url
- content type
- application json
- Which events would you like to trigger this webhook?
- "Just the push event." or "Send me everything."
- settings -> webook
- access tokenの設定
- 個人アカウントのsettings -> developer settings -> personal access token
- generate new token
- repoとadmin:repo_hookをチェック
- 個人アカウントのsettings -> developer settings -> personal access token
jenkins側の設定
fastaiのバージョン0.7をインストールする
fastaiという無料でディープラーニングについて学べる講座がありますが、その講義の中でfastaiというライブラリが使われています。 github.com
このライブラリはpytorchのラッパーのような形になっており、実際にディープラーニングをする上でのベストプラクティスが簡単に適用できることが特徴のようです。
実際に最適な学習率の初期値を見つけてくれる関数や[1506.01186] Cyclical Learning Rates for Training Neural Networksのような手法が簡単に使えるようになっています。
fastaiのライブラリは現在v1.0が公開されていますが、講義で使われてものやkaggleのkernelで使われているのはv0.7が多いようでv1を使っているとそのままでは動かないスクリプトが多数あります。 Githubにあるcondaやpipをつかってインストールすると最新版が入ってしますのでv0.7を使いたい場合は別の方法でインストールする必要があります。
fastaiのforumにはcondaの仮想環境を使ってv0.7のインストール方法がありますが、私の場合はどうもエラーが出てうまくいかなかったので別の方法をとりました。
v0.7のコードはfastaiレポジトリの old/fastai
に移されているのでここにpythonのパスを通すことで使うことにします。
まず適当なディレクトリに移動してfastaiのレポジトリからクローンしてきます。
cd /tmp/ git clone https://github.com/fastai/fastai cd fastai/old/
その後、pythonのスクリプトでパスを通すことでimport できるようになります。
import sys sys.path.append("/tmp/fastai/old") # on windows use \'s instead import fastai
fastaiは裏でpytorchやtorchvisionを呼び出しているので別途インストールしておいてださい。
conda install pytorch torchvision cuda92 -c pytorch
Hash Tables (Cousera Data Structure Week 4)
CouseraのData Strucureコースのweek4の内容です。
www.coursera.org
前回のweek3の内容の記事はこちら
rikeiin.hatenablog.com
Week4の内容はHash Tablesです。
Hashingの応用例
IP Addresses
あるWebサーバにアクセスしてきた各IPアドレスが過去一定期間にアクセスがあったか判定するという問題を考える。
単純に配列を使った実装の場合、考えうるIPアドレス数の長さの配列を用意し、該当するIPアドレスに対応するインデックスの要素をインクリメントする方法がある。
このとき、
- アクセスリストを更新する操作は(UpdateAccessList)は一つのログにつきO(1)
- 一定期間にアクセスがあったか判定する操作(AccessedLastHour)はO(1)
- IPv4でも2の32乗のメモリが必要
- IPv6では2の128乗であり、もはやメモリに乗らない
配列を使った実装はメモリを多く消費するため、リストを使った実装を考える。
List-based Mapping
過去一定期間にアクセスしてきたIPアドレスのみをリストに保存する。
このとき、アクセス順にリストにつなげていく。
- nは一定期間にアクセスがあったIPアドレスの数
- メモリ消費量は
- L.Append, L.Top, L.Popは
- L.Find, L.Eraseは
- UpdateAccessListは
- AccessedLastHourは
Hash Functions
ハッシュ関数の望ましい要件
- の計算が早い
- 異なるオブジェクトには異なる値
- メモリ使用量がでマップできる
- 基数は小さくしたい
- もしオブジェクトの数がより大きいとき、すべてを異なる値にマップすることは不可能である
Collisions
かつのとき、これを衝突(collision)と呼ぶ。
Chaining
Map
マップはあるオブジェクトからあるオブジェクトへのマッピングを行う。
例えば、以下のような例がある。
- ファイル名→ディスク上のファイルの場所
- 学生ID→学生名
- 契約名→契約電話番号
からへのマップはHashKey(O), Get(O), Set(O, v)の操作を持つデータ構造である。このとき、
Chaining
マップに関する各操作の疑似コードを以下に示す。
- の最大のチェインの長さをとすると、HashKey, Get, Setの時間計算量は
- のとき
- を現在のマップの異なるキーの数、をハッシュ関数の基数とするとメモリの消費量は
Hash Tables
Set
SetはAdd(O), Remove(O), Find(O)の操作を持つデータ構造である。
chainingを使ったSetの実装方法は二通りある。
- Setはからへのマップと等しい
- のペアを保存する代わりにのみを保存する
Setに対する各操作の疑似コードを以下に示す。
SetもしくはMapを用いたhashingの実装をHash tableと呼ぶ。
各プログラミング言語では以下のようなクラスに実装されている。
まとめ
- Chainingはhash tableを実装するためのテクニックのひとつである
- メモリ消費量は
- 各操作の時間計算量は
- どのようにmとcの両方を小さくするか?