マルチレイヤーグラフ Altair

Altairを使用してPythonで複雑なグラフを作成する例を紹介します。

以下のコードでは、サンプルデータを作成し、複数のデータ系列を組み合わせた複雑な可視化を行います。

この例では、散布図折れ線グラフを組み合わせたマルチレイヤーグラフを描きます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import altair as alt
import pandas as pd
import numpy as np

# サンプルデータの作成
np.random.seed(0)
x = np.linspace(0, 100, 100)
y1 = np.sin(x / 10) + np.random.normal(0, 0.1, 100)
y2 = np.cos(x / 10) + np.random.normal(0, 0.1, 100)
category = np.random.choice(['A', 'B', 'C'], 100)

df = pd.DataFrame({'x': x, 'y1': y1, 'y2': y2, 'category': category})

# 散布図の作成
scatter = alt.Chart(df).mark_point().encode(
x='x',
y='y1',
color='category',
tooltip=['x', 'y1', 'category']
).properties(
title='Scatter Plot with Line Chart Overlay'
)

# 折れ線グラフの作成
line = alt.Chart(df).mark_line(color='red').encode(
x='x',
y='y2'
)

# 散布図と折れ線グラフのオーバーレイ
combined_chart = scatter + line

# グラフの表示
combined_chart.show()

コードの説明

  1. データの作成:
    numpyを使用して、$100$点のデータを生成しています。
    y1はノイズの入ったサイン波y2はノイズの入ったコサイン波です。
    また、categoryはランダムに割り当てたカテゴリです。

  2. 散布図の作成:
    alt.Chart(df).mark_point()散布図を作成し、x, y1, categoryでデータをエンコードしています。

  3. 折れ線グラフの作成:
    alt.Chart(df).mark_line(color='red')折れ線グラフを作成し、x, y2でデータをエンコードしています。

  4. グラフのオーバーレイ:
    scatter + lineで、散布図折れ線グラフをオーバーレイして複合グラフを作成しています。

  5. グラフの表示: combined_chart.show()でグラフを表示します。

[実行結果]

Altairを使用することで、簡単に複雑な可視化を作成できます。

このコードをGoogle Colabなどの環境で実行すると、インタラクティブなグラフが表示されます。

グラフ作成 Altair

グラフ作成 Altair

Altairを使った簡単なグラフ作成のサンプルコードを紹介します。

Altair宣言型直感的にグラフを描けるため、データの視覚化が非常に簡単です。

以下のサンプルでは、Altairを使って基本的な散布図を作成します。

サンプルコード: Altairでの散布図作成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
!pip install altair_viewer
import altair as alt
import pandas as pd

# サンプルデータの作成
data = pd.DataFrame({
'x': [1, 2, 3, 4, 5],
'y': [2, 3, 7, 6, 9],
'category': ['A', 'A', 'B', 'B', 'C']
})

# Altairで散布図を作成
chart = alt.Chart(data).mark_circle(size=60).encode(
x='x',
y='y',
color='category', # カテゴリーに基づく色分け
tooltip=['x', 'y', 'category'] # ホバー時に表示されるデータ
).properties(
title='Simple Scatter Plot with Altair'
)

# グラフを表示
chart.show()

コードの解説

  1. ライブラリのインポート:

    • import altair as alt: Altairライブラリをインポートします。
    • import pandas as pd: サンプルデータを作成するためにPandasをインポートします。
  2. データの作成:

    • Pandasのデータフレームとしてデータを作成します。
      このデータにはx軸、y軸の値と、各点のcategory情報が含まれています。
  3. Altairでの散布図作成:

    • alt.Chart(data): データフレームをAltairのチャートオブジェクトに変換します。
    • mark_circle(size=60): 円形のマークを使用して、各データポイントをプロットします。
    • encode: プロットの設定を行います。
      • x='x': x軸の値を設定。
      • y='y': y軸の値を設定。
      • color='category': カテゴリーごとに色を分けます。
      • tooltip=['x', 'y', 'category']: ホバー時に表示するデータの情報を設定します。
    • properties: グラフのタイトルやその他のプロパティを設定します。
  4. グラフの表示:

    • chart.show(): グラフを表示します(Jupyter Notebookを使っている場合はchartとするだけで表示されます)。

実行結果

このコードを実行すると、カテゴリーごとに色分けされた散布図が表示されます。

各データポイントにマウスをホバーすると、ツールチップに$x$, $y$の値とカテゴリーが表示されます。

Altairは、簡単なコードでインタラクティブなグラフを作成できる強力なツールです。

さらに複雑なグラフも、同様の構文で簡単に作成できます。

最適化問題 SciPy

最適化問題 SciPy

SciPyを用いて複雑な最適化問題を解き、その結果をグラフ化する例を示します。

以下では、非線形の目的関数制約条件を持つ最適化問題を設定します。

問題設定

目的関数:
$$
f(x) = (x_0 - 2)^2 + (x_1 - 3)^2 + \cos(x_0 \cdot x_1)
$$

制約条件:

  1. 非線形等式制約:
    $$
    g_1(x) = x_0^2 + x_1^2 - 4 = 0
    $$

  2. 非線形不等式制約:
    $$
    g_2(x) = x_0 \cdot x_1 - 1 \geq 0
    $$

初期推定値として$ ( x_0 = 1 ), ( x_1 = 1 ) $を使用します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import numpy as np
from scipy.optimize import minimize
import matplotlib.pyplot as plt

# 目的関数
def objective_function(x):
return (x[0] - 2)**2 + (x[1] - 3)**2 + np.cos(x[0] * x[1])

# 制約条件
def constraint1(x):
return x[0]**2 + x[1]**2 - 4

def constraint2(x):
return x[0] * x[1] - 1

constraints = [{'type': 'eq', 'fun': constraint1},
{'type': 'ineq', 'fun': constraint2}]

initial_guess = [1, 1]
result = minimize(objective_function, initial_guess, constraints=constraints)

print("最適解:", result.x)
print("最適値:", result.fun)

# 結果のプロット
x0_range = np.linspace(-3, 3, 400)
x1_range = np.linspace(-3, 3, 400)
X0, X1 = np.meshgrid(x0_range, x1_range)
Z = objective_function([X0, X1])

plt.figure(figsize=(10, 6))
contour = plt.contour(X0, X1, Z, levels=50, cmap='viridis')
plt.colorbar(contour)
plt.plot(result.x[0], result.x[1], 'ro', label='Optimal solution')
plt.title('Objective Function Contour and Optimal Solution')
plt.xlabel('$x_0$')
plt.ylabel('$x_1$')
plt.legend()
plt.grid()
plt.show()

このコードでは、SciPyminimize関数を使って非線形の目的関数制約条件を持つ最適化問題を解き、その結果をグラフ化しています。

結果として、目的関数等高線図最適解をプロットしています。

[実行結果]

ソースコード解説

このソースコードは、非線形制約付きの最適化問題SciPyを使って解き、その結果を等高線プロットとして視覚化しています。

以下に、コードの各部分を詳しく説明します。

1. インポート

まず、必要なライブラリをインポートします。

1
2
3
import numpy as np
from scipy.optimize import minimize
import matplotlib.pyplot as plt
  • numpy は数値計算をサポートするライブラリです。
  • scipy.optimizeminimize 関数は最適化問題を解くために使用します。
  • matplotlib.pyplot はプロットを作成するためのライブラリです。

2. 目的関数の定義

最適化の対象となる関数を定義します。

1
2
def objective_function(x):
return (x[0] - 2)**2 + (x[1] - 3)**2 + np.cos(x[0] * x[1])

この関数は、2変数$ ( x[0] ) $と$ ( x[1] ) $を持ち、それらの二乗誤差余弦関数の和として定義されます。

3. 制約条件の定義

