見出し画像

超コンパクトでも高性能 Phi-4を試す

Microsoftが超コンパクト(と言っても14B)で70Bモデル超えの高性能を売りにしたLLM、Phi-4を発表した。Ollamaにも入っていたので早速試してみた。

異次元の高性能

元々Phiシリーズは、1.5Bとかの極端に小さいモデルでもそこそこ使える、会話できるというところを目指して作られていた。そこからすると14Bはまあまあ巨大なのだが、もしかするとCopilot+PCで動作させることを意識してるのかもしれない。

このサイズなので従来のPhiシリーズより賢いのは当たり前なのだが、一応試してみる。

% ollama run phi4
>>> quaternionについて説明して

クォータニオン(Quaternion)は、ベクトル解析や回転表現で広く使用される数学的構造です。通常の3次元空間における回転を扱う際に特に便利
とされています。以下は、クォータニオンの基本的な説明です。

### 概要

クォータニオンは4次元の数であり、1つの実部(スカラー成分)と3つの虚部(ベクトル成分)から構成されます。一般的にクォータニオン $${ q
}$$ は以下のように表されます:

$${ q = w + xi + yj + zk }$$

ここで、$${ w, x, y, z }$$ は実数であり、$${ i, j, k }$$ は虚数単位です。これらの単位は以下のような関係を満たします:

- $${ i^2 = j^2 = k^2 = ijk = -1 }$$
- $${ ij = k, \quad ji = -k }$$
- $${ jk = i, \quad kj = -i }$$
- $${ ki = j, \quad ik = -j }$$

### 特徴

1. **回転の表現**: クォータニオンは3次元空間における回転を直感的かつ効率的に表すことができます。特定の角度と回転軸を用いて、任意の点
やベクトルの回転を計算する際に有用です。

2. **スキュー関数(シフト)**: クォータニオンは3D空間での移動と回転(ローテーション・トランスレーション)を一貫して扱うことができま
す。特に、キャメラやロボットの姿勢制御などに利用されます。

3. **ソフトウェアおよびゲーム開発**: 三次元環境での回転を扱う際、クォータニオンは歪みが少なく、数学的に安定しているため、コンピュー
タグラフィックスやロボティクス、VR/ARアプリケーションで広く使用されます。

4. **環境の連鎖的な結合**: クォータニオンは他のクォータニオンとの積(乗算)を通じて、複数の回転を効率よく結合することができます。

### 計算

- **加法**: クォータニオン同士は成分ごとに足し算されます。

- **乗法**: 2つのクォータニオン $${ q_1 = a_1 + b_1i + c_1j + d_1k }$$ と $${ q_2 = a_2 + b_2i + c_2j + d_2k }$$ の積は以下のように計算
されます。

$${
q_1 q_2 = (a_1a_2 - b_1b_2 - c_1c_2 - d_1d_2) + (a_1b_2 + b_1a_2 + c_1d_2 - d_1c_2)i \\
+ (a_1c_2 - b_1d_2 + c_1a_2 + d_1b_2)j + (a_1d_2 + b_1c_2 - c_1b_2 + d_1a_2)k
}$$

- **複素共役**: クォータニオン $${ q = w + xi + yj + zk }$$ の共役は $${ q^* = w - xi - yj - zk }$$ です。

- **逆数**: もしクォータニオン $${ q }$$ の大きさ(ノルム)が0でない場合、その逆数は以下のように計算されます。

$${
q^{-1} = \frac{q^*}{|q|^2}
}$$

ここで、$${ |q| = \sqrt{w^2 + x^2 + y^2 + z^2} }$$ です。

### 応用

- **コンピュータグラフィックス**: モデルの回転やカメラワーク。
- **ロボティクス**: ロボットアームの制御。
- **航空宇宙工学**: 衛星の姿勢制御。

以上がクォータニオンに関する基本的な説明です。それらは数学的に強力で、3Dコンピューティング分野で特に価値を持っています。


なかなか良い。というかたいていの人間よりは賢い。
slerpについても説明してもらおう

>>> slerpについて説明して
SLERP(Spherical Linear Interpolation)は、クォータニオンの間をスムーズに補間するための手法です。特に3Dグラフィックスやロボティクス
で回転を自然に移行させる際に使用されます。SLERPは、球面上での直線的な補間として理解することができます。

### SLERPの特徴

1. **一定速度**: SLERPでは、回転角の変化が時間に対して均等です。これは、時間経過に応じて同じ量だけ回転するという意味であり、直感的な
動きを実現します。

