Unityでゲームアプリを作成しました

本サイトはUnity自作ゲームアプリサイトに切り換え予定です。ゲーム作成で工夫した箇所、C#のスクリプトの解説と合わせて掲載する予定です。
また、現在のドキュメントはサブドメイン、または別途サーバで公開継続する予定です。

↓ この「BLOCKGAME」アイコンこをクリックするとUnityで作成したゲームが起動します
【ゲームの遊び方】
・色のボタンでボールの色を変えます。
・ボールと当たったブロックの色が一致しているとき、
ブロックがくずせます。
・ボールが下の壁に落ちると消えます。ボールが下の壁に落ちないように矢印ボタンで壁を動かします。
・ボールは5個まで使えます。
・全てブロックをくずせたら「GOAL」となります。
・ブロックが残ったときは「GAME OVER」となります。
※ゲーム画面を終了するときは「×」でなく「閉じる」ボタンを押してください。

訪問回数:38

畳み込み

1.畳み込みとは
 畳み込み(たたみこみ、英: Convolution)とは、2つの関数を合わせて新しい関数を作る数学的な操作です。主に信号処理や画像処理、機械学習(特に畳み込みニューラルネットワーク、CNN)で広く使用されます。
 畳み込みの基本的な概念を理解するために、1次元の畳み込みについて説明します。2つの関数fとgの連続的な畳み込み(畳み込み積分)は、以下のように定義されます。

2.連続的な畳み込み(畳み込み積分)
 畳み込みは2つの関数を操作して新しい関数を生成する数学的な操作です。

畳み込みを視覚的に理解するために、例として、次の関数の畳み込みを考えます。

# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import quad

# Parameters
t = np.linspace(-10, 10, 800)  # Time range
tau_shift = 5  # Shift for the g(τ - t) plot
a = 1  # Parameter for the exponential function

# Define the functions
def f(t, a):
    return np.exp(-a * t) * (t >= 0)

def g(t):
    return np.heaviside(t, 1)

# Numerical convolution using scipy's quad
def numerical_convolution(t, a):
    result = np.zeros_like(t)
    for i in range(len(t)):
        if t[i] >= 0:
            result[i], _ = quad(lambda tau: f(tau, a) * g(t[i] - tau), 0, t[i])
    return result

# Compute the functions
f_tau = f(t, a)
g_tau = g(t)
y_t = numerical_convolution(t, a)

# Compute g(τ - t)
g_tau_t = g(tau_shift - t)

# Plot the functions
plt.figure(figsize=(10, 10))

# Plot f(τ)
plt.subplot(4, 1, 1)
plt.plot(t, f_tau, label=r'$f(\tau) = e^{-a\tau}$')
plt.xlabel('Time $\tau$')
plt.ylabel(r'$f(\tau)$')
plt.title(r'Function $f(\tau)$')
plt.legend()
plt.grid(True)

# Plot g(τ)
plt.subplot(4, 1, 2)
plt.plot(t, g_tau, label=r'$g(\tau) = u(\tau)$')
plt.xlabel('Time $\tau$')
plt.ylabel(r'$g(\tau)$')
plt.title(r'Step Function $g(\tau)$')
plt.legend()
plt.grid(True)

# Plot g(τ - t)
plt.subplot(4, 1, 3)
plt.plot(t, g_tau_t, label=r'$g(\tau - t)$ with $\tau = ' + str(tau_shift) + r'$')
plt.xlabel('Time $t$')
plt.ylabel(r'$g(\tau - t)$')
plt.title(r'Reversed and Shifted Step Function $g(\tau - t)$ with $\tau = ' + str(tau_shift) + r'$')
plt.legend()
plt.grid(True)

# Plot y(t) (Convolution result)
plt.subplot(4, 1, 4)
plt.plot(t, y_t, label=r'$y(t)$ (Numerical Convolution)', linestyle='--')
plt.xlabel('Time $t$')
plt.ylabel('$y(t)$')
plt.title(r'Convolution of $f(t)$ and Step Function $g(t)$')
plt.legend()
plt.grid(True)

# Adjust layout
plt.tight_layout()
plt.show()

3.離散的な畳み込み(畳み込み和)

y=[0. 1. 2.5 4. 5.5 7. 2.5]

# -*- coding: utf-8 -*-
import numpy as np

f = np.array([1, 2, 3, 4, 5])
g = np.array([0, 1, 0.5])

# 畳み込みの計算
conv_result = np.convolve(f, g, mode='full')
print(conv_result)

単純パーセプトロン

1.概要
 単純パーセプトロンは、最も基本的な形式のニューラルネットワークで、1つのニューロンから構成されます。入力ベクトルと重みベクトルの内積にバイアスを加え、その結果を活性化関数(しきい値関数)によって出力します。線形分離可能な問題に対して適していますが、非線形問題には対応できません。
 すなわち、入力データが線形平面で2つのクラスに分けられる場合にのみ分類が可能となり、非線形な問題や複雑なクラスの境界線を持つ問題には対応できません。多クラス分類問題に対応するには、複数の単純パーセプトロンを組み合わせたり、他の手法(例えば多クラスロジスティック回帰や多層パーセプトロン)を使用する必要があります。
 雑なモデルや手法を検討する必要があります。

2.パーセプトロンの構造と動作
 パーセプトロンは、ニューラルネットワークの最も基本的な単位であり、単層の線形分類器です。1950年代から1960年代にかけて、フランク・ローゼンブラットによって考案されました。パーセプトロンは、与えられた入力データを2つのクラスに分類するために使用されます。
(1)パーセプトロンの構造と動作
 パーセプトロンの基本的な構造は次の通りです。
 ①入力層
  特徴量ベクトルx=[x1,x2,・・・,xn]を入力します。各特徴量は重み付けされます。
 ②重みとバイアス
  各入力特徴量には対応する重み w=[w1,w2,・・・,wn]があります。
  バイアスb は、入力に対する追加の定数項です。
 ③線形結合
  入力特徴量と重みのドット積にバイアスを加えて、線形出力zを計算します。
  z = w・x+b ”・”はベクトル積
 ④活性化関数
  線形出力z に対して活性化関数を適用し、最終的な出力yを得ます。
  パーセプトロンでは、ステップ関数(Heavisideステップ関数)が一般的に使用されます。

(2)パーセプトロンの学習
 パーセプトロンは、誤差修正アルゴリズム(パーセプトロン学習アルゴリズム)を使用して重みとバイアスを更新します。学習の手順は以下の通りです。
 ①初期化:
  重みベクトルw とバイアスb を小さなランダムな値で初期化します。
 ②各トレーニングデータポイントの処理:
  各トレーニングデータ(x,y_true)に対して、以下を行います
 ・線形出力zを計算します
  z = w・x+b ”・”はベクトル積
 ・活性化関数を適用して予測ypredを得ます
 ・誤差を計算します
  error=ytrue-ypred
 ・重みとバイアスを更新します

③エポック
 上記の処理を全てのトレーニングデータに対して行い、これを複数回(エポック)繰り返します。

3.単純パーセプトロンのサンプル

import numpy as np

class SimplePerceptron:
    def __init__(self, num_features, learning_rate=0.1, epochs=4):
        self.num_features = num_features
        self.learning_rate = learning_rate
        self.epochs = epochs
        self.weights = np.zeros(num_features)
        self.bias = 0.0
    
    def predict(self, x): #x:トレーニングデータ、又はテストデータ(特徴量ベクトル)
        linear_output = np.dot(self.weights, x) + self.bias #線形結合
        prediction = 1 if linear_output > 0 else 0 #活性化関数(ステップ関数)
        return prediction #予測結果を返す
    
    def train(self, X_train, y_train):
        for epoch in range(self.epochs):
            for x, y_true in zip(X_train, y_train):
                y_pred = self.predict(x)#予測結果
                error = y_true - y_pred #誤差の計算
                self.weights += self.learning_rate * error * x #重み更新(w←w+η(学習率)⋅error⋅x)
                self.bias += self.learning_rate * error #バイアス更新(b←b+η(学習率)⋅error)
                print("epoch:",epoch,"self.weights:",self.weights,"self.bias:",self.bias)
 
    def evaluate(self, X_test, y_test):
        print("*** evaluate ***")
        correct = 0
        for x, y_true in zip(X_test, y_test):
            y_pred = self.predict(x)
            if y_pred == y_true:
                correct += 1
        accuracy = correct / len(y_test)
        return accuracy

# Example usage:
if __name__ == "__main__":
    # Example dataset
    X_train = np.array([[0.9, 0.8], [0.7, 0], [0.3, 0.5], [0, 0]])
    y_train = np.array([1, 1, 0, 0])  # Binary labels (1 or 0)
    
    # Create and train the Simple Perceptron
    perceptron = SimplePerceptron(num_features=2)
    perceptron.train(X_train, y_train)
    
    # Example evaluation
    X_test = np.array([[0.6, 0.6], [0.7, 0.3], [0.2, 0.4], [0.6, 0.2]])
    y_test = np.array([1, 1, 0, 0])
    accuracy = perceptron.evaluate(X_test, y_test)
    
    print(f"Accuracy: {accuracy}")
#トレーニングデータ
#X_train = np.array([[0.9, 0.8], [0.7, 0], [0.3, #0.5], [0, 0]])
#y_train = np.array([1, 1, 0, 0])

#テストデータ    
#X_test = np.array([[0.6, 0.6], [0.7, 0.3], [0.2, #0.4], [0.6, 0.2]])
#y_test = np.array([1, 1, 0, 0])

#実行結果
#epoch: 1 self.weights: [ 0.03 -0.02] self.bias: -0.1
#epoch: 2 self.weights: [0.09 0.01] self.bias: -0.1
#epoch: 3 self.weights: [0.15 0.04] self.bias: -0.1
#Accuracy: 1.0

 トレーニングデータ、テストデータ、及びepoch毎の実行結果weights、biasの線形出力をグラフに表示します。
 ここで、トレーニングデータのラベル0:青×、ラベル1:青○、テストデータのラベル0:赤×、ラベル1:赤○とします。 

import matplotlib.pyplot as plt
import numpy as np

# データ
data_circle = [[0.9, 0.8], [0.7, 0]]
data_cross = [[0.3, 0.5], [0, 0]]
test_circle = [[0.6, 0.6], [0.7, 0.3]]
test_cross = [[0.2, 0.4], [0.6, 0.2]]

# データをX軸とY軸に分割
x_circle, y_circle = zip(*data_circle)
x_cross, y_cross = zip(*data_cross)
xt_circle, yt_circle = zip(*test_circle)
xt_cross, yt_cross = zip(*test_cross)

# プロット
plt.figure(figsize=(8, 6))
plt.scatter(x_circle, y_circle, marker='o', color='b', label='label:1')
plt.scatter(x_cross, y_cross, marker='x', color='b', label='label:0')
plt.scatter(xt_circle, yt_circle, marker='o', color='r', label='label:1')
plt.scatter(xt_cross, yt_cross, marker='x', color='r', label='label:0')

# 直線のプロット
x_line = np.linspace(-0.1, 1, 400)  # x範囲を設定
y_line1 = -0.15 * x_line / 0.04 + 0.1 / 0.04
y_line2 = -0.03 * x_line / (-0.02) + 0.1 / (-0.02)
y_line3 = -0.09 * x_line / 0.01 + 0.1 / 0.01
plt.plot(x_line, y_line1, color='g', label='epoch:3 y=-0.15*x/0.04+0.1/0.04')
plt.plot(x_line, y_line2, color='y', label='epoch:1 y=-0.03*x/(-0.02)+0.1/(-0.02)')
plt.plot(x_line, y_line3, color='m', label='epoch:2 y=-0.09*x/0.01+0.1/0.01')


