LLMの学習方法(事前学習、FineTuning, RLHF)

LLMの学習方法は大きく分けて3つある

1. 事前学習(Pre-training)
目的:
「言語の基礎的な知識」を身につける段階
文法・語彙・一般常識・文脈の理解などを、膨大なテキストから学ぶ

方法:
教師なし学習(Self-supervised learning)
入力文の一部を隠して、隠された部分を予測する(=マスク言語モデル、例:BERT)
次の単語を予測する(=自己回帰モデル、例:GPT系列)

データ:
インターネット上のテキスト(Webページ、書籍、Wikipediaなど)
数百億〜数兆トークン規模

2. ファインチューニング(Fine-tuning)
目的:
事前学習で得た「言語の基礎力」を、特定の目的や用途に合わせて調整

方法:
教師あり学習(Supervised fine-tuning)
入力と正解(出力)のペアを与える。
例:「質問 → 回答」「命令 → 応答」
少量の高品質データで調整。

3.人間のフィードバックによる強化学習(RLHF: Reinforcement Learning from Human Feedback)
目的:
モデルの出力を「人間にとって好ましい形」にする。
(=有用で、正確で、安全で、自然な応答にする)

① SFTモデル(ファインチューニング済)を使って、複数の応答候補を生成。
② 人間の評価者が「どの回答が良いか」を順位付け。
**③ 報酬モデル(Reward Model)**を学習。
→ 「この応答が好ましい」と判断するスコア関数を作る。
**④ 強化学習(PPOなど)**でモデルを更新し、報酬が高い出力を出すように最適化。

### Pretraining

import torch
import torch.nn as nn
import torch.optim as optim

text = "I love AI . AI loves people ."
vocab = list(set(text.split()))
word2idx = {w:i for i,w in enumerate(vocab)}
idx2word = {i:w for w,i in word2idx.items()}

def make_data(text):
    words = text.split()
    X, Y = [], []
    for i in range(len(words)-2):
        X.append([word2idx[words[i]], word2idx[words[i+1]]])
        Y.append(word2idx[words[i+2]])
    return torch.tensor(X), torch.tensor(Y)


X, Y = make_data(text)

class TinyLM(nn.Module):
    def __init__(self, vocab_size, hidden_size=8):
        super().__init__()
        self.emb = nn.Embedding(vocab_size, hidden_size)
        self.fc = nn.Linear(hidden_size*2, vocab_size)

    def forward(self, x):
        x = self.emb(x).view(x.size(0), -1)
        return self.fc(x)

model = TinyLM(len(vocab))
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.01)

for epoch in range(200):
    output = model(X)
    loss = criterion(output, Y)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

test = torch.tensor([[word2idx["I"], word2idx["love"]]])
pred = torch.argmax(model(test), dim=1)
print("Predicted next word:", idx2word[pred.item()])

### Fine tuning

train_data = [
    ("Hello", "Hi there!"),
    ("How are you?", "I'm fine, thank you."),
    ("Bye", "Goodbye!")
]

from transformers import AutoTokenizer, AutoModelForCausalLM, Trainer, TrainingArguments

tokenizer = AutoTokenizer.from_pretrained("gpt2")
model = AutoModelForCausalLM.from_pretrained("gpt2")

# データを整形
inputs = [tokenizer(f"User: {q}\nAI:", return_tensors="pt") for q, _ in train_data]
labels = [tokenizer(a, return_tensors="pt")["input_ids"] for _, a in train_data]

# 学習っぽいループ
for i, (inp, lab) in enumerate(zip(inputs, labels)):
    out = model(**inp, labels=lab)
    loss = out.loss
    loss.backward()
print("Fine-tuning step complete ✅")

### RLHF

import random

# モデルの応答候補
responses = ["Sure, here’s an example.", "I don’t know.", "You’re stupid."]

# 人間が選んだランキング(良い順)
human_ranking = {"Sure, here’s an example.": 1, "I don’t know.": 0, "You’re stupid.": -1}

# 報酬モデル(スコア関数)を模擬
def reward(response):
    return human_ranking[response]

# 強化学習っぽい更新(高報酬応答を強化)
policy = {r: 0.0 for r in responses}
for _ in range(10):
    r = random.choice(responses)
    policy[r] += reward(r) * 0.1

print("Learned policy:", policy)

[android]Composeのanimate関数

class MainActivity : ComponentActivity() {

    overrider fun onCreate(savedInstanceState: Bundle?){
        super.onCreate(saveInstanceState)
        setContent {
            MaterialTheme {
                AnimatedBoxExample()
            }
        }
    }

