最近在用DeepLearning4J(DL4J)嘗試語音識別的深度學習,Git DL4J的程式碼,用IntelliJ IDEA開啟,配置好相關依賴後,執行包org.deeplearning4j.examples.test.Test的main,可以正常執行,但是有警告提示如下:
1 2 3 4 5 6 7 8 9 10 |
十一月 27, 2015 12:37:07 下午 com.github.fommil.netlib.BLAS <clinit> WARNING: Failed to load implementation from: com.github.fommil.netlib.NativeSystemBLAS 十一月 27, 2015 12:37:09 下午 com.github.fommil.jni.JniLoader liberalLoad **************************************************************** INFO: successfully loaded C:\Users\ADMINI~1\AppData\Local\Temp\jniloader6882206374132167742netlib-native_ref-win-x86_64.dll WARNING: COULD NOT LOAD NATIVE SYSTEM BLAS ND4J performance WILL be reduced Please install native BLAS library such as OpenBLAS or IntelMKL See http://nd4j.org/getstarted.html#open for further details **************************************************************** |
提示無法載入com.github.fommil.netlib.NativeSystemBLAS,和無法載入native system blas,DN4J的效能會受到影響.
查了github,stackoverflow,quora等,找到了如下網頁.github的是一個遇到類似問題的人抱怨native blas難以配置,害的自己在原始碼中才找到解決方法,而nd4j的程式設計師回答所有的深度學習框架中的native blas都是難配置的,我在搜尋的時候也發現了MLib等庫確實也會報這個警告.第二個部落格是一篇講述如何配置blas的文章.
1 2 |
https://github.com/deeplearning4j/nd4j/issues/456 http://avulanov.blogspot.cz/2014/09/howto-to-run-netlib-javabreeze-in.html |
這篇文章中,我想說明一下幾個問題:
1. native blas 是本地庫,用C/C++寫成,因而運算速度較快.
2. Java如果要呼叫C/C++的dll,一定要用JNI技術來呼叫對應的dll,那麼dll的路徑和名稱分別是什麼?
3. 如果找到需要的dll,假設叫a.dll,如果a.dll又依賴於b.dll和c.dll,那麼把a.dll,b.dll,c.dll都放在Java識別的路徑下,是不是就可以解決這個問題了.
1.dll的存放路徑和名稱
先看DL4J的原始碼,錯誤首先出現在如下程式碼中
1 |
Nd4j.getRandom().setSeed(seed); |
進入Nd4j的類,加斷點,單步除錯,經過如下的函式呼叫棧後,進入NativeSystemBLAS這個類.
我們看下NativeSystemBLAS類的內容,在static靜態塊中找到如下用於載入dll的程式碼:
1 2 3 4 5 |
static { String jnilib = JniNamer.getJniName("netlib-native_system"); String natives = System.getProperty("com.github.fommil.netlib.NativeSystemBLAS.natives", jnilib); JniLoader.load(natives.split(",")); } |
先加斷點單步執行,看看jnilib的內容,執行後得到的是”netlib-native_system-win-x86_64.dll”,為什麼是這個,我的電腦是64位,如果32位系統又會需要哪個dll庫呢?
我們進入getJniName函式,看下其程式碼,這裡arch用於獲得架構,os用於獲得系統版本,extension獲得字尾,然後拼出dll的名稱:
1 2 3 4 5 6 7 |
public static String getJniName(String stem) { String arch = arch();//獲得系統架構,x86,i386對應於i686,x86_64,amd64對應於x86_64,其他還有arm等架構 String abi = abi(arch);//判斷是不是arm架構,如果是arm架構,需要其他的設定,這裡不問 String os = os();//判斷系統是win,linux.還是mac os x String extension = extension(os);//根據系統版本,確定檔案字尾名是dll,還是so,osx return stem + "-" + os + "-" + arch + abi + "." + extension;//返回確定的庫名字 } |
arch程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
private static String arch() { String arch = System.getProperty("os.arch", "").toLowerCase(); if(!arch.equals("x86") && !arch.equals("i386") && !arch.equals("i486") && !arch.equals("i586") && !arch.equals("i686")) { if(!arch.equals("x86_64") && !arch.equals("amd64")) { if(arch.equals("ia64")) { return "ia64"; } else if(arch.equals("arm")) { return "arm"; } else if(arch.equals("armv5l")) { return "armv5l"; } else if(arch.equals("armv6l")) { return "armv6l"; } else if(arch.equals("armv7l")) { return "armv7l"; } else if(arch.equals("sparc")) { return "sparc"; } else if(arch.equals("sparcv9")) { return "sparcv9"; } else if(arch.equals("pa_risc2.0")) { return "risc2"; } else if(arch.equals("ppc")) { return "ppc"; } else if(arch.startsWith("ppc")) { return "ppc64"; } else { log.warning("unrecognised architecture: " + arch); return "unknown"; } } else { return "x86_64"; } } else { return "i686"; } } |
abi程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
private static String abi(String arch) { if(!arch.startsWith("arm")) { return ""; } else { try { String[] e = new String[]{"sun.boot.library.path", "java.library.path", "java.home"}; int len$ = e.length; int i$; String dir; for(i$ = 0; i$ < len$; ++i$) { dir = e[i$]; String file = System.getProperty(dir, ""); log.config(dir + ": " + file); if(file.matches(".*(gnueabihf|armhf).*")) { return "hf"; } } e = new String[]{"/lib/arm-linux-gnueabihf", "/usr/lib/arm-linux-gnueabihf"}; len$ = e.length; for(i$ = 0; i$ < len$; ++i$) { dir = e[i$]; File var7 = new File(dir); if(var7.exists()) { return "hf"; } } return ""; } catch (SecurityException var6) { log.log(Level.WARNING, "unable to detect ABI", var6); return "unknown"; } } } |
os程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
private static String os() { String os = System.getProperty("os.name", "").toLowerCase(); if(os.startsWith("linux")) { return "linux"; } else if(os.startsWith("windows")) { return "win"; } else if(!os.startsWith("mac os x") && !os.startsWith("darwin")) { if(os.startsWith("freebsd")) { return "freebsd"; } else if(os.startsWith("android")) { return "android"; } else if(os.startsWith("sunos")) { return "sun"; } else if(os.startsWith("hp-ux")) { return "hpux"; } else if(os.startsWith("kd")) { return "kd"; } else { log.warning("unable to detect OS type: " + os); return "unknown"; } } else { return "osx"; } } |
extension程式碼如下:
1 2 3 |
private static String extension(String os) { return os.equals("win")?"dll":(os.equals("osx")?"jnilib":"so"); } |
這樣,我就知道了dll的具體名字為什麼是netlib-native_system-win-x86_64.dll了.
接下來,我還需要知道這個dll要放在哪裡,才能被nd4j程式碼找到!
接下來,我們跳進JniLoader.load(natives.split(“,”)),看看load是如何執行的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
public static synchronized void load(String... paths) { if(paths != null && paths.length != 0) { String[] javaLibPath = paths; int arr$ = paths.length; int len$; String path; for(len$ = 0; len$ < arr$; ++len$) { String i$ = javaLibPath[len$]; path = (new File(i$)).getName(); if(loaded.contains(path)) { log.info("already loaded " + i$); return; } } javaLibPath = System.getProperty("java.library.path").split(File.pathSeparator);//這裡獲得windows下path對應的路徑,是個字串陣列 String[] var11 = paths; len$ = paths.length; for(int var12 = 0; var12 < len$; ++var12) { path = var11[var12]; log.config("JNI LIB = " + path); String[] extracted = javaLibPath; int len$1 = javaLibPath.length; for(int i$1 = 0; i$1 < len$1; ++i$1) { String libPath = extracted[i$1]; File file = (new File(libPath, path)).getAbsoluteFile();//這裡根據path路徑和dll的名稱拼成一個檔案路徑,下面檢查這個檔案是否存在,如果不存在,拼下一個檔案路徑,如果存在,就載入這個dll log.finest("checking " + file); if(file.exists() && file.isFile() && liberalLoad(file, path)) { return; } } File var13 = extract(path); if(var13 != null && liberalLoad(var13, path)) { return; } } throw new ExceptionInInitializerError("unable to load from " + Arrays.toString(paths)); } else { throw new ExceptionInInitializerError("invalid parameters"); } } |
其實這段程式碼本來我沒有看太懂,但是架不住除錯功能強大,執行一次,加斷點,看看內容怎麼變化,就知道是什麼意思了.
這裡假設我的windows下Path環境變數設定為:”D:\Python\Python35\Scripts\;D:\Program Files\Java\jdk1.7.0_15\bin;C:\WINDOWS;D:\BLAS;”,那麼javaLibPath的內容就會是這四個組成的字串陣列.接下來取出來第一個,和dll名稱(netlib-native_system-win-x86_64.dll)一起拼成一個檔案路徑”D:\Python\Python35\Scripts\netlib-native_system-win-x86_64.dll”,接下來檢查這個路徑是否是檔案,如果不是,就會繼續拼下一個,直到拼出”D:\BLAS\netlib-native_system-win-x86_64.dll”,然後我的dll檔案確實放在這裡,程式就會載入.
現在我們找到對應的dll檔案,這裡為netlib-native_system-win-x86_64.dll,然後放在D:\BLAS\這個位置,把D:\BLAS加入path變數,然後重啟Intellij(這一步不一定需要,大家自己嘗試),讓它重新讀取path值.
重新執行下Test裡的main函式,然後執行結果竟然還是如上的錯誤,額,是我們剛才的分析都是錯誤了麼?
2.dll庫的依賴和依賴查詢
我們在跳進載入dll的地方,看看究竟是哪裡錯了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
private static boolean liberalLoad(File file, String name) { try { log.finest("attempting to load " + file); System.load(file.getAbsolutePath()); log.info("successfully loaded " + file); loaded.add(name); return true; } catch (UnsatisfiedLinkError var6) { //這裡捕獲到一個異常,說Can't find dependent libraries. log.log(Level.FINE, "skipping load of " + file, var6); String tmpdir = System.getProperty("java.io.tmpdir"); if(tmpdir != null && tmpdir.trim().length() > 2 && file.getAbsolutePath().startsWith(tmpdir)) { log.log(Level.FINE, "deleting " + file); try { file.delete(); } catch (Exception var5) { log.info("failed to delete " + file); } } return false; } catch (SecurityException var7) { log.log(Level.INFO, "skipping load of " + file, var7); return false; } catch (Throwable var8) { throw new ExceptionInInitializerError(var8); } } |
這裡我們捕獲到一個異常,說 D:\BLAS\netlib-native_system-win-x86_64.dll: Can’t find dependent libraries.
原來如此我們的庫還需要一些依賴庫,這些依賴庫沒有找到.但是我們怎麼知道這個庫依賴於哪些庫呢?哪些是已經有的,哪些是沒有的?這裡推薦一個工具,叫PEStudio,可以檢視dll檔案或exe檔案依賴於哪些庫:
這裡顯示了netlib-native_system-win-x86_64.dll依賴於哪些庫,其中liblapack3.dll和libblas3.dll是我們需要找到了.其實到這一步,問題就算解決了,參見OpenBlas的官網,這些檔案都可以很輕鬆的下載到,就是要注意首先需要知道你要的是32位還是64位,其次所有dll需要是統一的,不能部分32,部分64. 謝謝曲奇餅的提醒, 這裡需要注意的還有jdk的版本, 在64位系統下, 應該使用64的jdk, 大家注意嘗試.
這裡是我用PEStudio找到的依賴關係:
至此,問題圓滿解決.
=========================
最後,把在win10 64位系統下成功執行的庫檔案放在這:
csdn:http://download.csdn.net/detail/u201011221/9355487
baiduyun: http://pan.baidu.com/s/1jGO5waE