🛠️

libsvm-jsのビルド

2023/12/10に公開

libsvm

svm実装のlibsvmがあります。

それをasm、wasm化したjavascript実装もあります。

5年前から更新されておらず、現時点のEmscripten v3.1.50ではビルド自体は成功します。
しかし、実行するとエラーになります。

Emscripten v3.1.50対応版libsvm-js

以降の対策を実施したコードを以下に置きましたので自己責任でご利用ください。

libsvm-jsのビルド

ビルド後、examples/xor.jsを実行すると以下のエラーが発生しました。

asm
failed
TypeError: libsvm.cwrap is not a function
    at module.exports (/home/home/workspace/libsvm/src/loadSVM.js:7:30)
    at Object.<anonymous> (/home/home/workspace/libsvm/asm.js:6:18)
    at Module._compile (node:internal/modules/cjs/loader:1256:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1310:10)
    at Module.load (node:internal/modules/cjs/loader:1119:32)
    at Module._load (node:internal/modules/cjs/loader:960:12)
    at Module.require (node:internal/modules/cjs/loader:1143:19)
    at require (node:internal/modules/cjs/helpers:121:18)
    at execAsm (/home/home/workspace/libsvm/examples/xor.js:25:15)
    at Object.<anonymous> (/home/home/workspace/libsvm/examples/xor.js:42:3)
failed to asynchronously prepare wasm: Error: ENOENT: no such file or directory, open '/home/home/workspace/libsvm/out/asm/libsvm.wasm'
Aborted(Error: ENOENT: no such file or directory, open '/home/home/workspace/libsvm/out/asm/libsvm.wasm')

また、githubのissueにもありますが、保存したモデルサイズが大きくなると動かない問題もあります。

自前でビルドすることでメモリサイズ等の調整もしてみたいと思います。

問題1

asmなのになぜかwasmがないと出ている

failed to asynchronously prepare wasm: Error: ENOENT: no such file or directory, open '/home/home/workspace/libsvm/out/asm/libsvm.wasm'

Makefileのasmに「-s WASM=0」を追加しました

Makefile
@@ -14,7 +14,7 @@ wasm: js-interfaces.c svm.o libsvm/svm.h
        mkdir -p $(BUILD_DIR)/wasm; $(CC) $(CFLAGS) js-interfaces.c svm.o -o $(BUILD_DIR)/wasm/libsvm.js -s DISABLE_EXCEPTION_CATCHING=0 -s NODEJS_CATCH_EXIT=0 -s "EXPORT_NAME=\"SVM\"" -s MODULARIZE=1 -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_FUNCTIONS=$(EXPORTED_FUNCTIONS)  -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap", "Pointer_stringify"]'

 asm: js-interfaces.c svm.o libsvm/svm.h
-       mkdir -p $(BUILD_DIR)/asm; $(CC) $(CFLAGS) js-interfaces.c svm.o -o $(BUILD_DIR)/asm/libsvm.js -s NODEJS_CATCH_EXIT=0 -s "EXPORT_NAME=\"SVM\"" -s MODULARIZE=1 -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_FUNCTIONS=$(EXPORTED_FUNCTIONS)   -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap", "Pointer_stringify"]'
+       mkdir -p $(BUILD_DIR)/asm; $(CC) $(CFLAGS) js-interfaces.c svm.o -o $(BUILD_DIR)/asm/libsvm.js -s NODEJS_CATCH_EXIT=0 -s "EXPORT_NAME=\"SVM\"" -s MODULARIZE=1 -s WASM=0 -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_FUNCTIONS=$(EXPORTED_FUNCTIONS)   -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap", "Pointer_stringify"]'

 clean:
        rm -f *~ js-interfaces.o ./svm.o

問題2

libsvm.cwrapがないといわれる

TypeError: libsvm.cwrap is not a function
    at module.exports (/home/home/workspace/libsvm/src/loadSVM.js:7:30)
    at Object.<anonymous> (/home/home/workspace/libsvm/asm.js:6:18)
    at Module._compile (node:internal/modules/cjs/loader:1256:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1310:10)
    at Module.load (node:internal/modules/cjs/loader:1119:32)
    at Module._load (node:internal/modules/cjs/loader:960:12)
    at Module.require (node:internal/modules/cjs/loader:1143:19)
    at require (node:internal/modules/cjs/helpers:121:18)
    at execAsm (/home/home/workspace/libsvm/examples/xor.js:25:15)
    at Object.<anonymous> (/home/home/workspace/libsvm/examples/xor.js:42:3)

オリジナルのasmはsynchronousでしたが、ビルドし直すとasynchronousとなっていました

asm.js
@@ -3,4 +3,4 @@
 const loadSVM = require('./src/loadSVM');
 const libsvm = require('./out/asm/libsvm');

-module.exports = loadSVM(libsvm);
+module.exports = libsvm.then((instance) => loadSVM(instance));
examples/xor.js
@@ -20,9 +20,9 @@ function xor(SVM) {
   console.log('save model', svm.serializeModel());
 }

