iOSのstate

stateとは、アプリのローカルメモリ
アプリを終了すると消える仕組み、アプリごとにローカルメモリを持つ

iOSアプリは1つの プロセス として動作する

OSは各プロセスに 独立した仮想アドレス空間 を割り当てる
アプリA (プロセスA) → 仮想メモリ 0x0000_0000〜0xFFFF_FFFF
アプリB (プロセスB) → 仮想メモリ 0x0000_0000〜0xFFFF_FFFF

iOSはメモリ管理に 制約が強い
バックグラウンドアプリは一定時間で メモリを解放される
メモリ不足になるとアプリが強制終了されることもある

iOSも Linux と同じく プロセスの中にスレッドがある 仕組みになっています。正確には、iOS は Darwin(macOS/iOSのカーネル)上で動く Unix 系 OS なので、プロセスとスレッドの概念は Linux とほぼ同じ

DispatchQueue.global(qos: .background).async {
    // 重い処理
    let result = doHeavyTask()

    // UI更新はメインスレッドで
    DispatchQueue.main.async {
        self.aiReply = result
    }
}
Task {
    let result = await fetchDataFromServer()
    // メインスレッドでUI更新
    await MainActor.run {
        self.aiReply = result
    }
}
let queue = OperationQueue()
queue.addOperation {
    let result = doHeavyTask()
    OperationQueue.main.addOperation {
        self.aiReply = result
    }
}

### 実際にstateを実装してみる
CounterPage.swift

import SwiftUI

struct CounterPage: View {
    @State private var count = 0
    
    var body: some View {
        VStack(spacing: 20) {
            Text("カウンター")
                .font(.title)
            
            Text("現在の値: \(count)")
                .font(.headline)
                 
            Button("+1") {
                count += 1
            }
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(8)
                 
            Button("リセット") {
                count = 0
            }
            .padding()
            .background(Color.red)
            .foregroundColor(.white)
            .cornerRadius(8)
            
            Spacer()
        }
        .padding()
    }
}

ユーザー操作やイベントによって変わる値を保持することが多い
なるほど〜

Style-Bert-VITS2を試そう

$ mkdir tts
$ cd tts
$ git clone https://github.com/litagin02/Style-Bert-VITS2.git
$ cd Style-Bert-VITS2
$ python3 -m venv venv
$ ls
$ source venv/bin/activate
$ pip3 install “torch<2.4" "torchaudio<2.4" --index-url https://download.pytorch.org/whl/cu118 $ cat requirements.txt [code] # onnxruntime-gpu; sys_platform != 'darwin' [/code] $ pip3 install onnxruntime $ pip3 install -r requirements.txt $ pip3 initialize.py $ pip install style-bert-vits2 soundfile simpleaudio $ sudo apt install libasound2-dev $ pip3 install simpleaudio $ pip3 install style-bert-vits2 soundfile [code] from style_bert_vits2.tts_model import TTSModel from pathlib import Path import soundfile as sf # soundfile は requirements.txt に含まれています # モデルパス model_path = Path("model_assets/jvnv-F1-jp/jvnv-F1-jp_e160_s14000.safetensors") config_path = Path("model_assets/jvnv-F1-jp/config.json") style_vec_path = Path("model_assets/jvnv-F1-jp/style_vectors.npy") model = TTSModel( model_path=model_path, config_path=config_path, style_vec_path=style_vec_path, device="cpu" ) text = "こんにちは!仮想環境でも音声ファイルに保存できます。" # 音声生成 sr, audio = model.infer(text=text) # WAV に保存 sf.write("output.wav", audio, sr) print("output.wav に保存完了") [/code] [audio wav="http://hpscript.com/blog/wp-content/uploads/2025/09/output.wav"][/audio] jvnv-F1-jp/jvnv-F1-jp_e160_s14000.safetensors はStyle-Bert-VITS2 が使う日本語向け音声合成モデル本体 style_vectors.npy:複数の話者や声質の特徴ベクトル(スタイル変換用) スタイルベクトルで 複数話者・声質を切り替えられる pitchで微調整できる。 [code] sr, audio = model.infer( text="こんにちは", style=selected_style, length_scale=1.0, # 1.0が標準、<1で早口、>1でゆっくり noise_scale=0.5, # 音声の自然さ noise_scale_w=0.5, # ピッチ揺れの調整 ) [/code]

mcd(Mel Cepstral Distortion)による音声評価

$ pip3 install librosa

import librosa
import numpy as np