    @Composable
    fun AnimatedBoxExample() {
        var expaned by remember {mutableStateOf(false)}

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

        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(32.dp),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Box(
                modifier = Modifier
                    .size(boxSize)
                    .padding(8.dp),
                contentAlignment = Alignment.Center
            ) {
                color = MaterialTheme.colorScheme.primary,
                modifier = Modifier.fillMaxSize()
            } {}
        }
        Spacer(modifier = Modifier.height(16.dp))

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

animateDpAsState:Dp 値を滑らかにアニメーションさせます。
targetValue:アニメーションで変化させたい目標値。
expanded を切り替えることで、Boxのサイズが 100.dp ↔ 200.dp にアニメーション付きで変化。

[LLM] トランスフォーマー構造の概要とAttentionの考え方

## 1. トランスフォーマーとは?
トランスフォーマーは2017年に登場した自然言語処理のニューラルネットワーク構造
RNNやCNNと違って系列データの処理を並列化できるのが最大の特徴
– 入力: 文章(単語やトークンに分けたもの)
– 出力: 文章の予測や分類など
– 主な構成:
1. エンコーダ: 入力を内部表現に変換
2. デコーダ: 内部表現から出力を生成
LLM(ChatGPTなど)は、基本的にデコーダ中心のトランスフォーマを使っている

## Attentionの考え方
Attentionは「文章の中でどの単語に注目すべきかを学習する仕組み」
文章: 「猫がネズミを追いかける」
「追いかける」を予測するとき、
「猫」が主語だから重要
「ネズミ」も目的語として重要
他の単語(「が」や「を」)はそれほど重要ではない
これを 数値化して重み付け するのが Attention の仕組み

## Self-Attentionの仕組み
トランスフォーマーでは 各単語が文章の他の単語を参照して特徴を作る
これを Self-Attention と呼ぶ

Query (Q):注目するための質問
Key (K):候補の特徴
Value (V):候補の情報

Query と Key の内積 → 「どれくらい注目すべきか」を示すスコア
Softmax で正規化 → 重みを 0〜1 に
Value に重みをかけて足し合わせ → 注目した情報の合成

## Multi-Head Attention
Attention は 1種類の見方だけでなく、複数の異なる視点で注目することができます。
これを Multi-Head Attention と呼ぶ

例:
1つ目のヘッド:主語と動詞の関係に注目
2つ目のヘッド:目的語と動詞の関係に注目
これにより、文脈を多面的に理解できる

なるほど~

[iOS/Swift] ネットワーク通信

swiftでネットワーク通知を学ぶ
– URLSession でAPIを叩く(例:天気アプリでAPIから天気データを取得)
– JSONデータのデコード(Codable)

対象API
https://api.open-meteo.com/v1/forecast?latitude=35.6895&longitude=139.6917&current_weather=true

データ

{
    "latitude":35.6895,
    "longitude":139.6917,
    "generationtime_ms":0.234,
    "utc_offset_seconds":0,
    "current_weather":{
        "temperature":28.3,
        "windspeed":5.2,
        "winddirection":90
    }
}

Swiftの場合だと、New Group で新規 Views, Modelsなどのディレクトリを作成する
– Views:画面用の SwiftUI View ファイルを入れる
– Models:JSON の Codable 構造体やデータモデルを入れる
models/Weather.swift

import Foundation

struct WeatherResponse: Codable {
    let current_weather: CurrentWeather
}

struct CurrentWeather: Codable {
    let temperature: Double
    let windspeed: Double
    let winddirection: Double
}

ContentView

import SwiftUI

struct ContentView: View {
    @State private var temperature: String = "__"
    @State private var windspeed: String = "__"
    
    var body: some View {
        VStack(spacing: 20) {
            Text("東京の現在天気")
                .font(.title)
            
            Text("気温: \(temperature)°C")
            Text("風速: \(windspeed)m/s")
            
            Button("天気を取得") {
                fetchWeather()
            }
        }
        .padding()
    }
    
    func fetchWeather() {
        guard let url = URL(string: "https://api.open-meteo.com/v1/forecast?latitude=35.6895&longitude=139.6917&current_weather=true") else { return }
        URLSession.shared.dataTask(with: url) { data, response, error in
            if let data = data {
                do {
                    let decoded = try JSONDecoder().decode(WeatherResponse.self, from: data)
                    DispatchQueue.main.async {
                                            self.temperature = String(decoded.current_weather.temperature)
                                            self.windspeed = String(decoded.current_weather.windspeed)
                                        }
                } catch {
                    print("JSONデコードエラー: \(error)")
                }
            } else if let error = error {
                print("通信エラー: \(error)")
            }
        }.resume()
    }
}

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

うおおおおおおおお、何だこれは すげえ

ポイント
– URLSession.shared.dataTask で非同期に API を叩く
– Codable で JSON を Swift の構造体にデコード
– DispatchQueue.main.async で UI 更新
– @State で変数をバインドして、UI に表示

[Figma] デザインを効率化・再利用する仕組みコンポーネント

### コンポーネントとは?
Figma内で再利用できる「部品テンプレート」です。
– 元(Main component)を変更すると、コピー(Instances)全てに反映される
– チームで一貫したデザインを維持できる
– 例えば「ボタン」「カード」「ヘッダー」などで使う

まずcomponentの作成

componentをフレームで利用する

テキスト内容は変更できる

### Variants
複数のデザインパターンを切り替えられるようにする
props = hover, default, … などと選択できるようにしていく

なるほど!なかなか奥が深い!

DifyにRedisを追加したい

$ sudo apt update
$ sudo apt install redis-server
$ sudo systemctl enable redis-server
$ sudo systemctl start redis-server

$ redis-cli ping

Vagrantfile

