Transformerの構造深堀り

# 目的:point_right_tone1:

　生成AIによるLLM（大規模言語モデル）がここまでうまくいっているということは当然意味があると考えるべきで、それを可能な限り構造的に分析したいと考えました。さらに可能であれば現在のTransformerの限界も解析して、きちんとその理由を明確にしたいところです。
　そういうわけで、現在のTransformerベースの生成AIのシステム構造の調査の覚書を掲載します。いろいろなサイトで調査されていますが、進歩が激しい分野なのでアップデートと整理が必要だと考えました。基礎理論から実装まで、現在の技術を可能な限り深堀りします。なお、Webサイトやソースコードだけではなく、ChatGPT,Geminiなど様々なAIからも調査していますが、出典が確認できないものはできるだけ使用AIを明記するようにします。

# 基本構造:classical_building:

## 学習と推論

　生成AIのTransformerは、学習フェイズで学習が行われます。これは膨大な学習データと専用の学習用NPUコンピュータを用いて行います。
　推論フェイズでは、質問を行うと、学習されたTransformerを用いて返答が返ってきます。

```mermaid
graph BT
subgraph 推論
  C(質問) --> E[Transformer]
  E --> B(回答)
end
subgraph 学習
  A(学習データ) --> D[Transformer]
end
  style A fill:#c09090
  style B fill:#c09090
  style C fill:#c09090
  style D fill:#90ee90
  style E fill:#90ee90
```


## 標準的なTransformerの構成
　これはWikipediaにある図そのものの引用です。この図の記述に従って説明します。

![500px-Full_GPT_architecture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/461750/ed03321a-2911-4d30-80ea-cbafc97f30cc.png)


## 単語トークン

　Transformerを通過するデータは、トークンと呼ばれる単語を表すN次元のベクトルです。ベクトルの要素は基本的は32ビット浮動小数点の値です。ただし、GPUなどと異なり倍精度浮動小数点のような値は不要で、８ビット整数や２ビット整数程度でも良い用途が増えてきているようです。１つのトークンは、基本的に１つの単語を意味します。Transformerを通過して最終的に出力されるのが、次に来ると予測した単語です。現在のLLMではトークンベクトルのモデル次元$d_{model}$は1000から10000以上にもなるようです。

## 学習と推論

　Transformerは、テキスト入力に対して学習結果を用いてテキストで返答させるのが基本です。Transformerの内部は、それまでの入力単語列に対して次に出てくるはずの単語を推測して出力するだけのものです。ただし、この単語推測のためのロジックが学習によって極めて複雑化していると考えられます。言い換えると、単純に直前の文章の内容だけを使って予測するのではなく、内部状態や学習結果を用いて出力することで、人間と同等の応答が出来てしまうというものでしょう。たとえば、入力テキストは、トークンという値に変換されて以下のようになります。本来はトークンはN次元の値ですが簡単に一次元の数値で表します。

input_ids  = [101, 2002, 2038, 1037, 5095, 102]  # 文: He has a dog.

　このテキストを「予測する単語の直前まで」入力した結果、Transformerの各層を通過して以下のテキストが順に出力されることを学習します。学習するテキストは、入力テキストと同じものをトークンを1つずらした形で使用します。


target_ids = [2002, 2038, 1037, 5095, 102, 0]    


　生成AIの学習では、このテキストの入力の量を膨大に増やしてきました。その結果、想像を超えて生成AIの問題解決力が増大する結果が出てきたのです。

## Tokenizer

　N次元空間に単語を配置します。単語同士の意味の近さがベクトル空間の内積に相当するようにトークンが設計されているようです。この内積が大きければ単語間の意味が近いと見なされます。

　念のため内積とは、それぞれのベクトルの同じ次元の値同士の積を計算して、次元ごとの計算結果をすべて加算することです。この結果がベクトル同士の一種の近さを示すという特徴があります。ただし、距離とは違い、ベクトル同士の向きが同じに近ければ大きく、ベクトル同士が直交に近づけば小さくなります。