plt.title('SimplePerceptron blue:train red:test')
plt.xlabel('X')
plt.ylabel('Y')
plt.legend()
plt.grid(True)
plt.show()

4.プログラムの解説
(1)SimplePerceptron クラスの初期化

def __init__(self, num_features, learning_rate=0.1, epochs=4):
        self.num_features = num_features
        self.learning_rate = learning_rate
        self.epochs = epochs
        self.weights = np.zeros(num_features)
        self.bias = 0.0

・特徴量の数 num_featuresを設定し、重みベクトルの初期化を行います。
 self.num_features: 2
 self.weights: [0. 0.]
・学習率 learning_rate、エポック数 epochs を設定します。

(2)predict メソッド(予測値を算出)

def predict(self, x): #x:トレーニングデータ、又はテストデータ(特徴量ベクトル)
        linear_output = np.dot(self.weights, x) + self.bias #線形結合
        prediction = 1 if linear_output > 0 else 0 #活性化関数(ステップ関数)
        return prediction #予測結果を返す

入力ベクトル(特徴量) x を用いて予測を行います。線形結合の結果を活性化関数として使い、閾値に基づいて予測を二値化します。
・linear_output = np.dot(self.weights, x) + self.bias
 重みと特徴量のベクトル積にバイアス値を加算(線形結合) 
・prediction = 1 if linear_output > 0 else 0
 三項演算子を使用しており、linear_output が 0 より大きい場合は 1 を、そうでない場合は 0 を prediction に代入します。

(3)train()メソッド(重み、バイアスの更新)

def train(self, X_train, y_train):
        for epoch in range(self.epochs):
            for x, y_true in zip(X_train, y_train):
                y_pred = self.predict(x)#予測結果
                error = y_true - y_pred #誤差の計算
                self.weights += self.learning_rate * error * x #重み更新(w←w+η(学習率)⋅error⋅x)
                self.bias += self.learning_rate * error #バイアス更新(b←b+η(学習率)⋅error)
                print("epoch:",epoch,"self.weights:",self.weights,"self.bias:",self.bias)

・与えられた訓練データ X_train(特徴量ベクトル) と y_train(ラベル値) を用いてパーセプトロンを学習します。各訓練データに対して予測を行い、誤差を計算し重みとバイアスを更新します。これをエポック数繰り返します。
・for x, y_true in zip(X_train, y_train):
X_train, y_train リストの要素を取り出す

(4)evaluate()メソッド(評価)

def evaluate(self, X_test, y_test):
        print("*** evaluate ***")
        correct = 0
        for x, y_true in zip(X_test, y_test):
            y_pred = self.predict(x)
            if y_pred == y_true:
                correct += 1
        accuracy = correct / len(y_test)
        return accuracy

・train()メソッドで算出した、weights(重みベクトル)、bias(バイアス値)を使って、予測値を算出し、ラベルの値と一致するか否かの評価を行う。

(5)main

# Example usage:
if __name__ == "__main__":
    # Example dataset
    X_train = np.array([[0.9, 0.8], [0.7, 0], [0.3, 0.5], [0, 0]])
    y_train = np.array([1, 1, 0, 0])  # Binary labels (1 or 0)
    
    # Create and train the Simple Perceptron
    perceptron = SimplePerceptron(num_features=2)
    perceptron.train(X_train, y_train)
    
    # Example evaluation
    X_test = np.array([[0.6, 0.6], [0.7, 0.3], [0.2, 0.4], [0.6, 0.2]])
    y_test = np.array([1, 1, 0, 0])
    accuracy = perceptron.evaluate(X_test, y_test)
    
    print(f"Accuracy: {accuracy}")

・if name == “main
  スクリプトが直接実行されたときにのみ、実行するための条件文
 ・np.array()でndarrayオブジェクトに変換する
 ・SimplePerceptron(num_features=2)でインスタンスを生成
 ・train()メソッドで重みベクトル、バイアス値の更新
 ・evaluate()で評価する

以上

各プログラミング言語の特徴

目次

1.言語の仕様
2.変数の型
3.プログラム構文の違い
4.命令セットとコンパイラ、プログラミング言語の関係
5.各プログラミング言語のプラットフォームへの依存性
6.各プログラミング言語による実装
7.GUI

以下の各プログラミング言語について、
 ・言語の仕様
 ・変数の型 
 ・プログラム構文の違い
 ・命令セットとコンパイラ、プログラミング言語の関係
 ・各プログラミング言語のプラットフォームへの依存性
 の各項目についてまとめました。

(比較したプログラミング言語)
 ①C、C++
 ②Java
 ③C#
 ④Python

1.言語の仕様
①CとC++
 C17  C言語の標準規格ISO/IEC 9899:2018
 C++20 C++ の標準規格 ISO/IEC 14882:2020
②java
 主にJava Community Process (JCP) によって決定される。JCP は、Java言語仕様、Java仮想マシン (JVM) 仕様、Java Standard Edition (Java SE) APIなどのJava標準の開発を監督するコミュニティ主導の組織
 仕様は、JSRs(Java Specification Requests)に記述されています
(参考) 
 List of all JSRs
 https://www.jcp.org/en/jsr/all

③C#
ECMA-334(Ecma International、以前の欧州電子計算機工業会)
ISO/IEC 23270
JIS X 3015
(参考)
 バージョン7.0
  ECMA-334:2023 (2023年12月)
  ISO/IEC 20619:2023 (2023年9月)
  リリース時期2017年3月 .NET Framework 4.7 Visual Studio2017 version 15.0
  https://ja.wikipedia.org/wiki/C_Sharp

④Python
 Pythonソフトウェア財団(英: Python Software Foundation; PSF)がサポート
 Python Enhancement Proposals (PEP): PEP は、Python の新機能、改善、または変更を提案を行うための文書
(参考)
 Python Software Foundation; PSF
 https://www.python.org/psf-landing/

2.変数の型
 変数はデータを格納する領域、型は格納できるデータの種類をコンパイラやインタープリタに知らせる役割を持ち、バイト長、領域の扱い方などが決められています。
 ここでは、オブジェクトの概念ができる前からある基本データ型とオブジェクトの概念に基づく参照型の分け方で整理します。
①C言語
(1)変数の主な型の一覧

(注1)型で使用できる範囲はコンピュータによるが32bitマシンの例を示す

    サイズはsizeof演算子で調べることができる (例)sizeof ( char )

(2)記憶クラス
 C 言語の変数はその宣言の場所と方法によって、その変数の通用範囲とメモリの割り当て方法(自動記憶域のスタック領域と静的記憶域のスタティック領域)が決まります。
 代表的な変数の性質(記憶クラス)には以下のものがあります。

①’ C++
 変数の型に関するCからの主な変更点を下記に示します。
・bool 型の変数には値 true と falseを設定できる。Cの_BOOLでは、値は通常、整数(偽の場合は 0、真の場合は非ゼロ) を使用。
・文字列型の追加。Cでは通常、文字列は文字の配列(char*char[])を使用。
・クラス型とオブジェクト指向機能のサポートにより、ユーザー定義型の作成が可能となった。
・テンプレートが導入され、任意のデータ型を処理できる関数とクラスが作成できる。
・参照型が追加され、C のポインターを使った関数の引数の操作に代わる方法を提供。
・列挙型クラスの作成ができる
(1)C++言語の変数の主な型の一

(注1)型で使用できる範囲はコンピュータによるが32bitマシンの例を示す
サイズはsizeof演算子で調べることができる (例)sizeof ( char )

(2)記憶クラス
 (C 言語と同じ)
new演算子で作成する配列、クラスのオブジェクトはヒープ領域上に割り当てられま
 す。そのオブジェクトへのポインタ自体は他の変数と同様に記憶クラスに従ってメモリに保存されます。

(参考) 
・[C++] new演算子による配列のメモリの動的確保
https://qiita.com/toshi_machine/items/c0682b7ae6c951fcadf8

・C++の基礎 : new/delete 演算子
http://www.s-cradle.com/developer/sophiaframework/tutorial/Cpp/newdelete.html

②java
(1)java言語の変数の主な型の一覧

(2)記憶クラス
 javaは、ガベージコレクションによりメモリを自動的に管理するため、Cの場合のようにメモリを明示的に割り当てたり割り当て解除したりする必要ありません。java では、new演算子でオブジェクトを作成すると、メモリがヒープ上に自動的に割り当てられます。オブジェクトが参照されなくなると、ガベージ コレクターによって自動的に割り当てが解除されます。

③C#
(1)C#言語の変数の主な型の一覧

(2)記憶クラス
 ガベージコレクションによりメモリを自動的に管理するため、C の場合のようにメモリを明示的に割り当てたり割り当て解除したりする必要はありません。C#では、new演算子でオブジェクトを作成すると、メモリがヒープ上に自動的に割り当てられます。オブジェクトが参照されなくなると、ガベージ コレクターによって自動的に割り当てが解除されます。

④Python
(1)Python言語の変数の主な型の一覧

・データ型を調べる時は、type()を使用
・Pythonは「動的型付き言語」で変数を作成するときは型の限定までは行わず、実行時にデータ型が判定される
・各データ型にはそれぞれ固有のメソッドがある

(2)記憶クラス
  メモリがヒープ上に自動的に割り当てられ、ヒープの管理は Python メモリマネージャ (Python memory manager)が行う

(参考)
Python/C API リファレンスマニュアル>>メモリ管理
https://docs.python.org/ja/3/c-api/memory.html

3.プログラム構文の違い
 変数の書き方など、ソースファイルを作成するときのスタイルについて、各プログラミング言語でまとめました。

表.各プログラミング言語とコーディングスタイルの一覧

(注1)プログラムのフォーマット
 空白 (スペース、タブ、改行) がプログラムの動作に影響を与えないものをフリーフォーマットという。特に「インデントの入れ方」と「中括弧{}の位置」については、可読性を高めるためのスタイル例を下記に示す。
・インデントは空白文字4文字を基本とする
・中括弧の位置はK&Rスタイルに準じる。
各言語のコーディングスタイルに準じるようにする。

K&Rスタイル
関数の中括弧には単一の行を与え、制御構文の中括弧には行を与えないスタイル
int main(int argc, char *argv[])
{					//単一の行を与える
	if (a < 2) {	//文に続ける
		...
	} else {		
		...
	}
	...
}

UpperCamelCase:単語の先頭を大文字にする(PascalCase)
 lowerCamelCase:先頭は小文字、以降の単語の先頭を大文字にする
  単語の先頭を大文字にするのは、単語間の区切りを表現するためで、
  最初の単語も大文字にするのがパスカルケース(アッパーキャメルケース)、
  最初の単語は小文字にするのがローワーキャメルケース
 snake_case:全て小文字としてアンダースコアで連結する

4.命令セットとコンパイラ、プログラミング言語の関係

5.各プログラミング言語のプラットフォームへの依存性
表.各プログラミング言語のプラットフォーム(動作環境のこと)への依存性

6.各プログラミング言語による実装
 ソフトウェア開発を要件定義、外部設計、および内部設計、実装(コーディング)、テストと分けたとき、内部設計までの各フェーズでは主に問題の理解、システムアーキテクチャの定義、ソフトウェアの機能、外部システム及びユーザーとのインタフェースを設計することに重点が置かれ、プログラミング言語を前提にしないことが重要と考えます。
 実装のフェーズでは、設計仕様を実際のコードに変換する必要があり、プロジェクトの要件、パフォーマンス、既存のシステムとの互換性、開発チームのスキルセットなどを考慮して適切なプログラミング言語を選択することになります。

