Thursday, January 19, 2017

Derin Öğrenim ile Text Üretmek, RNN, LSTM

Derin Öğrenim her yerde; yapay sinir ağları sağlam bir dönüş yaptılar, ve ardı ardına ilerlemeler, ve ilginç kullanımlar haberleri görülebiliyor. Bunlardan biri A. Karpathy'nin Kendini Tekrarlayan Yapay Sinir Ağları (Recurrent Neural Network, -RNN-) hakkında yazdıkları. Arkadaş Shakespeare'in uzun bir oyununu RNN'e okutup sonra bu ağdan Shakespeare ürettirmiş. Sonuçlar fena değil.

Öteki yandan alttaki Y. Goldberg adlı birisi sadece önceki N karakterden sonra her karakterin kaç kere geldiğini "sayarak" ve frekansları kullanarak metin üretmiş. Burada derin öğrenim filan yok, sadece basit sayım var. Sonuçlar onda da iyi. Ama nasıl oldu bu iş? Derin YSA hiçbir iş yapmıyor mu yani? Fakat Goldberg'in dediği gibi aslında RNN, LSTM gibi derin modelleri daha farklı bir şey yapıyorlar. Shakespeare örneğinde her iki yaklaşım da Shakespeare'imsi bir şeyler üretiyor, fakat RNN, LSTM yaklaşımında bazı gramer yapıları öğrenilmeye başlanıyor.

Mesela parantez açıp bir blok sonra parantez kapamak frekans modelleri için zor, fakat DO bunu başarıyor. Goldberg "N sayısını arttırıp 10,20 seviyesine getirsem belki bunları frekans modeli de yapardı ama hafızam yetmezdi" diye bir yorum da yapmış, ve bu nokta DO / YSA'nın önemli bir avantajını yakalamış. Frekans modeli mesela 9 harf geriye bakıp 10. harfi tahmin için, mumkun tum kombinasyonlar icin 20 harflik alfabede 20**10 öğeli bir sözlük / listeye sahip olmalıdır, bu 10240 milyar öğe demektir. O tüm kombinasyonlar tabii ki girdi verisi içinde olmayabilir, fakat az bir kısmı olsa bile bu müthiş büyük bir sayıdır. Halbuki derin öğrenim, her ne kadar modeli derinleştirse de, her seviye sadece bir sonraki seviyeyle iletişimde olduğu için yer israf etmiyor. Üstelik bu katmanlı yaklaşımı ile her katmanın belli alanlara odaklaşmasını teşvik ediyor, böylece model genelleştirebiliyor. Frekans modeli ezberci, derin öğrenim böyle değil.

Su yazıyı baz alarak mevcut olan bir DO'yu (LSTM) yükleyip biz de metin üretme yaptırdık, kurulum için

sudo pip install tensorflow
sudo pip install h5py

Kullanılan DO programı Keras. Kaynak indirilip python setup.py build ve install ile kurulabilir.

Model

http://www.cs.virginia.edu/~vicente/recognition/notebooks/weights-vicente.hdf5

Alttaki kodu arka arkaya isletince

a black cat is drinking into a bowl on the side of a road
a bike sitting next to a stick is taking his lapkay
a person is standing on a restaurant

gibi cümleler üretiliyor. Kelimelerin çoğunun mantıklı olması bir yana, gramer yapısı da fena değil.

Kod

import tensorflow as tf
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation, LSTM
from keras.optimizers import RMSprop
from keras.layers.wrappers import TimeDistributed

# Read captions into a python list.
maxSamples = 10000
captions = []
fopen = open('captions_train.txt', 'r')
iterator = 0
for line in fopen:
    if iterator < maxSamples:
        captions.append(line.lower().strip())
        iterator += 1
fopen.close()
    
# Compute a char2id and id2char vocabulary.
char2id = {}
id2char = {}
charIndex = 0
for caption in captions: 
    for char in caption:
        if char not in char2id:
            char2id[char] = charIndex
            id2char[charIndex] = char
            charIndex += 1

# Add a special starting and ending character to the dictionary.
char2id['S'] = charIndex; id2char[charIndex] = 'S'  # Special sentence start character.
char2id['E'] = charIndex + 1; id2char[charIndex + 1] = 'E'  # Special sentence ending character.
            
