ML.Net ile Müşteri Segmentasyonu | C#

Merhaba, veri analiz işinde, SQL’den gelen ham satış verilerinden anlamlı müşteri metrikleri (feature) üreten, ML.NET ile kümeleme (clustering) yapan, matematiksel olarak en başarılı modeli seçip ve çıkan sonuçları en popüler yapay zeka modellerine (Gemini, GPT, Claude, Grok) yorumlatarak PDF raporları üreten bir uygulama yazdım.

Projenin Ana Akışı

Geliştirdiğim mimari, verinin ilk doğduğu andan son PDF raporunun çıktığı ana kadar tamamen otomatize bir hat (pipeline) üzerinden ilerliyor:

  1. Veri Tedariği: Satış, müşteri ve kategori verileri asenkron olarak veritabanından çekiliyor.
  2. Feature Engineering (Özellik Mühendisliği): Ham işlemler, ML.NET’in anlayacağı ve kümeleme algoritmalarının işleyebileceği sayısal özetlere (MusteriFeature) dönüştürülüyor.
  3. Exploratory Data Analysis (EDA): Modeli eğitmeden önce verinin genel karakteristiği ve dağılımı analiz ediliyor.
  4. Model Otomasyonu ve Yarıştırma: Sistem sabit bir küme sayısı veya modelle yetinmiyor. 6 farklı özellik kombinasyonunu ve farklı K (küme sayısı) değerlerini otomatik olarak test ediyor.
  5. Model Seçimi ve JSON Çıktısı: Silhouette ve Davies-Bouldin metriklerine göre “En İyi” model otomatik belirlenip diske kaydediliyor ve yapay zekanın okuyabileceği zenginleştirilmiş bir JSON profili oluşturuluyor.
  6. Multi-LLM Analizi ve PDF Raporlama: Aynı JSON verisi; Gemini 2.0 Flash, GPT-4, Claude 3.7 Sonnet ve Grok 3.0 API’lerine gönderilerek pazarlama stratejileri alınıyor ve her satış temsilcisine özel PDF raporları basılıyor.

Bu ufak uygulama sayesinde, elle parametre denemek yerine tamamen ölçülebilir, test edilebilir ve her ay yeni verilerle kendini güncelleyebilen bir analiz hattı elde etmiş oluyoruz.

Aşağıdaki kod bloğu projenin ana motorunu oluşturuyor. Verilerin çekilmesinden, ön test aşamasına, oradan da tüm modellerin yarıştırılıp LLM katmanına aktarılmasına kadar olan tüm orkestrasyonu buradan yönetiyoruz:


using ConsoleMusteriSegmentasyonV1;
using Microsoft.Extensions.Configuration;
using Microsoft.ML;
using OrtakKutuphane;
using OrtakKutuphane.MusteriSegmentasyon;
using System.Globalization;
using System.Text;

Console.WriteLine("Hello, World!");
Console.WriteLine("Bismillah deyip, müşteri kümeleme işine başlıyoruz...");

string logFileName = $"console_{DateTime.Now:yyyy-MM-dd}.log";
string logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, logFileName);
var fileWriter = new StreamWriter(logFilePath, append: false)
{
    AutoFlush = true
};
var multiWriter = new MultiTextWriter(Console.Out, fileWriter);
Console.SetOut(multiWriter);
Console.SetError(multiWriter);

// 1) Dil seçimi
Console.WriteLine("Please select language / Lütfen dil seçin:");
Console.WriteLine("1) English");
Console.WriteLine("2) Türkçe");
Console.Write("Seçiminiz (1/2): ");
var choice = Console.ReadLine();
CultureInfo culture;
if (choice == "1")
    culture = CultureInfo.GetCultureInfo("en-US");
else
    culture = CultureInfo.GetCultureInfo("tr-TR");

Console.WriteLine($"Seçilen dil / Selected language: {culture.EnglishName}");
DateTime currentDate = new(DateTime.Today.Year, DateTime.Today.Month, 1, 0, 0, 0, DateTimeKind.Unspecified);

// 1. Satış verilerini veritabanından çekelim.
List<MusteriData> satislar = await SatisIslemleri.SatisVeriGetirAsync(Sabitler.HOLLANDA_FIRMA_KODU, currentDate);
Console.WriteLine($" {satislar.Count} satış kaydı yüklendi.\n");

// 2. Müşteri verilerini veritabanından çekelim.
List<MusteriTemsilciData> musteriler = await SatisIslemleri.MusteriVeriGetirAsync(Sabitler.HOLLANDA_FIRMA_KODU);
Console.WriteLine($" {musteriler.Count} müşteri kaydı yüklendi.\n");

// 3. Kategori verilerini veritabanından çekelim.
List<KategoriData> kategoriler = await SatisIslemleri.KategoriVeriGetirAsync(Sabitler.HOLLANDA_FIRMA_KODU);
Console.WriteLine($" {kategoriler.Count} kategori kaydı yüklendi.\n");

// 4. Verileri işlemeye hazır hale getirelim.
List<MusteriFeature> musteriVeriler = MusteriDataProcessor.MusteriVerileriniIsleVeFeatureListesiOlustur(satislar, currentDate);
Console.WriteLine($" {musteriVeriler.Count} müşteri verisi hazırlandı.\n");

// 5. YENİ: Exploratory Data Analysis (EDA)
MusteriDataAnalyzer.PerformEDA(musteriVeriler);