7.GUI
 GUIについて、プログラミング言語、及びOS(windows、Linux)で使われているツールについて

(1)Qt
 Qt はクロスプラットフォーム環境で使用でき、主に C++開発で使用されます。
 C#でもQt for C# (QtSharp)を使用することでクロスプラットフォーム GUI アプリケーションを構築できます。

(2)WPF(Windows Presentation Foundation)
 WindowsでのC#開発で使用。WPFはマイクロソフト社が開発する.NET Framework3.0以降に含まれるユーザーインターフェースサブシステムのこと。

(3)GTK
 GTK+(The Gimp Toolkit+)はC言語用のGUIツールキット、Gtk# (GtkSharp) は、GNOME などの Linux デスクトップ環境で使用される一般的なオープンソース GUI ツールキットである GTK+ ツールキットの .NET バインディング。

C言語のメモリ環境について

目次
1.メモリ領域の違い
1.1 各メモリの割り当て状況を確認する
2.スタックメモリの確認方法
3.ヒープメモリの確認
(1)動的メモリのデバッグツール:Valgrind(ヴァルグリンド、GNU General Public License)
(2)プロセスのメモリマッピングの確認
(3)/procファイルシステムの使用
(4)ランタイムライブラリの関数
4.Linuxのメモリマップ設定

1.メモリ領域の違い

メモリ領域          特徴
スタック領域・関数呼び出しやローカル変数の割り当てに使われる。・後入れ先出し(LIFO)のデータ構造をもつ。
ヒープ領域・プログラマが明示的にメモリの確保(C:malloc 関数、C++:new演算子)と解放(freeなど)を行う必要がある。・動的にメモリを確保・開放するのに適したデータ構造をもつ。
静的領域          ・グローバル変数などの静的変数が置かれる。
テキスト領域・機械語に翻訳されたプログラムが格納され、この機械語の命令が1行づつ実行されることでプログラムが動く。

(参考)
・メモリの4領域
https://brain.cc.kogakuin.ac.jp/~kanamaru/lecture/MP/final/part06/node8.html

1.1 各メモリの割り当て状況を確認する
(1)テキスト領域
 テキスト領域(プログラムコードが格納される領域)は、通常、実行ファイルの読み込みアドレスを確認することで確認できます。以下は、テキスト領域の開始アドレスを取得する方法です。

#include <stdio.h>

void dummy_function() {}

int main() {
    printf("Address of main: %p\n", (void*)main);
    printf("Address of dummy_function: %p\n", (void*)dummy_function);
    return 0;
}
#実行結果
koba@koba-VirtualBox:~/work$ gcc c_memo1.c
koba@koba-VirtualBox:~/work$ ./a.out
Address of main: 0x561e356a2154
Address of dummy_function: 0x561e356a2149

ここで、”(void)main”の “(void)”はvoidは、どんなデータ型にもキャスト可能なポインタ型です。これは、特定のデータ型を指さない汎用ポインタであり、さまざまな型のデータを指すことができます。voidは型情報を持たないため、キャストを間違えると意図しない動作を引き起こす可能性があり、ポインタ演算など行うときは正しいデータ型にキャストするよう注意が必要です。

(2)静的領域
 静的領域(グローバル変数や静的変数が格納される領域)のアドレスを取得するには、静的変数やグローバル変数のアドレスを確認します。

#include <stdio.h>

int global_var = 0;
static int static_var = 0;

int main() {
    printf("Address of global_var: %p\n", (void*)&global_var);
    printf("Address of static_var: %p\n", (void*)&static_var);
    return 0;
}
koba@koba-VirtualBox:~/work$ gcc c_memo2.c
koba@koba-VirtualBox:~/work$ ./a.out
Address of global_var: 0x55914cf55014
Address of static_var: 0x55914cf55018

(3)スタック領域
 スタック領域のメモリ割り当ては、ローカル変数のアドレスを確認することで確認できます。

#include <stdio.h>

void dummy_function() {}

int main() {
    printf("Address of main: %p\n", (void*)main);
    printf("Address of dummy_function: %p\n", (void*)dummy_function);
    return 0;
}
#実行結果
koba@koba-VirtualBox:~/work$ gcc c_memo3.c
koba@koba-VirtualBox:~/work$ ./a.out
Address of local_var: 0x7ffd25c10df4

(4)ヒープ領域
 ヒープ領域のメモリ割り当ては、malloc関数で動的に割り当てたメモリのアドレスを確認することで確認できます。

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *heap_var = (int*)malloc(sizeof(int));
    if (heap_var != NULL) {
        printf("Address of heap_var: %p\n", (void*)heap_var);
        free(heap_var);
    }
    return 0;
}
#実行結果
koba@koba-VirtualBox:~/work$ gcc c_memo4.c
koba@koba-VirtualBox:~/work$ ./a.out
Address of heap_var: 0x55dc2abee2a0

各種類の領域をまとめて表示

#include <stdio.h>
#include <stdlib.h>

// グローバル変数
int global_var = 0;
static int static_var = 0;

void dummy_function() {}

int main() {
    // ローカル変数
    int local_var = 0;
    // ヒープ変数
    int *heap_var = (int*)malloc(sizeof(int));
    
    if (heap_var == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return 1;
    }

    // 各メモリ領域のアドレスを表示
    printf("Address of main: %p (Text Segment)\n", (void*)main);
    printf("Address of dummy_function: %p (Text Segment)\n", (void*)dummy_function);
    printf("Address of global_var: %p (Static Segment)\n", (void*)&global_var);
    printf("Address of static_var: %p (Static Segment)\n", (void*)&static_var);
    printf("Address of local_var: %p (Stack Segment)\n", (void*)&local_var);
    printf("Address of heap_var: %p (Heap Segment)\n", (void*)heap_var);

    // メモリ解放
    free(heap_var);

    return 0;
}
#実行結果
koba@koba-VirtualBox:~/work$ gcc c_memo5.c
koba@koba-VirtualBox:~/work$ ./a.out
Address of main: 0x5629cd6bd1d4 (Text Segment)
Address of dummy_function: 0x5629cd6bd1c9 (Text Segment)
Address of global_var: 0x5629cd6c002c (Static Segment)
Address of static_var: 0x5629cd6c0030 (Static Segment)
Address of local_var: 0x7ffd969119dc (Stack Segment)
Address of heap_var: 0x5629cd9c82a0 (Heap Segment)
Address of main     : 0x5629cd6bd1d4 (Text Segment)  テキスト領域 low address
Address of dummy_function : 0x5629cd6bd1c9 (Text Segment)   〃
Address of global_var   : 0x5629cd6c002c (Static Segment) 静的領域
Address of static_var    : 0x5629cd6c0030 (Static Segment)  〃
Address of heap_var    : 0x5629cd9c82a0 (Heap Segment)  ヒープ領域
Address of local_var: 0x7ffd969119dc (Stack Segment)     スタック領域 high address 

2.スタックメモリの確認方法
(1)GDB (GNU Debugger)の使用

#include <stdio.h>

void func2(int x) {
    int y = x + 2;
    printf("func2: y = %d\n", y);
}

void func1(int a, int b) {
    int c = a + b;
    func2(c);
}

