【机器学习chp12代码示例】半监督学习

目录

一、自我训练

1、分类的例子

2、回归的例子

二、协同训练

三、生成式模型GMM

四、基于图的半监督算法-标签传播


一、自我训练

1、分类的例子

# 自我训练——分类

import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.semi_supervised import SelfTrainingClassifier
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification

# 生成示例数据:部分标签缺失
X, y = make_classification(n_samples=1000, n_features=20, n_informative=15, random_state=42)


# 划分训练集与测试集(测试集全部有标签)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 随机将训练集70%的标签设置为-1表示无标签
rng = np.random.RandomState(42)
random_unlabeled_points = rng.rand(len(y_train)) < 0.7
y_train[random_unlabeled_points] = -1


# 初始化基本分类器(例如随机森林)
base_clf = RandomForestClassifier(n_estimators=100, random_state=42)

# 用自我训练方法封装基本分类器
self_training_clf = SelfTrainingClassifier(base_clf, threshold=0.8, criterion='threshold')

# 训练模型
self_training_clf.fit(X_train, y_train)

# 评估性能
print("Test accuracy:", self_training_clf.score(X_test, y_test))

Test accuracy: 0.835

2、回归的例子

# 自我训练——回归


import numpy as np
from sklearn.ensemble import RandomForestRegressor
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

# 生成回归数据
X, y = make_regression(n_samples=1000, n_features=20, noise=10, random_state=42)

# 划分训练集和测试集(测试集保留完整标签)
X_train, X_test, y_train_full, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 模拟训练集中70%的样本无标签,设为NaN表示缺失标签
rng = np.random.RandomState(42)
mask = rng.rand(len(y_train_full)) < 0.9
y_train = y_train_full.copy()
y_train[mask] = np.nan

# 定义函数:利用随机森林各决策树的预测计算均值和标准差
def predict_with_std(rf, X):
    # 获取每棵树的预测结果
    preds = np.array([est.predict(X) for est in rf.estimators_])
    mean_preds = preds.mean(axis=0)
    std_preds = preds.std(axis=0)
    return mean_preds, std_preds

# 初始化基本回归器
base_regressor = RandomForestRegressor(n_estimators=100, random_state=42)

# 自我训练(伪标签)参数设置
max_iterations = 10
confidence_threshold = 120  # 阈值,标准差低于此值认为预测置信度高

# 用布尔数组记录哪些样本已标注(包括真实标签和伪标签)
labeled_mask = ~np.isnan(y_train)

for iteration in range(max_iterations):
    # 用当前标注样本训练回归器
    base_regressor.fit(X_train[labeled_mask], y_train[labeled_mask])
    
    # 找出无标签样本的索引
    unlabeled_indices = np.where(~labeled_mask)[0]
    if len(unlabeled_indices) == 0:
        break
    
    X_unlabeled = X_train[unlabeled_indices]
    
    # 利用模型对无标签数据进行预测,同时获得预测标准差
    pseudo_preds, std_preds = predict_with_std(base_regressor, X_unlabeled)
    
    # 选取预测标准差低于阈值的样本,认为预测可信
    #print(std_preds)
    confident_mask = std_preds < confidence_threshold
    if not confident_mask.any():
        print(f"Iteration {iteration}: No confident predictions, stopping.")
        break
    
    # 更新这些样本的标签为伪标签
    confident_indices = unlabeled_indices[confident_mask]
    y_train[confident_indices] = pseudo_preds[confident_mask]
    labeled_mask = ~np.isnan(y_train)  # 更新标注状态
    
    print(f"Iteration {iteration}: Added {len(confident_indices)} pseudo-labeled samples.")

# 最终模型在测试集上评估
y_pred_test = base_regressor.predict(X_test)


mse = mean_squared_error(y_test, y_pred_test)
print("Test MSE:", mse)


Iteration 0: Added 24 pseudo-labeled samples.

Iteration 1: Added 99 pseudo-labeled samples.

Iteration 2: Added 240 pseudo-labeled samples.

Iteration 3: Added 211 pseudo-labeled samples.

Iteration 4: Added 111 pseudo-labeled samples.

Iteration 5: Added 17 pseudo-labeled samples.

Iteration 6: Added 1 pseudo-labeled samples.

Iteration 7: Added 1 pseudo-labeled samples.

Iteration 8: No confident predictions, stopping.

Test MSE: 19191.928223350456

二、协同训练

# 协同训练

import numpy as np
from sklearn.naive_bayes import GaussianNB
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification

# 构造示例数据(假设特征可以分成两组)
def split_features(X):
    # 假设前一半为视角1,后一半为视角2
    d = X.shape[1] // 2
    return X[:, :d], X[:, d:]

X, y = make_classification(n_samples=500, n_features=20, n_informative=10, random_state=42)
X_view1, X_view2 = split_features(X)

# 随机设置部分样本无标签 (-1 表示未知标签)
rng = np.random.RandomState(42)
y_co = y.copy()
unlabeled_mask = rng.rand(len(y)) < 0.7
y_co[unlabeled_mask] = -1

# 划分训练(带标签)和无标签数据
labeled_idx = np.where(y_co != -1)[0]
unlabeled_idx = np.where(y_co == -1)[0]

X1_labeled, X2_labeled = X_view1[labeled_idx], X_view2[labeled_idx]
y_labeled = y[labeled_idx]

X1_unlabeled, X2_unlabeled = X_view1[unlabeled_idx], X_view2[unlabeled_idx]

# 初始化两个视角的分类器
clf1 = GaussianNB()
clf2 = GaussianNB()

# 初始训练:仅使用带标签数据
clf1.fit(X1_labeled, y_labeled)
clf2.fit(X2_labeled, y_labeled)

# 协同训练迭代次数
n_iter = 5
for it in range(n_iter):
    # 分别在无标签数据上进行预测
    pseudo_labels1 = clf1.predict(X1_unlabeled)
    pseudo_labels2 = clf2.predict(X2_unlabeled)
    
    # 找出两个分类器预测一致的样本作为高置信样本
    agreed = pseudo_labels1 == pseudo_labels2
    if np.sum(agreed) == 0:
        break
    X1_new = X1_unlabeled[agreed]
    X2_new = X2_unlabeled[agreed]
    y_new = pseudo_labels1[agreed]
    
    # 将这些高置信样本加入带标签数据中
    X1_labeled = np.vstack([X1_labeled, X1_new])
    X2_labeled = np.vstack([X2_labeled, X2_new])
    y_labeled = np.concatenate([y_labeled, y_new])
    
    # 移除已添加的无标签样本
    keep_mask = ~agreed
    X1_unlabeled = X1_unlabeled[keep_mask]
    X2_unlabeled = X2_unlabeled[keep_mask]
    
    # 重新训练两个分类器
    clf1.fit(X1_labeled, y_labeled)
    clf2.fit(X2_labeled, y_labeled)
    
    print(f"Iteration {it+1}: Added {len(y_new)} pseudo-labeled samples.")

# 最终模型可以对新数据的两个视角分别预测,然后投票决定最终类别

Iteration 1: Added 209 pseudo-labeled samples.

Iteration 2: Added 38 pseudo-labeled samples.

Iteration 3: Added 7 pseudo-labeled samples.

Iteration 4: Added 1 pseudo-labeled samples.

三、生成式模型GMM

# 生成式模型GMM

import numpy as np
from sklearn.datasets import make_classification
from sklearn.metrics import accuracy_score
from scipy.stats import multivariate_normal

# 1. 生成数据
# 生成一个二分类问题的数据集
X, y_true = make_classification(n_samples=1000, n_features=10, n_informative=5, 
                                n_clusters_per_class=1, random_state=42)

# 模拟半监督场景:80%的标签设为 -1 表示缺失
rng = np.random.RandomState(42)
y = y_true.copy()
unlabeled_mask = rng.rand(len(y)) < 0.8
y[unlabeled_mask] = -1

# 获取带标签数据的索引
labeled_idx = np.where(y != -1)[0]
print("有标签数据数:", len(labeled_idx))

# 2. 初始化参数
# 假设类别数与真实类别数一致(K=2)
K = len(np.unique(y_true))
n_samples, n_features = X.shape

# 初始化混合系数、均值和协方差矩阵
pi = np.zeros(K)
mu = np.zeros((K, n_features))
Sigma = np.zeros((K, n_features, n_features))
for k in range(K):
    # 利用带标签数据初始化参数
    X_k = X[y == k]
    if len(X_k) > 0:
        pi[k] = len(X_k) / n_samples
        mu[k] = np.mean(X_k, axis=0)
        # 保证协方差矩阵非奇异
        Sigma[k] = np.cov(X_k.T) + np.eye(n_features)*1e-6
    else:
        # 如果某个类别没有带标签样本,则随机初始化
        pi[k] = 1.0 / K
        mu[k] = np.random.randn(n_features)
        Sigma[k] = np.eye(n_features)

# 3. EM 算法迭代(半监督版)
max_iter = 50
tol = 1e-5

# 用于存储每个样本对每个高斯成分的责任
gamma = np.zeros((n_samples, K))