　これに単語間の時間的な近さを示すPositional Encodingが加わります。文章の中で離れた単語トークン同士は、内積が小さくなるように設計されています。posがトークン自体の位置、iがトークンベクトル内の位置、トークンベクトルのサイズが$d_{model}$.

$$PE(pos,2i)=sin(\frac{pos}{10000^{2i/d_{model}}})$$

$$PE(pos,2i+1)=cos(\frac{pos}{10000^{2i/d_{model}}})$$

　このトークンを用いて、AttentionではQとKの内積が実行されて、QとKとの間の意味的、位置的な近さが得られると考えます。単語からトークンへのエンコード（変換）方法は、最適なプリセットがいくつか作成されて公開されています。さらに、フィードバックによってエンコードの方法自体が修正されることもあるようです。

## Attentionとは

　Transformerの内部にある重要な機能です。同じトークンを３つに分けて、Q(Query)、K(Key)、V(Value)を用いて、沢山のK,Vトークンのペアの中から注目すべきVトークンを選択します。この注目対象を選択するのがKとQの内積です。Kに相当するVは、KQの内積で掛け算されてより強く伝搬されます。この構造は、名前から分かるようにQueryを用いてKeyからValueを検索するデータベースを連想させるものです。

 
```mermaid
graph BT
  A[Q] --> D(matmul)
  B[K_n] --> D
  D --> E(softmax)
  E --> F(matmul)
  C[V_n] --> F
  F --> G[Output]

  style A fill:#ee9090
  style B fill:#ee9090
  style C fill:#ee9090
  style D fill:#add8e6
  style E fill:#90ee90
  style F fill:#add8e6
  style G fill:#ee9090
```

　最初のmatmulでは、Queryのトークンと、履歴にある文章の中の全てのKトークンと、相互の関係をすべて内積計算します。その結果、Attentionの計算量は、Kトークンの長さ×トークンのモデル次元d_modelとなります。d_modelの計算式は以下の通りです。

$$\text{Scores}=\frac{QK\top}{\sqrt{d_{model}}}$$

　そして、履歴内の全てのVトークンを次のmatmulに入力して、Vトークンに相当するScoreで積算して、結果をすべて加算します。そして、最終的な出力Outputを得ます。 

$$Output=\sum_n {\text{Scores}} V_n $$

　こうして、QとKとの意味が近い場合に、相当するVが大きく加算されて出力されることになります。

　Attentionはさらに以下のような種類があります。


- Cross-Attention

	初期のTransformerの論文で用いられていた方法です。Encoder側のTransformerの出力を、Decoder側の別のTransformerのQに入力させて、２つの文章の間の関連を導き出します。主目的は言語間の翻訳です。

- Self-Attention

	文章の生成や文章解析。K,Q,Vの入力はすべて同一のトークン列を用います。これが高度な会話の応答を作り出します。

　K,Q,Vに同じトークンを使用しても意味がないように思えましたが、実際にAttentionで計算するのは現在使用している文章全体のトークンの組み合わせとなります。たとえば、トークン数L=512、トークンのモデル次元d_model=768として

$$	L \times L = 512 \times 512  = 262,144$$

 つまり、Attentionで行うベクトル内積の計算数は26万回以上になります。さらに、それぞれの計算が768次元のベクトル内積になります。K,Q,Vのトークンの値は、Attentionの入力前にLinear（線形変換）を通過して自動的に別のトークンに変換されます。変換方法も学習対象のパラメータです。

## Multi head attention

　トークンベクトルを分割して部分的に内積をとって、その部分ごとにsoftmaxの処理を行う方が経験的に有利と知られています。たとえば、トークン数L=512　モデル次元d_model=768 Head=12として、マルチヘッドを適用した内積の計算数は以下の式になります。

$$	L \times L \times h  = 512\times 512 \times 12 = 3,145,728 $$
	
内積の次元数はHeadで分割されて以下の通りになります。

$$	768 \div 12 = 64 $$

(ChatGPT)複数のAttention Headがあれば、それぞれが「異なる視点」「異なる粒度の関係」を捉えることができます。あるHeadは文法関係、別のHeadは語彙的関係など。

## FFN(FeedForwardNetwork)

