SVM-Light interface for MATLAB/Octave

SVM-LightMatlab/Octaveで使うためのインターフェイスを書いた.
正直言って,データ行列とクラスラベルをファイルとして保存するため,HDDの読み書きがあるので実行が遅くなるかもしれませんが,晒しておきます.

SVM-Lightのインストール

とりあえずソースからコンパイルします.
コンパイル済みのバイナリを使いたい人はそれでも良いです.



以下コンパイルしたい人向け.
SVM-Light Support Vector Machineよりsvm-light.tar.gzをダウンロード.

コンパイルします.

$ cd {Your downloaded directory}
$ mkdir svm_light
$ mv svm_light.tar.gz svm_light/.
$ gunzip -c svm_light.tar.gz | tar xvf -
$ cd svm_ligth
$ make

以上の手順を踏むと,svm_learnとsvm_classifyという実行ファイルができる.



バイナリでダウンロードした人もコンパイルした人も実行ファイル(svm_learn,svm_classify)にパスを通す.
例えば実行ファイルを/usr/local/binに置く.

$ sudo mv svm_learn /usr/local/bin/.
$ sudo mv svm_classify /usr/local/bin/.

rootじゃない人は,好きなディレクトリ(例えば,~/Myfunc/svm_light)において,パスを通しておく.

$ export PATH=~/Myfunc/svm_light/:$PATH

Windowsな人は良く分かりませんが,MATLAB/Octaveで使うディレクトリにsvm_learn.exeとsvm_classify.exeを置けばよいと思います.



以上をやってから,Terminalエミュレータ上でsvm_learnが使えるか確認する.

$ svm_learn

Not enough input parameters!

SVM-light V6.XX: Support Vector Machine, learning module
...

SVM-Lightの使い方

ググれ.

SVM-Light wrapper for MATLAB and Octave

ダウンロードは以下からどうぞ.
for svm_learn
for svm_classify
Sample code for test


svm_learn.m
function [out svm] = svm_learn(X, y, opt, samplefile, outfile)

