k近傍法（kNN）でモデルの適用範囲（AD）を評価する！

# はじめに
k近傍法を用いたモデルの適用範囲評価について解説していきます。

# k近傍法とは？
- k近傍法（**k-NN**：<u>k</u>-<u>N</u>earest <u>N</u>eighbors algotithm）はデータ密度を計算する手法
- あるサンプルに対してデータセットの中から最も距離が近いサンプルk個を選択してその平均距離を算出
- 距離の平均値が小さいほどデータの密度が高いと考える
- この手法を選択することでデータの密度で評価できるので、データ分布が複数に分かれている場合でも適切にAD（Applicability Domain）を設定できる

# k近傍方を用いたモデルの適用範囲の決定方法
- トレーニングデータで平均距離が小さい順にサンプルを並べる
- しきい値を上位●%と定め、その平均距離より大きいサンプルはデータの密度が小さくAD外と見なす
- 例えばトレーニングデータが100個あって上位80%のサンプルの平均距離を10とした際には、平均距離が10以下のサンプルはAD内と見なす

# k近傍法の実装
```python
#ライブラリのインポート
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.neighbors import NearestNeighbors  # k-NN
```

```python
# ハイパーパラメータの設定
rate_of_training_samples_inside_ad = 0.8  # AD 内となるトレーニングデータの割合。AD　のしきい値を決めるときに使用
k_in_knn = 5  # k-NN における k
number_of_test_samples = 150  # テストデータのサンプル数←ここはのちほど "len(x_test)"と定義しても良さそう
```

データセットを読み込みます。ここでは以下のようなボストンのデータセットを読み込みます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1057114/addaa6d5-af48-3087-82ef-ad074373105a.png)

```python
# データセットの読み込み
dataset = pd.read_csv('boston.csv', index_col=0)
dataset
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1057114/4ae53777-e208-a38a-7902-5d2a98ca4795.png)

```python
# データセットの大きさの確認
dataset.shape
```
```:出力
(506, 14)
```
```python
# データ分割
y = dataset.iloc[:, 0]  # 目的変数
x = dataset.iloc[:, 1:]  # 説明変数
```
```python
# ランダムにトレーニングデータとテストデータとに分割
# random_state に数字を与えることで、別のときに同じ数字を使えば、ランダムとはいえ同じ結果にすることができます
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=number_of_test_samples, shuffle=True, random_state=99)
```
```python
# オートスケーリング
autoscaled_x_train = (x_train - x_train.mean()) / x_train.std()
autoscaled_x_test = (x_test - x_train.mean()) / x_train.std()
```
```python
# k-NN による AD
ad_model = NearestNeighbors(n_neighbors=k_in_knn, metric='euclidean')  # AD モデルの宣言
ad_model.fit(autoscaled_x_train)  # k-NN による AD では、トレーニングデータの x を model_ad に格納することに対応
```
トレーニングデータに対して、kNNで平均距離を算出していく
```python
# サンプルごとの k 最近傍サンプルとの距離に加えて、k 最近傍サンプルのインデックス番号も一緒に出力されるため、出力用の変数を 2 つに
# トレーニングデータでは k 最近傍サンプルの中に自分も含まれ、自分との距離の 0 を除いた距離を考える必要があるため、k_in_knn + 1 個と設定
knn_distance_train, knn_index_train = ad_model.kneighbors(autoscaled_x_train, n_neighbors=k_in_knn + 1)
```
```python
# トレーニングデータでの計算結果
knn_distance_train = pd.DataFrame(knn_distance_train, index=x_train.index)  # DataFrame型に変換
mean_of_knn_distance_train = pd.DataFrame(knn_distance_train.iloc[:, 1:].mean(axis=1),
                                          columns=['mean_of_knn_distance'])  # 自分以外の k_in_knn 個の距離の平均
mean_of_knn_distance_train
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1057114/def1cf35-677f-687b-3b71-88ad4e292b92.png)

```python
# トレーニングデータのサンプルの rate_of_training_samples_inside_ad * 100 % が含まれるようにしきい値を設定
sorted_mean_of_knn_distance_train = mean_of_knn_distance_train.iloc[:, 0].sort_values(ascending=True)  # 距離の平均の小さい順に並び替え
# しきい値の計算
ad_threshold = sorted_mean_of_knn_distance_train.iloc[
    round(autoscaled_x_train.shape[0] * rate_of_training_samples_inside_ad) - 1]
ad_threshold
```
```:出力
1.4662693333774972
```
各サンプルで平均距離を計算して、この値より平均距離が小さいとAD内、大きいとAD外と判別していく。
```python
# テストデータに対する k-NN 距離の計算
knn_distance_test, knn_index_test = ad_model.kneighbors(autoscaled_x_test)
knn_distance_test = pd.DataFrame(knn_distance_test, index=x_test.index)  # DataFrame型に変換
mean_of_knn_distance_test = pd.DataFrame(knn_distance_test.mean(axis=1),
                                         columns=['mean_of_knn_distance'])  # k_in_knn 個の距離の平均
```
```python
# テストデータの平均距離一覧
mean_of_knn_distance_test
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1057114/500a68d1-9613-f5d8-cb56-d065bdb6b5a3.png)

```python
# テストデータに対して、AD の中か外かを判定
inside_ad_flag_test = pd.DataFrame(mean_of_knn_distance_test <= ad_threshold, index=x_test.index)  # AD 内のサンプルのみ TRUE
inside_ad_flag_test.columns=['inside_ad_flag']
# 平均距離を併記しておく（オリジナル）
inside_ad_flag_test["mean_of_knn_distance"] = mean_of_knn_distance_test["mean_of_knn_distance"]
inside_ad_flag_test
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1057114/ed144b00-9d94-a244-d931-eab76a3447dd.png)

```python
 # csv ファイルに保存。同じ名前のファイルがあるときは上書きされるため注意
inside_ad_flag_test.to_csv('inside_ad_flag_test_withL.csv') 
```

## 参考文献
- [化学のためのpythonによるデータ解析・機械学習入門](https://www.amazon.co.jp/%E5%8C%96%E5%AD%A6%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE-Python%E3%81%AB%E3%82%88%E3%82%8B%E3%83%87%E3%83%BC%E3%82%BF%E8%A7%A3%E6%9E%90%E3%83%BB%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92%E5%85%A5%E9%96%80-%E9%87%91%E5%AD%90-%E5%BC%98%E6%98%8C/dp/4274224414)