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不会给你,需要你熟练使用各种算法+数据敏感性去挖掘。