  config.vm.network "forwarded_port", guest: 6379, host: 6379 # 追加

うまくいかんな…

[android] データの非同期処理(Coroutines / Flow)

MainViewModel.kt

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

class MainViewModel : ViewModel() {
    private val _counter = MutableStateFlow(0)
    val counter: StateFlow<Int> = _counter

    fun startCounting() {
        viewModelScope.launch {
            for (i in 1..10) {
                delay(1000) // 1秒待つ
                _counter.value = i
            }
        }
    }
}
package com.example.myapplicationcoroutine

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import android.widget.Button
import android.widget.TextView
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.example.myapplicationcoroutine.ui.theme.MyApplicationCoroutineTheme

class MainActivity : ComponentActivity() {

    private val viewModel: MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val counterTextView = findViewById<TextView>(R.id.counterTextView)
        val startButton = findViewById<Button>(R.id.startButton)

        // ボタンを押すとカウントスタート
        startButton.setOnClickListener {
            viewModel.startCounting()
        }

        // Flowを監視してUI更新
        lifecycleScope.launch {
            viewModel.counter.collectLatest { value ->
                counterTextView.text = "カウント: $value"
            }
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    android:padding="16dp">

    <TextView
        android:id="@+id/counterTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="カウント: 0"
        android:textSize="24sp"
        android:layout_marginBottom="24dp"/>

    <Button
        android:id="@+id/startButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="カウント開始"/>
</LinearLayout>

[Swift] UsersDefaults

UsersDefaultsとはiOS や macOS アプリで ちょっとしたデータを簡単に保存できる仕組み です。

使い所
ユーザー名やメールアドレス
設定(ダークモードON/OFF、通知ON/OFFなど)
チュートリアルを見たかどうか
ちょっとしたリスト(お気に入りのIDなど)

struct ContentView: View {
    @State private var userMessage: String = ""
    @State private var aiReply: String = ""
    
    @State private var items: [String] = UserDefaults.standard.stringArray(forKey: "items") ?? ["りんご", "バナナ", "みかん"]
    
    var body: some View {
        NavigationView {
            VStack(spacing: 20) {
                Text("AI Chat Demo")
                    .font(.title)
                
                TextField("メッセージを入力", text: $userMessage)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .padding()
                
                Button("送信") {
                    sendMessage()
                }
                .padding()
                
                Text("AIの返答: \(aiReply)")
                    .padding()
                
                Divider()
                
                List {
                    ForEach(items, id:\.self) { item in
                        Text(item)
                    }
                    .onDelete(perform: deleteItem)
                    .onMove(perform: moveItem)
                }
                .frame(height: 200)
                
                Button("フルーツを追加") {
                    addItem()
                }
                .padding()
                
                Spacer()
                
                NavigationLink(destination: ImageViewPage()) {
                    Text("画像ページへ移動")
                        .foregroundColor(.white)
                        .padding()
                        .background(Color.blue)
                        .cornerRadius(8)
                }
                NavigationLink(destination: VideoViewPage()) {
                    Text("動画ページへ移動")
                        .foregroundColor(.white)
                        .padding()
                        .background(Color.green)
                        .cornerRadius(8)
                }
//                NavigationLink(destination: CounterPage()) {
//                    Text("カウンターページへ")
//                        .foregroundColor(.white)
//                        .padding()
//                        .background(Color.green)
//                        .cornerRadius(8)
//                }
            }
            .padding()
            .navigationTitle("チャット")
        }
    }
    
    func saveItems() {
        UserDefaults.standard.set(items, forKey: "items")
    }
    
    func addItem() {
        items.append("新しいフルーツ\(items.count + 1)")
        saveItems()
    }
    
    func deleteItem(at offsets: IndexSet) {
        items.remove(atOffsets: offsets)
        saveItems()
    }
    
    func moveItem(from source: IndexSet, to destination: Int) {
        items.move(fromOffsets: source, toOffset: destination)
        saveItems()
    }

[Figma]タイポグラフィ

左ツールバーの Text (T) を選択
画面にクリックして文字入力(例:「メールアドレス」)
ステップ2:文字の属性を変更
右パネルの Text セクションで変更可能:
Font family:フォントの種類
Weight:太さ(Regular, Bold, etc.)
Size:フォントサイズ
Line height:行の高さ
Letter spacing:文字間隔
Text decoration:下線・打ち消し線など

毎日5~10分でも変わりそうやな