# Place captions inside tensors.
maxSequenceLength = 1 + max([len(x) for x in captions])
# inputChars has one-hot encodings for every character, for every caption.
inputChars = np.zeros((len(captions), maxSequenceLength, len(char2id)), dtype=np.bool)
# nextChars has one-hot encodings for every character for every caption (shifted by one).
nextChars = np.zeros((len(captions), maxSequenceLength, len(char2id)), dtype=np.bool)
for i in range(0, len(captions)):
    inputChars[i, 0, char2id['S']] = 1
    nextChars[i, 0, char2id[captions[i][0]]] = 1
    for j in range(1, maxSequenceLength):
        if j < len(captions[i]) + 1:
            inputChars[i, j, char2id[captions[i][j - 1]]] = 1
            if j < len(captions[i]):
                nextChars[i, j, char2id[captions[i][j]]] = 1
            else:
                nextChars[i, j, char2id['E']] = 1
        else:
            inputChars[i, j, char2id['E']] = 1
            nextChars[i, j, char2id['E']] = 1

print("input:")
print(inputChars.shape)  # Print the size of the inputCharacters tensor.
print("output:")
print(nextChars.shape)  # Print the size of the nextCharacters tensor.
print("char2id:")
print(char2id)  # Print the character to ids mapping.

trainCaption = inputChars[25, :, :]  # Pick some caption
labelCaption = nextChars[25, :, :]  # Pick what we are trying to predict.

def printCaption(sampleCaption):
    charIds = np.zeros(sampleCaption.shape[0])
    for (idx, elem) in enumerate(sampleCaption):
        charIds[idx] = np.nonzero(elem)[0].squeeze()
    print(np.array([id2char[x] for x in charIds]))

printCaption(trainCaption)
printCaption(labelCaption)

print('Building training model...')
hiddenStateSize = 128
hiddenLayerSize = 128
model = Sequential()
# The output of the LSTM layer are the hidden states of the LSTM for every time step. 
model.add(LSTM(hiddenStateSize, return_sequences = True, input_shape=(maxSequenceLength, len(char2id))))
# Two things to notice here:
# 1. The Dense Layer is equivalent to nn.Linear(hiddenStateSize, hiddenLayerSize) in Torch.
#    In Keras, we often do not need to specify the input size of the layer because it gets inferred for us.
# 2. TimeDistributed applies the linear transformation from the Dense layer to every time step
#    of the output of the sequence produced by the LSTM.
model.add(TimeDistributed(Dense(hiddenLayerSize)))
model.add(TimeDistributed(Activation('relu'))) 
model.add(TimeDistributed(Dense(len(char2id))))  # Add another dense layer with the desired output size.
model.add(TimeDistributed(Activation('softmax')))
# We also specify here the optimization we will use, in this case we use RMSprop with learning rate 0.001.
# RMSprop is commonly used for RNNs instead of regular SGD.
# See this blog for info on RMSprop (http://sebastianruder.com/optimizing-gradient-descent/index.html#rmsprop)
# categorical_crossentropy is the same loss used for classification problems using softmax. (nn.ClassNLLCriterion)
model.compile(loss='categorical_crossentropy', optimizer = RMSprop(lr=0.001))

print(model.summary()) # Convenient function to see details about the network model.

# Test a simple prediction on a batch for this model.
print("Sample input Batch size:"),
print(inputChars[0:32, :, :].shape)
print("Sample input Batch labels (nextChars):"),
print(nextChars[0:32, :, :].shape)
outputs = model.predict(inputChars[0:32, :, :])
print("Output Sequence size:"),
print(outputs.shape)

inference_model = Sequential()
# Two differences here.
# 1. The inference model only takes one sample in the batch, and it always has sequence length 1.
# 2. The inference model is stateful, meaning it inputs the output hidden state ("its history state")
#    to the next batch input.
inference_model.add(LSTM(hiddenStateSize, batch_input_shape=(1, 1, len(char2id)), stateful = True))
# Since the above LSTM does not output sequences, we don't need TimeDistributed anymore.
inference_model.add(Dense(hiddenLayerSize))
inference_model.add(Activation('relu'))
inference_model.add(Dense(len(char2id)))
inference_model.add(Activation('softmax'))

