如何做数据竞赛·优秀案例学习(DC02)

2019年腾讯广告算法大赛冠军思路、代码(PART 2)

方案地址:https://zhuanlan.zhihu.com/p/73062485
代码地址:https://github.com/guoday/Tencent2019_Preliminary_Rank1st
数据地址:https://algo.qq.com/application/home/home/review.html


上一篇文章我们分析了这个项目数据清洗如何做数据竞赛·优秀案例学习(DC01),以及如何构建数据集的代码,这部分我们分析提取特征的代码。

word2vec算法:

该算法主要是寻找词与词之间的相似度,对于一些离散特征,我们很容易利用one-hot编码(one-hot编码:比如一个特征只有两个值,如性别,男和女,我们把男编码为0,女为1。如果里面有4个值就用00,01,10,11表示即可,总之就是一种将所有值用一个索引号来代替,但这些值之间并没有什么联系,只是一种表示方式。)但是对于一些特征,其可能包含很多值,one-hot之后可能很大,如何将这个向量变小,从而可以来学习词与词之间的关系,将一个稀疏向量映射到一个方便处理的维度向量,同时词和词之间有了联系,这个过程叫做词嵌入(Word Embedding)。如此操作之后,在经过整个句子的训练,词之间便有了联系,如图;
在这里插入图片描述

Deepwalk算法:

DeepWalk是一种学习网络中节点的表示的新的方法,是把language modeling的方法用在了social network里面,从而可以用deep learning的方法,不仅能表示节点,还能表示出节点之间的拓扑关系,也就是表现出社会网络的社会关系。
DeepWalk使用random walk的方法,random走过一串结点,产生一串结点组成的序列,等同于word2vec中的一个sentence,结点就等同于word2vec中的word。
DeepWalk算法充分利用了网络结构中的随机游走序列的信息,使用随机游走序列的信息有两点好处:

  • 1.随机游走序列只依赖于局部信息,所以可适用于分布式和在线系统,而使用邻接矩阵就必须把所有信息存储于内存中处理,面临着较高的计算时间和空间消耗。
  • 2.对随机游走序列进行建模可以降低建模0-1二值邻接矩阵的方差和不确定性。

提取特征代码:(extract_feature.py)

import os
import pandas as pd
import numpy as np
import random
import json
import gc
from gensim.corpora import WikiCorpus
from gensim.models import Word2Vec
from gensim.models.word2vec import LineSentence
from collections import Counter
from sklearn import preprocessing
import scipy.special as special
from pandas import DataFrame, Series
from tqdm import tqdm
import time
np.random.seed(2019)
random.seed(2019)

1 Word2Vec特征:

def w2v(log,pivot,f,flag,L):
    #word2vec算法
    #log为曝光日志,以pivot为主键,f为embedding的对象,flag为dev或test,L是embedding的维度
    print("w2v:",pivot,f)
    
    #构造文档
    log[f]=log[f].fillna(-1).astype(int)
    #对要进行embedding的对象,空缺值填-1,类型转换为int型 
    sentence=[]
    #构建一个空的列表存放上下文
    dic={}
    #创建一个字典结构
    day=0
    log=log.sort_values(by='request_day')
    #对曝光日志中文件按照 请求日排列
    log['day']=log['request_day']
    #添加新的属性列,等于请求日列
    for item in log[['day',pivot,f]].values:
    #遍历曝光文件中请求day,主键,和embedding对象
    #循环段的代码:如果出现的是新请求日的数据,则将上次请求日数据保存在sentence列表中,
    #反之如果是同一天的请求,则将共同主键所对应的值存在字典中
        if day!=item[0]:
            for key in dic:           
            #遍历字典,key为字典的key值
                sentence.append(dic[key])
                #append为追加,为列表在表末追加新元素
            dic={}
            day=item[0]
        try:
            dic[item[1]].append(str(int(item[2])))
            #如果在dic中有主键值,则在dic中对应主键,插入EB对象,EB对象转换为字符串格式
        except:
            dic[item[1]]=[str(int(item[2]))]
            #如果在dic中没有对应主键,则建立dic中的主键。
    for key in dic:
        sentence.append(dic[key])
        #最后一天的字典序列输入
    print(len(sentence))
    #训练Word2Vec模型
    print('training...')
    random.shuffle(sentence)
    #shuffle() 方法将序列的所有元素随机排序。shuffle() 是不能直接访问的,需要导入 random 模块,然后通过 random 静态对象调用该方法。
    model = Word2Vec(sentence, size=L, window=10, min_count=1, workers=10,iter=10)
    #调用gensim.models中的Word2Vec模型,sentence是语料库,用作训练,size为生成的词向量的维度,
    #window是当前词于预测词在一个句子中的最大距离。min_count用于过滤操作,词频少于 min_count 次数的单词会
    #被丢弃掉,默认值为 5。workers控制训练的并行数量。iter: 算法迭代次数,默认为 5。
    print('outputing...')
    #保存文件
    values=set(log[f].values)
    #将对应embedding对象存储为set,不重复
    w2v=[]
    #创建一个列表
    for v in values:
        try:
            a=[int(v)]
            a.extend(model[str(v)])
            #向量结构为array([-0.12038743, -0.30791789, ..., 0.83891463, -2.07493448], dtype=float32)
            #extend() 函数用于在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表)。
            #model(v)生成长度为L的向量
            w2v.append(a)
            #将对应的向量值存入w2v列表中
        except:
            pass
    out_df=pd.DataFrame(w2v)
    #构造一个dataframe结构来存储w2v
    names=[f]
    #对应dataframe的列的名称
    for i in range(L):
    #将64位向量分别拥有对应的列名,列名的形式为pivot_w2v_embedding_f_64_1
        names.append(pivot+'_w2v_embedding_'+f+'_'+str(L)+'_'+str(i))
    out_df.columns = names
    
    out_df.to_pickle('data/' +pivot+'_'+ f +'_'+flag +'_w2v_'+str(L)+'.pkl') 