int main() {
    int p = 5;
    int q = 10;
    func1(p, q);
    return 0;
}
#デバッカgdbで実行
// -gオプションを付けてコンパイル
koba@koba-VirtualBox:~/work$ gcc -g c_gdb.c
// gdbデバッカの起動
koba@koba-VirtualBox:~/work$ gdb ./a.out
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./a.out...
(gdb) i b
No breakpoints or watchpoints.
// ブレークポイントの設定
(gdb) break main
Breakpoint 1 at 0x11a4: file c_gdb.c, line 13.
(gdb) break func2
Breakpoint 2 at 0x1149: file c_gdb.c, line 3.
// ブレークポイントの一覧表示
(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00000000000011a4 in main at c_gdb.c:13
2       breakpoint     keep y   0x0000000000001149 in func2 at c_gdb.c:3
// 実行
(gdb) run
Starting program: /home/koba/work/a.out

Breakpoint 1, main () at c_gdb.c:13
13      int main() {
// バックトレースの表示
(gdb) bt
#0  main () at c_gdb.c:13
// プログラムの続行
(gdb) continue
Continuing.

Breakpoint 2, func2 (x=0) at c_gdb.c:3
3       void func2(int x) {
// バックトレースの表示
(gdb) bt
#0  func2 (x=0) at c_gdb.c:3
#1  0x00005555555551a1 in func1 (a=5, b=10) at c_gdb.c:10
#2  0x00005555555551cd in main () at c_gdb.c:16
(gdb) continue
Continuing.
func2: y = 17
// デバックの正常終了
[Inferior 1 (process 3392) exited normally]

(参考)
・gdbのコマンド一覧
https://www.fos.kuis.kyoto-u.ac.jp/le2soft/siryo-html/node49.html

gdbの主なコマンド

コマンド名省略型動作
run [args]rプログラムを(引数argsで)実行する
break nb nブレークポイントの設定、n行目又は関数名
deleated nnのブレークポイントを削除
info breakpointsi bブレークポイントの一覧
continuecプログラムの実行を再開
stepsステップ・イン実行
backtrace btバックトレースの表示
quitqgdbの終了

(2)プロセスリミットの確認
 Unix系OSでは、ulimitコマンドでプロセスのスタックサイズリミットを確認できます。
 ulimit -s コマンドを実行して 8192 と表示される場合、それはスタックサイズの制限をキロバイト単位で表しています。具体的には、現在のシェルセッションにおけるスタックサイズの制限が 8192 キロバイト(8 メガバイト)であることを示します。
 スタックサイズが小さいと、深い再帰や大量のローカル変数を使用するプログラムはスタックオーバーフローを引き起こす可能性があります。

 スタックサイズは次のシェルを実行することで16メガバイトにできます。
 $ ulimit -s 16384
 システム上で永続的に設定するときは、システムの設定ファイル(例えば /etc/security/limits.conf)を変更する必要があります(この設定ファイルには該当箇所はありませんでした)。

3.ヒープメモリの確認
(1)動的メモリのデバッグツール:Valgrind(ヴァルグリンド、GNU General Public License)
 Valgrindは、メモリリークや不正なメモリアクセスを検出するための動的解析ツールの一つです。Valgrindを使用してプログラムを実行すると、ヒープメモリの使用状況が詳細に報告されます。

#Valgrkoba@koba-VirtualBox:~/work$ sudo snap install valgrind --classic
valgrind 3.22.0 from Roger Light (ralight) installedindのインストール
#valgrindの実行結果
koba@koba-VirtualBox:~/work$ valgrind --leak-check=full ./a.out
==3987== Memcheck, a memory error detector
==3987== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==3987== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==3987== Command: ./a.out
==3987==
func2: y = 17
==3987==
==3987== HEAP SUMMARY:
==3987==     in use at exit: 0 bytes in 0 blocks
==3987==   total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated
==3987==
==3987== All heap blocks were freed -- no leaks are possible
==3987==
==3987== For lists of detected and suppressed errors, rerun with: -s
==3987== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

①Valgrindの概要
 Memcheck:Valgrindの一部で、メモリエラーの検出を行います。
 Copyright:Valgrindの著作権情報。
 Version:使用しているValgrindのバージョン。
 Command:実行されたコマンド(ここでは ./a.out )。
②プログラムの出力
 プログラム自体の出力でfunc2関数からの出力。
③HEAP SUMMARY(ヒープの概要)
・in use at exit:プログラム終了時に使用中のメモリの量。ここでは、0バイトが0ブロックに使用されていることを示しています。つまり、全てのメモリが解放されています。
・total heap usage:プログラムの実行中に行われたメモリ割り当て(allocs)と解放(frees)の総数、および割り当てられたメモリの総量。
・1 allocs:1回のメモリ割り当てが行われた。
・1 frees:1回のメモリ解放が行われた。
・1,024 bytes allocated:合計で1,024バイトのメモリが割り当てられた。
④メモリリークの確認
・All heap blocks were freed:すべてのヒープメモリブロックが解放されたことを示します。
・no leaks are possible:メモリリークがないことを意味します。
⑤エラーの概要
・0 errors from 0 contexts:検出されたエラーはゼロであることを示します。
・suppressed: 0 from 0:抑制されたエラーはないことを示します。

 このValgrindの出力から、次のことがわかります:
・プログラムは正常に実行され、出力も予期されたものでした。
・メモリ使用に関しては、メモリリークは発生していません。
・割り当てられたメモリはすべて適切に解放されています。
・検出されたメモリエラーはありません。
 この結果から、プログラムはメモリ管理に関して健全であると言えます。Valgrindを使用することで、メモリリークやその他のメモリエラーを検出し、修正することができます。

(参考)
・Valgrind による動的解析
https://qiita.com/QGv/items/d7857cb0fff80ecbe41d#valgrind%E3%81%A8%E3%81%AF

(2)プロセスのメモリマッピングの確認:
 Linuxでは、pmapコマンドを使ってプロセスのメモリマッピングを確認できます。ヒープ領域のサイズと位置を特定することができます。
メモリを動的に割り当てる簡単なCプログラムを用意します。

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *p = (int *)malloc(1024 * sizeof(int));  // 1024個のintを割り当て
    if (p == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return 1;
    }
    for (int i = 0; i < 1024; i++) {
        p[i] = i;
    }
    printf("Memory allocated and initialized\n");

    // プログラムを一時停止して、pmapコマンドで確認できるようにする
    printf("Press Enter to continue...");
    getchar();

    free(p);  // メモリを解放
    return 0;
}
#実行結果
// -gオプションでコンパイル
$ gcc -g -o c_mem6 c_memo6.c
// c_mem6プロセスIDを調べる
$ ps aux | grep c_memo6
//  c_mem6のプロセスID:2315でpmapコマンド
$ pmap 2315

ヒープ領域の詳細な確認
詳細なメモリ使用情報を表示するために、pmapコマンドに-xオプションを使用

#include <stdio.h>
#include <stdlib.h>

int main() {
    // 10MBのメモリを動的に割り当て
    size_t size = 10 * 1024 * 1024;
    int *p = (int *)malloc(size);
    if (p == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return 1;
    }

    // メモリを使用
    for (size_t i = 0; i < size / sizeof(int); i++) {
        p[i] = i;
    }

    printf("Memory allocated and initialized. Press Enter to continue...\n");
    getchar(); // ユーザー入力を待つ

    free(p); // メモリを解放
    return 0;
}
#実行結果
// -gオプションでコンパイル
$ gcc -g -o c_heaptest c_heaptest.c
// c_heaptestプロセスIDを調べる
$ ps aux | grep c_heaptest
//  c_heaptestのプロセスID:でpmapコマンド
$ pmap -x 2495

出力の中に、[ heap ] というラベルは表示されない場合、[ anon ] として表示される行がヒープ領域を示すことが多いとのこと。
 上記の例では、
 00007fe5066f0000 10244 10244 10244 rw— [ anon ]
アドレスにある 10MB(10244KB)の匿名メモリがヒープ領域です。

(3)/procファイルシステムの使用:
 /proc/[pid]/maps:
 Linuxでは、/procファイルシステムを使ってプロセスのメモリマップを確認できます。ヒープ領域は通常、heapとして表示されます

(4)ランタイムライブラリの関数:mallinfo:
 mallinfo関数は、ヒープメモリの統計情報を提供します。例えば、以下のコードでヒープメモリの使用状況を表示できます。

#include <malloc.h>
#include <stdio.h>

int main() {
    struct mallinfo mi = mallinfo();
    printf("Total non-mmapped bytes (arena):       %d\n", mi.arena);
    printf("Number of free chunks (ordblks):        %d\n", mi.ordblks);
    printf("Number of free fastbin blocks (smblks): %d\n", mi.smblks);
    printf("Number of mapped regions (hblks):       %d\n", mi.hblks);
    printf("Bytes in mapped regions (hblkhd):       %d\n", mi.hblkhd);
    printf("Max. total allocated space (usmblks):   %d\n", mi.usmblks);
    printf("Free bytes held in fastbins (fsmblks):  %d\n", mi.fsmblks);
    printf("Total allocated space (uordblks):       %d\n", mi.uordblks);
    printf("Total free space (fordblks):            %d\n", mi.fordblks);
    printf("Topmost releasable block (keepcost):    %d\n", mi.keepcost);
    return 0;
}

実行結果

c_heaptestを実行し、入力待ちになっている状態で、mallinfo()を実行

4.Linuxのメモリマップ設定
 Linuxでは、スタックのサイズはulimitコマンドで確認および設定できます

koba@koba-VirtualBox:~/work$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 7646
max locked memory       (kbytes, -l) 65536
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 7646
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

・標準のリンカスクリプト
 GCCがデフォルトで使用するリンカスクリプトは、インストールされたツールチェーンのディレクトリにあります。標準スクリプトの場所を見つけるには、以下のコマンドを使用して標準のリンカスクリプトを表示することができます

$ ld --verbose 

(以下、抜粋)
OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64",
	      "elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)
ENTRY(_start)
SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu"); SEARCH_DIR("=/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu64"); SEARCH_DIR("=/usr/local/lib64"); SEARCH_DIR("=/lib64"); SEARCH_DIR("=/usr/lib64"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib");
SECTIONS
{
   :

実際のプログラムをコンパイルしたとき、どのようにメモリに配置されるか確認します。

koba@koba-VirtualBox:~/work$ objdump -h my_program
my_program:     file format elf64-x86-64
Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .interp       0000001c  0000000000000318  0000000000000318  00000318  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .note.gnu.property 00000020  0000000000000338  0000000000000338  00000338  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .note.gnu.build-id 00000024  0000000000000358  0000000000000358  00000358  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .note.ABI-tag 00000020  000000000000037c  000000000000037c  0000037c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .gnu.hash     00000024  00000000000003a0  00000000000003a0  000003a0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .dynsym       000000c0  00000000000003c8  00000000000003c8  000003c8  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .dynstr       00000089  0000000000000488  0000000000000488  00000488  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .gnu.version  00000010  0000000000000512  0000000000000512  00000512  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  8 .gnu.version_r 00000020  0000000000000528  0000000000000528  00000528  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  9 .rela.dyn     000000c0  0000000000000548  0000000000000548  00000548  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 10 .rela.plt     00000030  0000000000000608  0000000000000608  00000608  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 11 .init         0000001b  0000000000001000  0000000000001000  00001000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 12 .plt          00000030  0000000000001020  0000000000001020  00001020  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 13 .plt.got      00000010  0000000000001050  0000000000001050  00001050  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 14 .plt.sec      00000020  0000000000001060  0000000000001060  00001060  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 15 .text         00000195  0000000000001080  0000000000001080  00001080  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 16 .fini         0000000d  0000000000001218  0000000000001218  00001218  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 17 .rodata       00000027  0000000000002000  0000000000002000  00002000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 18 .eh_frame_hdr 00000044  0000000000002028  0000000000002028  00002028  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 19 .eh_frame     00000108  0000000000002070  0000000000002070  00002070  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 20 .init_array   00000008  0000000000003db0  0000000000003db0  00002db0  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 21 .fini_array   00000008  0000000000003db8  0000000000003db8  00002db8  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 22 .dynamic      000001f0  0000000000003dc0  0000000000003dc0  00002dc0  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 23 .got          00000050  0000000000003fb0  0000000000003fb0  00002fb0  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 24 .data         00000014  0000000000004000  0000000000004000  00003000  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 25 .bss          00000004  0000000000004014  0000000000004014  00003014  2**0
                  ALLOC
 26 .comment      0000002b  0000000000000000  0000000000000000  00003014  2**0
                  CONTENTS, READONLY

ここで、
・VMA(Virtual Memory Address)
 VMAは、セクションが実行時に仮想メモリ空間内で占めるアドレスを示します。プログラムが実行されるときに、CPUがこのアドレスを使用してメモリにアクセスします。
例えば、コードセクション(.text)やデータセクション(.data)の実行時のアドレスはVMAで決まります。VMAは、プログラムが実行されるときに、オペレーティングシステムが各セクションをマッピングするアドレスです。
・LMA(Load Memory Address)
 LMAは、セクションが実際にメモリにロードされるアドレスを示します。特に、初期化データセクション(.data)やBSSセクション(.bss)のようなセクションで重要です。
LMAは、プログラムがディスクからメモリにロードされるときに、セクションが配置される物理メモリのアドレスです。
・VMAとLMAの違い
 VMAは実行時のアドレス、LMAはロード時のアドレスです。
 通常、コードセクション(.text)や読み取り専用データセクション(.rodata)では、VMAとLMAが同じです。しかし、初期化データセクション(.data)や未初期化データセクション(.bss)では異なることがあります。例えば、.dataセクションはROMからRAMにコピーされる場合、ROM上のアドレスがLMAであり、RAM上のアドレスがVMAです。

(参考)
・objdump – オブジェクトファイルの情報を表示する
https://linuxcommand.net/objdump/#_-h

(2)組み込みシステムやカスタムOSでのメモリマップ設定
 組み込みシステムやカスタムOSの場合、メモリマップはリンクスクリプト(例えば、GNU linkerの.ldファイル)によって設定されます。
・MEMORYセクション:使用可能なメモリ領域を定義します。
 各メモリ領域の開始アドレス(ORIGIN)とサイズ(LENGTH)を指定します。
・SECTIONSセクション:セクションの配置を定義します。
 各セクションがどのメモリ領域に配置されるかを指定します。

(参考)
・【 ulimit 】コマンド――ユーザーが使用できるリソースを制限する
https://atmarkit.itmedia.co.jp/ait/articles/1908/01/news031.html

・GNU Cを使いこなそう2017/06/30
リンカスクリプトを理解しよう
https://www.computex.co.jp/article/use_gcc_1.htm

AIモデル作成で使用するPythonのメソッド

目次
1.NumPyのメソッド
(1)np.array()
(2)np.zeros()
2.ndarray(N-dimensional arrayの略)
(1)ndarray.shape
(2)ndarray.dtype
(3)ndarray.reshape()
(4)ndarray.flatten()
(5)ndarray.sum()
(6)ndarray.mean()
(7)ndarray.max() と ndarray.min()
(8)ndarray.dot()
(9) ndarray.transpose()
3.ベクトルの類似性の評価
3.1 内積
(1)行ベクトル間の内積
(2)列ベクトル間の内積
3.2 コサイン類似度
4.コレクションデータ型とメソッド
4.1 リスト (List)
4.2 タプル (Tuple)
4.3 セット (Set)
4.4 ディクショナリ (Dictionary)
4.5 デフォルトディクショナリ (defaultdict)
5.データ構造の操作
5.1 インデックス参照(スライス)
(1)リストのスライス
(2)文字列のスライス
(3)1次元配列のスライス
(4)2次元配列のスライス
(5)3次元配列のスライス
(6)スライスオブジェクト

1.NumPyのメソッド
(1)np.array()
 多次元配列を作成します。

import numpy as np

array1 = np.array([1, 2, 3, 4, 5])
array2 = np.array([[1, 2, 3], [4, 5, 6]])
print("array1:",array1) # [1 2 3 4 5]
print("array2:",array2) #array2: [[1 2 3][4 5 6]]

(2)np.zeros()
 全ての要素がゼロで初期化された新しい配列を生成します。この関数は、配列の形状とデータ型を指定して、必要なサイズのゼロ配列を作成します。
a)1次元配列の生成

# -*- coding: utf-8 -*-
import numpy as np

# 要素数が5の1次元配列を生成
array = np.zeros(5)
print(array) # 出力: [0. 0. 0. 0. 0.]

b)2次元配列の生成