　Attentionよりもむしろこちらの方が、文章の意味の学習を行う本体と言って良いです。
　Attentionで「注目」した結果に対する学習で、トークン内部の意味の学習を行います。構成的には以下の図の通りで、２つのLinear（全結合層）を用いてトークン間の変換を行います。Linearはニューラルネットワークの基礎となる線形結合の組み合わせです。RELUは非線形活性化関数で、ニューラルネットワークの階層化には不可欠な工夫です。

　FFNに入力されるのは文章に相当するトークン全て（上の例では512）ですが、FFN内部では単一トークンベクトルの「内部での」接続が学習されます。トークン間の関係はここでの学習対象ではありません。単一トークンの内部では、トークンベクトルの要素同士のすべての組み合わせがクロスバスイッチ的に結合されます。


```mermaid
graph BT
  Z(input) --> A[Linear]
  A[Linear] --> B[RELU]
  B --> C[Linear]
  C --> Y(output)

  style Z fill:#e09090
  style A fill:#90ee90
  style B fill:#add8e6
  style C fill:#90ee90
  style Y fill:#e09090
```


# 個別の構造:wrench:


## Linear(全結合層）などのパラメータの初期化の方法

　機械学習のパラメータの初期値は実用的には0ではないようです。例えば、一様分布による Xavier 初期化などが実行されています。

```
torch.nn.init.xavier_uniform_(weight)
```

　これは「入力と出力のバランスをとりつつ、値が過度に偏らないように」慎重に設計されているようです。

## 活性化関数　GELU

　活性化関数は、伝搬に非線形を持たせて、フィードバックに偏りを持たせて論理回路を生成する手段です。逆伝搬の目的のために微分可能で滑らかな関数が採用されています。現在主流の活性化関数がGELUです。
初期のパーセプトロンのころは単なる階段関数(バイアス値以上なら１でそれ以下なら０）でした。

$$\text{GELU}(x)=x \cdot \Phi(x)$$

　$\Phi(x)$は正規分布の積分に相当します。つまり値がx以下になる確率を返してx自身の重みとします。

$$\phi(x)=\frac{1}{\sqrt{2\pi}} e^{-\frac{t^2}{2}} $$

$$\Phi(x)=\int^x_{-\infty} \phi(x)dt$$

　つまりこの関数は、「ノイズを含む入力が活性化されるべきかを、確率論的に判断するベイズ的期待値に基づいた関数」ということだそうです。フィードバックの計算のために必要な微分係数もコードで定義されています。

$$ \frac{d}{dx}\text{GELU}(x)=\Phi(x)+x\cdot\phi(x)$$

## クロスエントロピー損失

　Transformerの予測結果のトークンと実際の文章のトークンとの違いから、Transformerへとフィードバックする準備を行います。

$$L=−logP$$

　Pは得られた正解の予測確率。P=1に近ければ0。P=0に近ければ巨大な数になります。なぜlogをとるかというと、softmaxでの演算結果と相殺させるという目的もあるようです。

## Softmax

　SoftmaxはAttentionの内積が大きいものに対して指数関数をとって、Attentionで選択されたVをより大きく強調するためのモジュールです。結果のベクトルの正規化（ベクトルのN次元の長さを1に揃えるという意味）の役目も果たしています。

$$ \text{Attention}（Q,K,V)=softmax(\text{Scores})V$$

$$ y_i= \frac{e^{z_i}}{\sum^N_{j=1} e^{z_j}}$$

$$	\frac{\partial\text{softmax}_i}{\partial x_j}= s_i(\delta_ij-s_j)$$

　なお、$\delta_{ij}$は$i=j$のときだけ１となるデルタ関数です。
 
## LayerNorm(レイヤー正規化)

　TransformerのAttentionのベクトルを正規化するモジュールです。勾配の値の爆発や消失を防ぐ目的で使われます。

　$\gamma$ と$\beta$は学習可能なパラメータ。後者はバイアスです。これらはselfAttention,Feedforwardの前後に挿入されます。(pre-LN,post-LN)