2. **球面上の補間**: SLERPはクォータニオンが表す4次元ユークリッド空間において行われます。このため、補間結果も回転操作として正しいこ
とを保証します。

3. **正規化**: SLERPで得られるクォータニオンは常に正規化されています(ノルムが1)。これは、補間結果が有効な回転表現として保持される
ことを意味します。

### SLERPの計算

2つのクォータニオン $${ q_0 }$$ と $${ q_1 }$$ の間でパラメータ $${ t \in [0, 1] }$$ を用いて補間する場合、SLERPは以下のように計算されます

1. **内積を求める**:
$${
\text{dot} = q_0 \cdot q_1
}$$

2. **必要に応じて共役を取り、補間方向を調整する**:
- もし $${\text{dot} < 0}$$ の場合は、$${ q_1 = -q_1 }$$ を設定します。これは、二つのクォータニオンが反対側にある場合に補間をスムース
にするためです。

3. **角度と距離を計算**:
- 角度 $${\theta}$$ は以下で求まります。
$${
\theta = \arccos(\text{dot})
}$$

4. **SLERPの補間式**:
- $${ t = 0 }$$ のとき、結果は $${ q_0}$$、$${ t = 1 }$$ のときには $${ q_1 }$$ です。
- 中間点を計算する際の式は以下です:
$${
q(t) = \frac{\sin((1-t)\theta)}{\sin(\theta)} q_0 + \frac{\sin(t\theta)}{\sin(\theta)} q_1
}$$

### 使用例

SLERPは、アニメーションのフレーム間でキャラクターのモデルを滑らかに回転させたり、ロボットがゆっくりと目標姿勢に移行する際に用いられ
ます。例えば、3Dビデオゲームでキャラクターのカメラをスムーズに向きを変えたり、仮想現実(VR)アプリケーションでヘッドトラッキングを行
う際などに利用されます。

SLERPは非常に有効ですが、計算コストも少し高いため、必要な場面でのみ使用すると良いでしょう。また、クォータニオンの内積が-1に近い場合
(ほぼ180度回転)は別の補間手法を考慮することもあります。


ここで試したのはQ4_K_Mなので、もっと良い答えが欲しければQ8とかFP16とかも選択肢に入る。Q4_K_Mはわずか9GBしかない。M2以降のMacなら余裕だし、このレベルのLLMを持ち歩けるのは単純に嬉しい。

プログラムも書けるかやってみる。

>>> テトリス作って
テトリスをプログラムで作成するには、ゲームの基本的なルールやコンポーネントを理解し、それらを実装する必要があります。以下はシンプルな
Python版テトリスの概要と実装例です。

### 必要なコンポーネント

1. **ゲームフィールド(ボード)**: 通常10x20の2D配列で表現されます。
2. **テトリミノブロック**: 7種類の形状からランダムに選ばれ、回転や移動が可能です。
3. **ゲームルール**:
   - テトリミノをフィールドに落とし、全ての行を埋めると消去します。
   - 消去した後で新しいブロックが生成されます。
   - 特定のレベルに達するごとに落下速度が上昇します。

### 実装例(Python)

以下は基本的なテトリスゲームを作成するための簡単なコードです。`curses`ライブラリを使用して、ターミナルでプレイ可能にします。