inference_model.load_weights("weights-vicente.hdf5")

# Given the start Character 'S' (one-hot encoded), predict the next most likely character.
startChar = np.zeros((1, 1, len(char2id)))
startChar[0, 0, char2id['S']] = 1
nextCharProbabilities = inference_model.predict(startChar)

# print the most probable character that goes next.
print(id2char[nextCharProbabilities.argmax()])

inference_model.reset_states()  # This makes sure the initial hidden state is cleared every time.

startChar = np.zeros((1, 1, len(char2id)))
startChar[0, 0, char2id['S']] = 1

res = []
for i in range(0, 100):
    nextCharProbs = inference_model.predict(startChar)
    
    # In theory I should be able to input nextCharProbs to np.random.multinomial.
    nextCharProbs = np.asarray(nextCharProbs).astype('float64') # Weird type cast issues if not doing this.
    nextCharProbs = nextCharProbs / nextCharProbs.sum()  # Re-normalize for float64 to make exactly 1.0.
    
    nextCharId = np.random.multinomial(1, nextCharProbs.squeeze(), 1).argmax()
    res.append(id2char[nextCharId]) # The comma at the end avoids printing a return line character.
    startChar.fill(0)
    startChar[0, 0, nextCharId] = 1
print ''.join(res)

Wednesday, January 18, 2017

Friday, January 6, 2017

Tıklamaları Örnek Veriye Çevirmek

Bazen belli bir şekilde olan veri noktalarını kabaca tıklayarak üretmek gerekebilir, bu noktaları metin editör içinde yazmak zor olur, görsel yaklaşım tercih edilebilir. Bu işi yapacak basit bir Python programı altta

import pandas as pd
from matplotlib import pyplot as plt

class PointRec:
    def __init__(self, line, ax, fig):
        self.line = line
        self.ax = ax
        self.fig = fig
        self.xs = list(line.get_xdata())
        self.ys = list(line.get_ydata())
        self.cid = line.figure.canvas.mpl_connect('button_press_event', self)

    def __call__(self, event):
        if event.inaxes!=self.line.axes: return
        self.xs.append(event.xdata)
        self.ys.append(event.ydata)
        self.ax.plot(event.xdata, event.ydata,'rd')
        self.fig.canvas.draw()

fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_xlim(0, 20);ax.set_ylim(0, 20)
ax.set_title('click to record points, exit to save to out.csv')
line, = ax.plot([0], [0])  
rec = PointRec(line,ax,fig)

plt.show()

df = pd.DataFrame([rec.xs, rec.ys]).T
df.columns = ['x','y']
df.to_csv("out.csv",index=None)

Program tıklamaları hafızada biriktirerek tutar, ve program kapatıldığında veriyi out.csv dosyasına yazar. 



Wednesday, December 28, 2016

Python, Java, OSRM

OSRM harita servisi JSON ile bilgi gonderir; bu bilgiyi dekode etmek icin Python ve Java araclari var. Mesela bir yerden bir yere yol tarifi almak icin neler gerekir onlari gorelim.

Java

private static StringBuffer encodeSignedNumber(int num) {
    int sgn_num = num << 1;
    if (num < 0) {
        sgn_num = ~(sgn_num);
    }
    return(encodeNumber(sgn_num));
}

private static StringBuffer encodeNumber(int num) {
    StringBuffer encodeString = new StringBuffer();
    while (num >= 0x20) {
        int nextValue = (0x20 | (num & 0x1f)) + 63;
        encodeString.append((char)(nextValue));
        num >>= 5;
    }
    num += 63;
    encodeString.append((char)(num));
    return encodeString;
}

public static String encode(ArrayList<GeoPoint> polyline, int precision) {
    StringBuilder encodedPoints = new StringBuilder();
    int prev_lat = 0, prev_lng = 0;
    for (GeoPoint trackpoint:polyline) {
        int lat = trackpoint.getLatitudeE6() / precision;
        int lng = trackpoint.getLongitudeE6() / precision;
        encodedPoints.append(encodeSignedNumber(lat - prev_lat));
        encodedPoints.append(encodeSignedNumber(lng - prev_lng));
        prev_lat = lat;
        prev_lng = lng;
    }
    return encodedPoints.toString();

}

