26 Ekim 2014 Pazar

Numpy: Matrisler ve Basit Lineer Cebir

Sayısal analiz kitaplarının muhtevasını incelediğinizde fark ederseniz; birçoğu ilk bölümlerde doğrusal denklem sistemleri yani lineer cebir konusunu ele alır. Lineer cebirin bu kadar önemli olmasının sebebi, sayısal hesaplamaların çoğunun nihayetinde matris ve vektörlerden oluşan  doğrusal denklemlere indirgeniyor olmasıdır. Derslerimizin şu ana kadar ki kısımlarında genel olarak tek boyutlu vektörlerden bahsettik. Şimdi matrisleri biraz ayrıntılı irdeleyip Numpy kütüphanesini kullanarak temel lineer cebir işlemleri nasıl yapabileceğimiz öğrenelim.  
Matrisler
Matrisleri vektörler gibi oluşturabiliriz. Ancak matrislere özgü fonksiyonlar da mevcuttur. Bunları eye, identity, diag fonskiyonları olarak sıralayabiliriz. eye, identity fonksiyonları birbirine çok benzer olup birim matris oluşturmaya yarar.
>>> from numpy import *    # Numpy kütüphanesini ön alan adı eki olmadan ekliyoruz
>>> print(identity(3))     # birim köşegen matris
[[ 1.  0.  0.]
 [ 0.  1.  0.]
 [ 0.  0.  1.]]
>>> print(eye(3))          # birim köşegen matris
[[ 1.  0.  0.]
 [ 0.  1.  0.]
 [ 0.  0.  1.]]
>>> print(eye(3, k=1))     # köşegenin bir üst çapraza birim vektör ekleme 
[[ 0.  1.  0.]
 [ 0.  0.  1.]
 [ 0.  0.  0.]]
>>> print(eye(3, k=-2))    # köşegenin iki alt çapraza birim vektör ekleme 
[[ 0.  0.  0.]
 [ 0.  0.  0.]
 [ 1.  0.  0.]]
diag ise oldukça işlevsel bir fonksiyondur ve girilen bir vektörden köşegen matris oluşturur. Bu komut sayesinde aynı zamanda bant matrisleri de çok kolay oluşturabiliriz.
>>> print(diag([2,3,4], k=0))       # [2,3,4] dizisinden köşegen matris
[[2 0 0]
 [0 3 0]
 [0 0 4]]
>>> print(diag([2,3,4], k=1))       # aynı diziden bir üst köşegen matris
[[0 2 0 0]
 [0 0 3 0]
 [0 0 0 4]
 [0 0 0 0]]
>>>
>>> # üçlü bant matris oluşturma
>>> print(diag(ones(4),k=-1) + diag(2*ones(5),k=0) + diag(ones(4), k=1))
[[ 2.  1.  0.  0.  0.]
 [ 1.  2.  1.  0.  0.]
 [ 0.  1.  2.  1.  0.]
 [ 0.  0.  1.  2.  1.]
 [ 0.  0.  0.  1.  2.]]

Lineer Cebire Giriş
Lineer cebir temel olarak matrislerin özelliklerini ve tek boyutlu vektörlerle olan işlemlerinin halidir. En genel halde bir lineer cebrik sistem A matris x ve b tek boyutlu vektör olmak üzere Ax=b şeklinde bir forma sahiptir. Bu sistemin çözülebilmesi için gerekli olan temel işleçler çarpım ve bölme işlemleridir. Matris-matris, matris-vektör çarpma/bölme işlemi bir önceki yazımızda anlattığımız vektörel aritmetikten farklı olup birebir elemanların çarpımı veya bölümüyle yapılmaz. O yüzden Numpy'de bu amaçla hususi fonksiyonlar tanımlanmıştır. Farkı anlamak için aşağıdaki örneklere göz atalım.
>>> A = ones((4,4))    # 1'lerden oluşan matris
>>> x = ones(4)        # 1'lerden oluşan vektör
>>> b = A*x            # matrisin her bir sütununun x ile vektörel çarpımı
>>> print(b)
[[ 1.  1.  1.  1.]
 [ 1.  1.  1.  1.]
 [ 1.  1.  1.  1.]
 [ 1.  1.  1.  1.]]
