Day 12 : 弱监督式标注资料 Snorkel (spam 入门篇)

  • 当您需要更高效率标注大量资料时,人工标注不符合自动化的机械学习需求,采用靠着程序写条件就分类完成的 Snokel 就可以参考。
  • 而且在2021年 AI 台湾人工智慧年会,该 Snokel.ai 的 CEO 以 The Future of Data-Centric AI 为题在活动第1日演讲,有兴趣可以关注活动。
  • Colab 实作范例

什麽是 Snorkel

  • Snorkel 是一种「无需手动标记」即可以用程序构建和管理训练数据集的系统,源自 2016 年史丹佛大学的研究成果。在 Snorkel 中,可以在数小时或数天内开发大型训练数据集,节省人工标注时间。
  • Snorkel 目前公开了三个关键的程序化操作:
    • 标记数据 Labeling data :

      • 例如使用启发式规则或远程监督技术。
      • 以撰写 Python 函数的方式撰写分类条件。
    • 转换数据 Transforming data :

      • 例如旋转或拉伸图像以执行数据增强。
      • 数据增强常见於电脑视觉影像资料集,透过图片随机旋转、变形等方式增加训练资料,而文字也可以透过同义词进行数据增强。
      • 使用生成模型来估计不同标记函数的精度,然後重新加权并组合他们的标签以产生一组概率训练标签,有效解决新数据清洗和集成问题;
    • 资料切片 Slicing data :

      • 这些标签用於训练判别模型,将数据分成不同的关键子集以进行监控或有针对性的改进。

实际步骤

我们将完成五个基本步骤,透过官方范例 YouTube comments 资料集示范,具体程序执行笔者将官网范例调整为可以在 Colab 执行的范例,您可以透过 Colab 实作

  • 简单定义3个标签并接续後续流程:
    ABSTAIN = -1
    NOT_SPAM = 0
    SPAM = 1
    

1. 编写标签函数 (LFs):

  • 将用 LFs 以编程方式标记我们未标记的数据集,而不是手动标记任何训练数据。 以下为官方导览介绍的3种LF函数写法:

  • 关键字判别、正规表达式判别与用外部模组判别。

    from snorkel.labeling import labeling_function
    
    # 关键字'my'筛选
    @labeling_function()
    def lf_keyword_my(x):
        """Many spam comments talk about 'my channel', 'my video', etc."""
        return SPAM if "my" in x.text.lower() else ABSTAIN
    
    #正规表达式筛选
    import re
    
    @labeling_function()
    def lf_regex_check_out(x):
        """Spam comments say 'check out my video', 'check it out', etc."""
        return SPAM if re.search(r"check.*out", x.text, flags=re.I) else ABSTAIN
    
    #结合模组筛选
    from textblob import TextBlob
    
    @labeling_function()
    def lf_textblob_polarity(x):
        """
        We use a third-party sentiment classification model, TextBlob.
        We combine this with the heuristic that non-spam comments are often positive.
        """
        return NOT_SPAM if TextBlob(x.text).sentiment.polarity > 0.3 else ABSTAIN
    
  • 更多类型的标记函数(包括文本以外的数据模式),请参阅其他官方范例实际示例

2. 建模和组合 LF:

  • 将前述设定好的LabelModel LF 组合为 list,将 LFs 应用於伪标注的训练数据。
  • 由於标注函数 LFs 的准确度和相关性未知,输出标签可能会重叠和冲突。 snorkel.labeling.model.LabelModel 可以自动估计它们的准确性和相关性,重新加权和组合它们的标签,并生成我们最终的乾净、集成的训练标签集:
    from snorkel.labeling.model import LabelModel
    from snorkel.labeling import PandasLFApplier
    
    # set LFs
    lfs = [ lf_keyword_my, 
            lf_regex_check_out, 
            lf_short_comment, 
            lf_textblob_polarity
            ]
    
    # Apply the LFs to the unlabeled training data
    applier = PandasLFApplier(lfs)
    L_train = applier.apply(df_train)
    
    # Train the label model and compute the training labels
    label_model = LabelModel(cardinality=2, verbose=True)
    label_model.fit(L_train, n_epochs=500, log_freq=50, seed=123)
    df_train["label"] = label_model.predict(L=L_train, tie_break_policy="abstain")
    
  • 由於前述LabelModel可能很多数据为标注结果为放弃标示状态的ABSTAIN = -1,为清理训练资料集,将明显标注SPAMNOT_SPAM的训练资料集保留进行後去处理。
    df_train = df_train[df_train.label != ABSTAIN]
    

