【机器学习】半监督学习

de9ff55b88b0a39cc0902ef9d0fc2d28.png

0b813ea581265e59ed39d18593264d81.png

一、问题假设

要利用无标签样本进行训练,必须对样本的分布进行假设?

98ac9450ddfbacdfcdfd5631af469a22.png

231f060d7d9c8aa83fe5187ebbd53671.png

二、启发式算法

d3e5b4d9e8a0fa48025fc31800b7c5c3.png

a40837166de2801398905d8e3544c57a.png

61e7cd5cce3492c4d1627bb41699bb0e.png

自训练和协同训练是两种常用的半监督学习的方法,它们的主要区别在于使用的模型的数量和类型。

  • 自训练:自训练是一种使用单个模型的半监督学习的方法,它的过程是先用有标签的数据训练一个初始的模型,然后用这个模型对无标签的数据进行预测,选择一些预测结果最有信心的数据作为新的有标签的数据,加入到原来的有标签的数据集中,再用这个扩充的数据集训练一个新的模型,重复这个过程直到满足某个终止条件。自训练的优点是简单和高效,但是缺点是可能会放大模型的初始偏差,导致错误的标签传播。

  • 协同训练:协同训练是一种使用多个模型的半监督学习的方法,它的过程是先用有标签的数据训练两个或多个不同的模型,然后用这些模型分别对无标签的数据进行预测,选择一些预测结果最有信心且不一致的数据作为新的有标签的数据,分别加入到对应的有标签的数据集中,再用这些扩充的数据集训练新的模型,重复这个过程直到满足某个终止条件。协同训练的优点是能够利用模型之间的多样性和互补性,减少错误的标签传播,但是缺点是需要设计合适的模型和选择合适的数据,否则可能会降低协同训练的效果。

三、生成模型

787bdd69ded2d1b472b0da2a13167244.png

7054a16a1f95b6edd24cf56695a8a4d3.png

2cc1dc9fb665eceb1d4f5925618fab75.png

使用高斯混合模型的半监督学习生成模型的原理

d7946a08771ecfbec286f9ca200d4738.png

eb83e3dd6c40970323c3dfe21205336c.png

这是一个半监督学习的示例,使用高斯混合模型(GMM)来对数据进行聚类和分类

# 导入numpy库,用于进行数值计算和矩阵操作
import numpy as np
# 导入sklearn库中的make_blobs函数,用于生成模拟数据
from sklearn.datasets import make_blobs
# 导入sklearn库中的GaussianMixture类,用于创建和训练GMM模型
from sklearn.mixture import GaussianMixture


# 生成数据,其中有三个类别,每个类别有两个特征
# X是一个1000行2列的矩阵,表示1000个数据点的特征值
# y是一个长度为1000的向量,表示每个数据点的真实类别
X, y = make_blobs(n_samples=1000, n_features=2, centers=3, random_state=0)


# 随机选择一部分数据作为标记数据,其余的作为未标记数据
# n_labeled是标记数据的数量,这里设为100
n_labeled = 100
# indices是一个长度为1000的向量,表示对数据点的随机排列
indices = np.random.permutation(X.shape[0])
# X_labeled是一个100行2列的矩阵,表示标记数据的特征值
X_labeled = X[indices[:n_labeled]]
# y_labeled是一个长度为100的向量,表示标记数据的真实类别
y_labeled = y[indices[:n_labeled]]
# X_unlabeled是一个900行2列的矩阵,表示未标记数据的特征值
X_unlabeled = X[indices[n_labeled:]]


# 用标记数据来初始化GMM的参数,假设有三个高斯分布
# gmm是一个GaussianMixture对象,表示GMM模型
gmm = GaussianMixture(n_components=3, random_state=0)
# gmm.means_init是一个3行2列的矩阵,表示GMM模型的初始均值
# 这里用标记数据的均值来初始化GMM模型的均值
gmm.means_init = np.array([X_labeled[y_labeled == i].mean(axis=0) for i in range(3)])
# gmm.fit是一个方法,用于根据标记数据来训练GMM模型
# 这里用标记数据的特征值和类别来训练GMM模型
gmm.fit(X_labeled, y_labeled)