$$LayerNorm(x_i)=\gamma \frac{x_i-\mu}{\sqrt{\sigma^2+\epsilon}}+\beta$$

### Hidden state

　Transformerの内積計算は、過去に使用したKVを文章として記録しており、これが事実上の短期記憶として使われています。使用する時系列は512程度のこともあればもっと多いことあるようです。

（ChatGPT)なお、Transformerの短期記憶にはパラメータへのフィードバック学習も併用するようです。


### Adam(Adaptive Moment Estimation)
　フィードバックされた学習結果は、最終的には大量のlinearモジュール内部にあるウェイトパラメータ$w_i$とバイアスパラメータ$w_0$とに反映させます。このときに、これまで学習した結果をどの程度反映させるかを慎重に制御する必要があります。

- 学習率$\tau$
    単純に学習結果を反映する比率です。 
$$	w'_i=w_i-\tau\cdot \frac{\partial L}{\partial w_i}$$
- Momentum
	勾配の移動平均で滑らかに更新します。
	つまり前回の更新の変動速度を記録しておき、その速度で慣性を持たせて加算します
- AdaGrad
	頻繁に更新されるパラメータの学習率を小さく、まれなものは大きく
	そのためにG_tという累積勾配の二乗を加算して、結果を除算します

　これらの特徴を併せ持つものとして現在主流なのが、以下のAdamです。細かい実装は省略します。

$$ w'=w-\frac{\tau}{\sqrt{v_t}+\epsilon}\cdot m_t$$

　m_tはMomentumと同じく移動平均で慣性をつけて加算します。v_tはAdGradの派生品で頻度の大きな学習率を小さくします。一般的な定数を見る限り、AddGradよりもMomentum側の方が10倍大きいのが普通のようです。

### ドロップアウト

　過学習などを防ぐための経験的な手法で、ニューロンをランダムに選択して無効化する作用です。あくまでも学習の間に用いる機能で、推論時には用いられないようです。つまり学習と推論でモードが切り替わります。

- トークンを変換するInput ebmbeddingの後
- Attention内部のsoftmaxの直後
- FeedForwardNetworkの直後
- レイヤー正規化の直前


# 学習パラメータ

　Transformerで学習されるパラメータは以下の通りです。学習によってパラメータが自動的に変更されて、推論の時にパラメータを用いて最終的なLLMの返答を決定します。このパラメータは本当に膨大で、最新の大規模LLMでは億単位から兆単位に至るものもあります。
 

**Linear(全結合層)**

- Self-Attentionのクエリ（Q）、キー（K）、バリュー（V）を生成するLinear（線形変換）

- Attentionの出力を次層に渡すためのLinear

- FFN(Feed Forward Network)の2つのLinear層

　Linearはトークンベクトルと内積をとるウェイト$w_n$と、バイアス値$w_0$がパラメータです。

**Embedded Token**

- 入力トークンをベクトルに変換するword embeddingの重み

- (位置情報を加えるための)positional embeddingが学習可能な場合もある

**LayerNormのパラメータ**

- 内部のγ（スケーリング係数）と β（シフト量）は学習対象


# バックプロパゲーション:runner:

　これが生成AIの学習の本体です。学習内容に従って膨大なパラメータを補正します。

　入力列から予測した単語トークンと、観測された単語トークンとの差分から損失 $\mathcal{L}$が得られます。この観測された損失からスタートして、ニューラルネットワークを逆にたどり（バックプロパゲーション、逆伝搬）、観測との不一致の原因のパラメータすべてに少しづつ作用していきます。その際に、通過するユニットの微分勾配の積が重要になります。このことをチェーンルールと言うようです。

　PyTorchが優れている点として、PyTorchで順方向伝搬のコードを書けば、バックプロパゲーションがそのコードをもとに自動生成される機構が備わっています。トークンの予測と結果との差分が発生したら、複雑なTransformerの構造を逆伝搬することで、Transformerのパラメータが自動的に補正されます。

## Linear(線形変換)へのフィードバック

　線形変換へのフィードバックは、ニューラルネットワークの本質と言えます。線形変換をベクトル化した結果が以下の式です。