3. 编写数据增强的TF函数

  • 接着透过建立一个TF函数来增强这个标记的训练集。

  • 以下get_synonyms()nltk.wordnet获取单词的同义词。

    import random
    import nltk
    from nltk.corpus import wordnet as wn
    
    nltk.download("wordnet", quiet=True)
    
    def get_synonyms(word):
        """Get the synonyms of word from Wordnet."""
        lemmas = set().union(
            *[s.lemmas() for s in wn.synsets(word)]
            )
        return list(
            set(l.name().lower().replace("_", " ") for l in lemmas) - {word}
            )
    
  • 使用 TF snorkel.augmentation.transformation_function 做为装饰子,自订 tf_replace_word_with_synonym() 函数将生成的同义词加入训练资料集。

    from snorkel.augmentation import transformation_function
    
    @transformation_function()
    def tf_replace_word_with_synonym(x):
        """Try to replace a random word with a synonym."""
        words = x.text.lower().split()
        idx = random.choice(range(len(words)))
        synonyms = get_synonyms(words[idx])
        if len(synonyms) > 0:
            x.text = " ".join(words[:idx] + 
                              [synonyms[0]] + 
                              words[idx + 1 :]
                              )
            return x
    
  • 将自订 TF 函数加入训练数据集。

    from snorkel.augmentation import ApplyOnePolicy, PandasTFApplier
    
    tf_policy = ApplyOnePolicy(n_per_original=2, keep_original=True)
    tf_applier = PandasTFApplier([tf_replace_word_with_synonym], tf_policy)
    df_train_augmented = tf_applier.apply(df_train)
    
  • 更多数据增强的调整可参阅 Spam TFs tutorial

4. 建立切片函数 Slicing Function

  • Snorkel 的 Slicing Function 可用以监控特定切片,以及透过针对不同切片增加特徵以提高模型性能。

  • 延续 Youtube 评论之中可能有恶意连结的想法,为此撰写一个查找可疑缩网址的程序,这对找出恶意垃圾评论可能很关键。设定好 SF 可监控此切片的性能:

    from snorkel.slicing import slicing_function
    
    
    @slicing_function()
    def short_link(x):
        """
        Return whether text matches common pattern 
        for shortened ".ly" links.
        """
        return int(bool(re.search(r"\w+\.ly", x.text)))
    

5. 训练分类器

  • Snorkel 的最终目标是创建一个标注完成的训练资料集,然後将其插入任意机器学习框架(例如 TensorFlow、Keras、PyTorch、Scikit-Learn、Ludwig、XGBoost),以训练强大的机器学习模型。
  • 接续范例,将前述第3步完成的训练资料集df_train_augmented,以 Scikit-Learn 的 n-gram 逻辑回归模型进行推论,完成整个运用Snorkel 弱监督分类模型。
    from sklearn.feature_extraction.text import CountVectorizer
    from sklearn.linear_model import LogisticRegression
    
    train_text = df_train_augmented.text.tolist()
    X_train = CountVectorizer(ngram_range=(1, 2)).fit_transform(train_text)
    
    clf = LogisticRegression(solver="lbfgs")
    clf.fit(X=X_train, y=df_train_augmented.label.values)
    

小结

  • Snorkel 透过程序逻辑标注程 Labeling data ,透过数据增强方式自动化转换数据 Transforming data ,并且可以切片监控特定子资料集 Slicing data ,好处是可以轻易地融入机械学习系统工作流程,并且有自动标注的好处,标注水准还不错。
  • 虽然好用,但官方范例较复杂,希望能整理一份方便使用的指引,供您後续标注资料的参考。

参考


<<:  [iT铁人赛Day12]JAVA回圈范例

>>:  [13th][Day4] 容器四五事

【领域展开 23 式】 Page & Post ,双 P 关系确认

Page & Post 傻傻分不清楚 由於前两天在研究 Menu,发现设定 Menu 的时候...

Day08 - 寻找看板

今天来做搜寻看板的部分,首先Layout我先简单的放一个EditText以及Button,点击But...

[Day30] Flutter - App Icon(final)

前言 Hi, 我是鱼板伯爵今天铁人赛的最後一天了感谢大家的支持,不知道大家对这个架构是不是有一点感觉...

Day17-JDK堆栈跟踪工具:jstack(二)

前言 延续着上篇内容,这篇要继续来介绍jstack有些什麽options可以使用 options 介...

Day18 蒜香鲷鱼义大利面

昨晚群组热烈的讨论鱼排,从土魠鱼排、澎湖白金土魠鱼排、白鲳、鮸鱼、嘉鱲等等,让人好想来块煎的恰恰的鱼...