검색
← 모듈 로드맵

앙상블: 배깅 · 랜덤 포레스트 · 그래디언트 부스팅

강의 · 준전문가 · KAITABASE 정리

부트스트랩 · OOB · 랜덤 포레스트 · AdaBoost · 그래디언트 부스팅을 손계산으로 익히는 앙상블 수업

먼저 알아야 할 것

결정 트리 분할분산과 상관과적합 · 교차 검증경사 하강법

다루는 내용

여러 모델을 모아 강한 모델을 만드는 앙상블을 다룹니다. 평균의 분산 공식으로 상관을 낮추는 것이 왜 중요한지 보이고, 부트스트랩에서 빠지는 비율(약 36.8%)과 OOB 평가, 랜덤 포레스트의 특성 무작위 선택을 설명합니다. AdaBoost 가중치 갱신과 제곱 오차 그래디언트 부스팅의 잔차 적합을 두 단계 손계산하고, scikit-learn 기본값과 배깅 · 부스팅 선택 기준을 정리합니다.

해설

앙상블: 배깅 · 랜덤 포레스트 · 그래디언트 부스팅

결정 트리 하나는 데이터가 조금만 바뀌어도 모양이 크게 달라지는 불안정한 모델입니다. 여러 모델을 모아 평균 내거나(배깅 · 랜덤 포레스트), 앞 모델이 틀린 부분을 다음 모델이 고치게 하면(부스팅) 훨씬 강한 모델이 됩니다. 이 수업은 부트스트랩 · OOB 평가 · 특성 무작위 선택 · AdaBoost 가중치 갱신 · 잔차를 맞추는 그래디언트 부스팅을 작은 숫자로 직접 계산합니다.

항목내용
참고 자료scikit-learn 사용자 안내서 'Ensembles: Gradient boosting, random forests, bagging, voting, stacking' · scikit-learn.org/stable/modules/ensemble.html
트랙 · 수준머신러닝 · 중급
이 수업의 범위앙상블의 원리, 부트스트랩 · 배깅, OOB 추정, 랜덤 포레스트, 특성 중요도, AdaBoost, 그래디언트 부스팅(잔차 적합), 학습률 · 트리 수, 히스토그램 기반 부스팅
먼저 알면 좋은 것결정 트리의 분할(지니 · 제곱 오차), 분산의 뜻, 과적합 · 교차 검증

들어가기 전에

한눈에 보기

  • 여러 모델을 평균 내면 왜 분산이 줄어드는지, 모델끼리 상관이 높으면 왜 효과가 줄어드는지 식으로 압니다.
  • 부트스트랩 표본에 한 번도 뽑히지 않는 데이터가 약 36.8%라는 것과, 그 데이터로 하는 OOB 평가를 이해합니다.
  • 랜덤 포레스트가 배깅에 "분할마다 특성 일부만 보기"를 더한 이유를 압니다.
  • AdaBoost의 가중치 갱신과 그래디언트 부스팅의 잔차 맞추기를 손으로 두 단계 계산합니다.
  • 배깅 계열과 부스팅 계열을 언제 고르는지, 어떤 하이퍼파라미터부터 만지는지 압니다.

1. 앙상블은 왜 강한가

직관(해설용 비유): 퀴즈 대회에서 한 사람에게 답을 맡기는 것보다, 서로 다른 공부를 한 열 명의 답을 다수결로 정하는 편이 대체로 더 잘 맞습니다. 단, 열 명이 모두 같은 참고서로 공부했다면 같은 문제를 같이 틀리므로 이득이 적습니다. 앙상블(ensemble)의 핵심은 "각자 쓸 만하면서 서로 다르게 틀리는" 모델을 모으는 것입니다.

앙상블은 크게 두 갈래입니다.

갈래만드는 법주로 줄이는 것대표
평균 계열(averaging)서로 독립적으로 여러 모델을 학습해 평균 · 다수결분산배깅, 랜덤 포레스트
부스팅 계열(boosting)모델을 차례로 만들며 앞 모델의 약점을 보완편향(그리고 분산도 일부)AdaBoost, 그래디언트 부스팅

평균 내면 분산이 얼마나 줄어드나