public static ArrayList<String []> readOsrmUrl(String urlString) {
    ArrayList<String []> res = new ArrayList<String []>();
    try {
        URL url = new URL(urlString);
        BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
        String line = "";
        while ((line = in.readLine()) != null) {
            String regex =
                "bearing_after.*?:(\\d+),.*?" +
                "\\[(-*\\d+\\.\\d+,-*\\d+\\.\\d+)\\].*?" +
                "distance.\\:(.*?),.*?" +
                "name.\\:(.*?)," ;
            Pattern r = Pattern.compile(regex);
            Matcher m = r.matcher(line);
            while (m.find( )) {
                String [] tmp = new String[4];
                tmp[0] = m.group(1);
                tmp[1] = m.group(2);
                tmp[2] = m.group(3);
                tmp[3] = m.group(4);
                res.add(tmp);
            }
        }

    } catch (IOException e) {
        e.printStackTrace();
    }
    return res;
}

public static ArrayList<String []> shortestPath(double lat1, double lon1, double lat2, double lon2) {

    GeoPoint p1 = new GeoPoint(lat1, lon1);
    GeoPoint p2 = new GeoPoint(lat2, lon2);
    ArrayList<GeoPoint> l = new ArrayList<GeoPoint>();
    l.add(p1);
    l.add(p2);
    String geo = encode(l, 10);
    String ourl = "http://router.project-osrm.org/route/v1/foot/polyline("+ geo +
        ")?overview=simplified&steps=true&alternatives=false&geometries=polyline";
    ArrayList<String []> r = readOsrmUrl(ourl);

    return r;
}

Bu kodun kullandigi yardimci fonksiyonlar alttaki baglantida bulunabilir.


Python

Once,

apt-get install binutils libproj-dev gdal-bin python-gdal

sudo pip install geopandas polyline Fiona 

Su alttaki GH adresindeki Python kodlari fena degil, fakat tek script icinden pat diye isletebilmek icin biraz basitlestirme gerekti. Bu kod direk localhost:5000 uzerinde isleyen servise baglanir ve bilgiyi alir.

https://github.com/ustroetz/python-osrm

Alttaki kod yol bulma (route)  icin yeterli

import numpy as np
from pandas import DataFrame
from urllib2 import urlopen
from polyline import encode as polyline_encode
from ogr import Geometry
from polyline.codec import PolylineCodec
import json

def _chain(*lists):
    for li in lists:
        for elem in li: yield elem

class DefaultRequestConfig:
    def __init__(self):
        self.host = "http://localhost:5000"
        self.profile = "driving"
        self.version = "v1"

    def __str__(self):
        return("/".join([self.host, '*', self.version, self.profile]))

    def __repr__(self):
        return("/".join([self.host, '*', self.version, self.profile]))

    @staticmethod
    def __call__(addr=None):
        if not addr:
            return DefaultRequestConfig()
        else:
            tmp = addr.split('/')
            cla = DefaultRequestConfig()
            cla.host = tmp[0]
            i = len(tmp)
            cla.version = tmp[i-2]
            cla.profile = tmp[i-1]
            return cla

RequestConfig = DefaultRequestConfig()

def check_host(host):
    """ Helper function to get the hostname in desired format """
    if not ('http' in host and '//' in host) and host[len(host)-1] == '/':
        return ''.join(['http://', host[:len(host)-1]])
    elif not ('http' in host and '//' in host):
        return ''.join(['http://', host])
    elif host[len(host)-1] == '/':
        return host[:len(host)-1]
    else:
        return host

def decode_geom(encoded_polyline):
    ma_ligne = Geometry(2)
    lineAddPts = ma_ligne.AddPoint_2D
    for coord in PolylineCodec().decode(encoded_polyline):
        lineAddPts(coord[1], coord[0])
    return ma_ligne

