[LLM] RAG(検索拡張生成)

RAG(Retrieval-Augmented Generation/検索拡張生成)は、LLMの「記憶の弱点」を補うための仕組み

### RAGの基本アイディア
– 学習データが古い
– 企業固有の情報を知らない
– 事実を正確に記憶していない

### RAGの仕組み(流れ)
1. ユーザーの質問(Query)

2. 検索(Retrieval)
・質問を ベクトル化(embedding) して数値化
・ベクトルデータベース(例:FAISS, Chroma, Milvusなど)から
・「意味的に近い文書」を検索

3. 統合(Augmentation)
検索で得たテキストをまとめて「プロンプトに埋め込む」

以下の資料を参考に質問に答えてください:
[資料1: ...]
[資料2: ...]
質問:2025年の日本の経済成長率は?

4. 生成(Generation)
LLMがその検索結果(コンテキスト)をもとに回答を生成

ユーザー質問

【Embedding】→ ベクトル化

【Vector DB】→ 意味的に近い文書を検索

【Augmentation】→ 文書をプロンプトに追加

【LLM生成】→ 文脈に基づいて回答生成

### デメリット
– 検索精度が悪いと、間違った情報を引用してしまう
– ベクトルDBの管理・更新が必要
– 長いコンテキストを扱うとコストが増える

from openai import OpenAI
import faiss
import numpy as np

client = OpenAI(api_key="sk-***")

docs = ["富士山は日本で一番高い山です", "日本の首都は東京です"]
embs = [client.embeddings.create(model="text-embedding-3-small", input=d).data[0].embedding for d in docs]


index = faiss.IndexFlatL2(len(embs[0]))
index.add(np.array(embs))

query = "日本の一番高い山は?"
q_emb = client.embeddings.create(model="text-embedding-3-small", input=query).data[0].embedding

D, I = index.search(np.array([q_emb]), k=1)
context = docs[I[0][0]]

prompt = f"次の情報を参考に質問に答えてください。\n資料: {context}\n質問: {query}"
resp = client.responses.create(model="gpt-4o-mini", input=prompt)

print(resp.output_text)

$ python3 rag.py
日本の一番高い山は富士山です。

FAISSとPostgreSQLの違い
観点 FAISS PostgreSQL(psql)
開発元 Meta(旧Facebook) PostgreSQL Global Development Group
主な用途 ベクトル検索(類似度検索) 構造化データの検索・管理
データの種類 数値ベクトル(例:768次元の埋め込み) テーブル形式(行・列)
検索内容 「意味的に似ている文章や画像を探す」 「条件に一致する行を探す(例:WHERE句)」
検索方法 コサイン類似度 / 内積 / L2距離 SQL文(SELECT, JOIN, ORDER BYなど)
速度 超高速(数百万ベクトルでもミリ秒単位) 適度(構造化クエリに最適)
保存形式 メモリ上のインデックス(永続化も可) 永続的なデータベースファイル
例 「この文に似た文を探せ」 「age > 30 のユーザーを取得」

なるほど、FAISSはmetaが開発しているのね。

[LLM] 3Blue1BrownのLLM解説

3Blue1Brown は有名なYoutubeチャンネル
https://www.youtube.com/@3blue1brown

制作者:Grant Sanderson(グラント・サンダーソン)
内容:数学・AI・物理などの抽象的な概念を超美しいアニメーションで可視化する教育チャンネル
名前の意味:制作者の目の色(片方が青3つ、もう片方が茶1つ)に由来しています

■高品質なアニメーション
使用しているツールは Manim(Mathematical Animation Engine)
自作のPythonライブラリで、数学アニメーション専用。
現在はオープンソース化され、教育・AI研究の可視化に広く使われています。
👉 Manim公式

OpenAIやDeepMindのエンジニアも「彼の動画で理解した」と発言

### 3Blue1BrownのLLM解説
1. LLMとは「次の単語を予測する関数」
LLM(大規模言語モデル)は、あるテキストの直後に続く単語を予測する確率分布関数と考えられる。
ただし「最も確率の高い単語」を常に選ぶわけではなく、ランダム性を持たせて応答を自然にするよう設計されることがある。
モデルは、与えられた文脈(プロンプト+生成済みテキスト)をすべて使って次の単語を決定する。

2. Transformer/Attention の利用
3Blue1Brown の解説では、LLM内部で Transformer(Attention 機構) が使われている点を詳細に描写します。
– Embed(埋め込み)段階:単語を高次元ベクトルに変換
– Attention:各単語ベクトルが他の単語ベクトルに注目して情報を交換
– Feed Forward ネットワーク(MLP):Attention 後のベクトルを各位置で独立変換
– これらの処理を複数層重ねることで、文脈の情報が多段階で取り込まれていく