# -*- coding: utf-8 -*-
import numpy as np

# 2x3の2次元配列を生成
array = np.zeros((2, 3))
print(array)
# 出力:
# [[0. 0. 0.]
#  [0. 0. 0.]]

c)データ型を指定する
 dtype 引数を使用して配列のデータ型を指定できます。デフォルトでは、float64 型になりますが、整数型や他の型を指定することもできます。

# -*- coding: utf-8 -*-
import numpy as np

# 要素数が5の整数型(int)の1次元配列を生成
array = np.zeros(5, dtype=int)
print(array)
# 出力: [0 0 0 0 0]

(3)np.pad()
 np.pad()は、NumPy配列にパディング(追加の要素)を付けるための関数です。パディングは、配列の周りに一定の値や特定の方法で追加される要素です。これにより、配列のサイズを増やすことができます。

基本的な構文
numpy.pad(array, pad_width, mode, **kwargs)
ここで、
 ・array:配列
・pad_width:各軸ごとのパディングする量。
  pad_width=[((before_1, after_1), … (before_N, after_N))]
(before_i, after_i) は axis=i の index=0 側に before_i、index=-1 側
に after_i だけパディングすることを意味します
・mode:constant 標準、edge 端の値をコピーなど
・**kwargs:オプション

# -*- coding: utf-8 -*-
import numpy as np

array = np.array([[1, 2], [3, 4]])
#一定値でのパディング
padded_array = np.pad(array, pad_width=((1, 1), (1, 1)), mode='constant', constant_values=0)
print(padded_array)
#[[0 0 0 0]
# [0 1 2 0]
# [0 3 4 0]
# [0 0 0 0]]

#エッジの値でのパディング
padded_array = np.pad(array, pad_width=((1, 1), (1, 1)), mode='edge')
print(padded_array)
#[[1 1 2 2]
# [1 1 2 2]
# [3 3 4 4]
# [3 3 4 4]]

#反射モードでのパディング
padded_array = np.pad(array, pad_width=((1, 1), (1, 1)), mode='reflect')
print(padded_array)
#[[4 3 4 3]
# [2 1 2 1]
# [4 3 4 3]
# [2 1 2 1]]

#ラップアラウンドモードでのパディング
padded_array = np.pad(array, pad_width=((1, 1), (1, 1)), mode='wrap')
print(padded_array)
#[[4 3 4 3]
# [2 1 2 1]
# [4 3 4 3]
# [2 1 2 1]]

#オプション
#mode に応じて追加のキーワード引数が使用
#(例)'constant' モードで constant_values キーワード引数
padded_array = np.pad(array, pad_width=((1, 1), (1, 1)), mode='constant', constant_values=5)
print(padded_array)
#[[5 5 5 5]
# [5 1 2 5]
# [5 3 4 5]
# [5 5 5 5]]

2.ndarray(N-dimensional arrayの略)
 NumPyではPythonのリストではなく、効率性などからndarrayという独自のデータ構造を演算に使う。
 同じ属性や大きさを持った要素を持つ多次元配列を扱うためのクラスの1つ
 NumPy では Python の標準機能であるリスト型変数ではなく、NumPyのndarray型という特別な配列を使用し、これにより行列の掛け算、逆行列、固有値などの計算を高速に行うことができる。
ndarrayが持つ属性
 (ndarrayのインスタンス変数名).(属性)でndarrayインスタンスの情報を取得することができる。
 ndarrayクラスには、データ操作や計算に役立つ多くのメソッドが用意されています。以下に、いくつかの主要なメソッドを紹介します。
属性
ndarray.shape: 配列の形状を表すタプル。
ndarray.dtype: 配列の要素のデータ型。
ndarray.size: 配列の要素の総数。
ndarray.ndim: 配列の次元数。
メソッド
ndarray.astype(dtype): 配列の要素のデータ型を指定された型に変換した新しい配列を返す。
ndarray.copy(): 配列のコピーを作成して返す。
ndarray.flatten(): 配列を1次元にフラット化した新しい配列を返す。
ndarray.reshape(shape): 配列の形状を変更した新しい配列を返す。
ndarray.transpose(*axes): 配列の軸を並べ替えた新しい配列を返す。
ndarray.sum(axis=None): 配列要素の合計を返す。
ndarray.mean(axis=None): 配列要素の平均を返す。
ndarray.min(axis=None): 配列要素の最小値を返す。
ndarray.max(axis=None): 配列要素の最大値を返す。
ndarray.argmin(axis=None): 配列要素の最小値のインデックスを返す。
ndarray.argmax(axis=None): 配列要素の最大値のインデックスを返す。
ndarray.cumsum(axis=None): 配列要素の累積和を返す。
ndarray.cumprod(axis=None): 配列要素の累積積を返す。

(1)ndarray.shape
 NumPyの配列(ndarray)オブジェクトの形状を取得します。各次元のサイズを表すタプルが返されます。

import numpy as np

array1 = np.array([1, 2, 3, 4, 5])
array2 = np.array([[1, 2, 3], [4, 5, 6]])
print("array1:",array1) # [1 2 3 4 5]
print("array1.shape:",array1.shape)  # (5,)
print("array2:",array2) #array2: [[1 2 3][4 5 6]]
print("array2.shape:",array2.shape)  # (2,3)

(2)ndarray.dtype
 配列のデータ型を取得します。

import numpy as np

array = np.array([1, 2, 3])
print("array.dtype",array.dtype)  # int64

(3)ndarray.reshape()
 配列の形状を変更します。元のデータは保持されますが、形状が変わります。
 (元の配列)
 array = np.array([1, 2, 3, 4, 5, 6])
 (変更後の配列)
 reshaped_array [[1 2 3]
 [4 5 6]]

import numpy as np

array = np.array([1, 2, 3, 4, 5, 6])
reshaped_array = array.reshape((2, 3))
print("reshaped_array:",reshaped_array)
# [[1 2 3]
#  [4 5 6]]

(4)ndarray.flatten()
 多次元配列を1次元に変換します。


import numpy as np

array = np.array([[1, 2, 3], [4, 5, 6]])
flattened_array = array.flatten()
print("flattened_array",flattened_array)  # [1 2 3 4 5 6]

(5)ndarray.sum()
 配列の全要素の合計を計算します。軸を指定することで、その軸に沿った合計も求められます。

import numpy as np

array = np.array([[1, 2, 3], [4, 5, 6]])
total_sum = array.sum()
print("total_sum:",total_sum)  # 21

column_sum = array.sum(axis=0)
print("column_sum:",column_sum)  # [5 7 9]

row_sum = array.sum(axis=1)
print("row_sum:",row_sum)  # [ 6 15]

(6)ndarray.mean()
 配列の全要素の平均を計算します。軸を指定することで、その軸に沿った平均も求められます。


import numpy as np

array = np.array([[1, 2, 3], [4, 5, 6]])
total_mean = array.mean()
print("total_mean:",total_mean)  # 3.5

column_mean = array.mean(axis=0)
print()  # [2.5 3.5 4.5]
print("column_mean:",column_mean)  # [2.5 3.5 4.5]

row_mean = array.mean(axis=1)
print("row_mean:",row_mean)  # [2. 5.]

(7)ndarray.max() と ndarray.min()
 配列の最大値と最小値を取得します。軸を指定することで、その軸に沿った最大値と最小値も求められます。

import numpy as np

array = np.array([[1, 4, 8], [2, 5, 6]])
max_value = array.max()
print("max_value:",max_value)  # 8

min_value = array.min()
print("min_value:",min_value)  # 1

column_max = array.max(axis=0)
print("column_max:",column_max)  # [2 5 8]

row_min = array.min(axis=1)
print("row_min:",row_min)  # [1 2]

(8)ndarray.dot()
 行列のドット積(内積)を計算します。

import numpy as np

a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
dot_product = a.dot(b)
print("dot_product",dot_product)
#[[1*5+2*7 1*6+2*8]
#[3*5+4*7 3*6+4*8]] 
# 
#[[19 22]
#  [43 50]]

(9) ndarray.transpose()
 配列の転置を返します。行と列を入れ替えます。

import numpy as np

array = np.array([[1, 2, 3], [4, 5, 6]])
transposed_array = array.transpose()
print("transposed_array:",transposed_array)
# [[1 4]
#  [2 5]
#  [3 6]]

3.ベクトルの類似性の評価
3.1 内積
(1)行ベクトル間の内積
 行列aと行列bの各行ベクトル間の内積を計算する方法を示します。

# -*- coding: utf-8 -*-
import numpy as np

a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])

# 各行ベクトル間の内積を計算する
dot_products = np.dot(a, b.T)
print(dot_products)

(2)列ベクトル間の内積
 行列aと行列bの各行ベクトル間の内積を計算する方法を示します。

# -*- coding: utf-8 -*-
import numpy as np

a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])

# 各行ベクトル間の内積を計算する
dot_products = np.dot(a.T, b)
print(dot_products)

3.2 コサイン類似度

ベクトルのノルム(ユークリッドノルム)
デフォルトでは、np.linalg.norm() はユークリッドノルム(L2ノルム)を計算します。

# -*- coding: utf-8 -*-
import numpy as np

# ベクトルの定義
A = np.array([1, 2, 3])
B = np.array([4, 5, 6])

# ドット積の計算
dot_product = np.dot(A, B)

# ノルムの計算
norm_A = np.linalg.norm(A)
norm_B = np.linalg.norm(B)
print("norm_A:",norm_A)
print("norm_B:",norm_B)

# コサイン類似度の計算
cosine_similarity = dot_product / (norm_A * norm_B)
print("cosine_similarity:",cosine_similarity)

#norm_A: 3.7416573867739413
#norm_B: 8.774964387392123
#cosine_similarity: 0.9746318461970762

4.コレクションデータ型とメソッド
4.1 リスト (List)
 リストは、順序付けられた変更可能なコレクションです。

# -*- coding: utf-8 -*-
import numpy as np

# リストの作成
my_list = [1, 2, 3, 4, 5]

# 要素の追加
my_list.append(6)
print(my_list)  # 出力: [1, 2, 3, 4, 5, 6]

# 要素の挿入
my_list.insert(2, 2.5)
print(my_list)  # 出力: [1, 2, 2.5, 3, 4, 5, 6]

# 要素の削除
my_list.remove(2.5)
print(my_list)  # 出力: [1, 2, 3, 4, 5, 6]

# 要素のポップ(削除と取得)
last_element = my_list.pop()
print(last_element)  # 出力: 6
print(my_list)  # 出力: [1, 2, 3, 4, 5]

# インデックスの取得
index = my_list.index(3)
print(index)  # 出力: 2

# ソート
my_list.sort()
print(my_list)  # 出力: [1, 2, 3, 4, 5]

# リバース
my_list.reverse()
print(my_list)  # 出力: [5, 4, 3, 2, 1]