def calculate_mcd(ref_wav_path, syn_wav_path, sr=16000, n_mfcc=25):
    """
    Mel Cepstral Distortion (MCD) を計算する修正版関数
    正規化+フレームごとの距離平均を行います
    """
    # 音声読み込み
    ref, _ = librosa.load(ref_wav_path, sr=sr)
    syn, _ = librosa.load(syn_wav_path, sr=sr)

    # 正規化 (-1〜1 の範囲)
    ref = ref / np.max(np.abs(ref))
    syn = syn / np.max(np.abs(syn))

    # MFCC 抽出(c0 は除外)
    ref_mfcc = librosa.feature.mfcc(y=ref, sr=sr, n_mfcc=n_mfcc)[1:]
    syn_mfcc = librosa.feature.mfcc(y=syn, sr=sr, n_mfcc=n_mfcc)[1:]

    # フレーム数を揃える(短い方に合わせる)
    min_len = min(ref_mfcc.shape[1], syn_mfcc.shape[1])
    ref_mfcc = ref_mfcc[:, :min_len]
    syn_mfcc = syn_mfcc[:, :min_len]

    # フレームごとのユークリッド距離
    distances = np.sqrt(np.sum((ref_mfcc - syn_mfcc)**2, axis=0))

    # MCD 計算式(フレームごとの平均)
    mcd = (10.0 / np.log(10)) * np.sqrt(2.0) * np.mean(distances)

    return mcd

# 使用例
if __name__ == "__main__":
    ref_file = "original.wav"  # 参照音声
    syn_file = "tts.wav"       # 合成音声
    mcd_value = calculate_mcd(ref_file, syn_file)
    print(f"MCD: {mcd_value:.2f} dB")

$ python3 app.py
MCD: 568.47 dB

明らかに大きい

LangChainを触ってみる

LangChain は 大規模言語モデル(LLM, Large Language Models)を使ったアプリ開発を効率化するためのフレームワークです。

### LangChain とは?
– Python や JavaScript で使える オープンソースのライブラリ
– OpenAI GPT, Anthropic Claude, Llama などの LLM を 組み合わせて活用できる
– 単に「プロンプトを投げて応答をもらう」以上のことを簡単に構築できる

### 主な機能
– Prompt Management(プロンプト管理)
– Chains(チェーン)
「ユーザーの質問 → LLM 応答 → 外部データ検索 → さらに LLM 応答」みたいに処理をつなげられる
– Agents(エージェント)
LLM が「どのツールを使うか」を自分で判断して実行できる
例:Web検索、計算機、SQLデータベース などを LLM が使い分ける
– Memory(メモリ)
対話の履歴を覚えて、会話に文脈を持たせられる
– Retrieval(外部知識の利用)
RAG(Retrieval Augmented Generation)を簡単に構築できる
例:PDFやドキュメントをベクトルDBに格納して、質問に応じて検索し、LLMに渡す

### 必要なインストール
$ pip3 install langchain
$ pip3 install langchain-openai

from langchain_openai import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
import os
from dotenv import load_dotenv

load_dotenv()

llm = OpenAI(
    temperature=0.7,
    api_key=os.getenv("OPENAI_API_KEY")  # ← ここで明示的に渡す
)

# プロンプトのテンプレートを定義
template = "質問: {question}\n答えをわかりやすく説明してください。"
prompt = PromptTemplate(template=template, input_variables=["question"])

# LLMChain を作成
chain = prompt | llm

# 実行
response = chain.invoke({"question": "LangChainとは何ですか?"})
print(response)

$ python3 test_langchain.py

LangChain(言語チェーン)とは、さまざまな言語を繋げて利用することができるシステムのことを指します。つまり、複数の言語を一つのチェーン(連鎖)のようにつなげて、それぞれの言語を柔軟に切り替えて使うことができる仕組みです。これにより、異なる言語を話す人々がコミュニケーションを取る際に、よりスムーズに相手の言語を理解することができるようになります。また、翻訳や通訳の分野にも応用されています。

Gemini APIの利用

pip install -q google-generativeai

import google.generativeai as genai  # Googleの生成AIライブラリ
from google.colab import userdata  # Google Colabのユーザーデータモジュール

GOOGLE_API_KEY=userdata.get('GOOGLE_API_KEY')
genai.configure(api_key=GOOGLE_API_KEY)

print("使用可能なGeminiのモデル一覧:")
for model in genai.list_models():
    if "generateContent" in model.supported_generation_methods:
        print(model.name)

model = genai.GenerativeModel("models/gemini-2.0-flash-001")
print(f"選択されたモデル: {model.model_name}")

