半监督学习代码实战

sklearn官方例子——用半监督学习做数字识别
 

什么是半监督学习

半监督学习很重要,为什么呢?因为人工标注数据成本太高,现在大家参加比赛的数据都是标注好的了,那么如果老板给你一份没有标注的数据,而且有几百万条,让你做个分类什么的,你怎么办?不可能等标注好数据再去训练模型吧,所以你得会半监督学习算法。

不过我在这里先打击大家一下,用sklearn的包做不了大数据量的半监督学习,我用的数据量大概在15000条以上就要报MemoryError错误了,这个是我最讨厌的错误。暂时我还没有解决的办法,如果同志们是小数据量,那就用这个做着玩玩吧


算法流程

假设我们有一份数据集,共330个数字,其中前十个是已知的,已经标注好了,后320个是未知的,需要我们预测出来的。

  • 首先把这330个数据全部都放到半监督学习算法里,训练模型,预测那320个标签
  • 然后用某种方法(看下面代码的操作)得知这320个数据里最不确定的前5个数据,对它进行人工标注,然后把它放到之前的10个数据里,现在就有15个已知数据了
  • 这样循环个几次,已标注的数据就变多了,那么分类器的效果肯定也就变好了

  • 一共330个点,都是已经标注好的了,我们把其中的320个点赋值为-1,这样就可以假装这320个点都是没有标注的了
  • 训练一个只有10个标记点的标签传播模型
  • 然后从所有数据中选择要标记的前五个最不确定的点,把它们(带有正确标签)放到原来的10个点中
  • 接下来可以训练15个标记点(原始10个 + 5个新点)
  • 重复这个过程四次,就可以使用30个标记好的点来训练模型
  • 可以通过改变max_iterations将这个值增加到30以上

 

LabelSpreading是一个半监督学习模型

 

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
from sklearn import datasets
from sklearn.semi_supervised import label_propagation
from sklearn.metrics import classification_report,confusion_matrix

# 再加下面这个,不然会报错
from scipy.sparse.csgraph import *


digits = datasets.load_digits()
rng = np.random.RandomState(0)

# indices是随机产生的0-1796个数字,且打乱
#indices:[1081 1707  927 ... 1653  559  684]
indices = np.arange(len(digits.data))
rng.shuffle(indices)


# 取前330个数字来玩
X = digits.data[indices[:330]]
y = digits.target[indices[:330]]
images = digits.images[indices[:330]]


n_total_samples = len(y) # 330
n_labeled_points = 10 # 标注好的数据共10条
max_iterations = 5 # 迭代5次


#未标注的数据320条
#即[10 11 12 ... 329]
unlabeled_indices = np.arange(n_total_samples)[n_labeled_points:] 

f = plt.figure() # 画图用的