4.2 タプル (Tuple)
 タプルは、順序付けられた変更不可能なコレクションです。

# -*- coding: utf-8 -*-
import numpy as np

# タプルの作成
my_tuple = (1, 2, 3, 4, 5)

# 要素のアクセス
second_element = my_tuple[1]
print(second_element)  # 出力: 2

# タプルは変更不可能なので要素の追加や削除はできない

4.3 セット (Set)
 集合セットは重複しない要素(同じ値ではない要素、ユニークな要素)のコレクションで、和集合・差集合・積集合などの集合演算を行うことができます。

# -*- coding: utf-8 -*-
import numpy as np

# 二つのセットの定義
set1 = {1, 2, 3, 4, 5}
set2 = {4, 5, 6, 7, 8}

# 和集合の計算
union_set = set1.union(set2)
print("Union:", union_set)  # 出力: Union: {1, 2, 3, 4, 5, 6, 7, 8}

union_set = set1 | set2 #短縮記法
print("Union:", union_set)  # 出力: Union: {1, 2, 3, 4, 5, 6, 7, 8}

# 差集合の計算 (set1 - set2)
difference_set = set1.difference(set2)
print("Difference (set1 - set2):", difference_set)  # 出力: Difference (set1 - set2): {1, 2, 3}

difference_set = set1 - set2 #短縮記法
print("Difference (set1 - set2):", difference_set)  # 出力: Difference (set1 - set2): {1, 2, 3}

# 差集合の計算 (set2 - set1)
difference_set_rev = set2.difference(set1)
print("Difference (set2 - set1):", difference_set_rev)  # 出力: Difference (set2 - set1): {8, 6, 7}

difference_set_rev = set2 - set1 #短縮記法
print("Difference (set2 - set1):", difference_set_rev)  # 出力: Difference (set2 - set1): {8, 6, 7}

# 積集合の計算
intersection_set = set1.intersection(set2)
print("Intersection:", intersection_set)  # 出力: Intersection: {4, 5}

intersection_set = set1 & set2 #短縮記法
print("Intersection:", intersection_set)  # 出力: Intersection: {4, 5}

Union: {1, 2, 3, 4, 5, 6, 7, 8}
Union: {1, 2, 3, 4, 5, 6, 7, 8}
Difference (set1 – set2): {1, 2, 3}
Difference (set1 – set2): {1, 2, 3}
Difference (set2 – set1): {8, 6, 7}
Difference (set2 – set1): {8, 6, 7}
Intersection: {4, 5}
Intersection: {4, 5}

4.4 ディクショナリ (Dictionary)
 ディクショナリは、キーと値のペアのコレクションです。順序はPython 3.7以降で保持されます。

# -*- coding: utf-8 -*-
import numpy as np

# ディクショナリの作成
my_dict = {'a': 1, 'b': 2, 'c': 3}

# 要素の追加と更新
my_dict['d'] = 4
my_dict['a'] = 10
print(my_dict)  # 出力: {'a': 10, 'b': 2, 'c': 3, 'd': 4}

# 要素の削除
del my_dict['b']
print(my_dict)  # 出力: {'a': 10, 'c': 3, 'd': 4}

# キーの取得
keys = my_dict.keys()
print(keys)  # 出力: dict_keys(['a', 'c', 'd'])

# 値の取得
values = my_dict.values()
print(values)  # 出力: dict_values([10, 3, 4])

# キーと値の取得
items = my_dict.items()
print(items)  # 出力: dict_items([('a', 10), ('c', 3), ('d', 4)])

# 特定のキーの値を取得
value_a = my_dict.get('a')
print(value_a)  # 出力: 10

{‘a’: 10, ‘b’: 2, ‘c’: 3, ‘d’: 4}
{‘a’: 10, ‘c’: 3, ‘d’: 4}
dict_keys([‘a’, ‘c’, ‘d’])
dict_values([10, 3, 4])
dict_items([(‘a’, 10), (‘c’, 3), (‘d’, 4)])
10

4.5 デフォルトディクショナリ (defaultdict)
 defaultdict は、キーが存在しない場合にデフォルト値を提供するディクショナリです。
 このデフォルト値を生成するために使用されるのがファクトリ関数です。
 defaultdict を使用するには、collections モジュールをインポートし、デフォルト値を生成するためのファクトリ関数を渡してインスタンスを作成します。

 ファクトリ関数として、いくつかの組み込み関数や独自に定義した関数を使用できます。
 ・int:デフォルト値として0を返す
 ・list:デフォルト値として空のリストを返す
 ・set:デフォルト値として空のセットを返す
defaultdict(, {‘a’: 1})
defaultdict(, {‘a’: [1]})
defaultdict(, {‘a’: {1}})

# -*- coding: utf-8 -*-
import numpy as np

from collections import defaultdict

# ファクトリ関数として int を渡すと、デフォルト値が 0 になる
default_dict = defaultdict(int)

# 存在しないキーにアクセスすると、デフォルト値が返される
print(default_dict['a'])  # 出力: 0

# 値を設定する
default_dict['a'] += 1
print(default_dict)  # 出力: defaultdict(<class 'int'>, {'a': 1})

# デフォルト値として空のリストを返すファクトリ関数
list_dict = defaultdict(list)
list_dict['a'].append(1)
print(list_dict)  # 出力: defaultdict(<class 'list'>, {'a': [1]})

# デフォルト値として空のセットを返すファクトリ関数
set_dict = defaultdict(set)
set_dict['a'].add(1)
print(set_dict)  # 出力: defaultdict(<class 'set'>, {'a': {1}})

4.6 デキュー (deque)
 deque は、両端での高速な挿入と削除が可能なデータ構造です。

# -*- coding: utf-8 -*-
import numpy as np

from collections import deque

# デキューの作成
dq = deque([1, 2, 3, 4, 5])

# 要素の追加
dq.append(6)
dq.appendleft(0)
print(dq)  # 出力: deque([0, 1, 2, 3, 4, 5, 6])

# 要素の削除
dq.pop()
dq.popleft()
print(dq)  # 出力: deque([1, 2, 3, 4, 5])

deque([0, 1, 2, 3, 4, 5, 6])
deque([1, 2, 3, 4, 5])

5.データ構造の操作
5.1 インデックス参照(スライス)
 リスト、タプル、文字列、辞書、セット、Numpy配列などのデータ構造を操作するための基本的な方法です。これにより、特定の要素へのアクセス、更新、部分列の取得が可能になります。
 Pythonのスライスは、[start:stop:step] の形式で指定します。
 start: スライスの開始位置(この位置の要素を含む)
 stop: スライスの終了位置(この位置の要素を含まない)
 step: スライスのステップ

 以下に、スライスの基本的な使い方と例を示します。

(1)リストのスライス

# -*- coding: utf-8 -*-
import numpy as np

# リストのスライス
arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# インデックス2からインデックス5までの要素を取得
print(arr[2:6])  # [2, 3, 4, 5]

# インデックス0からインデックス5までの要素を2ステップずつ取得
print(arr[0:6:2])  # [0, 2, 4]

# インデックス5以降の要素を取得
print(arr[5:])  # [5, 6, 7, 8, 9]

# インデックス5以前の要素を取得
print(arr[:5])  # [0, 1, 2, 3, 4]

# すべての要素を2ステップずつ取得
print(arr[::2])  # [0, 2, 4, 6, 8]

# 逆順に取得
print(arr[::-1])  # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

[2, 3, 4, 5]
[0, 2, 4]
[5, 6, 7, 8, 9]
[0, 1, 2, 3, 4]
[0, 2, 4, 6, 8]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

(2)文字列のスライス

# -*- coding: utf-8 -*-
import numpy as np

s = "Hello, World!"

# インデックス0からインデックス5までの文字を取得
print(s[0:5])  # "Hello"

# インデックス7以降の文字を取得
print(s[7:])  # "World!"

# 逆順に取得
print(s[::-1])  # "!dlroW ,olleH"

Hello
World!
!dlroW ,olleH

(3)1次元配列のスライス

# -*- coding: utf-8 -*-
import numpy as np

arr = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

# インデックス2からインデックス5までの要素を取得
print(arr[2:6])  # [2 3 4 5]

# インデックス0からインデックス5までの要素を2ステップずつ取得
print(arr[0:6:2])  # [0 2 4]

[2 3 4 5]
[0 2 4]

(4)2次元配列のスライス

# -*- coding: utf-8 -*-
import numpy as np

arr = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

# インデックス2からインデックス5までの要素を取得
print(arr[2:6])  # [2 3 4 5]

# インデックス0からインデックス5までの要素を2ステップずつ取得
print(arr[0:6:2])  # [0 2 4]

[2 3 4 5]
[0 2 4]

(5)3次元配列のスライス

# -*- coding: utf-8 -*-
import numpy as np

# 3次元配列
tensor = np.array([
    [[ 0,  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]]
])

# 1番目と2番目の「スライス」を取得(最初の2つの「面」)
print(tensor[:2])
# [[[ 0  1  2]
#   [ 3  4  5]
#   [ 6  7  8]]
#
#  [[ 9 10 11]
#   [12 13 14]
#   [15 16 17]]]

# 各「面」の最初の2行を取得
print(tensor[:, :2])
# [[[ 0  1  2]
#   [ 3  4  5]]
#
#  [[ 9 10 11]
#   [12 13 14]]
#
#  [[18 19 20]
#   [21 22 23]]]

# 各「面」のすべての行と最初の2列を取得
print(tensor[:, :, :2])
# [[[ 0  1]
#   [ 3  4]
#   [ 6  7]]
#
#  [[ 9 10]
#   [12 13]
#   [15 16]]
#
#  [[18 19]
#   [21 22]
#   [24 25]]]

(6)スライスオブジェクト
 スライスオブジェクトはPythonのslice型で、スライスの開始、終了、およびステップを格納します。

# -*- coding: utf-8 -*-
import numpy as np

arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
slice_obj = slice(2, 6)
print(arr[slice_obj])  # [2, 3, 4, 5]

slice_obj = slice(0, 6, 2)
print(arr[slice_obj])  # [0, 2, 4]

[2, 3, 4, 5]
[0, 2, 4]

トランスフォーマー(Transformer)

1.概要
トランスフォーマーの元となるアーキテクチャは、以下の論文で発表されました:
 Title: Attention Is All You Need
 Authors: Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N. Gomez,
 Łukasz Kaiser, and Illia Polosukhin
 Published in: NeurIPS 2017 (Conference on Neural Information Processing Systems)
 ChatGPTなどの自然言語処理、画像生成などモデルとして使用されて、主な特徴を以下に示します。

(1)自己注意機構(Self-Attention Mechanism)
 自己注意機構は、入力シーケンス(文章など)の各要素(トークン)のシーケンス全体の他の要素に対する重要性を評価する方法です。これにより、文中の単語同士の関係を捉えることができるようになり、文章の生成において、次に続く単語の選択するときの重みづけ(文脈によって動的に変化する)ができるようになります。

(2)位置エンコーディング(Positional Encoding)
 トランスフォーマーはリカレント(再帰的)な構造を持たないため、入力シーケンスの順序情報を明示的にモデルに提供する必要があります。位置エンコーディングはこの問題を解決するために使用されます。

(3)エンコーダ・デコーダ構造(Encoder-Decoder Structure)
 トランスフォーマーモデルはエンコーダとデコーダの2つの主要部分から構成されています。エンコーダーは、入力された文章を固定長のベクトル(文脈ベクトル)に変換する役割をもちます。デコーダはエンコーダーが生成した文脈ベクトルを元に推論した結果(出力)を生成する役割をもちます。