#将数据存储为pickle格式,方便存取

2 deepwalk特征:

def deepwalk(log,f1,f2,flag,L):
#log为数据
    #Deepwalk算法,区别word2vec主要是构造sentence不同,这里先构造图,之后随机游走产生序列,然后训练
    print("deepwalk:",f1,f2)
    #构建图
    dic={}
    #创建一个字典结构
    for item in log[[f1,f2]].values:
        #对两个属性值遍历
        try:
            str(int(item[1]))
            str(int(item[0]))
            #尝试是否可以先int 后str数据
        except:
            continue
        try:
            dic['item_'+str(int(item[1]))].add('user_'+str(int(item[0])))
            #add() 方法用于给集合添加元素,如果添加的元素在集合中已存在,则不执行任何操作。
        except:
            dic['item_'+str(int(item[1]))]=set(['user_'+str(int(item[0]))])
 			#如果字典中没有对应的键值,则赋值
        try:
            dic['user_'+str(int(item[0]))].add('item_'+str(int(item[1])))
        except:
            dic['user_'+str(int(item[0]))]=set(['item_'+str(int(item[1]))])
            #上述操作同理,为了构建一个有向图网络
    dic_cont={}
    for key in dic:
    #对产生的字典结构遍历
        dic[key]=list(dic[key])
        #对应的set结构变为list结构
        dic_cont[key]=len(dic[key])
        #创建一个新的字典结构来存储每一个主键对应的list的长度
    print("creating")     
    #构建路径
    path_length=10        
    #设定每个sentence的长度为10
    sentences=[]
    #创建sentence列表
    length=[]
    #创建长度列表
    for key in dic:
    #对字典中数据遍历
        sentence=[key]
        #将key值首先存sentence结构中
        while len(sentence)!=path_length:
        ##当sentence结构长度不为10的时候循环
            key=dic[sentence[-1]][random.randint(0,dic_cont[sentence[-1]]-1)]
            #sentence[-1]代表sentence结构倒数第一个数据
            #random.randint(a, b)#返回闭区间 [a, b] 范围内的整数值,dic_cont记录的是每个key对应的sentence长度,这样可以达到一个随机的起始点
            if len(sentence)>=2 and key == sentence[-2]:
                break
                #如果此时sentence里有2个及以上的数据,此时key出现重复的,则停止循环,说明key所对应的字典值并没有那么多
            else:
                sentence.append(key)
                #sentence插入key值
        sentences.append(sentence)
        #将sentence插入到另一个以list为元素的新list中
        length.append(len(sentence))
        #将每个sentence的长度插入到length中
        if len(sentences)%100000==0:
            print(len(sentences))
            #这里只是提示左右,显示有多少条sentence已经生成
    print(np.mean(length))
    #显示平均sentence的长度
    print(len(sentences))
    #输出共有多少条sentence
    
	#训练Deepwalk模型
    print('training...')
    random.shuffle(sentences)
    #随机打乱sentence的序列
    model = Word2Vec(sentences, size=L, window=4,min_count=1,sg=1, workers=10,iter=20)
    #训练Word2Vec模型,和之前的一样,不同的是我们要根据对sentence的一些属性选择不同的参数值,sg: 用于设置训练算法,默认为0,对应CBOW算法;sg=1则采用skip-gram算法
    print('outputing...')
    #输出
    values=set(log[f1].values)
    #构建f1列对应的向量,存储f1对应的数据
    w2v=[]
    for v in values:
        try:
            a=[int(v)]
            a.extend(model['user_'+str(int(v))])
            w2v.append(a)
            #同上,保存输出的向量
        except:
            pass
    out_df=pd.DataFrame(w2v)
    #转换结构
    names=[f1]
    for i in range(L):
        names.append(f1+'_'+ f2+'_'+names[0]+'_deepwalk_embedding_'+str(L)+'_'+str(i))
    #扩展列
    out_df.columns = names
    print(out_df.head())
    out_df.to_pickle('data/' +f1+'_'+ f2+'_'+f1 +'_'+flag +'_deepwalk_'+str(L)+'.pkl') 
    #保存成pickle文件,方便读取
    ########################
    values=set(log[f2].values)
    #构建f2列对应的向量,存储f1对应的数据
    w2v=[]
    for v in values:
        try:
            a=[int(v)]
            a.extend(model['item_'+str(int(v))])
            w2v.append(a)
        except:
            pass
    out_df=pd.DataFrame(w2v)
    names=[f2]
    for i in range(L):
        names.append(f1+'_'+ f2+'_'+names[0]+'_deepwalk_embedding_'+str(L)+'_'+str(i))
    out_df.columns = names
    print(out_df.head())
    out_df.to_pickle('data/' +f1+'_'+ f2+'_'+f2 +'_'+flag +'_deepwalk_'+str(L)+'.pkl') 
    #同上

