北太天元应用案例分享:机器学习预测心脏病风险

标签: 软件版本更新

社区小助手 2024-07-26 17:38:40

卢朓老师《数值方法:原理、算法及应用》课堂 学生大作业~

供大家参考学习~


相关代码和结果如下:

%The accuracy of the SVM model is 81.11%

修改之后的代码可以在北太天元上运行, 所有的代码如下:

  1. load_plugin("optimization");
  2. % 导入数据
  3. data = readmatrix('heart disease.csv');
  4. % 检查并处理缺失值
  5. if any(any(ismissing(data)))
  6. data = rmmissing(data);
  7. end
  8. % 分离特征和标签
  9. X = data(:,1:end-1);
  10. y = data(:,end);
  11. % 主成分分析降维
  12. [coeff,score,latent] = pca(X);
  13. X_pca = score(:,1:2);
  14. % 划分训练集和测试集
  15. cv = my_cvpartition(size(X_pca,1),'HoldOut',0.3);
  16. idx = cv.test;
  17. train_idx = setdiff(1: size(X_pca,1), idx);
  18. X_train = X_pca(train_idx,:);
  19. Y_train = y(train_idx,:);
  20. X_test = X_pca(idx,:);
  21. Y_test = y(idx,:);
  22. % 训练SVM模型
  23. C = 1e2; % 设置较大的C值以确保硬间隔分类(对于线性可分数据)
  24. SVMModel = my_fitcecoc(X_train,Y_train,C);
  25. % 预测测试集
  26. [label,score] = predict(SVMModel,X_test);
  27. % 计算准确率
  28. accuracy = sum(label-1 == Y_test) / length(Y_test);
  29. % 可视化
  30. gscatter(X_pca(:,1),X_pca(:,2),y);
  31. hold on;
  32. % 绘制决策边界c
  33. d = 0.15;
  34. [x1Grid,x2Grid] = meshgrid(min(X_pca(:,1)):d:max(X_pca(:,1)),...
  35. min(X_pca(:,2)):d:max(X_pca(:,2)));
  36. xGrid = [x1Grid(:),x2Grid(:)];
  37. [~,scores] = predict(SVMModel,xGrid);
  38. contour(x1Grid,x2Grid,reshape(scores(:,2),size(x1Grid)),[0 0],'k');
  39. % 输出准确率
  40. fprintf('The accuracy of the SVM model is %.2f%%\n', accuracy * 100);
  41. hold off;
  42. function [coeff, score, latent] = pca(X)
  43. % X: 数据矩阵,每一列是一个特征,每一行是一个样本
  44. % coeff: 主成分系数
  45. % score: 表示主成分得分
  46. % latent: 主成分对应的特征值
  47. % 标准化数据(均值为0,方差为1)
  48. X = (X - mean(X)) ./ std(X);
  49. % 计算协方差矩阵
  50. CovMat = cov(X);
  51. % 对协方差矩阵进行特征值分解
  52. [V, D] = eig(CovMat);
  53. % 将特征值按降序排序,并获取对应的特征向量
  54. [latent, order] = sort(diag(D), 'descend');
  55. coeff = V(:, order);
  56. % 计算主成分得分
  57. score = X * coeff;
  58. end
  59. function cv = my_cvpartition(n, method, param)
  60. % n: 总样本数
  61. % method: 分区方法,目前仅支持 'HoldOut'
  62. % param: 分区方法的参数,对于 'HoldOut',该参数为留出的比例
  63. % 初始化输出结构体
  64. cv = struct('train', [], 'test', []);
  65. if strcmp(method, 'HoldOut')
  66. % 生成一个从 1 到 n 的整数数组
  67. idx = 1:n;
  68. % 随机打乱 idx 数组
  69. idx = idx(randperm(n));
  70. % 计算留出样本的数量
  71. numHoldOut = floor(n * param);
  72. % 分配训练和测试索引
  73. cv.test = idx(1:numHoldOut);
  74. cv.train = idx(numHoldOut+1:end);
  75. else
  76. error('Unsupported partition method.');
  77. end
  78. end
  79. function SVMModel = my_fitcecoc(X_train, Y_train, C)
  80. % X_train: 特征矩阵 (n x d),其中 n 是样本数,d 是特征维度
  81. % Y_train: 标签向量 (n x 1)
  82. % C: 正则化参数,控制对错分样本的惩罚程度
  83. % 获取类别数量
  84. uniqueClasses = unique(Y_train);
  85. numClasses = length(uniqueClasses);
  86. % 初始化 SVM 模型结构体数组
  87. SVMModel = struct('Classifiers', cell(numClasses*(numClasses-1)/2, 1), ...
  88. 'ClassPairs', []);
  89. % 训练一对一 SVM 分类器
  90. classifierIndex = 1;
  91. for i = 1:numClasses
  92. for j = (i+1):numClasses
  93. % 提取当前类别对的训练数据
  94. classI = Y_train == uniqueClasses(i);
  95. classJ = Y_train == uniqueClasses(j);
  96. X_train_ij = [X_train(classI, :); X_train(classJ, :)];
  97. Y_train_ij = [ones(sum(classI), 1); -1*ones(sum(classJ), 1)];
  98. % 训练 SVM 分类器
  99. SVMModel.Classifiers{classifierIndex} = my_fitcsvm_soft(X_train_ij, Y_train_ij, C);
  100. % 记录当前分类器对应的类别对
  101. SVMModel.ClassPairs(classifierIndex, :) = [uniqueClasses(i), uniqueClasses(j)];
  102. classifierIndex = classifierIndex + 1;
  103. end
  104. end
  105. end
  106. function [wb] = my_fitcsvm(X, Y)
  107. % X: 特征矩阵 (n x d),其中 n 是样本数,d 是特征维度
  108. % Y: 标签向量 (n x 1),取值为 +1 或 -1
  109. load_plugin("optimization");
  110. % 假设X和Y已经定义,如之前的示例
  111. N = size(X, 1); % 数据点的数量
  112. D = size(X,2); % 数据的维度
  113. % 将w和b组合成一个向量,以便使用quadprog
  114. % 注意:这里我们将w放在前面,b放在最后
  115. p = rand(D + 1, 1); % 初始猜测解(通常设为0)
  116. Aeq = []; % 没有等式约束
  117. beq = [];
  118. % 构造二次规划的目标函数和线性不等式约束
  119. H = eye(D+1); % Hessian矩阵(目标函数的二次项系数)
  120. f = zeros(D+1,1); % 目标函数的一次项系数(对于SVM原始问题,通常为负)
  121. % 线性不等式约束 Ax <= b
  122. % 对于每个数据点 (x_i, y_i),我们有 y_i * (w' * x_i + b) >= 1
  123. A = [-Y.*X, -Y]; % 不等式约束的系数矩阵
  124. b = -ones(N, 1); % 不等式约束的右侧向量
  125. % 使用quadprog求解二次规划问题
  126. options = optimoptions('quadprog','Algorithm','interior-point');
  127. [w_b, fval, exitflag, output] = quadprog(H, f, A, b, Aeq, beq, [], [], p, options);
  128. % 分离出w和b
  129. w = w_b(1:D);
  130. b = w_b(D+1);
  131. wb = struct('w',w,'b',b);
  132. end
  133. function [wb] = my_fitcsvm_soft(X, Y, C)
  134. % X: 特征矩阵 (n x d),其中 n 是样本数,d 是特征维度
  135. % Y: 标签向量 (n x 1),取值为 +1 或 -1
  136. % C: 正则化参数,控制对错分样本的惩罚程度
  137. % 假设X和Y已经定义,如之前的示例
  138. N = size(X, 1); % 数据点的数量
  139. D = size(X, 2); % 数据的维度
  140. % 初始化松弛变量(slack variables)
  141. xi = zeros(N, 1);
  142. % 将w和b以及松弛变量组合成一个向量,以便使用quadprog
  143. p = zeros(D + 1 + N, 1); % 初始猜测解
  144. % 构造二次规划的目标函数和线性不等式约束
  145. H = [diag( [ones(1,D),0] ), zeros(D+1, N); zeros(N, D+1), zeros(N,N)]; % Hessian矩阵
  146. f = [zeros(D+1, 1); C*ones(N, 1)]; % 目标函数的一次项系数
  147. % 线性不等式约束 Ax <= b
  148. % 对于每个数据点 (x_i, y_i),我们有 y_i * (w' * x_i + b) >= 1 - xi_i
  149. A = [-Y.*X, -Y, diag( -ones(1,N) )]; % 不等式约束的系数矩阵
  150. b = -ones(N, 1); % 不等式约束的右侧向量
  151. Aeq = []; % 没有等式约束
  152. beq = [];
  153. lb = [ -inf*ones(D+1,1); zeros(N,1)];
  154. ub = inf*ones(D+1+N,1);
  155. % 使用quadprog求解二次规划问题
  156. options = optimoptions('quadprog','Algorithm','interior-point');
  157. [w_b_xi, fval, exitflag, output] = quadprog(H, f, A, b, Aeq, beq, lb, ub, p, options)
  158. % 分离出w, b和xi
  159. w = w_b_xi(1:D);
  160. b = w_b_xi(D+1);
  161. xi = w_b_xi(D+2:end);
  162. % 构造并返回结构体wb,包含w和b
  163. wb = struct('w', w, 'b', b);
  164. end
  165. function [label, score] = predict(SVMModel, X_test)
  166. % SVMModel: 训练好的多类SVM模型,由my_fitcecoc函数返回
  167. % X_test: 测试集特征数据
  168. % label: 预测的类别标签
  169. % score: 预测的得分(可选,这里简化为投票得分)
  170. % 初始化预测标签和得分
  171. [numTest, ~] = size(X_test);
  172. numClasses = length(SVMModel.Classifiers);
  173. label = zeros(numTest, 1);
  174. score = zeros(numTest, numClasses);
  175. % 对每个测试样本进行预测
  176. for i = 1:numTest
  177. sample = X_test(i, :);
  178. votes = zeros(1, numClasses+1);
  179. % 使用每个SVM分类器进行预测,并进行投票
  180. for j = 1:numClasses
  181. svmModel = SVMModel.Classifiers{j};
  182. [~, ~, ~, output] = predictOneVsAll(svmModel, sample);
  183. if output == 1
  184. classIdx1 = SVMModel.ClassPairs(j, 1)+1;
  185. votes(classIdx1) = votes(classIdx1) + 1;
  186. else
  187. classIdx2 = SVMModel.ClassPairs(j, 2)+1;
  188. votes(classIdx2) = votes(classIdx2) + 1;
  189. end
  190. end
  191. % 找到得票最多的类别作为预测标签
  192. [~, maxVoteIdx] = max(votes);
  193. label(i) = maxVoteIdx;
  194. score(i, maxVoteIdx) = votes(maxVoteIdx); % 将最高票数作为得分
  195. end
  196. end
  197. function [predictedLabel, predictedScore, decisionValues, output] = predictOneVsAll(svmModel, sample)
  198. % 使用单个SVM模型进行预测
  199. % 这里简化为直接计算决策函数的值,实际应用中可能需要更复杂的处理
  200. w = svmModel.w;
  201. b = svmModel.b;
  202. decisionValues = dot(w, sample) + b;
  203. if decisionValues >= 0
  204. predictedLabel = 1; % 属于正类
  205. else
  206. predictedLabel = -1; % 属于负类
  207. end
  208. predictedScore = abs(decisionValues); % 决策函数的绝对值作为得分
  209. output = predictedLabel; % 用于投票的输出
  210. end

---

软间隔SVM通过引入松弛变量(slack variables)$\xi_i$来允许一些样本被错误分类,从而处理非线性可分的数据。这些松弛变量衡量了每个样本违反约束条件的程度。软间隔SVM的优化问题可以写成以下形式:

  1. $$\min_{\mathbf{w}, b, \xi} \frac{1}{2} ||\mathbf{w}||^2 + C \sum_{i=1}^{m} \xi_i$$

约束条件为:

  1. $$y_i(\mathbf{w}^T \mathbf{x}_i + b) \geq 1 - \xi_i, \quad i = 1, \ldots, m$$
  2. $$\xi_i \geq 0, \quad i = 1, \ldots, m$$

其中,$\mathbf{w}$是权重向量,$b$是偏置项,$C$是一个正则化参数,用于控制对错分样本的惩罚程度。$\xi_i$是第$i$个样本的松弛变量。

这个优化问题仍然是一个二次规划问题,但目标函数和约束条件都包含了松弛变量。当数据不是完全线性可分时,通过允许一些样本被错误分类(即$\xi_i > 0$),优化问题可以找到一个更好的分类超平面。

现在,我们来解释如何克服可行域为空集的问题。在硬间隔SVM(即不允许任何错误分类的SVM)中,如果数据不是线性可分的,那么约束条件$y_i(\mathbf{w}^T \mathbf{x}_i + b) \geq 1$对于所有样本可能都无法同时满足,导致可行域为空集。

然而,在软间隔SVM中,由于引入了松弛变量$\xi_i$,约束条件变为了$y_i(\mathbf{w}^T \mathbf{x}_i + b) \geq 1 - \xi_i$。这意味着即使某些样本不能满足原始的硬间隔约束(即$y_i(\mathbf{w}^T \mathbf{x}_i + b) \geq 1$),但只要它们满足新的软间隔约束(即$y_i(\mathbf{w}^T \mathbf{x}_i + b) \geq 1 - \xi_i$),并且松弛变量$\xi_i$足够小,那么这些样本就不会对优化问题的解产生太大的影响。

因此,通过引入松弛变量和软间隔约束,软间隔SVM能够处理非线性可分的数据,并找到一个尽可能将数据正确分类的分类超平面。在实际应用中,可以使用二次规划求解器(如SMO算法)来求解这个优化问题。


838 0 0 收藏 回复

回复

回复

段落格式
字号
代码语言
字数统计
重置 提交