-function execAsm() {
+async function execAsm() {
   console.log('asm');
-  const SVM = require('../asm');
+  const SVM = await require('../asm');
   xor(SVM);
 }

@@ -38,10 +38,12 @@ async function execWasm() {
   xor(SVM);
 }

-try {
-  execAsm(); // Synchronous
-  execWasm(); // Asynchronous
-} catch (e) {
-  console.log('failed');
-  console.log(e);
-}
+(async () => {
+  try {
+    await execAsm(); // Synchronous
+    await execWasm(); // Asynchronous
+  } catch (e) {
+    console.log('failed');
+    console.log(e);
+  }
+})();

問題3

libsvm._mallocがないといわれる

TypeError: libsvm._malloc is not a function
    at getIntArrayFromModel (/home/home/workspace/libsvm/src/loadSVM.js:295:27)
    at SVM.getSVIndices (/home/home/workspace/libsvm/src/loadSVM.js:229:14)
    at xor (/home/home/workspace/libsvm/examples/xor.js:18:33)
    at execAsm (/home/home/workspace/libsvm/examples/xor.js:26:3)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async /home/home/workspace/libsvm/examples/xor.js:43:5

MakefileのEXPORTED_FUNCTIONSに'_malloc'と'_free'を追加しました

Makefile
@@ -3,7 +3,7 @@ CXX = em++

 CFLAGS = -Wall -Wconversion -O3 -fPIC --memory-init-file 0
 BUILD_DIR=out/emscripten
-EXPORTED_FUNCTIONS="['_parse_command_line', '_create_svm_nodes', '_add_instance', '_libsvm_train_problem', '_libsvm_train', '_libsvm_predict_one', '_libsvm_predict_one_probability', '_get_svr_epsilon', '_svm_free_model', '_svm_get_svm_type', '_svm_get_nr_sv', '_svm_get_nr_class', '_svm_get_sv_indices', '_svm_get_labels', '_svm_get_svr_probability', '_libsvm_cross_validation', '_free_problem', '_serialize_model', '_deserialize_model']"
+EXPORTED_FUNCTIONS="['_parse_command_line', '_create_svm_nodes', '_add_instance', '_libsvm_train_problem', '_libsvm_train', '_libsvm_predict_one', '_libsvm_predict_one_probability', '_get_svr_epsilon', '_svm_free_model', '_svm_get_svm_type', '_svm_get_nr_sv', '_svm_get_nr_class', '_svm_get_sv_indices', '_svm_get_labels', '_svm_get_svr_probability', '_libsvm_cross_validation', '_free_problem', '_serialize_model', '_deserialize_model', '_malloc', '_free']"

問題4

libsvm.Pointer_stringifyがないといわれる

TypeError: libsvm.Pointer_stringify is not a function
    at SVM.serializeModel (/home/home/workspace/libsvm/src/loadSVM.js:240:26)
    at xor (/home/home/workspace/libsvm/examples/xor.js:20:33)
    at execAsm (/home/home/workspace/libsvm/examples/xor.js:26:3)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async /home/home/workspace/libsvm/examples/xor.js:43:5

UTF8ToStringを使用するようにします

Makefile
@@ -11,10 +11,10 @@ svm.o: libsvm/svm.cpp libsvm/svm.h
        $(CXX) $(CFLAGS) -c libsvm/svm.cpp -o svm.o

 wasm: js-interfaces.c svm.o libsvm/svm.h
-       mkdir -p $(BUILD_DIR)/wasm; $(CC) $(CFLAGS) js-interfaces.c svm.o -o $(BUILD_DIR)/wasm/libsvm.js -s DISABLE_EXCEPTION_CATCHING=0 -s NODEJS_CATCH_EXIT=0 -s "EXPORT_NAME=\"SVM\"" -s MODULARIZE=1 -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_FUNCTIONS=$(EXPORTED_FUNCTIONS)  -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap", "Pointer_stringify"]'
+       mkdir -p $(BUILD_DIR)/wasm; $(CC) $(CFLAGS) js-interfaces.c svm.o -o $(BUILD_DIR)/wasm/libsvm.js -s DISABLE_EXCEPTION_CATCHING=0 -s NODEJS_CATCH_EXIT=0 -s "EXPORT_NAME=\"SVM\"" -s MODULARIZE=1 -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_FUNCTIONS=$(EXPORTED_FUNCTIONS)  -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap", "UTF8ToString"]'

 asm: js-interfaces.c svm.o libsvm/svm.h
-       mkdir -p $(BUILD_DIR)/asm; $(CC) $(CFLAGS) js-interfaces.c svm.o -o $(BUILD_DIR)/asm/libsvm.js -s NODEJS_CATCH_EXIT=0 -s "EXPORT_NAME=\"SVM\"" -s MODULARIZE=1 -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_FUNCTIONS=$(EXPORTED_FUNCTIONS)   -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap", "Pointer_stringify"]'
+       mkdir -p $(BUILD_DIR)/asm; $(CC) $(CFLAGS) js-interfaces.c svm.o -o $(BUILD_DIR)/asm/libsvm.js -s NODEJS_CATCH_EXIT=0 -s "EXPORT_NAME=\"SVM\"" -s MODULARIZE=1 -s WASM=0 -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_FUNCTIONS=$(EXPORTED_FUNCTIONS)   -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap", "UTF8ToString"]'
src/loadSVM.js
@@ -237,7 +237,7 @@ module.exports = function (libsvm) {
     serializeModel() {
       if (!this.model) throw new Error('Cannot serialize model. No model was trained');
       const result = serialize_model(this.model);
-      const str = libsvm.Pointer_stringify(result);
+      const str = libsvm.UTF8ToString(result);
       libsvm._free(result);
       return str;
     }

問題5

libsvm.loadがないといわれる

TypeError: libsvm.load is not a function
    at Object.<anonymous> (/home/home/workspace/libsvm/wasm.js:6:25)
    at Module._compile (node:internal/modules/cjs/loader:1256:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1310:10)
    at Module.load (node:internal/modules/cjs/loader:1119:32)
    at Module._load (node:internal/modules/cjs/loader:960:12)
    at Module.require (node:internal/modules/cjs/loader:1143:19)
    at require (node:internal/modules/cjs/helpers:121:18)
    at execWasm (/home/home/workspace/libsvm/examples/xor.js:33:17)
    at /home/home/workspace/libsvm/examples/xor.js:44:11
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
wasm.js
@@ -3,4 +3,4 @@
 const loadSVM = require('./src/loadSVM');
 const libsvm = require('./out/wasm/libsvm');

-module.exports = libsvm.load().then(() => loadSVM(libsvm));
+module.exports = libsvm.then((instance) => instance.load().then(() => loadSVM(instance)));

問題6

モデルサイズが大きくなると動かない

Makefile
@@ -1,9 +1,9 @@
 CC = emcc
 CXX = em++

-CFLAGS = -Wall -Wconversion -O3 -fPIC --memory-init-file 0
+CFLAGS = -Wall -Wconversion -O3 -fPIC --memory-init-file 0 -sSTACK_SIZE=8388608
 BUILD_DIR=out/emscripten
-EXPORTED_FUNCTIONS="['_parse_command_line', '_create_svm_nodes', '_add_instance', '_libsvm_train_problem', '_libsvm_train', '_libsvm_predict_one', '_libsvm_predict_one_probability', '_get_svr_epsilon', '_svm_free_model', '_svm_get_svm_type', '_svm_get_nr_sv', '_svm_get_nr_class', '_svm_get_sv_indices', '_svm_get_labels', '_svm_get_svr_probability', '_libsvm_cross_validation', '_free_problem', '_serialize_model', '_deserialize_model']"
+EXPORTED_FUNCTIONS="['_parse_command_line', '_create_svm_nodes', '_add_instance', '_libsvm_train_problem', '_libsvm_train', '_libsvm_predict_one', '_libsvm_predict_one_probability', '_get_svr_epsilon', '_svm_free_model', '_svm_get_svm_type', '_svm_get_nr_sv', '_svm_get_nr_class', '_svm_get_sv_indices', '_svm_get_labels', '_svm_get_svr_probability', '_libsvm_cross_validation', '_free_problem', '_serialize_model', '_deserialize_model', '_malloc', '_free']"

 all: wasm asm

STACK_SIZEを大きくすると多少大きなモデルでも読み込めるようになりました

やっと動きました

$ node examples/xor.js
asm
*
optimization finished, #iter = 2
nu = 1.000000
obj = -3.200847, rho = 0.000000
nSV = 4, nBSV = 4
Total nSV = 4
actual: 0, predicted: 0
actual: 0, predicted: 0
actual: 1, predicted: 1
actual: 1, predicted: 1
sv indices [ 0, 1, 2, 3 ]
labels [ 0, 1 ]
save model svm_type c_svc
kernel_type rbf
gamma 1
nr_class 2
total_sv 4
rho 0
label 0 1
nr_sv 2 2
SV
1 1:0 2:0
1 1:1 2:1
-1 1:1 2:0
-1 1:0 2:1

wasm
[class SVM] {
  SVM_TYPES: {
    C_SVC: '0',
    NU_SVC: '1',
    ONE_CLASS: '2',
    EPSILON_SVR: '3',
    NU_SVR: '4'
  },
  KERNEL_TYPES: {
    LINEAR: '0',
    POLYNOMIAL: '1',
    RBF: '2',
    SIGMOID: '3',
    PRECOMPUTED: '4'
  }
}
*
optimization finished, #iter = 2
nu = 1.000000
obj = -3.200847, rho = 0.000000
nSV = 4, nBSV = 4
Total nSV = 4
actual: 0, predicted: 0
actual: 0, predicted: 0
actual: 1, predicted: 1
actual: 1, predicted: 1
sv indices [ 0, 1, 2, 3 ]
labels [ 0, 1 ]
save model svm_type c_svc
kernel_type rbf
gamma 1
nr_class 2
total_sv 4
rho 0
label 0 1
nr_sv 2 2
SV
1 1:0 2:0
1 1:1 2:1
-1 1:1 2:0
-1 1:0 2:1

Discussion