모델 BB개의 예측 f1,…,fBf_1, \dots, f_B가 각각 분산 σ2\sigma^2을 갖고, 서로 짝마다 상관계수가 ρ\rho라고 합시다. 평균 fˉ=1B∑bfb\bar f = \frac{1}{B}\sum_b f_b의 분산은 다음과 같습니다.

Var⁡(fˉ)=ρ σ2+1−ρB σ2\operatorname{Var}(\bar f) = \rho\,\sigma^2 + \frac{1-\rho}{B}\,\sigma^2
기호뜻
BB모은 모델 수
σ2\sigma^2모델 하나의 예측 분산
ρ\rho두 모델 예측 사이의 상관계수(0이면 서로 무관, 1이면 똑같음)

유도는 간단합니다. Var⁡(∑bfb)=Bσ2+B(B−1)ρσ2\operatorname{Var}(\sum_b f_b) = B\sigma^2 + B(B-1)\rho\sigma^2을 B2B^2으로 나누면 위 식이 됩니다.

숫자로 해 보기(해설용): σ2=1\sigma^2 = 1, ρ=0.3\rho = 0.3, B=10B = 10이면 0.3+0.7/10=0.370.3 + 0.7/10 = 0.37입니다. 모델을 아무리 늘려도(B→∞B \to \infty) 분산은 ρσ2=0.3\rho\sigma^2 = 0.3 아래로 내려가지 않습니다. 같은 조건을 난수 20만 번으로 모의실험해 보면 약 0.370이 나옵니다(파이썬으로 확인). 그래서 앙상블에서는 모델 수를 늘리는 것만큼 상관 ρ\rho를 낮추는 것이 중요합니다. 랜덤 포레스트의 특성 무작위 선택이 바로 ρ\rho를 낮추는 장치입니다.

2. 부트스트랩과 배깅

직관: 데이터는 한 벌뿐인데 서로 다른 모델을 여러 개 만들고 싶습니다. 그래서 원래 데이터에서 복원 추출(sampling with replacement)로 같은 크기의 표본을 여러 벌 만듭니다. 이것이 부트스트랩(bootstrap) 표본이고, 각 표본으로 모델을 하나씩 학습해 평균 · 다수결하는 방법이 배깅(bagging, bootstrap aggregating)입니다. Breiman이 1996년에 제안했습니다.

단계

  1. 데이터 nn개에서 복원 추출로 nn개를 뽑아 부트스트랩 표본을 만듭니다(같은 예제가 여러 번 뽑힐 수 있음).
  2. 표본마다 깊은 결정 트리를 하나씩 학습합니다.
  3. 회귀는 예측을 평균, 분류는 다수결(또는 확률 평균)로 합칩니다.

한 번도 안 뽑힐 확률: 특정 예제가 한 번 뽑기에서 빠질 확률은 1−1/n1 - 1/n이고, nn번 모두 빠질 확률은

(1−1n)n  →  n→∞    e−1≈0.368\left(1 - \frac{1}{n}\right)^n \;\xrightarrow{\;n\to\infty\;}\; e^{-1} \approx 0.368

숫자로 해 보기(해설용): n=10n = 10이면 0.910≈0.3490.9^{10} \approx 0.349, n=100n = 100이면 약 0.3660.366, n=1000n = 1000이면 약 0.3680.368입니다. 즉 부트스트랩 표본 하나에는 원래 데이터의 약 63.2%만 (중복 포함해) 들어가고, 약 36.8%는 빠집니다.

OOB 추정: 공짜 검증 데이터

빠진 약 36.8%를 OOB(out-of-bag) 표본이라고 합니다. 각 예제에 대해 "그 예제를 학습에 안 쓴 트리들"만 모아 예측하면, 별도 검증 데이터 없이도 일반화 성능을 어림할 수 있습니다. scikit-learn에서는 oob_score=True로 켭니다. 다만 scikit-learn 안내서는 그래디언트 부스팅의 OOB 개선량 추정은 대체로 비관적이므로, 시간이 허락하면 교차 검증을 쓰라고 권합니다.

왜 깊은 트리를 쓰나: 배깅은 분산을 줄이는 방법이므로, 편향은 작고 분산이 큰 모델(가지치기 안 한 깊은 트리)과 궁합이 좋습니다. 원래 안정적인 모델(선형 회귀 등)은 배깅해도 얻는 것이 적습니다.

3. 랜덤 포레스트