# 用未标记数据来更新GMM的参数,用EM算法来迭代优化
# gmm.fit是一个方法,用于根据未标记数据来更新GMM模型
# 这里用未标记数据的特征值来更新GMM模型
gmm.fit(X_unlabeled)


# 用更新后的GMM来计算每个数据点的后验概率
# probs是一个1000行3列的矩阵,表示每个数据点属于每个高斯分布的后验概率
# gmm.predict_proba是一个方法,用于根据GMM模型来计算后验概率
# 这里用GMM模型和所有数据的特征值来计算后验概率
probs = gmm.predict_proba(X)


# 用后验概率来对数据进行分类,将数据分配给后验概率最大的高斯分布所对应的类别
# y_pred是一个长度为1000的向量,表示每个数据点的预测类别
# probs.argmax是一个方法,用于沿着指定的轴返回最大值的索引
# 这里用后验概率的最大值所在的列的索引来表示预测类别
y_pred = probs.argmax(axis=1)


# 计算分类的准确率
# accuracy是一个浮点数,表示预测类别和真实类别的一致比例
# np.mean是一个函数,用于计算平均值
# 这里用预测类别和真实类别的相等情况的平均值来表示准确率
accuracy = np.mean(y_pred == y)
# print是一个函数,用于输出结果
# 这里用字符串格式化的方法,将准确率保留两位小数并输出
print(f"Accuracy: {accuracy:.2f}")

输出:Accuracy: 0.92

四、低密度分割

半监督支持向量机

7bf70995beaccb93814e3a85dea92b17.png

SSVM

474fc37c15d014cd809523e81de56361.png

# 导入相关的库
import numpy as np
from sklearn.datasets import make_blobs
from sklearn.svm import SVC


# 生成数据,其中有三个类别,每个类别有两个特征
X, y = make_blobs(n_samples=1000, n_features=2, centers=3, random_state=0)


# 随机选择一部分数据作为标记数据,其余的作为未标记数据
n_labeled = 100
indices = np.random.permutation(X.shape[0])
X_labeled = X[indices[:n_labeled]]
y_labeled = y[indices[:n_labeled]]
X_unlabeled = X[indices[n_labeled:]]


# 用标记数据来训练一个初始的SVM分类器,假设有三个类别
svm = SVC(C=1.0, kernel='rbf', gamma='auto', probability=True)
svm.fit(X_labeled, y_labeled)


# 用未标记数据来改进SVM分类器,用自训练的方法
n_iter = 10 # 迭代次数
n_add = 10 # 每次迭代添加的数据个数
for i in range(n_iter):
  # 用SVM分类器来对未标记数据进行预测,得到每个数据点属于每个类别的概率
  probs = svm.predict_proba(X_unlabeled)
  # 选择预测结果最有信心的一部分数据,即概率最大的数据
  max_probs = probs.max(axis=1)
  max_indices = max_probs.argsort()[-n_add:]
  # 将这些数据加入到标记数据中,用其预测的类别作为标签
  X_labeled = np.concatenate([X_labeled, X_unlabeled[max_indices]])
  y_labeled = np.concatenate([y_labeled, probs[max_indices].argmax(axis=1)])
  # 用这些数据来重新训练SVM分类器
  svm.fit(X_labeled, y_labeled)
  # 从未标记数据中移除这些数据
  X_unlabeled = np.delete(X_unlabeled, max_indices, axis=0)


# 用改进后的SVM分类器来对所有数据进行分类
y_pred = svm.predict(X)


# 计算分类的准确率
accuracy = np.mean(y_pred == y)
print(f"Accuracy: {accuracy:.2f}") # 输出 0.9

TSVM     Transductive SVM

52e2839a546a06bdfc04c0eb50389308.png