>>> b = dot(A,x)      # lineer cebir çarpımı
>>> print(b)
[ 4.  4.  4.  4.]
Görüldüğü gibi ' operatörü bizim istediğimiz manada lineer cebir çarpımını değil vektörel formdaki çarpım işlevini yerini getirir. Bu yüzden Numpy'de matris-vektör çarpımı için dot fonksiyonu tanımlanmıştır. Benzer şekilde lineer cebrik sistemin çözümü için bölme işlemine yani x=b/A ifadesini hesaplayabilmemiz lazım. Bu işlem için yine direkt bölme operatörünü kullanmak yerine linalg alt kütüphanesi içerisindeki inv komutuyla matrisin tersini bulup b vektörüyle çarpabiliriz. 
>>> A = random.random((4,4))     # rastgele kare A matrisi
>>> print(A)
[[ 0.69769186  0.79776825  0.09984681  0.21271922]
 [ 0.05979394  0.72758335  0.0570817   0.59825989]
 [ 0.61698744  0.29374527  0.82004994  0.41084264]
 [ 0.02961924  0.86964469  0.63311203  0.82975882]]
>>> b = random.random(4)         # rastgele b vektörü
>>> print(b)
[ 0.20801453  0.81169016  0.84603751  0.74985329]<
>>>
>>> x = dot(linalg.inv(A), b)    # Ax=b çözümü olarak x=A^-1*b
>>> print(dot(A,x))              # bulduğumuz çözümün sağlamasını yapıyoruz (Ax=b) 
[ 0.20801453  0.81169016  0.84603751  0.74985329]
Lineer sistemin çözümünü yukarıdaki gibi tersini alıp çarpmayla uğraşmak yerine linalg.solve komutunu kullanarak kısa yoldan gerçekleştirebiliriz.
>>> x = linalg.solve(A,b)
>>> print(dot(A,x))       # yine sağlamasını yapıp A*x=b olduğunu kontrol ediyoruz
[ 0.20801453  0.81169016  0.84603751  0.74985329]
Üniversitede lineer cebir almış olanlar bilirler matrisin determinantı ve öz değer/vektörlerini kağı kalemle bulmak oldukça meşakkatlidir. Numpy'nin lineer cebir paketi sayesinde tek bir komutla bu işlemlerimizi basitçe halledebiliriz.
>>> A = diag([2,3,4], k=0)
>>> print(A)             # köşegen A matrisi
[[2 0 0]
 [0 3 0]
 [0 0 4]]
>>> print(linalg.det(A)) # determinantı
24.0
>>> l, v = linalg.eig(A) # eig komutu öz değer ve öz vektörden oluşan iki döndürür
>>> print(l)             # öz değerler
[ 2.  3.  4.]
>>> print(v)             # öz vektörler yazdırılan matrisin sütunlarıdır
[[ 1.  0.  0.]
 [ 0.  1.  0.]
 [ 0.  0.  1.]]
Oldukça geniş bir konu lineer cebrik sistemlere ilişkin yazımızı, Scipy kütüphanesinin konuları arasında daha fazla değinmek üzere şimdilik bitiriyoruz.

21 Ekim 2014 Salı

Django Purge Pagination