for i in range(max_iterations):
    if len(unlabeled_indices) == 0:
        print("no unlabeled items left to label") # 没有未标记的标签了,全部标注好了
        break
    y_train = np.copy(y)
    y_train[unlabeled_indices] = -1 #把未标注的数据全部标记为-1,也就是后320条数据
    
    lp_model = label_propagation.LabelSpreading(gamma=0.25,max_iter=5) # 训练模型
    lp_model.fit(X,y_train)
    
    predicted_labels = lp_model.transduction_[unlabeled_indices] # 预测的标签
    true_labels = y[unlabeled_indices] # 真实的标签
    print('**************************')
    print(predicted_labels)
    print(true_labels)
    print('**************************')
    cm = confusion_matrix(true_labels,predicted_labels,
                         labels = lp_model.classes_)
    
    print("iteration %i %s" % (i,70 * "_")) # 打印迭代次数
    print("Label Spreading model: %d labeled & %d unlabeled (%d total)"
         % (n_labeled_points,n_total_samples-n_labeled_points,n_total_samples))
    
    print(classification_report(true_labels,predicted_labels))
    
    print("Confusion matrix")
    print(cm)
    
    # 计算转换标签分布的熵
    # lp_model.label_distributions_作用是Categorical distribution for each item
    pred_entropies = stats.distributions.entropy(
    lp_model.label_distributions_.T)
    
    # 选择分类器最不确定的前5位数字的索引
    # 首先计算出所有的熵,也就是不确定性,然后从320个中选择出前5个熵最大的
    # numpy.argsort(A)提取排序后各元素在原来数组中的索引。具体情况可看下面
    #  np.in1d 用于测试一个数组中的值在另一个数组中的成员资格,返回一个布尔型数组。具体情况可看下面
    uncertainty_index = np.argsort(pred_entropies)[::1]
    uncertainty_index = uncertainty_index[
        np.in1d(uncertainty_index,unlabeled_indices)][:5] # 这边可以确定每次选前几个作为不确定的数,最终都会加回到训练集
    
    # 跟踪我们获得标签的索引
    delete_indices = np.array([])
    
    # 可视化前5次的结果
    if i < 5:
        f.text(.05,(1 - (i + 1) * .183),
              'model %d\n\nfit with\n%d labels' %
              ((i + 1),i*5+10),size=10)
    for index,image_index in enumerate(uncertainty_index):
        # image_index是前5个不确定标签
        # index就是0-4
        image = images[image_index]

        # 可视化前5次的结果
        if i < 5:
            sub = f.add_subplot(5,5,index + 1 + (5*i))
            sub.imshow(image,cmap=plt.cm.gray_r)
            sub.set_title("predict:%i\ntrue: %i" % (
                lp_model.transduction_[image_index],y[image_index]),size=10)
            sub.axis('off')
        
        # 从320条里删除要那5个不确定的点
        # np.where里面的参数是条件,返回的是满足条件的索引
        delete_index, = np.where(unlabeled_indices == image_index)
        delete_indices = np.concatenate((delete_indices,delete_index))
        
    unlabeled_indices = np.delete(unlabeled_indices,delete_indices)
    # n_labeled_points是前面不确定的点有多少个被标注了
    n_labeled_points += len(uncertainty_index)
    
f.suptitle("Active learning with label propagation.\nRows show 5 most"
          "uncertain labels to learn with the next model")
plt.subplots_adjust(0.12,0.03,0.9,0.8,0.2,0.45)
plt.show()

 

参考:

https://www.jianshu.com/p/a21817a81890