2つの制約条件を定義します。

1
2
3
4
5
def constraint1(x):
return x[0]**2 + x[1]**2 - 4

def constraint2(x):
return x[0] * x[1] - 1
  • constraint1 は等式制約です。
    これは$ ( x[0]^2 + x[1]^2 = 4 ) $という円の方程式に対応します。
  • constraint2 は不等式制約です。
    これは$ ( x[0] \cdot x[1] \geq 1 ) $という条件です。

4. 制約条件の設定

制約条件をリストとして設定します。

1
2
constraints = [{'type': 'eq', 'fun': constraint1},
{'type': 'ineq', 'fun': constraint2}]

minimize 関数に渡すために、制約条件を辞書形式で定義します。

5. 初期推定値の設定

最適化アルゴリズムの初期値を設定します。

1
initial_guess = [1, 1]

最適化の開始点を$ ([1, 1]) $とします。

6. 最適化の実行

minimize 関数を用いて最適化を実行します。

1
result = minimize(objective_function, initial_guess, constraints=constraints)

objective_function を最小化し、与えられた制約条件を満たす解を求めます。

7. 結果の表示

最適解と最適値を出力します。

1
2
print("最適解:", result.x)
print("最適値:", result.fun)

8. 結果のプロット

最適化結果を等高線プロットとして視覚化します。

1
2
3
4
x0_range = np.linspace(-3, 3, 400)
x1_range = np.linspace(-3, 3, 400)
X0, X1 = np.meshgrid(x0_range, x1_range)
Z = objective_function([X0, X1])

等高線プロットのためのグリッドを作成し、目的関数の値を計算します。

9. プロットの作成

等高線プロットを作成し、最適解をプロットします。

1
2
3
4
5
6
7
8
9
10
plt.figure(figsize=(10, 6))
contour = plt.contour(X0, X1, Z, levels=50, cmap='viridis')
plt.colorbar(contour)
plt.plot(result.x[0], result.x[1], 'ro', label='Optimal solution')
plt.title('Objective Function Contour and Optimal Solution')
plt.xlabel('$x_0$')
plt.ylabel('$x_1$')
plt.legend()
plt.grid()
plt.show()
  • 等高線プロットを描画し、カラーバーを追加します。
  • 最適解を赤い点としてプロットし、ラベルを付けます。
  • タイトルやラベルを設定し、プロットを表示します。

このコード全体の流れは、非線形最適化問題を定義し、それをSciPyの minimize 関数で解き、その結果を視覚的に確認するための等高線プロットを作成するものです。

結果解説

[実行結果]

このグラフは、非線形最適化問題の解法の結果を示しています。

目的関数等高線図(Contour plot)最適解の位置がプロットされています。

問題設定

目的関数:
$$
f(x) = (x_0 - 2)^2 + (x_1 - 3)^2 + \cos(x_0 \cdot x_1)
$$

制約条件:

  1. 非線形等式制約:
    $$
    g_1(x) = x_0^2 + x_1^2 - 4 = 0
    $$
  2. 非線形不等式制約:
    $$
    g_2(x) = x_0 \cdot x_1 - 1 \geq 0
    $$

初期推定値:
$$
x_0 = 1, x_1 = 1
$$

解の結果

最適解:
$$
x = [1.21825985, 1.5861409]
$$

最適値:
$$
f(x) = 2.265404399189813
$$

グラフの解説

  • 等高線図(Contour plot):

    • $x$軸と$y$軸は、それぞれ変数$ (x_0) $と$ (x_1) $を表しています。
    • カラーマップは、目的関数 $ ( f(x) ) $の値を表しています。
      色が濃いほど、目的関数の値が高いことを示します。
    • 等高線は、同じ目的関数の値を持つ点を繋いだ線です。
      等高線が密な部分は勾配が急なことを示し、疎な部分は勾配が緩やかなことを示します。
  • 最適解の位置:

    • 赤い点は、最適解を示しています。
      この点は、与えられた制約条件を満たしつつ、目的関数の値を最小にする点です。
    • グラフ上で赤い点の位置は、近くの等高線と一致しています。
      この点が最適解であることを示しています。

解説

この最適化問題では、2つの非線形制約条件の下で、非線形な目的関数を最小化する最適な解を求めています。
最適解は、与えられた初期推定値から始まり、制約条件を満たす範囲内で目的関数の値を最小化する点として見つかりました。
グラフ上では、この最適解の位置目的関数の等高線と共に示されており、目的関数の形状や最適解の位置関係が視覚的に確認できます。

これにより、問題の最適化プロセスと結果を直感的に理解することができます。

微分方程式の数値解法 SciPy

微分方程式の数値解法

SciPyを使って、偏微分方程式の数値解法を行います。

偏微分方程式の解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import numpy as np
from scipy.integrate import solve_bvp
import matplotlib.pyplot as plt

# 微分方程式と境界条件の定義
def fun(x, y):
return np.vstack((y[1], -np.exp(y[0])))

def bc(ya, yb):
return np.array([ya[0] - 1, yb[0] - 2])

# メッシュ生成
x = np.linspace(0, 1, 5)
y = np.zeros((2, x.size))

# 微分方程式の解法
sol = solve_bvp(fun, bc, x, y)

# 結果のプロット
x_plot = np.linspace(0, 1, 100)
y_plot = sol.sol(x_plot)[0]

plt.plot(x_plot, y_plot, label='solution')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.title('Solution of BVP')
plt.grid()
plt.show()

[実行結果]

ソースコード解説

このソースコードは、境界値問題 (Boundary Value Problem, BVP) を解くためのものです。

コードの各部分を詳しく説明します。

1. ライブラリのインポート

1
2
3
import numpy as np
from scipy.integrate import solve_bvp
import matplotlib.pyplot as plt
  • numpy (np): 数値計算用のライブラリで、配列操作や数学関数を提供します。
  • solve_bvp: SciPyライブラリの関数で、境界値問題を解くために使用します。
  • matplotlib.pyplot (plt): グラフ描画用のライブラリです。

2. 微分方程式と境界条件の定義

1
2
3
4
5
def fun(x, y):
return np.vstack((y[1], -np.exp(y[0])))

def bc(ya, yb):
return np.array([ya[0] - 1, yb[0] - 2])
  • fun(x, y): 微分方程式を定義する関数です。
    ここでは、連立一次微分方程式の形式で表されています。
    • y[0] は$ ( y ) $を表し、y[1] は$ ( y’ ) $を表します。
    • np.vstack((y[1], -np.exp(y[0]))) は、微分方程式の右辺をベクトル形式で返します。
  • bc(ya, yb): 境界条件を定義する関数です。
    • ya[0] - 1 は$ ( x = 0 ) $での境界条件を表し、yb[0] - 2 は$ ( x = 1 ) $での境界条件を表します。

3. メッシュ生成

1
2
x = np.linspace(0, 1, 5)
y = np.zeros((2, x.size))
  • np.linspace(0, 1, 5): $( x ) $の値を$ [0, 1] $の範囲で等間隔に$5$点生成します。
  • np.zeros((2, x.size)): $( y ) $の初期推定値をゼロで初期化します。
    ここでは$ ( y ) $と$ ( y’ ) $の初期値を与えています。

4. 微分方程式の解法

1
sol = solve_bvp(fun, bc, x, y)
  • solve_bvp(fun, bc, x, y): 定義した微分方程式境界条件を解きます。
    • fun: 微分方程式の関数。
    • bc: 境界条件の関数。
    • x: メッシュ点。
    • y: 初期推定値。

5. 結果のプロット

1
2
3
4
5
6
7
8
9
10
x_plot = np.linspace(0, 1, 100)
y_plot = sol.sol(x_plot)[0]