직관: 배깅한 트리들은 여전히 비슷합니다. 아주 강한 특성 하나가 있으면 모든 트리가 맨 위에서 그 특성으로 나누기 때문입니다. 랜덤 포레스트(random forest, Breiman 2001)는 분할할 때마다 특성 중 일부만 무작위로 골라 그 안에서 최선의 분할을 찾게 해, 트리끼리의 상관 ρ\rho를 낮춥니다.

단계

  1. 부트스트랩 표본을 만듭니다(배깅과 같음).
  2. 트리를 키우면서, 노드마다 전체 특성 pp개 중 mm개를 무작위로 고르고 그 mm개 안에서만 분할을 찾습니다.
  3. 트리를 많이 만들어 합칩니다.
설정(scikit-learn 1.9 기본값)분류기 RandomForestClassifier회귀기 RandomForestRegressor
트리 수 n_estimators100100
분할마다 볼 특성 max_features"sqrt" (m=pm = \sqrt{p})1.0 (전부)
부트스트랩 bootstrapTrueTrue
깊이 max_depthNone(끝까지)None

숫자로 해 보기(해설용): 특성이 16개인 분류 문제라면 노드마다 16=4\sqrt{16} = 4개만 보고 분할합니다. 가장 강한 특성이 후보 4개 안에 들어올 확률은 4/16=25%4/16 = 25\%이므로, 나머지 75%의 노드에서는 다른 특성이 기회를 얻습니다.

구현의 작은 차이: scikit-learn은 원 논문처럼 트리마다 한 표씩 투표하는 대신, 트리들의 확률 예측을 평균해 분류합니다(사용자 안내서에 명시).

특성 중요도: 랜덤 포레스트는 각 특성이 분할로 줄인 불순도의 합(MDI)으로 중요도를 줍니다. 값이 많은(연속형 · 고유값 많은) 특성을 과대평가하는 경향이 있으므로, 검증 데이터에서 특성 값을 섞어 성능 하락을 재는 순열 중요도(permutation importance)와 함께 보는 것이 안전합니다.

4. 부스팅 1: AdaBoost

직관: 오답 노트 공부법입니다. 첫 모델이 틀린 문제에 별표를 크게 치고, 다음 모델은 별표 친 문제를 더 신경 써서 공부합니다. AdaBoost(Freund · Schapire, 1995년 학회 발표 · 1997년 학술지)는 틀린 예제의 가중치를 올려 가며 약한 분류기(보통 깊이 1짜리 트리, 스텀프 stump)를 차례로 학습합니다.

단계(이진 분류, 원래 형태를 설명용으로 단순화)

  1. 모든 예제 가중치를 wi=1/nw_i = 1/n으로 시작합니다.
  2. 가중치를 반영해 약한 분류기 hth_t를 학습하고, 가중 오류율 εt\varepsilon_t를 잽니다.
  3. 분류기의 발언권 αt\alpha_t를 계산합니다.
  4. 틀린 예제 가중치는 eαte^{\alpha_t}배, 맞힌 예제는 e−αte^{-\alpha_t}배 한 뒤 합이 1이 되게 나눕니다.
  5. 최종 예측은 sign⁡(∑tαtht(x))\operatorname{sign}\big(\sum_t \alpha_t h_t(x)\big)입니다.
αt=12ln⁡1−εtεt\alpha_t = \frac{1}{2}\ln\frac{1-\varepsilon_t}{\varepsilon_t}
기호뜻
εt\varepsilon_ttt번째 분류기의 가중 오류율(0.5보다 작아야 쓸모 있음)
αt\alpha_t그 분류기의 발언권. 오류가 작을수록 큼
ht(x)∈{−1,+1}h_t(x) \in \{-1, +1\}tt번째 약한 분류기의 예측

숫자로 해 보기(해설용): 예제 5개, 처음 가중치는 모두 0.2입니다. 첫 스텀프가 1개를 틀려 ε1=0.2\varepsilon_1 = 0.2라면 α1=12ln⁡4≈0.693\alpha_1 = \frac12 \ln 4 \approx 0.693, eα1=2e^{\alpha_1} = 2, e−α1=0.5e^{-\alpha_1} = 0.5입니다.

