🧠

ツンからデレに変換する 簡易LM を作ってみた!

に公開
📅 更新履歴
  • 2025/12/24: 初投稿

1. はじめに

はじめまして、Daichi と申します。
今年の4月ごろから、自分だけのAIを作りたいな~と思い、趣味でAIの勉強をしています。
学びの記録は、基本的に LinkedIn にアウトプットしてます。

2. 背景

現在、LM (Transformer) に挑戦しており、
仕組みを理解するために、手元で動く最小構成の簡易LMが必要になりました。
そこで、PyTorch を用いて開発することにしました。

題材として単純な翻訳ではなく、
ツン文から、デレ文へ変換できたら面白いなと思い、作ってみました。

3. 概要

論文[1] を参考にしつつ、Encoder + Decoder 構成で実装し、
推論時に同じ出力に固定されすぎないよう Top-p Sampling を採用しました。
モデル名は、「Dere + Transformer」から Dereformer としました。

  • 何ができるのか

    • ツン文から、デレ文に変換
  • 技術スタック

4. デモ

Input: 急に電話してくんな💢
Output: でも、声聞けるとちょっと安心する🥺

うまくできなかった例

Input: 調子乗んなよバカ💢
Run1: それくらい自信持ってくれてくれてた方が、あたしい🥰
Run2: それくらい自信持ってくれてくれてくれてくれてた方が、あたしも嬉しい🥰

メモ:学習データ量が少ない影響で、同じフレーズの繰り返しが出やすいと考えられる。

5. 詳細

5.1. 学習について

  • 環境:CPU
  • 時間:約1.5時間 (1100 Epochs)

5.2. データセットについて

  • 220個
    • 「1ペア = input 1文 + output 1文」を110個用意
      • こんな感じのを LLMに作ってもらいました
        tsun2dere.json
        [
          {
            "input": "べ、別にあんたのことなんか😠",
            "output": "あんたのこと好きに決まってんだろ🥰"
          },
          {
            "input": "う、うるさいなっ!💢",
            "output": "あんたの声、ちょっと落ち着く🥰"
          }
        ]
        

5.3. ハイパラメータについて

  • d_model = 128 (次元数)
  • nhead = 4 (Head 数)
  • num_layers = 1 (Encoder、 Decoderのレイヤー数)

5.4. Tokenizer について

学習用のデータセットに含まれる文字を、
語彙リストに登録する形とし、自作しました。(1文字:1トークンID)

6. 概要図

開く

7. 開発で苦労したこと

7.1. dimの考え方

PyTorch は今まで触ったことがなく、dim の概念の理解に苦労しました。
そこで、tensor 情報を表示する関数を作り、色々試して理解していきました。

def inspect_tensor
# -*- coding:utf-8 -*-
import torch

def inspect_tensor(x, verbose=None) -> torch.Tensor:
    if not isinstance(x, torch.Tensor):
        x = torch.tensor(x)

    print("🔍 Tensor Structure")
    print(f"  🧱 shape: {x.shape}")
    print(f"  📐 dims : {x.dim()} --> {[f'dim:{d}' for d in range(x.dim())]}")
    for dim, size in enumerate(x.shape):
        print(f"     🔷 [dim:{dim}]: {size}")

    if verbose is not None:
        print(f"  📘 Details:")
        _inspect_tensor(x, first_indent_size=6, verbose=verbose)

    return x


def _inspect_tensor(x, first_indent_size, verbose) -> None:
    # Indent settings
    first_indent = ' ' * first_indent_size
    indent = ' ' * 2

    # Verbose handling
    if verbose < 0:
        # Display all dimensions
        break_dim = x.dim() if x.dim() > 0 else 1
    else:
        # Display up to and including dim=verbose (0-based index)
        break_dim = verbose + 1

    # Define a recursive function
    def _recursive(tensor, dim=0):
        # Nothing if the dimension equals the breaking dimension
        if dim == break_dim:
            return

        # Scalar
        elif tensor.dim() == 0:
            print(f"{first_indent + indent * dim}[] -> {tensor.item()}")
            return

        else:
            for i in range(tensor.size(0)):
                print(f"{first_indent + indent * dim}[dim:{dim}] index:{i} -> ", end='')

                # Feature
                if tensor.dim() == 1:
                    print(f"{tensor[i].item()}")

                # Matrix
                else:
                    print(f"{tensor[i].detach().cpu().numpy().tolist()}")
                    _recursive(tensor[i], dim=dim+1)

    # Execute the inner function
    _recursive(x)
