Ders 04 / 22
Yayılım
Yayılım kuralı standart kitaplıkla modellenir ve sekiz biçim çifti üzerinde koşturulur: 4 çift doğru sonuç, 2 çift hata, 2 çift sessizce beklenmedik biçimde bir dizi üretir. Sessiz durumun bedeli sayılır: (1260, 1) ile (1, 1260) çarpımı 1.260 öğe yerine 1.587.600 öğe verir, ayrılan bayt 10.160'tan 12.700.880'e çıkar, öğelerin yalnız 1.260'ı doğrudur ve raporlanan kişi başı ortalama 36,938180 yerine 36,497564 m³ olur. İkinci sessiz durumda dönem tabanı 3 değer yerine 9 değer üretir ve düzeltilmiş taban toplamı 83,901904 m³ yerine 251,846368 m³ çıkar.
İçindekiler
Önceki derste bütün işlemler bir dizi ile bir sayı arasında yapıldı ve biçim hiç değişmedi. Gerçek hesaplarda iki dizi karşılaşır ve biçimleri çoğu zaman aynı olmaz: 3.780 hücrelik okuma tablosunun yanında dönem başına bir taban (3 değer), abone başına bir hane sayısı (1.260 değer) ve tarife basamağı başına bir birim fiyat (3 değer) durur. Bunları teker teker döngüyle eşleştirmek yerine dizi arayüzü yayılım kuralını uygular: küçük biçim, büyük biçme uyacak şekilde tekrarlanmış sayılır.
Kural iki satırda anlatılır ve tam olarak iki satır olduğu için tehlikelidir. Biçimler sağdan hizalanır; hizalanan her boyutta ya iki sayı eşittir ya da biri 1’dir. Bu koşul sağlanırsa işlem yapılır. Koşulun sağlanması işlemin doğru olduğu anlamına gelmez, yalnız mümkün olduğu anlamına gelir. Bu dersin ölçtüğü şey aradaki farktır.
- SH7. Yayılım kuralı biçimlerin uyumunu denetler, işlemin anlamını denetlemez. Kural sağlandığında hesap yapılır ve hiçbir uyarı üretilmez.
- SH8. Uyumsuz bir biçim çifti iki sonuçtan birini verir: bir hata ya da beklenmedik büyüklükte bir dizi. Birincisi hesabı durdurur, ikincisi hesabı sürdürür ve öğe sayısını binlerce kat büyütebilir.
Kuralı Yazmak
# yayilim.py — MODELDIR: yayilim kurali standart kitaplikla yaziliyor. Kural: # bicimler sagdan hizalanir; her boyutta ya iki sayi esittir ya da biri 1'dir. # Kural saglanirsa islem yapilir. Kutuphane cagrilmiyor. import math def yayilim_bicimi(b1, b2): n = max(len(b1), len(b2)) s1 = (1,) * (n - len(b1)) + tuple(b1) s2 = (1,) * (n - len(b2)) + tuple(b2) c = [] for x, y2 in zip(s1, s2): if x == y2 or x == 1 or y2 == 1: c.append(max(x, y2)) else: return None return tuple(c) oge = lambda b: 0 if b is None else math.prod(b) CIFT = [ ("okuma x tek katsayi", (1260, 3), (1,), 3780), ("okuma x donem tabani", (1260, 3), (3,), 3780), ("okuma x abone hane sayisi", (1260, 3), (1260, 1), 3780), ("okuma x abone (duz yazim)", (1260, 3), (1260,), 3780), ("okuma x donem (iki boyut)", (1260, 3), (1, 3), 3780), ("abone toplami x hane satiri", (1260, 1), (1, 1260), 1260), ("donem tabani x donem duzeltme", (3,), (3, 1), 3), ("okuma x iki basamak", (1260, 3), (2,), 3780), ] gen = lambda w: lambda h: "".join(str(v).ljust(w[0]) if j == 0 else str(v).rjust(w[j]) for j, v in enumerate(h)) y = gen([32, 12, 11, 13, 11, 11, 9]) print(y(["islem", "bicim 1", "bicim 2", "sonuc bicimi", "oge", "beklenen", "sonuc"])) hata = sessiz = dogru = 0 for ad, b1, b2, bek in CIFT: c = yayilim_bicimi(b1, b2) if c is None: d, hata = "hata", hata + 1 elif oge(c) != bek: d, sessiz = "sessiz", sessiz + 1 else: d, dogru = "dogru", dogru + 1 print(y([ad, str(b1), str(b2), str(c) if c else "-", oge(c), bek, d])) print(f"\n{len(CIFT)} cift: {dogru} dogru, {hata} hata, {sessiz} sessizce beklenmedik bicim")
islem bicim 1 bicim 2 sonuc bicimi oge beklenen sonuc okuma x tek katsayi (1260, 3) (1,) (1260, 3) 3780 3780 dogru okuma x donem tabani (1260, 3) (3,) (1260, 3) 3780 3780 dogru okuma x abone hane sayisi (1260, 3) (1260, 1) (1260, 3) 3780 3780 dogru okuma x abone (duz yazim) (1260, 3) (1260,) - 0 3780 hata okuma x donem (iki boyut) (1260, 3) (1, 3) (1260, 3) 3780 3780 dogru abone toplami x hane satiri (1260, 1) (1, 1260) (1260, 1260) 1587600 1260 sessiz donem tabani x donem duzeltme (3,) (3, 1) (3, 3) 9 3 sessiz okuma x iki basamak (1260, 3) (2,) - 0 3780 hata 8 cift: 4 dogru, 2 hata, 2 sessizce beklenmedik bicim
İlk üç satır kuralın işe yaradığı yerlerdir ve kazanç gerçektir. Dönem başına taban çıkarmak elle yazıldığında iki iç içe döngü ve bir dizin hesabı tutar; yayılımla tek işlemdir ve 3 değerlik taban 1.260 kez tekrarlanmış gibi davranır — tekrarlanan kopya bellekte oluşturulmadan. Abone başına hane sayısı da aynı şekilde, biçim (1260, 1) yazıldığı sürece, doğru yönde yayılır.
Dördüncü satır kuralın koruduğu yerdir. Abone başına hane sayısı düz bir dizi olarak, yani (1260,) biçiminde yazılırsa sağdan hizalama son boyutları karşılaştırır: 3 ile 1.260. İkisi de birbirine eşit değil ve hiçbiri 1 değil, dolayısıyla kural sağlanmaz ve işlem yapılmaz. Bu bir kusur değil, kuralın tek gerçek güvencesidir: yanlış eksende hizalama girişimi çoğu zaman durdurulur. Sekizinci satır da aynı türdendir; iki basamaklık bir katsayı dizisi üç dönemlik bir tabloya yayılamaz.
Altıncı ve yedinci satırlar ise durdurulmaz. Kural sağlanır, işlem yapılır ve sonuç beklenenden başka bir biçimdedir. Sekiz çiftin ikisi bu durumda ve ikisinin de ortak yanı şudur: yazan kişi biçimlerden birini transpoze yazmıştır. Bir sonraki bölüm bunun bedelini sayıyor.
Sessizce Üretilen Sonuç
# sessiz.py — MODELDIR: sessizce uretilen sonucun bedeli. Abone basina toplam # tuketim, abone basina hane sayisina bolunuyor. Hane sayisi kurgudur. import sys from array import array def uretec(tohum): s = tohum & 0xFFFFFFFF def sonraki(): nonlocal s s = (1664525 * s + 1013904223) & 0xFFFFFFFF return s / 4294967296 return sonraki ABONE, DONEM = 1260, 3 r = uretec(20260218) HAM = [round(4 + r() * 56, 2) for _ in range(ABONE * DONEM)] r2 = uretec(9110723) p = [r2() for _ in range(ABONE * DONEM)] BOS = set(sorted(range(len(p)), key=lambda j: p[j])[:581]) OKUMA = [0.0 if j in BOS else HAM[j] for j in range(ABONE * DONEM)] r3 = uretec(31081977) HANE = [1 + int(r3() * 5) for _ in range(ABONE)] TOPLAM = [sum(OKUMA[i*DONEM:(i+1)*DONEM]) for i in range(ABONE)] # Dogru yazim: (1260, 1) ile (1260, 1) DOGRU = array("d", [TOPLAM[i] / HANE[i] for i in range(ABONE)]) # Sessiz yazim: (1260, 1) ile (1, 1260) -> (1260, 1260) YANLIS = array("d", [TOPLAM[i] / HANE[j] for i in range(ABONE) for j in range(ABONE)]) kosegen = [YANLIS[i * ABONE + i] for i in range(ABONE)] dogru_oge = sum(1 for i in range(ABONE) if abs(kosegen[i] - DOGRU[i]) < 1e-9) z = gen([26, 18, 20, 12]) print(z(["olcu", "dogru yazim", "sessiz yazim", "oran"])) print(z(["sonuc bicimi", "(1260, 1)", "(1260, 1260)", "-"])) print(z(["oge sayisi", len(DOGRU), len(YANLIS), f"{len(YANLIS)//len(DOGRU)}x"])) print(z(["ayrilan bayt", sys.getsizeof(DOGRU), sys.getsizeof(YANLIS), f"{sys.getsizeof(YANLIS)//sys.getsizeof(DOGRU)}x"])) print(z(["kisi basi ortalama", f"{sum(DOGRU)/len(DOGRU):.6f}", f"{sum(YANLIS)/len(YANLIS):.6f}", "-"])) print(f"\nsessiz dizinin {len(YANLIS)} ogesinin {dogru_oge}'i dogru (kosegen), " f"{len(YANLIS)-dogru_oge} ogesi bir abonenin tuketimini baska bir abonenin hanesine boluyor") print(f"raporlanan kisi basi ortalama {sum(DOGRU)/len(DOGRU):.6f} yerine " f"{sum(YANLIS)/len(YANLIS):.6f} m3, fark {abs(sum(YANLIS)/len(YANLIS)-sum(DOGRU)/len(DOGRU)):.6f} m3") # Ikinci sessiz durum: donem tabani (3,) ile mevsim duzeltmesi (3, 1). DONEM_ORT = [sum(OKUMA[i*DONEM+d] for i in range(ABONE)) / ABONE for d in range(DONEM)] DUZELTME = [0.98, 1.03, 1.10] # kurgu mevsim duzeltmesi d3 = [DONEM_ORT[d] * DUZELTME[d] for d in range(DONEM)] s3 = [DONEM_ORT[d] * DUZELTME[e] for e in range(DONEM) for d in range(DONEM)] print(f"\nikinci sessiz durum: donem tabani (3,) ile duzeltme (3, 1) carpiminda sonuc " f"{len(d3)} deger yerine {len(s3)} deger") print(f"duzeltilmis taban toplami {sum(d3):.6f} m3 yerine {sum(s3):.6f} m3; " f"bicim buyudugu icin toplam {sum(s3)/sum(d3):.4f} katina cikti")
olcu dogru yazim sessiz yazim oran sonuc bicimi (1260, 1) (1260, 1260) - oge sayisi 1260 1587600 1260x ayrilan bayt 10160 12700880 1250x kisi basi ortalama 36.938180 36.497564 - sessiz dizinin 1587600 ogesinin 1260'i dogru (kosegen), 1586340 ogesi bir abonenin tuketimini baska bir abonenin hanesine boluyor raporlanan kisi basi ortalama 36.938180 yerine 36.497564 m3, fark 0.440616 m3 ikinci sessiz durum: donem tabani (3,) ile duzeltme (3, 1) carpiminda sonuc 3 deger yerine 9 deger duzeltilmis taban toplami 83.901904 m3 yerine 251.846368 m3; bicim buyudugu icin toplam 3.0017 katina cikti
Hesap şudur: her abonenin üç dönemlik toplam tüketimi, o abonenin hane sayısına bölünecek. Doğru sonuç 1.260 değerdir. Hane sayısı dizisi satır olarak yazılınca, yani (1260, 1) yerine (1, 1260) biçiminde, kural yine sağlanır — birinci boyutta 1.260 ile 1, ikinci boyutta 1 ile 1.260 karşılaşır ve her ikisinde de taraflardan biri 1’dir. Sonuç 1.587.600 öğelik bir dizidir ve içinde her abonenin tüketimi her abonenin hane sayısına bölünmüş halde durur.
Bedelin üç ayrı ölçüsü var. Öğe sayısı 1.260 kat, ayrılan bayt 10.160’tan 12.700.880’e, yani 1.250 kat büyür; belleğe sığdığı sürece bu büyüme hiçbir uyarı üretmez ve tam da sığdığı için fark edilmez. Doğruluk ölçüsü daha keskin: 1.587.600 öğenin yalnız 1.260’ı doğrudur, bunlar da köşegen üzerindekilerdir. Kalan 1.586.340 öğe bir abonenin tüketimini başka bir abonenin hanesine bölmektedir, yani hiçbir gerçek karşılığı yoktur.
Raporlanan sayı ise bu iki ölçünün hiçbirini yansıtmaz. Kişi başı ortalama tüketim doğru dizide 36,938180 m³, sessiz dizide 36,497564 m³ çıkar; aradaki fark 0,440616 m³, yaklaşık yüzde 1,2. Bu yayılımın en tehlikeli yanıdır: yanlış sonuç makul görünür. Bir bulgu 36,5 ile 36,9 arasında oynadığında kimse biçim kontrolü yapmayı düşünmez. Sayının kendisi hatayı ele vermez; hatayı ele veren tek şey öğe sayısıdır ve o sayı çoğu zaman rapora girmez.
İkinci sessiz durum daha küçüktür ve tam bu yüzden daha sinsidir. Üç dönemin ortalama tabanı üç mevsim düzeltmesiyle çarpılacak; düzeltme sütun olarak yazılınca sonuç 3 değer yerine 9 değer olur. Düzeltilmiş taban toplamı 83,901904 m³ yerine 251,846368 m³, yani 3,0017 katı çıkar. Buradaki çarpan tam olarak 3 değildir ve bu ayrıntı önemlidir: tam bir katsayı görülse “bir yerde üçle çarpmışım” denip aranabilirdi, 3,0017 ise hesabın kendisinden gelen bir sayı gibi durur.
Bu iki durumdan çıkan çalışma kuralı tek cümledir: yayılım kullanılan her işlemde sonucun biçimi yazılır ve beklenen öğe sayısıyla karşılaştırılır. Kural sağlandığı için sonucun doğru olduğu varsayılamaz; kuralın sağlanması yalnızca hesabın yapılabildiğini söyler. Yukarıdaki sekiz çiftin ikisinde hesap yapılmadı ve bu iyi haberdi; ikisinde yapıldı ve kötü haber oydu.
Kuralın Kazandırdığı
# kazanc.py — MODELDIR: yayilimin kazanci. Donem basina taban cikarilacak. # Elle yol tabani 3.780 ogeye kopyalar; yayilim kopyalamaz, dizini hesaplar. TABAN = array("d", [9.80, 10.40, 11.20]) # kurgu donem tabani, m3 def kopyalayarak(v, t): genis = array("d", [t[j % DONEM] for j in range(len(v))]) return array("d", [v[j] - genis[j] for j in range(len(v))]), sys.getsizeof(genis) def yayilarak(v, t): return array("d", [v[j] - t[j % DONEM] for j in range(len(v))]), 0 K1, ek1 = kopyalayarak(OKUMA, TABAN) K2, ek2 = yayilarak(OKUMA, TABAN) sapan = sum(1 for a, b in zip(K1, K2) if abs(a - b) > 1e-9) w = gen([26, 16, 16, 12]) print(w(["olcu", "kopyalayarak", "yayilarak", "oran"])) print(w(["taban dizisinin bayti", sys.getsizeof(TABAN), sys.getsizeof(TABAN), "1x"])) print(w(["ek olarak ayrilan bayt", ek1, ek2, "-"])) print(w(["toplam ayrilan bayt", ek1 + sys.getsizeof(K1), ek2 + sys.getsizeof(K2), f"{(ek1+sys.getsizeof(K1))/(ek2+sys.getsizeof(K2)):.1f}x"])) print(f"\niki yol ayni sonucu veriyor: {sapan} oge 1e-9 m3 esiginin ustunde sapiyor") print(f"yayilim 3 degerlik tabani {len(OKUMA)} ogeye kopyalamadan uyguluyor; " f"kopyalanan surumde taban {sys.getsizeof(TABAN)} bayt yerine {ek1} bayt tutuyor")
olcu kopyalayarak yayilarak oran taban dizisinin bayti 104 104 1x ek olarak ayrilan bayt 30320 0 - toplam ayrilan bayt 60640 30320 2.0x iki yol ayni sonucu veriyor: 0 oge 1e-9 m3 esiginin ustunde sapiyor yayilim 3 degerlik tabani 3780 ogeye kopyalamadan uyguluyor; kopyalanan surumde taban 104 bayt yerine 30320 bayt tutuyor
Kuralın kazancı bu tabloda duruyor ve hesaplanan sonuçla ilgili değildir — iki yol da aynı 3.780 değeri veriyor, sapma 1e-9 m³ eşiğinin üstünde tek öğede bile yok. Fark kopyalamadadır. Dönem tabanını elle uygulamanın en doğrudan yolu, 3 değeri 3.780 öğeye genişletip iki eşit biçimi öğe öğe çıkarmaktır; bu genişletme 30.320 bayt tutar ve toplam ayrılan bayt 60.640’a çıkar. Yayılım genişletilmiş diziyi hiç kurmaz; küçük biçimin dizinini büyük biçimden hesaplar ve taban 104 bayt olarak kalır. Toplam ayrılan bayt yarıya, 30.320’ye iner.
Oran burada 2 kat çünkü genişletilen taraf sonuç dizisiyle aynı boyda. Küçük dizinin büyük diziye oranı düştükçe kazanç büyür: aynı işlem 3 dönem yerine 3 tarife basamağıyla ve 100.000 satırlık bir tabloyla yapılsaydı kopyalanan sürüm iki tam tablo, yayılım tek tablo tutardı. Yayılımın kazandırdığı şey hesap değil, kurulmayan dizidir ve bir önceki bölümdeki bedelin de aynı yerden geldiğine dikkat etmek gerekir: kurulmayan dizi ucuz olduğu için beklenmedik biçimdeki dizi de ucuza kurulur ve dikkat çekmeden büyür.
Özet
- Yayılım kuralı biçimleri sağdan hizalar ve her boyutta eşitlik ya da 1 arar; kural biçim uyumunu denetler, işlemin anlamını denetlemez.
- Sekiz biçim çiftinin 4’ü doğru sonuç, 2’si hata, 2’si sessizce beklenmedik biçimde bir dizi üretir.
- Hane sayısı satır olarak yazıldığında sonuç 1.260 öğe yerine 1.587.600 öğe olur; ayrılan bayt 10.160’tan 12.700.880’e, yani 1.250 kat büyür.
- Sessiz dizinin 1.587.600 öğesinin yalnız 1.260’ı doğrudur; kalan 1.586.340 öğenin gerçek bir karşılığı yoktur ve raporlanan kişi başı ortalama 36,938180 yerine 36,497564 m³ çıkar.
- Dönem tabanı düzeltmesi 3 değer yerine 9 değer üretir ve düzeltilmiş taban toplamını 83,901904 m³’ten 251,846368 m³’e, 3,0017 katına taşır.
- Kuralın kazancı kurulmayan dizidir: dönem tabanını kopyalayarak uygulamak 30.320 bayt ek yer ister, yayılım 0 bayt ister ve toplam ayrılan bayt 60.640’tan 30.320’ye iner.
Sonraki Adım
Yayılım aynı biçimdeki iki diziyi öğe öğe eşleştirir ve bu, elde edilebilecek en basit çok dizili işlemdir. Tarife hesabının kendisi ise öğe eşleştirmesi değildir: üç basamağın birim fiyatları ile üç dönemin tüketimi çarpılıp toplandığında her sonuç değeri bütün bir satır ile bütün bir sütunun birlikte hesabıdır. Sonraki ders bu işlemi — matris çarpımını ve ondan türeyen çözüm problemini — elle döngüyle ve soyutlamayla yazar, geçiş sayılarını karşılaştırır ve sayısal duyarlılığın nerede devreye girdiğini ölçer. Biçim uyumu orada da belirleyicidir, ama kural değişir: yayılımda uyum sağdan hizalamayla aranırken çarpımda tek bir eşitlik aranır ve o eşitlik sağlanmadığında işlem hiçbir koşulda yapılamaz.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.