import numpy as np
import sklearn.svm as svm
import time
class TransductiveSVM(svm.SVC):
    def __init__(self,kernel="rbf",Cl=1,Cu=0.01,gamma=0.1,X2=None):
        '''
        Initial TSVM
        Parameters
        ----------
        kernel: kernel of svm
        Cl: Penalty Inductive SVM
        Cu: Penalty Unlabeled set
        gamma: gamma for rbf kernel
        X2: Unlabeled set(only features)
                np.array, shape:[n2, m], n2: numbers of samples without labels, m: numbers of features




        '''
        self.Cl=Cl
        self.Cu=Cu
        self.kernel = kernel
        self.gamma=gamma
        self.clf=svm.SVC(C=self.Cl,kernel=kernel,gamma=self.gamma,probability=True)
        self.Yresult=None
        self.X2=X2


    def fit(self, X1, Y1):
        '''
        Train TSVM by X1, Y1, X2(X2 is passed on init)
        Parameters
        ----------
        X1: Input data with labels
                np.array, shape:[n1, m], n1: numbers of samples with labels, m: numbers of features
        Y1: labels of X1
                np.array, shape:[n1, ], n1: numbers of samples with labels




        '''


        t=time.time()
        X2=self.X2
        Y1[Y1!=+1]=-1
        Y1 = np.expand_dims(Y1, 1)
        ratio=sum(1 for i in Y1 if i==+1)/len(X1)
        num_plus=int(len(X2)*ratio) #number of positive example as describe in joachims svm


        N = len(X1) + len(X2)


        sample_weight = np.zeros(N)
        sample_weight[:len(X1)] = self.Cl




        self.clf.fit(X1, Y1,sample_weight=sample_weight[:len(X1)])  #classify the num_plus examples with the highest value with +1, other -1
        Y2=np.full(shape=self.clf.predict(X2).shape,fill_value=-1)
        Y2_d = self.clf.decision_function(X2)
        index=Y2_d.argsort()[-num_plus:][::-1]
        for item in index:
            Y2[item]=+1




        #INIT CMINUS E CLUS
        #C_minus=.00001
        C_minus=.00001
        C_plus=.00001*(num_plus/(len(X2)-num_plus))
        for i in range(len(Y2)):
            if(Y2[i]==+1):
                sample_weight[len(X1)+i]=C_plus
            else:
                sample_weight[len(X1)+i]=C_minus




        Y2 = np.expand_dims(Y2, 1)
        X3 = np.vstack((X1, X2))
        Y3 = np.vstack((Y1, Y2))
        k=0




        while (C_minus<self.Cu or C_plus<self.Cu): #LOOP 1
            self.clf.fit(X3, Y3, sample_weight=sample_weight)
            Y3 = Y3.reshape(-1)
            #slack=Y3*(self.clf.decision_function(X3))
            slack = Y3*self.clf.decision_function(X3)
            idx=np.argwhere(slack<1)
            eslackD=np.zeros(shape=slack.shape)
            for index in idx:
                eslackD[index]=1-slack[index]
            eslack2=np.zeros(shape=Y2.shape)
            eslack=eslackD[:len(X1)] #EPSILON OF LABELLED DATA
            eslack2=eslackD[len(X1):] # EPSILON FOR UNLABELED DATA




            condition=self.checkCondition(Y2,eslack2) #CONDITION OF LOOP
            l=0
            while(condition): #LOOP 2
                l+=1


                i,j=self.getIndexCondition(Y2,eslack2)  #TAKE A POSITIVE AND NEGATIVE SET
                #print("Switching at loop "+str(k)+"."+str(l)+"     index: "+str(i)+" "+str(j))
                #print("Switching values: "+str(eslack2[i])+" "+str(eslack2[j]))
                Y2[i]=Y2[i]*-1 #SWITCHING EXAMPLE
                Y2[j]= Y2[j]*-1


                sample_weight[len(X1)+i],sample_weight[len(X1)+j]=sample_weight[len(X1)+j],sample_weight[len(X1)+i] #UPDATE THE WEIGHT


                Y3=np.concatenate((Y1,Y2),axis=0)
                self.clf.fit(X3, Y3, sample_weight=sample_weight) #TRAINING WITH NEW LABELLING
                Y3 = Y3.reshape(-1)
                #slack =Y3*(self.clf.decision_function(X3))
                slack = Y3*self.clf.decision_function(X3)
                idx = np.argwhere(slack < 1)
                eslackD = np.zeros(shape=slack.shape)


                for index in idx:
                    eslackD[index] = 1 - slack[index]


                eslack = eslackD[:len(X1)]
                eslack2 = np.zeros(shape=Y2.shape)
                eslack2 = eslackD[len(X1):]
                condition = self.checkCondition(Y2, eslack2)
            k+=1
            #print(eslack2)
            C_minus=min(2*C_minus,self.Cu)
            C_plus=min(2*C_plus,self.Cu)
            #print("Loop "+str(k)+" Ctest="+str(self.Cu)+"   Cplus="+str(C_plus)+"   Cminus="+str(C_minus))


            for i in range(len(Y2)):
                if (Y2[i] == 1):
                    sample_weight[len(X1)+i] = C_plus
                else:
                    sample_weight[len(X1)+i] = C_minus


        self.Yresult=Y2
        Y3 = np.concatenate((Y1, Y2), axis=0)
        Y3=Y3.reshape(-1)
        end=time.time()
        print("The training finish in  "+str(end-t)+"  seconds")
        return self


    def checkCondition(self,Y,slack):
        '''
        Check condition of the loop 2
        Parameters
        ----------


        Y: labels of X2
                np.array, shape:[n1, ], n1: numbers of samples with semi-labels


        slack: slack variable for unlabeled set
                np.array, shape:[n1, ], n1: numbers of with semi-labels




        '''
        condition=False
        M=len(Y)
        for i in range(M):
            for j in range(M):
                if((Y[i]!=Y[j]) and (slack[i]>0) and (slack[j]>0) and ((slack[i]+slack[j])>2.001)):
                    condition=True
                    return condition
        return condition


    def getIndexCondition(self,Y,slack):
        '''
        Get index that satisfies condition of loop 2
        Parameters
        ----------


        Y: labels of X2
                np.array, shape:[n1, ], n1: numbers of samples with semi-labels


        slack: slack variable for unlabeled set
                np.array, shape:[n1, ], n1: numbers of with semi-labels




        '''
        M=len(Y)
        for i in range(M):
            for j in range(M):
                if(Y[i]!=Y[j] and slack[i]>0 and slack[j]>0 and (slack[i]+slack[j]>2.001)):
                    return i,j


    def predict(self, X):
        return self.clf.predict(X)


    def predict_proba(self):
        return self.clf.predict_proba()


    def decision_function(self, X):
        return self.clf.decision_function(X)


    def getResult(self):
        return self.Yresult


