3702 字
19 分钟
页面浏览--访问次数--
天文数据科学|机器学习甄别系外行星

在前两篇文章中,我们通过肉眼和 BLS 算法分析了单颗恒星的光变曲线。但在真实的 Kepler 任务中,望远镜发现了数千个候选信号——其中许多是假阳性(如食双星、仪器噪声等)。

本文的目标是:利用 Pandas DataFrame 组织大规模候选体数据,训练一个随机森林分类器,让机器学习模型自动甄别真实的系外行星。


1. DataFrame 热身练习#

1.1 创建玩具数据集#

用三个模拟的系外行星候选体数据来熟悉 DataFrame 的基本操作。每条记录包含周期、凌星深度、持续时间、信噪比和文字描述:

import pandas as pd
warmup = pd.DataFrame([
{
"case": "A",
"period_days": 3.2,
"depth_percent": 0.8,
"duration_hours": 2.1,
"snr": 14.0,
"description": "regular repeated U-shaped dips"
},
{
"case": "B",
"period_days": 8.5,
"depth_percent": 0.1,
"duration_hours": 1.0,
"snr": 2.2,
"description": "weak messy signal, not clearly repeating"
},
{
"case": "C",
"period_days": 1.7,
"depth_percent": 8.5,
"duration_hours": 6.0,
"snr": 20.0,
"description": "very deep V-shaped repeated dips"
},
])
warmup

输出:

case period_days depth_percent duration_hours snr \
0 A 3.2 0.8 2.1 14.0
1 B 8.5 0.1 1.0 2.2
2 C 1.7 8.5 6.0 20.0
description
0 regular repeated U-shaped dips
1 weak messy signal, not clearly repeating
2 very deep V-shaped repeated dips

1.2 添加新列#

为每个案例添加人工标注的分类标签。在天文学中,常见的分类包括:

  • candidate_like:类似真实行星候选体(U 形凹陷、高 SNR、周期性)
  • weak_or_noise:信号太弱或噪声(低 SNR、无清晰重复)
  • possible_binary:可能是食双星(很深的 V 形凹陷)
toy_df = warmup.copy()
toy_df["human_label"] = [
"candidate_like",
"weak_or_noise",
"possible_binary"
]
toy_df

输出:

case period_days depth_percent duration_hours snr \
0 A 3.2 0.8 2.1 14.0
1 B 8.5 0.1 1.0 2.2
2 C 1.7 8.5 6.0 20.0
description human_label
0 regular repeated U-shaped dips candidate_like
1 weak messy signal, not clearly repeating weak_or_noise
2 very deep V-shaped repeated dips possible_binary

1.3 DataFrame 基础属性#

print("Shape: ", toy_df.shape)
print('Rows:', toy_df.shape[0])
print('Columns:', toy_df.shape[1])

输出:

Shape: (3, 7)
Rows: 3
Columns: 7

shape 返回 (行数, 列数) 元组。

1.4 列筛选#

选取特定列的子集。双层方括号 df[['col1', 'col2']] 返回 DataFrame,单层方括号 df['col'] 返回 Series:

toy_df[['case', 'period_days', 'depth_percent', 'snr']]

输出:

case period_days depth_percent snr
0 A 3.2 0.8 14.0
1 B 8.5 0.1 2.2
2 C 1.7 8.5 20.0

1.5 特征矩阵与标签向量#

在机器学习中,特征(Features)是输入模型的数值列,标签(Labels)是要预测的目标:

feature_columns = [
'period_days',
'depth_percent',
'duration_hours',
'snr'
]
toy_features = toy_df[feature_columns]
toy_features

输出:

period_days depth_percent duration_hours snr
0 3.2 0.8 2.1 14.0
1 8.5 0.1 1.0 2.2
2 1.7 8.5 6.0 20.0
toy_labels = toy_df['human_label']
toy_labels

输出:

0 candidate_like
1 weak_or_noise
2 possible_binary
Name: human_label, dtype: object

这就是监督学习的基本数据结构:X(特征矩阵) 和 y(标签向量)。

1.6 布尔条件筛选#

high_snr = toy_df[toy_df['snr'] > 5]
high_snr

输出:

case period_days depth_percent duration_hours snr \
0 A 3.2 0.8 2.1 14.0
2 C 1.7 8.5 6.0 20.0
description human_label
0 regular repeated U-shaped dips candidate_like
2 very deep V-shaped repeated dips possible_binary
weak_signals = toy_df[toy_df['snr'] <= 5]
weak_signals

输出:

case period_days depth_percent duration_hours snr \
1 B 8.5 0.1 1.0 2.2
description human_label
1 weak messy signal, not clearly repeating weak_or_noise

1.7 多条件组合筛选#

用 &(与)和括号组合多个条件:

deep_signals = toy_df[toy_df['depth_percent'] > 2.0]
deep_signals
interesting = toy_df[
(toy_df["snr"] > 5) &
(toy_df["depth_percent"] < 5)
]
interesting

输出:

case period_days depth_percent duration_hours snr \
0 A 3.2 0.8 2.1 14.0
description human_label
0 regular repeated U-shaped dips candidate_like

这个查询找出了”高 SNR 但深度适中”的信号——最有可能是真实行星的候选体。

1.8 排序#

toy_df.sort_values("snr", ascending=False)

输出:

case period_days depth_percent duration_hours snr \
2 C 1.7 8.5 6.0 20.0
0 A 3.2 0.8 2.1 14.0
1 B 8.5 0.1 1.0 2.2
description human_label
2 very deep V-shaped repeated dips possible_binary
0 regular repeated U-shaped dips candidate_like
1 weak messy signal, not clearly repeating weak_or_noise
toy_df.sort_values("depth_percent", ascending=False)

1.9 值计数与布尔列#

toy_df["human_label"].value_counts()

输出:

human_label
candidate_like 1
weak_or_noise 1
possible_binary 1
Name: count, dtype: int64

创建布尔列作为派生特征:

toy_df["strong_signal"] = toy_df["snr"] >= 7
toy_df[["case", "snr", "strong_signal", "human_label"]]

输出:

case snr strong_signal human_label
0 A 14.0 True candidate_like
1 B 2.2 False weak_or_noise
2 C 20.0 True possible_binary
toy_df["very_deep"] = toy_df["depth_percent"] > 5
toy_df[["case", "depth_percent", "very_deep", "human_label"]]

1.10 列运算与分组聚合#

创建派生数值列——简单评分 = SNR − 深度:

toy_df["simple_score"] = toy_df["snr"] - toy_df["depth_percent"]
toy_df[["case", "snr", "depth_percent", "simple_score", "human_label"]]

输出:

case snr depth_percent simple_score human_label
0 A 14.0 0.8 13.2 candidate_like
1 B 2.2 0.1 2.1 weak_or_noise
2 C 20.0 8.5 11.5 possible_binary

按类别分组计算均值:

toy_df.groupby("human_label")[["period_days", "depth_percent", "duration_hours", "snr"]].mean()

输出:

period_days depth_percent duration_hours snr
human_label
candidate_like 3.2 0.8 2.1 14.0
possible_binary 1.7 8.5 6.0 20.0
weak_or_noise 8.5 0.1 1.0 2.2

groupby("human_label").mean() 按分类标签分组后计算每个特征的平均值。可以看到:candidate_like 类具有适中的深度和较高的 SNR,而 possible_binary 类深度很大。

1.11 准备机器学习数据结构#

X_toy = toy_df[feature_columns]
y_toy = toy_df["human_label"]
print("X shape:", X_toy.shape)
print("y shape:", y_toy.shape)
display(X_toy)
display(y_toy)

输出:

X shape: (3, 4)
y shape: (3,)
period_days depth_percent duration_hours snr
0 3.2 0.8 2.1 14.0
1 8.5 0.1 1.0 2.2
2 1.7 8.5 6.0 20.0
0 candidate_like
1 weak_or_noise
2 possible_binary
Name: human_label, dtype: object

2. 大规模真实数据#

2.1 环境配置#

安装 scikit-learn 并导入所需库:

!pip -q install scikit-learn
import warnings
warnings.filterwarnings("ignore")
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import requests
from io import StringIO
from IPython.display import display
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import (
accuracy_score,
precision_score,
recall_score,
classification_report,
confusion_matrix,
ConfusionMatrixDisplay,
)
RANDOM_STATE = 42
print("Libraries imported successfully.")
print("numpy:", np.__version__)
print("pandas:", pd.__version__)