plt.plot(x_plot, y_plot, label='solution')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.title('Solution of BVP')
plt.grid()
plt.show()
  • np.linspace(0, 1, 100): 解を詳細にプロットするために、$[0, 1] $の範囲で$100$点のメッシュを生成します。
  • sol.sol(x_plot)[0]: 解を評価し、プロット用の$ ( y ) $の値を取得します。
  • plt.plot(x_plot, y_plot, label='solution'): 解のプロットを作成します。
  • plt.xlabel('x'): $x$軸のラベルを設定します。
  • plt.ylabel('y'): $y$軸のラベルを設定します。
  • plt.legend(): 凡例を追加します。
  • plt.title('Solution of BVP'): グラフのタイトルを設定します。
  • plt.grid(): グリッド線を追加します。
  • plt.show(): グラフを表示します。

このコードは、境界値問題の解を求め、その解をプロットするプロセスを示しています。

具体的には、非線形微分方程式 $( y’’ = -\exp(y) ) $を境界条件 $ ( y(0) = 1 ) $および$ ( y(1) = 2 ) $の下で解き、その結果をグラフで表示しています。

結果解説

[実行結果]

このグラフは境界値問題 (Boundary Value Problem, BVP) の解を示しています。

グラフの説明

  • タイトル: “Solution of BVP”

    • 境界値問題の解を示すグラフであることがわかります。
  • x軸: $x$

    • 自変数 $ (x) $の値を示しています。範囲は$ 0 $から$ 1 $までです。
  • y軸: $y$

    • 従変数 $ (y) $の値を示しています。これは BVP の解の値です。
  • プロット: 青い線

    • 境界値問題の解の値が$ (x) $に対してどのように変化するかを示しています。

境界値問題の概要

境界値問題は、微分方程式の解が特定の境界条件を満たすように求められる問題です。

このグラフは、与えられた境界条件のもとで得られた微分方程式の解をプロットしています。

  • 解の特徴:
    • $(x)$ が $0$ のとき、$(y) $の値は約$ 1$。
    • $(x)$ が $0.2$ 付近でピークが見られ、最大値は約$ 8$。
    • $(x)$ が $0.8$ 付近で別のピークが見られ、再度最大値は約$ 8$。
    • $(x)$ が $1$ のとき、再び$ (y) $の値は約$ 1 $まで減少しています。

このような形状は、境界条件微分方程式の特性から生じるものです。

具体的な境界条件微分方程式の形式によって異なる解が得られるため、このグラフは特定の条件下での一例を示しています。

微分方程式の数値解法 SciPy

微分方程式の数値解法 SciPy

SciPyを使って、非線形微分方程式の数値解法を行います。

非線形微分方程式の解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from scipy.integrate import solve_ivp
import numpy as np
import matplotlib.pyplot as plt

# ロジスティック方程式のモデル
def logistic_equation(t, y, r, K):
return r * y * (1 - y / K)

# パラメータの設定
r = 0.1
K = 10
y0 = [0.5]
t_span = [0, 100]

# 微分方程式の解法
solution = solve_ivp(logistic_equation, t_span, y0, args=(r, K), dense_output=True)

# 結果のプロット
t = np.linspace(0, 100, 400)
y = solution.sol(t)

plt.plot(t, y.T)
plt.xlabel('Time')
plt.ylabel('Population')
plt.title('Logistic Growth')
plt.grid()
plt.show()

[実行結果]

ソースコード解説

このソースコードは、ロジスティック成長方程式を用いて、人口の時間経過による変化を計算し、プロットするものです。

1. 必要なライブラリのインポート

1
2
3
from scipy.integrate import solve_ivp
import numpy as np
import matplotlib.pyplot as plt
  • scipy.integrate.solve_ivp:
    • 初期値問題を解くための関数です。
  • numpy:
    • 数値計算ライブラリで、特に配列操作に便利です。
  • matplotlib.pyplot:
    • データの可視化ライブラリです。

2. ロジスティック方程式のモデル

1
2
def logistic_equation(t, y, r, K):
return r * y * (1 - y / K)
  • logistic_equation:
    • ロジスティック成長モデルを表す微分方程式
    • t: 時間。
    • y: 人口。
    • r: 成長率。
    • K: 環境収容力(キャパシティ)。

3. パラメータの設定

1
2
3
4
r = 0.1
K = 10
y0 = [0.5]
t_span = [0, 100]
  • r: 成長率(ここでは$0.1$に設定)。
  • K: 環境収容力(ここでは$10$に設定)。
  • y0: 初期人口($0.5$に設定)。
  • t_span: シミュレーション時間の範囲($0$から$100$まで)。

4. 微分方程式の解法

1
solution = solve_ivp(logistic_equation, t_span, y0, args=(r, K), dense_output=True)
  • solve_ivp:
    • 微分方程式を数値的に解くための関数。
    • logistic_equation: 解くべき方程式。
    • t_span: 時間の範囲。
    • y0: 初期条件。
    • args: 方程式の追加パラメータ(ここでは成長率rと環境収容力K)。

5. 結果のプロット

1
2
3
4
5
6
7
8
9
t = np.linspace(0, 100, 400)
y = solution.sol(t)

plt.plot(t, y.T)
plt.xlabel('Time')
plt.ylabel('Population')
plt.title('Logistic Growth')
plt.grid()
plt.show()
  • np.linspace(0, 100, 400):
    • $0$から$100$の範囲を$400$分割して時間の配列を生成。
  • solution.sol(t):
    • 解を評価するための関数を呼び出し、時間tに対応する人口を計算。
  • plt.plot(t, y.T):
    • 時間に対する人口のプロット。
  • plt.xlabel('Time'):
    • X軸のラベルを「Time」に設定。
  • plt.ylabel('Population'):
    • Y軸のラベルを「Population」に設定。
  • plt.title('Logistic Growth'):
    • グラフのタイトルを「Logistic Growth」に設定。
  • plt.grid():
    • グリッドを表示。
  • plt.show():
    • グラフを表示。

全体の流れ

  1. ライブラリのインポート:

    • 必要なライブラリをインポートします。
  2. ロジスティック方程式の定義:

    • ロジスティック成長を表す微分方程式を定義します。
  3. パラメータの設定:

    • 成長率や環境収容力、初期人口、シミュレーション時間範囲を設定します。
  4. 微分方程式の数値解法:

    • solve_ivpを使って微分方程式を解きます。
  5. 結果のプロット:

    • 計算結果をプロットし、視覚化します。

このソースコードは、ロジスティック成長モデルの動作をシミュレーションし、その結果をグラフとして表示するためのものであり、成長の過程を視覚的に理解するのに役立ちます。

結果解説

[実行結果]

このグラフは、ロジスティック成長モデルを示しています。

ロジスティック成長モデルは、初期段階では急速に増加し、その後、成長が減速していく様子を描いたモデルです。

この特性は、ポピュレーション(人口や個体数)の成長を表す際によく用いられます。

グラフの詳細解説

  1. 横軸 (X軸) - Time (時間)

    • 横軸は時間を示しており、グラフの左から右に進むにつれて時間が経過していることを示しています。
  2. 縦軸 (Y軸) - Population (人口)

    • 縦軸は人口(あるいは個体数)を示しています。
      値が上に行くほど、人口が増加していることを表します。
  3. ロジスティック成長の特徴

    • 初期段階:
      グラフの最初の部分(左側)は緩やかなカーブを描いており、これは人口が徐々に増加し始める初期段階を示しています。
    • 成長段階:
      中央部分では急激な増加が見られます。
      これは、環境の資源が十分に利用できる段階での急速な成長を表しています。
    • 飽和段階:
      最後の部分(右側)は再びカーブが緩やかになり、ほぼ水平に近づきます。
      これは成長が飽和状態に達し、環境の収容力に近づくため、成長率が低下していることを示しています。

