NLPとコンピュータビジョン

目次

概要

まず、この章の中心構造を図で確認します。細部に入る前に、どの概念がどこへつながるかをつかむための地図です。

flowchart LR A["入力データ"] --> B{"種類"} B -->|テキスト| C["トークン化"] B -->|画像| D["パッチ化・特徴抽出"] C --> E["埋め込み表現"] D --> E E --> F["事前学習済みモデル"] F --> G["微調整・プロンプト設計"] G --> H["タスク出力"] H --> I["評価と監視"]

言語と画像は、どちらも高次元の表現を扱う

自然言語処理とコンピュータビジョンは、扱うデータは違っても、表現学習、特徴抽出、タスク固有の出力設計という点で共通しています。どちらも「生データを、そのままでは使えない表現から、判断しやすい表現へ変換する」問題です。

要点

NLPとCVは別分野に見えますが、表現学習、事前学習、下流タスク適応という流れはよく似ています。

この章で重視すること

  • NLPとCVの共通構造を理解する
  • 埋め込み、注意機構、事前学習の役割を整理する
  • タスクごとの評価指標の違いを押さえる

NLP

言語モデル、系列ラベリング、検索、翻訳、要約、対話などを扱います。最近はTransformerと事前学習済みモデルが中心です。

テキスト表現

テキストはそのまま数値では扱えないため、tokenizationとembeddingが必要です。単語単位、subword単位、文字単位のどこで分けるかにより、未知語、語彙サイズ、多言語対応の性質が変わります。

代表的なタスクは次です。

  • text classification
  • named entity recognition
  • question answering
  • summarization
  • machine translation
  • retrieval

評価

NLPの評価はタスクによって異なります。分類ならaccuracyやF1、翻訳ならBLEU、生成なら自動指標だけでなく人間評価やtask successも見ます。LLMでは、正確性、安全性、引用の妥当性、指示追従を分けて評価する必要があります。

NLPの難しさは、入力が同じ意味でも多様な表現を持つことです。表記揺れ、略語、文脈、省略、皮肉、専門用語によって、単純な文字列一致では扱えません。

問題 設計上の注意
表記揺れ AI、人工知能、生成AI 正規化や辞書だけに頼りすぎない
文脈依存 「それ」「前者」 周辺文脈をどこまで入れるか
多義語 Java、Ruby ドメイン文脈を使う
長文 契約書、議事録 chunkingと検索設計
評価困難 要約、対話 人間評価と自動評価を組み合わせる

Transformerベースの言語モデル

BERTやGPT系モデルはTransformerアーキテクチャを基盤としています。BERT(Bidirectional Encoder Representations from Transformers)は2018年に提案され、両方向コンテキストを使うマスク言語モデリングで事前学習されます。

入力テキスト:「我々は[MASK]に向かって進んでいる」
事前学習タスク:[MASK]位置の単語を予測
BERTの学習:両方向のコンテキストから「未来」などを予測

一方、GPT系は因果言語モデリング(causal language modeling)で、左から右へのみコンテキストを使います。これにより、テキスト生成時に自然な次トークン予測ができます。

HuggingFaceエコシステム

実務ではHuggingFaceが事実上の標準になっています。

from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch

# 事前学習済みモデルの読み込み
model_name = "bert-base-multilingual-cased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(
    model_name,
    num_labels=2
)

# テキスト入力の前処理
text = "これは素晴らしい映画です。"
inputs = tokenizer(
    text,
    padding=True,
    truncation=True,
    max_length=512,
    return_tensors="pt"
)

# 推論
with torch.no_grad():
    outputs = model(**inputs)
    logits = outputs.logits
    prediction = torch.argmax(logits, dim=-1)

Tokenizationの詳細

Subword tokenization(BPE、WordPiece)は稀な単語や未知語に対応します。例えば、「transformers」という単語が学習データに含まれなくても、「transform」「ers」という部分語に分割され、各々の埋め込みから合成表現が作られます。