$$ y= w_ix_i + w_0$$

　$w_i$が$x_i$入力に対する重みとなり、$w_0$がバイアスとなります。これらを適用した結果が$y$です。このパラメータ$w_i$、$W_0$などがフィードバックからの学習対象となって更新されます。

　フィードバックの結果、正解との差分である損失$\mathcal{L}$を使います。この損失$\mathcal{L}$の差分が偏微分$\frac{\partial \mathcal{L}}{\partial y}$で表されて、以下の式でそれぞれのパラメータに加算されていきます。入力ウェイトの加算は損失と入力値xの積となります。バイアスは損失そのものが加算されます。つまり値が０に近い入力は、ウェイトがフィードバックされないということです。

$$\frac{\partial \mathcal{L}}{\partial w_i} = \frac{\partial \mathcal{L}}{\partial y} \cdot x$$

$$\frac{\partial \mathcal{L}}{\partial w_0} = \frac{\partial \mathcal{L}}{\partial y}$$

　線形変換の入力$x$へのさらなる逆伝搬は以下の式になります。バイアスWの転置行列との積がさらなる起点へのフィードバックになっていきます。

$$ \frac{\partial \mathcal{L}}{\partial x} = W^{\top}\cdot \frac{\partial \mathcal{L}}{\partial y}$$

　なお、バイアスやウェイトの実際の更新方法は、主にAdam(Adaptive Moment Estimation)と呼ばれる特殊なアルゴリズムで、学習を平均化するように工夫されています。

## 多層構造のニューラルネットワークの逆伝搬

　Transformerをバックプロパゲーションする際に、通過してきたAttentionの内積関数や活性化関数などを逆に通過するのですが、逆伝搬するためにはユニット全てに微分勾配が定義されている必要があります。そのため、Transformerの経路は全て微分可能である必要があります。

　この手法は、順方向関数f(x)のテイラー展開の一次の項を微分勾配として用意しておいて、逆にxを微分勾配を割り算して求めるという形とも考えられます。複数の関数が通過するときも、呼ばれる方法で順に微分係数を使って割り算することになります。これもテイラー展開の階層化と言えるでしょう。

　つまり逆伝搬では、どんな複雑な経路をたどっても、元の結果の差分に微分勾配を順次積算することで、フィードバック対象に加算する値$V$を求めることができます。これが勾配のチェーンルールです。

$$ \frac{\partial \mathcal{L}}{\partial V} =  \frac{\partial X}{\partial V} \frac{\partial Y}{\partial X} \frac{\partial \mathcal{L}}{\partial Y}$$

　チェーンルールは自動的に実行されますが、個々の関数の微分勾配は自動的に計算はできません。そのため、softmax、活性化関数RELUなどはすべて、pytorchのライブラリの中に微分勾配の実装を用意しているようです。

## 活性化関数の通過

　活性化関数は、ニューラルネットワークの基本である単純な階段関数は採用されなくなりました。現在使用されているGELUでは、偏微分が以下の式で表されます。

$$ \frac{d}{dx}\text{GELU}(x)=\Phi(x)+x\cdot\phi(x)$$

## フィードバックからVへの逆伝搬

　Attention の出力ベクトル $A$ を経由して、損失 $\mathcal{L}$が観測されたとします。その時に、勾配$\frac{\partial \mathcal{L}}{\partial A}$を入力ベクトルVに伝える経路の逆伝搬を説明します。まずは、Attention 出力：$A = S V$とします。


$$S = \text{softmax} \left( \frac{QK^\top}{\sqrt{d_k}} \right)$$

　入力ベクトルVへの逆伝搬の勾配は、softmaxの結果を用いて以下のようになります。SはSoftmax関数の結果を保存しておくことで逆伝搬の際の計算を省略します。

$$\frac{\partial \mathcal{L}}{\partial V}=S^\top\frac{\partial \mathcal{L}}{\partial A}$$

　これが活性化関数GELUの微分勾配です。


## フィードバックからQ,Kへの逆伝搬