if __name__ == '__main__':
    from sklearn.datasets import load_breast_cancer
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import accuracy_score, precision_score, recall_score,roc_curve,f1_score


    X,y = load_breast_cancer(return_X_y=True)
    y[y==0]=-1
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.985)
    print(X_train.shape,X_test.shape)
    gammas=[0.001,0.01,0.2,0.4,0.8,1.5,3]
    cls=[1,2,5,10,20]
    c=1
    g=0.0001
    clf1 = svm.SVC(C=c,kernel="linear",gamma=g)
    #y_train=np.ravel(y_train)
    clf1.fit(X_train, y_train)


    y_svm = clf1.predict(X_test)
    f2 = accuracy_score(y_true=y_test, y_pred=y_svm)
    print("ACCURACY SVM " + str(f2))
    print("F1 SVM  ",f1_score(y_true=y_test,y_pred=y_svm))
    print("                                ")
    #print(clf1.coef_[0])
    clf=TransductiveSVM(kernel="linear",Cl=c,Cu=0.5,X2=X_test,gamma=g)
    #y_train=np.ravel(y_train)
    clf.fit(X_train,y_train)
    y_predicted=clf.predict(X_test)
    f = accuracy_score(y_true=y_test, y_pred=y_predicted)
    print("ACCURACY TSVM " + str(f))
    print("F1 TSVM " + str(f1_score(y_true=y_test,y_pred=y_predicted)))
    #print(clf.clf.coef_[0])


    #print(y_svm)

输出:

(8, 30) (561, 30)

ACCURACY SVM 0.8645276292335116

F1 SVM   0.9025641025641026

The training finish in  4.932965517044067  seconds

ACCURACY TSVM 0.8413547237076648

F1 TSVM 0.8593996840442337

五、基于图的算法

e27cdc37a5e0233b748da8c2d089dcd9.png

78553cb048952285c3b3b9f2ca42e31a.png

使用Python实现的基于图的半监督学习算法的示例代码,它使用了标签传播(Label Propagation)的方法:

# 导入相关的库
import numpy as np
from sklearn.datasets import make_blobs
from sklearn.metrics.pairwise import rbf_kernel


# 生成数据,其中有三个类别,每个类别有两个特征
X, y = make_blobs(n_samples=1000, n_features=2, centers=3, random_state=0)


