前回、正規分布のパラメータが平均値と標準偏差であること、正規分布の構成方法、データの複製方法を学びました。ただしあれだけだと、ちょっと不都合が生じる場合があります。これを理解するため、以下の例を考えて見ます。
今、身長と体重のデータをたくさん欲しいと考えています。10人くらいから、身長と体重のデータを集めました。そして、身長と体重の平均値と標準偏差を求め、身長の正規分布、体重の正規分布、これを求めました。最後に、2つの正規分布でデータを複製させ、大量のデータを得ました。
これで問題になることは、実際には少数しかいない存在が多くの比率で発生してしまう点にあります。身長が高い人は、骨や内臓なども大きいので、身長と体重は一般に強い相関性があります(身長が高い人で痩せている人がまったくいないとは言っていないことに注意してください)。これをまとめると、なんとなくですが以下のようになるでしょう。
しかし、先ほどの事例では、身長と体重を完全に独立させ、正規分布に従い乱数を生成してしまっているので、以下のようになってしまいます。
これは、身長と体重の正規分布を独立に生成している、すなわち、その強い相関性を無視しているがために起こる現象です。これを解消するため、今回は、変数間の相関性を維持したままの乱数を生成する方法として、多変量正規分布を扱います。
import numpy as np
dataset = np.zeros([10, 2]) # 10人分の身長と体重を格納するnumpy配列
# 1人目
dataset[0, 0] = 150
dataset[0, 1] = 59
# 2人目
dataset[1, 0] = 153
dataset[1, 1] = 61
# 3人目
dataset[2, 0] = 149
dataset[2, 1] = 75
# 4人目
dataset[3, 0] = 165
dataset[3, 1] = 67
# 5人目
dataset[4, 0] = 164
dataset[4, 1] = 68
# 6人目
dataset[5, 0] = 163
dataset[5, 1] = 49
# 7人目
dataset[6, 0] = 178
dataset[6, 1] = 69
# 8人目
dataset[7, 0] = 180
dataset[7, 1] = 70
# 9人目
dataset[8, 0] = 177
dataset[8, 1] = 95
# 10人目
dataset[9, 0] = 176
dataset[9, 1] = 65
print(dataset)
1変数の場合は平均値と標準偏差を計算しました。今回は、2変数間の関連性も算出する必要があります。これを保有する情報に、分散共分散行列というものがあります。詳しく知りたい人は自分で調べてください。
#平均の算出
m = np.mean(dataset, 0) # 0指定で行の平均, 1指定で列の平均
# 分散共分散行列の算出
cov = np.cov(dataset, rowvar=False)
# 行が個別データを表すなら rowvar = False
# 列が個別データを表すなら rowvar = True
print("平均値:")
print(m)
print("分散共分散行列:")
print(cov)
from scipy.stats import multivariate_normal
# 描画範囲の設定 (最小値, 最大値, 分割数の順に)
x, y = np.meshgrid(np.linspace(100, 200, 100), np.linspace(25, 125, 100))
z = np.dstack((x, y))
# 二変量正規分布の構築
pdf2 = multivariate_normal.pdf(z, m, cov) #範囲、平均、分散共分散の順
# 出力
print(pdf2)
これで、multivariate...が二変量正規分布を構成する部分です。これには、範囲、平均、分散共分散行列を入れる必要があります。printでその中身を表示していますが、わかりにくいので、以下で空間を可視化してみます。pcolor関数を用いることで、これを実現することができます。
from matplotlib import pyplot as plt
plt.pcolor(x, y, pdf2, cmap="hot")
plt.colorbar() # カラーバー
plt.title('visualization', size=20)
plt.xlabel('height [cm]', size=20)
plt.ylabel('weight [kg]', size=20)
plt.show()
色が強いほど、そのデータの発生確率が高いことを示しています。楕円になっているので、身長が高く体重が高い人ほど発生確率が高いこと、身長が低く体重も低い人ほど発生確率が高いことが見て取れます。一方、身長が低く体重が重い、といった人も一応は発生するようになっています。このように、変数間の相関関係を維持した正規分布を構成することができました。
ところで、pythonのmatplotlibでは、いろいろな色が用意されています。
詳しくはこちら: https://matplotlib.org/examples/color/colormaps_reference.html もしお気に入りの色があれば、cmapにその文字列を代入してみましょう。以下、適当に6つピックアップした結果です。奇抜なものもありますね。お硬い場所では変な色は使わないように注意してください。plt.figure(figsize=(10, 5)) # 描画領域の横幅, 縦幅
plt.subplot(2,3,1)
plt.pcolor(x, y, pdf2, cmap="YlGn")
plt.subplot(2,3,2)
plt.pcolor(x, y, pdf2, cmap="bone")
plt.subplot(2,3,3)
plt.pcolor(x, y, pdf2, cmap="cool")
plt.subplot(2,3,4)
plt.pcolor(x, y, pdf2, cmap="seismic")
plt.subplot(2,3,5)
plt.pcolor(x, y, pdf2, cmap="tab10") # きちんとした場所では使ったらダメ
plt.subplot(2,3,6)
plt.pcolor(x, y, pdf2, cmap="Pastel1") # きちんとした場所では使ったらダメ
plt.show()
それでは、2変量正規分布からデータを生成してみます。高い相関性を有するデータを集めたが、数が少ない...というときに使います。平均mと分散共分散行列は上で求めたものを使います。
# データ生成 平均、分散共分散行列、生成したデータ数の順に入れる
Dataset_new = np.random.multivariate_normal(m, cov, 100)
print(Dataset_new[0:10])
このように、新たなデータを生成できました(10個だけ表示しています)。例外もありますが、身長と体重になんとなく関連性がありそうですね。それでは、散布図で取得したデータを確認してみます。
plt.scatter(Dataset_new[:, 0], Dataset_new[:, 1])
plt.show()
「身長が低いほど体重が低く、身長が高いほど体重が高い」という傾向があり、例外もちょっとはあるよというデータを生成できました。このように、2変量正規分布を用いることで、2つのデータの間にある関連性を保存したまま、データを発生させることができます。人工知能を作りたいが、ちょっとしかデータを集められなかったといったときに使うと良いでしょう。
これまで、2変数の関連性を保存させたままデータを発生させる方法を学びました。しかし、現実的には3変数、4変数など、関連性のある変数の数は、いろいろな場合があります(例えば、算数、物理、化学は理系科目なので、点数の関連性が強い、などです)。この場合は、以下のコードを書けばokです。参考までに、4人から算数、物理、数学のテスト結果を教えてもらったことを想定したデータを用意します。
dataset3 = np.zeros([4, 3]) # 4人分、3科目
# 1人目
dataset3[0, 0] = 90
dataset3[0, 1] = 80
dataset3[0, 2] = 79
# 2人目
dataset3[1, 0] = 53
dataset3[1, 1] = 60
dataset3[1, 2] = 61
# 3人目
dataset3[2, 0] = 54
dataset3[2, 1] = 46
dataset3[2, 2] = 59
# 4人目
dataset3[3, 0] = 76
dataset3[3, 1] = 41
dataset3[3, 2] = 30
続いて、先ほどと同様に平均と分散共分散行列を求めてみます。
# 平均と分散共分散行列を求める
m3 = np.mean(dataset3, 0)
cov3 = np.cov(dataset3, rowvar=False)
最後に、以下のように書きます。これで、100セットのデータが複製できます。
Dataset_new3 = np.random.multivariate_normal(m3, cov3, 100)
print(Dataset_new3[0:10])
散布図で確認してみます。今回は3科目なので、それらの組み合わせ分散布図を描いてみます。
plt.figure(figsize=(13, 3)) # 描画領域の横幅, 縦幅
plt.subplot(1,3,1)
plt.scatter(Dataset_new3[:, 0], Dataset_new3[:, 1])
plt.xlabel("x0")
plt.ylabel("x1")
plt.subplot(1,3,2)
plt.scatter(Dataset_new3[:, 0], Dataset_new3[:, 2])
plt.xlabel("x0")
plt.ylabel("x2")
plt.subplot(1,3,3)
plt.scatter(Dataset_new3[:, 1], Dataset_new3[:, 2])
plt.xlabel("x1")
plt.ylabel("x2")
plt.show()
このように、3変数でもそれらのデータ間の関連性を保存したまま、データを生成できることが確認できました。毎回同じデータを発生させたい場合は、プログラムの一番上にnp.random.seed(1)を記載することを忘れないようにしてください。