元の単語:transformers
WordPiece分割:[transform] [##ers]
(##は前の単語と繋がることを示す)

結果:各subwordの埋め込みをLSTMやTransformer層で合成

多言語対応では、言語ごとのtokenizationの効率が異なります。CJK言語(中国語、日本語、韓国語)では文字単位の分割が増えやすく、結果として系列長が長くなり計算量が増えます。

コンピュータビジョン

分類、検出、セグメンテーション、生成、マルチモーダル理解などを扱います。CNNからVision Transformerへと表現の中心が広がっています。

画像表現

画像はピクセルの配列ですが、モデルが使うのはエッジ、テクスチャ、形、物体、関係性のような階層的表現です。CNNは局所的な畳み込みで特徴を抽出し、Vision Transformerはpatchをtokenのような扱いにします。

代表的なタスクは次です。

  • image classification
  • object detection
  • semantic segmentation
  • instance segmentation
  • pose estimation
  • image generation

評価

分類ではaccuracyやtop-k accuracy、検出ではIoUやmAP、セグメンテーションではpixel accuracyやmean IoUを使います。生成画像では、品質、多様性、指示との一致、安全性を分けて見る必要があります。

画像タスクでは、撮影条件が性能に大きく影響します。照明、角度、解像度、圧縮、背景、センサーの違いによって、開発時の評価と本番の入力がずれることがあります。

flowchart LR Capture["撮影条件"] Image["画像"] Preprocess["前処理"] Model["モデル"] Output["分類/検出/分割"] Review["人間確認"] Capture --> Image --> Preprocess --> Model --> Output --> Review

モデルを改善する前に、本番で入ってくる画像が評価データと似ているかを確認します。入力品質が悪い場合、モデルを大きくするより、撮影ガイドや前処理を直す方が効くことがあります。

Vision Transformer(ViT)

Vision Transformerは画像を16x16ピクセルのパッチに分割し、各パッチを線形投影してtoken埋め込みに変換します。その後、標準的なTransformerエンコーダで処理します。

入力画像(224x224)
↓
16x16パッチに分割(196パッチ)
↓
各パッチを線形投影(768次元)
↓
位置埋め込み追加 + class tokenTransformer encoder 12層
↓
class tokenMLP head → クラス出力

ViTの利点は、CNN特有の帰納バイアス(局所性など)を持たないため、大規模データで学習したときの汎化が良いことです。ImageNet-21kで事前学習したViT-Largeは、多くのベンチマークでCNNを上回ります。

CNN vs ViT

特性 CNN Vision Transformer
帰納バイアス 局所性、平行移動不変性 外部attention機構
小規模データ 高性能 過学習しやすい
大規模データ scaling限界 scaling効果が高い
計算量 効率的 quadraticに増加
多タスク転移 良好 より一般的

事前学習と転移学習

NLPとCVの両方で、巨大なデータで事前学習したモデルを、下流タスクへ適応する方法が一般的です。少量データでゼロから学習するより、事前学習済み表現を使う方が性能と学習効率が上がりやすくなります。

適応方法には次があります。

  • fine-tuning
  • linear probing
  • prompt-based adaptation
  • parameter-efficient tuning

Fine-tuning 戦略

全層fine-tuning は計算量が多いため、以下の工夫が使われます。

Layer-wise Learning Rate Decay

# 後ろの層ほど学習率を小さくする
# 事前学習済み特徴を壊さない
optimizer = torch.optim.AdamW(
    [
        {'params': model.transformer.h[-1].parameters(), 'lr': 1e-4},
        {'params': model.transformer.h[-2].parameters(), 'lr': 1e-5},
        {'params': model.classifier.parameters(), 'lr': 1e-3},
    ]
)

LoRA(Low-Rank Adaptation)

全重みを更新する代わり、低ランク行列(rank=8など)を追加学習します。

元の重み行列 W: (d_out, d_in)
LoRA追加: W_A: (d_out, r) @ W_B: (r, d_in)
出力: y = Wx + αW_Ax'Wx ≈ (W + ΔW)x

パラメータ削減率が非常に高く、GPUメモリが限定的な環境で有効です。

Linear Probing

事前学習層を凍結し、最後のlinear layerだけ学習します。表現が十分に汎化していれば、小さなデータセットでもlinear probeで良い性能が出ます。

# 事前学習層を凍結
for param in model.backbone.parameters():
    param.requires_grad = False

# 最後の層のみ学習
model.head = nn.Linear(hidden_dim, num_classes)
optimizer = torch.optim.SGD(model.head.parameters(), lr=0.1)

マルチモーダル

画像と言語を同時に扱うモデルでは、画像のpatch表現と言語token表現を同じ空間へ写し、相互に参照できるようにします。画像検索、画像説明、視覚質問応答、文書理解などで使われます。

マルチモーダルでは、どの情報をどのmodalから得たのかを追えることが重要です。たとえば請求書の画像から金額を読み取り、周辺テキストで意味を補う場合、OCRの誤り、表構造の誤解、言語モデルの補完が混ざります。

失敗箇所 確認方法
入力 画像がぼやけている 品質スコア、再撮影
OCR 数字を読み間違える confidence、human review
レイアウト理解 表の列を取り違える bounding box確認
言語推論 存在しない項目を補う 根拠箇所の提示
出力 JSON schemaに合わない validation

マルチモーダルアプリケーションでは、最終回答だけでなく、中間結果を確認できる設計があると運用しやすくなります。

Contrastive Learning

CLIP(Contrastive Language-Image Pre-training)はテキストと画像を同じ埋め込み空間に写し、対応する画像テキストペアを近づけ、対応しないペアを遠ざけます。

テキストエンコーダ → T_i
画像エンコーダ → I_i

損失:L = -log(exp(sim(T_i, I_i) / τ) / Σ_j exp(sim(T_i, I_j) / τ))
(τ = temperature)

この事前学習により、新しいカテゴリにも「ゼロショット」で対応できます。例えば「猫」というテキストが学習データになくても、猫画像と意味的に近いテキスト表現を学べます。

データセットの注意点

NLPとCVはデータの偏りがそのまま出やすい領域です。

  • ラベル品質
  • domain shift
  • 個人情報
  • 著作権
  • 有害表現
  • クラス不均衡

モデル性能だけでなく、どのデータで学んだかを把握することが重要です。

ImageNet効果と事前学習データ

ImageNetで1000万枚以上の画像で事前学習することが標準化しましたが、以下の注意があります。

  • 西欧中心のラベル付けバイアス(「アフガン犬」は特定地域の犬种)
  • 撮影スタイル(研究用なので背景がシンプル)
  • クラス不均衡(一部クラスが多い)
  • 有害なステレオタイプ(職業と性別の結びつけなど)

事前学習後に新データで fine-tune するときは、新データのバイアスも評価対象にします。

実務での選び方

  • 既存モデルで十分か
  • fine-tuningが必要か
  • latency要件はどれくらいか
  • edgeで動かす必要があるか
  • 誤分類のコストは何か
  • 人間レビューをどこに入れるか

トークン化と画像パッチ

NLPでは文章をtokenへ分け、CVでは画像をpixelやpatchへ分けます。どちらも「連続した生データを、モデルが扱える単位へ変換する」処理です。

領域 入力単位 典型的な前処理 注意点
NLP token、subword、文字 正規化、tokenization、embedding 多言語、未知語、文脈依存
CV pixel、patch、region resize、crop、augmentation 解像度、照明、背景、ラベル粒度

NLPのsubword tokenizationは未知語に強く、CVのpatch表現はVision Transformerのように画像を系列として扱う入口になります。ただし、入力単位を細かくすればよいわけではありません。細かいほど系列長や計算量が増え、粗いほど局所情報が失われます。

表現学習の比較

NLPとCVの発展は、手作り特徴量から学習済み表現へ移ってきた歴史として見ると分かりやすくなります。

時代 NLP CV
手作り特徴量 n-gram、TF-IDF SIFT、HOG
深層学習 word embedding、RNN CNN
事前学習 BERT、GPT系 ImageNet事前学習、Vision Transformer
マルチモーダル text embedding image-text contrastive learning

この変化により、少量のタスク固有データでも、事前学習済みモデルを調整して使える場面が増えました。一方で、事前学習データの偏りやライセンス、個人情報、評価の透明性も設計問題になります。

Transformerアーキテクチャ

Transformerは self-attention 機構を中核とします。各単語(またはパッチ)が他のすべての単語と相互作用し、重要な部分に注目度を集中させます。

入力系列: [CLS] word_1 word_2 ... word_n
↓
埋め込み + 位置埋め込み
↓
Multi-head Self-Attention8-12 heads)
↓
Feed-Forward Network
↓
層正規化(Layer Norm)
↓
これを N 層繰り返す

Self-Attentionの計算

Q = X W_Q, K = X W_K, V = X W_V
Attention = softmax(Q K^T / √d_k) V

各ヘッドが異なる注目パターンを学び、マルチヘッド attention で複数の関係性を同時に捉えます。

事前学習パラダイム

Masked Language Modeling(MLM、NLP

テキストの一部をマスクし、周辺文脈から予測します。

入力: "猫は[MASK]の上で寝ている"
事前学習: [MASK]位置を予測(答え:机)
結果: 双方向文脈理解が習得される

Next Sentence Prediction(NSP)

2つの文が連続しているかを予測(BERT)。ただし効果が限定的として、後続モデルでは廃止。

Causal Language Modeling(CLM、生成モデル)

左から右への因果的予測。

入力: "猫は机"
次トークン予測: "の" (→ "猫は机の")
次トークン予測: "上" (→ "猫は机の上")
...

これにより、生成時に自然な継続が得られます。

代表タスクの設計観点

NLPとCVでは、タスクごとに出力の形が違います。出力の形が違うと、評価指標も失敗の見方も変わります。

タスク 出力 失敗の例
テキスト分類 クラス 文脈を読まずキーワードだけで分類する
固有表現抽出 spanとラベル 人名と組織名を取り違える
検索 文書順位 関連文書を上位に出せない
画像分類 クラス 背景に引きずられる
物体検出 boxとクラス 小さい物体を見落とす
セグメンテーション pixelごとのラベル 境界が粗い

生成タスクでは、正解が1つではないため、BLEUやROUGEのような自動指標だけでは不十分です。要約、対話、画像生成では、人間評価、タスク成功率、安全性評価を組み合わせます。

BLEU と ROUGE

BLEU(Bilingual Evaluation Understudy)

BLEU = BP × expw_n log(p_n))

機械翻訳の評価で使われます。1-gramから4-gramまでの一致度を見ます。ただし、異なる表現で同じ意味を表しても低スコアになる欠点があります。

ROUGE(Recall-Oriented Understudy for Gisting Evaluation)

ROUGE-L = 最長共通部分列(LCS)を参照文と生成文で比較

要約評価で用いられます。複数の参照文との比較で robustness が高まります。

データ拡張

NLPとCVでは、データ拡張の考え方が少し違います。

領域 注意点
NLP 言い換え、back translation、masking 意味が変わるとラベルが壊れる
CV crop、flip、色変換、mixup タスクによっては反転が不自然

データ拡張は汎化に効きますが、業務上あり得ない入力を作ると、逆に性能を悪化させます。医療画像、帳票、地図、部品検査のような領域では、専門家と一緒に拡張の妥当性を確認します。

Back Translation

元の文: "これは素晴らしい映画です" (日本語)翻訳(日本語→英語): "This is a wonderful movie"逆翻訳(英語→日本語): "これは見事な映画です"結果: 同じ意味だが別の表現

実運用での監視

NLP/CVモデルも、配備後に入力分布が変わります。

  • 新しい言い回しやスラングが増える
  • カメラや照明条件が変わる
  • OCR対象の帳票レイアウトが変わる
  • 画像の圧縮率や解像度が変わる
  • 利用者層が変わる

監視では、モデル出力だけでなく、入力の長さ、言語比率、画像サイズ、欠損、低信頼度率、人間レビューでの修正率を見ます。

ドリフト検知

import numpy as np
from scipy.stats import ks_2samp

# 参照分布(学習時)と新分布(本番)を比較
ref_lengths = [len(t) for t in train_texts]
prod_lengths = [len(t) for t in prod_texts]

ks_stat, p_value = ks_2samp(ref_lengths, prod_lengths)
if p_value < 0.05:
    print("テキスト長分布に有意な変化あり")

評価指標と失敗分析

ラベル設計と評価

NLP/CVの品質は、モデルだけでなくラベルの設計に強く依存します。曖昧なラベルを与えると、モデルは曖昧さまで学習します。分類、抽出、検出、セグメンテーションでは、何を正解とするかを先に決め、annotator間で判断が揃うかを確認します。

観点 NLP CV
ラベル境界 entityの開始・終了、曖昧語 bounding box、mask境界
文脈 前後文、話者、言語 撮影条件、遮蔽、解像度
評価 exact match、F1、human評価 IoU、mAP、pixel accuracy
失敗分析 表記ゆれ、否定、皮肉 小物体、暗所、重なり

実務では、全体平均だけでなくsegment別に評価します。言語、地域、端末、撮影条件、対象カテゴリごとに見ると、モデルがどこで弱いかが分かります。

混同行列(Confusion Matrix)分析

        予測:正      予測:負
実際:正  TP (80)     FN (20)
実際:負  FP (10)     TN (890)

Precision = TP / (TP + FP) = 80 / 900.89
Recall = TP / (TP + FN) = 80 / 100 = 0.80
F1 = 2 × (Precision × Recall) / (Precision + Recall) ≈ 0.84

稀なクラスの場合、全体accuracy だけでは不十分で、クラスごとのrecall を見る必要があります。

トランスフォーマーアーキテクチャの詳細

Attention is All You Needで提案されたTransformer は、RNN/CNNを使わず注意機構だけで構成されるモデルです。現在のNLP/CV最先端の基盤となっています。

マルチヘッド注意の計算

注意スコアは Query, Key, Value の3つから計算されます:

Attention(Q, K, V) = softmax(QK^T / sqrt(d_k)) V

ここで d_k は特徴量次元。複数ヘッド h を並列に実行:

import torch
import torch.nn as nn

class MultiHeadAttention(nn.Module):
    def __init__(self, d_model=512, num_heads=8):
        super().__init__()
        self.num_heads = num_heads
        self.d_k = d_model // num_heads
        self.W_q = nn.Linear(d_model, d_model)
        self.W_k = nn.Linear(d_model, d_model)
        self.W_v = nn.Linear(d_model, d_model)
        self.W_o = nn.Linear(d_model, d_model)
    
    def forward(self, Q, K, V):
        # Q,K,V shape: (batch, seq_len, d_model)
        batch_size = Q.shape[0]
        
        # 線形変換と分割
        Q = self.W_q(Q).reshape(batch_size, -1, self.num_heads, self.d_k)
        K = self.W_k(K).reshape(batch_size, -1, self.num_heads, self.d_k)
        V = self.W_v(V).reshape(batch_size, -1, self.num_heads, self.d_k)
        
        # 注意スコア計算
        scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.d_k ** 0.5)
        attn_weights = torch.softmax(scores, dim=-1)
        context = torch.matmul(attn_weights, V)
        
        # ヘッド統合
        context = context.reshape(batch_size, -1, self.num_heads * self.d_k)
        output = self.W_o(context)
        return output

