[TTS] TTSとは何か?

TTSとは 「Text-To-Speech(テキスト読み上げ)」 の略で、
簡単に言うと 文字を音声に変換する技術 のことです。

たとえば…
スマホの読み上げ機能で、メッセージやニュースを声で聞ける
カーナビが目的地までの案内を音声で話す
Siri や Google アシスタントが話して答える
これらはすべて TTS(音声合成) の仕組みを使っている。

仕組みのざっくりイメージ
テキスト入力:「こんにちは、今日はいい天気ですね」
音声合成エンジンが文字を読み方に変換
人工的な声で読み上げる音声を生成(自然な抑揚をつける)

最近のTTSはAI技術(特に深層学習)によって、
まるで人間のように自然な話し方ができるようになっている。

「深層学習を使ったTTS」は、文字列→音声の処理を学習ベースで行い、人間らしい抑揚・リズム・声質を自動で作れるようになった技術です。従来より滑らかで自然な発話が可能になり、少ない手作業で多様な話し方や声を生成できるようになった。

仕組み(高レベルのパイプライン)
一般的な現代TTSは大きく3つのステップに分かれる

– テキスト前処理(Frontend)
文字列を音素(発音単位)に変換(G2P = Grapheme→Phoneme)したり、句読点や数字を正しい読みへ変換したりする。

– アコースティックモデル(Acoustic / Spectrogram Generator)
テキスト(音素や言語特徴)を入力に取り、音の時間周波数表現(例:メルスペクトログラム)を生成する。
ここで抑揚(ピッチ)、長さ(音の持続)、強弱などの「話し方」が決まる。

– ボコーダ(Vocoder)
メルスペクトログラムなどの中間表現を受け取り、実際の波形(聞ける音声)を生成する。
深層学習は主に(2)と(3)で活躍

なるほど〜 かなり勉強になりますね。少しずつでも毎日勉強したほうが良さそう…

[LLM] BLEU, ROUGE, 人手評価, ガイドラインベース評価

1. BLEU(Bilingual Evaluation Understudy)
主に「機械翻訳」や「文章生成の精度」を自動で測る指標。

生成文 と 正解文(リファレンス) の間で、
単語やフレーズ(n-gram) の一致率を計算
「どれだけ同じ単語列が出てきたか」を重視する手法です。
正解文: 私はリンゴを食べました
生成文: 私はリンゴを食べた

共通の n-gram が多いため、
BLEUスコアは高くなる(例: 0.8 など)。

「食べた」 vs 「食事した」 は同じ意味でも評価されない

2.ROUGE(Recall-Oriented Understudy for Gisting Evaluation)
目的:主に「要約タスク」の自動評価に使われます。
BLEUが「生成文にどれだけ正解単語が含まれるか(Precision)」を重視するのに対し、
ROUGEは正解文の情報をどれだけ拾えているか(Recall) を重視します。
特に有名なのは ROUGE-1(単語)、ROUGE-2(2-gram)、ROUGE-L(最長共通部分列)。

正解要約: 富士山は日本で最も高い山である。
生成要約: 富士山は日本一高い山。
→ 多くの重要単語(富士山、日本、高い山)が共通
→ ROUGEスコア高め

3.人手評価(Human Evaluation)
目的:
AIが出力した文章を人間が直接見て判断する方法。
一番信頼できるが、一番コストが高い。
正確性(Accuracy) 事実・情報が正しいか
一貫性(Consistency) 文全体に矛盾がないか
流暢性(Fluency) 日本語として自然か
関連性(Relevance) 質問や文脈に合っているか
創造性(Creativity) 独自性・表現力があるか

4. ガイドラインベース評価(Guideline-based Evaluation)
人手評価の主観を抑え、一貫性のある基準で評価する方法。
💡 概念
OpenAIやAnthropicが内部で使用しているように、
評価者に「詳細な採点基準(ガイドライン)」を与えることで、
人手評価を半構造化・定量化します
指標 種類 評価対象 特徴 長所 短所
BLEU 自動 翻訳・生成 単語の一致率 定量的で高速 意味を理解できない
ROUGE 自動 要約 単語の網羅性 要約に適する 言い換えに弱い
人手評価 手動 全般 主観ベース 正確・柔軟 高コスト・主観的
ガイドライン評価 手動+基準化 全般 定義ベース 再現性が高い 準備が必要

[django] models

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'book.apps.BookConfig',
]
from django.db import models

# Create your models here.
class SampleModel(models.Model):
    title = models.CharField(max_length=100)
    number = models.IntegerField()

$ python3 manage.py makemigrations
Migrations for ‘book’:
book/migrations/0001_initial.py
– Create model SampleModel

    operations = [
        migrations.CreateModel(
            name='SampleModel',
            fields=[
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('title', models.CharField(max_length=100)),
                ('number', models.IntegerField()),
            ],
        ),
    ]

$ python3 manage.py migrate

[Figma]列グリッドと制約

多少慣れてきました。