　Q,Kへのフィードバックはsoftmaxを逆に通過するので話は少し複雑になりますが、結果はむしろ簡単になるようです。式の導出方法はまだ整理できていません。

　伝搬元のQ,Kに相当するベクトルを$z_i$として、

$$ y_i=\text{softmax}(z_i)$$

　正解の値のベクトルを$t_i$としてクロスエントロピー損失は以下の式になります。

$$ \mathcal{L}=-\sum_i t_i log(y_i)$$

　softmaxの結果$y$を保存しておいて使うと、求めるべき逆伝搬の勾配は簡単に

$$\frac{\partial \mathcal{L}}{\partial z}=y-t$$

　このような式になります。


## フィードバックから入力ベクトルxへの逆伝搬

　Q,K,Vは、下の層からの共通の出力xからそれぞれ別のLinear層の行列$W^Q$、$W^K$、$W^V$を通過して入力されます。

$$ Q=xW^Q,K=xW^K,V=xW^V$$

　フィードバックはxの入力に対しても行われるため、以下の式で誤差${\partial \mathcal{L}}$が起点に向けて逆伝搬されます。

$$ \frac{\partial \mathcal{L}}{\partial x}=\frac{\partial \mathcal{L}}{\partial Q}W^{Q\top}+\frac{\partial \mathcal{L}}{\partial K}W^{K\top}
+\frac{\partial \mathcal{L}}{\partial V}W^{V\top}$$


# 実装と運用:factory:

## 標準的な動作システム

　Transformerと呼ばれる生成AIは、どれも基本的にはPython言語とPyTorchライブラリなどを用いたプログラムで構成されており、小規模なPCでも一応動作します。動作部分は、以下の例のようなtorch.nn.Moduleなどの派生クラスが中核となるアルゴリズムです。このforward()の中に必要な処理を追加することで、実用になる生成AIとなります。

```
class SimpleModel(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = torch.nn.Linear(10, 5)

    def forward(self, x):
        return self.linear(x)
```

　驚くことに、この構成のプログラムをそのまま大規模なLLMでも運用しているようです。大規模なLLMと何が違うというと、動作環境です。このTransformerアルゴリズムは、すべての目的に対して用いることができるアルゴリズムが１つあるわけではなく、目的によって少しづつ変更されています。

## 動作環境

　python+pytorch自体は当然CPUだけでも動作が可能ですが、PytorchがGPUを用いたCUDAをサポートすることで飛躍的な性能向上に成功しました。
　さらに現在は、TPU,NPUと呼ばれる専用プロセッサが開発されています。このNPUの最大の特徴はシストリックアレイ演算と演算器のビット数の削減です。GPUの64ビット演算器と8ビット演算器とは100倍以上の回路規模の差がありますが、機械学習の予測フェイズに限っては性能に大差がないと知られてきました。そのため、ビット数を削減した専用NPUの優位性は明確となりました。
　シストリックアレイとはSIMDと似ている並列演算ですが、これは、データを二次元的に順にシフトさせてパイプライン的に演算する構造になっていて、これによりトークン行列のような1000次元以上の巨大な行列演算の並列化が可能になりました。
　演算ユニットは16bit浮動小数点、8bit整数が主流で、ほかにも1bit整数、2bit整数なども試みられています。ただし、学習では32bitの浮動小数点が主流のようです。

```mermaid
graph TD
  A[python+pytorch] --> B[CPU]
  A --> C[GPU CUDA]
  A --> D[NPU ONNX]
  A --> E[DeepSpeed]
  style A fill:#add8e6
  style B fill:#90ee90
  style C fill:#ffffe0
  style D fill:#ffffe0
  style E fill:#ffffe0
```

## GPUのCUDA並列化

　GPU CUDAによる単一マシン複数GPUでの並列処理は以下のようなコードになります。

```
import torch.nn.parallel
model = torch.nn.DataParallel(model)
model = model.to("cuda")
```

　ネットワーク分散並列処理は以下のようにして実行されます。

```
model = torch.nn.parallel.DistributedDataParallel(model)
```

## NPU並列化

　このPythonで書かれたプログラムをNPUなどを用いて高速化するための方法が用意されています。