3. 事実の記憶(Knowledge / Facts の “格納”)
3Blue1Brown は “LLM がどうやって事実(知識)を内部に持つか” という観点に触れています。特に “How might LLMs store facts” という章で詳述。
– モデルの MLP(多層パーセプトロン)層 に、事実情報がある程度蓄えられている可能性を指摘
– 例示:入力「Michael Jordan plays the sport of ___」に対して “basketball” を予測できるのは、内部で “Michael Jordan → basketball” という対応を何らかのベクトル操作で実現しているから、という説明
3blue1brown.com
+1
– この観点から、「Attention は文脈処理/結合、MLP は知識格納の役割を持つ」という分担感が描かれています

4. モデルの規模と訓練の難しさ
3Blue1Brown では、LLM の巨大さ・計算コストにも触れています。
3blue1brown.com
何十億〜何千億というパラメータ(重み)を持つ
それらを最適化するための学習には膨大なデータと計算資源が必要
人間が株ごとパラメータを設定するわけではなく、勾配法 + 損失関数 によって徐々に学ばれる

5. 応答の確率的性・多様性
3Blue1Brown は「同じプロンプトでも違う応答が返る」点にも言及します。
3blue1brown.com
モデルは確率分布を使って次の単語を選ぶので、必ずしも最頻出語を返すわけではない
少し確率が下がる語を選ぶことで、応答が自然・人間らしくなる

[LLM] Hugging Face の「Transformers」入門

### Hugging Faceとは
AIモデル(特にLLM)を「共有・利用・学習」できるオープンソースのAIプラットフォームです。
– GitHubのように世界中の研究者・企業がモデルを公開
– 代表ライブラリが「transformers」
– 無料で多くのモデルをダウンロードして使える

### transformers ライブラリの目的
transformers は 最先端のNLPモデルを簡単に使うためのPythonライブラリ。
代表的な利用例:
– 文章生成(GPT, LLaMA)
– 翻訳(Marian, T5)
– 要約(BART)
– 感情分析(BERT)
– 質問応答(DistilBERT)

### 基本の3構成
1️⃣ Tokenizer(トークナイザー)
→ テキストを数値化(単語 → トークンID)
2️⃣ Model(モデル)
→ トークン列を処理して出力を返す
3️⃣ Pipeline(パイプライン)
→ Tokenizer と Model をまとめて簡単に使える便利関数

実際のコード例

from transformers import pipeline
# 感情分析のパイプラインをロード
classifier = pipeline("sentiment-analysis")
# テキストを入力
result = classifier("I love Hugging Face!")
print(result)
# [{'label': 'POSITIVE', 'score': 0.9998}]

モデルを指定したコード

from transformers import AutoTokenizer, AutoModelForCausalLM

model_name = "gpt2"

tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name)

input_text = "Hello, my name is"
inputs = tokenizer(input_text, return_tensors="pt")

outputs = model.generate(**inputs, max_length=20)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))

主なpipeline
感情分析 pipeline(“sentiment-analysis”) 映画レビューなど
翻訳 pipeline(“translation”, model=”Helsinki-NLP/opus-mt-en-jp”) 英→日翻訳
要約 pipeline(“summarization”) ニュース要約
文章生成 pipeline(“text-generation”, model=”gpt2″) GPT風応答
質問応答 pipeline(“question-answering”) QAボット

注意点
– モデルサイズが大きい場合、ダウンロードに時間がかかる
– GPUがあると高速(CUDA対応)
– オフライン実行も可能(cache_dirを指定)

[LLM] Attention is All You Need

Attention is All You Need

従来の機械翻訳(Seq2Seq)はこうでした:
🧩 RNN(LSTMなど)で入力文を順に読み、隠れ状態にエンコード
🧩 その状態をデコーダーが順に展開して出力を生成
→ つまり「時系列処理(順番に読む)」が前提。
しかしRNNは並列化できず、長文では文脈を忘れやすいという弱点がありました。

Transformerの発想
「全部並列で読んで、Attention(注意機構)で関係性だけを学習すればいい」
つまり:
時系列構造をやめて「全単語を同時に見て」
単語間の関係を Attention で表現する
→ これがタイトルの “Attention is All You Need” の意味です。

構成は「エンコーダ」と「デコーダ」の2ブロック:
ブロック 役割 構成
Encoder 入力文の意味を圧縮・表現 Self-Attention + Feed Forward
Decoder その表現をもとに出力文を生成 Masked Self-Attention + Cross-Attention + Feed Forward

名前 役割
Q (Query) 注目する視点(どの単語に注目したいか)
K (Key) 単語の特徴(注目される側)
V (Value) 実際に取り出す情報

🎭 Multi-Head Attentionとは?
「1つの視点では足りない」ので、
複数の異なるQ, K, V空間(ヘッド) を並列で使い、
さまざまな関係(文法的・意味的など)を学びます。