输出:

Libraries imported successfully.
numpy: 2.3.5
pandas: 2.3.3

2.2 查询 NASA 系外行星数据库#

从 NASA Exoplanet Archive 下载 5000 条已知分类的记录——包括已确认行星(CONFIRMED)、候选体(CANDIDATE)和假阳性(FALSE POSITIVE):

TAP_URL = "https://exoplanetarchive.ipac.caltech.edu/TAP/sync"
query = '''
select top 5000
kepid,
kepoi_name,
kepler_name,
koi_disposition,
koi_period,
koi_duration,
koi_depth,
koi_prad,
koi_model_snr,
koi_kepmag,
koi_score
from cumulative
where koi_disposition in ('CONFIRMED', 'CANDIDATE', 'FALSE POSITIVE')
and koi_period is not null
and koi_duration is not null
and koi_depth is not null
and koi_prad is not null
and koi_model_snr is not null
and koi_kepmag is not null
'''

如果 NASA 查询失败,使用合成回退数据(make_fallback_archive 函数生成具有合理统计分布的人造数据):

def make_fallback_archive(n_confirmed=120, n_fp=120, n_candidate=60, seed=42):
# Create a small synthetic fallback table if the live NASA query fails.
rng = np.random.default_rng(seed)
rows = []
def add_rows(n, disposition):
for i in range(n):
if disposition == "CONFIRMED":
period = 10 ** rng.uniform(np.log10(0.8), np.log10(25))
duration = rng.uniform(1.0, 8.0)
depth = 10 ** rng.uniform(np.log10(200), np.log10(12000))
prad = 10 ** rng.uniform(np.log10(0.8), np.log10(12))
snr = 10 ** rng.uniform(np.log10(7), np.log10(80))
kepmag = rng.uniform(10, 15)
elif disposition == "FALSE POSITIVE":
period = 10 ** rng.uniform(np.log10(0.3), np.log10(40))
duration = rng.uniform(0.5, 16.0)
depth = 10 ** rng.uniform(np.log10(1000), np.log10(100000))
prad = 10 ** rng.uniform(np.log10(5), np.log10(80))
snr = 10 ** rng.uniform(np.log10(3), np.log10(120))
kepmag = rng.uniform(11, 16)
else:
period = 10 ** rng.uniform(np.log10(0.5), np.log10(35))
duration = rng.uniform(0.7, 12.0)
depth = 10 ** rng.uniform(np.log10(200), np.log10(50000))
prad = 10 ** rng.uniform(np.log10(1), np.log10(35))
snr = 10 ** rng.uniform(np.log10(4), np.log10(90))
kepmag = rng.uniform(10, 16)
rows.append({
"kepid": int(10_000_000 + len(rows)),
"kepoi_name": f"K{len(rows):05d}.01",
"kepler_name": None if disposition != "CONFIRMED" else f"Kepler-{len(rows)} b",
"koi_disposition": disposition,
"koi_period": period,
"koi_duration": duration,
"koi_depth": depth,
"koi_prad": prad,
"koi_model_snr": snr,
"koi_kepmag": kepmag,
"koi_score": np.nan,
})
add_rows(n_confirmed, "CONFIRMED")
add_rows(n_fp, "FALSE POSITIVE")
add_rows(n_candidate, "CANDIDATE")
return pd.DataFrame(rows)
archive_df = None
try:
print("Querying NASA Exoplanet Archive...")
response = requests.get(
TAP_URL,
params={"query": query, "format": "csv"},
timeout=60,
)
response.raise_for_status()
archive_df = pd.read_csv(StringIO(response.text))
print("Live NASA rows downloaded:", len(archive_df))
except Exception as exc:
print("NASA query failed. Using synthetic fallback data for the lesson.")
print("Error:", exc)
archive_df = make_fallback_archive()
print("Final table shape:", archive_df.shape)
archive_df.head()

输出:

Querying NASA Exoplanet Archive...
Live NASA rows downloaded: 5000
Final table shape: (5000, 11)
kepid kepoi_name kepler_name koi_disposition koi_period \
0 10797460 K00752.01 Kepler-227 b CONFIRMED 9.488036
1 10797460 K00752.02 Kepler-227 c CONFIRMED 54.418383
2 10811496 K00753.01 NaN CANDIDATE 19.899140
3 10848459 K00754.01 NaN FALSE POSITIVE 1.736952
4 10854555 K00755.01 Kepler-664 b CONFIRMED 2.525592
koi_duration koi_depth koi_prad koi_model_snr koi_kepmag koi_score
0 2.95750 615.8 2.26 35.8 15.347 1.000
1 4.50700 874.8 2.83 25.8 15.347 0.969
2 1.78220 10829.0 14.60 76.3 15.436 0.000
3 2.40641 8079.2 33.46 505.6 15.597 0.000
4 1.65450 603.3 2.75 40.9 15.509 1.000
print('Columns:', list(archive_df.columns))

输出:

Columns: ['kepid', 'kepoi_name', 'kepler_name', 'koi_disposition', 'koi_period', 'koi_duration', 'koi_depth', 'koi_prad', 'koi_model_snr', 'koi_kepmag', 'koi_score']

3. 特征工程#

3.1 对数变换#

天文学数据通常是对数正态分布(数量级跨越很大)。取对数后数据更接近正态分布,模型训练效果更好:

def safe_log10(x):
# Log10 transform that avoids problems with zero or negative values.
x = pd.to_numeric(x, errors="coerce")
return np.log10(np.clip(x, 1e-12, None))
features_df = archive_df.copy()
features_df["log_period"] = safe_log10(features_df["koi_period"])
features_df["log_duration"] = safe_log10(features_df["koi_duration"])
features_df["log_depth"] = safe_log10(features_df["koi_depth"])
features_df["log_prad"] = safe_log10(features_df["koi_prad"])
features_df["log_snr"] = safe_log10(features_df["koi_model_snr"])
features_df["kepmag"] = pd.to_numeric(features_df["koi_kepmag"], errors="coerce")
FEATURES = [
"log_period",
"log_duration",
"log_depth",
"log_prad",
"log_snr",
"kepmag",
]
features_df = features_df.dropna(subset=FEATURES + ["koi_disposition"]).copy()
print("Feature columns:", FEATURES)
features_df[["kepoi_name", "koi_disposition"] + FEATURES].head()

输出:

Feature columns: ['log_period', 'log_duration', 'log_depth', 'log_prad', 'log_snr', 'kepmag']
kepoi_name koi_disposition log_period log_duration log_depth log_prad \
0 K00752.01 CONFIRMED 0.977176 0.470925 2.789440 0.354108
1 K00752.02 CONFIRMED 1.735746 0.653888 2.941909 0.451786
2 K00753.01 CANDIDATE 1.298834 0.250956 4.034588 1.164353
3 K00754.01 FALSE POSITIVE 0.239788 0.381370 3.907368 1.524526
4 K00755.01 CONFIRMED 0.402363 0.218667 2.780533 0.439333
log_snr kepmag
0 1.553883 15.347
1 1.411620 15.347
2 1.882525 15.436
3 2.703807 15.597
4 1.611723 15.509

np.clip(x, 1e-12, None) 将所有 ≤0 的值替换为一个极小的正数(10⁻¹²),避免 log10(0) 或 log10(负数) 的数学错误。safe_log10 封装了这一安全处理。

3.2 划分训练集#

只使用已知标签的数据(CONFIRMED = 1,FALSE POSITIVE = 0)作为训练集,CANDIDATE 留作后续预测:

known_df = features_df[
features_df["koi_disposition"].isin(["CONFIRMED", "FALSE POSITIVE"])
].copy()
candidate_df = features_df[
features_df["koi_disposition"] == "CANDIDATE"
].copy()
known_df["label"] = (known_df["koi_disposition"] == "CONFIRMED").astype(int)
print("Known examples:", known_df.shape)
print("Candidate rows for later ranking:", candidate_df.shape)
print("\nLabel counts:")
print(known_df["label"].value_counts().rename(index={0: "false positive", 1: "confirmed"}))

输出:

Known examples: (4194, 18)
Candidate rows for later ranking: (806, 17)
Label counts:
label
confirmed 2526
false positive 1668
Name: count, dtype: int64

.astype(int) 将布尔值 True/False 转换为 1/0,这是机器学习中二分类标签的标准格式。

3.3 类别平衡#

如果确认行星(2526 个)远多于假阳性(1668 个),模型会偏向多数类。通过随机欠采样使两类数量相等:

confirmed = known_df[known_df["label"] == 1]
false_positive = known_df[known_df["label"] == 0]
n = min(len(confirmed), len(false_positive))
if n < 20:
print("Warning: very few examples. The model will be only a toy demonstration.")
confirmed_bal = confirmed.sample(n=n, random_state=RANDOM_STATE)
false_positive_bal = false_positive.sample(n=n, random_state=RANDOM_STATE)
model_df = pd.concat([confirmed_bal, false_positive_bal], ignore_index=True)
model_df = model_df.sample(frac=1, random_state=RANDOM_STATE).reset_index(drop=True)
print("Balanced training table:", model_df.shape)
print(model_df["label"].value_counts().rename(index={0: "false positive", 1: "confirmed"}))
model_df[["kepoi_name", "koi_disposition"] + FEATURES + ["label"]].head()

输出:

Balanced training table: (3336, 18)
label
confirmed 1668
false positive 1668
Name: count, dtype: int64
kepoi_name koi_disposition log_period log_duration log_depth log_prad \
0 K01441.01 CONFIRMED 0.929775 0.468052 2.470557 0.278754
1 K03084.01 FALSE POSITIVE 0.187637 0.231215 2.029789 -0.036212
2 K00082.04 CONFIRMED 0.849503 0.440437 1.817565 -0.200659
3 K03279.01 CONFIRMED 1.488943 0.624695 2.807061 0.383815
4 K00351.01 CONFIRMED 2.520611 1.158483 3.920233 1.037028
log_snr kepmag label
0 1.315970 15.135 1
1 1.082785 14.991 0
2 1.350248 11.492 1
3 1.143015 15.765 1
4 2.550228 13.804 1

pd.concat 拼接两个 DataFrame,sample(frac=1) 以 100% 比例打乱行顺序,reset_index(drop=True) 重置索引。

3.4 特征空间可视化#

以 log_depth 和 log_snr 为坐标轴,观察两类样本的分布:

plt.figure(figsize=(8, 5))
for label_value, label_name in [(1, "confirmed"), (0, "false positive")]:
subset = model_df[model_df["label"] == label_value]
plt.scatter(
subset["log_depth"],
subset["log_snr"],
s=20,
alpha=0.65,
label=label_name,
)
plt.xlabel("log10(transit depth [ppm])")
plt.ylabel("log10(model SNR)")
plt.title("Feature space: depth vs SNR")
plt.legend()
plt.grid(True)
plt.show()

已确认行星(蓝色)倾向于集中在特定的深度-SNR 区域,而假阳性(橙色)分布更分散——这为机器学习分类器提供了可学习的信息。


4. 训练随机森林分类器#

4.1 训练/测试拆分#

train_test_split 将数据随机分为训练集(75%)和测试集(25%),stratify=y 确保两个集合中类别比例一致:

X = model_df[FEATURES]
y = model_df["label"]
X_train, X_test, y_train, y_test, train_idx, test_idx = train_test_split(
X,
y,
model_df.index,
test_size=0.25,
random_state=RANDOM_STATE,
stratify=y,
)
print("Training examples:", len(X_train))
print("Test examples:", len(X_test))
print("\nTrain label counts:")
print(y_train.value_counts().rename(index={0: "false positive", 1: "confirmed"}))
print("\nTest label counts:")
print(y_test.value_counts().rename(index={0: "false positive", 1: "confirmed"}))

输出:

Training examples: 2502
Test examples: 834
Train label counts:
label
false positive 1251
confirmed 1251
Name: count, dtype: int64
Test label counts:
label
false positive 417
confirmed 417
Name: count, dtype: int64

4.2 训练模型#

