NumpyとScikit-Learnで合成データを生成する

このチュートリアルでは、Numpy と Scikit-learn ライブラリを用いて、様々な合成データセットを生成する詳細について説明します。

既知のパラメータを持つ様々な分布から、どのように異なるサンプルを生成することができるかを見ていきます。

また、回帰、分類、クラスタリングなど、異なる目的のためのデータセット生成についても説明します。

最後に、既存のデータセットの分布を模倣したデータセットを生成する方法について説明します。

合成データの必要性

データサイエンスにおいて、合成データは非常に重要な役割を担っている。

合成データによって、新しいアルゴリズムを制御された条件下でテストすることができる。

言い換えれば、アルゴリズムの特定の特性や挙動をテストするためのデータを生成することができる。

例えば、バランスのとれたデータセットと不均衡なデータセットでの性能をテストしたり、異なるノイズレベル下での性能を評価したりすることができます。

このようにすることで、様々なシナリオにおけるアルゴリズムの性能のベースラインを確立することができます。

合成データが必要とされるケースは他にもたくさんある。

例えば、実データは入手が困難であったり、高価であったり、データポイントが少なすぎたりすることがある。

また、実データを他人に知られたくないというプライバシー上の理由もある。

セットアップ

合成データ生成のコードを書く前に、必要なライブラリをインポートしておきましょう。

import numpy as np


# Needed for plotting
import matplotlib.colors
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D


# Needed for generating classification, regression and clustering datasets
import sklearn.datasets as dt


# Needed for generating data from an existing dataset
from sklearn.neighbors import KernelDensity
from sklearn.model_selection import GridSearchCV


そして、最初に便利な変数を用意する。

# Define the seed so that results can be reproduced
seed = 11
rand_state = 11


# Define the color maps for plots
color_map = plt.cm.get_cmap('RdYlBu')
color_map_discrete = matplotlib.colors.LinearSegmentedColormap.from_list("", ["red","cyan","magenta","blue"])


既知の分布からの1次元サンプルの生成

では、1次元の既知の分布から標本点を生成する方法について説明します。

numpyrandomモジュールは、固定されたパラメータのセットを持つ既知の分布からサンプリングされた乱数を生成するための幅広い方法を提供します。

再現のために、RandomStateの呼び出しにseed` を渡して、同じシードを使用する限り、同じ数字を得ることができます。

ここでは、uniform, normal, exponential などの分布リスト、パラメータリスト、そしてこれらを視覚的に識別できるように色リストを定義してみましょう。

rand = np.random.RandomState(seed)


dist_list = ['uniform','normal','exponential','lognormal','chisquare','beta']
param_list = ['-1,1','0,1','1','0,1','2','0.5,0.9']
colors_list = ['green','blue','yellow','cyan','magenta','pink']


さて、これらを可視化するために Figure のサブプロットにまとめ、これらの分布やパラメータに基づいて合成データを生成し、適切な色を割り当てます。

これは、Pythonの式を生成するための関数 eval() を使って行われます。

例えば、 rand.exponential(1, 5000) を使用すると、スケール 1 、サイズ 5000 の指数分布からサンプルを生成することができます。

ここでは、 dist_listparam_listcolor_list を使って、これらの呼び出しを生成してみましょう。

fig,ax = plt.subplots(nrows=2, ncols=3,figsize=(12,7))
plt_ind_list = np.arange(6)+231


for dist, plt_ind, param, colors in zip(dist_list, plt_ind_list, param_list, colors_list):
    x = eval('rand.'+dist+'('+param+',5000)') 

    plt.subplot(plt_ind)
    plt.hist(x,bins=50,color=colors)
    plt.title(dist)


fig.subplots_adjust(hspace=0.4,wspace=.3) 
plt.suptitle('Sampling from Various Distributions',fontsize=20)
plt.show()


この結果、次のようになります。

回帰のための合成データ

sklearn.datasets パッケージには、回帰のための合成データセットを生成するための関数があります。

ここでは、回帰のための線形データと非線形データについて説明する。

make_regression()` 関数は、入力データ点 (regressor) とその出力 (target) のセットを返す。

この関数は、以下のパラメータで調整することができる。

    1. n_features – 生成されるデータの次元数/特徴量
    1. noise – ガウシアンノイズの標準偏差
    1. n_samples – サンプル数