Django'da sayfalama için kullanabileceğimiz uygulama olan purge-pagination'dan bahsedeceğiz. Django'nun sayfalama yapısı bizim tam olarak işimizi görmediği için bu sayfalama uygulamasını kullanabiliriz. Genelde sayfa sayısının çok fazla olduğu yerlerde tüm sayfalar gösterilmektense baştan bir kaç sayfa bulunulan sayfa ve sondan bir kaç sayfa gösterilerek güzel bir görüntü oluşması sağlanır. Purge Pagination bu işi halletmemizi sağlıyor. Kurmak için
pip install django-pure-pagination
Uygulamayı kurduktan sonra projemize eklememiz gerekiyor. Bunun için;
INSTALLED_APPS = (
    ...
    'pure_pagination',
)
Uygulamayı yükledikten sonra settings.py içerisinde ayarlamalarını yapabiliriz.
PAGINATION_SETTINGS = {
    'PAGE_RANGE_DISPLAYED': 10,
    'MARGIN_PAGES_DISPLAYED': 2,
}
Buradaki PAGE_RANGE_DISPLAYED ayarı ortada kaç tane sayfa göstericeğini belirtiyor. MARGIN_PAGES_DISPLAYED ise başta ve sonra kaç sayfa olacağını gösteriyor. Yani sayfalama başta 2 sayfa...ortada 10 sayfa...sonda 2 sayfa şeklinde olacak.
Uygulamayı eklediğimize göre kullanabiliriz. Django'nun kendi Pagination yapısı ile kullanımı hemen hemen aynı. Django'da Pagination metodlarınışu şekilde yürklüyoruz.
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
Purge Pagination'da ise sadece uygulama değişiyor. Metodlar ve kullanımları aynı.
from pure_pagination import Paginator, EmptyPage, PageNotAnInteger
View içerisinde şu şekilde kullanabiliriz.
tum_yazilar = Yazilar.objects.filter(onay=1)
yazi_sayfalari = Paginator(tum_yazilar, 10, request=request) 
yazilar = yazi_sayfalari.page(page)
Son olarak ise template içerisinde sayfaları şu şekilde gösterebiliriz.
        {% if yazilar.has_previous or yazilar.has_next %}
            {% load i18n %}
            
    {% if yazilar.has_previous %}
  • {% else %}
  • ‹‹
  • {% endif %} {% for page in yazilar.pages %} {% if page %} {% ifequal page yazilar.number %}
  • {{ page }}
  • {% else %}
  • {{ page }}
  • {% endifequal %} {% else %}
  • ...
  • {% endif %} {% endfor %} {% if yazilar.has_next %}
  • {% else %}
  • ››
  • {% endif %}
{% endif %}
Artık sayfalamamız şuna benzer bir şekilde görünecektir. Sizin temanıza göre stili değişir. Fakat sayfa sayısı bu şekilde olacaktır. Burada resmi uygulamanın github hesabından aldık.
Kaynakça

6 Ekim 2014 Pazartesi

Numpy: Vektörleştirme ve Dilimleme


Bilimsel hesaplamalarda ekseriyetle büyük veri kümeleri üzerinden işlem yapılır. Klasik programlamada, bu türden hesaplamalar liste halindeki sayısal değerlerin döngü içerisinde birer birer çağrılmasıyla yapılır. Ancak günümüzde ise mümkün mertebe döngü kullanımından kaçınılmaktadır. Hatta büyük firmalar tarafından bu amaca yönelik optimize edilmiş program arayüzleri ve kütüphaneler ticari olarak geliştirilmektedir (Matlab, Intel MKL). Bu yazımızda da Python'un Numpy kütüphanesi sayesinde dizi halindeki sayısal veriler üzerinden nasıl hızlı ve basit işlemlerimizi gerçekleştirebileceğimizden bahsedeceğiz. 

Vektörleştirme

