在本例中,将探讨如何使用半监督学习技术对20个新闻组数据集进行分类。这个数据集将自动下载。可以通过指定类别名称或设置为None来调整类别数量,以获取全部20个类别。数据集中包含2823个文档,涵盖5个类别。
在全监督学习中,使用SGD分类器对所有训练样本进行训练。训练样本数量为2117,未标记样本数量为0。在测试集上的微平均F1分数为0.885。
当训练样本数量减少到411,未标记样本数量为0时,SGD分类器在测试集上的微平均F1分数下降到0.773。这说明训练样本数量对模型性能有显著影响。
自训练分类器在只有20%的训练数据上进行训练,其余数据为未标记。在迭代过程中,分类器逐渐为未标记样本添加标签。经过10次迭代,分类器在测试集上的微平均F1分数达到0.834,高于仅使用20%标记数据的全监督SGD分类器。
标签传播方法同样在20%的训练数据上进行训练,其余数据为未标记。然而,这种方法在测试集上的微平均F1分数仅为0.644,低于自训练分类器。
以下是使用Python和scikit-learn库实现上述半监督分类方法的代码示例。首先,导入必要的库和函数。
import numpy as np
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer
from sklearn.linear_model import SGDClassifier
from sklearn.metrics import f1_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import FunctionTransformer
from sklearn.semi_supervised import LabelSpreading, SelfTrainingClassifier
接下来,加载20个新闻组数据集的前五个类别,并设置SGD分类器的参数。然后,构建了三个管道:全监督管道、自训练管道和标签传播管道。
# 加载数据集
data = fetch_20newsgroups(subset="train", categories=["alt.atheism", "comp.graphics", "comp.os.ms-windows.misc", "comp.sys.ibm.pc.hardware", "comp.sys.mac.hardware"])
print("%d documents" % len(data.filenames))
print("%d categories" % len(data.target_names))
# 参数设置
sdg_params = dict(alpha=1e-5, penalty="l2", loss="log_loss")
vectorizer_params = dict(ngram_range=(1, 2), min_df=5, max_df=0.8)
# 全监督管道
pipeline = Pipeline([
("vect", CountVectorizer(**vectorizer_params)),
("tfidf", TfidfTransformer()),
("clf", SGDClassifier(**sdg_params)),
])
# 自训练管道
st_pipeline = Pipeline([
("vect", CountVectorizer(**vectorizer_params)),
("tfidf", TfidfTransformer()),
("clf", SelfTrainingClassifier(SGDClassifier(**sdg_params), verbose=True)),
])
# 标签传播管道
ls_pipeline = Pipeline([
("vect", CountVectorizer(**vectorizer_params)),
("tfidf", TfidfTransformer()),
("toarray", FunctionTransformer(lambda x: x.toarray())),
("clf", LabelSpreading()),
])
最后,定义了一个函数来评估和打印分类器的性能指标,并在主函数中调用这个函数来训练和评估分类器。
def eval_and_print_metrics(clf, X_train, y_train, X_test, y_test):
print("Number of training samples:", len(X_train))
print("Unlabeled samples in training set:", sum(1 for x in y_train if x == -1))
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
print("Micro-averaged F1 score on test set: %0.3f" % f1_score(y_test, y_pred, average="micro"))
print("-"*10)
print()
if __name__ == "__main__":
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(X, y)
print("Supervised SGDClassifier on 100% of the data:")
eval_and_print_metrics(pipeline, X_train, y_train, X_test, y_test)
# 选择20%的训练数据集
y_mask = np.random.rand(len(y_train)) < 0.2
X_20, y_20 = map(list, zip(*((x, y) for x, y, m in zip(X_train, y_train, y_mask) if m)))
print("Supervised SGDClassifier on 20% of the training data:")
eval_and_print_metrics(pipeline, X_20, y_20, X_test, y_test)
# 将非掩码子集设置为未标记
y_train[~y_mask] = -1
print("SelfTrainingClassifier on 20% of the training data (rest is unlabeled):")
eval_and_print_metrics(st_pipeline, X_train, y_train, X_test, y_test)
print("LabelSpreading on 20% of the data (rest is unlabeled):")
eval_and_print_metrics(ls_pipeline, X_train, y_train, X_test, y_test)