応答変数は、生成された入力集合の線形結合である。

応答変数とは、他の変数に依存するもので、この場合、他のすべての入力特徴量を用いて予測しようとする目標特徴量のことです。

以下のコードでは、合成データは異なるノイズレベルに対して生成されており、2つの入力特徴と1つのターゲット変数で構成されています。

入力ポイントの色の変化は、データポイントに対応するターゲットの値の変化を示している。

データは視覚化しやすいように2次元で生成されているが、n_featuresパラメータを用いると高次元データを作成することができる。

map_colors = plt.cm.get_cmap('RdYlBu')
fig,ax = plt.subplots(nrows=2, ncols=3,figsize=(16,7))
plt_ind_list = np.arange(6)+231


for noise,plt_ind in zip([0,0.1,1,10,100,1000],plt_ind_list): 
    x,y = dt.make_regression(n_samples=1000,
                             n_features=2,
                             noise=noise,
                             random_state=rand_state) 

    plt.subplot(plt_ind)
    my_scatter_plot = plt.scatter(x[:,0],
                                  x[:,1],
                                  c=y,
                                  vmin=min(y),
                                  vmax=max(y),
                                  s=35,
                                  cmap=color_map)

    plt.title('noise: '+str(noise))
    plt.colorbar(my_scatter_plot)

fig.subplots_adjust(hspace=0.3,wspace=.3)
plt.suptitle('make_regression() With Different Noise Levels',fontsize=20)
plt.show()


ここでは、2つの入力変数(特徴量)を持つ1000サンプルのプールを作成しました。

ノイズレベル(0..1000)に応じて、生成されたデータが大きく異なることが散布図からわかります。

make_friedman 関数ファミリ