Girişte de bahsettiğimiz gibi bir matematiksel işlemi çoğunlukla dizi halindeki verinin tüm elemanları üzerinden gerçekleştirmek isteriz. Bu durumda işlemi for döngüsü içerisinde dizinin herbir elemanı için teker teker yapmak yerine Numpy kütüphanesi sayesinde tek değer üzerinden işlem yapıyormuş gibi gerçekleştirebiliriz. Şimdi aşağıdaki örnekte, polinom ve trigonometri ifadeleri içeren bir hesaplamayı hem döngü ile hem de vektörel formda yapıp karşılaştıralım.
>>> import time as tm   # süre tutma için gerekli olan modül
>>> import numpy as np  # Numpy kütüphanesi 'np' ön adıyla ekliyoruz
>>>
>>> N = 10000               # dizinin eleman uzunluğu
>>> x = np.random.random(N) # argüman dizisi
>>> y = np.zeros_like(x)    # sonuç dizisi
>>> 
>>> t0 = tm.clock()         # kronometre başlangıcı
>>> 
>>> # döngü ile hesap
>>> for i in np.arange(N):
...     y[i] = x[i]**2 + 3*x[i] - x[i]/5 + np.sin(x[i])
>>> t1 = tm.clock()
>>> 
>>> # vektörel hesap
>>> y = x**2 + 3*x - x/5 + np.sin(x)
>>> t2 = tm.clock()
>>> 
>>> print("Döngü ile hesaplama: %5.2f ms" %((t1-t0)*1E3))
>>> print("Vektörel hesaplama: %5.2f ms" %((t2-t1)*1E3))
Döngü ile hesaplama: 106.15 ms
Vektörel hesaplama:  0.67 ms
Görüldüğü gibi vektörel hesaplamayla basit bir hesaplamada bile en az 100 kat daha hızlı işlem yapılabilmektedir. Ayrıca kod metnimiz daha sade ve anlaşılır hale gelmiştir.
Numpy kendi dizi yapıları için temel aritmetik işlemleri (+,-,x,/) ve standart math/cmath kütüphanelerindeki fonksiyonları vektörel formda sağlamaktadır. Eğer harici fonksiyonlar da aynı verimlilikle hesap yapabilmek için vektörleştirmek istenirse bu, vectorize fonksiyonu kullanarak sağlanabilir. 
>>> from math import sqrt           # standart kütüphanedeki kök alma fonksiyonu
>>> def afunc(x):
...    if x<0:
...        return sqrt(-x) + x**2
...    else:
...        return sqrt(x) + x**2
>>> 
>>> afunc_v = np.vectorize(afunc)   # fonksiyonu vektörel hale getiriyoruz
>>> 
>>> N = 10000                       # dizinin eleman uzunluğu
>>> x = np.random.uniform(-1,+1,N)  # argüman dizisi
>>> y = np.zeros_like(x)            # sonuç dizisi
>>> 
>>> t0 = tm.clock()                 # kronometre başlangıcı
>>> 
>>> # döngü ile hesap
>>> for i in np.arange(N):
...    y[i] = afunc(x[i])
>>> t1 = tm.clock()
>>> 
>>> # vektörel hesap
>>> y = afunc_v(x)
>>> t2 = tm.clock()
>>> 
>>> print("Döngü ile hesaplama: %5.2f ms" %((t1-t0)*1E3))
>>> print("Vektörel hesaplama: %5.2f ms" %((t2-t1)*1E3))
Döngü ile hesaplama: 45.30 ms
Vektörel hesaplama:  10.99 ms
Eğer harici fonksiyonumuz sadece temel aritmetik işleçler ve standart kütüphanedeki fonksiyonları içeriyorsa yukarıdaki gibi vektörleştirmeye gerek kalmadan fonksiyonun direk kendisiyle çalışılabilir.

