t-SNEとPCAの実行
想定として、ある時間に200~800nmまでの波長データで何かしらの値を取得できるセンサーがあり、そのログデータから異常値を予測したいものとします。
正常データとして使用するデータと異常データとして使用するデータを読み込み
t-SNEによる次元削減を行って可視化してみます。
これでデータの分布に違いがあるか判り、使用する正常データと異常データの選択の指標の一つになります。
データは訓練用データ、検証用(異常値)データ、テスト用(異常値)データ、検証用(異常値)データです。
データの内容
- ./data/train
:学習に使用するノイズの少ないsin波データ
- ./data/test/normal
:検証とテスト兼用の正常sin波データ
- ./data/test/anomaly/test
:異常値のテストデータ
- ./data/test/anomaly/valid
:異常値の検証用データ
正常データを1
、異常データを-1
としてラベルを付与して可視化します。
import os import glob import time import numpy as np import pandas as pd import matplotlib.pyplot as plt %matplotlib inline import seaborn as sns from sklearn.manifold import TSNE from sklearn.decomposition import PCA plt.style.use('seaborn') # 警告の非表示 import warnings warnings.filterwarnings('ignore')
分析用疑似データ作成
# 保存用フォルダ作成 if not(os.path.isdir('./data')): os.mkdir('./data') if not(os.path.isdir('./data/train')): os.mkdir('./data/train') if not(os.path.isdir('./data/test/normal')): os.mkdir('./data/test/normal') if not(os.path.isdir('./data/test')): os.mkdir('./data/test') if not(os.path.isdir('data/test/anomaly')): os.mkdir('data/test/anomaly') if not(os.path.isdir('data/test/anomaly/test')): os.mkdir('data/test/anomaly/test') if not(os.path.isdir('data/test/anomaly/valid')): os.mkdir('data/test/anomaly/valid')
データ内容の確認
それぞれどのようなデータが入るかを可視化して確認してみます。
def Make_data(index, eta=0.2, outlier=3, mode='train'): ''' sin波にノイズを加えたデータを作成する ''' np.random.seed(42) data_size = 1200 # データ数 X = np.linspace(0,1, data_size) # 0~1まで20個データを作成する noise = np.random.uniform(low= -1.0, high=1.0, size=data_size) * eta # -1~1までの値をデータサイズ個作成し、引数倍する y = np.sin(2.0 * np.pi * X) + noise # sin波にノイズを追加する # 外れ値の設定 # 40個ごとにランダムで0~引数outlierまでの1個の値を加算 if mode == 'test': for cnt in range(data_size): if cnt % 40 == 0: y[cnt] += np.random.randint(0, outlier, 1) plt.subplots(figsize=(16, 9)) # 表示サイズ指定 plt.scatter(X, y) # 散布図 plt.show() # DataFrameを引数をカラム名として作成 df = pd.DataFrame({ index:y }) return df
# train Make_data(1, 0.05).head()
1 | |
---|---|
0 | -0.012546 |
1 | 0.050312 |
2 | 0.033680 |
3 | 0.025586 |
4 | -0.013438 |
# valid_anomaly Make_data(1, 0.15, outlier=2.5, mode='test').head()
1 | |
---|---|
0 | -0.037638 |
1 | 0.140455 |
2 | 0.080079 |
3 | 0.045318 |
4 | -0.082235 |
# test_anomaly Make_data(1, 0.2, outlier=3, mode='test').head()
1 | |
---|---|
0 | -0.050184 |
1 | 0.185526 |
2 | 0.103278 |
3 | 0.055184 |
4 | -0.116633 |
# 可視化の内容を消去して再度宣言 def Make_data(index, eta=0.05, outlier=3, mode='train'): ''' sin波にノイズを加えたデータを作成する ''' np.random.seed(42) data_size = 1200 # データ数 X = np.linspace(0,1, data_size) # 0~1まで20個データを作成する noise = np.random.uniform(low= -1.0, high=1.0, size=data_size) * eta # -1~1までの値をデータサイズ個作成し、引数倍する y = np.sin(2.0 * np.pi * X) + noise # sin波にノイズを追加する # 外れ値の設定 # 100個ごとにランダムで0~引数outlierまでの1個の値を加算 if mode == 'test': for cnt in range(data_size): if cnt % 100 == 0: y[cnt] += np.random.randint(0, outlier, 1) # DataFrameを引数をカラム名として作成 df = pd.DataFrame({ index:y }) return df
訓練用データを作成
関数Make_data()
を使用して訓練用データを作成します。
ノイズはデフォルトの0.2
でデータ数160個
作成します。
df_train = pd.DataFrame([]) for i in range(1, 161): df_base = Make_data(i).T df_train = pd.concat([df_train, df_base]) # 200nm~800nmをカラムとして1桁で丸めて設定 df_train.columns = np.round(np.linspace(200, 800, 1200), 1) df_train.head()
200.0 | 200.5 | 201.0 | 201.5 | 202.0 | 202.5 | 203.0 | 203.5 | 204.0 | 204.5 | ... | 795.5 | 796.0 | 796.5 | 797.0 | 797.5 | 798.0 | 798.5 | 799.0 | 799.5 | 800.0 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | -0.012546 | 0.050312 | 0.03368 | 0.025586 | -0.013438 | -0.008202 | -0.012755 | 0.073292 | 0.052022 | 0.067953 | ... | -0.090411 | -0.033694 | -0.052086 | -0.019345 | -0.071625 | 0.016194 | 0.031628 | 0.036407 | 0.019725 | -0.036991 |
2 | -0.012546 | 0.050312 | 0.03368 | 0.025586 | -0.013438 | -0.008202 | -0.012755 | 0.073292 | 0.052022 | 0.067953 | ... | -0.090411 | -0.033694 | -0.052086 | -0.019345 | -0.071625 | 0.016194 | 0.031628 | 0.036407 | 0.019725 | -0.036991 |
3 | -0.012546 | 0.050312 | 0.03368 | 0.025586 | -0.013438 | -0.008202 | -0.012755 | 0.073292 | 0.052022 | 0.067953 | ... | -0.090411 | -0.033694 | -0.052086 | -0.019345 | -0.071625 | 0.016194 | 0.031628 | 0.036407 | 0.019725 | -0.036991 |
4 | -0.012546 | 0.050312 | 0.03368 | 0.025586 | -0.013438 | -0.008202 | -0.012755 | 0.073292 | 0.052022 | 0.067953 | ... | -0.090411 | -0.033694 | -0.052086 | -0.019345 | -0.071625 | 0.016194 | 0.031628 | 0.036407 | 0.019725 | -0.036991 |
5 | -0.012546 | 0.050312 | 0.03368 | 0.025586 | -0.013438 | -0.008202 | -0.012755 | 0.073292 | 0.052022 | 0.067953 | ... | -0.090411 | -0.033694 | -0.052086 | -0.019345 | -0.071625 | 0.016194 | 0.031628 | 0.036407 | 0.019725 | -0.036991 |
5 rows × 1200 columns
訓練データの保存
# index不要のとき df_train.to_csv('data/train/X_train.csv', index=False)
検証とテスト兼用の正常sin波データ
ノイズはデフォルトの0.2
でデータ数80個
作成します。
df_normal = pd.DataFrame([]) for i in range(1, 81): df_base = Make_data(i).T df_normal = pd.concat([df_normal, df_base]) # 200nm~800nmをカラムとして1桁で丸めて設定 df_normal.columns = np.round(np.linspace(200, 800, 1200), 1) df_normal.head()
200.0 | 200.5 | 201.0 | 201.5 | 202.0 | 202.5 | 203.0 | 203.5 | 204.0 | 204.5 | ... | 795.5 | 796.0 | 796.5 | 797.0 | 797.5 | 798.0 | 798.5 | 799.0 | 799.5 | 800.0 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | -0.012546 | 0.050312 | 0.03368 | 0.025586 | -0.013438 | -0.008202 | -0.012755 | 0.073292 | 0.052022 | 0.067953 | ... | -0.090411 | -0.033694 | -0.052086 | -0.019345 | -0.071625 | 0.016194 | 0.031628 | 0.036407 | 0.019725 | -0.036991 |
2 | -0.012546 | 0.050312 | 0.03368 | 0.025586 | -0.013438 | -0.008202 | -0.012755 | 0.073292 | 0.052022 | 0.067953 | ... | -0.090411 | -0.033694 | -0.052086 | -0.019345 | -0.071625 | 0.016194 | 0.031628 | 0.036407 | 0.019725 | -0.036991 |
3 | -0.012546 | 0.050312 | 0.03368 | 0.025586 | -0.013438 | -0.008202 | -0.012755 | 0.073292 | 0.052022 | 0.067953 | ... | -0.090411 | -0.033694 | -0.052086 | -0.019345 | -0.071625 | 0.016194 | 0.031628 | 0.036407 | 0.019725 | -0.036991 |
4 | -0.012546 | 0.050312 | 0.03368 | 0.025586 | -0.013438 | -0.008202 | -0.012755 | 0.073292 | 0.052022 | 0.067953 | ... | -0.090411 | -0.033694 | -0.052086 | -0.019345 | -0.071625 | 0.016194 | 0.031628 | 0.036407 | 0.019725 | -0.036991 |
5 | -0.012546 | 0.050312 | 0.03368 | 0.025586 | -0.013438 | -0.008202 | -0.012755 | 0.073292 | 0.052022 | 0.067953 | ... | -0.090411 | -0.033694 | -0.052086 | -0.019345 | -0.071625 | 0.016194 | 0.031628 | 0.036407 | 0.019725 | -0.036991 |
5 rows × 1200 columns
検証とテスト兼用の正常sin波データの保存
# index不要のとき df_normal.to_csv('./data/test/normal/X_normal.csv', index=False)
検証用(異常値)データを作成
ノイズは0.15
で、外れ値は2.5
、データ数30個
作成します。
df_valid = pd.DataFrame([]) for i in range(1, 31): df_base = Make_data(i, 0.15, outlier=2.5, mode='test').T df_valid = pd.concat([df_valid, df_base]) # 200nm~800nmをカラムとして1桁で丸めて設定 df_valid.columns = np.round(np.linspace(200, 800, 1200), 1) df_valid.head()
200.0 | 200.5 | 201.0 | 201.5 | 202.0 | 202.5 | 203.0 | 203.5 | 204.0 | 204.5 | ... | 795.5 | 796.0 | 796.5 | 797.0 | 797.5 | 798.0 | 798.5 | 799.0 | 799.5 | 800.0 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | -0.037638 | 0.140455 | 0.080079 | 0.045318 | -0.082235 | -0.077003 | -0.101138 | 0.146527 | 0.072245 | 0.109567 | ... | -0.176941 | -0.017259 | -0.082909 | 0.004838 | -0.162476 | 0.090501 | 0.126326 | 0.130183 | 0.069655 | -0.110974 |
2 | -0.037638 | 0.140455 | 0.080079 | 0.045318 | -0.082235 | -0.077003 | -0.101138 | 0.146527 | 0.072245 | 0.109567 | ... | -0.176941 | -0.017259 | -0.082909 | 0.004838 | -0.162476 | 0.090501 | 0.126326 | 0.130183 | 0.069655 | -0.110974 |
3 | -0.037638 | 0.140455 | 0.080079 | 0.045318 | -0.082235 | -0.077003 | -0.101138 | 0.146527 | 0.072245 | 0.109567 | ... | -0.176941 | -0.017259 | -0.082909 | 0.004838 | -0.162476 | 0.090501 | 0.126326 | 0.130183 | 0.069655 | -0.110974 |
4 | -0.037638 | 0.140455 | 0.080079 | 0.045318 | -0.082235 | -0.077003 | -0.101138 | 0.146527 | 0.072245 | 0.109567 | ... | -0.176941 | -0.017259 | -0.082909 | 0.004838 | -0.162476 | 0.090501 | 0.126326 | 0.130183 | 0.069655 | -0.110974 |
5 | -0.037638 | 0.140455 | 0.080079 | 0.045318 | -0.082235 | -0.077003 | -0.101138 | 0.146527 | 0.072245 | 0.109567 | ... | -0.176941 | -0.017259 | -0.082909 | 0.004838 | -0.162476 | 0.090501 | 0.126326 | 0.130183 | 0.069655 | -0.110974 |
5 rows × 1200 columns
検証(異常値)データの保存
# index不要のとき df_valid.to_csv('./data/test/anomaly/valid/X_valid.csv', index=False)
テスト用(異常値)データを作成
ノイズはデフォルトの0.2
で外れ値は3
、データ数48個
作成します。
df_test = pd.DataFrame([]) for i in range(1, 49): df_base = Make_data(i, 0.2, outlier=3, mode='test').T df_test = pd.concat([df_test, df_base]) # 200nm~800nmをカラムとして1桁で丸めて設定 df_test.columns = np.round(np.linspace(200, 800, 1200), 1) df_test.head()
200.0 | 200.5 | 201.0 | 201.5 | 202.0 | 202.5 | 203.0 | 203.5 | 204.0 | 204.5 | ... | 795.5 | 796.0 | 796.5 | 797.0 | 797.5 | 798.0 | 798.5 | 799.0 | 799.5 | 800.0 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | -0.050184 | 0.185526 | 0.103278 | 0.055184 | -0.116633 | -0.111403 | -0.14533 | 0.183145 | 0.082357 | 0.130375 | ... | -0.220205 | -0.009042 | -0.098321 | 0.016929 | -0.207902 | 0.127655 | 0.173675 | 0.177071 | 0.09462 | -0.147966 |
2 | -0.050184 | 0.185526 | 0.103278 | 0.055184 | -0.116633 | -0.111403 | -0.14533 | 0.183145 | 0.082357 | 0.130375 | ... | -0.220205 | -0.009042 | -0.098321 | 0.016929 | -0.207902 | 0.127655 | 0.173675 | 0.177071 | 0.09462 | -0.147966 |
3 | -0.050184 | 0.185526 | 0.103278 | 0.055184 | -0.116633 | -0.111403 | -0.14533 | 0.183145 | 0.082357 | 0.130375 | ... | -0.220205 | -0.009042 | -0.098321 | 0.016929 | -0.207902 | 0.127655 | 0.173675 | 0.177071 | 0.09462 | -0.147966 |
4 | -0.050184 | 0.185526 | 0.103278 | 0.055184 | -0.116633 | -0.111403 | -0.14533 | 0.183145 | 0.082357 | 0.130375 | ... | -0.220205 | -0.009042 | -0.098321 | 0.016929 | -0.207902 | 0.127655 | 0.173675 | 0.177071 | 0.09462 | -0.147966 |
5 | -0.050184 | 0.185526 | 0.103278 | 0.055184 | -0.116633 | -0.111403 | -0.14533 | 0.183145 | 0.082357 | 0.130375 | ... | -0.220205 | -0.009042 | -0.098321 | 0.016929 | -0.207902 | 0.127655 | 0.173675 | 0.177071 | 0.09462 | -0.147966 |
5 rows × 1200 columns
テスト用(異常値)データの保存
# index不要のとき df_test.to_csv('./data/test/anomaly/test/X_test.csv', index=False)
データの読み込みとラベル付与
start = time.time() # 実行開始時間の取得
""" 正常データの読み込みとラベル付与 """ # フォルダ内のファイル一覧を取得 check_train_normal_files = glob.glob('./data/train/*') check_test_normal_files = glob.glob('./data/test/normal/*') X_train = pd.DataFrame([]) y_train = [] for file_name in check_train_normal_files: csv = pd.read_csv(file_name) X_train = pd.concat([X_train, csv]) for i in range(0, len(csv)): y_train.append(1) for file_name in check_test_normal_files: csv = pd.read_csv(file_name) X_train = pd.concat([X_train, csv]) for i in range(0, len(csv)): y_train.append(1)
""" 異常データの読み込みとラベル付与 """ check_anomaly1_files = glob.glob('./data/test/anomaly/test/*') check_anomaly2_files = glob.glob('./data/test/anomaly/valid/*') X_test = pd.DataFrame([]) y_test = [] for file_name in check_anomaly1_files: csv = pd.read_csv(file_name) X_test = pd.concat([X_test, csv]) for i in range(0, len(csv)): y_test.append(-1) for file_name in check_anomaly1_files: csv = pd.read_csv(file_name) X_test = pd.concat([X_test, csv]) for i in range(0, len(csv)): y_test.append(-1)
# trainとtestのデータを一つにまとめる
df = pd.concat([X_train, X_test])
targets = y_train
targets.extend(y_test)
targets = np.array(targets)
t-SNEの実行
データ全体を使用して学習を実行してみます。
tsne = TSNE(n_components=2, random_state=42) df_embedded = tsne.fit_transform(df) # xとyはt-SNE分解からの2つのコンポーネントで、targetsは実際の数 tsne_df = pd.DataFrame( # データを縦(カラム方向)に結合、学習結果と説明変数を結合 np.column_stack((df_embedded, targets)), columns=['x', 'y', 'targets'] ) # 念のため正解ラベルのint型への型変換 tsne_df.loc[:, 'targets'] = tsne_df['targets'].astype(int)
grid = sns.FacetGrid(tsne_df, hue='targets', size=8) grid.map(plt.scatter, 'x', 'y') # grid.set(xlim=(-20, 20), ylim=(-60, 60), xticks=[-20, -10, 0, 10, 20]) grid.add_legend() plt.title('t-SNE scatter prot') plt.tight_layout() # plt.savefig('./image/t-SNE_data_check.png') plt.show()
PCAの実行
データ全体を使用して学習を実行してみます。
pca = PCA(n_components=2, random_state=42) df_embedded = pca.fit_transform(df) # xとyはt-SNE分解からの2つのコンポーネントで、targetsは実際の数 tsne_df = pd.DataFrame( # データを縦(カラム方向)に結合、学習結果と説明変数を結合 np.column_stack((df_embedded, targets)), columns=['x', 'y', 'targets'] ) # 念のため正解ラベルのint型への型変換 tsne_df.loc[:, 'targets'] = tsne_df['targets'].astype(int)
grid = sns.FacetGrid(tsne_df, hue='targets', size=8) grid.map(plt.scatter, 'x', 'y') # grid.set(xlim=(-20, 20), ylim=(-60, 60), xticks=[-20, -10, 0, 10, 20]) # seabornの凡例を使用すると結果と被るので変更 # grid.add_legend() plt.legend(loc="best", frameon=True, edgecolor="blue") plt.title('PCA scatter prot') plt.tight_layout() plt.savefig('./image/PCA_data_check.png') plt.show()
普段は細かくデータの選別をするならt-SNE
よりクラスタ間の距離感をみるならPCA
を重視して確認を行っています。