- ONNX (Open Neural Network Exchange)

　現在のNPUは主にこれをサポートしているようです。ONNXのトランスレータがこのPythonプログラムを解析して、NPU用のプログラムへと変換しています。
　pytorchで用いられているライブラリを可能な限りサポートするため、ONNXの命令セットも結構巨大なもののようです。

https://github.com/onnx/onnx/

　なお、Transformerに相当するものをNPUその他でハードコーディングして高速化することは当然可能のはずですが、変化の激しいTransformer界隈でアルゴリズムを１つに定めることは危険と考えられているらしく、あまり主流でないらしいです。


## ONNXについて

　現在主流となっているNPUの共通開発言語と言ってよいようです。
　このONNXを利用してNPUで機械学習を動作させるには、Pythonのpytorchで記述されたコードから変換します。pytorchで記述されたモデル、具体的にはtorch.onnx.export関数が、torch.nn.Moduleのインスタンスを解析して変換を行い、中間表現のONNX形式に変換しています。このときに、使用しているtorch.nnの関数はONNXの命令セットに置き換えられます。

　この命令セットは、現在のTransformerに必要な様々な演算を一通りそろえているようです。さらに、Transformerの進歩とともに演算内容も増やしてきているそうです。

## ONNXへの変換方法
```
import torch
import torch.onnx

# モデルと入力を定義
model = MyModel()
dummy_input = torch.randn(1, 3, 224, 224)  # 例: 画像入力

# モデルを評価モードに
model.eval()

# ONNX にエクスポート
torch.onnx.export(
    model,                   # PyTorch モデル
    dummy_input,             # 入力の形
    "model.onnx",            # 出力ファイル名
    export_params=True,      # 学習済みパラメータも保存
    opset_version=17,        # ONNX opset バージョン（11以降が一般的）
    do_constant_folding=True,# 定数折りたたみ
    input_names=['input'],   # 入力名
    output_names=['output'], # 出力名
)
```

　この方法で、pytorchで記述されたモデルがONNX の IR（intermediate representation）形式でファイル出力されます。とにかくpytorchでモデルを記述して変換すれば、大半のNPUでの動作が可能になるという優れモノです。

（ChatGPT)この変換は厳密なコンパイラ的なものではなく、ダミー入力を用いてメソッドを実行して解析することで変換しています。


## ONNXの代表的な命令

|カテゴリ	|	代表オペレーター例|
|--------------|---------------------------|
|行列演算	|MatMul, Gemm|
|畳み込み	|Conv, MaxPool, AveragePool|
|活性化関数	|Relu, Sigmoid, Tanh, Softmax|
|正規化	|BatchNormalization, LayerNormalization|
|シーケンス系	|RNN, LSTM, GRU|
|テンソルデータ変換	|Reshape, Transpose, Cast|
|条件・ループ制御	|If, Loop|
|Attention	|MatMul + Softmax + MatMul 等で構築|


- Matmul
	行列積
- Gemm
	バイアス付き行列演算で、ニューラルネットワークの基礎
$$	Y=\alpha \cdot A \cdot B + \beta\cdot C$$

- MaxPool
	最大値プーリングで、2x2などのプーリングウィンドウ内で最も強い特徴を残す。画像などのエッジ検出に向く。
- AveragePool
	平均値プーリングで全体的な傾向を均等に残す。
- Conv
	畳み込み演算。画像のエッジ抽出などで用いられる。
- BatchNormalization
	画像系で用いられるバッチ分割して分布を正規化（ベクトルの絶対値=長さを１にするように書き換える）
- LinearNormalization
	全体の分布を正規化

命令セットはここで詳細に説明されています。

https://github.com/onnx/onnx/blob/main/docs/Operators.md

## DeepSpeed


　LLNの膨大なデータセットの学習には膨大な計算量が必要になります。学習規模が大きいほど驚くほどの知性が得られることが判明したからです。
この学習データを複数のNPUなどに分散させて、それぞれ並列化で学習するほうが性能が向上すると考えられました。そのための学習データの分割がbatchで、batchの数だけネットワーク分散で並列化します。