随机森林(Random Forest)是多个决策树的集成,每棵树在数据的随机子集上训练。参数说明:

  • n_estimators=300:300 棵决策树
  • max_depth=5:每棵树最多 5 层(防止过拟合)
  • min_samples_leaf=3:叶节点最少 3 个样本
  • class_weight="balanced":自动处理类别不平衡
model = RandomForestClassifier(
n_estimators=300,
max_depth=5,
min_samples_leaf=3,
random_state=RANDOM_STATE,
class_weight="balanced",
)
model.fit(X_train, y_train)
print("Random Forest trained.")

输出:

Random Forest trained.

4.3 模型评估#

在测试集上评估模型性能:

  • 准确率(Accuracy):预测正确的比例
  • 精确率(Precision):预测为”确认行星”中真正是行星的比例
  • 召回率(Recall):真实行星中被正确找出的比例
y_pred = model.predict(X_test)
y_proba = model.predict_proba(X_test)[:, 1]
acc = accuracy_score(y_test, y_pred)
prec = precision_score(y_test, y_pred, zero_division=0)
rec = recall_score(y_test, y_pred, zero_division=0)
print("Test accuracy:", round(acc, 3))
print("Test precision:", round(prec, 3))
print("Test recall:", round(rec, 3))
print("\nClassification report:")
print(classification_report(
y_test,
y_pred,
target_names=["false positive", "confirmed"],
zero_division=0,
))

输出:

Test accuracy: 0.841
Test precision: 0.817
Test recall: 0.878
Classification report:
precision recall f1-score support
false positive 0.87 0.80 0.83 417
confirmed 0.82 0.88 0.85 417
accuracy 0.84 834
macro avg 0.84 0.84 0.84 834
weighted avg 0.84 0.84 0.84 834

predict_proba 返回每个样本属于各类的概率,[:, 1] 取第二列(确认行星的概率)。模型整体准确率 84%,对确认行星的召回率 88%——模型能找出大多数真实行星。

4.4 混淆矩阵#

混淆矩阵可视化预测结果 vs 真实标签:

cm = confusion_matrix(y_test, y_pred, labels=[1, 0])
fig, ax = plt.subplots(figsize=(5, 5))
disp = ConfusionMatrixDisplay(
confusion_matrix=cm,
display_labels=["confirmed", "false positive"],
)
disp.plot(ax=ax, values_format="d", colorbar=False)
plt.title("Confusion matrix — test set")
plt.grid(False)
plt.show()
  • 左上角(True Positive,366):正确识别为行星
  • 右下角(True Negative,335):正确识别为假阳性
  • 右上角(False Negative,51):行星被误判为假阳性(漏报)
  • 左下角(False Positive,82):假阳性被误判为行星(误报)

4.5 分析分类错误的案例#

test_rows = model_df.loc[test_idx].copy()
test_rows["true_label"] = y_test.values
test_rows["pred_label"] = y_pred
test_rows["confirmed_probability"] = y_proba
test_rows["correct"] = test_rows["true_label"] == test_rows["pred_label"]
mistakes = test_rows[~test_rows["correct"]].copy()
print("Number of test mistakes:", len(mistakes))
mistakes[[
"kepid",
"kepoi_name",
"kepler_name",
"koi_disposition",
"koi_period",
"koi_depth",
"koi_prad",
"koi_model_snr",
"confirmed_probability",
"true_label",
"pred_label",
]].sort_values("confirmed_probability", ascending=False).head(20)

输出(前20条假阳性被误判为行星):

Number of test mistakes: 133
kepid kepoi_name kepler_name koi_disposition koi_period koi_depth \
2496 5443604 K02713.01 NaN FALSE POSITIVE 21.391012 645.8
2139 8345172 K02385.01 NaN FALSE POSITIVE 50.571581 1282.6
1707 3120276 K01111.01 NaN FALSE POSITIVE 10.265477 399.6
...
koi_prad koi_model_snr confirmed_probability true_label pred_label
2496 1.72 25.1 0.834551 0 1
2139 2.74 19.9 0.834471 0 1

这些被模型高置信度误判的假阳性,通常具有”看起来像行星”的物理参数:适中的周期、合理的凌星深度和小行星半径。这正是机器学习面临的挑战——有些假阳性在特征空间中与真实行星高度重叠。