sample01.py
x = inspect_tensor(
    [
        # [dim: 0] -- index: 0
        [1, 2, 3],  # [dim: 1] -- indexes: 0, 1, 2
        # [dim: 0] -- index: 1
        [4, 5, 6]   # [dim: 1] -- indexes: 0, 1, 2
    ],
    verbose=-1
)

print(x)
output (sample01.py)
🔍 Tensor Structure
  🧱 shape: torch.Size([2, 3])
  📐 dims : 2 --> ['dim:0', 'dim:1']
     🔷 [dim:0]: 2
     🔷 [dim:1]: 3
  📘 Details:
      [dim:0] index:0 -> [1, 2, 3]
        [dim:1] index:0 -> 1
        [dim:1] index:1 -> 2
        [dim:1] index:2 -> 3
      [dim:0] index:1 -> [4, 5, 6]
        [dim:1] index:0 -> 4
        [dim:1] index:1 -> 5
        [dim:1] index:2 -> 6
tensor([[1, 2, 3],
        [4, 5, 6]])
sample02.py
x = torch.tensor([1, 2, 3])
print(x)

inspect_tensor(x, verbose=-1)
print()

x = x.unsqueeze(0)
print(x)

inspect_tensor(x, verbose=-1)
output (sample02.py)
tensor([1, 2, 3])
🔍 Tensor Structure
  🧱 shape: torch.Size([3])
  📐 dims : 1 --> ['dim:0']
     🔷 [dim:0]: 3
  📘 Details:
      [dim:0] index:0 -> 1
      [dim:0] index:1 -> 2
      [dim:0] index:2 -> 3

tensor([[1, 2, 3]])
🔍 Tensor Structure
  🧱 shape: torch.Size([1, 3])
  📐 dims : 2 --> ['dim:0', 'dim:1']
     🔷 [dim:0]: 1
     🔷 [dim:1]: 3
  📘 Details:
      [dim:0] index:0 -> [1, 2, 3]
        [dim:1] index:0 -> 1
        [dim:1] index:1 -> 2
        [dim:1] index:2 -> 3

7.2. batch_first の扱い

TransformerEncoderLayer などでは
引数に batch_first の設定項目があり、
それに合った shape を input として渡す必要があります。

違いについて
例えば、以下の2文をバッチとして考えてみます。
 - "I" "like" "you"
 - "He" "is" "kind"

  • batch_first=False の場合:

    • [dim0, dim1] = [seq_len, batch_size]
      👉 縦に流れる感じ (時間方向)
      イメージ
      # shape: [3, 2]
      [
        ["I",    "He"],
        ["like", "is"],
        ["you",  "kind"]
      ]
      
  • batch_first=True の場合

    • [dim0, dim1] = [batch_size, seq_len]
      👉 横に並べる感じ (普通のデータ表記に近い形)
      イメージ
      # shape: [2, 3]
      [
        ["I", "like", "you"],
        ["He", "is", "kind"]
      ]
      

8. 終わりに

最後まで読んでくださりありがとうございました🙇‍♂️

以前「個人でAIを作るのは無理だろう」と言われたことがあったのですが、
ひとまず形にできたため、自分を信じてよかったと思ってます。
日々 少しずつ学んで積み上げていけば、時間はかかるが不可能ではないと感じています。

また、学んでる中で新しいAIの構想も思いついたため、
現在は論文の執筆にも着手しています。

脚注
  1. Attention Is All You Need ↩︎

Discussion