### 半监督学习实战教程与代码示例 以下是几个典型的半监督学习实战案例和代码示例: #### 1. 使用伪标签方法处理图像分类任务 伪标签是一种常见的半监督学习技术,其中未标注数据被赋予由模型预测的概率最高的标签。这些新生成的标签随后用于进一步训练模型。 ```python import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from sklearn.model_selection import train_test_split # 数据加载与预处理 transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))]) dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform) labeled_data, unlabeled_data = train_test_split(dataset, test_size=0.9, random_state=42) def pseudo_labeling(model, data_loader, threshold=0.8): model.eval() pseudo_labeled_data = [] with torch.no_grad(): for images, _ in data_loader: outputs = model(images) probabilities = nn.functional.softmax(outputs, dim=1) max_probs, preds = torch.max(probabilities, dim=1) mask = max_probs >= threshold selected_images = images[mask] selected_labels = preds[mask] pseudo_labeled_data.append((selected_images, selected_labels)) return pseudo_labeled_data class SimpleCNN(nn.Module): def __init__(self): super(SimpleCNN, self).__init__() self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1) self.fc1 = nn.Linear(32 * 28 * 28, 10) def forward(self, x): x = torch.relu(self.conv1(x)) x = x.view(-1, 32 * 28 * 28) x = self.fc1(x) return x model = SimpleCNN() pseudo_labeled_data = pseudo_labeling(model, unlabeled_data) # 动态更新伪标签[^5] criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters()) for epoch in range(10): # 训练循环 total_loss = 0 for batch_idx, ((images, labels), (pseudo_images, pseudo_labels)) in enumerate(zip(labeled_data, pseudo_labeled_data)): optimizer.zero_grad() combined_images = torch.cat([images, pseudo_images], dim=0) combined_labels = torch.cat([labels, pseudo_labels], dim=0) outputs = model(combined_images) loss = criterion(outputs, combined_labels) loss.backward() optimizer.step() total_loss += loss.item() print(f'Epoch {epoch}, Loss: {total_loss / len(labeled_data)}') ``` --- #### 2. FlexMatch 方法的应用 FlexMatch 是一种先进的半监督学习框架,能够动态调整阈值并选择高质量的未标注样本参与训练。这种方法特别适合于大规模数据集上的实验。 ```bash git clone https://github.com/Languege/FlexMatch.git cd FlexMatch pip install -r requirements.txt ``` 运行以下命令即可快速启动 CIFAR-10 上的 FlexMatch 实验: ```bash python main.py --arch wideresnet --num-labeled 4000 --expand-labels \ --batch-size 64 --lr 0.03 --warmup-epochs 0 --weight-decay 5e-4 \ --ema-decay 0.999 --nesterov --use-ctaugment --cta-strength 0.5 \ --mu 7 --lambda-u 1 --threshold 0.95 --out results/cifar10_flexmatch ``` 上述脚本会利用少量有标签数据以及大量无标签数据完成训练,并通过动态阈值机制筛选可靠的未标注样本来提高泛化性能。 --- #### 3. 图形嵌入与图神经网络(GNN) 对于结构化的数据(如社交网络、分子图),图形嵌入技术和 GNN 提供了强大的建模能力。下面是一个简单的例子,展示如何使用 PyTorch Geometric 进行节点分类任务。 ```python import torch from torch_geometric.datasets import Planetoid from torch_geometric.nn import GCNConv dataset = Planetoid(root='/tmp/Cora', name='Cora') class GCN(torch.nn.Module): def __init__(self): super(GCN, self).__init__() self.conv1 = GCNConv(dataset.num_node_features, 16) self.conv2 = GCNConv(16, dataset.num_classes) def forward(self, data): x, edge_index = data.x, data.edge_index x = torch.relu(self.conv1(x, edge_index)) x = torch.dropout(x, p=0.5, training=self.training) x = self.conv2(x, edge_index) return torch.log_softmax(x, dim=1) device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = GCN().to(device) data = dataset[0].to(device) optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4) model.train() for epoch in range(200): optimizer.zero_grad() out = model(data) loss = torch.nn.functional.nll_loss(out[data.train_mask], data.y[data.train_mask]) loss.backward() optimizer.step() print(f'Epoch {epoch}, Loss: {loss.item()}') # 结合部分已知标签进行训练[^3] ``` --- #### 4. 自动编码器辅助的半监督学习 自编码器可以通过重建输入信号提取有用的特征表示,进而应用于下游任务。此方法尤其适用于高维稀疏数据。 ```python import numpy as np import tensorflow as tf from tensorflow.keras.layers import Input, Dense from tensorflow.keras.models import Model input_dim = 784 # MNIST 输入维度 encoding_dim = 32 # 编码层大小 # 定义自动编码器架构 input_layer = Input(shape=(input_dim,)) encoded = Dense(encoding_dim, activation='relu')(input_layer) decoded = Dense(input_dim, activation='sigmoid')(encoded) autoencoder = Model(inputs=input_layer, outputs=decoded) autoencoder.compile(optimizer='adam', loss='binary_crossentropy') # 加载数据 (x_train, y_train), (_, _) = tf.keras.datasets.mnist.load_data() x_train = x_train.astype('float32') / 255. x_train = x_train.reshape((len(x_train), np.prod(x_train.shape[1:])))[:10000] # 只取前一万条作为训练集 # 预训练自动编码器 autoencoder.fit(x_train, x_train, epochs=50, batch_size=256, shuffle=True) # 提取编码器部分 encoder = Model(inputs=input_layer, outputs=encoded) encoded_input = Input(shape=(encoding_dim,)) decoder_layer = autoencoder.layers[-1] decoder = Model(encoded_input, decoder_layer(encoded_input)) # 将编码后的特征传递给分类器 classifier_input = encoder.output classifier_output = Dense(10, activation='softmax')(classifier_input) classifier_model = Model(encoder.input, classifier_output) classifier_model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy']) # 利用部分带标签数据微调分类器 y_train_subset = y_train[:1000] # 假设只有前一千条是有标签的 classifier_model.fit(x_train[:1000], y_train_subset, epochs=20, batch_size=32)[^2] ``` ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值