grid, constraint, autolayoutはcssやbootstrapでもよく出てくる概念ですね。
colorとかそういうのをもう少し勉強したい気がするが、まぁいいか。

[iOS] CoreData

アプリを閉じても Core Data に保存される(永続化)

Xcode で新しいプロジェクトを作るときに
「Use Core Data」にチェックを入れます ☑️
これで自動的に以下が追加されます:
YourAppName.xcdatamodeld(データモデル)
Persistence.swift

ステップ 2:データモデルを定義
YourAppName.xcdatamodeld を開いて「+」を押し、
新しい Entity を作成します:

Entity 名:Memo
Attribute:
text(String)
timestamp(Date)

import SwiftUI
import CoreData

struct ContentView: View {
    // Core Data の管理オブジェクトコンテキスト
    @Environment(\.managedObjectContext) private var viewContext

    // 保存済みメモを取得して監視
    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Memo.timestamp, ascending: false)],
        animation: .default)
    private var memos: FetchedResults<Memo>

    @State private var newMemo: String = ""

    var body: some View {
        NavigationView {
            VStack(spacing: 20) {
                // メモ入力欄
                TextField("メモを入力", text: $newMemo)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .padding(.horizontal)

                // 保存ボタン
                Button(action: addMemo) {
                    Label("保存", systemImage: "square.and.arrow.down")
                }
                .buttonStyle(.borderedProminent)

                Divider()

                // メモ一覧
                List {
                    ForEach(memos) { memo in
                        VStack(alignment: .leading, spacing: 4) {
                            Text(memo.text ?? "")
                                .font(.headline)
                            if let date = memo.timestamp {
                                Text(date, style: .date)
                                    .font(.caption)
                                    .foregroundColor(.gray)
                            }
                        }
                    }
                    .onDelete(perform: deleteMemos)
                }
                .listStyle(.inset)

                Spacer()
            }
            .navigationTitle("メモ帳")
            .toolbar {
                EditButton()
            }
        }
    }

    // MARK: - Core Data 操作

    /// メモを追加
    private func addMemo() {
        guard !newMemo.isEmpty else { return }
        let memo = Memo(context: viewContext)
        memo.text = newMemo
        memo.timestamp = Date()

        do {
            try viewContext.save()
            newMemo = ""
        } catch {
            print("💥 保存エラー: \(error.localizedDescription)")
        }
    }

    /// メモを削除
    private func deleteMemos(at offsets: IndexSet) {
        for index in offsets {
            let memo = memos[index]
            viewContext.delete(memo)
        }

        do {
            try viewContext.save()
        } catch {
            print("💥 削除エラー: \(error.localizedDescription)")
        }
    }
}

// MARK: - プレビュー用

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        // プレビューでも Core Data のコンテキストを注入
        let context = PersistenceController.preview.container.viewContext
        ContentView()
            .environment(\.managedObjectContext, context)
    }
}

CoreDataの概念はわかったが、ソースの実行がうまくいかん..

[LLM] LoRA / PEFT(軽量ファインチューニング)

### ファインチューニング(fine-tuning) とは?
すでに学習済みのLLM(例:LLaMA, GPT, Mistralなど)を
自分のデータで「再訓練」して、特定の用途に特化させること。

e.g.
ChatGPTを「医療専門AI」にしたい
LLaMAを「企業マニュアルの質問回答AI」にしたい
Mistralを「社内チャットボット」に最適化したい

### why it need fine tuning
LLMは汎用的なので、例えば次のような弱点があります。

通常のLLM チューニング後
「契約書の条文っぽい文章」を知らない 法律文体で出力できる
「企業内の独自用語」を知らない 社内専門語で答える
指定フォーマットを守らない JSON形式で必ず回答する

### LoRA / PEFTとは?(軽量ファインチューニング)
通常のファインチューニングは、
巨大モデル全体を再学習するため、
GPU・時間・コストが非常に高くつきます(数十GB〜数百GB)。
→ そこで登場したのが LoRA や PEFT。

🔹 LoRA(Low-Rank Adaptation)
LoRAは、「モデル全体ではなく一部の重みだけを微調整」する仕組みです。
📘 ざっくり言うと:
「LLM本体は凍結(変更しない)」
「一部の行列(重み)だけを低次元(rank低い)で学習」

これにより👇
項目 通常ファインチューニング LoRA
学習パラメータ数 数百億 数百万(1〜3%)
必要GPUメモリ 数百GB 数GB
コスト 非常に高い 低コスト
再利用 不可(モデルごと) 可能(LoRA層だけ切り替え可)

元の重み W に、小さな変化 ΔW を足してチューニングする。
ΔW = A × B (A,Bは小さい行列=低ランク)

🔹 PEFT(Parameter-Efficient Fine-Tuning)
LoRAを含む「軽量ファインチューニング技術の総称」です。
PEFTには以下のような手法があります:

手法 概要
LoRA 一部の重みを低ランクで更新
Prefix Tuning プロンプトに「学習したベクトル」を前置
Prompt Tuning 入力部分だけ学習(モデル本体は凍結)
Adapter Tuning モデルの中間層に追加ネットワークを挿入
➡️ Hugging Face の peft ライブラリ はこれらを統一的に扱えます。