Feed Forward Network(FFN)
Self-Attentionの出力(各単語)を、
個別に2層のMLP(全結合層)で変換します。
これにより非線形変換が入り、表現力が上がります。

[LLM] コンテキスト長、推論速度、埋め込み

① コンテキスト長(Context Length)とは?

「モデルが一度に理解・処理できる情報量」 のことです。つまり、「LLMが一度の会話・文書入力で覚えておける最大トークン数(単語や記号のかたまり)」を指す。

🧩 たとえば:
GPT-4 Turbo:128,000トークン(=日本語でおよそ8〜10万文字)
Gemini 1.5 Pro:100万トークン(=本一冊レベル)
小型モデル(LLaMA 3 8Bなど):通常4,000〜8,000トークン

短いコンテキスト長(例:4,000トークン)
→「一度に短文しか読めないAI」
→ 小説1章を超えると前の内容を忘れてしまう。

長いコンテキスト長(例:100万トークン)
→「本1冊をまるごと読んで要約できるAI」
→ ドキュメント全体を把握して、前後の関係を理解できる。

長いコンテキスト長を持つモデルほど、
「文脈を途切れずに理解」できる

### 推論速度(Inference Speed)とは?
AIが応答を返すまでの速さ のこと
同じ質問をしても、モデルの設計や規模によって速度は大きく異なる

Flash系 (例: Gemini Flash, GPT-4o-mini) 軽量スポーツカー 速い 🚀
Pro系 (例: GPT-4, Gemini Pro) 高性能SUV やや遅いが高精度
巨大モデル (例: Mixtral, LLaMA 70B) 重トラック 高精度だが遅い 🐢

速度は主に以下の3要素で決まります。
モデルサイズ(パラメータ数が多いほど遅い)
ハードウェア(GPU or CPU)
並列化や量子化(最適化で速くなる)

チャットボットやQA → 高速モデル(Flash系)
高精度な分析や生成 → Pro系モデル
リアルタイム処理が必要な場面 → 推論速度重視

埋め込み(Embedding)とは?

これは、LLMの「意味を数値に変換する技術」です。
テキストを単なる文字列ではなく、
AIが「意味的な距離」で比較できるようにします。

🔍 たとえば:
テキスト 埋め込み後のベクトル(例)
「りんご」 [0.12, -0.87, 0.44, …]
「みかん」 [0.10, -0.80, 0.46, …]
「車」 [-0.55, 0.92, -0.33, …]

このとき、「りんご」と「みかん」はベクトルが近い

[Django] HTTP requestの仕組み

HTTP request
-> urls.py, views.py,
models.py ⇄ Database

WSL… Windows Subsystem for Linux

$ python3 -m venv venv
$ source venv/bin/activate
$ pip3 install django==3.2
$ django-admin startproject helloworldproject
$ python3 manage.py runserver

$ python3 manage.py migrate
$ python3 manage.py runserver
http://127.0.0.1:8000/admin/login/?next=/admin/

views.py

from django.http import HttpResponse

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

urls.py

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

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

http://127.0.0.1:8000/helloworldurl/

[figma] Motion / Interaction(アニメーション・状態変化)

1. Motion / Interaction の概念理解
Figmaでの「モーション」「インタラクション」は以下の2つを組み合わせて作る
– Variant(状態): デザイン上の“見た目の変化”, Default / Hover / Pressed など
– Prototype(遷移・動き): 状態や画面を“動的につなぐ”, クリックで次の画面へ、ホバーで色変化など

prototypeのプレビューでoverlayすると、画面が切り替わる

おおおおおおおお、すげぇええええええええ 
ちょっと世界観変わったぜ!

[iOS] Combine / Swift Concurrency(async/await)

非同期で呼び出す仕組み

import SwiftUI

struct ContentView: View {
    @State private var temperature: String = "__"
    @State private var windspeed: String = "__"
    @State private var showSheet = false
    
    var body: some View {
        VStack(spacing: 20) {
            Text("東京の現在天気")
                .font(.title)
            
            Text("気温: \(temperature)°C")
            Text("風速: \(windspeed)m/s")
            
            Button("天気を取得") {
                Task {
                    await fetchWeather()
                }
            }
            
            Button("詳細を表示(ボトムシート)") {
                showSheet = true
            }
        }
        .padding()
        .sheet(isPresented: $showSheet) {
            BottomSheetView(
                temperature: temperature,
                windspeed: windspeed
            )
            .presentationDetents([.fraction(0.3), .medium, .large])
            .presentationDragIndicator(.visible)
        }
    }
    