for iteration in range(max_iter):
    # E 步骤:计算责任
    for i in range(n_samples):
        if y[i] != -1:
            # 对于有标签数据,强制其责任只落在真实类别上
            gamma[i] = np.zeros(K)
            gamma[i, y[i]] = 1.0
        else:
            # 对于无标签数据,根据当前模型计算后验概率
            probs = np.array([pi[k] * multivariate_normal.pdf(X[i], mean=mu[k], cov=Sigma[k])
                              for k in range(K)])
            # 防止数值下溢
            if probs.sum() == 0:
                probs = np.ones(K)
            gamma[i] = probs / probs.sum()
    
    # M 步骤:根据责任更新参数
    pi_new = gamma.sum(axis=0) / n_samples
    mu_new = np.zeros_like(mu)
    Sigma_new = np.zeros_like(Sigma)
    
    for k in range(K):
        N_k = gamma[:, k].sum()  # 某一成分的有效样本数
        if N_k > 0:
            mu_new[k] = np.sum(gamma[:, k][:, None] * X, axis=0) / N_k
            diff = X - mu_new[k]
            Sigma_new[k] = np.dot((gamma[:, k][:, None] * diff).T, diff) / N_k
            # 加入正则项避免协方差矩阵奇异
            Sigma_new[k] += np.eye(n_features) * 1e-6
        else:
            mu_new[k] = mu[k]
            Sigma_new[k] = Sigma[k]
    
    # 判断是否收敛,这里用均值变化作为衡量标准
    if np.linalg.norm(mu_new - mu) < tol:
        print("收敛于迭代次数:", iteration)
        break
    
    # 更新参数
    pi = pi_new
    mu = mu_new
    Sigma = Sigma_new

# 4. 预测与评估
# 根据最终的责任分布,将每个样本分配到后验概率最大的成分
y_pred = np.argmax(gamma, axis=1)

# 仅在有标签数据上评估分类准确率
accuracy = accuracy_score(y_true[labeled_idx], y_pred[labeled_idx])
print("半监督生成模型在有标签数据上的准确率:", accuracy)

有标签数据数: 199

收敛于迭代次数: 6

半监督生成模型在有标签数据上的准确率: 1.0

四、基于图的半监督算法-标签传播

# 基于图的半监督算法-标签传播

import numpy as np
from sklearn.datasets import load_iris
from sklearn.semi_supervised import LabelSpreading
from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt

# 1. 加载数据
iris = load_iris()
X = iris.data
y_true = iris.target  # 保存真实标签
y = np.copy(y_true)
print("总样本数:",len(y))

# 2. 模拟半监督场景:随机将50%的标签置为未标注(-1)
rng = np.random.RandomState(42)
mask = rng.rand(len(y)) < 0.5  # 50%概率
y[mask] = -1
print("有标签样本数量:", np.sum(y != -1))

# 3. 构建并训练 LabelSpreading 模型
# 使用基于k近邻的核函数,构造样本间的图
label_spread = LabelSpreading(kernel='knn', n_neighbors=7, alpha=0.2)
label_spread.fit(X, y)

# 4. 获取传播后的标签
y_pred = label_spread.transduction_

# 5. 评估效果:仅在原有标签(置为 -1)上计算准确率
labeled_idx = np.where(y == -1)[0]
print("无标记样本位置:",labeled_idx)
print("无标记样本数:",len(labeled_idx))
accuracy = accuracy_score(y_true[labeled_idx], y_pred[labeled_idx])
print("无标签样本上的准确率:", accuracy)

# 可选:可视化数据分布与传播结果(降维至二维)
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
X_reduced = pca.fit_transform(X)

plt.figure(figsize=(8, 4))
plt.subplot(1, 2, 1)
plt.title("原始真实标签")
plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=y_true, cmap='viridis', edgecolor='k', s=40)
plt.subplot(1, 2, 2)
plt.title("LabelSpreading传播结果")
plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=y_pred, cmap='viridis', edgecolor='k', s=40)
plt.tight_layout()
plt.show()

总样本数: 150

有标签样本数量: 70

无标记样本位置: [ 0 4 5 6 10 13 14 15 16 18 19 21 22 23 24 26 29 31 32 36 37 39 40 41 42 44 46 49 56 57 58 59 60 61 63 64 66 68 71 72 77 78 79 82 83 84 85 89 90 95 97 98 99 100 102 105 106 108 109 110 111 117 122 123 124 125 128 130 131 132 133 135 138 141 142 143 144 145 148 149]

无标记样本数: 80

无标签样本上的准确率: 0.9625

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值