Tuesday, August 14, 2018

virtualenv, Python İzole, Sanal Çalışma Alanı (Python Virtual Environment)

Farklı projelerde çalışırken kurulması gereken farklı paketler olabilir, bu paketlerden bazıları diğerleri ile tam uyumlu olmayabilir, ya da bazı projelerde Python 2 diğerinde Python 3 kullanmak gerekebilir. Bu tür ihtiyaçlar için virtualenv ya da Anaconda kullanmak mümkün, Anaconda daha önce islendi, virtualenv'e göre daha iddialı bir sistem denebilir. Anaconda hem sanal ortam, hem de pip yerine geçmek isteyen bir paket idare sistemiyle de geliyor (Anaconda'da paket kurmak için pip install yerine conda install denir mesela).

Biz daha az iddialı ama bir o kadar faydalı, sadece sanal, izole ortam yaratan virtualenv'i anlatalım.

Ubuntu'da mesela python kurduğumuzda, bu sudo apt-get ile olabilir, komut satırında çağırdığımız python global, makinanın tümü için geçerli bir çağrıdır. Ardından pip install ile paket kurduğumuzda bu global python için yine global paket kurulumu yapmış oluruz. Kurulan paketler /usr/local/lib/python2.7/dist-packages gibi yerlere kurulur. İşte çakışma, potansiyel problemler burada ortaya çıkar.

Sanal ortam ile belli izole dizinler altında, ayrı python'u ayrı paketleri olan sanal ortamlar yaratabiliriz. Mesela bir Python 2 ortamı yaratalım,

virtualenv -p /usr/bin/python2 py2a

Sonuc

Running virtualenv with interpreter /usr/bin/python2
New python executable in /home/burak/py2a/bin/python2
Also creating executable in /home/burak/py2a/bin/python
Installing setuptools, pip, wheel...done.

Simdi ls ile dizine bakalım, bir py2a alt dizini göreceğiz. Simdi

source py2a/bin/activate

işletelim. Böylece biraz önce tanımladığımız sanal ortama girmiş oluyoruz. Bu komut, komut satır işaretini değiştirecek ve içinde olduğumuz sanal ortam ismi orada gözükecek, 

(py2a) burak@burak-Aspire-R3-131T:~$

oldu. Burada which python isletirsek

/home/burak/py2a/bin/python

görürüz, yani py2a altındaki python kullanılıyor. Gerçi bu bir sembolik bağlantı, ve başta verdiğimiz python'a bağlanıyor, fakat diğer her tür irdeleme için bu Python o sanal ortamda yerel kabul edilebilir. Burada artık pip install işletince Python paketleri sanal ortam içinde kurulacak. "Dışarıdaki" global python bunları göremeyecek. Örnek için bir pip install ardından py2a/lib/python2.7/site-packages altına bakılabilir. 

Sanal ortamları yerel dizinler üzerinden idare etmenin güzel tarafı 1) Güvenlik - artık paketler, kurulumu, herşey yerel bir dizin üzerinden idare edilebilir 2) Kolaylık - eğer bir ortamı silmek istiyorsak onun sanal dizinini sildiğimiz anda bu ortam silinmiş olur. 

Çıkmak için deactivate işletilir ve normal komut satırına geri dönülür.

Dikkat: source ile izole ortama girmek sadece pip ile kurulum yapma işlemleri için gerekli. Sadece script işletmek için mesela py2a/bin/python script.py çağrısı yeterli. Bu çağrı py2a altındaki python'u ve onun için kurulmus paketleri kullanacaktır.

Python 3

Ubuntu'da artık Python 3 yorumlayıcısı ayrı bir python3 komutu ile geliyor, apt-get ile python3 kurmak mumkun. Bu komut /usr/bin/python3 dosyasında; istersek onun üzerinden ayrı bir sanal ortam da yaratabilirdik, 

virtualenv -p /usr/bin/python3 py3a

ve

source py3a/bin/activate

ile bu yeni ortama gireriz. Burada python --version deyince

Python 3.5.2

görülecektir.

Not: Ubuntu Python 3 için mesela pip3 sağlamış, apt-get ile kurulan paketleri de python3-xxx diye kurulabiliyor. Bir bakıma Ubuntu da bir kolaylık sağlayarak Python 2 ile 3'ü ayrı isimlendirme üzerinden birbirinden ayırmış, fakat nihai izolasyon için hala virtualenv lazım olacaktır.

Spesifik Python Versiyonları