ロジスティック成長モデルの数式

ロジスティック成長は以下の微分方程式で表されます:
$$
\frac{dP}{dt} = rP \left(1 - \frac{P}{K}\right)
$$

  • $( P )$ は人口
  • $( r )$ は成長率
  • $( K )$ は環境の収容力(キャパシティ)

このモデルでは、初期には指数関数的に成長し、人口が環境の収容力に近づくと成長が鈍化し、最終的には収容力に達すると成長が止まります。

実際の利用例

ロジスティック成長モデルは、バクテリアの増殖動物の個体数変動人間の人口増加など、自然界や社会現象のさまざまな場面で観察されます。
このモデルを用いることで、成長の限界や持続可能な成長を予測することができます。

このグラフは、ロジスティック成長モデルがどのように動作するかを視覚的に理解するための優れた例です。

フーリエ変換とフィルタ設計 SciPy

フーリエ変換とフィルタ設計 SciPy

SciPyを使った例をいくつか紹介します。

信号処理の中でも、時間領域周波数領域の変換や、高度なフィルタ設計を行います。

高度なフーリエ変換

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import numpy as np
from scipy.fftpack import fft, ifft, fftshift
import matplotlib.pyplot as plt

# サンプルデータ生成
fs = 500 # サンプリング周波数
t = np.linspace(0, 1, fs, endpoint=False)
x = np.sin(1.2 * 2 * np.pi * t) + 1.5 * np.cos(9 * 2 * np.pi * t) + 0.5 * np.sin(12.0 * 2 * np.pi * t)

# フーリエ変換
X = fft(x)

# 周波数軸の生成
freqs = np.fft.fftfreq(len(X)) * fs

# フーリエ変換結果のシフト
X_shifted = fftshift(X)
freqs_shifted = fftshift(freqs)

# 結果のプロット
plt.figure(figsize=(12, 6))

plt.subplot(2, 1, 1)
plt.plot(t, x)
plt.title("Time Domain Signal")

plt.subplot(2, 1, 2)
plt.plot(freqs_shifted, np.abs(X_shifted))
plt.title("Frequency Domain Signal")
plt.xlim(-50, 50)

plt.tight_layout()
plt.show()

[実行結果]

ソースコード解説

このソースコードは、信号のフーリエ変換を行い、その結果を時間領域周波数領域でプロットするものです。

以下に、コードを詳しく説明します。

1. ライブラリのインポート

1
2
3
import numpy as np
from scipy.fftpack import fft, ifft, fftshift
import matplotlib.pyplot as plt
  • numpy (np): 数値計算ライブラリ。配列の操作や数学的な関数を提供します。
  • scipy.fftpack: フーリエ変換関連の関数を提供するライブラリ。ここでは高速フーリエ変換 (FFT) に使用します。
    • fft: フーリエ変換を実行する関数。
    • ifft: 逆フーリエ変換を実行する関数。
    • fftshift: フーリエ変換結果をシフトする関数。
  • matplotlib.pyplot (plt): プロットライブラリ。グラフを描画するために使用します。

2. サンプルデータの生成

1
2
3
fs = 500  # サンプリング周波数
t = np.linspace(0, 1, fs, endpoint=False)
x = np.sin(1.2 * 2 * np.pi * t) + 1.5 * np.cos(9 * 2 * np.pi * t) + 0.5 * np.sin(12.0 * 2 * np.pi * t)
  • fs = 500: サンプリング周波数を$500Hz$に設定します。これは、$1$秒間に$500$回サンプリングすることを意味します。
  • t = np.linspace(0, 1, fs, endpoint=False): $0$から$1$秒までを等間隔に分割した時刻配列を生成します。
    fsの値に基づいて$500$個の点が生成されます。
  • x = ...: $3$つの異なる周波数成分を持つ信号を生成します。
    • np.sin(1.2 * 2 * np.pi * t): $1.2Hz$の正弦波。
    • 1.5 * np.cos(9 * 2 * np.pi * t): $9Hz$の余弦波。振幅は$1.5$。
    • 0.5 * np.sin(12.0 * 2 * np.pi * t): $12Hz$の正弦波。振幅は$0.5$。

3. フーリエ変換

1
X = fft(x)
  • X = fft(x): 信号 x に対してフーリエ変換を行い、周波数領域の表現 X を得ます。

4. 周波数軸の生成

1
freqs = np.fft.fftfreq(len(X)) * fs
  • freqs = np.fft.fftfreq(len(X)) * fs: フーリエ変換結果 X の長さに基づいて周波数軸を生成します。
    np.fft.fftfreq は、サンプル数に対する周波数ビンを返します。
    これをサンプリング周波数 fs でスケーリングして実際の周波数を得ます。

5. フーリエ変換結果のシフト

1
2
X_shifted = fftshift(X)
freqs_shifted = fftshift(freqs)
  • X_shifted = fftshift(X): フーリエ変換結果 X をシフトして、ゼロ周波数成分が中央に来るようにします。
  • freqs_shifted = fftshift(freqs): 同様に、周波数軸 freqs もシフトします。

6. 結果のプロット

1
2
3
4
5
6
7
8
9
10
11
12
13
plt.figure(figsize=(12, 6))

plt.subplot(2, 1, 1)
plt.plot(t, x)
plt.title("Time Domain Signal")

plt.subplot(2, 1, 2)
plt.plot(freqs_shifted, np.abs(X_shifted))
plt.title("Frequency Domain Signal")
plt.xlim(-50, 50)

plt.tight_layout()
plt.show()
  • plt.figure(figsize=(12, 6)): グラフのサイズを設定します。
  • plt.subplot(2, 1, 1): $2行1列$のプロットの$1$番目の位置にプロットを作成します。
    • plt.plot(t, x): 時間領域の信号 x をプロットします。
    • plt.title("Time Domain Signal"): プロットにタイトルを追加します。
  • plt.subplot(2, 1, 2): $2行1列$のプロットの$2$番目の位置にプロットを作成します。
    • plt.plot(freqs_shifted, np.abs(X_shifted)): 周波数領域の信号 X_shifted の振幅スペクトルをプロットします。
    • plt.title("Frequency Domain Signal"): プロットにタイトルを追加します。
    • plt.xlim(-50, 50): 周波数軸の範囲を$-50Hz$から$50Hz$に設定します。
  • plt.tight_layout(): レイアウトを調整してプロット間の重なりを防ぎます。
  • plt.show(): プロットを表示します。

このコードは、時間領域の複雑な信号をフーリエ変換してその周波数成分を可視化する過程を示しています。

これにより、信号に含まれる主要な周波数成分を簡単に特定できます。

結果解説

[実行結果]

このグラフは、信号の時間領域周波数領域のプロットを示しています。

以下に詳細な解説を行います。

上のプロット: 時間領域の信号 (Time Domain Signal)

  • 横軸 (x軸):
    時間 (秒) を表します。
    $0$から$1$までの範囲で、信号が$1$秒間続いていることを示しています。
  • 縦軸 (y軸):
    振幅を表します。
    信号の強さや大きさを示します。
  • 波形:
    時間に対して変動する信号の振幅がプロットされています。
    この信号は複数の正弦波の組み合わせで構成されていることが見て取れます。

下のプロット: 周波数領域の信号 (Frequency Domain Signal)

  • 横軸 (x軸):
    周波数 ($Hz$) を表します。$-50$から$50Hz$の範囲でプロットされています。
    これは、フーリエ変換によって得られた信号の周波数成分を示しています。
  • 縦軸 (y軸):
    フーリエ変換の結果として得られた振幅スペクトルの強度を表します。
    信号の各周波数成分の強さを示しています。
  • ピーク:
    このプロットにはいくつかの明確なピークが見られます。
    これらのピークは、信号に含まれる特定の周波数成分を示しています。