(4)並列計算の効率性
 トランスフォーマーは並列計算に適しており、従来のRNN(Recurrent Neural Networks)やLSTM(Long Short-Term Memory)に比べてトレーニング速度が速いという利点があります。これは特に大規模なデータセットでの学習においてメリットとなります。

(5)スケーラビリティ
 トランスフォーマーは非常に大規模なモデルにスケールさせることができ、多くのパラメータを持つことで高い表現力を持っています。例えば、BERTやGPTなどの大規模なトランスフォーマーモデルは数億から数十億のパラメータを持ち、非常に高い性能を発揮します。

(6)多用途性
 トランスフォーマーは機械翻訳、テキスト生成、要約、質問応答、画像認識など、さまざまなタスクに適用可能です。これは、トランスフォーマーがシーケンスの関係性をうまく捉えることができるためです。

2.トランスフォーマーの構成
 以上の特徴をモデルの各部分について考察していきます。
 大まかにトランスフォーマーはエンコーダーとデコーダに分けられます。エンコーダで入力データを埋め込みベクトル空間に変換します。

arXiv:170 2 Aug 2023「Attention Is All You Need」から引用

文章の生成を行う場合を例に、エンコーダーとデコーダの働きについて見ていきます。

2.1 エンコーダーの働き
 Inputsで文章を入力し、入力済みの文章に続く単語(トークン)の予測の基となるベクトルを作成するまでがエンコーダーの機能となります。
 Inputsから順番に見ていきます。

①InputsとinputEmbedding
 文章を構成する全ての単語を事前学習したEmbededdingのデータで数値ベクトルに変換します。これを埋め込みベクトルといいます。ここでは単語から一意的にEmbededdingのデータに置き換えるだけで文脈は考慮されていません。

②PositionalEncoding
 ①のinputの文章の各単語をEmbeddingベクトル空間に置き換えたベクトルデータに対して、文脈上の単語の位置情報を加えます。
 (位置エンコーディングの計算方法)
 位置エンコーディングは、サイン関数とコサイン関数を使用して計算されます。具体的には、次のような形で各次元ごとに計算されます:

この位置エンコーディングにおける各次元は、異なる周期や位相を持つ関数の値を表し、第一次元は高周波で周期的な情報を担い、第二次元は低周波で周期的な情報を担うといった役割をします。

③Multi-HeadAttention

(a)入力ベクトルV、K、Qの準備
  入力シーケンスを受け取り、Query(Q)、Key(K)、Value(V)を生成します

(b)Linear変換
 Linear変換は、Q、K、Vそれぞれに対して線形変換(入力ベクトルに重み行列を掛け合わせ、バイアスベクトルを加える操作)を適用し、新しいQ’、K’、V’を得ます。線形変換は以下の式で表されます

Multi-Head Attentionでは、以下のようにQuery、Key、Valueそれぞれに異なる重み行列を適用します。

(c)ScaledDot-ProductAttention
 ScaledDot-ProductAttentionは、Queryベクトル(入力シーケンスで注意を払う単語)と入力シーケンスの他の要素のKeyベクトル、Valueベクトルから下記の計算により、スコアを求めます。

Scaled Dot-Product Attentionの出力は、入力のValue行列Vと同じ形状を持ちます。
 具体的には、出力の形状はVの形状(バッチサイズ、シーケンス長、埋め込み次元)と一致します。

 Multi-Head Attention機構においてはこれらの処理はヘッドが複数あり、各ヘッドは異なる部分空間(埋め込みベクトルの異なる次元)で独自のScaled Dot-Product Attentionを計算し、その結果を結合して最終的な出力を生成します。例として、埋め込み次元が8でヘッド数が4のときは各ヘッドの次元は8/4=2となります。

(c)Concat
 各ヘッドの出力を結合し、最終的な出力を得ます。

④Add&Norm
(a)Add(残差接続)
 勾配消失問題を軽減し、トレーニングの安定性を高めるため入力(Query)をその層の出力に直接足し合わせます。
 Output=Layer Output+Input(ここではQuery)

(b)Norm(正規化) 
 正規化は各層の出力のスケールを調整しトレーニングの安定性と収束速度を向上させます。

⑤Feedward
 フィードフォワードニューラルネットワーク(Feedforward Neural Network、FFN)は、トランスフォーマーモデルの各エンコーダーおよびデコーダーブロック内で、トークンごとの情報を変換し、特徴抽出や複雑なパターンを学習するためのものです。自己注意機構が各トークン間の相関を捉えるのに対して、FFNは各トークンの特徴を独立に変換し、より高度な特徴を学習する役割を担います。具体的には最初の線形変換で高次元に変換し、非線形活性化関数を適用し、最後に線形変換で元の次元に戻します。

⑥Add&Norm
(a)Add(残差接続)
 勾配消失問題を軽減し、トレーニングの安定性を高めるため入力(Query)をその層の出力に直接足し合わせます。
 Output=Layer Output+Input(ここではQuery)

(b)Norm(正規化) 
 正規化は各層の出力のスケールを調整しトレーニングの安定性と収束速度を向上させます。

2.2 デコーダの働き
 トランスフォーマーモデルのデコーダは、エンコーダから得られたコンテキスト情報をもとに、逐次的に出力シーケンスを生成する役割を担います
①OutputsとOutputsEmbedding
 現在までに生成された部分シーケンスのトークンをベクトル表現に変換したものです。通常、これらのベクトルはEmbedding層を通じて得られます。
 Embedding層は、語彙内の各トークンを固定次元の連続値ベクトルにマッピングします。

②PositionalEncoding
 ①のoutputの文章の各単語をEmbeddingベクトル空間に置き換えたベクトルデータに対して、文脈上の単語の位置情報を加えます。
 位置ベクトルの計算方法は2.1項と同じです。

③Masked Multi-Head Attention
 Masked Multi-Head Attention (Masked MHA) は、トランスフォーマーのデコーダで使用される自己注意機構の一部です。マスク付き自己注意機構は、未来の情報を見ないようにするためにマスクを適用します。

④Multi-Head Attention
 デコーダのMulti-Head Attentionの入力にエンコーダからの出力が入っている理由は、エンコーダが入力シーケンスの各トークンの特徴を含むコンテキスト情報を提供するためです。これにより、デコーダは現在生成しているトークンが入力シーケンスのどの部分と関係しているかを学習できます。

⑤Linear
 デコーダの出力は、通常、トークンの埋め込み次元を持つベクトルです。このベクトルを、単語サイズの次元に変換する必要があります。線形層は、デコーダの出力ベクトルに対して線形変換を適用し、単語サイズに対応するスコアベクトルを生成します。

⑥softmax
 線形層からの出力のスコアにソフトマックス関数を適用することで確率分布に変換します。ソフトマックス関数は、各スコアを指数関数的に変換し、全体のスコアの合計が1になるように正規化します。

3.サンプル
 トランスフォーマーのモデルを簡略化したエンコーダデコーダモデル(下記のブロック図)について実装し、確認を行います。

import numpy as np

# 単語の初期化
vocab = ["りんご", "赤い", "みかん", "黄色い", "の", "色", "は"]
inv_vocab = {i: word for i, word in enumerate(vocab)}

# 手動で設定されたエンベディングベクトル
embeddings = {
    "りんご": np.array([0.8, 0.0, 0.8, 0.2]),
    "赤い": np.array([0.8, 0.5, 0.8, 0.2]),
    "みかん": np.array([0.1, 0.9, 0.1, 0.8]),
    "黄色い": np.array([0.3, 0.8, 0.3, 0.8]),
    "の": np.array([0.2, 0.3, 0.1, 0.2]),
    "色": np.array([0.3, 0.2, 0.2, 0.3]),
    "は": np.array([0.1, 0.2, 0.2, 0.2])
}

# エンコーダの入力
encoder_input = ["りんご", "の", "色","は"]

# デコーダの入力
decoder_input = encoder_input

# 単語を埋め込みベクトルに変換
def embed_sequence(sequence):
    return np.array([embeddings[word] for word in sequence])

# 位置エンコーディングの生成
def positional_encoding(sequence_length, d_model):
    position = np.arange(sequence_length)[:, np.newaxis]
    div_term = np.exp(np.arange(0, d_model, 2) * -(np.log(10000.0) / d_model))
    pe = np.zeros((sequence_length, d_model))
    pe[:, 0::2] = np.sin(position * div_term)
    pe[:, 1::2] = np.cos(position * div_term)
    return pe

# マルチヘッドアテンション
def multi_head_attention(Q, K, V, num_heads):
    def attention(Q, K, V):
        d_k = Q.shape[-1]
        scores = np.dot(Q, K.T) / np.sqrt(d_k)
        weights = softmax(scores)
        print("weights:",weights,"V:",V)
        return np.dot(weights, V)
    
    d_model = Q.shape[-1]
    head_dim = d_model // num_heads
    
    heads = []
    for i in range(num_heads):
        Q_i = Q[:, i*head_dim:(i+1)*head_dim]
        print("i:",i,"Q_i:",Q_i)
        K_i = K[:, i*head_dim:(i+1)*head_dim]
        V_i = V[:, i*head_dim:(i+1)*head_dim]
        heads.append(attention(Q_i, K_i, V_i))
        print("④i:",i,"heads:",heads)
    return np.concatenate(heads, axis=-1) #ヘッドの出力を次元(埋め込みベクトルの次元)に沿って結合

# ソフトマックス関数
def softmax(x):
    exp_x = np.exp(x - np.max(x, axis=-1, keepdims=True))
    return exp_x / np.sum(exp_x, axis=-1, keepdims=True)

# エンコーダ
def encoder(encoder_input_emb, num_heads):
    print("# エンコーダのセルフアテンション")
    encoder_self_attention =multi_head_attention(encoder_input_emb, encoder_input_emb, encoder_input_emb, num_heads)
    print("encoder_self_attention:",encoder_self_attention)
    return encoder_self_attention

# デコーダ
def decoder(decoder_input_emb, encoder_output, num_heads):
    print("# デコーダのセルフアテンション")
    decoder_self_attention = multi_head_attention(decoder_input_emb, decoder_input_emb, decoder_input_emb, num_heads)
    print("decoder_self_attention:",decoder_self_attention)
    
    print("# エンコーダ-デコーダアテンション")
    encoder_decoder_attention = multi_head_attention(decoder_self_attention, encoder_output, encoder_output, num_heads)
    print("encoder_decoder_attention:",encoder_decoder_attention)
    return encoder_decoder_attention

# 埋め込みに変換
encoder_input_emb = embed_sequence(encoder_input)
print("①encoder_input_emb:",encoder_input_emb)
decoder_input_emb = embed_sequence(decoder_input)
print("①'decoder_input_emb:",decoder_input_emb)

# 位置エンコーディングの追加
## 位置エンコーディングの生成
encoder_pos_enc = positional_encoding(len(encoder_input), encoder_input_emb.shape[1])
print("②encoder_pos_enc:",encoder_pos_enc)
decoder_pos_enc = positional_encoding(len(decoder_input), decoder_input_emb.shape[1])
print("②'decoder_pos_enc:",decoder_pos_enc)
## 埋め込みベクトルに位置ベクトルを加算
encoder_input_emb += encoder_pos_enc
print("③encoder_input_emb:",encoder_input_emb)
decoder_input_emb += decoder_pos_enc
print("③'decoder_input_emb:",decoder_input_emb)