Diyelim ki çok spesifik bir Python versiyonu gerekiyor, bizde 2.7.1 var, ama illa ki 2.7.7 lazım. Olur mu? Olur. Öyle bir paket vardır ki belli bir Python versiyonunda desteklenmektedir sadece, vs. Bu gibi durumlarda Python'u kaynaklarından derlemek gerekebilir, ve sonra virtualenv ile sanal ortam yaratılırken o derlenen Python kullanılır. Derlemek için 

wget http://www.python.org/ftp/python/2.7.7/Python-2.7.7.tgz
tar zxvf Python-2.7.7.tgz 
cd Python-2.7.7 
./configure --prefix=$HOME/opt/python-2.7.7
make
make install

Artık 

virtualenv -p /home/burak/opt/python-2.7.7/bin/python py2b

diyebiliriz. 

source py2b/bin/activate

ile ortama girince python --version çağrısının

Python 2.7.7

Tuesday, August 7, 2018

Paralel Python - Dask

TensorFlow hesap ağı / çiziti üzerinden paralelize etmeyi popüler hale getirdi. Pür Python seviyesinde çalışan benzer kavramları kullanan bir paket Dask.

Dask kutuphanesi Pandas, Numpy paketlerindeki DataFrame, matris işlemlerine benzer arayüzler sağliyor, ve bu veri yapıları üzerindeki işlemleri basit şekilde paralel işletilmesini sağlıyor. Fakat daha basit bağlamda pür Python işlemlerini de paralelleştirmek kolay. İlk önce tek makinadaki birden fazla çekirdeğe dağılım yapabilecek kodu görelim, bu en temel kullanım. Ama yine de faydalı, artık Amazon AWS, ya da Google bulut servislerinde ucuz şekilde 20-30 çekirdekli tek makina başlatmak mümkün. Bu çok zaman alabilecek doğal parallelliği olan bir işlemi 30 kat hızlı işletebiliriz demektir!

Kurmak icin pip install dask.

Şöyle bir kod düşünelim,

from time import sleep

def inc(x):
    sleep(1)
    return x + 1

def add(x, y):
    sleep(1)
    return x + y

%time x = inc(1)
%time y = inc(2)
%time z = add(x, y)

CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 1 s
CPU times: user 4 ms, sys: 0 ns, total: 4 ms
Wall time: 1 s
CPU times: user 4 ms, sys: 0 ns, total: 4 ms
Wall time: 1 s

%time komutu jupyter not defteri içinde çalışıyor, kendi başına işleyen kodda bunu çıkartabiliriz.

Üstteki toplam 3 saniye alan bir işlem, çünkü tüm çağrılar seri şekilde yapılmak zorunda. Bir sonraki bir önceki çağrıyı beklemeli. Fakat delayed komutu ile bir hesap çiziti oluşturabiliriz, bu çizitteki düğümler paralel şekilde işletilebilirler,

from dask import delayed

%time x = delayed(inc)(1)
%time y = delayed(inc)(2)
%time z = delayed(add)(x, y)
%time print z.compute()

CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 382 us
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 411 us
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 342 us
5
CPU times: user 12 ms, sys: 0 ns, total: 12 ms
Wall time: 2.01 s

Bu işlem 2 saniye sürdü. Çünkü çağrılar paralel işledi. x,inc zincirinin y,inc zincirini beklemesine gerek yok.

Şimdi paralel liste işlemeyi görelim. Bir listedeki tüm öğelerin karekökunu alıp ikinci bir liste üretme islemi yapalim,

import time
L = range(30)
def f(x):
    time.sleep(0.1)
    return np.sqrt(x)
results = []
%time for x in L: results.append(f(x))

CPU times: user 4 ms, sys: 0 ns, total: 4 ms
Wall time: 3.02 s

Bu kod seri işledi ve 3 saniye aldı. Parallellik için listedeki her öge için yapılan karekök (sqrt) çağrısını delayed hale getirmek lazım,

import time
L = range(30)
def f(x):
    time.sleep(0.1)
    return np.sqrt(x)
results = []
for x in L:
    y = dask.delayed(f)(x)
    results.append(y)

%time results = dask.compute(*results)  # call compute after you have collected many delayed calls

CPU times: user 32 ms, sys: 16 ms, total: 48 ms
Wall time: 1.54 s

Bizim dizüstü bilgisayarda 2 çekirdek var, hesap iki kat daha hızlı işledi. 30 tane olsa 30 kat daha hızlı işlerdi!