def simple_route(coord_origin_old, coord_dest_old, coord_intermediate=None,
                 alternatives=False, steps=False, output="full",
                 geometry='polyline', overview="simplified",
                 url_config=RequestConfig, send_as_polyline=True):
    if geometry.lower() not in ('wkt', 'well-known-text', 'text', 'polyline',
                                'wkb', 'well-known-binary', 'geojson'):
        raise ValueError("Invalid output format")
    else:
        geom_request = "geojson" if "geojson" in geometry.lower() \
            else "polyline"

    coord_origin = tuple(reversed(coord_origin_old))
    coord_dest = tuple(reversed(coord_dest_old))

    host = check_host(url_config.host)

    if not send_as_polyline:
        url = [host, "/route/", url_config.version, "/", url_config.profile,
               "/", "{},{}".format(coord_origin[0], coord_origin[1]), ';']

        if coord_intermediate:
            url.append(";".join(
                [','.join([str(i), str(j)]) for i, j in coord_intermediate]))

        url.extend([
            '{},{}'.format(coord_dest[0], coord_dest[1]),
            "?overview={}&steps={}&alternatives={}&geometries={}".format(
                 overview, str(steps).lower(),
                 str(alternatives).lower(), geom_request)
            ])
    else:
        coords = [
            pt[::-1] for pt in _chain(
                        [coord_origin],
                        coord_intermediate if coord_intermediate else [],
                        [coord_dest])
            ]
        url = [
            host, "/route/", url_config.version, "/", url_config.profile, "/",
            "polyline(", polyline_encode(coords), ")",
            "?overview={}&steps={}&alternatives={}&geometries={}".format(
                 overview, str(steps).lower(),
                 str(alternatives).lower(), geom_request)
            ]
    rep = urlopen(''.join(url))
    parsed_json = json.loads(rep.read().decode('utf-8'))

    if "Ok" in parsed_json['code']:
        if geometry in ("polyline", "geojson") and output == "full":
            return parsed_json
        elif geometry in ("polyline", "geojson") and output == "routes":
            return parsed_json["routes"]
        else:
            if geometry == "wkb":
                func = Geometry.ExportToWkb
            elif geometry == "wkt":
                func = Geometry.ExportToWkt

            for route in parsed_json["routes"]:
                route["geometry"] = func(decode_geom(route["geometry"]))

        return parsed_json if output == "full" else parsed_json["routes"]

    else:
        raise ValueError(
            'Error - OSRM status : {} \n Full json reponse : {}'.format(
                parsed_json['code'], parsed_json))

# test
result1 = simple_route((40.987659,29.036428), (40.992186,29.039228),
                       output="routes",
                       geometry="wkt",
                       send_as_polyline=True, steps=True)
print result1

Bu arada ciktilarda enlem, boylam degerleri yer degisikligine ugramis, OSRM her nedense boyle yapiyor, biz sadece girdide sirayi degistirdik.

Wednesday, December 14, 2016

SQLite, Android, JDBC, Python

Android cep telefonlarında SQL bazlı erişilebilen bir taban gerekirse SQLite programı bu ihtiyaçı karşılıyor. Bu taban çok hafif, hızlı çalışır, taban olarak tek ihtiyaçı bir .db dosyasından ibarettir. Dizüstü bilgisayarında da SQLite kullanılabiliyor, pek çok dilden erişim var, Python, Ruby, Java, vs.. Diğer bazı özellikler tablo indeksleyebilmek, birleşim (join), alt sorgular (subjoin) - kuvvetli bir program.

Bizim için en çok gereken özellik dışarıdan yaratılan (mobil için) büyükçe bir tabanı, 20 MB civarı, telefona kopyalayarak bu veriye anahtar bazlı hızlı erişim sağlamak. SQLite bunu rahatça sağladı. Niye koca bir JSON'u Android'den okuyup, mesela HashMap üzerinden, o şekilde direk erişim yapmadık? Çünkü o şekilde tek bir obje için 20 MB'in tamamını hafızaya yüklemek gerekir, SQLite, her diğer saygıdeğer ilişkisel tabanın yapacağı gibi, düzgün indekslendiği durumda disk'te sadece gerekli yere gider ve gereken satırı yükler, tabanının tamamını hafızaya yüklemeden.

Geliştirme ortamında veriyi Ubuntu'da Python ile yaratıyoruz. Dizüstü Ubuntu ortamında kurmak