Vision Transformerの構造

ViT は画像を16x16パッチに分割し、各パッチを線形埋め込みしてSequenceとして処理します:

  1. Image: (H, W, 3) → Patches: (N, P²×3)
  2. Linear Embedding: N × d_model
  3. Positional Embedding: Position encode
  4. Transformer Blocks: L層
  5. Classification Head: [CLS]トークンから出力

ImageNet で 80.6% 精度達成(ResNet-50: 76.1%)。計算効率が良く、事前学習データが多いほど性能向上します。

NLPにおける大規模言語モデルの実装

HuggingFaceの transformers ライブラリは、BERT、GPT-2、T5など数百のモデルアーキテクチャを提供します。

BERTによる文分類

from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch

model_name = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(
    model_name, num_labels=2
)

# 入力のトークン化
text = "I love this movie!"
inputs = tokenizer(text, return_tensors="pt", padding=True)

# 推論
with torch.no_grad():
    outputs = model(**inputs)
    logits = outputs.logits
    pred_label = logits.argmax(-1)

Fine-tuningの実装

from transformers import Trainer, TrainingArguments
from datasets import load_dataset

# データセット読み込み
dataset = load_dataset("imdb")

# 前処理
def preprocess(examples):
    return tokenizer(examples["text"], truncation=True, padding="max_length")