make_friedman?()関数には3つのバージョンがあります (?` から選んだ値に置き換えてください)。

これらの関数は、以下のように入力変数の非線形結合を用いてターゲット変数を生成します。

  • make_friedman1(): この関数の n_features 引数は少なくとも5でなければならず、したがって最小限の5次元の入力変数を生成します。ここで、ターゲットは以下のように与えられます。

y(x)=10∗sin(πx0x1)+20(x2−0.5)2+10×3+5×4+noisey(x)=10∗sin⁡(πx0x1)+20(x2−0.5)2+10×3+5×4+noise
y(x) = 10 * sin(pi x_0 x_1) + 20(x_2 – 0.5)^2 + 10x_3 + 5x_4 + text{noise}.
* make_friedman2(): 生成されたデータの入力次元は4次元である。

応答変数は次式で与えられる。

y(x)=√(x20+x1x2−1(x1x3)2)+noisey(x)=(x02+x1x2−1(x1x3)2)+noise
y(x) = sqrt{(x_0^2+x__1 x__2 – frac{1}{(x__1 x__3)^2})} ←クリックすると拡大します。

  • make_friedman3(): この場合の生成データも4次元です。出力変数は次式で与えられる。

y(x)=arctan(x1x2-1(x1x3)x0)+noisey(x)=arctan(x1x2-1(x1x3)x0)+noise
y(x) = arctan(frac{x_1 xxx_2 -thankfrac{1}{(xxx_1 xxx_3)}}{x_0})+the_text{noise})

以下のコードでは、これらの関数を用いてデータセットを生成し、最初の3つの特徴量を対象変数に応じて色を変えて3Dでプロットしている。

fig = plt.figure(figsize=(18,5))


x,y = dt.make_friedman1(n_samples=1000,n_features=5,random_state=rand_state)
ax = fig.add_subplot(131, projection='3d')
my_scatter_plot = ax.scatter(x[:,0], x[:,1],x[:,2], c=y, cmap=color_map)
fig.colorbar(my_scatter_plot)
plt.title('make_friedman1')


x,y = dt.make_friedman2(n_samples=1000,random_state=rand_state)
ax = fig.add_subplot(132, projection='3d')
my_scatter_plot = ax.scatter(x[:,0], x[:,1],x[:,2], c=y, cmap=color_map)
fig.colorbar(my_scatter_plot)
plt.title('make_friedman2')


x,y = dt.make_friedman3(n_samples=1000,random_state=rand_state)
ax = fig.add_subplot(133, projection='3d')
my_scatter_plot = ax.scatter(x[:,0], x[:,1],x[:,2], c=y, cmap=color_map)
fig.colorbar(my_scatter_plot)
plt.suptitle('make_friedman?() for Non-Linear Data',fontsize=20)
plt.title('make_friedman3')


plt.show()


分類のための合成データ

Scikit-learn には sklearn.dataset モジュールの中に分類のためのデータセットを生成するためのシンプルで使いやすい関数があります。

いくつかの例を見てみましょう。

nクラス分類問題に対する make_classification() の実装

nクラス分類問題では、 make_classification() 関数にはいくつかのオプションがあります。

  1. class_sep`: 異なるクラスをより分散させ、識別しやすくするかどうかを指定します。
    1. n_features: 特徴量の数
  2. n_redundant`: 冗長な特徴量の数
  3. n_repeated`: 繰り返される特徴量の数
  4. n_classes`: クラスの総数

2次元の入力データに対して、分類データセットを作ってみましょう。

2値分類問題に対して、 class_sep の値を変えてみる。

同じ色の点は同じクラスに属します。

この関数は、不均衡なクラスを生成することもできることに注目しましょう。

fig,ax = plt.subplots(nrows=1, ncols=3,figsize=(16,5))
plt_ind_list = np.arange(3)+131


for class_sep,plt_ind in zip([0.1,1,10],plt_ind_list):
    x,y = dt.make_classification(n_samples=1000,
                                 n_features=2,
                                 n_repeated=0,
                                 class_sep=class_sep,
                                 n_redundant=0,
                                 random_state=rand_state)

    plt.subplot(plt_ind)
    my_scatter_plot = plt.scatter(x[:,0],
                                  x[:,1],
                                  c=y,
                                  vmin=min(y),
                                  vmax=max(y),
                                  s=35,
                                  cmap=color_map_discrete)
    plt.title('class_sep: '+str(class_sep))


fig.subplots_adjust(hspace=0.3,wspace=.3)
plt.suptitle('make_classification() With Different class_sep Values',fontsize=20)
plt.show()


マルチラベル分類問題に対する make_multilabel_classification() の実装

make_multilabel_classification()関数は、マルチラベル分類問題のためのデータを生成します。

この関数には様々なオプションがありますが、最も注目すべきはn_label` で、これはデータ点あたりの平均ラベル数を設定します。

ここでは、4クラスのマルチラベル問題を考えてみましょう。

対象となるラベルのベクトルは、視覚化のために1つの値に変換されています。

点は、2値ラベルベクトルの10進表現に従って色付けされる。

このコードでは、n_labelに異なる値を使うことで、生成されたデータポイントの分類がどのように変わるかを見ることができます。

fig,ax = plt.subplots(nrows=1, ncols=3,figsize=(16,5))
plt_ind_list = np.arange(3)+131


for label,plt_ind in zip([2,3,4],plt_ind_list):
    x,y = dt.make_multilabel_classification(n_samples=1000,
                                            n_features=2,
                                            n_labels=label,
                                            n_classes=4,
                                            random_state=rand_state)
    target = np.sum(y*[8,4,2,1],axis=1)

    plt.subplot(plt_ind)
    my_scatter_plot = plt.scatter(x[:,0],
                                  x[:,1],
                                  c=target,
                                  vmin=min(target),
                                  vmax=max(target),
                                  cmap=color_map)
    plt.title('n_labels: '+str(label))


fig.subplots_adjust(hspace=0.3,wspace=.3)
plt.suptitle('make_multilabel_classification() With Different n_labels Values',fontsize=20)
plt.show()


クラスタリングのための合成データ

クラスタリングのために、sklearn.datasets はいくつかのオプションを提供しています。

ここでは、 make_blob()make_circles() 関数を取り上げます。

make_blobs().

make_blobs()` 関数は、等方性ガウス分布からデータを生成する。

引数として、特徴の数、中心の数、各クラスターの標準偏差を指定することができる。

ここでは、この関数を2次元で説明し、 cluster_std パラメータの値の違いによりデータ点がどのように変化するかを示す。

fig,ax = plt.subplots(nrows=1, ncols=3,figsize=(16,5))
plt_ind_list = np.arange(3)+131


for std,plt_ind in zip([0.5,1,10],plt_ind_list):
    x, label = dt.make_blobs(n_features=2,
                             centers=4,
                             cluster_std=std,
                             random_state=rand_state)

    plt.subplot(plt_ind)    
    my_scatter_plot = plt.scatter(x[:,0],
                                  x[:,1],
                                  c=label,
                                  vmin=min(label),
                                  vmax=max(label),
                                  cmap=color_map_discrete)
    plt.title('cluster_std: '+str(std))


fig.subplots_adjust(hspace=0.3,wspace=.3)
plt.suptitle('make_blobs() With Different cluster_std Values',fontsize=20)
plt.show()


make_circles().

make_circles()` 関数は、同じ中心を持つ2つの同心円を、もう一方の円の中に生成します。

ノイズパラメータを用いると、生成されたデータに歪みを加えることができる。

このようなデータは、アフィニティーベースのクラスタリングアルゴリズムを評価するのに有効です。

以下のコードは、異なるノイズレベルで生成された合成データを示しています。

fig,ax = plt.subplots(nrows=1, ncols=3,figsize=(16,5))
plt_ind_list = np.arange(3)+131


for noise,plt_ind in zip([0,0.1,1],plt_ind_list):
    x, label = dt.make_circles(noise=noise,random_state=rand_state)

    plt.subplot(plt_ind)    
    my_scatter_plot = plt.scatter(x[:,0],
                                  x[:,1],
                                  c=label,
                                  vmin=min(label),
                                  vmax=max(label),
                                  cmap=color_map_discrete)
    plt.title('noise: '+str(noise))


fig.subplots_adjust(hspace=0.3,wspace=.3)
plt.suptitle('make_circles() With Different Noise Levels',fontsize=20)
plt.show()


入力データセットから派生したサンプルの生成

既存のデータセットから追加のデータサンプルを生成する方法は数多くある。

ここでは、まずガウシアンカーネルを用いてデータのカーネル密度を推定し、この分布から追加のサンプルを生成する非常にシンプルな方法を説明する。

新しく生成されたサンプルを可視化するために、sklearn.datasets.fetch_olivetti_faces()で取得できるOlivetti faces datasetを見てみましょう。

このデータセットには、40人分の10種類の顔画像があります。

これから行うことは以下の通りです。

  1. 顔データを取得する
  2. データからカーネル密度モデルを生成する
    1. カーネル密度を用いて新しいデータサンプルを生成する
  3. 元の顔と合成顔を表示する。
# Fetch the dataset and store in X
faces = dt.fetch_olivetti_faces()
X= faces.data


# Fit a kernel density model using GridSearchCV to determine the best parameter for bandwidth
bandwidth_params = {'bandwidth': np.arange(0.01,1,0.05)}
grid_search = GridSearchCV(KernelDensity(), bandwidth_params)
grid_search.fit(X)
kde = grid_search.best_estimator_


# Generate/sample 8 new faces from this dataset
new_faces = kde.sample(8, random_state=rand_state)


# Show a sample of 8 original face images and 8 generated faces derived from the faces dataset
fig,ax = plt.subplots(nrows=2, ncols=8,figsize=(18,6),subplot_kw=dict(xticks=[], yticks=[]))
for i in np.arange(8):
    ax[0,i].imshow(X[10*i,:].reshape(64,64),cmap=plt.cm.gray)   
    ax[1,i].imshow(new_faces[i,:].reshape(64,64),cmap=plt.cm.gray)    
ax[0,3].set_title('Original Data',fontsize=20)
ax[1,3].set_title('Synthetic Data',fontsize=20)
fig.subplots_adjust(wspace=.1)
plt.show()


ここで表示されている元の顔は、元のデータセットがどのようなものかを知るために、400枚の画像から選ばれた8つの顔のサンプルです。

sample()`関数を使えば、いくらでも新しいデータポイントを生成することができます。

この例では、8つの新しいサンプルが生成されました。

ここで示される合成顔は、必ずしもその上に示される人物の顔に対応していないことに注意してください。

結論

この記事では、様々な問題に対する合成データセットを生成するためのいくつかの方法を知ることができました。

合成データセットは、制御された条件下で我々のアルゴリズムを評価し、性能測定の基準値を設定するのに役立ちます。

Pythonには、人工データ生成に利用できる幅広い関数があります。

どの関数とAPIが自分の特定の要件に使用できるかを理解することが重要です。

タイトルとURLをコピーしました