config = genai.GenerationConfig(
    max_output_tokens=2048,  # 生成されるトークンの最大数
    temperature=0.8,  # 出力のランダム性を制御
)

def generate_content(model, prompt):
    response = model.generate_content(prompt, generation_config=config)
    return response.text

user_input = input("質問を入力してください: ")
response = generate_content(model, user_input)
print(f"Gemini: {response}")

LLMの内部構造: LLaMA-2

LLaMA-2の主要構造
1. Pythonコード (約1000行) : llama
2. パラメータ : llama-2-7b 次の単語予想に使用
3. トークナイザ : 前処理でテキストを分割

step1: トークナイザで入力テキストを細かく分割する
step2: Transformerとパラメータを用いて次の単語を予測
step3: 予測結果を元に次の単語を選択し結合
※予測結果から単語を選ぶプロセスをサンプリングと呼ぶ

model.py

class Transformer(nn.Module):
    def __init__(self, params: ModelArgs):
        """
        Initialize a Transformer model.

        Args:
            params (ModelArgs): Model configuration parameters.

        Attributes:
            params (ModelArgs): Model configuration parameters.
            vocab_size (int): Vocabulary size.
            n_layers (int): Number of layers in the model.
            tok_embeddings (ParallelEmbedding): Token embeddings.
            layers (torch.nn.ModuleList): List of Transformer blocks.
            norm (RMSNorm): Layer normalization for the model output.
            output (ColumnParallelLinear): Linear layer for final output.
            freqs_cis (torch.Tensor): Precomputed cosine and sine frequencies.

        """
        super().__init__()
        self.params = params
        self.vocab_size = params.vocab_size
        self.n_layers = params.n_layers

        self.tok_embeddings = ParallelEmbedding(
            params.vocab_size, params.dim, init_method=lambda x: x
        )

        self.layers = torch.nn.ModuleList()
        for layer_id in range(params.n_layers):
            self.layers.append(TransformerBlock(layer_id, params))

        self.norm = RMSNorm(params.dim, eps=params.norm_eps)
        self.output = ColumnParallelLinear(
            params.dim, params.vocab_size, bias=False, init_method=lambda x: x
        )

        self.freqs_cis = precompute_freqs_cis(
            # Note that self.params.max_seq_len is multiplied by 2 because the token limit for the Llama 2 generation of models is 4096. 
            # Adding this multiplier instead of using 4096 directly allows for dynamism of token lengths while training or fine-tuning.
            self.params.dim // self.params.n_heads, self.params.max_seq_len * 2
        )

    @torch.inference_mode()
    def forward(self, tokens: torch.Tensor, start_pos: int):
        """
        Perform a forward pass through the Transformer model.

        Args:
            tokens (torch.Tensor): Input token indices.
            start_pos (int): Starting position for attention caching.

        Returns:
            torch.Tensor: Output logits after applying the Transformer model.

        """
        _bsz, seqlen = tokens.shape
        h = self.tok_embeddings(tokens)
        self.freqs_cis = self.freqs_cis.to(h.device)
        freqs_cis = self.freqs_cis[start_pos : start_pos + seqlen]

        mask = None
        if seqlen > 1:
            mask = torch.full(
                (seqlen, seqlen), float("-inf"), device=tokens.device
            )

            mask = torch.triu(mask, diagonal=1)

            # When performing key-value caching, we compute the attention scores
            # only for the new sequence. Thus, the matrix of scores is of size
            # (seqlen, cache_len + seqlen), and the only masked entries are (i, j) for
            # j > cache_len + i, since row i corresponds to token cache_len + i.
            mask = torch.hstack([
                torch.zeros((seqlen, start_pos), device=tokens.device),
                mask
            ]).type_as(h)

        for layer in self.layers:
            h = layer(h, start_pos, freqs_cis, mask)
        h = self.norm(h)
        output = self.output(h).float()
        return output

いくつかの処理では、パラメータと呼ばれる数値を使用して演算が行われる
パラメータを繰り返し調整し、予想の精度を高める
※予想が誤っていた場合、パラメータを修正する (バックプロパゲーションと呼ばれる)
※LLMのパラメータ: 「Apple」という単語は、その単語が持つ意味、文脈、関連する概念(例えば、食べ物、会社名、色など)が、膨大な数値の集合(ベクトル)として表現されます。これらの数値の組み合わせによって、単語間の関係性が学習されています。
※単語のIDやトークンに対して数値ベクトルが割り当てられる
※モデルの大部分を占めるのは、単語間の関係性を理解し、次の単語を予測するための複雑な計算を行う、**アテンション機構やフィードフォワードネットワーク内の重み(weights)やバイアス(biases)**です。これらのパラメータが、文脈に応じた適切な単語の埋め込みベクトルを組み合わせ、最終的な出力を生成する。

