OpenAI Fine-tuning ile .NET'te Özel Model Eğitimi

Fine-tuning, genel amaçlı bir LLM'i kendi alanınıza özgü davranışlar sergilemesi için yeniden eğitmenin yoludur. Müşteri hizmetleri botları, kod asistanları veya belirli bir yazı stili için ince ayar yapılmış modeller, hem daha tutarlı hem de daha ucuz çıktı üretir.

Fine-tuning Ne Zaman Kullanılır?

  • Belirli bir format veya stil tutarlılığı gerektiğinde
  • Prompt mühendisliğiyle elde edilemeyen alan uzmanlığı için
  • Çok sayıda few-shot örneği yerine küçük, eğitilmiş model tercih edildiğinde
  • Yanıt kalitesini korurken maliyeti düşürmek için (mini model fine-tune)

Eğitim Verisi Hazırlama (JSONL)

public class TrainingDataBuilder
{
    public async Task BuildJsonlFileAsync(
        IEnumerable<(string UserMsg, string AssistantResponse)> examples,
        string outputPath,
        string systemPrompt = "Sen yardımcı bir teknik destek asistanısın.")
    {
        await using var writer = new StreamWriter(outputPath, append: false,
            encoding: System.Text.Encoding.UTF8);

        foreach (var (user, assistant) in examples)
        {
            var record = new
            {
                messages = new object[]
                {
                    new { role = "system", content = systemPrompt },
                    new { role = "user", content = user },
                    new { role = "assistant", content = assistant }
                }
            };

            await writer.WriteLineAsync(JsonSerializer.Serialize(record));
        }
    }
}

// Kullanım
var builder = new TrainingDataBuilder();
await builder.BuildJsonlFileAsync(
    new[]
    {
        ("ASP.NET Core'da middleware nedir?",
         "Middleware, HTTP istek-yanıt pipeline'ında sırayla çalışan bileşenlerdir. " +
         "Her middleware sonraki bileşeni çağırabilir ya da pipeline'ı bitirebilir."),
        ("IActionResult ile ActionResult farkı?",
         "IActionResult bir arayüzdür; ActionResult ise bu arayüzü uygulayan somut taban sınıftır. " +
         "ActionResult<T> ise tip güvenli yanıtlar için tercih edilir.")
    },
    "training_data.jsonl");

Veri Kalitesi Kontrolü

public class TrainingDataValidator
{
    private readonly Tokenizer _tokenizer;

    public ValidationReport Validate(string jsonlPath)
    {
        var lines = File.ReadAllLines(jsonlPath);
        var errors = new List<string>();
        var warnings = new List<string>();
        int totalTokens = 0;

        for (int i = 0; i < lines.Length; i++)
        {
            try
            {
                var record = JsonSerializer.Deserialize<TrainingRecord>(lines[i]);
                if (record?.Messages == null || record.Messages.Length < 2)
                {
                    errors.Add($"Satır {i + 1}: Eksik mesaj.");
                    continue;
                }

                int lineTokens = record.Messages.Sum(m => _tokenizer.CountTokens(m.Content));
                totalTokens += lineTokens;

                if (lineTokens > 4096)
                    warnings.Add($"Satır {i + 1}: {lineTokens} token (limit: 4096).");
            }
            catch (JsonException ex)
            {
                errors.Add($"Satır {i + 1}: Geçersiz JSON — {ex.Message}");
            }
        }

        return new ValidationReport(
            TotalExamples: lines.Length,
            TotalTokens: totalTokens,
            EstimatedCostUsd: totalTokens / 1000m * 0.008m, // gpt-4o-mini eğitim fiyatı
            Errors: errors,
            Warnings: warnings);
    }
}

Fine-tuning İşini Başlatma

using OpenAI.FineTuning;

public class FineTuningService
{
    private readonly FineTuningClient _client;

    public FineTuningService(IConfiguration config)
    {
        var openAi = new OpenAI.OpenAIClient(config["OpenAI:ApiKey"]!);
        _client = openAi.GetFineTuningClient();
    }

