机器学习通俗易懂系列之trainingData

本文介绍如何通过规则筛选、迭代模型、聚类及clickGraph等方法自动标注数据,解决监督学习中训练集不足的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.写在前面

做Machine Learning的同学都很熟悉,很多时候主要还是有监督学习,不能跳过训练集这个问题。

有了训练集,接下来实验各种模型(Google + Github 玩转所有主流模型了解一下…),选个效率和效果适合的model,改改结构,调调参啥的就ok。

实际上,最麻烦的应该是训练集和特征工程,后者或许因为DeepLearning的出现被弱化了一些,但在很多领域还是头号种子地位。

这一篇,说说最基础的,Training Data/Labeled Data获取,没这个,你玩个球?

你不可能全靠人去大量给你标注训练集,因为时间成本+金钱成本。
为啥有bert/XLnet这种玩意儿出现,就是因为有监督的弱点太明显。

so,你是做算法的,请用你的智慧去给data打上你要的label,谢谢!

以下主要经验来源于文本分类,但可迁移许多场景,好记性不如烂笔头。

2.数据打上label

2.1 规则筛选+迭代模型

1.应该是使用最频繁、最有效的方式;

2.举个例子:
有一堆文本,现在让你给其中影视相关的打上“影视”这个label,去爬下主流的比如aiqiyi、TencentVideo的影视专名,根据命中专名的频率等信息来构建规则筛选样本

3.迭代弱模型:
然后你可以训练个弱分类器对剩余样本进行label,第一阶段review置信度高的data,第二阶段review边界部分样本,以此迭代分类器,最终模型标注所有的data,此谓Active Learning

4.update:
建议走迭代弱模型之前,多做一个操作,即规则自增/关键特征自增,仍然举例子,对于操作1获得的data,可以通过类似tf-idf的思想设计算法筛选能够代表该正样本集合的特征,例如可筛选出“剧情”,“多少集”这种的字段,此时规则进行了扩充,又可以额外标注样本。
这个步骤最好有个测试集,训练模型来判断自增加的规则是否有效。

这种方式对文本数据、结构化数据都适用。

2.2 聚类+标注样本?

1.也挺常见,但是因为需要特征向量,所以稍微麻烦一些,具体的聚类算法以及分布式使用后续再说;

2.特征向量的做法:对于文本有TF-IDF,LDA主题模型,预训练模型的向量提取如BERT提取、word2vec词向量求和/平均转化为文本向量等等,对于结构化数据有特征工程后的产物(可能需要PCA/SVD);

3.向量记得比较下是否归一化更好;

4.聚类的簇多一些好;

5.簇的label可以依据上一种规则筛选方式的Labeled Data确认,比如使用出现频率最大的label作为该簇的label,值得注意的是这种方法需要考虑样本分布比例,举个例子:正样本与负样本真实比例为1:10(大致估计,或者少量抽样标注);那么一个簇中,Labeled Data正负样本比例为4:6,尽管负样本多,但是可能说这个簇更偏向于正样本而不是负样本。

这里有一个要注意的,向量是需要自己想办法构建的,除了前面的特征工程、文本的vector,构建行为图然后node2vec也是一个好路子,比如用户的商品购买行为、连续搜索行为、评论行为…

2.3 clickGraph/co-click挖掘

1.这个原理很简单,举个例子,很多影视query,点击集中于某几个网站如 www.aiqiyi.com,那么反过来,点击这些网站相关的可以认为是影视query;

2.clickGraph这是一个论文,自己去搜下,原理是可以理解为协同过滤(可以看做矩阵分解)+ 部分labeled Data有监督loss最小化

3.注意截断url,这里就有说法了,一级、二级还是三级,看实际情况;

4.可以举一反三到其他领域;

5.代码如下,主要是一些矩阵操作,可以用spark自带的,我因为具体情况需要自己也写了,大致复现论文,供参考,抛砖引玉。

package F_DistribClickGraph

import org.apache.spark.sql.DataFrame
import org.apache.spark.sql.functions.{col, lit, sum}

import scala.collection.mutable
import scala.collection.mutable.ListBuffer

object F_Utils {
    //========================自定义矩阵的各种运算,可以看看===================
    //query url 生成CoordinateMatrix矩阵,这个矩阵要求行列索引为Long型,麻烦
    //自定义一个索引为任意类型可以满足的矩阵运算函数,满足坐标矩阵CoordinateMatrix的形式
    def matrix_multi(dataFrame1: DataFrame,dataFrame2: DataFrame):DataFrame = {
        val d1_row_name = dataFrame1.columns(0)
        val d1_col_name = dataFrame1.columns(1)
        val d1_value_name = dataFrame1.columns(2)
        val d2_row_name = dataFrame2.columns(0)
        val d2_col_name = dataFrame2.columns(1)
        val d2_value_name = dataFrame2.columns(2)

        val mergeDF = dataFrame1.join(dataFrame2,dataFrame1.col(d1_col_name) === dataFrame2(d2_row_name))
        val predictDF = mergeDF.
          withColumn("value_multi", col(d1_value_name)*col(d2_value_name)).
          drop(d1_col_name,d2_row_name,d1_value_name,d2_value_name).
          groupBy(d1_row_name,d2_col_name).
          agg(sum("value_multi") as "value_multi_new")
        predictDF
    }

