大規模データのクラスタリングには Mini Batch K-Means を使うべきという話

タイトルの通りですが、大規模データをクラスタリングする際には単純なK-Means法ではなく、Mini Batch K-Means法を使うべきという話です。

とある大規模データ(150万件ほどの文章ベクトル)をクラスタリングしたいことがあったのですが、単純にScikit-learnのK-Means法に投げてクラスタリングを走らせていたところ、数時間経っても一向に終わる気配がありませんでした。色々と調べていると、大規模データのクラスタリングにはMini Batch K-Means法を使うべきという記述を見つけました。公式ドキュメントによると、大体1万件を超えるデータをクラスタリングする場合にはMini Batch K-Meansを使うべきとのことです。

APIとしては単純にKMeansをMiniBatchKMeansに置き換えれば動きます。理論的な背景としては、論文 “Web Scale K-Means clustering” D. Sculley, Proceedings of the 19th international conference on World wide web (2010)に書かれており、ざっと読んだところランダムサンプリングしてクラスタの中心を計算していくのですが、KMeansとは異なり、各点ごとに中心を逐次的にアップデートしていくことで計算量を減らしています。

論文に載っていた速度比較ですが、圧倒的にMiniBatchKMeansが高速です。図の青がMiniBatchKMeans、赤がKMeans、横軸が時間。

この手法を使ったところ、KMeansでは数時間経っても終わらなかったクラスタリングが、MiniBatchKMeansでは数分程度で終わりました。ということで、大規模データのクラスタリングにはMIniBatchKMeansを使うべきということを学んだという話でした。

RNNのDropoutはどこに入れるべきか?:Where to Apply Dropout in Recurrent Neural Networks for Handwriting Recognition?

タイトルの通り、RNNに対してDropout層を追加する場合、どこに入れるのが適切なのか?と思い少し調べてみました。

ことの発端は、KerasにあるLSTMとGRUの、GPUによる高速化版であるCuDNNLSTMとCuDNNGRUには、dropoutとrecurrent_dropoutというCPU版には存在するパラメータが無いため、これらの層の前後にDropoutを入れても効果あるのかな?と疑問に思ったことが始まりです。

以下のKerasのgithubでこれらのパラメータについての議論が行われており、作者のChollet氏は以下のように述べており、通常のDropoutはRNNに対して効果が無いように読み取れます。https://github.com/keras-team/keras/issues/8935

Recurrent dropout is not implemented in cuDNN RNN ops. At the cuDNN level. So we can’t have it in Keras.

The dropout option in the cuDNN API is not recurrent dropout (unlike what is in Keras), so it is basically useless (regular dropout doesn’t work with RNNs).

Actually using such dropout in a stacked RNN will wreck training.

その後、少し調べてみるとタイトルにある論文「Where to Apply Dropout in Recurrent Neural Networks for Handwriting Recognition?」を見つけたので読んでみました。この論文では、手書き文字認識タスクにRNNを使用して、DropoutをRNNの前、中、後に入れてみて汎化性能を試しています。何種類かのデータセットで実験を行っていますが、結果はまちまちで、傾向としてはRNNの前にDropoutを使うのが性能が良いことが多いが、中、後に入れる場合も性能が良くなることがあり何とも言えない感じの結論となっています。ただ、著者らは複数層のRNNモデルに対しても実験を行っており、その場合にはネットワークの最初と最後にDropoutを入れるのが良い性能の傾向があると結論付けています。

はっきりとした結論は言えそうも無く、結局はタスクによるんじゃないかという感じですが、この辺はやはりディープラーニングはハイパーパラメータチューニング職人の世界であるといわれる一因なんじゃないかと感じました。

 

CNNによるセグメンテーション論文:「U-Net Convolutional Networks for Biomedical Image Segmentation」を読んだ

タイトルの通り、CNNを用いて医療画像をセグメンテーションするU-Netというネットワーク構造の論文を読んだ。

2015年に発表されたネットワーク構造だが、その後セグメンテーションでは古典的な内容になっており、いくつか発展形のネットワークも提案されている。

論文のまとめは以下。

U-Net Convolutional Networks for Biomedical Image Segmentation_Slide

Pythonでデータ整形まわりをまとめた本:「Python for Data Analysis (第2版)」を読んだ

