앙상블 트리 기반 분류기 개요
핵심 개념
여러 개의 약한 의사결정 트리를 결합하여 강력한 예측 모델을 구성하는 방식으로, 대표적인 방법은 다음과 같다.
| 알고리즘 | 전략 | 주요 장점 |
|---|---|---|
| 랜덤 포레스트 | 부트스트랩 샘플링과 특성 무작위 추출로 병렬 트리 생성 | 과적합 억제, 고차원 데이터 처리 우수 |
| 그래디언트 부스팅 트리 (GBDT) | 이전 트리의 오차를 보완하도록 순차적 학습 | 높은 정확도, 유연한 모델링 가능 |
| AdaBoost | 잘못 분류된 샘플에 가중치 조정 | 단순하고 빠름, 노이즈에 민감함 |
핵심 클래스 구현
1. 의사결정 트리 기본 클래스
classdef TreeClassifier < handle
properties
MinLeafSize % 최소 리프 노드 크기
MaxDepth % 최대 트리 깊이
RootNode % 루트 노드 참조
FeatureIndices % 사용된 특성 인덱스
Importance % 특성 중요도
end
methods
function obj = TreeClassifier(minSize, maxDepth)
if nargin < 2 || isempty(maxDepth), maxDepth = 15; end
if nargin < 1 || isempty(minSize), minSize = 1; end
obj.MinLeafSize = minSize;
obj.MaxDepth = maxDepth;
end
function obj = Train(obj, X, y, featIdx)
if nargin < 4 || isempty(featIdx)
featIdx = 1:size(X, 2);
end
obj.FeatureIndices = featIdx;
obj.Importance = zeros(1, length(featIdx));
obj.RootNode = obj.SplitNode(X, y, 0);
end
function node = SplitNode(obj, X, y, depth)
n = size(X, 1); classes = unique(y);
impurity = obj.ComputeGini(y);
% 종료 조건
if depth >= obj.MaxDepth || n <= obj.MinLeafSize || length(classes) == 1
node = LeafNode();
node.Label = mode(y);
node.Prob = histcounts(y, [classes; max(classes)+1]) / n;
return;
end
% 최적 분할 탐색
[feat, val, gain] = obj.BestSplit(X, y, impurity);
if gain <= eps
node = LeafNode();
node.Label = mode(y);
return;
end
leftMask = X(:, feat) <= val;
rightMask = ~leftMask;
node = InternalNode();
node.Feature = feat;
node.Threshold = val;
obj.Importance(feat) = obj.Importance(feat) + gain * n;
node.Left = obj.SplitNode(X(leftMask,:), y(leftMask), depth+1);
node.Right = obj.SplitNode(X(rightMask,:), y(rightMask), depth+1);
end
function [f, v, g] = BestSplit(obj, X, y, baseImp)
[n, d] = size(X); g = 0; f = 0; v = 0;
for j = 1:d
vals = unique(X(:,j));
for k = 1:length(vals)-1
threshold = (vals(k) + vals(k+1)) / 2;
mask = X(:,j) <= threshold;
if sum(mask) < obj.MinLeafSize || sum(~mask) < obj.MinLeafSize
continue;
end
leftImp = obj.ComputeGini(y(mask));
rightImp = obj.ComputeGini(y(~mask));
wLeft = sum(mask)/n; wRight = 1 - wLeft;
reduction = baseImp - (wLeft*leftImp + wRight*rightImp);
if reduction > g
g = reduction; f = j; v = threshold;
end
end
end
end
function score = ComputeGini(~, labels)
cnts = histcounts(labels, [unique(labels); inf]);
probs = cnts / sum(cnts);
score = 1 - sum(probs.^2);
end
function pred = Predict(obj, X)
m = size(X,1); pred = zeros(m,1);
for i = 1:m
node = obj.RootNode;
while isa(node, 'InternalNode')
if X(i, node.Feature) <= node.Threshold
node = node.Left;
else
node = node.Right;
end
end
pred(i) = node.Label;
end
end
end
end
% 노드 클래스들
classdef LeafNode < handle
properties
Label
Prob
end
end
classdef InternalNode < handle
properties
Feature
Threshold
Left
Right
end
end
2. 랜덤 포레스트 구현
classdef RandomForestModel < handle
properties
NumTrees % 트리 수
SubspaceRatio % 특성 샘플링 비율
Trees % 트리 컬렉션
FeatureRank % 전체 중요도
end
methods
function obj = RandomForestModel(nTree, ratio)
if nargin < 2 || isempty(ratio), ratio = 0.7; end
if nargin < 1 || isempty(nTree), nTree = 50; end
obj.NumTrees = nTree;
obj.SubspaceRatio = ratio;
obj.Trees = cell(nTree, 1);
end
function obj = Fit(obj, X, y)
[n, d] = size(X);
numSample = round(d * obj.SubspaceRatio);
fprintf('랜덤 포레스트 훈련 시작 (%d개 트리)\n', obj.NumTrees);
parfor t = 1:obj.NumTrees
idx = randsample(n, n, true); % 부트스트랩
Xb = X(idx, :); yb = y(idx);
feats = randperm(d, numSample); % 특성 서브셋
tree = TreeClassifier(2, 10);
tree.Train(Xb(:,feats), yb, feats);
obj.Trees{t} = tree;
end
obj.ComputeImportance(d);
end
function ComputeImportance(obj, totalFeat)
obj.FeatureRank = zeros(1, totalFeat);
for t = 1:obj.NumTrees
tree = obj.Trees{t};
for k = 1:length(tree.FeatureIndices)
fid = tree.FeatureIndices(k);
obj.FeatureRank(fid) = obj.FeatureRank(fid) + tree.Importance(k);
end
end
obj.FeatureRank = obj.FeatureRank / sum(obj.FeatureRank);
end
function pred = PredictAll(obj, X)
n = size(X,1); votes = zeros(n, obj.NumTrees);
parfor t = 1:obj.NumTrees
votes(:,t) = obj.Trees{t}.Predict(X);
end
pred = mode(votes, 2);
end
function PlotImportance(obj, names)
if nargin < 2
names = compose("특성%d", 1:length(obj.FeatureRank));
end
[~, ord] = sort(obj.FeatureRank, 'descend');
topN = min(15, length(ord));
figure; barh(obj.FeatureRank(ord(1:topN)));
yticklabels(names(ord(1:topN)));
xlabel('중요도 점수'); title('상위 15개 중요 특성');
grid on;
end
end
end
3. 그래디언트 부스팅 트리
classdef GradientBoostingMachine < handle
properties
Estimators % 트리 목록
LearningRate % 스텝 크기
InitScore % 초기 로짓 값
ClassLabels % 범주 집합
end
methods
function obj = GradientBoostingMachine(rate, nEst)
if nargin < 2 || isempty(nEst), nEst = 100; end
if nargin < 1 || isempty(rate), rate = 0.1; end
obj.LearningRate = rate;
obj.Estimators = cell(nEst, 1);
end
function obj = Learn(obj, X, y)
obj.ClassLabels = unique(y)';
K = length(obj.ClassLabels);
N = size(X,1);
Ybin = zeros(N, K);
for k = 1:K
Ybin(:,k) = double(y == obj.ClassLabels(k));
end
obj.InitScore = log(mean(Ybin, 1) ./ (1 - mean(Ybin, 1)));
F = repmat(obj.InitScore, N, 1);
for m = 1:length(obj.Estimators)
P = obj.Softmax(F);
R = Ybin - P;
forest = cell(K,1);
for k = 1:K
regTree = RegressionTree();
regTree.Fit(X, R(:,k));
forest{k} = regTree;
end
obj.Estimators{m} = forest;
for k = 1:K
F(:,k) = F(:,k) + obj.LearningRate * forest{k}.Predict(X);
end
end
end
function p = Softmax(~, logits)
expLogit = exp(logits - max(logits,[],2));
p = expLogit ./ sum(expLogit, 2);
end
function label = PredictLabel(obj, X)
prob = obj.PredictProb(X);
[~, idx] = max(prob, [], 2);
label = obj.ClassLabels(idx);
end
function prob = PredictProb(obj, X)
F = repmat(obj.InitScore, size(X,1), 1);
for m = 1:length(obj.Estimators)
forest = obj.Estimators{m};
for k = 1:length(forest)
F(:,k) = F(:,k) + obj.LearningRate * forest{k}.Predict(X);
end
end
prob = obj.Softmax(F);
end
end
end
4. 성능 평가 및 시각화 예제
function RunEnsembleComparison()
% 샘플 데이터 생성
[X, y] = GenerateSyntheticData();
% 데이터 분할
rng(123);
cv = cvpartition(y, 'Holdout', 0.25);
Xtrain = X(training(cv), :); ytrain = y(training(cv));
Xtest = X(test(cv), :); ytest = y(test(cv));
disp(['데이터: ', num2str(size(X,1)), '개 샘플, ', ...
num2str(size(X,2)), '개 특성']);
% 비교 모델 설정
models = {
struct('Name','랜덤 포레스트',...
'Model',RandomForestModel(80, 0.6)),
struct('Name','부스팅 트리',...
'Model',GradientBoostingMachine(0.1, 100)),
struct('Name','단일 트리',...
'Model',TreeClassifier(5, 8))
};
results = {};
for i = 1:length(models)
mod = models{i};
tic;
if isequal(mod.Name, '단일 트리')
mod.Model.Train(Xtrain, ytrain);
elseif isequal(mod.Name, '랜덤 포레스트')
mod.Model.Fit(Xtrain, ytrain);
else
mod.Model.Learn(Xtrain, ytrain);
end
trainTime = toc;
pred = mod.Model.PredictAll(Xtest);
acc = sum(pred == ytest)/length(ytest);
results{i} = struct('Accuracy',acc,...
'Time',trainTime,...
'Pred',pred,...
'ModelName',mod.Name);
fprintf('%s - 정확도: %.4f, 시간: %.2f초\n', ...
mod.Name, acc, trainTime);
end
VisualizeResults(results, ytest);
end
function [X, y] = GenerateSyntheticData()
rng(42);
n = 800; p = 15; c = 3;
X = [];
y = [];
for k = 1:c
mu = 3*(k-1)*ones(1,p-3);
sigma = diag(ones(1,p-3));
part = mvnrnd(mu, sigma, n/c);
noise = randn(n/c, 3);
block = [part, noise];
X = [X; block];
y = [y; k*ones(n/c,1)];
end
idx = randperm(n);
X = X(idx,:); y = y(idx);
end
function VisualizeResults(res, trueY)
figure('Position',[100,100,1100,600]);
% 정확도 및 시간 비교
subplot(2,3,1); hold on;
bar([res.Accuracy]); title('정확도 비교');
xticklabels({res.ModelName}); ylabel('정확도');
subplot(2,3,2);
bar([res.Time]); title('훈련 시간');
xticklabels({res.ModelName}); ylabel('초');
% 혼동 행렬 (최고 모델)
[~, best] = max([res.Accuracy]);
pred = res(best).Pred;
cm = confusionmat(trueY, pred);
subplot(2,3,3);
imagesc(cm); colorbar;
title([res(best).ModelName, ' 혼동 행렬']);
textLabels = string(unique(trueY));
xticklabels(textLabels); yticklabels(textLabels);
xlabel('예측'); ylabel('실제');
% 특성 중요도 (랜덤 포레스트)
rfModel = find(strcmp({res.ModelName},'랜덤 포레스트'));
if ~isempty(rfModel)
subplot(2,3,4);
importance = res(rfModel).Model.FeatureRank;
[~, idx] = sort(importance, 'descend');
bar(importance(idx(1:12)));
title('특성 중요도 (상위 12)');
xticklabels(compose('F%d', idx(1:12)));
grid on;
end
end
사용법 예시
% 전체 데모 실행
RunEnsembleComparison();
% 개별 모델 사용
rf = RandomForestModel(100, 0.7);
rf.Fit(X_train, y_train);
result = rf.PredictAll(X_test);
% 특성 중요도 시각화
rf.PlotImportance();
주요 기능 요약
- 다양한 앙상블 전략 지원: 배깅, 부스팅
- 병렬 처리를 통한 훈련 가속화 (
parfor) - 상세한 성능 지표 제공 (정확도, 정밀도, 재현율 등)
- 자동화된 시각화 도구 포함
- 확장 가능한 객체 지향 아키텍처
- 실시간 모니터링과 GUI 기반 분석 가능성