# エンコーダとデコーダの出力
num_heads = 2
encoder_output = encoder(encoder_input_emb, num_heads)
print("⑤encoder_output:",encoder_output)
decoder_output = decoder(decoder_input_emb, encoder_output, num_heads)
print("⑥decoder_output:",decoder_output)
# 次の単語の予測
def predict_next_word(decoder_output, embeddings, inv_vocab):
    last_output = decoder_output[-1]  # デコーダの出力の最後のベクトル
    print("last_output:",last_output)
    similarities = np.dot(np.array(list(embeddings.values())), last_output)
    print("similarities:",similarities)
    return inv_vocab[np.argmax(similarities)]

next_word = predict_next_word(decoder_output, embeddings, inv_vocab)
print("次に来る単語は:", next_word)

・前提条件
 単語のエンベディングベクトルは回答が合うように手動で設定した
 embeddings = {
“りんご”: np.array([0.8, 0.0, 0.8, 0.2]),
“赤い”: np.array([0.8, 0.5, 0.8, 0.2]),
“みかん”: np.array([0.1, 0.9, 0.1, 0.8]),
“黄色い”: np.array([0.3, 0.8, 0.3, 0.8]),
“の”: np.array([0.2, 0.3, 0.1, 0.2]),
“色”: np.array([0.3, 0.2, 0.2, 0.3]),
“は”: np.array([0.1, 0.2, 0.2, 0.2])
}

・実行環境
Windows 10 Pro
Python 3.12.4
numpy 1.26.4

・実行結果

similarities: [1.23933206 1.39950039 1.39310835 1.60962455 0.5542311 0.76903867
0.46902724]
次に来る単語は: 黄色い #単語の4番目

(パラメータ数)
(計算例)
ボキャブラリサイズ:7
エンベデイングサイズ:4
マルチヘッドアッテンションのヘッド数:2
エンコーダのマルチヘッドアッテンション層:1
デコーダのマルチヘッドアッテンション層:2
のときのパラメータ数を求める

1.エンベディング層のパラメータ数

2.マルチヘッドアテンション層のパラメータ数

3.フィードフォワードネットワーク層のパラメータ数(サンプルでは省略)

4.エンコーダ層のパラメータ数
 エンコーダ層はマルチヘッドアテンション層1つとフィードフォワード層1つ(仮定)を持ちます。

次に処理の順番に各ベクトルのパラメータの変化を示します。

c言語 関数ポインタ

 C言語では、関数ポインタは関数のアドレスを格納する変数です。これにより、プログラムは関数を間接的に呼び出すことができます。関数ポインターは、コールバック関数の実装、動的ディスパッチの実装、ポリモーフィズムのメカニズムの提供など、さまざまな方法で使用できます。
 この例では、2 つの整数を取り、整数を返す関数”add”を指す関数ポインタを宣言します。次に、関数”add”のアドレスをポインターに割り当てます。関数ポインタに2 つの整数を引数として渡すことにより、ポインターを介して関数”add”を間接的に呼び出します。

サンプル
fpointer_sample1.c

#include <stdio.h>

// 2 つの整数を引数として、その合計を返す関数
int add(int a, int b) {
    return a + b;
}

int main() {
    // 関数ポインタの宣言
    int (*func_ptr)(int, int);
    
    // 「add」関数のアドレスをポインタ変数に代入
    func_ptr = &add;
    
    // ポインタを介して関数を間接的に呼び出す
    int result = (*func_ptr)(2, 3);
    
    printf("The result is %d\n", result);
    
    return 0;
}

実行結果

end

c言語 配列と線形リスト

1.配列とは
 配列は、同じデータ型の要素を番号(添字:インデックス)順に並べたものを表します。
 添字は、C言語を含む多くの言語は0スタートとなります。
 (例)要素数100のときの1次元配列a、a[0]・・・a[99]

配列のサンプル(1)
list_sample1.c

#include <stdio.h>

#define MAX_SIZE 100

int list[MAX_SIZE];//list[0]...list[99]
int size = 0;

void insert(int value) {
    if(size < MAX_SIZE) {
        list[size++] = value;
    	printf("list[%d]:%d\n ", size-1,list[size-1]);
    }
}

void remov(int index) {
    if(index < size) {
        for(int i=index; i<size-1; i++) {
            list[i] = list[i+1];
        }
        size--;
    }
}

int get(int index) {
    if(index < size) {
        return list[index];
    }
    return -1; // return -1 to indicate an error or invalid index
}

int search(int value) {
    for(int i=0; i<size; i++) {
        if(list[i] == value) {
            return i;
        }
    }
    return -1; // return -1 to indicate not found
}

int main(void){
	insert(10);//list[0]:10
	insert(20);//list[1]:20
	insert(30);//list[2]:30
	printf("%d\n ", get(1));//20
	printf("%d\n ", search(20));//1
	remov(1);//list[0]:10,list[1]:30
	printf("%d\n ", get(0));//10
	printf("%d\n ", get(1));//30
	printf("%d\n ", get(2));//-1
	return 0;
}

例では、配列の要素数MAX_SIZE変数を定義します。insert関数はリストの末尾に新しい要素を挿入し、remov関数は特定のインデックスの要素を削除します。get関数は特定のインデックスの要素を返し、search関数は特定の値を持つ要素を検索してそのインデックスを返します。
 ここでは要素はint型としていますが、他のデータ型の要素を格納する必要がある場合は、配列listのデータ型と関数のパラメーターと戻り値を変更する必要があります。

実行結果

配列のサンプル(2)
list_sample2.c

#include <stdio.h>

#define MAX_SIZE 100 // maximum size of the list

struct List {
    int items[MAX_SIZE];
    int size;
};

// initialize the list
void initList(struct List *list) {
    list->size = 0;
}

// add an item to the list
void addItem(struct List *list, int item) {
    if (list->size == MAX_SIZE) {
        printf("List is full\n");
        return;
    }
    list->items[list->size++] = item;
}

// remove an item from the list
void removeItem(struct List *list, int index) {
    if (index < 0 || index >= list->size) {
        printf("Invalid index\n");
        return;
    }
    for (int i = index; i < list->size - 1; i++) {
        list->items[i] = list->items[i+1];
    }
    list->size--;
}

// get an item from the list
int getItem(struct List *list, int index) {
    if (index < 0 || index >= list->size) {
        printf("Invalid index\n");
        return -1;
    }
    return list->items[index];
}

// print the list
void printList(struct List *list) {
    for (int i = 0; i < list->size; i++) {
        printf("%d ", list->items[i]);
    }
    printf("\n");
}

int main(void){
	struct List list;
	initList(&list);
	addItem(&list, 10);
	addItem(&list, 20);
	addItem(&list, 30);
	printList(&list); // prints "10 20 30\n"
	removeItem(&list, 1);
	printList(&list); // prints "10 30\n"
	printf("%d\n", getItem(&list, 1)); // prints "30\n"
    return 0;
}

 この例では構造体を作成して、構造体を呼び出して配列を操作します。

実行結果

2.線形リスト
 線形リストは、データと「次のデータを指し示すポインタ(場所)」が入ったノードと呼ばれる要素がポインタでつながっているデータ構造で、隣接するデータ同士をポインタで連結して表現します。

線形リストのサンプル
list_sample3.c

#include <stdio.h>
#include <stdlib.h>

//ノード
struct Node {
    int data;
    struct Node *next;
};

struct Node *head = NULL;

void insert(int value) {
    struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));
    newNode->data = value;
    newNode->next = NULL;

    if(head == NULL) {
        head = newNode;
    } else {
        struct Node *current = head;	//headのノードから最後のノードを順番に調べて
        while(current->next != NULL) {	//その次に新しいノードを追加する
            current = current->next;
        }
        current->next = newNode;
    }
}

void display() {
    if(head == NULL) {
        printf("List is empty.\n");
    } else {
        struct Node *current = head;
        while(current != NULL) {	//次のノードがNULLポインタになるまで繰り返す
        	printf("%d ", current->data);
            current = current->next;
        }
        printf("\n");
    }
}

int main() {
    insert(5);
    insert(10);
    insert(15);
    display();//5 10 15
    return 0;
}

データとリスト内の次のノードへのポインターを保持するために、Nodeと呼ばれる構造体を定義します。関数insertはリストの最後に新しいノードを追加し、関数displayは各ノードのデータを出力します。
 main関数では、3 つの値を挿入し、結果のリストを表示します。

実行結果

end

c言語 スレッド間の排他制御

1.概要
 複数のスレッドで共有リソースにアクセスするとき、同時に1つのスレッドだけが共有リソースにアクセスできるようにするために、ミューテックス (英: mutex)が使用されます。

2.ミューテックスを使った排他制御
 下記のサンプルでは、2 つのスレッドを作成し、実行するスレッド関数としてthread_functionを渡します。thread_function内では、最初に pthread_mutex_lockを使用してミューテックスをロックし、一度に 1 つのスレッドだけが共有リソースにアクセスできるようにします。共有リソースにアクセスした後、pthread_mutex_unlockを使用してミューテックスのロックを解除します。メインでは、pthread_joinを使用して両方のスレッドが終了するのを待ち、最後に pthread_mutex_destroy を使用してミューテックスを破棄します。
 ミューテックスを正しく使用しないと、デッドロックが発生する可能性があります。デッドロックは、2 つ以上のスレッドがブロックされ、互いがミューテックスを解放するのを待っているときに発生します。デッドロックを回避するには、複数のミューテックスにアクセスするときは常に同じロック順序を使用します。

3.サンプル
thread_mutex1.c

#include <pthread.h>
#include <stdio.h>

pthread_mutex_t mutex;

void* thread_function(void* arg) {
    // 共有リソースにアクセスする前にロックする
    pthread_mutex_lock(&mutex);
    
    // 共有リソースにアクセス
    printf("Thread %ld is accessing the shared resource.\n", pthread_self());
    
    // ロックを解除する
    pthread_mutex_unlock(&mutex);
    return NULL;
}

int main() {
    pthread_t thread1, thread2;
    
    // Initialize the mutex
    pthread_mutex_init(&mutex, NULL);
    
    // Create two threads
    pthread_create(&thread1, NULL, thread_function, NULL);
    pthread_create(&thread2, NULL, thread_function, NULL);
    
    // Wait for the threads to finish
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    
    // Destroy the mutex
    pthread_mutex_destroy(&mutex);
    return 0;
}

(※1)pthread_create()
新しいスレッドを生成する
(書式)
#include <pthread.h>
int pthread_create(pthread_t * thread,
pthread_attr_t * attr,
void * (*start_routine)(void *),void * arg);
引数:
thread:スレッド管理用
attr:スレッド属性、NULLのときはデフォルト
(*start_routine)(void *):スレッドから呼び出される関数へのポインタ
arg:start_routine()の引数で渡すデータのポインタ
戻り値:
成功すると新しく作成したスレッドの識別子が引数threadの指す領域へ格納され、0 が返る。エラーの場合、非 0 のエラーコードが返る。

(※2)pthread_join()
別のスレッドの終了を待つ
(書式)
#include <pthread.h>
int pthread_join(pthread_t th, void **thread_return);
引数:
th:待機するスレッドの指定
**thread_return:スレッドの戻り値を格納する領域
戻り値:
成功すると、thの返り値がthread_returnで指し示された領域に格納され、0が返る。エラーの場合、非 0 のエラーコードが返る。

(※3)pthread_self()
呼び出したスレッドのIDを取得する
(書式)
#include <pthread.h>
pthread_t pthread_self(void);


コンパイルするとき、マルチスレッドをサポートするために、pthreads ライブラリーを使用するようにコンパイラーに指示します。
$ gcc -pthread thread_mutex1.c

実行結果

end