Dilimleme
Önceki konu başlığında tüm dizi elemanları üzerinden nasıl hızlı işlem yapabileceğimizden bahsettik. Ancak bazı durumlarda dizinin tamamıyla değil de belli kısımlarıyla işlem yapmak isteyebiliriz. Mesela; dizinin yarıdan sonrası, çift indisli elemanları gibi... Dilimleme operatörü de işte dizilerdeki elemanlara bu açıdan erişmemizi sağlar. Kullanılışı köşeli parantez içerisindeki iki nokta üst üste işaretleriyle sağlanır [ilk:son:adım]. Parantez içerisindeki tüm değerler tam sayı olmalıdır. Python'daki standart indisleme kuralları geçerlidir. Yani; son indisteki değer aralığa dahil değildir ve adım aralığı geriye doğru ilerlemek için negatif değer alabilir. Aşağıdaki kısa örneklerle kullanışını açıklamaya çalışalım.
>>> N = 10
>>> dizi = np.arange(N, dtype='int')
>>> print(dizi[::])         # dizinin tüm elemanları
[0 1 2 3 4 5 6 7 8 9]
>>> print(dizi[0:N/2:2])    # dizinin yarısına kadar 2'şer aralıklarla
[0 2 4]
>>> print(dizi[::-1])       # tersten geriye doğru birer adımlama
[9 8 7 6 5 4 3 2 1 0]
Dilimleme tekniği özellikle iteratif işlem gerektiren algoritmaların programlanmasında çok önemlidir. Şimdi bize nasıl bir kolaylık sağladığını iyice görmek açısından gerçekçi bir örnek yapalım. Bilindiği gibi analitik türevi mümkün olmayan fonksiyonların ilgili aralıktaki örnek değerleri üzerinden sayısal türevi sonlu farklar yöntemiyle belli doğrulukta hesaplanabilmektedir. Algortimayla alakalı detaylı bilgiyi http://tr.wikipedia.org/wiki/Sonlu_fark referansına bırakıp nasıl kodlandığına bakalım. Karşılaştırmayı mümkün kılmak için analitik türevini bildiğimiz sin fonksiyonunun türevini sayısal olarak üç farklı sonlu fark formülüyle aşağıdaki gibi hesaplıyoruz.
>>> # adım aralığı bilgisini de alacak şekilde eş aralıklı örnekleme
>>> N = 10  # örnekleme sayısı
>>> x, dx = np.linspace(0,+np.pi,N,retstep=True) # [0,+pi]
>>> 
>>> # test fonksiyonu ve analitik türevi
>>> y = np.sin(x)
>>> yp_analitik = np.cos(x)
>>> 
>>> # sonlu farklar
>>> yp_merkezi = ( y[2:N]-y[0:N-2] )/(2*dx) # merkezi fark
>>> yp_ileri = ( y[1:N]-y[0:N-1] )/dx       # ileri fark
>>> yp_geri = ( y[1:N]-y[0:N-1] )/dx        # geri fark
Görüldüğü gibi her bir sonlu fark hesabını dilimleme operatörü sayesinde birer satırda gerçekleştirebildik. Not olarak belirtmek gerekir ki, ileri ve geri fark ifadeleri birbirine eşit görünmekle birlikte açı dizisinde (x) karşılık geldiği noktalar farklıdır.
Yazımızı, bulduğumuz sonuçları görselleştirip, çıktılarını inceleyerek bitirelim. Aşağıdaki kodda Matplotlib kütüphanesi kullanılarak analitik türevle birlikte sayısal türevler tek bir grafik üzerinde gösterilmek istenmiştir. İleri ve geri fark sonuçları ise birbirleriyle benzer hata oranıyla analitik değere iyi bir biçimde yakınsamaktadır. Ancak beklediğimiz gibi, merkezi fark yöntemiyle hesaplanan türev diğer ikisinden daha az yanılmayla sonuca yaklaşmıştır. Örnekleme sayısını arttıracak olsaydık sayısal türev değerleri giderek daha fazla analitik değere benzeyecek, grafik eğrileri üzerinde ayırt edilemez hale gelecekti.
>>> import matplotlib.pyplot as plt # matplotlib kütüphanesini çizdiricisi
>>> 
>>> fig = plt.figure()              # yeni grafik ekranı
>>> 
>>> plt.plot(x,yp_analitik, color='blue', label='analitik')
>>> plt.plot(x[1:N-1],yp_merkezi, color='red', label='merkezi')
>>> plt.plot(x[0:N-1],yp_ileri, color='yellow', label='ileri')
>>> plt.plot(x[1:N],yp_geri, color='green', label='geri')
>>> plt.legend(loc='upper right')
>>> plt.title('Sinüs Fonksiyonunun Türevi')
>>> plt.xlabel('x')
>>> plt.ylabel('y\'(x)')
>>> plt.grid()
>>> plt.xlim(0,+np.pi)
>>> plt.ylim(-1,1)
>>> plt.show()

Kaynaklar:
[1] Langtangen H. P., Python Scripting for Computational Science, Springer, 2007