dataset = dataset.map(preprocess, batched=True)

# 学習設定
training_args = TrainingArguments(
    output_dir="./results",
    num_train_epochs=3,
    per_device_train_batch_size=8,
    per_device_eval_batch_size=8,
    learning_rate=2e-5,
    weight_decay=0.01,
)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=dataset["train"],
    eval_dataset=dataset["test"],
)

trainer.train()

Prompt Engineering と In-context Learning

大規模言語モデル(GPT-3, LLaMA等)では、プロンプト設計がパフォーマンスを大きく左右します:

Zero-shot: プロンプトのみで実行

Classify: "Great movie!"Sentiment: positive

Few-shot: 例を含める

Example 1: "Great movie!"Sentiment: positive
Example 2: "Terrible acting"Sentiment: negative
Classify: "Amazing performance!"Sentiment:

Chain-of-Thought: 推論過程をステップバイステップ記述

Q: 5+3-2 = ?
A: First, 5+3 = 8. Then, 8-2 = 6. So answer is 6.

CoTプロンプティングは数学問題で精度を大幅に向上させます。

コンピュータビジョンの物体検出

YOLOv5/v8 は実時間物体検出の最先端です。

import torch
from PIL import Image

model = torch.hub.load('ultralytics/yolov5', 'yolov5s')
img = Image.open('image.jpg')
results = model(img)