```python
import random
import time
import curses

# テトリミノの形状(0が空白)
TETROMINOES = [
    [[1, 1, 1, 1]], # I
    [[2, 2], [2, 2]], # O
    [[3, 0, 0], [3, 3, 3]], # T
    [[4, 4, 0], [0, 4, 4]], # S
    [[0, 5, 5], [5, 5, 0]], # Z
    [[6, 6, 6], [0, 6, 0]], # J
    [[0, 7, 7], [7, 7, 0]]  # L
]

def rotate_clockwise(shape):
    return [ [ shape[y][x] for y in range(len(shape)) ] 
             for x in range(len(shape[0]) - 1, -1, -1) ]

def check_collision(board, piece, offset):
    off_x, off_y = offset
    for cy, row in enumerate(piece):
        for cx, cell in enumerate(row):
            try:
                if cell and board[cy + off_y][cx + off_x]:
                    return True
            except IndexError:
                return True
    return False

def join_matrices(mat1, mat2, mat2_off):
    off_x, off_y = mat2_off
    for cy, row in enumerate(mat2):
        for cx, val in enumerate(row):
            if val:
                mat1[cy + off_y][cx + off_x] = val
    return mat1

def clear_rows(board):
    new_board = [row for row in board if any(cell == 0 for cell in row)]
    cleared_rows = len(board) - len(new_board)
    new_board = [[0 for _ in range(10)]] * cleared_rows + new_board
    return new_board, cleared_rows

def main(stdscr):
    curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK)
    curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK)
    curses.init_pair(3, curses.COLOR_YELLOW, curses.COLOR_BLACK)
    curses.init_pair(4, curses.COLOR_BLUE, curses.COLOR_BLACK)
    curses.init_pair(5, curses.COLOR_MAGENTA, curses.COLOR_BLACK)
    curses.init_pair(6, curses.COLOR_CYAN, curses.COLOR_BLACK)
    
    stdscr.nodelay(True)
    curses.curs_set(False)
    screen = curses.initscr()
    h, w = 20, 10
    board = [[0 for _ in range(w)] for _ in range(h)]
    
    def print_board(board):
        for y, row in enumerate(board):
            for x, cell in enumerate(row):
                stdscr.addstr(y, x*2, "██" if cell else "  ")
        stdscr.refresh()
        
    falling_piece = random.choice(TETROMINOES)
    piece_offset = [w // 2 - len(falling_piece[0]) // 2, 0]
    
    while True:
        time.sleep(0.5)
        next_y = piece_offset[1] + 1
        
        if not check_collision(board, falling_piece, (piece_offset[0], next_y)):
            piece_offset = [piece_offset[0], next_y]
        else:
            board = join_matrices(board, falling_piece, piece_offset)
            board, cleared_rows = clear_rows(board)
            if any(cell != 0 for row in board[:4] for cell in row):
                break
            piece_offset = [w // 2 - len(falling_piece[0]) // 2, 0]
            falling_piece = random.choice(TETROMINOES)

        print_board(board)

curses.wrapper(main)
```

### 説明

- **TETROMINOES**: 各テトリミノの形状をリスト化。
- **rotate_clockwise**: テトリミノを時計回りに90度回転させる関数。
- **check_collision**: ボード上での衝突をチェックする関数。
- **join_matrices**: 新しいブロックが落下した位置に反映されるようにボードとブロックを結合する。
- **clear_rows**: 完全に埋まった行を消去し、得点を返す関数。

### 注意事項

このコードは基本的なテトリスのプレイ感覚を表現していますが、完全なゲームとしては以下を考慮する必要があります:

- **制御**: キー入力によるブロックの移動や回転。
- **スコアシステム**: 行を消去した際の得点計算とレベル管理。
- **ゲームオーバーの判定**。

このコードは`curses`ライブラリで動作し、ターミナル上で簡易的なグラフィックを表示します。さらに洗練されたインターフェースや機能が必要
な場合は、Pygameのようなゲーム開発用ライブラリも検討できます。

このプログラムを実行したところ、闇夜のカラスのようなアホみたいな実行結果になった。

Pygameで作り直させてみる。

>>> pygameでテトリスを作って
Pygameを使って簡単なテトリスクラシックゲームを作成するには、いくつかの主要なコンポーネントが必要です。ここでは基本的な構造と処理方法
を説明します。

### 必要な準備

まず、Pygameがインストールされていることを確認してください:

```bash
pip install pygame
```

### プログラムの基本構造

以下に簡単なテトリスゲームのサンプルコードを示します。