    //矩阵转置
    //df.select(df.columns(1),df.columns(0),df.columns(2))
    def matrix_trans(df:DataFrame):DataFrame={
        df.select(col(df.columns(1)) as df.columns(1)+"row_T",
            col(df.columns(0)) as df.columns(0)+"column_T",
            col(df.columns(2)) as df.columns(2)+"value_T")
    }

    //矩阵数乘
    def matrix_num(df:DataFrame,num:Float):DataFrame={
        df.withColumn(df.columns(2),lit(num) * col(df.columns(2)))
    }

    //矩阵加减,把行列名做成相同的,外面withColumn
    //多列key来join,两种写法,注意加减这里,不能是inner join 否则A矩阵有的元素在B的位置没有就会被抛弃,这和矩阵相乘不同
    //val Lead_all = Leads.join(Utm_Master,Leaddetails.columns("LeadSource","Utm_Source","Utm_Medium","Utm_Campaign") ==Utm_Master.columns("LeadSource","Utm_Source","Utm_Medium","Utm_Campaign"),"left")
    def matrix_add(dataFrame1: DataFrame,dataFrame2: DataFrame,tag:String):DataFrame = {
        val d1_row_name = dataFrame1.columns(0)
        val d1_col_name = dataFrame1.columns(1)
        val mergeDF = dataFrame1.join(dataFrame2,Seq(d1_row_name,d1_col_name),"fullouter").na.fill(0.0)
        var predictDF = mergeDF.withColumn("value_add",col(dataFrame1.columns(2))+col(dataFrame2.columns(2)))
        if(tag == "-"){
            predictDF = mergeDF.withColumn("value_add",col(dataFrame1.columns(2))-col(dataFrame2.columns(2)))
        }
        predictDF.select(d1_row_name,d1_col_name,"value_add")
    }

    //矩阵value之和
    def value_sum(df:DataFrame):Double = {
        df.agg(sum(df.columns(2))).first.getDouble(0)
    }

}
val pow_udf = udf[Float, Float](value_float => pow(value_float.toDouble,-0.5).toFloat)
//W矩阵,D矩阵
//=================================clickGraph算法===================================
val matrix_W = spark.read.parquet("hdfs://xxxx/url_filter_toCGA_W")
val matrix_WWT = matrix_multi(matrix_W,matrix_trans(matrix_W))
matrix_WWT.repartition(60).write.parquet("hdfs://xxxx/matrix_wWT_allCls")
val matrix_D_ne_1 = matrix_WWT.groupBy("query").
      agg(sum("value_multi_new") as "value_multi_new_sum").
      drop("value_multi_new").
      withColumn("pow_1_2",pow_udf(col("value_multi_new_sum"))).
      withColumn("query_T",col("query")).
      withColumnRenamed("query","query_D").
      select("query_D","query_T","pow_1_2")
matrix_D_ne_1.repartition(60).write.parquet("hdfs://xxxx/matrix_D_allCls")
//B矩阵,query url value_matrixB
val matrix_B_1 = matrix_multi(matrix_D_ne_1,matrix_W).
      withColumnRenamed("value_multi_new","value_matrixB").
      withColumnRenamed("query_D","query")
matrix_B_1.repartition(60).write.parquet(hdfs://xxxx/matrix_B_allCls")

val matrix_B = spark.read.parquet("hdfs://xxxx/matrix_B_allCls")
val alpha = 0.6.toFloat

//F矩阵, query,cls1,count_cls_rela
//H矩阵迭代,url cls1 value_multi_new
val F_init = spark.read.parquet("hdfs://xxxx/url_filter_F")
var matrix_F = F_init
var matrix_H = matrix_multi(matrix_trans(matrix_B),matrix_F)
F_init.write.parquet("hdfs://xxxx/url_filter_F0")

for(i <- 1 until 10){
    matrix_F = spark.read.parquet("hdfs://xxxx/url_filter_F"+(i-1))
    matrix_H = matrix_multi(matrix_trans(matrix_B),matrix_F)
    //println(value_sum(matrix_H))
    matrix_F = matrix_add(
        matrix_num(matrix_multi(matrix_B,matrix_H),alpha),
        matrix_num(F_init,1.0.toFloat-alpha),
        "+"
        ).withColumnRenamed("value_add","count_cls_rela")
    //query cls1 value_multi_new + query,cls1,count_cls_rela  ->query_D cls1 value_add
    println(value_sum(matrix_F))
    println(matrix_F.count())
    HdfsFileUtils.deleteFile("hdfs://xxxx/url_filter_F"+i)
    matrix_F.repartition(60).write.parquet("hdfs://xxxx/url_filter_F"+i)
        }

2.4 tri-training

利用少量标注样本扩招,具体看周老师的论文吧

3.训练集制作+小结

3.1 样本比例优化

不是data打上label之后就是训练集了,还存在分布比例平衡的问题,可以根据测试集上的表现来优化,比如某个类别A大量误召其他类别B、C、D、E…,且类别上数量分散,很有可能是训练集中A比例过高

3.2 样本label清洗

当测试集上的错误和3.1不一样,是某两类的互相误召,那可能是边界样本标签问题,可利用前面样本扩招的方式定位这些bad Case的相似样本进行清洗

3.3 训练集有了,happy model吧

所谓大数据时代,重要的就是数据,现在都快被炒烂的AI、DL等等很是光鲜,但实际解决问题时,数据尤其labeled数据,可是很珍贵的,毕竟这些github不会给你,需要你熟练使用各种算法+数据敏感性去挖掘。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值