from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import LoraConfig, get_peft_model

# ベースモデル
model_name = "meta-llama/Llama-2-7b-hf"
model = AutoModelForCausalLM.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)

# LoRA設定
lora_config = LoraConfig(
    r=8,               # rank(低次元のサイズ)
    lora_alpha=16,     # 学習率スケーリング
    target_modules=["q_proj", "v_proj"],  # attention層だけチューニング
    lora_dropout=0.05,
)

# LoRAモデルを作成
model = get_peft_model(model, lora_config)

# 学習用データ(例)
texts = [
    {"input": "会社の理念を説明してください。", "output": "私たちは顧客の信頼を第一とします。"},
]

# トークナイズと学習処理(実際はTrainerを使用)
# model.fit(...)

# 学習済みLoRAを保存(ベースモデルと分離)
model.save_pretrained("my_lora_adapter/")

[Dify LLM] PythonでDifyのworkflowに入力して出力を取得する

import requests
import json
import os

from dotenv import load_dotenv
load_dotenv()

API_KEY = os.environ.get("DIFY_API_KEY")
APP_ID = os.environ.get("APP_ID")

BASE_URL = "https://api.dify.ai/v1" 
CHAT_ENDPOINT = f"{BASE_URL}/chat-messages"

user_input = "おはようございます。"

payload = {
    # ユーザーからのメインの質問(ワークフローの {{query}} に対応)
    "query": user_input,
    
    # ワークフロー内の他の入力ノードに渡す変数(オプション)
    "inputs": {
        # 例: ワークフロー内の変数名が 'context' の場合、ここで値を渡せる
        "context": "Null" 
    },
    
    # ユーザーIDは必須(セッション管理用)。適当なUUIDなどを指定
    "user": "user_python_script_001", 
    
    # ストリーミングを無効にする
    "response_mode": "blocking", 
}

headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json"
}

try:
    # --- API呼び出し ---
    response = requests.post(
        CHAT_ENDPOINT,
        headers=headers,
        data=json.dumps(payload)
    )
    
    response.raise_for_status() # HTTPエラーが発生した場合に例外を発生させる
    data = response.json()

    # --- 出力の取得 ---
    if data and 'answer' in data:
        print("✅ ワークフローの出力 (回答):")
        print(data['answer'])
    else:
        print("⚠️ ワークフローからの回答が見つかりません。")
        print(data)

except requests.exceptions.HTTPError as errh:
    print(f"HTTP エラーが発生しました: {errh}")
    print(response.text)
except requests.exceptions.RequestException as err:
    print(f"リクエスト中にエラーが発生しました: {err}")

$ python3 dify-llm.py
✅ ワークフローの出力 (回答):
おはようございます。今日は何のお手伝いができますか?

アプリ(iOS/Android)から直接Difyを呼び出すのではなく、バックエンドサーバー(Python)を経由させて、そのサーバーでTTS(Text-to-Speech、音声合成)処理を行ってからアプリに返す構成の方が、より一般的で推奨されている。

なるほど、Dify APIの理解度が一気に高まった。

[Django] Template View

urls.py

from django.contrib import admin
from django.urls import path
from .views import helloworldfunc, HelloWorldClass

urlpatterns = [
    path('admin/', admin.site.urls),
    path('helloworldurl/', helloworldfunc),
    path('helloworldurl2/', HelloWorldClass.as_view()),
]

views.py

from django.http import HttpResponse
from django.views.generic import TemplateView 

def helloworldfunc(request):
    return HttpResponse("<h1>Hello, World!</h1>")

class HelloWorldClass(TemplateView):
    template_name = "hello.html"

settings.py

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

[Figma] 画面遷移の設定

① 画面(Frame)を2つ作る
② ボタンを置く
③ Prototypeモードに切り替える
④ 画面をつなぐ

Designの中にフレームが一つだけでしたが、複数フレームを置いて、Prototypeモードで繋げるのですね。なるほど!

[android]UIのアニメーション Composeのanimate*関数で滑らかなUI体験

MainActivity.kt は「アプリの起動エントリポイント(=最初に表示される画面)」を定義する場所
UI(画面の中身)はComposable関数として別ファイルに分けて定義 するのが一般的

package com.example.zeus

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            AnimatedBoxDemo()
        }
    }
}

@Composable
fun AnimatedBoxDemo() {
    var expanded by remember { mutableStateOf(false) }

    val boxSize by animateDpAsState(
        targetValue = if (expanded) 200.dp else 100.dp,
        label = "box animation"
    )

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Box(
            modifier = Modifier
                .size(boxSize)
                .background(Color(0xFF4CAF50))
        )

        Spacer(modifier = Modifier.height(24.dp))

        Button(onClick = { expanded = !expanded }) {
            Text(if (expanded) "縮小" else "拡大")
        }
    }
}

byはプロパティデリゲーション(Property Delegation)というkotlin独特の記法で任せるという書き方。