# 随机选择一部分数据作为标记数据,其余的作为未标记数据
n_labeled = 100
indices = np.random.permutation(X.shape[0])
X_labeled = X[indices[:n_labeled]]
y_labeled = y[indices[:n_labeled]]
X_unlabeled = X[indices[n_labeled:]]


# 用标记数据和未标记数据构建一个图,用高斯核函数作为相似度度量,用k近邻图作为图结构
k = 10 # 近邻的个数
sigma = 1.0 # 高斯核函数的参数
# 计算数据点之间的高斯核矩阵
K = rbf_kernel(X, gamma=1.0 / (2 * sigma ** 2))
# 对每个数据点,只保留最近的k个邻居的相似度,其余的设为0,得到一个稀疏的相似度矩阵
W = np.zeros_like(K)
for i in range(X.shape[0]):
  # 找到第i个数据点的最近的k个邻居的索引
  neighbors = K[i].argsort()[-(k + 1):-1]
  # 将这些邻居的相似度保留,其余的设为0
  W[i, neighbors] = K[i, neighbors]
# 使相似度矩阵对称,即如果i和j是邻居,那么j和i也是邻居
W = np.maximum(W, W.T)


# 用标记数据的类别信息来初始化图中的节点的标签,用一个矩阵Y表示,其中Y[i, j]表示第i个数据点属于第j个类别的概率
n_classes = len(np.unique(y)) # 类别的个数
Y = np.zeros((X.shape[0], n_classes)) # 初始化标签矩阵
# 对于标记数据,用one-hot编码表示其类别,即Y[i, j] = 1当且仅当第i个数据点属于第j个类别
for i in range(n_labeled):
  Y[indices[i], y_labeled[i]] = 1
# 对于未标记数据,用均匀分布表示其类别,即Y[i, j] = 1 / n_classes
Y[indices[n_labeled:]] = 1.0 / n_classes


# 用图中的边来传播节点的标签,用标签传播的方法
alpha = 0.99 # 平滑系数,介于0和1之间,越接近1表示越依赖于未标记数据的信息,越接近0表示越依赖于标记数据的信息
n_iter = 10 # 迭代次数
# 在每次迭代中,用以下的公式来更新标签矩阵:Y = alpha * W * Y + (1 - alpha) * Y
for i in range(n_iter):
  # 计算W * Y,即用每个数据点的邻居的标签的加权平均来更新其标签
  WY = W.dot(Y)
  # 将标记数据的标签保持不变,即用(1 - alpha) * Y来表示
  WY[indices[:n_labeled]] = Y[indices[:n_labeled]]
  # 用alpha * W * Y + (1 - alpha) * Y来更新标签矩阵
  Y = alpha * WY + (1 - alpha) * Y
  # 对每个数据点,将其标签归一化,使得其概率之和为1
  Y = Y / Y.sum(axis=1, keepdims=True)


# 用收敛后的标签矩阵来对数据进行分类,即将数据分配给概率最大的类别
y_pred = Y.argmax(axis=1)


# 计算分类的准确率
accuracy = np.mean(y_pred == y)
print(f"Accuracy: {accuracy:.2f}")  # Accuracy: 0.91

六、半监督深度学习

c5c319d9bfdd6e7f2811d7614eae8d3b.png

af859b7ae2453393a106012ad21007d8.png

142dfec5efda4e9c2e169eafd90d508f.png

ladderNet网络

186c24784c6df27fad1b767347e876c6.png

相关推荐

  1. 机器学习-监督学习

    2024-01-13 14:06:05       9 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-13 14:06:05       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-13 14:06:05       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-13 14:06:05       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-13 14:06:05       20 阅读

热门阅读

  1. go最佳实践:如何舒适地编码

    2024-01-13 14:06:05       37 阅读
  2. Entity Framework Core

    2024-01-13 14:06:05       40 阅读
  3. CentOS 7.9下JDK 1.8安装

    2024-01-13 14:06:05       33 阅读
  4. 在 Ubuntu 系统上安装和彻底卸载 MySQL

    2024-01-13 14:06:05       31 阅读
  5. Pandas实战100例 | 案例 43: 数据排序

    2024-01-13 14:06:05       31 阅读
  6. 根能抵达的节点(二分法、DFS)C++

    2024-01-13 14:06:05       31 阅读