Java’da Derin Öğrenme ve GPU kullanımı üzerine

Musa Ataş
6 min readDec 28, 2020

--

Herkese Merhaba,

Bildiğiniz gibi Java programlama dili uzun yıllardır (son zamanlarda popülaritesini Python dilini kaptırmış olsa da) piyasada özellikle backend ve iş katmanlarında çok yoğun ve verimli olarak kullanılmaktadır. Makine öğrenmesi ve veri madenciliğinde çok önceleri Java ile geliştirilmiş Weka ve RapidMiner aracını çoğumuz kullanmışızdır. Aslında Java 8 den sonra dile gelen fonksiyonel programlama kabiliyeti ve iyileştirmeler çok olumlu sayılabilir ancak yaklaşık 15 yıllık bir Java programcısı olarak özellikle derin öğrenme konularında Python ve C++ a göre oldukça gerilerden geldiğimizi üzülerek izliyorum. Halihazırda derin öğrenme ile ilgili olarak Java tarafında Apache Spark, Deep learning for Java (DL4J ) ve Deep Java Library (DJL) çalışmaları hız kesmeden devam ediyor. Java ile ilgili geliştirilen makine öğrenme araçlarının detaylı tanıtımına buradan erişebilirsiniz. DL4J ve DJL kurulumlarımı zor da olsa gerçekleştirdikten sonraki genel izlenimlerim Java derin öğrenme API’lerinin Python Keras, Tensorflow ve/veya Pytorch a göre oldukça karışık olması ve yeni başlayanlar için öğrenme eğrisinin yüksek oluşudur. Her ne kadar bu tür projeler artık Maven veya Gradle tabanlı geliştiriliyor olsa da derin öğrenme alanına yeni başlayan bir Java yazılımcısı için üretilen hataların çözümünde stack overflow araştırmaları oldukça büyük zaman alacağı görülecektir. Gerçi Python tarafında da hatalarla karşılaşmak özellikle Tensorflow güncellemelerinden sonra sık karşılaştığım bir durum oldu ancak genelde tüm problemlerin açıklayıcı cevaplarını internette bulma fırsatım her zaman oldu. Neyse sözü uzatmadan derin öğrenme bağlamında Java tarafında mevcut kronik sorunlara bir göz atalım isterseniz.

1- DJL’yi Nvidia GPU lu PC’imde intelliJ üzerinde bir türlü başarılı bir şekilde çalıştıramadım. Her ne kadar sitesindeki yönergeleri harfiyen izleyip gereksinimleri yerine getirmiş olsam bile henüz başarılı olamadım.

2- DL4J geliştirme sitesinde denilenleri yaptığınızda yaklaşık bir iki saat içinde kurulumunu sorunsuz bir şekilde yapıyorsunuz. Ancak örnekleri genellikle CPU tabanlı çalıştırabildim. GPU bir türlü çalışmadı. Halbuki aynı makinemde Python TensorFlow canavar gibi GPU’yu kullanabildi. Birçok kez sorunu gruplarına yazmış olsam da tatmin edici bir çözüme ulaşamadım.

3- Gerek DJL olsun ve gerekse de DL4J olsun programcı açısından az kod yazarak hedefe ulaşabilme şansı yok. Birçok seremoniden ve ön hazırlık aşamasından sonra isteğinize ulaşabiliyorsunuz ki bence işin bu kısmı birçok programcıyı Python tarafına geçmek zorunda bırakıyor. Halbuki burada anlatılan gibi

Glide.with(this).load(URL).into(ImageView)

fluent interface tarzında bir yaklaşım bence derin öğrenme pipeline’ı için de düşünülebilir ve de çok işe yarar. Bu tarz yaklaşımlar kod okunurluğunu arttırmakla birlikte hata yapma riskini de azaltır diye düşünüyorum.