Sonuç listesini üretirken olanlara dikkat: karekök hesabını değil o hesabı yapan çağrıyı listeye ekledik, ve bu çağrı delayed ile paralelize edilmiş çağrıydı. Gerçek hesap dask.compute çağrısı o liste üzerinde işletilince tetikleniyor, ve o sırada Dask arka planda elde olan çekirdeklere gerekli dağıtımı yapıyor.

Numpy işlemlerinin Dask karşılığından bahsettik, mesela normal Numpy ile

import numpy as np
# normal mean
%time print np.random.randn(10000,10000).mean()

Dask ile

import dask.array as da

# dask mean
x = da.random.normal(0, 1, size=(10000,10000), chunks=(1000, 1000))
%time print x.mean().compute()

Bu çağrı da iki kat daha hızlı işledi. Burada yapılan bölme işlemi chunks anahtar kelimesi üzerinden; Dask'e işlenecek her parçanın ne kadar büyük olacağını söyledik, ona göre parçalar yaratılıp hesap buna göre planlandı. Döngü örneğinde parçaları elle biz yaratmıştık, burada sadece büyüklük tanımlayıp parçaları Dask'e bıraktık.

http://dask.pydata.org/en/latest/delayed-best-practices.html

https://github.com/dask/dask-tutorial

https://gist.github.com/mrocklin/d009e5d4a1f49ecdb433107f3d72c7f3#file-pygotham-dataframes-ipynb

Friday, March 30, 2018

Basit, Hızlı Diske Değer Yazma Okuma

Bir Python uygulaması için  basit, hızlı bir şekilde diske yazılabilen, ve istendiği zaman anahtar bazlı, tüm dosyayı hafızaya getirmeden hızlı şekilde okunabilen bir yapıya ihtiyaç vardı. Çözüm için önce Python'un sözlük (dictionary) yapısının diske yazılmasına izin veren çözümler akla geliyor, ki muhakkak o çerçevede pek çok çözüm var. Fakat bir çözüm var ki Python kurulumunun zaten içinde, tek bir dosyaya yönlendirilebilen depolaması mevcut, ve her yazımın diske gitmesini, ve hızlı okunabilmesini sağlıyor.

Bu yazılım sqlite3'ten başkası değil. Eğer az SQL yazmayı göze alırsak, ki bu dili tüm bilişimciler bilir, sqlite3 üstteki tüm ihtiyaçlara cevap verir. Test edelim,

import sqlite3
db = sqlite3.connect('/tmp/mydb.db')
import random

cursor = db.cursor()
cursor.execute('''CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT,
                  phone TEXT, email TEXT, password TEXT)
       ''')
db.commit()

cursor = db.cursor()
name1 = 'Andres'
phone1 = '3366858' 
email1 = 'user@example.com' + str(random.random())
password1 = '12345'
print (email1)
cursor.execute('''INSERT INTO users(name, phone, email, password)
                  VALUES(?,?,?,?)''', (name1,phone1, email1, password1))
db.commit()

Taban tek bir dosya, hangisi olacağını biz tanımladık, gayet basit.  Üstteki insert komutunu ardı ardına işletelim ve arada sırada /tmp/mydb.db dosyasına bakalım. Bu dosya içinde yaratılan farklı isimleri göreceğiz. Dosya büyüklüğünü kontrol etmek ise yaramayabilir çünkü sqlite taban dosyasını her yazimda degil, belli aralıklarla büyütüyor. 

Arada satır sayısını alttaki ile kontrol edebiliriz,

cursor = db.cursor()
cursor.execute('''SELECT count(*) FROM users''')
print (cursor.fetchone())

Taban iş bitince db.close ile kapatılır. Fakat düzgün kapatılmasa bile commit ile yazılanlar kaybolmayacaktır, yani süreç çökse, tekrar tabana dönsek en son yazdığımız satırı orada bulacağız. 

Bu tüm ihtiyaçları karşılıyor, farklı bir süreçte işleyen büyük bir taban değil, kurulmasına bile gerek yok zaten Python sürümünde var, ayrıca diske yazım var, hızlı okuma (herşeyi hafızaya getirmeden) destekleniyor. 

Monday, March 26, 2018

Veri Taban İçeriğini Gezmek

Eğer veri tabanı, içindeki tabloları, bu tablolardaki bazı verileri görmek istersek, GUİ araçları yerine bir jupyter not defteri üzerinden kod işletmeyi seçebiliriz. Taban erişimi için psycopg2 iş görür. Bağlantı aldıktan sonra (Postgresql olsun),

import psycopg2

conn = psycopg2.connect(
    host='[makina]',
    database='[taban]',
    user='[kullanici]',
    password='[sifre]',
    port=port
)