```python
import pygame
import random
import sys

# Pygame初期化
pygame.init()

# ウィンドウ設定
WINDOW_WIDTH = 300
WINDOW_HEIGHT = 600
BLOCK_SIZE = 30
GRID_WIDTH = WINDOW_WIDTH // BLOCK_SIZE
GRID_HEIGHT = WINDOW_HEIGHT // BLOCK_SIZE

# 色の設定
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)

# フォント設定
pygame.font.init()
font = pygame.font.Font(None, 36)

# ゲームクラス
class Tetris:
    def __init__(self):
        self.grid = [[0 for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
        self.game_over = False
        self.score = 0

        # テトリミノの形状定義(I, O, T, S, Z, J, L)
        self.shapes = [
            [[1, 1, 1, 1]],  # I
            [[1, 1], [1, 1]],  # O
            [[0, 1, 0], [1, 1, 1]],  # T
            [[0, 1, 1], [1, 1, 0]],  # S
            [[1, 1, 0], [0, 1, 1]],  # Z
            [[1, 0, 0], [1, 1, 1]],  # J
            [[0, 0, 1], [1, 1, 1]]   # L
        ]
        
        self.current_shape = self.new_piece()
        self.next_shape = self.new_piece()

    def new_piece(self):
        shape = random.choice(self.shapes)
        position = [0, GRID_WIDTH // 2 - len(shape[0]) // 2]
        return {'shape': shape, 'position': position}

    def draw_grid(self, screen):
        for y in range(GRID_HEIGHT):
            for x in range(GRID_WIDTH):
                rect = pygame.Rect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
                color = WHITE if self.grid[y][x] else BLACK
                pygame.draw.rect(screen, color, rect)

    def draw_shape(self, screen, shape, position):
        for y, row in enumerate(shape['shape']):
            for x, cell in enumerate(row):
                if cell:
                    rect = pygame.Rect((position[0]+x) * BLOCK_SIZE, (position[1]+y) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
                    pygame.draw.rect(screen, WHITE, rect)

    def rotate(self, shape):
        # テトリミノの回転(90度)
        return {'shape': [list(row) for row in zip(*shape['shape'][::-1])], 'position': shape['position']}

    def move_down(self):
        shape = self.current_shape
        if not self.collision(shape, 0, 1):
            shape['position'][1] += 1
        else:
            self.lock_piece()
            self.clear_lines()
            if not self.start_new_piece():
                self.game_over = True

    def start_new_piece(self):
        self.current_shape = self.next_shape
        self.next_shape = self.new_piece()

        return not self.collision(self.current_shape, 0, 0)

    def lock_piece(self):
        shape = self.current_shape
        for y, row in enumerate(shape['shape']):
            for x, cell in enumerate(row):
                if cell:
                    self.grid[shape['position'][1] + y][shape['position'][0] + x] = 1

    def clear_lines(self):
        lines_to_clear = [index for index, row in enumerate(self.grid) if all(row)]
        for index in lines_to_clear:
            del self.grid[index]
            self.grid.insert(0, [0 for _ in range(GRID_WIDTH)])
            self.score += 1

    def collision(self, shape, dx=0, dy=0):
        for y, row in enumerate(shape['shape']):
            for x, cell in enumerate(row):
                if cell:
                    nx = shape['position'][0] + x + dx
                    ny = shape['position'][1] + y + dy
                    if nx < 0 or nx >= GRID_WIDTH or ny >= GRID_HEIGHT or (ny >= 0 and self.grid[ny][nx]):
                        return True
        return False

    def draw_next_shape(self, screen):
        next_rect = pygame.Rect(WINDOW_WIDTH - BLOCK_SIZE * len(self.next_shape['shape']), 0, BLOCK_SIZE * 
len(self.next_shape['shape']), BLOCK_SIZE * len(self.next_shape['shape']))
        for y, row in enumerate(self.next_shape['shape']):
            for x, cell in enumerate(row):
                if cell:
                    rect = pygame.Rect(next_rect.x + x * BLOCK_SIZE, next_rect.y + y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
                    pygame.draw.rect(screen, WHITE, rect)

# メインループ
def main():
    screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
    pygame.display.set_caption("Tetris")
    clock = pygame.time.Clock()
    
    tetris = Tetris()

    while not tetris.game_over:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT and not tetris.collision(tetris.current_shape, -1, 0):
                    tetris.current_shape['position'][0] -= 1
                elif event.key == pygame.K_RIGHT and not tetris.collision(tetris.current_shape, 1, 0):
                    tetris.current_shape['position'][0] += 1
                elif event.key == pygame.K_DOWN:
                    if not tetris.collision(tetris.current_shape, 0, 1):
                        tetris.current_shape['position'][1] += 1
                    else:
                        tetris.lock_piece()
                        tetris.clear_lines()
                        if not tetris.start_new_piece():
                            tetris.game_over = True
                elif event.key == pygame.K_UP:
                    rotated = tetris.rotate(tetris.current_shape)
                    if not tetris.collision(rotated):
                        tetris.current_shape = rotated

        tetris.move_down()

        screen.fill(BLACK)
        tetris.draw_grid(screen)
        tetris.draw_shape(screen, tetris.current_shape, tetris.current_shape['position'])
        tetris.draw_next_shape(screen)

        score_text = font.render(f'Score: {tetris.score}', True, WHITE)
        screen.blit(score_text, (5, 5))

        pygame.display.flip()
        clock.tick(10)  # ゲームの速度

if __name__ == "__main__":
    main()
```