    func fetchWeather() async {
        guard let url = URL(string: "https://api.open-meteo.com/v1/forecast?latitude=35.6895&longitude=139.6917&current_weather=true") else { return }
        
        do {
            // 非同期で通信(完了を待つ)
            let (data, _) = try await URLSession.shared.data(from: url)
            let decoded = try JSONDecoder().decode(WeatherResponse.self, from: data)

            // メインスレッドでUI更新
            await MainActor.run {
                self.temperature = String(decoded.current_weather.temperature)
                self.windspeed = String(decoded.current_weather.windspeed)
            }
        } catch {
            print("エラー: \(error.localizedDescription)")
        }
    }
}


struct BottomSheetView: View {
    let temperature: String
    let windspeed: String
    
    var body: some View {
        VStack(spacing: 16) {
            Text("詳細情報")
                .font(.title3)
                .bold()
            Text("気温: \(temperature)°C")
            Text("風速: \(windspeed)m/s")
            Text("データ提供: open-meteo.com")
                .font(.footnote)
                .foregroundColor(.gray)
        }
        .padding()
    }
}

struct WeatherResponse: Codable {
    struct CurrentWeather: Codable {
        let temperature: Double
        let windspeed: Double
    }
    let current_weather: CurrentWeather
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

fetchWeather() に async を付ける 非同期処理を行う関数になる
Task { await fetchWeather() } SwiftUI のボタン内で非同期関数を呼ぶための安全な方法
try await URLSession.shared.data(from:) 通信が完了するまで待機(コールバック不要)
await MainActor.run { … } UI更新は常にメインスレッドで行う

うん、asyncは他の言語でもよく出てくるのでわかりやすい。

[LLM] オープンモデルとクローズドモデル

「オープンモデル」は中身が公開されていて自由に使える
「クローズドモデル」は中身が非公開で、企業のサーバー経由でしか使えないモデル

### オープンモデル(Open Model)
モデルの中身(重み・コード)が公開されている
研究者・開発者が自由にダウンロードして、改良・再学習できる
オフラインでも動かせる(ローカル環境で実行可能)

代表例
LLaMA 3(Meta社)
Mistral / Mixtral
Falcon
Gemma(Googleのオープン版)
Japanese StableLM(日本語特化)

### クローズドモデル(Closed Model)
■特徴
モデルの中身(重み)は非公開
提供企業(OpenAI, Googleなど)のAPIを通じて利用する
モデルの更新や最適化は提供企業が行う

■代表例
GPTシリーズ(OpenAI)
Claude(Anthropic)
Gemini(Google)
Command R+(Cohere)

超軽量モデル
$ pip3 install accelerate

from transformers import pipeline

# 軽量モデル(日本語対応)
pipe = pipeline(
    "text-generation",
    model="cyberagent/open-calm-1b",  # 約1Bの日本語モデル
)

prompt = "オープンモデルとクローズドモデルの違いを教えてください。"
result = pipe(prompt, max_new_tokens=150)
print(result[0]['generated_text'])

モデルを動かすにはGPUがないとキツそう…

[android] bearer tokenの使いたい

/data/api/ApiService.kt

interface ApiService {
    @GET("user/profile")
    suspend fun getUserProfile(): Response<UserProfile>
}

data/api/AuthInterceptor.kt

class AuthInterceptor(private val tokenProvider: ()-> String?):Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val token = tokenProvider()
        val request = chain.request().newBuilder().apply {
            token?.let {
                addHeader("Authorization", "Bearer $it")
            }
        }.build()
        return chain.proceed(request)
    }
}

data/api/ApiClient.kt

object ApiClient {
    fun create(tokenProvider: () -> String?): ApiService {
        val client = OkHttpClient.Builder()
            .addInterceptor(AuthInterceptor(tokenProvider))
            .build()

        return Retrofit.Builder()
            .baseUrl("https://your-api.com/") // 実際のURLに変更
            .client(client)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiService::class.java)
    }
}

presentation/viewmodel/MyViewModel.kt

class MyViewModel : ViewModel() {
    private val token = ""

    private val apiService = ApiClient.create {token}

    var userProfile by mutableStateOf<UserProfile?>(null)
        private set

    fun loadUserProfile() {
        viewModelScope.launch {
            try {
                val response = apiService.getUserProfile()
                if (response.isSuccessful) {
                    userProfile = response.body()
                } else {
                    // エラー処理
                }
            } catch (e: Exception) {
                // 通信エラーなど
            }
        }
    }

}

presentation/screen/ProfileScreen.kt

@Composable
fun ProfileScreen(viewModel: MyViewModel = viewModel()) {
    val profile = viewModel.userProfile

    LaunchedEffect(Unit) {
        viewModel.loadUserProfile()
    }

    profile?.let {
        Text("ユーザー名: ${it.name}")
    } ?: Text("読み込み中...")
}

domain/model/UserProfile.kt

data class UserProfile(
    val id: Int,
    val name: String,
    val email: String
)