# 検出結果
detections = results.xyxy[0]  # x1, y1, x2, y2, confidence, class
for det in detections:
    x1, y1, x2, y2, conf, cls = det
    print(f"Class: {int(cls)}, Confidence: {conf:.2f}")

YOLOv8の改善点:

  • Decoupled head: 分類と位置推定の独立化
  • Anchor-free: アンカーボックスを廃止
  • 任意アスペクト比対応: 柔軟な入力サイズ

FPS(フレーム/秒)では YOLOv8 が最高性能です。

Embedding と言語表現

現代NLPの基礎は単語埋め込み(Word Embedding)です。

Word2Vec(Skip-gram と CBOW)

Word2Vec は、文脈から単語を予測:

from gensim.models import Word2Vec
import numpy as np

sentences = [
    "the quick brown fox jumps over the lazy dog".split(),
    "never jump over the lazy dog quickly".split(),
]

model = Word2Vec(
    sentences,
    vector_size=100,
    window=5,
    min_count=1,
    workers=4,
    sg=1  # Skip-gram: 1, CBOW: 0
)

# 単語の埋め込み取得
fox_vector = model.wv['fox']  # shape: (100,)

# 類似単語を検索
similar = model.wv.most_similar('fox', topn=5)
# [('quick', 0.85), ('brown', 0.82), ...]