### 説明

1. **初期設定**:
   - Pygameを初期化し、ウィンドウサイズとブロックサイズを設定します。
   - ゲームのグリッドを作成し、各テトリミノの形状を定義します。

2. **ゲームクラス**:
   - `Tetris`クラスは、現在と次のテトリミノを管理し、行削除や点数計算を行います。
   - グリッドに対する衝突チェック、テトリミノの移動と回転、新しいテトリミノの生成などの機能を持ちます。

3. **メインループ**:
   - イベントを処理し(キー入力による左右移動や回転)、ゲームロジックを更新します。
   - スクリーンの描画とフレーム毎の更新を行います。

このコードは基本的なテトリスゲームです。拡張機能として、より高度なグラフィック表示や音声エフェクト、ハイスコア管理などが考えられます
。また、ゲームの難易度を調整するために、落下速度やゲームオーバー時の処理も追加できます。

確かにテトリスのようなゲームができた。

ソリッドなテトリス

あまりにも落ちてくるスピードが速いのでスクショを撮ろうとすると縦に重なってしまったが、ちゃんと回転するし移動できる。

コンテキストサイズは16Kなので、そこまで複雑なものは作れないが、ちょっとしたプログラムを書かせるならたいていの人間よりマシだろう。

ただ、テトリスを書ける理由は、テトリスがアルゴリズムがよく知られたゲームだからであり、未知のゲームやよく知られていないゲームをいきなり作らせることは難しい。レイフォースのようなホーミングレーザーの出てくるゲームを作ってと言ったら案の定、全くデタラメなコードが出てきた。

ちょっとしたエラーメッセージの解釈やちょっとした翻訳にはもしかしたら使えるかもしれない。

さっきの文章を英訳させてみる。

>>> 以下のメッセージを英訳して   テトリスを書ける理由は、テトリスがアルゴリズムがよく知られたゲームだからであり、未知のゲームやよく知
... られていないゲームをいきなり作らせることは難しい。レイフォースのようなホーミングレーザーの出てくるゲームを作ってと言ったら案の定、全
... くデタラメなコードが出てきた。
The reason why Tetris can be implemented is that it's a well-known game with familiar algorithms, making it challenging to start 
creating an entirely new or unfamiliar game right away. If you were to ask someone to create a game like RayForce, which involves 
homing lasers appearing, predictably, they would produce completely nonsensical code.

Here’s the translation:

"The reason Tetris can be written is that its algorithms are well-known. It's difficult to suddenly make an unfamiliar game from 
scratch. If you asked for a game similar to RayForce with homing laser attacks, as expected, completely nonsensical code would 
emerge."

あ、便利だね。このレベルの翻訳エンジンがローカルで動くようになったということだけでも喜ばしい。もちろん要約もできる。

昨日のnote(https://note.com/shi3zblog/n/n432905d15632)を要約させてみる。

この文章は、「技研AIマーケット」というイベントの企画とその詳細について述べています。主に以下のポイントが要約されます:

1. **AIの可能性**: AI技術が進化し、漫画やアニメを作成する能力があるものの、プロはなかなかその分野に踏み込めていません。これにより、
アマチュアへの活動の機会が広がっています。

2. **イベント企画の背景**: 技研ベースでの同人誌「2015年の人工知能」の成功と、「Space Detective 宇宙の探偵(仮)」の新刊出版計画を契機
に、AI関連の作品や技術を集めたイベントが企画されました。

3. **展示内容**: AIで生成した同人誌、画集、動画、マンガ、アニメなど多岐にわたる作品が含まれます。また、AI関連のハードウェアやWebサー
ビスも展示される予定です。

4. **イベント規模**: 技研ベース全館を使用し、これまでにない大規模なイベントとして開催されます。

5. **参加条件**: AI関連のあらゆる作品や技術が対象です。GPUを搭載した持ち運び可能なケースなども含まれます。

6. **参加募集とスケジュール**: サークルの参加者を募集中で、1月20日が締め切りです。応募は抽選によるため、早めの申し込みが求められます


このイベントはAI技術とクリエイティブな作品を融合させることで、新たな可能性を模索する場として位置づけられています。

要約エンジンの性能をなんとか上げようとしていた昔のことを考えると、まるで夢のようである。

DeepSeekV3のollama版もそろそろ出るらしいので期待したい。まあ動く環境は限られるが