    public async Task<string> StartFineTuningAsync(
        string trainingFilePath,
        string? validationFilePath = null)
    {
        // 1. Dosyayı yükle
        await using var trainingStream = File.OpenRead(trainingFilePath);
        var trainingFile = await UploadFileAsync(trainingStream, "training_data.jsonl");

        string? validationFileId = null;
        if (validationFilePath != null)
        {
            await using var valStream = File.OpenRead(validationFilePath);
            validationFileId = await UploadFileAsync(valStream, "validation_data.jsonl");
        }

        // 2. Fine-tuning işini oluştur
        var job = await _client.CreateJobAsync(new FineTuningOptions
        {
            Model = "gpt-4o-mini-2024-07-18",
            TrainingFile = trainingFile,
            ValidationFile = validationFileId,
            Hyperparameters = new HyperparameterOptions
            {
                NEpochs = 3, // 3 epoch genellikle iyi bir başlangıç noktası
                BatchSize = "auto",
                LearningRateMultiplier = "auto"
            },
            Suffix = "teknik-destek-v1"
        });

        return job.Value.Id;
    }

    private async Task<string> UploadFileAsync(Stream stream, string filename)
    {
        var files = new OpenAI.Files.FileClient(/* ... */);
        var result = await files.UploadFileAsync(stream, filename,
            OpenAI.Files.FileUploadPurpose.FineTune);
        return result.Value.Id;
    }
}

İş Durumu Takibi

public async Task<FineTuningJobStatus> PollJobStatusAsync(string jobId)
{
    while (true)
    {
        var job = await _client.GetJobAsync(jobId);
        var status = job.Value.Status;

        Console.WriteLine($"[{DateTime.UtcNow:HH:mm:ss}] Status: {status}");

        if (status == "succeeded")
        {
            Console.WriteLine($"Model: {job.Value.FineTunedModel}");
            return FineTuningJobStatus.Succeeded;
        }

        if (status == "failed")
        {
            Console.WriteLine($"Hata: {job.Value.Error?.Message}");
            return FineTuningJobStatus.Failed;
        }

        if (status is "running" or "queued" or "validating_files")
        {
            await Task.Delay(TimeSpan.FromMinutes(1));
            continue;
        }

        return FineTuningJobStatus.Unknown;
    }
}

Fine-tuned Modeli Kullanma

// Fine-tuning tamamlandıktan sonra
// Model ID: ft:gpt-4o-mini-2024-07-18:org:teknik-destek-v1:abcXYZ

public class FineTunedChatService
{
    private readonly ChatClient _client;

    public FineTunedChatService(IConfiguration config)
    {
        var openAi = new OpenAI.OpenAIClient(config["OpenAI:ApiKey"]!);
        // Fine-tuned model ID'sini kullan
        _client = openAi.GetChatClient(config["OpenAI:FineTunedModelId"]!);
    }

    public async Task<string> ChatAsync(string userMessage)
    {
        var result = await _client.CompleteChatAsync(
            [new UserChatMessage(userMessage)],
            new ChatCompletionOptions { MaxOutputTokenCount = 500 });

        return result.Value.Content[0].Text;
    }
}

A/B Testi ile Model Karşılaştırma

public class ModelAbTestService
{
    private readonly ChatClient _baseModel;
    private readonly ChatClient _fineTunedModel;

    public async Task<AbTestResult> CompareAsync(string prompt)
    {
        var baseTask = _baseModel.CompleteChatAsync([new UserChatMessage(prompt)]);
        var ftTask = _fineTunedModel.CompleteChatAsync([new UserChatMessage(prompt)]);

        await Task.WhenAll(baseTask, ftTask);

        return new AbTestResult(
            BaseResponse: (await baseTask).Value.Content[0].Text,
            FineTunedResponse: (await ftTask).Value.Content[0].Text,
            BaseTokensUsed: (await baseTask).Value.Usage.TotalTokenCount,
            FineTunedTokensUsed: (await ftTask).Value.Usage.TotalTokenCount);
    }
}

Sonuç

Fine-tuning, özellikle tekrarlayan, belirli bir domain'e özgü görevler için büyük değer yaratır. gpt-4o-mini'yi kendi verilerinizle ince ayar yaparak hem daha tutarlı yanıtlar elde eder hem de maliyeti düşürürsünüz. Başarılı bir fine-tuning için yüksek kaliteli, çeşitli ve en az 50-100 örnek içeren bir veri seti hazırlayın.