　DeepSpeedは、この動作のバッチによる分散化を目的としたシステムです。百万ドル以上の電力コストが必要ともいわれる大規模LLMの学習を支える技術です。10000以上の並列化が実現されているようです。

　ONNXと同じく、torch.nn.Module で定義された標準的なPyTorchモデルを、deepspeed.initialize()で初期化して動作させます。構造的には、torch.nn.Moduleの内部で使用されているメソッドなどをDeepSpeedを使用する形に置き換えるもので、PyTorchで記述されたコードを中間表現に変換するものではないようです。


　以下は、設定ファイルdeepspeed_config.json の例です。最適化技術、バッチサイズなどの設定を行っています。

```
{
  "train_batch_size": 16,
  "train_micro_batch_size_per_gpu": 4,
  "gradient_accumulation_steps": 4,
  "optimizer": {
    "type": "AdamW",
    "params": {
      "lr": 1e-4,
      "weight_decay": 0.01
    }
  },
  "scheduler": {
    "type": "WarmupLR",
    "params": {
      "warmup_min_lr": 0,
      "warmup_max_lr": 1e-4,
      "warmup_num_steps": 100
    }
  },
  "gradient_clipping": 1.0,
  "zero_optimization": {
    "stage": 0
  },
  "fp16": {
    "enabled": false
  }
}
```

　この設定の場合の全体のバッチサイズは16*4*4=64となります。学習での浮動小数点精度は32bitが主流で、16ビットも指定が可能となっています。
　(ChatGPT)分散されて学習された学習結果は、それぞれ別のパラメータが学習されます。それらのパラメータは平均をとって統合されるのが基本のようです。


# トータルの考察:thinking:

## 全体構造の理解の要約

Transformerは、
Attentionが関連性を見つけ、
FeedForwardがその関係の中で何を意味するかを変換し、
残りの要素はその過程を安定かつ効率的に支えるための補助装置です。

## Attentionの作用の意義

　Transformerの目的は簡単に言えば、長い文章の単語同士の関連を詳しく見つけ出すことです。そのためには、大量のトークンの間の組み合わせの全てをばらばらに評価してフィードバックするのは効率が悪いので、Attentionを使うと理解できました。トークンの内積を使って必要なトークンに対して注目を行うことで、注目されたトークン内部だけで重点的にフィードバックや学習を行うことができます。
　逆に言えば実は、Attentionは効率化の手段であって、意味の学習自体には貢献していないと結論できます。しかしこの効率化が生成AIの圧倒的な進歩をもたらしたのは事実です。

## 逆伝搬の数学的な証明

　クロスエントロピー損失とsoftmaxの組み合わせがAttentionの逆伝搬をシンプルなものにしています。これはかなり技巧的なもので、自分にはいまひとつ整理できていないのでもう少し調査が必要そうです。

## 厳密さに欠けると感じる要素

　生成AIの機械学習は経験的に進歩してきた分野なので、LLMのベンチマークの向上を指標にしています。逆に言えば、そのために数学的な厳密さに欠けるという印象があります。
　ここまで調べた結果、以下の要素は特に厳密とは言い難いと感じています。しかし、今のニューラルネットワークでは、総じてほかに方法はないのでしょう。

- クロスエントロピー損失
- Softmax関数
- 活性化関数　GELU
-  Adam（Adaptive Moment Estimation）
- ドロップアウト


## 生成AIの総論

　生成AIの大規模言語モデルLLMそのものは、極めて明確な進歩をもたらしました。そのため、LLMの発展方針はおおむね正しいと考えます。しかし、ハルシネーションがいつ発生するかが予測できないため、完全に信頼するには足りないのが現状であり、今後の発展に致命的な影響を与えると考えます。その理由はフィードバックの精度にあるというのが個人的な見解です。
　ニューラルネットワークは、数学的には不変近似定理に基づいてあらゆる関数を再現することが可能とされています。ただしそれは、正しくパラメータが設定されればの話です。パラメータが学習によって自動的に正しく設定される保証はいまだなく、過学習などの問題が発生してそれに対処しているのが現状です。