Bir kaç gündür nette Java ve GPU konuları üzerinde araştırma yapıyorum https://www.youtube.com/watch?v=BjdYRtL6qjg ve https://www.youtube.com/watch?v=4q9fPOI-x80 bu videoları izlemenizi öneririm. Ayrıca aparapi github sitesine buradan, ve çeşitli yazılara (Dmitry Aleksandrov un yazısı)buradan, benchmarklara buradan ve çok boyutlu dizlerle ilgili programlar için buradan erişebilirsiniz. Özellikle @Dmitry Aleksandrov ‘un yazısını okumanızı ve incelemenizi isterim.

Ben de bu aparapi’yi kendi projelerimde kullanabilir miyim ve bana ne kadar faydası olur maksadıyla aşağıya kodlarını yazacağım küçük bir benchmark yaptım. Bunun için genelde kullandığım Netbeans 8 editörümde yeni bir java projesi açtım. Aparapi jar larını (buradan indirilebilir) projeye tanıttıktan sonra aşağıdaki kodu yazdım. Kod kısaca iki boyutlu 5000x5000 bir float dizisinin her bir elemanına bazı matematiksel fonksiyonları (sin, cos, exp vb.) uyguluyor. Ve bu işlemi sıradan bir derin öğrenme algoritmasında (çok daha soyut hali) kullanılan epoch mantığıyla 100 defa çağırıyor. Sonuç gerçekten de kayda değer şekilde etkili idi. NVidia Geforce 750 M ekran kartına sahip olan dizüstü bilgisayarımda (grafik sürücüsünün kendi driverlarını Windows’u yüklerken yüklemem dışında) hiçbir ekstra kurulum işlemi yapmamama rağmen Gpu tabanlı Java programım sorunsuz bir şekilde çalıştı.

‘Java ’

package aparapitest;

import com.aparapi.Kernel;
import com.aparapi.Range;
import java.util.Arrays;
import java.util.Random;

/**
*
* @author cezerilab
*/
public class AparapiTest {

public static void main(String[] args) {
final int size = 5000;
float[][] values = populateArrayRand(size, size);
//float[][] values = new float[size][size];
processWithGpu(values, 100);
System.out.println(“ — — — — — — — — — — — — — — — “);
//processWithCpu(values, 10);
}

private static void processWithGpu(float[][] values, int n) {
long t = System.currentTimeMillis();
Random r = new Random(123);
for (int i = 0; i < n; i++) {
int nr = values.length;
int nc = values[0].length;
final float[] squares = new float[nr * nc];
final float[] val = convert1D(values);
Kernel kernel = new Kernel() {
@Override
public void run() {
int gid = getGlobalId();
//val[gid]=r.nextFloat()*100;
squares[gid] = (float) (Math.cos(Math.sin(val[gid])) + Math.sin(Math.cos(val[gid])));
squares[gid] = (float) (1.0 / (1 + Math.exp(squares[gid])));
}
};
kernel.execute(Range.create(nr * nc));
//System.out.println(“Device = “ + kernel.getTargetDevice().getShortDescription());
kernel.dispose();
}

//System.out.println(Arrays.toString(val));
//System.out.println(Arrays.toString(squares));
//values = convert2D(val, nr, nc);
System.out.println(“GPU elapsed time:” + (System.currentTimeMillis() — t));
}

private static void processWithCpu(float[][] values, int n) {
Random r = new Random(123);
long t = System.currentTimeMillis();
int nr = values.length;
int nc = values[0].length;
final float[][] squares = new float[nr][nc];
for (int k = 0; k < n; k++) {
for (int i = 0; i < nr; i++) {
for (int j = 0; j < nc; j++) {
//values[i][j]=r.nextFloat()*100;
squares[i][j] = (float) (Math.cos(Math.sin(values[i][j])) + Math.sin(Math.cos(values[i][j])));
squares[i][j] = (float) (1.0 / (1 + Math.exp(squares[i][j])));
}
}
}
//float[] v1 = convert1D(values);
//System.out.println(Arrays.toString(v1));
//float[] v2 = convert1D(squares);
//System.out.println(Arrays.toString(v2));
System.out.println(“CPU elapsed time:” + (System.currentTimeMillis() — t));
}

private static float[] convert1D(float[][] d) {
int nr = d.length;
int nc = d[0].length;
float[] q = new float[nr * nc];
for (int i = 0; i < nr; i++) {
for (int j = 0; j < nc; j++) {
q[i * nc + j] = d[i][j];
}
}
return q;
}

private static float[][] convert2D(float[] d, int nr, int nc) {
float[][] ret = new float[nr][nc];
for (int i = 0; i < nr; i++) {
for (int j = 0; j < nc; j++) {
ret[i][j] = d[i * nc + j];
}
}
return ret;
}

private static void test(float[][] values) {
int nr = values.length;
int nc = values[0].length;
Random r = new Random(123);
for (int i = 0; i < nr; i++) {
for (int j = 0; j < nc; j++) {
values[i][j] = r.nextFloat() * 100;
System.out.print(values[i][j] + “,”);
}
System.out.println();
}
System.out.println(“ — — — — — — — — — — — — — — — — — — — — — — — — “);
float[] m = convert1D(values);
System.out.println(Arrays.toString(m));
System.out.println(“ — — — — — — — — — — — — — — — — — — — — — — — — “);
float[][] m2 = convert2D(m, nr, nc);
for (int i = 0; i < nr; i++) {
for (int j = 0; j < nc; j++) {
System.out.print(m2[i][j] + “,”);
}
System.out.println(“”);
}
System.out.println(“ — — — — — — — — — — — — — — — — — — — — — — — — “);
}

private static float[][] populateArrayRand(int nr, int nc) {
Random r = new Random(123);
float[][] ret = new float[nr][nc];
for (int i = 0; i < nr; i++) {
for (int j = 0; j < nc; j++) {
ret[i][j] = r.nextFloat() * 100;
}
}
return ret;
}
}