sudo apt-get install sqlite3 libsqlite3-dev

Basit kod

import sqlite3
conn = sqlite3.connect('taban.db')

c.execute('''CREATE TABLE vs (key text primary key not null, ... )''')
c.execute('INSERT INTO vs values (...)')
...
conn.commit()
conn.close()

Eger taban.db yoksa yaratilir. 

Android 

Yaratılan tabanı SD kartına kopyalarsak Android SQLite'i ona rahatça erişiyor. 

import android.database.sqlite.*;
import android.database.*;
..
f = "/storage/emulated/0/Downloads/taban.db"
SQLiteDatabase db = SQLiteDatabase.openDatabase(f, null, SQLiteDatabase.NO_LOCALIZED_COLLATORS);
Cursor c = db.rawQuery("SELECT key FROM vs where ..", null);
c.moveToFirst();
Log.d("cam", "key"+c.getString(0));
..
c.close();
db.close();

Bu kod işler, burada problem yok.

Fakat üstteki API JDBC değil, yani standart bir arayüz değil. Eğer geliştirme ortamında birim testi yazmak istersek bu kod baş ağrısı yaratacaktır. Gerçi üstteki kullanımın taklitleme (mocking) üzerinden testi mümkün, ama bizce en iyisi Android üzerinde JDBC kullanmak. 

Şu arkadaşlar Android için bir JDBC kodu yazmışlar, 

https://github.com/SQLDroid/SQLDroid

Jar'i indirmek icin 

http://search.maven.org/#search%7Cga%7C1%7Csqldroid

Biraz onceki kodu

import java.sql.*;
..
try {
    Class clazz = Class.forName("org.sqldroid.SQLDroidDriver");
    DriverManager.registerDriver((Driver)clazz.newInstance());
} catch (Exception e) {
    e.printStackTrace();
}

String jdbcUrl = "jdbc:sqldroid:" + "/storage/emulated/0/Downloads/taban.db"
try {
    Connection conn = DriverManager.getConnection(jdbcUrl);
    Statement stmt = conn.createStatement();
    String sql = "SELECT key FROM vs where ..";
    ResultSet rs = stmt.executeQuery(sql);
    rs.next();
    Log.d("cam", "key"+rs.getString(1));
    rs.close();
    conn.close();         
} catch (SQLException e) {
    throw new RuntimeException(e);
}       

olarak değiştirebiliriz. Tabii SQLDroid jar'ını projemizin lib dizine koymayı unutmuyoruz.

Dikkat: Android DB API'si kolonlara 0 indis bazlı erişiyor, JDBC 1 indis bazlı erişiyor.

Birim testler şimdi daha kolaylaştı; Android kodundaki veri erişimini belli metotlara koyarız, bu metotlar dışarıdan bir taban bağlantısı Connection alırlar; Android ortamından bu objenin gerçekleştirimi / gerçek hali SQLDroıdConnection olur, geliştirme ortamında org.sqlite.JDBC olur.. ama test edilen metot bunlardan habersiz bir şekilde ona verilen Connection ile güzelce çalışır.

Dizustu icin JDBC

try {
    Class clazz = Class.forName("org.sqlite.JDBC");
    DriverManager.registerDriver((Driver)clazz.newInstance());
    String jdbcUrl = "jdbc:sqlite:" + "/vs/vs/taban.db";
    Connection connection = DriverManager.getConnection(jdbcUrl);
} catch (Exception e) {
    e.printStackTrace();
}

Not: Akla şu soru gelebilir, "Android'in kapalı API'sinin hangi jar'ı kullandığını bulsam, o jar'ı dizüstü'nde kullansam, testi bu şekilde işletebilir miyim?". Bu olmuyor, Android içindeki SQLite'i telefon dışında işletmek mümkün değil. Android benzetici (emulator) ile tabii tüm telefon fonksiyonları dizüstü'nde işletilebilir, ama bu da uzun iş. Zaten o sebeple insanlar taklitleme işine girmişler, ama o da bize göre külfetli.

Dizustu ortami icin Java JDBC SQLite jar'i

https://repo1.maven.org/maven2/org/xerial/sqlite-jdbc/3.8.9.1/