# 単語演算
king_vec = model.wv['king'] - model.wv['man'] + model.wv['woman']
closest = model.wv.similar_by_vector(king_vec, topn=1)
# [('queen', 0.92)]

Skip-gram は稀な単語に強く、大規模コーパスで活躍。

GloVe(Global Vectors)

GloVe は共起行列の分解により埋め込みを学習:

最小化: Σ w_ij * (w_i · w_j + b_i + b_j - log(X_ij))²

Word2Vec より学習が安定し、小規模コーパスでも有効。

FastText

FastText はサブワード情報を活用。OOV(未知語)への対応が強い:

from gensim.models import FastText

model = FastText(
    sentences,
    vector_size=100,
    window=5,
    min_count=1,
    epochs=5
)

# 未知語の埋め込みも生成可能
unknown_word_vector = model.wv.get_vector('unknownword')
# サブワード(n-gram)を使って推定

LSTM と RNN による系列処理

LSTM は長期依存関係を捕捉:

import torch
import torch.nn as nn

class LSTMClassifier(nn.Module):
    def __init__(self, vocab_size, embedding_dim, hidden_dim, output_dim):
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=0)
        self.lstm = nn.LSTM(
            embedding_dim, hidden_dim,
            num_layers=2,
            bidirectional=True,
            dropout=0.3,
            batch_first=True
        )
        self.fc = nn.Linear(hidden_dim * 2, output_dim)
        self.dropout = nn.Dropout(0.3)
    
    def forward(self, text):
        embedded = self.dropout(self.embedding(text))
        output, (hidden, cell) = self.lstm(embedded)
        
        # 最後のタイムステップの隠れ状態を使用
        hidden_concat = torch.cat([hidden[-2], hidden[-1]], dim=1)
        logits = self.fc(self.dropout(hidden_concat))
        return logits

model = LSTMClassifier(
    vocab_size=10000,
    embedding_dim=100,
    hidden_dim=256,
    output_dim=2
)

Bidirectional LSTM は前後両方向の情報を活用。

注意機構による特徴抽出

class AttentionLayer(nn.Module):
    def __init__(self, hidden_dim):
        super().__init__()
        self.attention = nn.Linear(hidden_dim * 2, 1)
    
    def forward(self, output):
        # output: (batch_size, seq_len, hidden_dim * 2)
        scores = self.attention(output)  # (batch_size, seq_len, 1)
        weights = torch.softmax(scores, dim=1)
        
        # 重み付き和
        context = torch.sum(output * weights, dim=1)
        return context

Attention で重要な単語に高い重み。

機械翻訳における Seq2Seq

Encoder-Decoder アーキテクチャ:

Source text: "Hello world" 
→ Encoder RNN → Context vector
→ Decoder RNN → Target text: "Bonjour monde"

Encoder と Decoder は独立の LSTM:

class Seq2Seq(nn.Module):
    def __init__(self, encoder, decoder):
        super().__init__()
        self.encoder = encoder
        self.decoder = decoder
    
    def forward(self, source, target, teacher_forcing_ratio=0.5):
        # Encoding
        context, hidden = self.encoder(source)
        
        # Decoding
        outputs = []
        input_token = target[:, 0]  # <START>
        
        for t in range(1, target.shape[1]):
            output, hidden = self.decoder(input_token, hidden)
            outputs.append(output)
            
            # Teacher forcing
            if torch.rand(1).item() < teacher_forcing_ratio:
                input_token = target[:, t]
            else:
                input_token = output.argmax(1)
        
        return torch.stack(outputs, dim=1)

Teacher forcing:訓練時は正解トークンを入力。推論時は予測を使用。

コンピュータビジョンの最適化テクニック

Data Augmentation(データ拡張)

from torchvision import transforms
from PIL import Image

augmentation = transforms.Compose([
    transforms.RandomHorizontalFlip(p=0.5),
    transforms.RandomRotation(15),
    transforms.ColorJitter(brightness=0.2, contrast=0.2),
    transforms.RandomAffine(degrees=0, translate=(0.1, 0.1)),
    transforms.RandomPerspective(),
    transforms.GaussianBlur(kernel_size=3),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406],
                        std=[0.229, 0.224, 0.225])
])

image = Image.open('image.jpg')
augmented_images = [augmentation(image) for _ in range(8)]

Data Augmentation は過学習を抑制。

Transfer Learning(転移学習)

import torchvision.models as models

# ImageNet で事前学習したResNet
model = models.resnet50(pretrained=True)

# 最後の層を新しいタスク用に置き換え
num_classes = 10
model.fc = nn.Linear(model.fc.in_features, num_classes)

# オプション: 早い層は固定、後ろだけファインチューニング
for param in model.layer1.parameters():
    param.requires_grad = False