% SVM-Light interface for MATLAB/Octave (svm_learn).
%
% INPUTS
% 	- X: d by N data matrix as [x_1 x_2, ..., x_N],
% 		where x_n is a feature vector in larning dataset.
% 	- y: N-dimensional label row vector, y_i in {-1, +1}.
% 	- opt: options for learning.
% 		Learning options
% 		c [float]: soft margin (def. (mean(x*x)^{-1})
% 		j [float]: Cost: cost-factor, by which training errors on
% 				postive examples out weighte erros on negative
% 				example (def. = 1)
% 		b {0, 1}: if 1, uses biased hyperplane (i.e. w'*x + b)
% 				if 0, uses unbiased hyperplane (i.e. w'*x)
% 				(def. = 1)
% 		i {0, 1}: remove inconsistent training examples and retrain
% 				(def. 0)
% 		Transduction options
% 		p [0, 1]: fraction of unlabeled examples to be classified
% 				into the positive class (default is the ratio of
% 				positive and negative examples in the training
% 				data)
% 		Kernel options
% 		t {0, 1, 2, 3, 4}: type of kernel function
% 			0: linear
% 			1: polynomial
% 			2: radial basis function (RBF)
% 			3: sigmoid tanh
% 			4: user defined kernel from kernel.h
% 		d [int]: parameter d in polynimial kernel
% 		g [float]: parameter gamma in RBF kernel
% 		s [float]: parameter c in sigmoid/polynomial kernel
% 		u [string]: parameter of user dfined kernel kernel
% 		Optimization options
% 		q [2, inf]: maximum size of QP-subprblems (default 10)
% 		n [2, q]: number of new variables entering the working set
% 				in each iteration (def. n = q).
% 				Set n < q to prevent zig-zagging
% 		m [5, inf]: size of cache for kernel evaluations in MB
% 				(def. = 40). The larger the faster calculating
% 		e [float]: Allow that error for termination criterion
% 				y(w'*x + b) - 1 = e (def. = 0.001)
% 		h [5, inf]: number of iterations a variable needs to be
% 				optimal before considered for shrinking
% 				(def. = 100)
% 		f {0, 1}: do final optimality check for variables removed
% 				by shrinking. Akthough this test is usually positive,
% 				there is no guarantee that the optimum was found if
% 				the test is omitted (def. = 1)
% 		y [string]: if option is given, reads alphas from file with
% 				given and uses them as starting point.
% 				(def. 'disabled')
% 		# [int]: terminate optimization, if no prgress after
% 				this number of iterations (def = 100000)

%
%% temporary files
if exist('samplefile', 'var') == 0
	samplefile = tempname(pwd);
end

if exist('outfile', 'var') == 0
	outfile = tempname(pwd);
end

%
%% options
opt_all = ['c' 'w' 'j' 'b' 'i' 'p' 't' 'd' 'g' 's' 'r' 'u',...
	'q' 'n' 'm' 'e' 'h' 'f' 'y' '#']';
opt_type = ['f' 'f' 'f' 'd' 'd' 'f' 'd' 'd' 'f' 'f' 'f' 's',...
	'f' 'd' 'd' 'f' 'd' 'd' 's' 'd']';
input_opt = cell2mat(fieldnames(opt));
Nopt = length(input_opt);
opt_str = '';
for ii = 1:Nopt
	input_val = eval(sprintf('opt.%s', input_opt(ii)));
	opt_str = make_opt(input_opt(ii), input_val,...
		opt_all, opt_type, opt_str);
end

%
% write sample file
[d N] = size(X);

fid = fopen(samplefile, 'w');
if fid < 0
	error(sprintf('cannot open %s', samplefile));
end
for ii = 1:N
	fprintf(fid, '%d ', y(ii));
	fprintf(fid, '%d:%e ', [[1:d]; X(:, ii)']);
	fprintf(fid, '\n');
end
fclose(fid);
%system(sprintf('cat %s', samplefile))

%
% excute svm_learn
sysc = sprintf('svm_learn -v 0 -z c %s %s %s',...
	opt_str, samplefile, outfile);
[a b] = system(sysc);
delete(samplefile)

%
% read output file of svm_learn to cell array
%system(sprintf('cat %s', outfile))
fid = fopen(outfile, 'r');
if fid < 0
	error(sprintf('cannot open %s', outfile));
end
cnt = 1;
outl1 = 0;
while outl1 ~= -1;
	outl1 = fgetl(fid);
	if outl1 ~= -1
		out{cnt} = outl1;
		cnt = cnt + 1;
	end
end
fclose(fid);
delete(outfile);

%
% read w and b
svm.version = out{1};
svm.kernel = str2num(strtok(out{2}, ' '));
svm.d = str2num(strtok(out{3}, ' '));
svm.g = str2num(strtok(out{4}, ' '));
svm.s = str2num(strtok(out{5}, ' '));
svm.r = str2num(strtok(out{6}, ' '));
svm.u = strtok(out{7}, '#');
svm.d = str2num(strtok(out{8}, ' '));
svm.N = str2num(strtok(out{9}, ' '));
svm.Nsv = str2num(strtok(out{10}, ' ')) - 1;
svm.b = str2num(strtok(out{11}, ' '));

alpha = zeros(1, svm.Nsv);
sv = zeros(svm.d, svm.Nsv);

for ii=1:svm.Nsv
	val = out{ii + 11}; % SVs are written from line 12;
	val(val==':')= ' ';
	val(val=='#')= ' ';
	val = str2num(val);
	alpha(ii) = val(1);
	sv(:, ii) = val(3:2:end)';
end
svm.w = alpha*sv';


%% ==================================
%% making string for options
function str = make_opt(name, val, opt_all, opt_type, str)
egs = findstr(opt_all, name);
if length(egs) == 1
	type_str = sprintf('%%%s', opt_type(egs));
	str = sprintf(['%s -%s ' type_str], str, name, val);
else
	error('Invalid option is inputted')
end

簡単に解説すると,
サンプルデータはXという行列.
サンプルデータのラベルはベクトルy.
入力変数optは構造体になってて,そこにSVM-Lightに使うオプションを並べれば良い.
一時ファイル名は入れても入れなくても良い.Linuxだったらtmpfsを使えば早く読み書きできるかもしれない.
出力をそのままsvm_classify.mに入れる.
svm_classifyを使いたくないときは,出力svm.wとsvm.bなどが使えます.


svm_classify.m
function [labs z] = svm_classify(X, y, model, samplefile, outfile, modelfile)

% SVM-Light interface for MATLAB/Octave (svm_classify).
%
% INPUTS
% 	- X: d by N data matrix as [x_1 x_2, ..., x_N],
% 		where x_n is a feature vector in test samples.
% 	- y: N-dimensional label row vector, y_i in {-1, +1}.
% 	- model: Model of SVM.

%
%% temporary files
if exist('samplefile', 'var') == 0
	samplefile = tempname(pwd);
end

if exist('outfile', 'var') == 0
	outfile = tempname(pwd);
end

if exist('modelfile', 'var') == 0
	modelfile = tempname(pwd);
end

%
%% correct labels
[d N] = size(X);
if length(y) == 0
	y = zeros(1, N);
end

%
% write sample file
fid = fopen(samplefile, 'w');
if fid < 0
	error(sprintf('cannot open %s', samplefile));
end
for ii = 1:N
	fprintf(fid, '%d ', y(ii));
	fprintf(fid, '%d:%e ', [[1:d]; X(:, ii)']);
	fprintf(fid, '\n');
end
fclose(fid);

%
% write model file
fid = fopen(modelfile, 'w');
if fid < 0
	error(sprintf('cannot open %s', modelfile));
end
Nml = size(model, 2);
for ii = 1:Nml
	fprintf(fid, '%s', model{ii});
	fprintf(fid, '\n');
end
fclose(fid);
%system(sprintf('cat %s', modelfile))

%
% excute svm_classify
sysc = sprintf('svm_classify -v 0 %s %s %s',...
	samplefile, modelfile, outfile);
[a b] = system(sysc);
delete(samplefile)
delete(modelfile)
%system(sprintf('cat %s', outfile))

%
% read out file
fid = fopen(outfile, 'r');
if fid < 0
	error(sprintf('cannot open %s', outfile));
end
cnt = 1;
outl1 = 0;
while outl1 ~= -1;
	outl1 = fgetl(fid);
	if outl1 ~= -1
		z(1, cnt) = str2num(outl1);
		cnt = cnt + 1;
	end
end
fclose(fid);
delete(outfile);
labs(find(z < 0)) = -1;
labs(find(z >= 0)) = 1;

サンプルデータはXという行列.
サンプルデータのラベルはベクトルy.


test_svm_light.m
clear all
randn('state',1);

N = 20; % number of samples for a class
X1 = randn(2, N) + [2 2]'*ones(1, N);
X2 = randn(2, N) + [0 0]'*ones(1, N);

%X1 = randn(2, N);
%X1 = X1./repmat(sqrt(diag(X1'*X1))', 2, 1) + .5*randn(2, N);
%X2 = randn(2, N);
%X2 = X2./repmat(sqrt(diag(X2'*X2))'*.5, 2, 1) + .5*randn(2, N);

X = [X1 X2]; % input learning samples
y = [ones(1, N) -1*ones(1, N)]; % input learning labels

% options for SVM
opt.t = 0; % 0: linear, 1: polynomial, 2: radial basis function (RBF),
		   % 3: sigmoid tanh, 4: user defined kernel from kernel.h
opt.b = 1; % use bias
opt.g = 1; % gamma for RBF kernel
opt.d = 2; % order for polynomial kernel
opt.c = 10; % soft margin parameter

% svm learn
[out svm] = svm_learn(X, y, opt);

% svm classify for training accuracy
[out_label z] = svm_classify(X, [], out);
acc = length(find((out_label - y) == 0))/(2*N);
fprintf('training accuracy: %.2f%%\n', acc*100)

% plot hyperplane
x = -4:0.1:4;
[XX YY] = meshgrid(x);
Xmesh(1, :) = reshape(XX, 1, prod(size(XX)));
Xmesh(2, :) = reshape(YY, 1, prod(size(XX)));
[out_label z] = svm_classify(Xmesh, [], out);
Xmesh1 = Xmesh(:, find(out_label == 1));
Xmesh2 = Xmesh(:, find(out_label == -1));

%% plot results
plot(Xmesh1(1, :),  Xmesh1(2, :),  'y.',...
	Xmesh2(1, :),  Xmesh2(2, :),  'r.',...
	X1(1, :),  X1(2, :),  'o', 'linewidth', 5,...
	X2(1, :),  X2(2, :),  's', 'linewidth', 5)
grid on
axis([-4 4 -4 4])
xlabel('x(1)')
ylabel('x(2)')

サンプルデータの学習をして,識別境界をプロットする.

実行例

サンプルデータを

X1 = randn(2, N) + [2 2]'*ones(1, N);
X2 = randn(2, N) + [0 0]'*ones(1, N);

という感じに作り,線形カーネルによって境界面を作ると下図のように識別できる.
(青と緑がそれぞれのトレーニングサンプル,赤と黄の境界が識別境界)



サンプルデータを

X1 = randn(2, N);
X1 = X1./repmat(sqrt(diag(X1'*X1))', 2, 1) + .5*randn(2, N);
X2 = randn(2, N);
X2 = X2./repmat(sqrt(diag(X2'*X2))'*.5, 2, 1) + .5*randn(2, N);

という感じに作り,2次の多項式カーネルで境界面作ると,

となる.
同じデータで,ガウスカーネルによって識別すると,

となる.過学習ぎみデスネ.





以上です.
LIBSVMにもMATLAB interfaceがあるようなので,それも試してみる予定.
あと,SVM-KMというのもある.