### LLMの学習ステップ
学習ステップ1. 事前学習(Pre-training): 基盤モデル ただし、対話形式の学習が不足、不適切な質問にも答えてしまう
学習ステップ2. ファインチューニング(Fine-tuning): 特定のタスクや分野に特化させることができる
学習ステップ3. ヒューマンフィードバック(Human Feedback): 人間からのフィードバックを得る

### 事前学習に使われるデータ
モデルの用途によって、収集するデータ元(Webサイト、書籍、会話テキスト)の比率などが変わってくる。例えば、チャット用途の場合は、比較的会話データが多く学習される傾向にある。

### Transformer
Transformerの構造は主にEncoder(テキスト理解)とDecoder(テキスト生成)の要素から成り立つ

Attention機構の役割: 文中から関連度の高い単語を発見する

モーションAI

元画像

text
“This note is a personal reflection of a freelance engineer’s experience.”

動画生成

### モーションAIサービス
音声解析+AIモーション生成
音声データから「発話のリズム・イントネーション」を自動解析
AIモデルが「口の開閉」「表情の変化」「頭や体の動き」を推定して生成
=> JSONやスクリプトの形で、「フレームごとに口パク・まばたき・首の動き」を指示するタイムラインを自動生成
=> 既存のアニメーションライブラリ(例えば Live2D、Unity、After Effects、Blender など)に食わせるデータを準備

Canvaで画像合成

Chat GPTと同じように、Canvaでも画像合成ができる。

背景を変えることもできる。

🔍 Canvaマジック編集の仕組みイメージ
ユーザーがマスクを指定(首から下など)
テキスト指示(例: “a black suit and tie”)を入力
Canvaのサーバー側で拡散モデルに送信
画像の「残す部分(顔)」を保持
マスク部分を inpainting(塗り直し)
出力候補を複数生成し、ユーザーに提示

つまり内部的には、Stable Diffusion の inpainting とほぼ同じ流れ
Canvaの服差し替えも「Diffusion Model の inpainting」と考えて良い。

なんだこれは、凄すぎる。。。

### 動画の合成技術
1. 共通点(画像と同じ部分)
生成モデルを使う
画像生成で使うDALL·EやStable Diffusionのように、動画合成もニューラルネットワーク(特に生成モデル)を使います。
条件付き生成が可能
画像: 「黒いスーツを着た人」というプロンプト
動画: 「黒いスーツを着た人が歩く」というプロンプト
→ プロンプトに応じてフレームごとに生成する点は共通
データ表現はピクセルや特徴量
動画も結局は連続した画像(フレーム)なので、フレーム単位での画像合成技術が応用されます。

2. 動画ならではの追加課題
時間方向の一貫性
画像は単独で完結しますが、動画はフレーム間で連続性が必要
例: 人の手が前のフレームから急に消えると不自然
→ 「時間方向のモデル(Temporal model)」や「フロー情報」を使う
フレーム数が多い
1秒で30フレームなら30枚の画像を生成する必要がある
単純にフレームごとに画像生成すると重くなる
→ 先読みや特徴量伝播で効率化
マスクや編集の複雑さ
動画編集では「同じ位置のオブジェクトを全フレームでマスク」する必要がある
画像編集よりマスク処理が大規模かつ複雑

3. 技術例
画像拡張型
Stable Diffusion をフレームごとに生成+フレーム間のブレ補正
ビデオ特化型生成モデル
Imagen Video(Google)
Runway Gen-2
動画の時間方向情報も含めて生成する
既存動画の変換や編集
Deepfake や映像スタイル変換(例: 動画に別の服装や表情を合成)
元動画を入力として、時間方向に一貫した編集を行う

なるほど、なかなか勉強になるというか、面白いですね。

AIによる写真の合成技術

### 元画像

### ChatGTP-5
1. 範囲指定: 写真の首から下をマスク
2. 希望する服の種類を指示: スーツ+ネクタイ

アウトプット

その他の方法
## 方法1: 画像編集ソフトを使う
Photoshop / GIMP などを使用
– 首から下を「選択ツール」で切り抜き。
– 新しい服の画像を別レイヤーとして用意。
– サイズ・角度を合わせて合成。
– 境界を「ぼかし」や「マスク」で自然になじませる。
これは一番手動ですが、精密に調整できる。