틀린 예제 1개맞힌 예제 4개
갱신 전0.20.2씩
곱한 뒤0.2×2=0.40.2 \times 2 = 0.40.2×0.5=0.10.2 \times 0.5 = 0.1씩
정규화(합 0.8로 나눔)0.50.125씩

틀린 예제 하나가 전체 가중치의 절반을 차지하게 됐습니다. 다음 스텀프는 이 예제를 맞히는 쪽으로 학습됩니다. scikit-learn의 AdaBoostClassifier는 다중 클래스로 넓힌 SAMME 방식(Zhu 외 2009)을 쓰며, 기본 약한 분류기는 깊이 1 트리, 기본 n_estimators는 50입니다.

5. 부스팅 2: 그래디언트 부스팅

직관: 골프에 비유하면, 첫 샷으로 홀 근처까지 보내고, 다음 샷은 "남은 거리"만큼 치고, 그다음도 남은 거리만큼 칩니다. 그래디언트 부스팅(gradient boosting, Friedman 2001)은 지금까지 모델이 못 맞힌 부분(잔차)을 다음 트리가 맞히게 해 예측을 조금씩 더해 갑니다.

단계(제곱 오차 회귀)

  1. 상수 예측 F0(x)=yˉF_0(x) = \bar y(평균)으로 시작합니다.
  2. m=1,2,…,Mm = 1, 2, \dots, M마다 잔차 ri=yi−Fm−1(xi)r_i = y_i - F_{m-1}(x_i)를 계산합니다.
  3. 잔차를 맞히는 작은 트리 hmh_m을 학습합니다.
  4. Fm(x)=Fm−1(x)+η hm(x)F_m(x) = F_{m-1}(x) + \eta\, h_m(x)로 조금만 더합니다.
Fm(x)=Fm−1(x)+η hm(x),ri(m)=−∂L(yi,F)∂F∣F=Fm−1(xi)F_m(x) = F_{m-1}(x) + \eta\, h_m(x), \qquad r_i^{(m)} = -\frac{\partial L(y_i, F)}{\partial F}\Big|_{F = F_{m-1}(x_i)}
기호뜻
FmF_mmm번째 단계까지 더한 전체 모델
hmh_mmm번째로 학습한 작은 트리
η\eta학습률(shrinkage). 한 걸음을 얼마나 작게 걸을지
LL손실 함수. 제곱 오차 L=12(y−F)2L = \frac12(y - F)^2이면 음의 기울기가 바로 잔차 y−Fy - F

"그래디언트"라는 이름은 둘째 식에서 옵니다. 다음 트리가 맞히는 대상은 손실의 음의 기울기이고, 제곱 오차에서는 그것이 잔차와 같습니다. 로그 손실 · 절대 오차 등 다른 손실도 같은 틀로 쓸 수 있다는 것이 이 방법의 힘입니다.

숫자로 해 보기(해설용, 파이썬으로 확인): x=(1,2,3,4)x = (1, 2, 3, 4), y=(1,3,2,6)y = (1, 3, 2, 6), 깊이 1 트리, η=0.5\eta = 0.5입니다.

  • 시작: F0=3F_0 = 3(평균). 평균 제곱 오차(MSE) =(4+0+1+9)/4=3.5= (4 + 0 + 1 + 9)/4 = 3.5.
  • 1단계 잔차: (−2,0,−1,3)(-2, 0, -1, 3). 가능한 분할 세 곳의 잔차 제곱합을 비교하면 x<1.5x < 1.5: 8.67, x<2.5x < 2.5: 10.0, x<3.5x < 3.5: 2.0 → x<3.5x < 3.5로 나눕니다. 왼쪽 평균 −1-1, 오른쪽 평균 33.
  • 갱신: F1=3+0.5×(−1,−1,−1,3)=(2.5,2.5,2.5,4.5)F_1 = 3 + 0.5 \times (-1, -1, -1, 3) = (2.5, 2.5, 2.5, 4.5). MSE =1.25= 1.25.
  • 2단계 잔차: (−1.5,0.5,−0.5,1.5)(-1.5, 0.5, -0.5, 1.5). 이번에는 x<1.5x < 1.5와 x<3.5x < 3.5가 둘 다 잔차 제곱합 2.0으로 동점이라 앞의 것(x<1.5x<1.5; 왼쪽 −1.5-1.5, 오른쪽 0.50.5)을 고릅니다.
  • 갱신: F2=(1.75,2.75,2.75,4.75)F_2 = (1.75, 2.75, 2.75, 4.75). MSE =0.6875= 0.6875.