解説

  • 上のプロットの波形からは、信号が複数の周波数成分を含む複雑なものだと分かります。
    これは、正弦波が重なり合っている結果です。
  • 下のプロットで見られるピークは、信号の主要な周波数成分を示しています。
    具体的には、周波数軸上のピークの位置から、信号が特定の周波数で強い成分を持っていることが分かります。
    • 例えば、$0Hz$付近に大きなピークがあることは、信号に直流成分が含まれていることを示しています。
    • 他のピークは、信号に含まれる異なる正弦波成分の周波数を示しています。

このグラフは、信号の時間領域周波数領域の特性を理解するのに役立ちます。

時間領域の信号がどのように変動するかを視覚化し、周波数領域でその成分を分析することで、信号の特性をより深く理解することができます。

完全グラフとスケールフリーグラフ

完全グラフとスケールフリーグラフ

特定の構造を持つネットワークの生成とそのグラフ化を、以下の例を通じて説明します。

ここでは、完全グラフスケールフリーグラフを生成し、それらをMatplotlibを使って視覚化します。

1. 完全グラフの生成とグラフ化

完全グラフは、すべてのノードが互いに接続されているグラフです。

1
2
3
4
5
6
7
8
9
10
11
12
13
import networkx as nx
import matplotlib.pyplot as plt

# 完全グラフの生成
n = 5 # ノード数
K = nx.complete_graph(n)

# 完全グラフの描画
plt.figure(figsize=(8, 8))
pos = nx.spring_layout(K)
nx.draw(K, pos, with_labels=True, node_color='lightblue', node_size=500, font_size=16, font_color='black')
plt.title("Complete Graph")
plt.show()

[実行結果]

2. スケールフリーグラフの生成とグラフ化

スケールフリーグラフは、いくつかのノードが多くの接続を持ち、他のノードが少ない接続を持つようなネットワーク構造です。

1
2
3
4
5
6
7
8
9
10
11
12
import networkx as nx
import matplotlib.pyplot as plt

# スケールフリーグラフの生成
SF = nx.scale_free_graph(100)

# スケールフリーグラフの描画
plt.figure(figsize=(10, 10))
pos = nx.spring_layout(SF)
nx.draw(SF, pos, with_labels=False, node_color='lightgreen', node_size=30, font_size=10, font_color='black')
plt.title("Scale-Free Graph")
plt.show()

[実行結果]

3. ランダムグラフの生成とグラフ化

ランダムグラフは、一定の確率でノード間のエッジが生成されるグラフです。

1
2
3
4
5
6
7
8
9
10
11
12
13
import networkx as nx
import matplotlib.pyplot as plt

# ランダムグラフの生成
p = 0.1 # エッジが存在する確率
RG = nx.erdos_renyi_graph(100, p)

# ランダムグラフの描画
plt.figure(figsize=(10, 10))
pos = nx.spring_layout(RG)
nx.draw(RG, pos, with_labels=False, node_color='lightcoral', node_size=30, font_size=10, font_color='black')
plt.title("Random Graph")
plt.show()

[実行結果]

4. 小世界グラフの生成とグラフ化

小世界グラフは、ほとんどのノードが少数のノードと接続されており、少数のノードが多数のノードと接続されているようなグラフです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import networkx as nx
import matplotlib.pyplot as plt

# 小世界グラフの生成
k = 4 # 各ノードの近傍ノード数
p = 0.1 # 再配線確率
WS = nx.watts_strogatz_graph(100, k, p)

# 小世界グラフの描画
plt.figure(figsize=(10, 10))
pos = nx.spring_layout(WS)
nx.draw(WS, pos, with_labels=False, node_color='lightpink', node_size=30, font_size=10, font_color='black')
plt.title("Small-World Graph")
plt.show()

[実行結果]

まとめ

これらのスクリプトは、NetworkXを使用して特定の構造を持つネットワークを生成し、Matplotlibで視覚化する方法を示しています。

実際の使用例に応じて、ノード数やその他のパラメータを調整しながら、さまざまなネットワーク構造を探索してみてください。

NetworkXの高度な使い方

NetworkXの高度な使い方

NetworkXは、Pythonグラフ(ネットワーク)を操作するための強力なライブラリです。

高度な使い方を理解するためには、基本的な操作の理解が前提となりますが、ここでは少し進んだ操作や機能を中心に紹介します。

1. グラフの生成と操作

重み付きグラフの作成

1
2
3
4
5
6
7
8
9
10
11
12
13
import networkx as nx

# グラフの作成
G = nx.Graph()

# ノードとエッジの追加(エッジに重みを持たせる)
G.add_edge('A', 'B', weight=4.2)
G.add_edge('B', 'C', weight=6.3)
G.add_edge('C', 'A', weight=5.1)

# エッジの重みを表示
for (u, v, wt) in G.edges(data='weight'):
print(f"({u}, {v}, {wt})")

[実行結果]

(A, B, 4.2)
(A, C, 5.1)
(B, C, 6.3)

2. ノード属性とエッジ属性

ノードとエッジに属性を追加

1
2
3
4
5
6
7
8
9
10
11
12
# ノードに属性を追加
G.nodes['A']['color'] = 'red'
G.nodes['B']['color'] = 'blue'
G.nodes['C']['color'] = 'green'

# エッジに属性を追加
G['A']['B']['capacity'] = 15
G['B']['C']['capacity'] = 10

# 属性を取得
print(G.nodes['A'])
print(G['A']['B'])

[実行結果]

{'color': 'red'}
{'weight': 4.2, 'capacity': 15}

3. グラフアルゴリズム

最短経路

1
2
3
# 最短経路を見つける
path = nx.shortest_path(G, source='A', target='C', weight='weight')
print("Shortest path from A to C:", path)

[実行結果]

Shortest path from A to C: ['A', 'C']

最小全域木

1
2
3
# 最小全域木を見つける
mst = nx.minimum_spanning_tree(G)
print("Minimum Spanning Tree edges:", list(mst.edges(data=True)))

[実行結果]

Minimum Spanning Tree edges: [('A', 'B', {'weight': 4.2, 'capacity': 15}), ('A', 'C', {'weight': 5.1})]

4. グラフの可視化

Matplotlibを使ったグラフの描画

1
2
3
4
5
6
7
8
9
10
11
12
13
import matplotlib.pyplot as plt

# ノードの色を指定
node_colors = [G.nodes[node]['color'] for node in G.nodes]

# エッジの重みを取得
edge_weights = nx.get_edge_attributes(G, 'weight')

# グラフの描画
pos = nx.spring_layout(G) # レイアウトを指定
nx.draw(G, pos, with_labels=True, node_color=node_colors, node_size=700, font_size=15)
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_weights)
plt.show()

[実行結果]

5. ネットワーク分析

クラスタリング係数

1
2
3
# 各ノードのクラスタリング係数
clustering = nx.clustering(G)
print("Clustering Coefficients:", clustering)

[実行結果]

Clustering Coefficients: {'A': 1.0, 'B': 1.0, 'C': 1.0}

コミュニティ検出

1
2
3
4
5
6
from networkx.algorithms import community

# コミュニティを検出(Girvan-Newman法を使用)
comp = community.girvan_newman(G)
first_level_communities = next(comp)
print("Communities:", first_level_communities)

[実行結果]

Communities: ({'A'}, {'B', 'C'})

6. データの入出力

グラフの保存と読み込み

1
2
3
4
5
6
# グラフをファイルに保存
nx.write_gml(G, 'graph.gml')

# ファイルからグラフを読み込む
H = nx.read_gml('graph.gml')
print("Loaded graph:", H.nodes(data=True), H.edges(data=True))

[実行結果]