最近、Pythonを使って機械学習を勉強しているがnumpyやpandas, グラフ作成辺りの体系的な知識が足りない気がしていたので、この辺りをまとめた本「Python for Data Analysis (第2版)」を読んだ。感想としてはもっと早くに読んでおくべきだったと思う。今まで何となく書いていたnumpyやpandasが割と頭の中で整理できたと思う。

日本語版は第1版の翻訳が出ているようだ。Amazonのレビューを見ると第1版はPython2系列で書かれており、今読むとしたら内容が古いのでオススメはしない。第2版ではPython3で書かれている。

以下に学んだことをピックアップしておく。

  • IPythonでは変数の後に「?」をつけると変数の情報が見れる
  • リストに要素を逐次的に追加していく場合、extendメソッドを使った方が要素を結合するよりも速い
  • arr2d[0][2]とarr2d[0,2]は同じ意味。
  • ffillメソッドで前方向のinterpolationができる
  • 大きなCSVファイルをpandasで読み込む際には、read_csv()にchunksizeを指定して処理していく
  • データ分析において前処理にかかる時間は全体の80%くらいという報告がある
  • Jupyter notebookではplotの設定がセル毎なので複雑なプロットは一つのセルにコマンドをまとめて書く必要がある
  • plot.kde()でカーネル密度推定をグラフにプロットすることが出来る
  • pandasのrolling()メソッドを使うことで、Moving Window Averageが取れる
  • 時系列データ分析では、exponentially weighted functionsが最近のデータに大きい重みをつける手法としてよく使われる
  • 特定の値しかとらないデータカラムはcategory型にすると使用するメモリが減って、機械学習の処理の高速化を見込める
  • DataFrameをNumPy配列に変換するにはdata.values
  • Pythonの関数を高速化する手法としてnumbaがある。numba.jit()を使うことで関数をJITコンパイルできる

最後の章に、この本よりもさらに学ぶのにオススメの本が挙げられている。「Hands-On Machine Learning with Scikit-Learn and TensorFlow」は読んだことがあるが、とても分かりやすく、特にこの本でnumpyやpandasを学んでからならすんなりと機械学習の手法を理解することが出来ると思う。読んだ際の感想はこちら。Hands-On Machine Learning with Scikit-Learn and TensorFlow: Concepts, Tools, and Techniques to Build Intelligent Systems を読んだ

紹介されている次に読むべきオススメの本:





PythonでOpenCV

以下のページを参考にしてMacにOpenCVを入れてPythonで動かしてみた。
Macでpython+opencvを使う

画像マッチングの例はなぜかFlannBasedMatcherを使うとエラーが出て失敗するので、 ここを参考にして、 BFMatcherを使って動作確認した。


import numpy as np
import cv2
from matplotlib import pyplot as plt

MIN_MATCH_COUNT = 10

img1 = cv2.imread('box.png',0) # queryImage
img2 = cv2.imread('box_in_scene.png',0) # trainImage

# Initiate SIFT detector
sift = cv2.xfeatures2d.SIFT_create()

# find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)

FLANN_INDEX_KDTREE = 0
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks = 50)

bf = cv2.BFMatcher()

matches = bf.knnMatch(des1, des2, k=2)

# store all the good matches as per Lowe's ratio test.
good = []
for m,n in matches:
if m.distance < 0.7*n.distance:
good.append(m)

if len(good)>MIN_MATCH_COUNT:
src_pts = np.float32([ kp1[m.queryIdx].pt for m in good ]).reshape(-1,1,2)
dst_pts = np.float32([ kp2[m.trainIdx].pt for m in good ]).reshape(-1,1,2)

M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC,5.0)
matchesMask = mask.ravel().tolist()

h,w = img1.shape
pts = np.float32([ [0,0],[0,h-1],[w-1,h-1],[w-1,0] ]).reshape(-1,1,2)
dst = cv2.perspectiveTransform(pts,M)

img2 = cv2.polylines(img2,[np.int32(dst)],True,255,3, cv2.LINE_AA)

else:
print "Not enough matches are found - %d/%d" % (len(good),MIN_MATCH_COUNT)
matchesMask = None

draw_params = dict(matchColor = (0,255,0), # draw matches in green color
singlePointColor = None,
matchesMask = matchesMask, # draw only inliers
flags = 2)

img3 = cv2.drawMatches(img1,kp1,img2,kp2,good,None,**draw_params)

plt.imshow(img3, 'gray'),plt.show()

実行結果