3 构建当天投放时段特征:

def predict_periods(train_df,test_df,wday):
    #提取测试当天投放时段特征
    print("predict_periods features")    
    #提取训练集的投放时段特征,分别有48维的01向量,和投放时段总数
    items=[]
    for item in train_df[['wday','delivery_periods']].values:
    #遍历,wday一周的第几天,delivery_periods表示投放时段
        w=item[0]
        item=item[1]
        #赋值操作
        val=int(item.split(',')[w])
        #提取对应天的投放时段信息,split分割item,提取第w天的数据
        temp=[]
        for i in range(48):
        #创建一个循环i从0-47
            if val%2==1:
                temp.append(1)
                #取余操作,如果末尾是1,则加入temp中1
            else:
                temp.append(0)
                #反之加入0
            val//=2
            #移位操作,将48位数据向左移位
        assert val==0
        #检查条件,不符合就终止程序,如果assert不为0,则终止程序
        temp.append(sum(temp))
        #加入temp的值
        items.append(temp)
        #将temp作为元素加入到items列表中
    df=pd.DataFrame(items)
    #创建dataframe结构体
    df.columns=['periods_on_'+str(i) for i in range(48)]+['periods_cont']
    #列名规则,48位的时间段信息和时段统计
    for f in ['periods_on_'+str(i) for i in range(48)]+['periods_cont']:
        train_df[f]=df[f]
        #将统计的时段信息 加入到训练集中
    del df
    del items
    gc.collect()
    #清除内存空间    
    #提取测试集的投放时段特征,分别有48维的01向量,和投放时段总数
    items=[]
    for item in test_df['delivery_periods'].values:
        val=int(item.split(',')[wday])
        temp=[]
        for i in range(48):
            if val%2==1:
                temp.append(1)
            else:
                temp.append(0)
            val//=2
        assert val==0
        temp.append(sum(temp))
        items.append(temp)
    df=pd.DataFrame(items)
    df.columns=['periods_on_'+str(i) for i in range(48)]+['periods_cont']
    for f in ['periods_on_'+str(i) for i in range(48)]+['periods_cont']:
        test_df[f]=df[f]
    del df
    del items
    gc.collect()
    #同上训练集操作

4 构建TOPKEY 特征:

def crowd_uid(train_df,test_df,f1,f2,log,k):
    #多值特征,提取以f1为主键,f2在log中出现Topk的ID
    #如f1=aid,f2=uid,k=100,则表示访问该广告最多的前100名用户
    print("crowd_uid features",f1,f2)
    dic={}
    log[f1]=log[f1].fillna(-1).astype(int)
    #对log中缺失值填充-1
    train_df[f1]=train_df[f1].fillna(-1).astype(int)
    #对训练集中f1对应列缺失值填充-1
    test_df[f1]=test_df[f1].fillna(-1).astype(int)
    #对测试集中f1对应列缺失值填充-1
    
    for item in tqdm(log[[f1,f2,'request_day']].values,total=len(log)):
    #显示进度条,TOTAL表示共有多少个
        try:
            dic[item[0]][0][item[1]]+=1
            #第一个counter对象以item1 为键值所对应的value+1
        except:
            dic[item[0]]=[Counter(),Counter()]
            #Counter 的本质就是一个特殊的 dict,只不过它的 key 都是其所包含的元素,而它的 value 则记录了该 key 出现的次数。因此,如果通过 Counter 并不存在的 key 访问 value,将会输出 0(代表该 key 出现了 0 次)。
            #表示字典以item 0号元素为主键存储的是两个counter对象
            dic[item[0]][0][item[1]]=1
            #第一个counter对象的键值是item1 值是1
            
    items=[]
    for key in tqdm(dic,total=len(dic)):
    #遍历dic字典
        conter=dic[key][0]
        #取出对应key值所建立的第一个counter对象
        item=[str(x[0]) for x in conter.most_common(k)]
       #因为conter.most_common(k)返回值是是二元组类似 [('a', 5), ('b', 2), ('r', 2)],所以我们取出x的第一项将其组合为list结构
        if len(item)==0:
            item=['-1']
            #如果没有,则返回-1
        items.append([key,' '.join(item)])
    #将key和其对应得最大k个商品返回
    df=pd.DataFrame(items)
    #构建dataframe格式数据
    df.columns=[f1,f1+'_'+f2+'s']
    #重新命名列,有两列数据
    df = df.drop_duplicates(f1)
    #删除f1的重复行
    try:
        del train_df[f1+'_'+f2+'s']
        del test_df[f1+'_'+f2+'s']
        #删除训练数据集中对应列,如果没有该列则执行except语句
    except:
        pass
    train_df=train_df.merge(df,on=f1,how='left')
    #将df和训练数据集合并
    test_df=test_df.merge(df,on=f1,how='left')
    #将df和测试数据集合并
    train_df[f1+'_'+f2+'s']=train_df[f1+'_'+f2+'s'].fillna('-1')
    #对训练数据集中缺失值添-1
    test_df[f1+'_'+f2+'s']=test_df[f1+'_'+f2+'s'].fillna('-1')
    #同理
    del df
    del items
    del dic
    gc.collect()
    #清除变量,更新内存
    return train_df,test_df  
    #返回训练和测试数据集

5 构建人群定向特征:

def crowd_direction(train_df,test_df):
    #人群定向,多值特征
    print("crowd_direction features")
    index={
       'age':0,
        'gender':1,
        'area':2,
        'status':3,
        'education':4,
        'consuptionAbility':5,
        'device':6,
        'os':6,
        'work':7,
        'connectionType':8,
        'behavior':9
    }
    revert_index={}
    for key in index:
        revert_index[index[key]]=key
        #将index字典翻转,即0:age,1:gender.....存入字典    
    #构造训练集的人群定向,有年龄,性别等多值特征
    items=[]
    for item in train_df['crowd_direction'].values:
        if item=='all':
            items.append(['all']*10)
            #对于训练数据集中人群定向属性是all的,则将10个all存入items列表中 ,['all']*10表示将all扩充10次
        else:
            temp=['all' for i in range(10)]
            #同样是将all复制10次
            for features in item.split('|'):
            #将item按照|分割成列表,开始遍历
                key=features.split(':')[0]
                #将feature按照:分割,取第一个元素,作为key
                val=features.split(':')[1].split(',')
                #将feature按照:分割,取第二元素,将第二个元素按照,分割成列表
                temp[index[key]]=' '.join(val)
                # join() 方法用于将序列中的元素以指定的字符连接生成一个新的字符串。这里的连接符为空格。
                
            items.append(temp)
            #将一次循环temp结果加入到items中
    df=pd.DataFrame(items)
    #形成dataframe数据结构
    df.columns=['age','gender','area','status','education','consuptionAbility','os','work','connectionType','behavior']
    #重新命名列
    for f in ['age','gender','area','status','education','consuptionAbility','os','work','connectionType','behavior']:
        train_df[f]=df[f]
        #将其加入到训练数据集中
    del df
    del items
    gc.collect() 
    #清除变量,清空内存

    #构造测试集的人群定向,有年龄,性别等多值特征
    items=[]
    for item in test_df['crowd_direction'].values:
        if item=='all':
            items.append(['all']*10)
        else:
            temp=['all' for i in range(10)]
            for features in item.split('|'):
                key=features.split(':')[0]
                val=features.split(':')[1].split(',')
                temp[index[key]]=' '.join(val)
            items.append(temp)
    df=pd.DataFrame(items)
    df.columns=['age','gender','area','status','education','consuptionAbility','os','work','connectionType','behavior']
    for f in ['age','gender','area','status','education','consuptionAbility','os','work','connectionType','behavior']:
        test_df[f]=df[f]
    del df
    del items
    gc.collect()   
    #同上操作