bu yazılan Java programını diğer dillerle kıyaslamak için de aşağıdaki kodları yazdım

Python dilinde

— — — — —

import timeit
import numpy as np

n=5000
t=0
defa=100
# y=np.zeros((n,n))
y=np.random.rand(n,n)

for x in range(defa):
start = timeit.default_timer()
y=np.cos(np.sin(y))+np.sin(np.cos(y))
y=1/(1+np.exp(y))
stop = timeit.default_timer()
t+=(stop — start)
print(‘Time: ‘, stop — start)

print(‘total time=’,t*1000,” ms”)

C Dilinde

— — — — — — — — — — — -

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define size 5000
#define max 100

float values[size][size];

void doStuff(){
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
values[i][j]=rand()%100;
values[i][j] = (float) (cos(sin(values[i][j])) + sin(cos(values[i][j])));
values[i][j] = (float) (1.0 / (1 + exp(values[i][j])));
}
}
}
int main()
{
for(int i=0;i<max;i++){
doStuff();
}
printf(“finished\n”);
return 0;
}

Dizüstü bilgisayarımdaki çıktılar da aşağıda

1 defa çağrılırsa

Java (cpu backend) → 10 sn

C (cpu backend) → 6 sn

Python (numy+blas) → 1.4 sn

Java (gpu backend) → 1.9 sn

100 defa çağrıldığında

Java (cpu backend) → 1000 sn

C (cpu backend) → 600 sn

Python (numy+blas) → 140 sn

Java (gpu backend) → 30 sn

çekti. Bu da bize gösteriyor ki gelecekte programlama dilleri çalışma anında karmaşıklık durumuna göre cpu veya gpu back endine otomatik switch edecekler. Ve performans belirgin bir şekilde artacak diye düşünüyorum. Ben Open Cezeri Library’ ye aparapi jar larını eklemeye çoktan başladım bile. :)

Open Cezeri Library ile ilgili kısmet olursa ayrıca bir yazı yazmayı da düşünüyorum. Görüşmek üzere.

Selamlar.

--

--

Musa Ataş
Musa Ataş

Written by Musa Ataş

Software Development, Java, IOT, Machine Learning, Game Development

Responses (1)