4.6 特征重要性#

随机森林可以告诉我们哪些特征对分类贡献最大:

importance = pd.DataFrame({
"feature": FEATURES,
"importance": model.feature_importances_,
}).sort_values("importance", ascending=True)
plt.figure(figsize=(8, 4))
plt.barh(importance["feature"], importance["importance"])
plt.xlabel("Feature importance")
plt.title("Which features did the forest use most?")
plt.grid(axis="x", alpha=0.3)
plt.show()
importance.sort_values("importance", ascending=False)

通常 log_depth(凌星深度)和 log_prad(行星半径)是最重要的特征——深度越大、半径越大的信号越可能是假阳性(如食双星)。

4.7 过拟合检查#

train_pred = model.predict(X_train)
test_pred = model.predict(X_test)
train_acc = accuracy_score(y_train, train_pred)
test_acc = accuracy_score(y_test, test_pred)
print("Train accuracy:", round(train_acc, 3))
print("Test accuracy:", round(test_acc, 3))
if train_acc > test_acc + 0.15:
print("Possible overfitting: train score is much higher than test score.")
else:
print("Train/test gap is not extremely large for this demo.")

输出:

Train accuracy: 0.867
Test accuracy: 0.841
Train/test gap is not extremely large for this demo.

训练集(86.7%)与测试集(84.1%)差距仅 2.6 个百分点,模型未严重过拟合。


5. 候选体排序(Candidate Triage)#

5.1 对未分类候选体进行预测#

Kepler 数据中有 806 条 CANDIDATE 状态的记录——它们的真实身份尚未被 NASA 确认。用训练好的模型对它们打分:

if len(candidate_df) == 0:
print("No candidate rows available. Skipping candidate triage.")
else:
candidate_X = candidate_df[FEATURES]
candidate_df = candidate_df.copy()
candidate_df["confirmed_like_probability"] = model.predict_proba(candidate_X)[:, 1]
ranked_candidates = candidate_df.sort_values("confirmed_like_probability", ascending=False)
print("Ranked candidate rows:", len(ranked_candidates))
display_cols = [
"kepid",
"kepoi_name",
"kepler_name",
"koi_period",
"koi_depth",
"koi_prad",
"koi_model_snr",
"koi_kepmag",
"confirmed_like_probability",
]
display(ranked_candidates[display_cols].head(15))

输出(前 15 个高概率候选体):

Ranked candidate rows: 806
kepid kepoi_name kepler_name koi_period koi_depth koi_prad \
3144 4951877 K00501.01 NaN 24.796280 574.4 2.70
1561 4242147 K01934.01 NaN 28.782710 864.5 2.11
3400 5131180 K00641.01 NaN 14.851855 1341.0 2.73
...
koi_model_snr koi_kepmag confirmed_like_probability
3144 43.6 14.612 0.836956
1561 40.1 14.649 0.836525

模型按概率从高到低排列了所有候选体。这些最有可能的候选体具有:适中的对数深度、小行星半径(1.5−3 地球半径)、高 SNR——与已确认行星的特征高度吻合,值得天文学家优先进行后续观测验证。


6. 本节小结#

本节完成了以下内容:

  • 掌握了 Pandas DataFrame 的核心操作:创建、列筛选、布尔过滤、排序、groupby 分组聚合、新列派生。
  • 构建了特征矩阵(X)和标签向量(y),为监督学习做好了数据准备。
  • 从 NASA Exoplanet Archive 获取了 5000 条真实系外行星候选体数据。
  • 实施了特征工程:对数变换(safe_log10)使天文学数量级数据更适合机器学习模型。
  • 训练了随机森林分类器(RandomForestClassifier),在测试集上达到 84% 准确率和 88% 召回率。
  • 分析了混淆矩阵和特征重要性,理解了模型的优势和局限性。
  • 对 806 个未确认候选体进行了概率排序,实现了自动化候选体筛选。

第 3 篇,共 3 篇本文属于系列

天文数据科学|机器学习甄别系外行星
https://luq-blog.xyz/posts/2026-07-09-astro-data-science-03-machine-learning-classifier/
作者
Luquiescene
发布于
2026-07-09
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

评论

正在加载评论...