はじめに
AIに仕事を奪われづらい人ってどういう特徴を持っているのかを考えることが多くなってきました。
最近、タスクをAIにまとめておねがいすることが多く、自分がやってもAIがやっても変わらないアウトプットが出て、このままだと淘汰されるなと少し感じ始めました。
そんな状況でも、基礎の力が圧倒的に強い人たちは、AIに仕事を奪われず価値を生んでいるように感じます。
そんなことを考えたので、私も少しずつ基礎をインプットして、基礎力のあるエンジニアになろうかなと思います。
その第一歩として、直近勉強しようと思っていたAIの基礎中の基礎Transformerから理解していこうと思います。
発端
はじめにで記載したのですが、結局基礎力がある人が顕著にアウトプットを出せているように最近感じているので、一個ずつ基礎をやっていこうと思います。
AIの内部の仕組みの深堀りを一個ずつ漏らさずにやっていきます。
現在、想定しているAIやLLMについて勉強しようと思っているトピックは以下です。おすすめがあったら、教えてください(※202607現在)
とりあえず、自分のユースケースに近いものを上げてみましたが、途中で疑問に思ったり、不足があったら、寄り道していくと思います。
- Transformerについて
- LLMの基礎と限界
- Prompt設計とAgentの制御
- Ragの設計と評価
- Tool useとFunction Calling
- Multi-Agent
- Agent EvaluationとObservability
- Safety/Aligment/Guardirails
- Agentの本番運用設計
1.座学
ともあれ、座学ですね。歴史とかも軽く調べながら全体をつかんでいきたいと思います。
Transformerの概要
Transformerの記事といえば、Vaswani らの論文 (Attention Is All You Need)が有名のようです。
系列を一語ずつ順番に処理する RNN を使わず、各トークンがほかのトークンを直接参照する Attention を中心にモデルを構成できる。
と記載がありました。一個ずつちゃんとさっぱりなのでわからんものを挙げて調べていこうと思います。
RNNとは再帰的ニューラルネットワーク(Recurrent Neural Network)の略で、前の処理結果を次に引き継いで処理を行うようです。
よく見かける例だと
「吾輩は猫である」という文章があるとしたら、RNNだと下記のように記憶します。
吾輩 → は → 猫 → で → ある
↓ ↓ ↓ ↓ ↓
記憶→記憶→記憶→記憶→記憶
今までの情報を記憶し、保持しながら次の単語を処理するようです。
これ以上は、別のタイミングで深堀りしようかなと思います。
いよいよ本題のTransformerについてです。
Transformer は、各トークンが系列内のほかのトークンとの関連度を Attention で計算し、必要な情報を直接集めるモデルです。RNN の逐次処理を外したことで訓練を並列化しやすく、離れた語の関係も短い経路で扱えます。語順は positional encoding で加えます。
文章全体を一度に見て、単語動詞の関係を難しい計算をするようです。
だから、論文のタイトルがAttension(注意・注目)になっているんですかね。
初期環境構築
とりあえず、環境構築していきます。
以前の薄ら知識で、pythonをWSLにインストールして、UV環境の中で実行するようにしていこうと思います。
Transformerをやるためには、PyTorchという機械学習用のライブラリがあるので、それを使用します。
まず、Dockerfileを作成します。
FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:0.11.26 /uv /uvx /bin/
WORKDIR /workspace
COPY pyproject.toml ./
RUN uv sync
ENV PATH="/workspace/.venv/bin:$PATH"
CMD ["bash"]
続いて、pyproject.tomlを作成します
[project]
name = "transformer-training"
version = "0.1.0"
description = "Transformer implementation training"
requires-python = ">=3.12"
dependencies = [
"torch",
]
dockerを起動
docker build -t transformer-training .
うまく起動はしているんですが、pytorchでnumpyを使っているみたいでwarningが出ているんで、ダウンロードしておきます。
root@16f253d68ca5:/workspace# pwd
which python
python --version
uv --version
python -c "import torch; print(torch.__version__)"
/workspace
/workspace/.venv/bin/python
Python 3.12.13
uv 0.11.26 (x86_64-unknown-linux-musl)
/workspace/.venv/lib/python3.12/site-packages/torch/_subclasses/functional_tensor.py:362: UserWarning: Failed to initialize NumPy: No module named 'numpy' (Triggered internally at /pytorch/torch/csrc/utils/tensor_numpy.cpp:84.)
cpu = _conversion_method_template(device=torch.device("cpu"))
2.12.1+cu130
仕組みの整理と実装
Transformerの仕組みは、細かく見るといろいろありますが、大雑把には以下の流れです。
文章
↓
トークン化
↓
トークンIDに変換
↓
単語Embeddingに変換
↓
位置Embeddingを足す
↓
Query・Key・Valueを作る
↓
Self-Attention
↓
Multi-Head Attention
↓
Feed Forward
↓
Transformer Blockを通す
↓
次のトークンを予測する
↓
小さな文章生成モデルになる
step1 トークン化と単語embedding変換
文をトークンに分けて、それぞれにIDを振ります。
そのIDを nn.Embedding に渡して、各トークンを4次元のベクトルに変換します。
つまり「言葉を、モデルが計算できる数値ベクトルに変える」処理です。
import torch
from torch import nn
# ① 単語とIDを対応させた語彙表を用意する
vocab = {
"私": 0,
"は": 1,
"猫": 2,
"である": 3,
}
# ② 文章をトークン(単語)に分割する
tokens = ["私", "は", "猫", "である"]
# ③ tokensから単語を一つずつ取り出す
# vocabを使って単語をIDに変換し、
# IDのリストをPyTorchのテンソルにする
token_ids = torch.tensor(
[vocab[token] for token in tokens]
)
# 結果:
# tensor([0, 1, 2, 3])
# ④ Embedding表を作成する
# 語彙数は4なので4行、
# 各トークンを4次元のベクトルで表すので4列の表になる
# 表の数値はPyTorchによってランダムに初期化される
# ただし、学習によってこの値は更新されていく
embedding = nn.Embedding(
num_embeddings=len(vocab), # 登録するIDの数:4
embedding_dim=4 # 1つのIDを表す数値の数:4
)
# embedding.weightのイメージ:
#
# ID 0 → [ 0.2, -0.5, 0.1, 0.8]
# ID 1 → [-0.1, 0.3, 0.7, -0.2]
# ID 2 → [ 0.9, 0.2, -0.4, 0.1]
# ID 3 → [ 0.3, -0.8, 0.5, 0.4]
# ⑤ token_idsの各IDに対応する行を
# Embedding表から取り出す
vector = embedding(token_ids)
# vectorのイメージ:
#
# token_ids = [0, 1, 2, 3]
#
# 0 → ID 0の行を取り出す → 「私」のベクトル
# 1 → ID 1の行を取り出す → 「は」のベクトル
# 2 → ID 2の行を取り出す → 「猫」のベクトル
# 3 → ID 3の行を取り出す → 「である」のベクトル
step2 単語の位置情報
単語Embeddingに位置Embeddingを足します。
Transformerは単語の並び順をそのままでは理解できないため、
「この単語は何番目にあるか」という位置情報もベクトルとして持たせます。
つまり、
単語の意味っぽいベクトル + その単語が何番目かを表すベクトル化する処理です
# ① step1で作ったトークンIDを用意する
# ここでは、
# 私 → 0
# は → 1
# 猫 → 2
# である → 3
# というID列だと考える
tokens = torch.tensor([0, 1, 2, 3])
# ② 単語Embedding表を作成する
# 各トークンIDを4次元のベクトルに変換するための表
token_embedding = nn.Embedding(
num_embeddings=4, # 登録するトークンIDの数:4
embedding_dim=4, # 1つのトークンを表す数値の数:4
)
# ③ 位置Embedding表を作成する
# 0番目、1番目、2番目、3番目、という
# 位置情報も4次元のベクトルで表す
positional_embedding = nn.Embedding(
num_embeddings=4, # 扱える位置の数:4
embedding_dim=4, # 1つの位置を表す数値の数:4
)
# ④ 各トークンの位置IDを作成する
# len(tokens) は 4 なので、
# torch.arange(4) によって [0, 1, 2, 3] が作られる
positions = torch.arange(len(tokens))
# 結果:
# tensor([0, 1, 2, 3])
#
# 意味:
# 「私」 は0番目
# 「は」 は1番目
# 「猫」 は2番目
# 「である」 は3番目
# ⑤ トークンIDを単語ベクトルに変換する
# token_embeddingの表から、
# tokensの各IDに対応する行を取り出す
token_vectors = token_embedding(tokens)
# token_vectorsのイメージ:
#
# token 0 → 「私」のベクトル
# token 1 → 「は」のベクトル
# token 2 → 「猫」のベクトル
# token 3 → 「である」のベクトル
#
# shapeは [4, 4]
# 4個のトークン × 4次元ベクトル
# ⑥ 位置IDを位置ベクトルに変換する
# positional_embeddingの表から、
# positionsの各IDに対応する行を取り出す
position_vectors = positional_embedding(positions)
# position_vectorsのイメージ:
#
# position 0 → 0番目を表すベクトル
# position 1 → 1番目を表すベクトル
# position 2 → 2番目を表すベクトル
# position 3 → 3番目を表すベクトル
#
# shapeは [4, 4]
# 4個の位置 × 4次元ベクトル
# ⑦ 単語ベクトルと位置ベクトルを足す
# Transformerに入力する最初のベクトルを作る
x = token_vectors + position_vectors
# xのイメージ:
#
# 「私」のベクトル + 0番目のベクトル
# 「は」のベクトル + 1番目のベクトル
# 「猫」のベクトル + 2番目のベクトル
# 「である」のベクトル + 3番目のベクトル
#
# これによって、
# 「どの単語か」
# 「文の中で何番目か」
# の両方の情報を持ったベクトルになる
学び
今回はTransformerの入口部分である、トークン化・Embedding・位置Embeddingについて学びました。
文章はそのままではモデルが扱えないため、まずトークンに分けてIDに変換します。
そのIDをEmbeddingによってベクトルに変換し、さらに位置Embeddingを足すことで「どの単語か」と「何番目にあるか」の情報を持たせます。
Attentionに入る前に、Transformerが計算できる入力を作る必要があることを理解しました。
このペースでやっていくとかなり長くなりそうなので、今回はここまでとします!
transformerの次のQuery・Key・Valueの勉強も次の記事でやっていきます。
今回は最初の2ステップのベクトル化と順序性の付与の部分を実際に実装してみました。
結局、Attentionの部分のTransformerにはたどり着けなかったのですが、次回以降でしっかり理解していこうと思います。