當前位置: 首頁>>機器學習>>正文


揭開機器學習的麵紗:SVM 100行代碼實現[Python版]

之前對機器學習的理解,僅僅停留在書本上的推導公式,或者對一些開源工具的使用上。高大上的機器學習究竟如何訓練、怎樣預測的,對我們來說就像是一個黑盒充滿神秘。今天,我們就以經典的機器學習算法SVM為例,拋開各種實際處理上的tricky, 實現一個簡單純潔的SVM,用CODING說話,揭開SVM的神秘麵紗,讓機器學習的過程更加接地氣!

原理就不多說了:hinge損失函數+L2正則化+梯度下降,Python代碼如下svm.py【注意,為了減少代碼量,替身閱讀效率,省掉了很多魯棒性檢測的代碼】:

#!/usr/bin/python
#coding=utf8
import sys;
import random;
import math;
EPS = 0.000000001 # 很小的數字,用於判斷浮點數是否等於0

def load_data(filename, data, dim):
	'''
	輸入數據格式: label\tindex1:value1\tindex2:value2\tindex3:value3..., 其中index是特征的編號, 從1開始
	data的數據格式: [[label, sample],[label, sample], ...],  其中sample: [v0, v1, v2, v3, ..., v[dim]]
	''' 
	for line in open(filename, 'rt'):
		sample = [0.0 for v in range(0, dim + 1)]; 
		line = line.rstrip("\r\n\t ");
		fields = line.split("\t");
		label = int(fields[0]); # LABEL取值: 1 or -1
		sample[0] = 1.0; #sample第一個元素用於存x0特征, 默認置為1.0[方便把 WX+b => WX]
		for field in fields[1:]:
			kv = field.split(":");
			idx = int(kv[0]); # ensure idx >= 1
			val = float(kv[1]);
			sample[idx] = val;
		data.append((label, sample));
	
def svm_train(data4train, dim, W, iterations, lm, lr):
	'''
	目標函數: obj(<X,y>, W) = (對所有<X,y>SUM{max{0, 1 - W*X*y}}) + lm / 2 * ||W||^2, 即:hinge+L2
	'''
	X = [0.0 for v in range(0, dim + 1)]; # <sample, label> => <X, y>
	grad = [0.0 for v in range(0, dim + 1)]; # 梯度
	num_train = len(data4train);
	for i in range(0, iterations):
		#每次迭代隨機選擇一個訓練樣本
		index = random.randint(0, num_train - 1);
		y = data4train[index][0]; # y其實就是label
		for j in range(0, dim + 1):
			X[j] = data4train[index][1][j];# sample的vj
		
		#計算梯度
		#for j in range(0, dim + 1):
		#	grad = lm * W[j];
		WX = 0.0;
		for j in range(0, dim + 1):
			WX += W[j] * X[j];
		if 1 - WX *y > 0:
			for j in range(0, dim + 1):
				grad[j] = lm * W[j] - X[j] * y;
		else:  # 1-WX *y <= 0的時候,目標函數的前半部分恒等於0, 梯度也是0
			for j in range(0, dim + 1):
				grad[j] = lm * W[j] - 0;

		
		#更新權重, lr是學習速率
		for j in range(0, dim + 1):
			W[j] = W[j] - lr * grad[j];

def svm_predict(data4test, dim , W):
	num_test = len(data4test);
	num_correct = 0;
	for i in range(0, num_test):
		target = data4test[i][0]; #即label
		X = data4test[i][1]; # 即sample
		sum = 0.0;
		for j in range(0, dim + 1):
			sum += X[j] * W[j];
		predict = -1;
		#print sum;
		if sum > 0:  #權值>0,認為目標值為1
			predict = 1;
		if predict * target > 0: #預測值和目標值符號相同
			num_correct += 1;
	
	return num_correct * 1.0 / num_test;	

if __name__ == "__main__":
	'''
	主函數:設置參數=>導入數據=>訓練=>輸出結果
	'''
	#設置參數
	epochs = 100;		#迭代輪數
	iterations = 10;	#每一輪中梯度下降迭代次數, 這個其實可以和epochs合並為一個參數
	data4train = []; 	#訓練集, 假設每個樣本的特征數量都一樣
	data4test = []; 	#測試集, 假設每個樣本的特征數量都一樣
	lm = 0.0001; 		#lambda, 對權值做正則化限製的權重
	lr = 0.01;   		#lr, 是學習速率,用於調整訓練收斂的速度
	dim = 1000;		#dim, 特征的最大維度, 所有樣本不同特征總數
	W = [0.0 for v in range(0, dim + 1)]; #權值
	#導入測試集&訓練集
	load_data("train.txt", data4train, dim);
	load_data("test.txt", data4test, dim);
	#訓練, 實際迭代次數=epochs * iterations
	for i in range(0, epochs):
		svm_train(data4train, dim, W, iterations, lm, lr);
		accuracy = svm_predict(data4test, dim, W);
		print "epoch:%d\taccuracy:%f"%(i, accuracy);
	#輸出結果權值	
	for i in range(0, dim + 1):
		if math.fabs(W[i]) > EPS:
			print "%d\t%f"%(i, W[i]);	
	sys.exit(0);

附:手工編的少量測試集和訓練集數據:train test, 非常簡陋的數據,可以簡單用作測試。

運行python svm.py, 運行結果:

1

參考:飛旋的世界SVM的C代碼實現,做了一些修正。

本文由《純淨天空》出品。文章地址: https://vimsky.com/zh-tw/article/222.html,未經允許,請勿轉載。