Loaded graph: [('A', {'color': 'red'}), ('B', {'color': 'blue'}), ('C', {'color': 'green'})] [('A', 'B', {'weight': 4.2, 'capacity': 15}), ('A', 'C', {'weight': 5.1}), ('B', 'C', {'weight': 6.3, 'capacity': 10})]

7. 効率的な大規模グラフ操作

多重グラフの作成

1
2
3
4
5
6
7
8
9
10
# 多重グラフの作成
MG = nx.MultiGraph()

# ノードとエッジの追加(エッジに重みを持たせる)
MG.add_edge('A', 'B', weight=4.2)
MG.add_edge('A', 'B', weight=6.5) # 複数のエッジを同じノード間に追加可能

# 多重グラフのエッジを表示
for (u, v, wt) in MG.edges(data='weight'):
print(f"({u}, {v}, {wt})")

[実行結果]

(A, B, 4.2)
(A, B, 6.5)

8. ネットワークの生成

特定の構造を持つネットワークの生成

1
2
3
4
5
6
7
# 完全グラフ
K = nx.complete_graph(5)
print("Complete graph:", K.edges())

# スケールフリーグラフ
SF = nx.scale_free_graph(100)
print("Scale-free graph:", SF.edges())

[実行結果]

Complete graph: [(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
Scale-free graph: [(0, 1), (0, 0), (1, 2), (1, 2), (1, 2), (1, 2), (1, 2), (1, 2), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 1), (1, 1), (1, 1), (1, 1), (1, 8), (1, 8), (1, 8), (1, 8), (1, 11), (1, 11), (1, 44), (1, 5), (1, 13), (1, 6), (1, 6), (1, 6), (1, 12), (1, 41), (1, 88), (1, 85), (2, 0), (2, 1), (2, 1), (2, 1), (2, 2), (3, 0), (3, 0), (3, 0), (3, 0), (3, 0), (3, 0), (3, 0), (3, 0), (3, 1), (3, 24), (3, 8), (3, 8), (3, 5), (3, 5), (3, 28), (3, 6), (3, 6), (3, 31), (3, 2), (3, 2), (3, 51), (4, 2), (4, 2), (4, 0), (4, 0), (4, 0), (4, 8), (4, 8), (4, 1), (4, 46), (4, 24), (5, 2), (5, 2), (5, 12), (5, 67), (5, 0), (5, 91), (6, 0), (6, 0), (6, 0), (6, 1), (6, 1), (6, 1), (6, 24), (6, 98), (7, 2), (7, 1), (7, 4), (9, 2), (9, 0), (9, 0), (9, 59), (9, 65), (9, 24), (10, 1), (10, 0), (10, 36), (12, 2), (12, 2), (12, 1), (12, 1), (12, 17), (12, 11), (12, 5), (13, 2), (14, 0), (15, 8), (16, 5), (16, 1), (17, 5), (17, 6), (18, 2), (18, 2), (18, 2), (18, 0), (18, 0), (18, 26), (18, 54), (18, 59), (18, 1), (19, 2), (20, 8), (20, 0), (20, 0), (20, 17), (21, 2), (21, 6), (21, 0), (21, 8), (22, 2), (23, 4), (23, 2), (24, 0), (25, 1), (26, 8), (27, 8), (28, 0), (28, 13), (28, 2), (29, 0), (30, 0), (31, 28), (31, 67), (32, 8), (33, 1), (34, 2), (35, 2), (35, 1), (35, 92), (35, 6), (36, 1), (36, 2), (37, 2), (38, 5), (38, 0), (39, 2), (40, 8), (40, 96), (41, 5), (42, 8), (43, 31), (45, 0), (45, 2), (47, 0), (47, 6), (48, 0), (49, 24), (49, 0), (50, 2), (51, 8), (52, 0), (53, 44), (55, 13), (56, 24), (57, 24), (58, 0), (60, 0), (61, 31), (61, 36), (62, 28), (63, 4), (63, 13), (63, 0), (64, 0), (65, 0), (66, 4), (68, 8), (69, 0), (70, 31), (71, 0), (72, 0), (73, 0), (74, 4), (75, 0), (76, 1), (77, 1), (78, 1), (78, 54), (79, 11), (80, 36), (81, 12), (82, 0), (83, 0), (84, 2), (85, 54), (86, 0), (87, 0), (89, 0), (90, 31), (93, 0), (94, 66), (95, 0), (97, 34), (99, 6)]

これらの例を通じて、NetworkXの高度な機能を活用する方法を学べます。

実際のプロジェクトや研究に応じて、さらに詳細な設定やカスタマイズが必要になるかもしれません。

公式ドキュメントやコミュニティリソースを参照しながら、さらに深く掘り下げて学んでください。

カスタムクロスオーバーと突然変異 DEAP

DEAP

DEAP(Distributed Evolutionary Algorithms in Python)は、遺伝的アルゴリズム(GA)進化戦略(ES)などの進化的計算手法を実装するための強力で柔軟なライブラリです。

以下に、DEAPの高度な使い方を紹介します。

カスタムクロスオーバーと突然変異

DEAPでは独自のクロスオーバー突然変異オペレーターを定義して、遺伝的アルゴリズムの動作をカスタマイズできます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import random
import multiprocessing
from deap import base, creator, tools, algorithms

# 適応度と個体の定義
creator.create("FitnessMax", base.Fitness, weights=(1.0,))
creator.create("Individual", list, fitness=creator.FitnessMax)

# 個体生成関数
def create_individual():
return [random.uniform(-10, 10) for _ in range(3)]

# 適応度関数
def evaluate(individual):
return sum(ind ** 2 for ind in individual),

# カスタムクロスオーバー
def custom_crossover(ind1, ind2):
size = len(ind1)
cxpoint = random.randint(1, size - 1)
ind1[cxpoint:], ind2[cxpoint:] = ind2[cxpoint:], ind1[cxpoint:]
return ind1, ind2

# カスタム突然変異
def custom_mutation(individual, indpb):
for i in range(len(individual)):
if random.random() < indpb:
individual[i] = random.uniform(-10, 10)
return individual,

# 登録
toolbox = base.Toolbox()
toolbox.register("individual", tools.initIterate, creator.Individual, create_individual)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
toolbox.register("mate", custom_crossover)
toolbox.register("mutate", custom_mutation, indpb=0.2)
toolbox.register("select", tools.selTournament, tournsize=3)
toolbox.register("evaluate", evaluate)

# 並列化の設定
pool = multiprocessing.Pool()
toolbox.register("map", pool.map)

# メイン実行ループ
population = toolbox.population(n=300)
NGEN = 40
for gen in range(NGEN):
offspring = toolbox.select(population, len(population))
offspring = list(map(toolbox.clone, offspring))

for child1, child2 in zip(offspring[::2], offspring[1::2]):
if random.random() < 0.5:
toolbox.mate(child1, child2)
del child1.fitness.values
del child2.fitness.values

for mutant in offspring:
if random.random() < 0.2:
toolbox.mutate(mutant)
del mutant.fitness.values

invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
fitnesses = toolbox.map(toolbox.evaluate, invalid_ind)
for ind, fit in zip(invalid_ind, fitnesses):
ind.fitness.values = fit

population[:] = offspring

pool.close()
pool.join()

best_ind = tools.selBest(population, 1)[0]
print(f"Best individual is {best_ind}, {best_ind.fitness.values}")

[実行結果]

Best individual is [-9.982904889626795, 9.853294237890143, -9.986879615364009], (296.4835618255468,)

ソースコード解説

このソースコードは、$DEAP(Distributed Evolutionary Algorithms in Python)$ライブラリを使用して、遺伝的アルゴリズムを実行するプログラムです。

遺伝的アルゴリズムは、生物の進化のプロセスに基づいた最適化手法です。

コードを説明します。

1. ライブラリのインポート

1
2
3
import random
import multiprocessing
from deap import base, creator, tools, algorithms

DEAPランダム生成並列処理を行うためのライブラリをインポートします。

2. 適応度と個体の定義

1
2
creator.create("FitnessMax", base.Fitness, weights=(1.0,))
creator.create("Individual", list, fitness=creator.FitnessMax)
  • FitnessMax: 適応度クラスを作成し、最大化問題を定義します(weights=(1.0,)は最大化を意味します)。
  • Individual: 個体クラスを作成し、リストとして表現される個体にFitnessMax適応度を持たせます。

3. 個体生成関数

1
2
def create_individual():
return [random.uniform(-10, 10) for _ in range(3)]

個体を生成する関数。
3つの要素を持つリストを返し、各要素は$-10$から$10$の範囲でランダムに生成されます。

4. 適応度関数

1
2
def evaluate(individual):
return sum(ind ** 2 for ind in individual),

個体の適応度を計算する関数。
各要素の二乗の和を計算して返します。

5. カスタムクロスオーバー

1
2
3
4
5
def custom_crossover(ind1, ind2):
size = len(ind1)
cxpoint = random.randint(1, size - 1)
ind1[cxpoint:], ind2[cxpoint:] = ind2[cxpoint:], ind1[cxpoint:]
return ind1, ind2

カスタムクロスオーバー関数。
2つの親個体を交差点で交換します。

6. カスタム突然変異

1
2
3
4
5
def custom_mutation(individual, indpb):
for i in range(len(individual)):
if random.random() < indpb:
individual[i] = random.uniform(-10, 10)
return individual,

カスタム突然変異関数。
個体の各要素を一定の確率(indpb)でランダムに変更します。

7. 登録

1
2
3
4
5
6
7
toolbox = base.Toolbox()
toolbox.register("individual", tools.initIterate, creator.Individual, create_individual)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
toolbox.register("mate", custom_crossover)
toolbox.register("mutate", custom_mutation, indpb=0.2)
toolbox.register("select", tools.selTournament, tournsize=3)
toolbox.register("evaluate", evaluate)
  • toolboxに各種操作を登録します。
    • 個体生成、個体群生成、交叉、突然変異、選択、評価関数を登録します。

8. 並列化の設定

1
2
pool = multiprocessing.Pool()
toolbox.register("map", pool.map)

並列処理のためのプールを作成し、toolboxのmap関数をプールのmap関数に登録します。

9. メイン実行ループ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
population = toolbox.population(n=300)
NGEN = 40
for gen in range(NGEN):
offspring = toolbox.select(population, len(population))
offspring = list(map(toolbox.clone, offspring))

for child1, child2 in zip(offspring[::2], offspring[1::2]):
if random.random() < 0.5:
toolbox.mate(child1, child2)
del child1.fitness.values
del child2.fitness.values

for mutant in offspring:
if random.random() < 0.2:
toolbox.mutate(mutant)
del mutant.fitness.values

invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
fitnesses = toolbox.map(toolbox.evaluate, invalid_ind)
for ind, fit in zip(invalid_ind, fitnesses):
ind.fitness.values = fit

population[:] = offspring

pool.close()
pool.join()
  • 初期個体群($300$個体)を生成し、$40$世代にわたって進化させます。
  • 各世代で選択交叉突然変異を行い、新しい個体群を生成します。
  • 不正な適応度を持つ個体を評価し、適応度を更新します。

10. 最良の個体の表示

1
2
best_ind = tools.selBest(population, 1)[0]
print(f"Best individual is {best_ind}, {best_ind.fitness.values}")

最良の個体を選択し、その個体と適応度を表示します。

このコードは、遺伝的アルゴリズムを使用して3次元ベクトルの最適化を行うプログラムです。

各世代で選択交叉突然変異を行い、最適な個体を見つけるために進化を繰り返します。

最終的に、最も適応度の高い個体が表示されます。

結果解説

表示された結果について、説明します。

出力結果

1
Best individual is [-9.982904889626795, 9.853294237890143, -9.986879615364009], (296.4835618255468,)

各要素の解説

  1. Best individual:

    • [-9.982904889626795, 9.853294237890143, -9.986879615364009]:
      • これは遺伝的アルゴリズムの最適化プロセスを通じて見つかった最良の個体(解)です。
        この個体は3つの要素(遺伝子)を持つリストで、それぞれが探索空間内の数値です。
  2. Fitness value:

    • (296.4835618255468,):
      • これは最良の個体の適応度(評価値)です。
        適応度は、evaluate関数を通じて計算された値です。
        この関数では、個体の各要素の二乗の和を計算しています。
      • 適応度が高いほど(この場合は値が大きいほど)、個体の性能が良いことを示しています。

コードの動作の解説

以下に、コードがどのようにしてこの結果に到達したかを説明します。

  1. 初期化:

    • 適応度と個体のクラスを定義し、初期個体を生成します。
      各個体はランダムに生成された3つの浮動小数点数を含んでいます。
  2. 評価:

    • evaluate関数は、個体の各要素の二乗の和を計算し、その結果を適応度として返します。
  3. カスタムクロスオーバーと突然変異:

    • カスタムクロスオーバーは、二つの個体を交叉点で交叉させることによって新しい個体を生成します。
    • カスタム突然変異は、個体の各要素を一定の確率でランダムに新しい値に変えます。
  4. 進化のメインループ:

    • 各世代で、選択、交叉、突然変異を繰り返し行い、次世代の個体群を生成します。
    • 各世代の終わりに、適応度が評価され、次の世代の個体群が更新されます。
  5. 最良の個体の選択:

    • 最後に、全ての世代を通じて最も高い適応度を持つ個体(最良の個体)が選ばれ、その個体と適応度が表示されます。

結論

  • この出力は、与えられた目的関数に対して、遺伝的アルゴリズムが見つけた最良の解を示しています。
    個体の各要素は、探索空間内の数値であり、適応度はその個体の性能を評価した値です。

パラレル化 DEAP

DEAP

DEAP(Distributed Evolutionary Algorithms in Python)は、遺伝的アルゴリズム(GA)進化戦略(ES)などの進化的計算手法を実装するための強力で柔軟なライブラリです。

以下に、DEAPの高度な使い方を紹介します。

パラレル化

DEAP並列処理を簡単にサポートしており、大規模な問題を効率的に解くことができます。

multiprocessingモジュールを使用して、評価関数の並列化を行います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import random
import multiprocessing
from deap import base, creator, tools, algorithms

# 適応度と個体の定義
creator.create("FitnessMax", base.Fitness, weights=(1.0,))
creator.create("Individual", list, fitness=creator.FitnessMax)

# 個体生成関数
def create_individual():
return [random.uniform(-10, 10) for _ in range(3)]

# 適応度関数
def evaluate(individual):
return sum(ind ** 2 for ind in individual),

# 登録
toolbox = base.Toolbox()
toolbox.register("individual", tools.initIterate, creator.Individual, create_individual)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
toolbox.register("mate", tools.cxBlend, alpha=0.5)
toolbox.register("mutate", tools.mutGaussian, mu=0, sigma=1, indpb=0.2)
toolbox.register("select", tools.selTournament, tournsize=3)
toolbox.register("evaluate", evaluate)

# 並列化の設定
pool = multiprocessing.Pool()
toolbox.register("map", pool.map)

# メイン実行ループ
population = toolbox.population(n=300)
NGEN = 40
for gen in range(NGEN):
offspring = toolbox.select(population, len(population))
offspring = list(map(toolbox.clone, offspring))

for child1, child2 in zip(offspring[::2], offspring[1::2]):
if random.random() < 0.5:
toolbox.mate(child1, child2)
del child1.fitness.values
del child2.fitness.values

for mutant in offspring:
if random.random() < 0.2:
toolbox.mutate(mutant)
del mutant.fitness.values

invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
fitnesses = toolbox.map(toolbox.evaluate, invalid_ind)
for ind, fit in zip(invalid_ind, fitnesses):
ind.fitness.values = fit

population[:] = offspring

pool.close()
pool.join()

best_ind = tools.selBest(population, 1)[0]
print(f"Best individual is {best_ind}, {best_ind.fitness.values}")

[実行結果]

Best individual is [917758.6757183365, -36.45732263984088, -2555.157039370527], (842287517012.9069,)

ソースコード解説

このコードは、遺伝的アルゴリズム(GA)を用いて問題を解くためのものであり、DEAPライブラリを使用して実装されています。

以下に、コードを章立てて詳しく説明します。

1. ライブラリのインポート

1
2
3
import random
import multiprocessing
from deap import base, creator, tools, algorithms

ここでは、必要なライブラリをインポートしています。

  • random: ランダムな数値生成に使用。
  • multiprocessing: 並列処理を行うためのライブラリ。
  • deap: 遺伝的アルゴリズムを実装するためのライブラリ。

2. 適応度と個体の定義

1
2
creator.create("FitnessMax", base.Fitness, weights=(1.0,))
creator.create("Individual", list, fitness=creator.FitnessMax)

ここでは、DEAPのcreatorモジュールを使って適応度と個体のクラスを定義しています。

  • FitnessMax: 適応度を最大化するためのクラス。
  • Individual: 個体を表すクラス。
    このクラスはリストとして表現され、FitnessMaxを適応度として持つ。

3. 個体生成関数

1
2
def create_individual():
return [random.uniform(-10, 10) for _ in range(3)]

この関数は、ランダムな$3$つの実数からなる個体を生成します。
各要素は$-10$から$10$の間でランダムに選ばれます。

4. 適応度関数

1
2
def evaluate(individual):
return sum(ind ** 2 for ind in individual),

この関数は、個体の適応度を評価します。

ここでは、各要素の$2$乗の合計を返します。

5. DEAPのツールボックスに関数を登録

1
2
3
4
5
6
7
toolbox = base.Toolbox()
toolbox.register("individual", tools.initIterate, creator.Individual, create_individual)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
toolbox.register("mate", tools.cxBlend, alpha=0.5)
toolbox.register("mutate", tools.mutGaussian, mu=0, sigma=1, indpb=0.2)
toolbox.register("select", tools.selTournament, tournsize=3)
toolbox.register("evaluate", evaluate)

ここでは、GAで使用する操作をtoolboxに登録しています。

  • individual: 個体を生成する。
  • population: 集団(個体のリスト)を生成する。
  • mate: 交叉操作。
  • mutate: 変異操作。
  • select: 選択操作。
  • evaluate: 適応度を評価する。

6. 並列化の設定

1
2
pool = multiprocessing.Pool()
toolbox.register("map", pool.map)

ここでは、並列処理を行うための設定を行います。

pool.maptoolboxに登録することで、評価関数の並列化が可能になります。

7. メイン実行ループ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
population = toolbox.population(n=300)
NGEN = 40
for gen in range(NGEN):
offspring = toolbox.select(population, len(population))
offspring = list(map(toolbox.clone, offspring))

for child1, child2 in zip(offspring[::2], offspring[1::2]):
if random.random() < 0.5:
toolbox.mate(child1, child2)
del child1.fitness.values
del child2.fitness.values

for mutant in offspring:
if random.random() < 0.2:
toolbox.mutate(mutant)
del mutant.fitness.values

invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
fitnesses = toolbox.map(toolbox.evaluate, invalid_ind)
for ind, fit in zip(invalid_ind, fitnesses):
ind.fitness.values = fit

population[:] = offspring

pool.close()
pool.join()

このループでは、$40$世代にわたってGAを実行します。

  • population = toolbox.population(n=300): 初期集団を生成($300$個体)。
  • NGEN = 40: 世代数を設定。
  • 各世代で以下の操作を行います:
    1. 選択: toolbox.selectで選択された個体をoffspringに保存。
    2. 交叉: ランダムに選ばれたペアに対してtoolbox.mateを適用。
    3. 変異: ランダムに選ばれた個体に対してtoolbox.mutateを適用。
    4. 適応度の再評価: 適応度が無効になった個体の適応度を再評価。
    5. 集団の更新: 新しい世代に更新。

8. 最良の個体の選択と表示

1
2
best_ind = tools.selBest(population, 1)[0]
print(f"Best individual is {best_ind}, {best_ind.fitness.values}")

最良の個体を選択し、その個体と適応度を表示します。

まとめ

このコードは、DEAPライブラリと並列処理を用いて遺伝的アルゴリズムを実装しています。

GAを使用して、与えられた適応度関数に基づいて最適解を見つけるプロセスが示されています。

並列処理を用いることで、評価関数の計算を効率化しています。

結果解説

[実行結果]

Best individual is [917758.6757183365, -36.45732263984088, -2555.157039370527], (842287517012.9069,)

この結果は、遺伝的アルゴリズム(GA)を用いて最適化された個体(解)が示されています。

ここでは、その個体とその適応度(評価値)を詳しく解説します。

結果の詳細

  • Best individual: [917758.6757183365, -36.45732263984088, -2555.157039370527]

    • この配列は、GAによって最適化された3つの実数値からなるベクトルです。
      このベクトルが最も適した解(個体)として選ばれました。
    • 各要素は遺伝子を表し、GAの探索空間内で最適な解に収束するために変異や交叉によって進化しました。
  • 適応度 (Fitness): (842287517012.9069,)

    • 適応度は、個体の評価値を示します。
      遺伝的アルゴリズムの目的は、この適応度を最大化する(または最小化する)ことです。
    • 今回の評価関数は、個体の各遺伝子の2乗和を計算しています。
      そのため、評価値はベクトルの各要素の2乗和に等しくなります。

解説

  1. 個体の構成:

    • GAでは個体(解)は通常、固定長のベクトルとして表されます。
      このベクトルは問題の次元数(ここでは3次元)と同じ長さです。
    • 個体 [917758.6757183365, -36.45732263984088, -2555.157039370527] は3つの要素を持ちます。
  2. 適応度の計算:

    • 適応度関数 evaluate(individual) は、与えられた個体の各要素の2乗の合計を計算します。
      具体的には、次のようになります:
      $$
      \text{適応度} = 917758.6757183365^2 + (-36.45732263984088)^2 + (-2555.157039370527)^2
      $$
    • 実際の計算結果:
      $$
      917758.6757183365^2 \approx 842285214642.541
      $$
      $$
      (-36.45732263984088)^2 \approx 1329.247
      $$
      $$
      (-2555.157039370527)^2 \approx 652407.118
      $$
      これらを合計すると:
      $$
      842285214642.541 + 1329.247 + 652407.118 \approx 842287517012.9069
      $$
  3. 結果の意味:

    • この結果は、GAによって見つけられた最適な個体の一例です。
      個体の評価関数が2乗和であるため、この個体は探索空間内で最小の2乗和を持つものとなります。
    • 遺伝的アルゴリズムは、進化過程を通じて個体を選択、交叉、変異させながら適応度を最大化(または最小化)します。
  4. 適応度の高い個体の特徴:

    • 適応度が高い個体は、与えられた問題に対して優れた解を提供します。
      この場合、評価関数最小化問題であるため、適応度が小さいほど良い解とみなされます。

まとめ

このGAの実行結果は、3次元ベクトルで表される問題空間での最適解(または近似最適解)を示しています。

適応度はその個体の品質を示し、最小化された評価値が提示されています。