for param in model.layer2.parameters():
    param.requires_grad = False

optimizer = torch.optim.Adam(
    filter(lambda p: p.requires_grad, model.parameters()),
    lr=0.001
)

事前学習の低層は汎用特徴(エッジ、テクスチャ)を習得済み。

推論の最適化

# モデルの量子化
quantized_model = torch.quantization.quantize_dynamic(
    model,
    {nn.Linear},
    dtype=torch.qint8
)

# 半精度(FP16)
model.half()

# ONNX にエクスポート
torch.onnx.export(
    model,
    torch.randn(1, 3, 224, 224),
    'model.onnx',
    opset_version=12
)

量子化で 4倍メモリ削減。FP16 で 2倍高速化。

Transformer アーキテクチャの詳細(2017-現在)

Transformer は Vaswani et al.(2017)が提案した、RNN/CNN を置き換える基本単位。

Self-Attention メカニズム

Attention は「ある単語が他の単語とどのくらい関連するか」を計算:

Attention(Q, K, V) = softmax(Q * K^T / √d_k) * V
  • Q (Query):現在の単語が「何に注目するか」を表現
  • K (Key):全単語の「注目される特性」
  • V (Value):全単語の「実際の値」
例:文 "The cat sat on the mat"

単語 "sat"Query を使い:
- "The" に対する Attention スコア:0.1
- "cat" に対する Attention スコア:0.6  ← 高い(主語に注目)
- "on" に対する Attention スコア:0.2
- "mat" に対する Attention スコア:0.1

加重和 = 0.1 * V_the + 0.6 * V_cat + 0.2 * V_on + 0.1 * V_mat

Multi-Head Attention

複数の異なる観点から Attention を計算:

MultiHeadAttention(Q, K, V) = Concat(head_1, ..., head_h) * W^O

head_i = Attention(Q * W_i^Q, K * W_i^K, V * W_i^V)
  • h=8 個のヘッドが並列実行
  • 各ヘッドが異なる部分空間で Attention を学習
  • 構文的関係、意味的関係、長距離依存等を同時にキャプチャ

Transformer ブロックの構成

Input -> [MultiHeadAttention] -> [Add & Norm] -> [FFN] -> [Add & Norm] -> Output

FFN = Linear(d_model) -> ReLU -> Linear(d_model)

残差接続(Residual Connection)

y = LayerNorm(x + MultiHeadAttention(x))
z = LayerNorm(y + FFN(y))

残差接続により、深いネットワーク(100+層)でも安定して学習できます。

Position Encoding

Transformer に位置情報がないので、明示的に位置を エンコード:

PE(pos, 2i) = sin(pos / 10000^(2i / d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i / d_model))

各位置に異なる周波数の sin/cos が付与。これにより、モデルは相対位置を学習。

Vision Transformer(ViT):CNN から Transformer へ

画像を単なる「パッチ列」として扱い、NLP と同じ Transformer で処理:

入力画像 (H, W, C)
  ↓
H/P × W/P 個のパッチに分割(P=16)
  ↓
各パッチをフラット化 → 線形投影
  ↓
位置エンコード追加
  ↓
Transformer エンコーダ
  ↓
[CLS] トークン (頭部)で分類

メリット

  • CNN より計算効率が良い(スケール性)
  • 長距離依存を自然に習得
  • 事前学習データが豊富で、微調整で様々なタスクに適用

デメリット

  • CNN より学習データが必要(ImageNet は不十分)
  • 大規模事前学習必須(JFT-300M など)

Vision Transformer の具体例

import torch.nn as nn
from einops import rearrange

