外れ値検出、OneClassSVMについて

統計データにはよく外れ値なるものが含まれていることが多いそう。
外れ値とはデータの分布において他の観測値から大きく外れた値のことで、異常値とやらもあるけどそれとは異なるっぽい

外れ値検出、処理することは機械学習を行う上で学習の妨げをなくす大切な過程だそうで、行う必要があるので試してみた。

一つ目は単純にデータの集合から、第一四分位数、第三四分位数、四分位範囲(IQR)を求めて外れ値を検出する方法を試す。
大体のことはこのサイトを参考にした。

結果としてはこんな感じに
f:id:Owatank:20170907143825p:plain
青が元のデータ集合、赤が外れ値の集合
こうしてみると外れ値じゃない範囲がとても狭い・・・

次に scikit-learn というライブラリにあるOneClassSVMを試す
SVMってRCNNの論文とか読んだ限り、クラス分類に使われているイメージなので最低でも2クラス以上で使用するんじゃないとかと思ったのですが調べる限りSVMの性質を使った外れ値のようだ。

実装についてはまずscikit-learnの公式にあるチュートリアルを理解することに。

Outlier detection on a real data set — scikit-learn 0.19.0 documentation
チュートリアルで使用するデータをまずは表示してみる

# Get data
X1 = load_boston()['data'][:, [8, 10]]  # two clusters
X2 = load_boston()['data'][:, [5, 12]]  # "banana"-shaped

plt.xlim((-8, 28))
plt.ylim((3, 40))
plt.scatter(X1[:, 0], X1[:, 1], color='red')

plt.xlim((3, 10))
plt.ylim((-5, 45))
plt.scatter(X2[:, 0], X2[:, 1], color='blue')

f:id:Owatank:20170907144742p:plainf:id:Owatank:20170907144752p:plain

目で見てもわかるくらいの外れ値が確認できる

次にOCSVMを行うための下準備

# Define "classifiers" to be used
classifiers = {"OCSVM": OneClassSVM(nu=0.261, gamma=0.05)}
colors = ['m', 'g', 'b']
legend1 = {}
legend2 = {}

xx1, yy1 = np.meshgrid(np.linspace(-8, 28, 500), np.linspace(3, 40, 500))
xx2, yy2 = np.meshgrid(np.linspace(3, 10, 500), np.linspace(-5, 45, 500))

下2つのnp.meshgridは外れ値を除いたデータの集合を囲むための方眼紙のようなグリッド線を定義しているはず

赤い方のデータセット X1 からOCSVMを試す

for i, (clf_name, clf) in enumerate(classifiers.items()):
    plt.figure(1)
    # OCSVMにサンプル集合X1を与え、サンプル集合の境界を検出する
    clf.fit(X1)
    # xx1.ravel(), yy1.ravel() : 配列を1次元配列に変換する [[1,2,3],[1,2,3]] -> [1,2,3,4,5,6]
    # np.c_() : axis=1の配列の値で連結を行う
    # 各データとの距離が最大となる超平面を求める
    Z1 = clf.decision_function(np.c_[xx1.ravel(), yy1.ravel()])
    Z1 = Z1.reshape(xx1.shape)
    plt.title("Outlier detection on a real data set (boston housing)")
    plt.scatter(X1[:, 0], X1[:, 1], color='red')
    bbox_args = dict(boxstyle="round", fc="0.8")
    arrow_args = dict(arrowstyle="->")
    plt.annotate("several confounded points", xy=(24, 19),
                 xycoords="data", textcoords="data",
                 xytext=(13, 10), bbox=bbox_args, arrowprops=arrow_args)
    plt.scatter(X1[:, 0], X1[:, 1], color='red')
    # 境界の輪郭を描写する
    legend1[clf_name] = plt.contour(
        xx1, yy1, Z1, levels=[0], linewidths=2, colors=colors[i])

OneClassSVM()にあるfit()にデータを渡して検出を行っている
plt.contourにて描いた輪郭は以下のような結果になった
f:id:Owatank:20170907145719p:plain

青のデータ X2 についてはこのような結果になった
f:id:Owatank:20170907145902p:plain

どっちもデータが密集しているような場所を囲っているから大体学習は成功しているのかな

四分位数で使ったデータに対して同じようにOCSVMを試して見たところこんな感じになった
f:id:Owatank:20170907150042p:plain

赤の外れ値をみる限りはいい感じかな囲みがちょっと狭いかも?

外れ値を気にするようなデータを扱ったことがないから実感わかないが、魑魅魍魎跋扈するこの地獄変、外れ値を含む実データなんて腐る程あるんだろうしそんなデータセットを用いてお仕事してる人がたくさんいるんだろう。統計についても勉強すべきだ・・・