Matematiksel Model
Kümeler, parametreler, ikili karar değişkenleri, amaç fonksiyonu ve kısıtlar.
Blok Transfer · 3 / 7
Matematiksel model bölümü, çoğu yazıda okuyucunun kaydırıp geçtiği yerdir. Sembol görünce insanın içinden “bunu anlamak için matematikçi olmak lazım” demek gelir. Bu bölümü atlamayın, çünkü burada yeni bir şey yok. Bir önceki yazıda Ali beyaz tahtaya bir terazi çizmişti: bir kefede alıcıda kazanılacak satış, öbür kefede vericide kaybedilecek satış artı taşıma masrafı. Aşağıdaki her sembolün o tahtada bir karşılığı var. Yaptığımız tek şey teraziyi çözücünün okuyabileceği dilde yazmak.
Bir sözüm var: her formülden sonra o formülün Türkçesini de yazacağım. İkisi uyuşmuyorsa formül yanlıştır, Türkçe değil.
Buradaki model uydurma değil; vakalar/blok-transfer/ altındaki çalışan
kodun birebir kendisi. Aşağıdaki bütün sayılar da Lumoda’nın sezon
ortasındaki, indirimden önceki bir pazartesinin gerçek stok fotoğrafından
çıktı.
Kümeler ve indisler
Üç küme var. İlk ikisi tahmin edeceğiniz gibi:
, karar anında açık olan fiziksel mağazalar. Lumoda’nın 84 fiziksel mağazasından dördü o pazartesi kapalı; online kanal da transfer almıyor.
Üçüncüsü bu modelin en önemli parçası — ve çoğu anlatımda es geçilen yer:
aday kümesidir: ” mağazasındaki option’ının bloğu mağazasına gidebilir mi?” sorusunun sorulmaya değer olduğu üçlüler. Sorulmaya değer olmayanlar buraya hiç girmez.
Neden önemli olduğunu sayılar söylüyor. Kısıtsız üçlü sayısı:
Yalnız karar anında rafta bulunan 552 option’a baksanız bile 3.488.640. Gerçek aday sayısı ise referans senaryoda, yani vericide coverMevcut stoğun satış hızına bölünmesiyle bulunan yeterlilik süresi; kaç haftalık satışı karşılayacağını söyler. eşiği 6 hafta ve alıcıda asgari haftalık satış 1 adet iken: 5.369.
On bir milyondan beş bine. Bu düşüşü yapan şey modelin kısıtları değil; kümenin tanımı. Verici olabilmek için stok bulunması, setin kırık olmaması (orta bedenlerin rafta durması), cover’ın eşiği geçmesi ve o rafın soğumada olmaması gerekiyor. Alıcı olabilmek için setin kırıkBir option'ın beden setinin bozulması; ara bedenler tükenmiş, uçlar rafta kalmıştır. ya da stoğun sıfır olması, üstüne satış hızının eşiği geçmesi gerekiyor. Tek bedenli bir option’ın seti zaten kırılamaz (Lumoda’da 211 tane var); onlar alıcı kümesine ancak rafları boşalınca girer. Outlet line’ındaki bir option’ın vitrin mağazasına gitmesi ise ticaretin yasağı. Ali’nin tahtaya yazdığı ikinci sayı, yani alıcıdaki azami cover, bu kümenin üçüncü kapısıdır (satıyor ama eldeki mal tavanın altında kalıyor); bu yazının bütün sayıları o kapı kapalıyken, alıcı yalnız kırık ya da stoksuz olabilirken çıktı.
Verici tanımındaki “setin kırık olmaması” şartına bir kez daha bakın. İkinci yazıda Pelin’in bulduğu tuzaktı: kırık setin satışı durur, cover’ı sonsuza gider ve cover’a tek başına bakan bir model o rafı en iyi verici sayar. Bunu bir kısıt satırıyla yasaklamadık. Kırık raf verici kümesine hiç girmiyor; çözücü, kırık bir setten mal çekmeyi düşünmek zorunda bile kalmıyor.
Bunların hepsini çözücüye kısıt olarak da yazabilirdik. Yazmadık, çünkü o zaman 11.818.400 değişkenli bir model kurup çözücüden bunların 11.813.031’ini “zaten imkânsız” diye elemesini beklerdik. Bu yazının en pratik cümlesi şu olabilir: modelin en önemli kısmı çoğu zaman yazmadığınız kısıtlardır. İş bilgisi modele kısıt olarak değil, kümenin tanımı olarak girdiğinde model küçülür ve okunur kalır.
Parametreler
Hepsi veriden ya da parametreler.py’den geliyor; hiçbiri elle yazılmış
sihirli sayı değil:
| Sembol | Anlamı | Değer / kaynak |
|---|---|---|
| mağazasındaki option’ının stoğu | Stok fotoğrafı | |
| Haftalık satış hızı | Son 8 haftadaki stoklu haftaların ortalaması | |
| Option’ın liste fiyatı | Ürün master’ı | |
| Planlama ufku | 8 hafta | |
| Adet başına taşıma maliyeti | 25 TL | |
| Rota başına sabit maliyet | 500 TL | |
| Minimum koli | 6 adet | |
| mağazasının kapasite boşluğu | Tepe stok − mevcut stok |
‘nun tanımındaki “stoklu” kelimesi kalın yazılmayı hak ediyor. Bir önceki yazıda Ali’nin “satmıyor ile satamıyor aynı şey değil” dediği yer burası: raf boşken geçen haftaları da ortalamaya katarsanız, stoksuz kaldığı için satamamış bir ürünü “yavaş satan ürün” ilan edersiniz. Hız yalnız malın rafta olduğu haftalardan ölçülür.
‘deki tepe stok, mağazanın son 52 haftada fiilen taşıdığı en yüksek toplam stok. Mağaza master’ında bir kapasite kolonu da var ama onu kullanmıyoruz, çünkü karar anında 80 mağazanın 78’i o sayıyı zaten aşmış durumda (medyan doluluk 1,76): mağazanın ne taşıyabildiğinin kanıtı kâğıttaki kapasite değil, daha önce taşıdığı yük.
Karar değişkenleri
İki tane. Birincisi işin kendisi:
Türkçesi: ‘deki bloğu ‘ye taşınsın mı, taşınmasın mı? Kesir yok; bloğun yarısı diye bir şey yok. Blok bölünmez, çünkü bölünen şey beden setidir.
İkincisi ilk bakışta gereksiz görünür:
Türkçesi: ‘den ‘ye bu hafta bir sevkiyat açılıyor mu? Neden ayrı bir değişken? Çünkü taşımanın iki tür maliyeti var. Adet başına olan kısmı () zaten ile ölçeklenir. Ama koli kaldırmanın, aracı yollamanın, karşı tarafın mal kabulünü açmasının bedeli adede değil, çifte bağlıdır: aynı iki mağaza arasında bir option da taşısanız beş option da taşısanız o masraf bir kez ödenir. Bunu ile ifade edemezsiniz; rotanın kendisine ait bir değişken gerekir.
Modelin incelikli yerlerinden biri burasıdır: olmadan model her hareketi tek başına değerlendirir, ile birlikte hareketleri birleştirmeyi öğrenir.
Amaç fonksiyonu
Önce tek bir adayın terazisi. Üç parça:
Türkçesi sırayla: alıcı bu bloğu ufuk içinde ne kadar satabilirse o kadar kazanırız; ama elindeki stoktan fazlasını satamaz, o yüzden minimum. Vericiden aldığımızda onun satacağı kadarını kaybederiz; aynı sebeple yine minimum. Taşıma adet başınadır.
Net ağırlık ikisinin farkı eksi masraf:
Ve amaç:
Türkçesi: seçtiğin hareketlerin net faydasını topla, açtığın her rota için sabit masrafı düş, kalanı büyüt. Tahtadaki terazinin tamamı bu satırda.
Kısıtlar
Dört tane, hepsi bir iş kuralının doğrudan çevirisi.
1. Blok tek hedefe gider.
Bir mağazadaki bir option’ın stoğu en çok bir yere gönderilebilir. Aynı bloğu ikiye bölüp iki mağazaya dağıtmak bu sürümün kapsamı dışında; sebebini aşağıda yazıyorum.
2. Alıcı kapasitesi aşılmaz.
Bir mağazaya boşluğundan fazla mal gönderemezsiniz. Deponun ve rafın sonu vardır. Bu kısıt kâğıt üzerinde kalmıyor: Lumoda’da 80 mağazanın 12’sinin karar anında hiç boşluğu yok; sonraki iki yazıda bu satırın planı nasıl biçimlendirdiğini göreceğiz.
3. Rota açılmadan taşıma olmaz.
Bir hareket varsa o çift arasında sevkiyat açılmış demektir. Bu kısıt ‘yi dürüst tutar: model bedava rota kullanamaz.
4. Açık rota koliyi doldurur.
Sevkiyat açtıysan en az minimum koli kadar mal taşı. İki adet için kamyon kaldırılmaz. Dikkat: bu kısıt üçüncüsüyle birlikte çalışır. Üçüncüsü “rota açılmadan hareket olmaz” der, dördüncüsü “rota açıldıysa boş gitmez” der. İkisi birden, modeli küçük hareketleri aynı rotada toplamaya zorlar.
Ölçek: 7.730 değişken, 8.965 kısıt
Referans senaryoda modelin gerçek boyutları şöyle:
| Parça | Sayı |
|---|---|
| değişkeni (aday hareket) | 5.369 |
| değişkeni (rota) | 2.361 |
| Toplam ikili değişken | 7.730 |
| Blok tekliği kısıtı | 1.155 |
| Kapasite kısıtı | 80 |
| Rota bağlama kısıtı | 5.369 |
| Minimum koli kısıtı | 2.361 |
| Toplam kısıt | 8.965 |
Şimdi şu 7.730 sayısına bakalım. Her değişken 0 veya 1 olabildiğine göre olası atama sayısı , yaklaşık . Gözlemlenebilir evrendeki atom sayısı kabaca olarak tahmin ediliyor. Yani bu zincirin tek bir haftalık transfer kararı, kaba kuvvetle denenmek istense, evrendeki bütün atomlara birer bilgisayar verseniz bile bitmez.
Çözücünün (CBC) bu modelle işini bitirme süresi: bir dakikadan kısa.
Bu iki cümle yan yana durunca insanın aklına ilk gelen şey “bir yerde hata olmalı” oluyor. Yok. Çözücü kombinasyonları saymıyor. Önce problemi gevşetiyor: ‘lerin 0 ile 1 arasında kesirli olmasına izin veriyor, ki bu hâli hızlı çözülen bir doğrusal program. Çıkan sayı gerçek en iyi çözümün üst sınırıdır. Sonra kesirli çıkan bir değişkeni seçip iki dal açıyor (“bu sıfır olsun” / “bu bir olsun”), her dalda üst sınırı yeniden hesaplıyor ve bir dalın üst sınırı eldeki en iyi çözümden kötüyse o dalın tamamını, milyarlarca kombinasyonu, tek hamlede çöpe atıyor. Adı dal-sınır. Bir önceki yazıda Ali’nin yüz elli bin kere kuramam dediği terazinin cevabı budur: kimse kurmuyor, çözücü bakmadan eliyor.
Bir şeyi de baştan söyleyeyim, çünkü çözücü çıktısını okurken en sık burada yanılıyoruz. Çözücüye “en iyiyi bul” demedik; “elindeki plan ile üst sınır arasındaki fark %0,5’in altına inince dur” dedik. Bu modelde durduğu noktada fark %0,18’di. Yani elimizdeki plan için “en iyi plan” diyemiyoruz; “en iyi plandan en fazla %0,18 uzak bir plan” diyebiliyoruz. Bu ikisinin farkını beşinci yazıda açacağım.
Çözücünün bulduğu plan 661 option’ı, toplam 109.100 adedi taşıyor. Sonraki iki yazıda bu sorunu iki ayrı yoldan çözeceğiz; iki yol aynı yere varmayacak.
Varsayımlar ve bu model nerede yetmez
Dürüst olmak lazım; yukarıdaki temizlik gerçek hayatın kendisi değil, sadeleştirilmiş hâli.
kâr değil, brüt ciro etkisidir. Kazanç ve kayıp liste fiyatıyla hesaplanıyor, alış maliyeti düşülmüyor. Yani model “bu transfer ne kadar ciroyu yer değiştirir” sorusunu cevaplıyor, “ne kadar kâr eder” sorusunu değil. Bir hareketin iki kefesinde de aynı ürün durduğu için marj iki kefeyi aynı oranda küçültür; asıl soru hareketleri birbiriyle yarıştırırken çıkar, çünkü Lumoda’da marj ürüne göre değişiyor: brüt marj %54,5 ile %68,4 arasında. Ölçtüğümüzde, teraziyi kârla kurunca açgözlü planın kabaca onda biri, çözücünün planının beşte biri değişiyor; kâr ise yalnız binde birkaç artıyor (açgözlüde %0,68, çözücüde %0,18; ikincisi çözücünün kendi toleransının içinde kalıyor). Amacı bu yüzden ciroda bıraktık. Sonuç yazısındaki TL rakamlarını okurken bunu akılda tutun: onlar kâr değil, hareket eden ciro.
Blok bölünmez. Bir mağazadan alınan stok tek hedefe gider. Bunu ikiye bölmek matematikte kolay görünür ama iki yeni sorun doğurur: hangi bedenler kime, ve iki yarım set bir tam setten daha mı iyi? O ayrı bir transfer türü ve kendi yazısını hak ediyor.
Ufuk sabit 8 hafta. Veride sezon kodu da indirim takvimi de var; karar anını bilerek sezonun ortasında, indirimden önce seçtik ve sekiz haftalık ufuk indirim başlamadan kapanıyor. Ama ikinci yazıdaki “raf ömrü kadranı”, yani sezon sonu yaklaştıkça terazinin taşımaya yatması, modelde yok. Gerçek bir kurulumda option başına değişir ve indirim tarihine bağlanır; takvim veride durduğu için bu bir veri eksiği değil, sıradaki adım.
Soğuma bütün varışları görüyor. Veride depo sevkiyatlarının yanında mağazalar arası geçmiş transferler de var (3.049 satır). Soğuma kuralı rafa malın hangi yoldan geldiğine bakmıyor; son iki haftada varış olduysa raf soğumada. Karar anında 11.250 raf soğumada, 21’ine mal başka bir mağazadan gelmiş. “Geçen hafta zaten oradan almıştık” kuralı bu yüzden sınanabiliyor.
Talep sansürlü ölçülüyor. ‘yu gözlenen satıştan hesaplıyoruz ama kırık setli bir mağazanın gerçek talebi ölçtüğümüzden yüksektir. Yani en çok yardıma muhtaç mağaza, modelin gözünde olduğundan biraz daha sönük görünür. Bunu düzeltmeye çalışmadık; düzeltmek tahmin üstüne tahmin kurmak olurdu ve modelin nereye dayandığını bulanıklaştırırdı.
Kapasite adet bazlı ve geçmişten ölçülüyor. Metrekare, reyon ve askı kapasitesi ayrı ayrı modellenmiyor; mağaza geçen yıl hiç taşımadığı bir yükü taşıyamaz sayılıyor. Sizin verinizde zor olan muhtemelen burası olacak: kâğıttaki kapasite ile mağazanın fiilen taşıdığı yük arasındaki farkı kimse bir tabloda tutmuyor; hangisinin doğru olduğuna veri değil mağaza karar veriyor.
Bir sonraki yazıda bu modeli hiç kurmadan, yalnız SQL ve bir sıralama kuralıyla probleme saldıracağız; sektörde bu iş çoğunlukla öyle yapılıyor çünkü. Sonra ikisini yan yana koyacağız. Şunu şimdiden söyleyeyim: aradaki fark yüzde 9,1; o farkın nereden çıktığı da tahmin ettiğiniz yer değil 🙂