Console.WriteLine("\n" + new string('=', 60));
Console.WriteLine("ML.NET KÜMELEME BAŞLIYOR");
Console.WriteLine(new string('=', 60) + "\n");

var mlContext = new MLContext(seed: 42);
IDataView dataView = mlContext.Data.LoadFromEnumerable(musteriVeriler);

Console.WriteLine($" {musteriVeriler.Count} müşteri IDataView'a yüklendi.\n");

// ===================================================================
// 1. HIZLI ÖN TEST (Quick Sanity Check & Debug)
// ===================================================================
Console.WriteLine("=== HIZLI ÖN TEST: BASELINE MODEL (K=4) ===");
var testModel = ClusteringEngine.TrainModel(mlContext, dataView, ModelFeatures.BaselineFeatures, numberOfClusters: 4);
var testPredictions = ClusteringEngine.GetPredictions(mlContext, testModel, dataView);
ClusteringEngine.PrintClusterDistribution(testPredictions, musteriVeriler.Count, 4);
var testMetrics = ClusteringEngine.EvaluateModel(mlContext, testModel, dataView);

// ==============================
// 6 MODELİN PROFESYONEL KARŞILAŞTIRMASI
// ==============================
var allResults = new List<ModelResult>();

foreach (var kvp in ModelFeatures.AllModels)
{
    try
    {
        Console.WriteLine($"\n→ {kvp.Key} değerlendiriliyor... ({kvp.Value.Length} feature)");
        var result = ModelEvaluator.EvaluateModelSetWithSilhouette(mlContext, dataView, kvp.Key, kvp.Value);
        allResults.Add(result);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"   {kvp.Key} değerlendirilirken hata: {ex.Message}");
    }
}

// ==============================
// FINAL KARŞILAŞTIRMA VE EN İYİ MODELİ KAYDET
// ==============================
if (allResults.Count > 0)
{
    var sorted = allResults.OrderByDescending(r => r.Silhouette).ToList();
    var winner = sorted[0];
    string modelsDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Models");
    Directory.CreateDirectory(modelsDir);
    string winnerPath = Path.Combine(modelsDir, $"{winner.ModelName}_k{winner.SuggestedK}.zip");

    mlContext.Model.Save(winner.Model, dataView.Schema, winnerPath);
    
    string jsonForLlmApiPath = $"cluster_profile_for_llm_{dateStr}.json";
    string fullPath = Path.GetFullPath(jsonForLlmApiPath);
    var enriched = ClusterProfilerService.GenerateAndSaveReport(winner, musteriVeriler, fullPath);

    if (!string.IsNullOrEmpty(jsonForLlmApiPath))
    {
        // LLM API Çağrıları (Gemini, GPT, Claude, Grok)
        var configuration = new ConfigurationBuilder()
                    .SetBasePath(Directory.GetCurrentDirectory())
                    .AddJsonFile("appsettings.json", optional: false)
                    .Build();
                    
        var gemini = new GeminiIslemleri(configuration[Sabitler.GEMINI_API_KEY]);
        string geminiResponse = await gemini.JsonRaporAnalizi(jsonForLlmApiPath, culture.EnglishName);

        var gpt = new GptIslemleri(configuration[Sabitler.GPT_API_KEY]);
        string gptResponse = await gpt.JsonRaporAnalizi(jsonForLlmApiPath, culture.EnglishName);

        var claude = new ClaudeIslemleri(configuration[Sabitler.CLAUDE_API_KEY]);
        string claudeResponse = await claude.JsonRaporAnalizi(jsonForLlmApiPath, culture.EnglishName);

        var grok = new GrokIslemleri(configuration[Sabitler.GROK_API_KEY]);
        string grokResponse = await grok.JsonRaporAnalizi(jsonForLlmApiPath, culture.EnglishName);

        // Sonuçları birleştirme ve PDF üretimi
        string outPathSonuc = $"full_analysis_{dateStr}.json";
        string fullPathSonuc = Path.GetFullPath(outPathSonuc);
        AnalizSonucService.BuildAnalizSonuc(musteriler, winner, musteriVeriler, enriched, fullPathSonuc);

        AnalizSonucService.AddLlmResponse(fullPathSonuc, "gemini", "Gemini 2.0 Flash", geminiResponse);
        AnalizSonucService.AddLlmResponse(fullPathSonuc, "gpt", "GPT-5-Nano", gptResponse);
        AnalizSonucService.AddLlmResponse(fullPathSonuc, "claude", "Claude 3.7 Sonnet", claudeResponse);
        AnalizSonucService.AddLlmResponse(fullPathSonuc, "grok", "Grok 3.0", grokResponse);

        string reportsFolder = Path.Combine("Reports", dateStr);
        PdfRaporService.GenerateAllReports(fullPathSonuc, reportsFolder, culture);
    }
}

Sürecin sonunda elimizde sadece ham sayılar kalmıyor. ML.NET hangi müşterinin hangi grupta olduğunu söylerken; Claude, GPT, Gemini ve Grok ise o gruba nasıl satış yapmamız gerektiğini, hangi ürünleri önermemiz gerektiğini kurumsal bir dille anlatıyor. Yapay zekayı bir chatbot olmaktan çıkarıp, backend süreçlerimizin bir işçisi haline getirmiş olduk…

Selamlar!

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top