## 方法2: AI画像編集ツールを使う
– Adobe Photoshop(生成塗りつぶし)
– Canva の「AI背景リムーバー+衣装差し替え」
– Fotor, Pixlr, Photopea などの無料ツール
– Stable Diffusion / ComfyUI で「inpainting」機能を使い、服を指定して描き直す

やり方は共通で:
1. 写真をアップロード
2. 首から下を選択(マスク指定)
3. 「服を変える」「スーツにする」など指示
4. AIが服を生成して合成

## 方法3: コーデ専用AIサービス
Fashn.AI や TryOnAI などは、服のバーチャル試着が可能。
自分の写真をアップすると、選んだ服を着せてくれる仕組み。

# AIによる画像合成の仕組み
1. 画像生成モデル(例:拡散モデル)
現在主流なのは 拡散モデル(Diffusion Model, Stable Diffusion など) です。
仕組みをざっくりいうと:

■ノイズ付加
もとの学習画像に少しずつランダムなノイズを加えていき、最終的に「ただのノイズ画像」にします。

■ノイズ除去の学習
「ノイズ画像から元画像を推定する」タスクを繰り返し学習します。

■生成
実際に画像を作るときは、ランダムなノイズ画像から始めて、学習した「ノイズ除去器(U-Netなど)」で少しずつノイズを取り除き、条件(テキスト指示やマスク部分の画像など)に沿った新しい画像に仕上げます。

2. 条件付き生成(テキストやマスクの利用)
■テキスト条件
「黒いスーツ」「ネクタイ」などの文章を CLIP などのテキスト埋め込みモデルで数値化し、それをガイドとして生成します。

■マスク条件(inpainting)
画像の特定範囲(例:首から下)にマスクをかけて「ここだけ生成し直す」と指定します。
すると AI は残りの画像を手掛かりに、マスク部分を自然に埋めてくれます。

3. 合成時の工夫
境界処理:マスクの縁をぼかして違和感を減らす
コンテキスト利用:背景や光の方向を読み取り、生成結果を自然に馴染ませる
ランダム性制御:シード値を固定すれば毎回同じ結果を再現できる

## ブラウザ上で操作できるサービス
✅ Canva
Webブラウザで利用可能(無料プランあり)
「AI背景リムーバー」や「マジック編集(服の差し替え)」が使える
👉 https://www.canva.com/

✅ Fotor
ブラウザ版あり(無料プラン+有料プラン)
AIによる背景削除、合成、衣装差し替えが可能
👉 https://www.fotor.com/

✅ Pixlr
完全にブラウザで動作(広告付き無料プランあり)
Photoshop風の操作感で、レイヤー編集・マスクも可能
👉 https://pixlr.com/

✅ Photopea
完全無料でブラウザ上で動作
Photoshop互換の操作感
手動マスク+他画像合成は可能(AI自動生成は弱め)
👉 https://www.photopea.com/

拡散モデル(diffusion)

import torch
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt

# ==== MNIST データセットから1枚取得 ====
transform = transforms.Compose([
    transforms.ToTensor(),
])
dataset = torchvision.datasets.MNIST(root="./data", train=True, download=True, transform=transform)
image, _ = dataset[0]  # 1枚だけ
image = image.squeeze(0)  # (1,28,28) → (28,28)

# ==== 拡散プロセス ====
T = 10  # ステップ数
noisy_images = []
x = image.clone()
for t in range(T):
    noise = torch.randn_like(x) * 0.1
    x = (x + noise).clamp(0,1)  # ノイズを足す
    noisy_images.append(x)

# ===== 擬似的な逆拡散(平均を取ってノイズを少しずつ減らすだけ) =====
denoised_images = []
y = noisy_images[-1].clone()
for t in range(T):
    y = (y*0.9 + image*0.1)  # 単純な補間で元画像に近づける
    denoised_images.append(y)

# ===== 可視化 =====
fig, axes = plt.subplots(3, T, figsize=(15, 5))

# 元画像
axes[0,0].imshow(image, cmap="gray")
axes[0,0].set_title("Original")
axes[0,0].axis("off")

# 拡散 (ノイズ付与)
for i in range(T):
    axes[1,i].imshow(noisy_images[i], cmap="gray")
    axes[1,i].axis("off")
axes[1,0].set_title("Forward Diffusion")

# 逆拡散 (ノイズ除去の雰囲気だけ)
for i in range(T):
    axes[2,i].imshow(denoised_images[i], cmap="gray")
    axes[2,i].axis("off")
axes[2,0].set_title("Reverse (Toy)")

plt.show()