şu metotlarla taban içeriği gösterilebilir

import psycopg2

sql = "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'"

def get_tables(con):
    """
    Tum tablolari goster
    """    
    cursor = con.cursor()
    res = []
    cursor.execute(sql)
    for table in cursor.fetchall():
        res.append(table)
    return res

def get_table_col_names(con, table_str):
    """
    Tablo kolon isimlerini al
    """
    col_names = []
    try:
        cur = con.cursor()
        cur.execute("select * from " + table_str + " LIMIT 0")
        for desc in cur.description:
            col_names.append(desc[0])        
        cur.close()
    except psycopg2.Error as e:
        print (e)
    return col_names

def get_sample(con, table_str):
    """
    Tablodan rasgele satirlar goster
    """
    res = []
    try:        
        cur = con.cursor()
        cur.execute("select * from " + table_str + " ORDER BY RANDOM() LIMIT 10")
        for x in cur: res.append(x)
        cur.close()
    except psycopg2.Error as e:
        print (e)
    return res

def get_count(con, table_str):
    """
    Tabloda kac satir var
    """    
    res = []
    try:        
        cur = con.cursor()
        cur.execute("select count(*) from " + table_str)
        res = cur.fetchone()
        cur.close()
    except psycopg2.Error as e:
        print (e)
    return res

Jupyter not defterinde bildiğimiz gibi etkileşimli (interactive) bir şekilde kod yazılabiliyor, bir hücrede yazılan kodu hemen işletip doküman içinde görüyoruz.. Eh böyle olunca üstteki çağrıları istedigimiz tablo üzerinde yapınca sanki GUİ araçından taban içeriğini gezmis oluyoruz.

Jupyter kurmak istemeyenler, direk markdown md dosyaları içinden üstteki işlemleri yapmak isteyenler (ve Emacs kullanıcıları) bizim eklentiyi kullanabilir.

Thursday, February 22, 2018

Elde Olmayan Eğitim Verisi

Kaggle sitesinde pek çok veri bilimcinin bildiği üzere veri bilim yarışmaları düzenleniyor. Mesela geçende bir tanesi basit ses komutlarını tanımak (Tensorflow kullanarak) hakkındaydı.

https://www.kaggle.com/c/tensorflow-speech-recognition-challenge

Eğitim verisi sağlanmış, yani etiketleriyle beraber ses verisi (mesela bir wav ses dosyası ve onun hangi komut olduğu, "yes", "no" gibi) bir de test verisi var, bu veri için etiket verilmemiş, sadece ses kaydı. Yarışmacı eğitim verisi üzerinde eğitim yapacak test verisi üzerinde bu modeli kullanıp etiket üretecek, ve Kaggle sitesine kontrol için verecek - sistem hemen başarıyı ölçüyor, kısa sürede cevap sağlıyor.

Yarışmacılar eğitimi yaparken tabii ki eğitim verisini alıp onun içinden kendileri bir test verisi yaratıyorlar, ki skorun gerçekten ne olabileceğini görebilsinler, model "ezberliyorsa" onu hemen anlayabilsinler, bu standart bir teknik. Fakat bazı yarışmacılar farkediyor ki eğitim / kendi test verilerinde elde edilen başarı Kaggle'dan gelen cevabın %10-15 gerisinde! Bu demektir ki Kaggle'ın sağladığı test verisi eğitim verisinden çok farklı. Yarışmacıdan beklenen ses verisini değiştirip, gürültü ekleyip, kaydırıp vs yeni eğitim verisi "üretmesi" ve böylece başarısını arttırması.

Bu durumun gerçek dünya şartlarını pek yansıtmadığını söylemek gerekir. Genelde modeller eldeki veri neyse onun üzerinde eğitilir, yani "gelecekte ne olabilir" türündeki spekülasyona girilmez. Eğer girilebiliyorsa o veri de elde vardır demektir ve eğitim verisi o'dur. Kaggle'ın yaptığı resimden kedi tanıyan bir yarışmada test verisinde üç, dört kulaklı kedilere hazır olunmasını istemek gibi.

Fakat gerçek dünyada şu olur: kullanılmış dış dünyadan gelen / gelmis veri değişmeye başlayabilir, bu duruma hazır olunması gerekir ki böylece bu yeni veriyle tekrar eğitim yapılsın, modeller güncellensin. O değişimi yakalamak için rutinler yazılabilir, vs. Fakat bu durum spekülatif şekilde elde olmayan veriye göre eğitmekten farklı.