終于把機(jī)器學(xué)習(xí)中的交叉驗(yàn)證搞懂了?。?!
大家好,我是小寒
今天給大家分享機(jī)器學(xué)習(xí)中的一個關(guān)鍵概念,交叉驗(yàn)證
交叉驗(yàn)證是機(jī)器學(xué)習(xí)中用于評估模型泛化能力的一種方法,用于衡量模型在訓(xùn)練集之外的新數(shù)據(jù)上的表現(xiàn)。
它的核心思想是將數(shù)據(jù)集劃分為多個子集,模型在不同的子集上交替進(jìn)行訓(xùn)練和測試,從而減少模型在某一特定數(shù)據(jù)分割上的偏差。交叉驗(yàn)證能夠有效地降低模型過擬合的風(fēng)險,從而提高模型評估的穩(wěn)定性和可靠性。
交叉驗(yàn)證的作用
- 提升模型穩(wěn)定性
通過在不同數(shù)據(jù)組合上測試模型,可以減少由于數(shù)據(jù)集單一劃分引起的偏差。 - 防止過擬合
在多個驗(yàn)證集上驗(yàn)證模型,可以有效評估模型在新數(shù)據(jù)上的表現(xiàn),從而降低過擬合的風(fēng)險。 - 更客觀的評估
交叉驗(yàn)證提供了一種系統(tǒng)性、可重復(fù)的方法,使模型評估更為準(zhǔn)確和客觀。
交叉驗(yàn)證的工作流程
- 數(shù)據(jù)劃分
將數(shù)據(jù)集隨機(jī)劃分為多個子集(通常稱為“折”)。 - 循環(huán)訓(xùn)練和驗(yàn)證
在每次循環(huán)中,選擇一個子集作為驗(yàn)證集,剩下的子集作為訓(xùn)練集,用訓(xùn)練集訓(xùn)練模型,然后在驗(yàn)證集上測試模型的性能。 - 計(jì)算平均性能
每次驗(yàn)證的結(jié)果都記錄下來,最后對這些結(jié)果求平均值,得到模型的總體性能評估。
常見的交叉驗(yàn)證方法
1.K 折交叉驗(yàn)證方法
K 折交叉驗(yàn)證將數(shù)據(jù)集分成 K 個不重疊的子集(折),每次將其中一個子集(折)作為測試集,其余 K-1 個折作為訓(xùn)練集。
重復(fù)這個過程 K 次,每次用不同的折作為測試集,最終將 K 次測試的結(jié)果平均,以得到模型的總體性能。
圖片
優(yōu)點(diǎn)
- 每個數(shù)據(jù)點(diǎn)都能作為測試集的一部分,且每個折都被用于訓(xùn)練和測試。
- 適合數(shù)據(jù)量較小的情況。
缺點(diǎn)
- 計(jì)算開銷較大,特別是當(dāng) K 較大時。
- 對于時間序列等具有時間依賴性的數(shù)據(jù),不適用。
from sklearn.model_selection import KFold, cross_val_score
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
# 加載數(shù)據(jù)
data = load_iris()
X, y = data.data, data.target
# 設(shè)置K折交叉驗(yàn)證
kf = KFold(n_splits=5)
model = RandomForestClassifier()
# 使用交叉驗(yàn)證評分
scores = cross_val_score(model, X, y, cv=kf)
print("K-Fold Cross-Validation Scores:", scores)
print("Mean Score:", scores.mean())
2.留一交叉驗(yàn)證
留一交叉驗(yàn)證是 K 折交叉驗(yàn)證的極端形式,其中 K 等于樣本總數(shù) N。
每次選取一個樣本作為測試集,其余 N-1 個樣本作為訓(xùn)練集,重復(fù)該過程 N 次,最后計(jì)算平均誤差。
圖片
優(yōu)點(diǎn)
- 使用了幾乎所有的數(shù)據(jù)進(jìn)行訓(xùn)練,模型訓(xùn)練效果較好。
- 適合數(shù)據(jù)集極小的情況。
缺點(diǎn)
- 計(jì)算成本高,特別是當(dāng)數(shù)據(jù)量很大時。
- 當(dāng)數(shù)據(jù)包含噪聲時,結(jié)果容易受到單個異常值的影響。
from sklearn.model_selection import LeaveOneOut
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
# 加載數(shù)據(jù)
data = load_iris()
X, y = data.data, data.target
# 設(shè)置留一交叉驗(yàn)證
loo = LeaveOneOut()
model = RandomForestClassifier()
# 使用交叉驗(yàn)證評分
scores = cross_val_score(model, X, y, cv=loo)
print("Leave-One-Out Cross-Validation Scores:", scores)
print("Mean Score:", scores.mean())
3.引導(dǎo)法
引導(dǎo)法通過有放回地從原始數(shù)據(jù)集中抽取樣本,生成多個子樣本(通常與原始數(shù)據(jù)集大小相同)。
每次從生成的樣本中訓(xùn)練模型,未被抽中的樣本(稱為“包外樣本”)作為測試集。
重復(fù)該過程多次,并對多次測試結(jié)果進(jìn)行平均。
圖片
優(yōu)點(diǎn)
- 包外樣本可以很好地代表未見數(shù)據(jù),從而提供較好的泛化誤差估計(jì)。
- 數(shù)據(jù)集中的每個樣本有可能在不同的抽樣中多次被使用,從而增加數(shù)據(jù)利用率。
缺點(diǎn):
- 由于樣本是有放回地抽取,導(dǎo)致可能有一些樣本被過多地抽取,而有些樣本未被抽中。
- 計(jì)算復(fù)雜度較高。
import numpy as np
from sklearn.utils import resample
from sklearn.metrics import accuracy_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
# 加載數(shù)據(jù)
data = load_iris()
X, y = data.data, data.target
# 設(shè)置引導(dǎo)驗(yàn)證
n_iterations = 100 # 100次引導(dǎo)采樣
model = RandomForestClassifier()
bootstrapped_scores = []
for _ in range(n_iterations):
# 使用引導(dǎo)法抽樣
X_train, y_train = resample(X, y, replace=True, n_samples=len(y))
X_test = np.array([x for x in X if x.tolist() not in X_train.tolist()])
y_test = np.array([y[i] for i, x in enumerate(X) if x.tolist() not in X_train.tolist()])
# 訓(xùn)練模型并計(jì)算準(zhǔn)確率
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
score = accuracy_score(y_test, y_pred)
bootstrapped_scores.append(score)
print("Bootstrap Validation Scores:", bootstrapped_scores)
print("Mean Score:", np.mean(bootstrapped_scores))
4.嵌套交叉驗(yàn)證
嵌套交叉驗(yàn)證主要用于模型選擇和超參數(shù)優(yōu)化。
外層交叉驗(yàn)證用于評估模型的性能,內(nèi)層交叉驗(yàn)證用于選擇模型的最佳超參數(shù)。
具體來說,外層將數(shù)據(jù)分成多個折,每個折作為驗(yàn)證集,剩余部分作為訓(xùn)練集;而在每個外層折的訓(xùn)練集中,又使用內(nèi)層交叉驗(yàn)證進(jìn)行超參數(shù)搜索。
優(yōu)點(diǎn)
- 有效防止數(shù)據(jù)泄漏,是超參數(shù)調(diào)優(yōu)的標(biāo)準(zhǔn)方法。
- 提供可靠的模型評估結(jié)果,適合用于復(fù)雜模型的選擇。
缺點(diǎn)
- 計(jì)算成本非常高,特別是數(shù)據(jù)集較大或超參數(shù)網(wǎng)格較大時。
from sklearn.model_selection import GridSearchCV, cross_val_score, KFold
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
# 加載數(shù)據(jù)
data = load_iris()
X, y = data.data, data.target
# 設(shè)置參數(shù)網(wǎng)格
param_grid = {
'n_estimators': [10, 50, 100],
'max_depth': [3, 5, None]
}
# 設(shè)置嵌套交叉驗(yàn)證
outer_cv = KFold(n_splits=5)
inner_cv = KFold(n_splits=3)
# 設(shè)置網(wǎng)格搜索和嵌套交叉驗(yàn)證
model = RandomForestClassifier()
grid_search = GridSearchCV(estimator=model, param_grid=param_grid, cv=inner_cv)
nested_scores = cross_val_score(grid_search, X, y, cv=outer_cv)
print("Nested Cross-Validation Scores:", nested_scores)
print("Mean Score:", nested_scores.mean())
5.分層交叉驗(yàn)證
分層交叉驗(yàn)證是一種特殊的交叉驗(yàn)證方法,特別適用于分類任務(wù)。
在分層交叉驗(yàn)證中,每個折內(nèi)的類別分布與整個數(shù)據(jù)集的類別分布相同,從而在不同折中保持相對一致的標(biāo)簽分布,避免模型評估因?yàn)轭悇e失衡而產(chǎn)生偏差。
圖片
優(yōu)點(diǎn)
- 對于類別不均衡的數(shù)據(jù)特別有效,能夠提供更準(zhǔn)確的評估。
- 避免因隨機(jī)劃分導(dǎo)致某些折中類別分布嚴(yán)重偏斜。
缺點(diǎn)
- 僅適用于分類任務(wù),不適用于回歸或時間序列任務(wù)。
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
# 加載數(shù)據(jù)集
data = load_iris()
X, y = data.data, data.target
# 使用分層交叉驗(yàn)證
skf = StratifiedKFold(n_splits=5)
model = RandomForestClassifier()
# 計(jì)算交叉驗(yàn)證分?jǐn)?shù)
scores = cross_val_score(model, X, y, cv=skf)
print("Stratified Cross-Validation Scores:", scores)
print("Mean Score:", scores.mean())
6.時間序列交叉驗(yàn)證
時間序列交叉驗(yàn)證是一種專門用于時間序列數(shù)據(jù)的模型評估方法。與傳統(tǒng)交叉驗(yàn)證方法(如 K 折交叉驗(yàn)證)不同,時間序列交叉驗(yàn)證遵循時間的自然順序,從過去到未來逐步劃分?jǐn)?shù)據(jù)。這種方法適用于時間序列預(yù)測任務(wù),能夠避免將未來的數(shù)據(jù)泄漏到模型訓(xùn)練過程中,從而保證模型的真實(shí)性能評估。
時間序列交叉驗(yàn)證的常用方法
滾動預(yù)測法
在滾動預(yù)測法中,每次劃分訓(xùn)練和測試集時,訓(xùn)練集的大小保持不變,僅在時間上向前滾動。每次只使用最新的訓(xùn)練集數(shù)據(jù)來預(yù)測未來的測試集數(shù)據(jù)。適合模擬模型實(shí)時滾動預(yù)測的情況。
圖片
優(yōu)缺點(diǎn):
- 優(yōu)點(diǎn):模擬真實(shí)的預(yù)測過程,能更好地反映模型在動態(tài)預(yù)測中的表現(xiàn)。
- 缺點(diǎn):每次訓(xùn)練數(shù)據(jù)有限,模型難以利用更多的歷史數(shù)據(jù),可能導(dǎo)致不穩(wěn)定性。
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
# 創(chuàng)建時間序列數(shù)據(jù)
X = np.arange(1, 101).reshape(-1, 1) # 1到100的時間序列
y = X.flatten() + np.random.normal(scale=5, size=X.shape[0]) # 添加噪聲
# 定義滾動窗口的大小
window_size = 20 # 每次訓(xùn)練的窗口大小
# 初始化模型
model = LinearRegression()
# 存儲預(yù)測結(jié)果和誤差
predictions = []
errors = []
# 滾動窗口法進(jìn)行預(yù)測
for i in range(len(X) - window_size - 1):
# 定義訓(xùn)練集和測試集
X_train = X[i : i + window_size]
y_train = y[i : i + window_size]
X_test = X[i + window_size : i + window_size + 1]
y_test = y[i + window_size : i + window_size + 1]
# 訓(xùn)練模型
model.fit(X_train, y_train)
# 進(jìn)行預(yù)測
y_pred = model.predict(X_test)
# 記錄預(yù)測和誤差
predictions.append(y_pred[0])
mse = mean_squared_error(y_test, y_pred)
errors.append(mse)
print(f"Window {i+1}: Predicted = {y_pred[0]:.2f}, Actual = {y_test[0]:.2f}, MSE = {mse:.2f}")
# 計(jì)算平均誤差
average_mse = np.mean(errors)
print(f"\nAverage MSE over all windows: {average_mse:.2f}")
擴(kuò)展窗口法
在擴(kuò)展窗口法中,訓(xùn)練集會隨著時間不斷擴(kuò)大,即每次訓(xùn)練時,訓(xùn)練集包含更長時間段的數(shù)據(jù),而測試集仍為后續(xù)的一小部分?jǐn)?shù)據(jù)。這種方法能讓模型在預(yù)測時利用更多歷史數(shù)據(jù)。
圖片
優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):通過逐漸擴(kuò)大訓(xùn)練集,模型可以利用更多的歷史信息,提升預(yù)測穩(wěn)定性。
- 缺點(diǎn):計(jì)算開銷較大,特別是在訓(xùn)練集不斷增大時。
from sklearn.model_selection import TimeSeriesSplit
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
import numpy as np
# 創(chuàng)建時間序列數(shù)據(jù)
X = np.arange(1, 101).reshape(-1, 1)
y = X.flatten() + np.random.normal(scale=5, size=X.shape[0])
# 使用TimeSeriesSplit實(shí)現(xiàn)滾動預(yù)測法
tscv = TimeSeriesSplit(n_splits=5)
model = LinearRegression()
for train_index, test_index in tscv.split(X):
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
print("Test MSE:", mean_squared_error(y_test, y_pred)