단계마다 오차가 줄어드는 모습이 보입니다. η=1\eta = 1이면 더 빨리 줄지만, 실제로는 작은 η\eta와 많은 트리를 쓰는 편이 새 데이터에서 더 잘 맞는 경우가 많습니다.

mermaid
flowchart LR
  A[학습 데이터] --> B[F0 = 평균]
  B --> C[잔차 = 정답 - 현재 예측]
  C --> D[잔차를 맞히는 작은 트리 학습]
  D --> E[예측에 학습률 x 트리 출력 더하기]
  E --> F{트리 수 M 도달 또는 검증 손실 정체?}
  F -- 아니오 --> C
  F -- 예 --> G[최종 모델 = F0 + 트리들의 합]

하이퍼파라미터와 실무 선택

설정(scikit-learn 1.9 GradientBoostingClassifier 기본값)값의미
learning_rate0.1작을수록 트리가 더 많이 필요하지만 일반화가 좋아지는 경향
n_estimators100트리 수. 학습률과 맞바꾸는 관계
max_depth3트리 하나의 깊이. 특성 간 상호작용 차수를 정함
subsample1.01보다 작으면 단계마다 일부 데이터만 사용(확률적 그래디언트 부스팅, Friedman 2002)

scikit-learn 안내서는 표본이 수만 개를 넘으면 특성 값을 구간(bin)으로 묶어 계산하는 HistGradientBoosting...이 기존 구현보다 몇 자릿수 빠를 수 있다고 설명합니다. 이 방식은 LightGBM에서 영감을 받았고, 정규화 항을 목적 함수에 넣는 방식은 다음 항목인 XGBoost와 닮았습니다.

왜 부스팅에는 얕은 트리를 쓰나: 부스팅은 편향을 줄여 가는 방법이므로, 부품 하나는 단순(편향 큼 · 분산 작음)해도 됩니다. 깊은 트리를 쓰면 몇 단계 만에 학습 데이터를 외워 과적합하기 쉽습니다. 배깅과 정반대의 선택입니다.

6. 배깅이냐 부스팅이냐

상황먼저 고려할 것이유
빠르게 튼튼한 기준선이 필요랜덤 포레스트기본값으로도 잘 작동, 튜닝이 적음, 병렬 학습
표 형태 데이터에서 최고 성능그래디언트 부스팅(HistGB · XGBoost · LightGBM)편향을 끝까지 줄임, 대회 · 실무에서 강함
잡음 · 이상한 라벨이 많음랜덤 포레스트부스팅은 틀린 예제에 집중하므로 잘못된 라벨에 휘둘리기 쉬움
학습 시간이 짧아야 함랜덤 포레스트(병렬) 또는 HistGB고전 그래디언트 부스팅은 순차 학습이라 느림

연습 문제

1. 데이터가 5개일 때, 특정 예제가 부트스트랩 표본에서 한 번도 뽑히지 않을 확률은?

풀이: (1−1/5)5=0.85=0.32768(1 - 1/5)^5 = 0.8^5 = 0.32768, 약 32.8%입니다.

2. 모델 분산 σ2=4\sigma^2 = 4, 상관 ρ=0.5\rho = 0.5일 때, 모델 4개 평균과 무한히 많은 모델 평균의 분산은?

풀이: 0.5×4+0.5×4/4=2+0.5=2.50.5 \times 4 + 0.5 \times 4 / 4 = 2 + 0.5 = 2.5, 무한대에서는 ρσ2=2\rho\sigma^2 = 2입니다.

3. AdaBoost에서 가중 오류율이 0.1이면 α\alpha는? 오류율 0.5이면?

풀이: 12ln⁡9≈1.099\frac12 \ln 9 \approx 1.099. 0.5이면 12ln⁡1=0\frac12 \ln 1 = 0으로 동전 던지기 수준이라 발언권이 없습니다.

4. 5절 예에서 η=1\eta = 1로 1단계만 하면 F1F_1과 MSE는?

풀이: F1=3+(−1,−1,−1,3)=(2,2,2,6)F_1 = 3 + (-1, -1, -1, 3) = (2, 2, 2, 6). 오차 (−1,1,0,0)(-1, 1, 0, 0) → MSE =2/4=0.5= 2/4 = 0.5입니다. 한 걸음에 크게 줄지만 그만큼 학습 데이터에 빠르게 맞춰집니다.