class ViT(nn.Module):
    def __init__(self, img_size=256, patch_size=16, num_classes=1000, dim=768, depth=12, heads=12):
        super().__init__()
        self.patch_embed = nn.Conv2d(3, dim, patch_size, patch_size)
        
        num_patches = (img_size // patch_size) ** 2
        self.pos_embed = nn.Parameter(torch.randn(1, num_patches + 1, dim))
        self.cls_token = nn.Parameter(torch.randn(1, 1, dim))
        
        self.transformer = nn.TransformerEncoder(
            nn.TransformerEncoderLayer(d_model=dim, nhead=heads, dim_feedforward=dim*4),
            num_layers=depth
        )
        self.mlp_head = nn.Sequential(
            nn.LayerNorm(dim),
            nn.Linear(dim, num_classes)
        )
    
    def forward(self, x):
        # x: (B, 3, H, W)
        x = self.patch_embed(x)  # (B, dim, H', W')
        x = rearrange(x, 'b c h w -> b (h w) c')  # (B, num_patches, dim)
        
        cls = repeat(self.cls_token, '1 1 d -> b 1 d', b=x.shape[0])
        x = torch.cat([cls, x], dim=1)  # (B, num_patches+1, dim)
        
        x = x + self.pos_embed
        x = self.transformer(x)
        
        cls_output = x[:, 0]  # CLS トークン
        return self.mlp_head(cls_output)

Stanford CS231n - Convolutional Neural Networks (CNN) 深掘り

Stanford が提供する CNN 最大級の講座。画像分類の基礎から最新手法まで。

CNN の層設計(歴史的展開)

LeNet-5(1998)

Input -> Conv -> ReLU -> Pool -> Conv -> ReLU -> Pool -> FC -> Output

MNIST(手書き数字)認識に使用。3 層程度で十分。

AlexNet(2012)

11×11 Conv -> ReLU -> MaxPool
-> 5×5 Conv -> ReLU -> MaxPool
-> 3×3 Conv -> ReLU
-> 3×3 Conv -> ReLU
-> 3×3 Conv -> ReLU -> MaxPool
-> FC(4096) -> Dropout -> FC(4096) -> Dropout -> FC(1000)

ImageNet で大勝利。GPU 計算により深いネットワークが可能に。

VGGNet(2014)

小さなカーネル(3×3)を積み重ねる
大きなカーネル(5×5, 7×7)の代わりに 3×3 を 3 層
理由:受容野は同じだが、パラメータが少ない、非線形が増える

ResNet(2015)

残差接続により 100+ 層が可能
y = F(x) + x  # スキップ接続

勾配消失問題を解決。

DenseNet(2017)

全ての前層に接続
dense_output = Concat([x, F_1(x), F_2([x, F_1(x)]), ...])

Batch Normalization

各層の入力分布を正規化し、学習を高速化:

y = γ * (x - μ) / √(σ² + ε) + β

効果

  • 内部共変量シフト(Internal Covariate Shift)を軽減
  • より高い学習率を使用可能
  • Dropout が不要に(正則化効果)
  • より深いネットワークを訓練可能
import torch.nn as nn

class CNNBlock(nn.Module):
    def __init__(self, in_channels, out_channels):
        super().__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1)
        self.bn = nn.BatchNorm2d(out_channels)
        self.relu = nn.ReLU(inplace=True)
    
    def forward(self, x):
        x = self.conv(x)
        x = self.bn(x)
        x = self.relu(x)
        return x

受容野(Receptive Field)の計算

ネットワークの各ピクセルが「元画像のどのサイズを見ているか」:

カーネルサイズ 3×3、stride 1:受容野 3
カーネルサイズ 3×3、stride 1、2 層:受容野 5
カーネルサイズ 3×3、stride 1、3 層:受容野 7
...

十分な受容野がないと、長距離依存(グローバル構造)を学習できません。

Hugging Face Transformers:NLP 実装の標準

Hugging Face は、BERT, GPT, T5 等の事前学習モデルを統一インターフェースで提供。

パイプライン API(高レベル)

from transformers import pipeline

# 感情分析
classifier = pipeline("sentiment-analysis")
result = classifier("I love Transformers!")
# Output: [{'label': 'POSITIVE', 'score': 0.9999}]

# テキスト生成
generator = pipeline("text-generation", model="gpt2")
result = generator("Once upon a time")

# 質問応答
qa = pipeline("question-answering", model="deepset/roberta-base-squad2")
result = qa(question="What is AI?", context="AI is...")

# 固有表現抽出
ner = pipeline("ner")
result = ner("Obama was born in Hawaii")
# Output: [{'entity': 'PERSON', 'word': 'Obama'}, ...]

モデル API(低レベル)

from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch

# トークナイザとモデルを自動ロード
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
model = AutoModelForSequenceClassification.from_pretrained("bert-base-uncased")

# テキストをトークン化
inputs = tokenizer("Hello world", return_tensors="pt")
# {'input_ids': [[101, 7592, 1000, 102]], 'attention_mask': [[1, 1, 1, 1]]}

# 推論
with torch.no_grad():
    outputs = model(**inputs)
    logits = outputs.logits

# クラス確率
probabilities = torch.softmax(logits, dim=-1)

ファインチューニング

from transformers import Trainer, TrainingArguments

training_args = TrainingArguments(
    output_dir="./results",
    num_train_epochs=3,
    per_device_train_batch_size=8,
    per_device_eval_batch_size=16,
    warmup_steps=500,
    weight_decay=0.01,
    logging_dir="./logs",
)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
)

trainer.train()

まとめ

NLPとCVは応用分野として分かれていますが、どちらも「良い表現を学ぶ」ことが中心にあります。深層学習とLLMの章で見た考え方が、他分野へどう広がるかを見るための章です。

参考文献

講義・記事

解説・補助