6 构建历史特征:

def history(train_df,test_df,log,pivot,f):
    #以pivot为主键,统计最近一次f的值
    print("history",pivot,f)
    nan=log[f].median()
    #nan作为f列的中位数
    dic={}
    #创建一个字典
    temp_log=log[[pivot,'request_day',f,'aid']].drop_duplicates(['aid','request_day'],keep='last')
    #提取数据4个属性列的值,同时按照'aid','request_day'去重,保留最后一个
    for item in log[[pivot,'request_day',f]].values:
        if (item[0],item[1]) not in dic:
            dic[(item[0],item[1])]=[item[2]]
            #如果pivot,'request_day'没有出现在字典中,则创建对应的键值信息和值
        else:
            dic[(item[0],item[1])].append(item[2])
            #反之若存在,则将f值插入到键所对应的列表中
    for key in dic:
        dic[key]=np.mean(dic[key])
        #对每一个键对应的列表求平均值,然后代替这个列表存入dic中
        
    #统计训练集的特征
    items=[]
    cont=0
    day=log['request_day'].min()
    #统计曝光日志中请求日最小的一天
    for item in train_df[[pivot,'request_day']].values:
    #对训练数据pivot,'request_day'遍历
        flag=False
        for i in range(item[1]-1,day-1,-1):
        #i从(请求日-1)天开始,递减到最小请求日
            if (item[0],i) in dic:
            #如果主键和对应的请求日出现在dic中
                items.append(dic[(item[0],i)])
                #将此信息对应的平均值存入到temp中
                flag=True
                #标记这条数据在历史记录中出现过
                cont+=1
                break
        if flag is False:
        #如果在dic中没有找到对应的键值,也就是说在历史记录中没有出现过
            items.append(nan)
            #则用f列中位数来代替存入items中
    train_df['history_'+pivot+'_'+f]=items
    #创建训练数据集新列作为一个特征
    
    #统计测试集的特征
    items=[]
    cont=0
    day_min=log['request_day'].min()
    day_max=log['request_day'].max()
    for item in test_df[pivot].values:
        flag=False
        for i in range(day_max,day_min-1,-1):
            if (item,i) in dic:
                items.append(dic[(item,i)])
                flag=True
                cont+=1
                break
        if flag is False:
            items.append(nan)
    test_df['history_'+pivot+'_'+f]=items
    #原理同上,只不过训练集是按照request_day排序过数据,而测试集没有,所以需要找到最大的一天,循环判断比较
    print(train_df['history_'+pivot+'_'+f].mean())
    #输出新属性的平均数
    print(test_df['history_'+pivot+'_'+f].mean())
    del items
    del dic
    gc.collect()
    #清理内存
    return train_df,test_df    
    #返回训练集和测试集

7 构建聚合特征:

def get_agg_features(train_df,test_df,f1,f2,agg,log=None):
#获得聚合特征
    if type(f1)==str:
    #判断f1列的类型是否是字符串
        f1=[f1]
        #f1变为列表
    if log is None:
    #如果没有曝光日志
        if agg!='size':
            data=train_df[f1+[f2]].append(test_df.drop_duplicates(f1+[f2])[f1+[f2]])
            #对应的f1,f2属性,让训练集和测试集合并
        else:
            data=train_df[f1].append(test_df.drop_duplicates(f1)[f1])
            #如果为size,则只合并f1列的数据集和测试集
    else:
    #如果存在曝光日志
        if agg!='size':
            data=log[f1+[f2]]
            #则将曝光日志的f1,f2列保存
        else:
            data=log[f1]
    if agg=="size":
        tmp = pd.DataFrame(data.groupby(f1).size()).reset_index()
        #如果是size将提取的f1列为主键分组,同时计算每组大小,重置索引
    elif agg == "count":
        tmp = pd.DataFrame(data.groupby(f1)[f2].count()).reset_index()
        #计算按照f1分组,计算每个分组中f2的个数
    elif agg=="mean":
        tmp = pd.DataFrame(data.groupby(f1)[f2].mean()).reset_index()
        #计算按照f1分组,计算每个分组中f2的平均值
    elif agg=="unique":
        tmp = pd.DataFrame(data.groupby(f1)[f2].nunique()).reset_index()
        #计算按照f1分组,计算每个分组中f2不重复的个数
    elif agg=="max":
        tmp = pd.DataFrame(data.groupby(f1)[f2].max()).reset_index()
        ##计算按照f1分组,计算每个分组中f2的最大值
    elif agg=="min":
        tmp = pd.DataFrame(data.groupby(f1)[f2].min()).reset_index()
        #计算按照f1分组,计算每个分组中f2的最小值
    elif agg=="sum":
        tmp = pd.DataFrame(data.groupby(f1)[f2].sum()).reset_index()
        #计算按照f1分组,计算每个分组中f2的和
    elif agg=="std":
        tmp = pd.DataFrame(data.groupby(f1)[f2].std()).reset_index()
        #计算按照f1分组,计算每个分组中f2的标准差
    elif agg=="median":
        tmp = pd.DataFrame(data.groupby(f1)[f2].median()).reset_index()
        #计算按照f1分组,计算每个分组中f2的中位数
    elif agg=="skew":
        tmp = pd.DataFrame(data.groupby(f1)[f2].skew()).reset_index()
        #计算按照f1分组,计算每个分组中f2的偏度,表征概率分布密度曲线相对于平均值不对称程度的特征数。
    elif agg=="unique_mean":
        group=data.groupby(f1)
        group=group.apply(lambda x:np.mean(list(Counter(list(x[f2])).values())))
        tmp = pd.DataFrame(group.reset_index())
        #apply函数默认情况下按照列来操作,对x[f2]会查看每一个分好组f1所对应的f2的值,将其变为list结构,然后转化为Counter结构,取出对应Counter 下的值,即不同值的f2个数,变成list之后求平均。这样求出来是分组下对应不同f2值的平均个数
    elif agg=="unique_var":
        group=data.groupby(f1)
        group=group.apply(lambda x:np.var(list(Counter(list(x[f2])).values())))
        tmp = pd.DataFrame(group.reset_index())
        #同上,只不过求的是方差
    else:
        raise "agg error"
    if log is None:
        tmp.columns = f1+['_'.join(f1)+"_"+f2+"_"+agg]
        print('_'.join(f1)+"_"+f2+"_"+agg)
        #构建聚合特征列名
    else:
        tmp.columns = f1+['_'.join(f1)+"_"+f2+"_log_"+agg]
        print('_'.join(f1)+"_"+f2+"_log_"+agg)
        ##构建聚合特征列名
    try:
        del test_df['_'.join(f1)+"_"+f2+"_"+agg]
        del train_df['_'.join(f1)+"_"+f2+"_"+agg]
        #尝试删除训练集测试集特征列,如果没有该列则执行except 
    except:
        pass
    test_df=test_df.merge(tmp, on=f1, how='left')
    #增加特征
    train_df=train_df.merge(tmp, on=f1, how='left')
    #同上
    del tmp
    del data
    gc.collect()
    #清空内存
    print(train_df.shape,test_df.shape)
    #打印训练集和测试集大小
    return train_df,test_df  
    #返回训练集和测试集

8 构建5折交叉统计特征:

def kfold_static(train_df,test_df,f,label):
#构建5折交叉统计特征 
    print("K-fold static:",f+'_'+label)
    #K-fold positive and negative num
    avg_rate=train_df[label].mean()
    #查看label列的平均值
    num=len(train_df)//5
    #训练集被分为5份
    index=[0 for i in range(num)]+[1 for i in range(num)]+[2 for i in range(num)]+[3 for i in range(num)]+[4 for i in range(len(train_df)-4*num)]
    #构建标识每一份归属的标签集
    random.shuffle(index)
    #打乱标签集
    train_df['index']=index
    #作为训练数据集的标签集
    #五折统计
    dic=[{} for i in range(5)]
    dic_all={}
    for item in train_df[['index',f,label]].values:
        #遍历标签,f列,f作为主键 label列,item为列表
        try:
            dic[item[0]][item[1]].append(item[2])
            #将item[0]对应的份数作为标识dic中第几个字典集,创建key和value对应,如果加入失败,则执行except创建语句
        except:
            dic[item[0]][item[1]]=[]
            dic[item[0]][item[1]].append(item[2])
    print("static done!")
    #构造训练集的五折特征,均值,中位数等       
    mean=[]
    median=[]
    std=[]
    Min=[]
    Max=[]
    cache={}
    for item in train_df[['index',f]].values:
    #遍历训练集标签列和f列
        if tuple(item) not in cache:
        #tuple元组与列表类似,不同之处在于元组的元素不能修改。
            temp=[]
            for i in range(5):
                 if i!=item[0]:
                 #对于不同标签的数据操作
                    try:
                        temp+=dic[i][item[1]]
                        #提取其他标签下主键是item[1]的数据
                    except:
                        pass
            if len(temp)==0:
                cache[tuple(item)]=[-1]*5
                #如果temp为空,代表其他标签下,不存在数据则对应元组下为-1
            else:
                cache[tuple(item)]=[np.mean(temp),np.median(temp),np.std(temp),np.min(temp),np.max(temp)]
                #反之,该元组下对应其他标签下的数据的平均值,中位数,标准差,最小值,最大值
        temp=cache[tuple(item)]
        #反之,如果cache中存在对应的元组,则将提取出该对应的数据
        mean.append(temp[0])
        #将平均值插入mean列表
        median.append(temp[1])
        #同理
        std.append(temp[2])
        #同理
        Min.append(temp[3])
        #同理
        Max.append(temp[4])
        #同理                     
    del cache   
    #清楚cache变量     
    train_df[f+'_'+label+'_mean']=mean
    #这样构造的是每一个数据下 对应其他4份数据在f主键下label的平均值
    train_df[f+'_'+label+'_median']=median
    #同理
    train_df[f+'_'+label+'_std']=std
    #同理
    train_df[f+'_'+label+'_min']=Min
    #同理
    train_df[f+'_'+label+'_max']=Max   
    #同理
    print("train done!")
    
    #构造测试集的五折特征,均值,中位数等  ,和构建训练集相同步骤
    mean=[]
    median=[]
    std=[]
    Min=[]
    Max=[]
    cache={}
    for uid in test_df[f].values:
        if uid not in cache:
            temp=[]
            for i in range(5):
                try:
                    temp+=dic[i][uid]
                except:
                    pass
            if len(temp)==0:
                cache[uid]=[-1]*5
            else:
                cache[uid]=[np.mean(temp),np.median(temp),np.std(temp),np.min(temp),np.max(temp)]
        temp=cache[uid]
        mean.append(temp[0])
        median.append(temp[1])
        std.append(temp[2])
        Min.append(temp[3])
        Max.append(temp[4])           
        
    test_df[f+'_'+label+'_mean']=mean
    test_df[f+'_'+label+'_median']=median
    test_df[f+'_'+label+'_std']=std
    test_df[f+'_'+label+'_min']=Min
    test_df[f+'_'+label+'_max']=Max   
    print("test done!")
    del train_df['index']
    print(f+'_'+label+'_mean')
    print(f+'_'+label+'_median')
    print(f+'_'+label+'_std')
    print(f+'_'+label+'_min')
    print(f+'_'+label+'_max')
    print('avg of mean',np.mean(train_df[f+'_'+label+'_mean']),np.mean(test_df[f+'_'+label+'_mean']))
    print('avg of median',np.mean(train_df[f+'_'+label+'_median']),np.mean(test_df[f+'_'+label+'_median']))
    print('avg of std',np.mean(train_df[f+'_'+label+'_std']),np.mean(test_df[f+'_'+label+'_std']))
    print('avg of min',np.mean(train_df[f+'_'+label+'_min']),np.mean(test_df[f+'_'+label+'_min']))
    print('avg of max',np.mean(train_df[f+'_'+label+'_max']),np.mean(test_df[f+'_'+label+'_max']))
        #打印构造的5折交叉统计数据特征的信息

9 主函数:

#主函数
if __name__ == "__main__":    
    for path1,path2,log_path,flag,wday,day in [('data/train_dev.pkl','data/dev.pkl','data/user_log_dev.pkl','dev',1,17974),('data/train.pkl','data/test.pkl','data/user_log_test.pkl','test',3,17976)]:
    #循环读取验证集和测试集
    
            ##拼接静态特征
            print(path1,path2,log_path,flag)
            train_df=pd.read_pickle(path1)
            #读取训练测试集
            test_df=pd.read_pickle(path2)
            #读取验证集
            log=pd.read_pickle(log_path)
            #读取曝光日志文件
            print(train_df.shape,test_df.shape,log.shape)
            df =pd.read_pickle('data/testA/ad_static_feature.pkl')
            #读取静态广告数据文件
            log=log.merge(df,on='aid',how='left')
            #曝光日志文件和静态广告文件在aid属性合并
            del df
            #清理内存中变量释放内存
            gc.collect()
            #清理
            print(train_df.shape,test_df.shape,log.shape)                 
            
            #人群定向
            crowd_direction(train_df,test_df)
            #处理人群定向多值特征,10个维度
            #['age','gender','area','status','education','consuptionAbility','os','work','connectionType','behavior']
            
            #投放时段
            predict_periods(train_df,test_df,wday)
            #扩展投放时段特征,将其扩展为48位0、1特征

            #多值特征
            train_df,test_df=crowd_uid(train_df,test_df,'good_id','advertiser',log,100)
            ##多值特征,求曝光日志中,对于每一个goodid求其前100 多次出现的advertiser作为其特征
            train_df,test_df=crowd_uid(train_df,test_df,'good_id','request_day',log,100)
            #求曝光日志中,对于每一个goodid求其前100 多次出现的request_day作为其特征
            train_df,test_df=crowd_uid(train_df,test_df,'good_id','position',log,100)
            #同上
            train_df,test_df=crowd_uid(train_df,test_df,'good_id','period_id',log,100)
            train_df,test_df=crowd_uid(train_df,test_df,'good_id','wday',log,100)
            train_df,test_df=crowd_uid(train_df,test_df,'advertiser','good_id',log,100)
            train_df,test_df=crowd_uid(train_df,test_df,'advertiser','request_day',log,100)
            train_df,test_df=crowd_uid(train_df,test_df,'advertiser','position',log,100)
            train_df,test_df=crowd_uid(train_df,test_df,'advertiser','period_id',log,100)
            train_df,test_df=crowd_uid(train_df,test_df,'advertiser','wday',log,100)
            train_df,test_df=crowd_uid(train_df,test_df,'aid','uid',log,20)
            
            #历史特征
            for pivot in ['aid']:
                for f in ['imp','bid','pctr','quality_ecpm','totalEcpm']:
                    history(train_df,test_df,log,pivot,f)  
                    #构建历史信息特征,如果aid和request_day在曝光文件中出现过,则用曝光文件的对应aid和request_day的平均值构建该特征,如果没有,则用f列的中位数来构建
                    
            #五折特征
            kfold_static(train_df,test_df,'aid','imp')
            #对测试集和训练集五折交叉构建特征,将训练集和测试集分成五份,对其他份中的相同的aid对应的imp值求平均,中位,标准差,最大最小值等属性
            kfold_static(train_df,test_df,'good_id','imp')
            #同理
            kfold_static(train_df,test_df,'advertiser','imp')
            #
            #统计特征
            train_df,test_df=get_agg_features(train_df,test_df,["good_id"],'advertiser',"count")
            #按照goodid分组,并统计每组中advertiser 的个数
            train_df,test_df=get_agg_features(train_df,test_df,["good_id"],'aid',"count") 
            #同理
            train_df,test_df=get_agg_features(train_df,test_df,["good_id"],'ad_size',"count") 
            train_df,test_df=get_agg_features(train_df,test_df,["good_id"],'ad_type_id',"count") 
            train_df,test_df=get_agg_features(train_df,test_df,["good_id"],'good_id',"size")
            #按照goodid分组,并统计每组中advertiser 的个数,并作为新的特征列
            train_df,test_df=get_agg_features(train_df,test_df,["advertiser"],'good_id',"count") 
            train_df,test_df=get_agg_features(train_df,test_df,["advertiser"],'aid',"count") 
            train_df,test_df=get_agg_features(train_df,test_df,["advertiser"],'ad_size',"count")    
            train_df,test_df=get_agg_features(train_df,test_df,["advertiser"],'ad_type_id',"count")     
            train_df,test_df=get_agg_features(train_df,test_df,["advertiser"],'good_type',"count") 
            train_df,test_df=get_agg_features(train_df,test_df,["advertiser"],'advertiser',"size") 
            train_df,test_df=get_agg_features(train_df,test_df,['good_type'],'good_type',"size")                                  
            train_df,test_df=get_agg_features(train_df,test_df,["aid"],'aid',"size") 
            #保存数据
            print(train_df.shape,test_df.shape,log.shape)
            train_df.to_pickle(path1) 
            test_df.to_pickle(path2)
            #保存文件  
            print(list(train_df))
            print("*"*80)
            print("save done!")
            
            #Word2vec
            w2v(log,'uid','good_id',flag,64)
            #uid 为主键,goodid为embedding对象,将其变为64维向量并作为特征
            w2v(log,'uid','advertiser',flag,64)
            #同理
            w2v(log,'uid','aid',flag,64)
            #同理
            #Deepwalk
            deepwalk(log,'uid','aid',flag,64)
            #分别构建uid和aid对应的64位向量特征,以uid为主键随机游走产生aid,反之也以aid为主键随机游走uid构建特征
            deepwalk(log,'uid','good_id',flag,64)
			#构建uid和good_id构建的64位特征
            del train_df
            del test_df
            del log
            gc.collect()
            #清除内存

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值