스스로 점검

  1. 랜덤 포레스트가 배깅보다 좋아지는 핵심 이유는?
    • 답: 분할마다 특성 일부만 보게 해 트리끼리의 상관 ρ\rho를 낮추기 때문입니다. 평균의 분산 하한 ρσ2\rho\sigma^2가 내려갑니다.
  2. 그래디언트 부스팅의 "그래디언트"는 무엇의 기울기인가요?
    • 답: 현재 예측 FF에 대한 손실의 기울기입니다. 다음 트리는 그 음의 기울기(제곱 오차에서는 잔차)를 맞힙니다.
  3. 배깅에는 깊은 트리, 부스팅에는 얕은 트리를 쓰는 이유는?
    • 답: 배깅은 분산을 줄이므로 분산이 큰 깊은 트리가 알맞고, 부스팅은 편향을 차례로 줄이므로 단순한 트리를 많이 더하는 편이 과적합을 막습니다.

용어 정리

용어쉬운 뜻
앙상블(ensemble)여러 모델의 예측을 합쳐 하나로 쓰는 방법
부트스트랩(bootstrap)원래 데이터에서 복원 추출로 같은 크기 표본을 만드는 것
배깅(bagging)부트스트랩 표본마다 모델을 학습해 평균 · 다수결
OOB 표본(out-of-bag)어떤 부트스트랩 표본에 뽑히지 않은 데이터, 공짜 검증용
랜덤 포레스트(random forest)배깅 + 분할마다 특성 일부만 무작위로 보는 트리 앙상블
순열 중요도(permutation importance)특성 값을 섞었을 때 성능이 얼마나 떨어지는지로 잰 중요도
부스팅(boosting)앞 모델의 약점을 다음 모델이 보완하도록 차례로 쌓는 방법
스텀프(stump)깊이 1짜리 결정 트리
AdaBoost틀린 예제 가중치를 올려 가며 약한 분류기를 쌓는 부스팅
잔차(residual)정답에서 현재 예측을 뺀 값
그래디언트 부스팅(gradient boosting)손실의 음의 기울기를 맞히는 트리를 더해 가는 부스팅
학습률 · 축소(learning rate · shrinkage)새 트리 출력을 얼마나 작게 더할지 정하는 계수

더 깊이 보려면

scikit-learn 사용자 안내서의 앙상블 장은 그래디언트 부스팅(히스토그램 기반 포함) · 랜덤 포레스트와 Extra-Trees · 배깅 · 투표 · 스태킹 · AdaBoost 순으로 구성되어 있고, 조기 종료 · 단조 제약 · 범주형 특성 처리 같은 실무 옵션을 자세히 다룹니다. 그래디언트 부스팅의 정규화 목적 함수와 분할 점수 공식은 다음 항목인 XGBoost 해설에서 이어집니다.

다음에 볼 것

라이선스

이 글은 원문 번역이 아니라 KAITABASE가 직접 쓴 해설이며, 숫자 예는 모두 설명용으로 만든 것입니다(파이썬으로 다시 계산해 확인). 참고 자료인 scikit-learn 문서는 BSD-3-Clause 라이선스입니다.

출처

  • 참고: scikit-learn.org/stable/modules/ensemble.… (scikit-learn 사용자 안내서 앙상블 장 — Breiman 1996 · 2001, Freund · Schapire, Friedman 2001 · 2002, LightGBM 인용, OOB · 확률 평균 · HistGB 설명)
  • 기본값 확인: scikit-learn 1.9.0 RandomForestClassifier · RandomForestRegressor · AdaBoostClassifier · GradientBoostingClassifier 생성자 서명과 문서 문자열(SAMME, Zhu 외 2009 인용)

AI 원리부터 이해하기 · 머신러닝 모듈

머신러닝 모듈 학습

이 글은 머신러닝 모듈의 학습 자료입니다. 전체 17개 자료 중 8번째입니다.

AI 기초 과정은 현재 진도 저장을 지원하지 않습니다.

출처·원문

원문 보기

요약만

본문 자체 작성 · 참고 자료 scikit-learn 사용자 안내서(BSD-3)