智慧海洋建设TOP_1方案学习笔记


比赛来源:天池平台 智慧海洋建设

感谢大佬开源TOP1方案: 智慧海洋建设TOP1

本博客只是单纯的对TOP1方案进行整理学习。

赛题

要求

本赛题基于位置数据对海上目标进行智能识别和作业行为分析,要求选手通过分析渔船北斗设备位置数据,得出该船的生产作业行为,具体判断出是拖网作业、围网作业还是流刺网作业。

数据

每艘渔船对应一个csv文件,训练集一共提供11000条渔船北斗数据。
在这里插入图片描述

每个csv文件由若干条以下数据组成,刻画了渔船在不同时刻的运动状态。

在这里插入图片描述

TOP1 解决方案

导入相关依赖:

import pandas as pd
import numpy as numpy
from tqdm import tqdm
from pyproj import Proj
from sklearn.metrics import classification_report, f1_score
from sklearn.model_selection import StratifiedKFold, KFold
from sklearn.decomposition import TruncatedSVD
from sklearn.feature_extraction.text import TfidfVectorizer,CountVectorizer
from gensim.models import Word2Vec
from gensim.models.doc2vec import Doc2Vec, TaggedDocument
import lightgbm as lgb
import os

数据读取

利用文件读取的方式,对数据读取,将多表数据整合到一张表中。将所有的训练测试数据预测数据保存到一个dataFrame中,便于后续的特征工程。

TRAIN_PATH = '../input/hy_round1_train_20200102'
TEST_PATH =  '../input/hy_round1_testA_20200102'

def get_data(file_path,model):
    file_path = file_path
    paths = os.listdir(file_path)  # 得到file_path目录下的文件名,存入数组paths中
    tmp = []
    for t in tqdm(range(len(paths))):
        p = paths[t]
        with open('{}/{}'.format(file_path,p),encoding='utf-8') as f:
            next(f)
            for line in f.readlines():
            	# strip()去除字符串的头和尾的空格,并以,切割字符串,每一行数据形成一个字符数组。将字符数组添加到tmp数组中。
                tmp.append(line.strip().split(','))
    tmp_df = pd.DataFrame(tmp)
    print(tmp_df.head())
    if model == 'train':
            tmp_df.columns = ['ID', 'lat', 'lon', 'speed', 'direction', 'time', 'type']
    else:
            tmp_df['type'] = 'unknown'
            tmp_df.columns = ['ID', 'lat', 'lon', 'speed', 'direction', 'time', 'type']
    tmp_df['lat'] = tmp_df['lat'].astype(float)
    tmp_df['lon'] = tmp_df['lon'].astype(float)
    tmp_df['speed'] = tmp_df['speed'].astype(float)
    tmp_df['direction'] = tmp_df['direction'].astype(int)
    return tmp_df
    
# 调用函数,读取并整合数据
train = get_data(TRAIN_PATH,'train')
test = get_data(TEST_PATH,'test')
train = train.append(test)    

将原始数据的x,y转为经纬度。

# 将原始数据得x,y 转为经纬度。
def transform_xy2lonlat(df):
    x = df['lat'].values
    y = df['lon'].values
    p = Proj("+proj=tmerc +lat_0=0 +lon_0=120 +k=1 +x_0=500000 +y_0=0 +a=6378140 +b=6356755.288157528 +units=m +no_defs")
    df['lon'], df['lat'] = p(x, y, inverse=True)
    return df
train = transform_xy2lonlat(train)
df = train.copy()

到这里数据读取结束。所有的训练集测试集都保存在表df中。

数据长这样:

在这里插入图片描述
在这里插入图片描述

每个ID都有若干条记录。type=unknown的表示测试集。

特征工程

基本预处理


# 根据ID和time进行排序
df.sort_values(['ID','time'],inplace=True)
# 将时间处理成 2019-11-10 11:38:19的形式,并转成pandas时间格式
# df['time'] = df['time'].apply(lambda x: '2019-'+ x.split(' ')[0][:2]+ '-'+x.split(' ')[0][2:]+' '+x.split(' ')[1])
df['time'] = df['time'].apply(lambda x: '2019-' + x.split(' ')[0][:2] + '-' + x.split(' ')[0][2:] + ' ' + x.split(' ')[1])
df['time']=pd.to_datetime(df['time'])

# 求每艘船的经度 维度 速度 时间的 差分
df['lat_diff'] = df.groupby('ID')['lat'].diff(1)
df['lon_diff'] = df.groupby('ID')['lon'].diff(1)
df['speed_diff'] = df.groupby('ID')['speed'].diff(1)
df['diff_minutes'] = df.groupby('ID')['time'].diff(1).dt.seconds //60

# 每艘船是否满足:在10s时间内,经纬度变化量<0.01, 速度<0.1, 如果满足则该船是在锚点处,即停止。
# 使用apply 实现, axis=1表示将函数应用与每一行
df['anchor'] = df.apply(lambda x: 1 if x['lat_diff']<0.01 and x['lon_diff']<0.01 and x['speed']<0.1 and x['diff_minutes'] <=10 else 0,axis=1)

# 处理标签 将标签匹配成数字类别如围网对应0,使用map函数实现
df['type'] = df['type'].map({'围网':0, '刺网':1, '拖网':2, 'unknown':-1})

生成group_df 用于保存后续所有特征工程生成的结果。

# 生成group_df,每个ID对应的label,以及每个ID出现的总次数
# mean的结果代价label,count等价于每个ID的数据的记录数。
group_df = df.groupby('ID')['type'].agg({'label': 'mean', 'cnt':'count'}).reset_index() 

统计特征

1、每艘船的停船率。

# 获取锚点位置信息,先对ID进行分组,每个ID的anchor求和,求出每艘船的锚点位置个数
#  使用groupby+agg组合,分组聚合。返回dataframe                                          
anchor_df = df.groupby('ID')['anchor'].agg('sum').reset_index()                                             
anchor_df.columns = ['ID','anchor_cnt']

#将锚点位置信息合并group_df, 求出每艘船的停止率。停止次数/总次数。                                            
group_df = group_df.merge(anchor_df, on='ID', how='left')
group_df['anchor_ratio']= group_df['anchor_cnt'] / group_df['cnt']  

2、分别对每个ID的:

(1)所有航行记录的所有数据,

(2)所有数据中经纬度变化量不等于0的数据

(3)所有数据中速度不为0的数据

的’lat’, ‘lon’, ‘speed’, 'direction’特征,求统计量:‘min’, ‘max’, ‘mean’, ‘median’, ‘nunique’, q10, q20, q30, q40, q60, q70, q80, q90。

先取出df中经纬度变化量不等于0的数据lat_lon_neq_zero,取出速度变化量不等于0的数据speed_neg_zero

lat_lon_neq_zero = df[(df['lat_diff']!=0)&(df['lon_diff']!=0)]                                             
speed_neq_zero = df[df['speed_diff']!=0]   

定义函数,获取相应的分位点:

# 定义一些函数,返回数据的分位点数据
def q10(x):
    return x.quantile(0.1)


def q20(x):
    return x.quantile(0.2)


def q30(x):
    return x.quantile(0.3)


def q40(x):
    return x.quantile(0.4)


def q60(x):
    return x.quantile(0.6)


def q70(x):
    return x.quantile(0.7)


def q80(x):
    return x.quantile(0.8)


def q90(x):
    return x.quantile(0.9)

利用groupby+agg 组合计算相应的统计特征:

stat_functions = ['min', 'max', 'mean', 'median', 'nunique', q10, q20, q30, q40, q60, q70, q80, q90]
stat_ways = ['min', 'max', 'mean', 'median', 'nunique', 'q_10', 'q_20', 'q_30', 'q_40', 'q_60', 'q_70', 'q_80', 'q_90']
stat_cols = ['lat', 'lon', 'speed', 'direction']

# 所有数据'lat', 'lon', 'speed', 'direction'统计特征
group_tmp = df.groupby('ID')[stat_cols].agg(stat_functions).reset_index()
group_tmp.columns = ['ID'] + ['{}_{}'.format(i,j) for i in stat_cols for j in stat_ways] 

#经纬度变化量不等于0的数据的'lat', 'lon', 'speed', 'direction'的统计特征
lat_lon_neq_group = lat_lon_neq_zero.groupby('ID')[stat_cols].agg(stat_functions).reset_index()
lat_lon_neq_group.columns = ['ID'] + ['pos_neq_zero_{}_{}'.format(i, j) for i in stat_cols for j in stat_ways]

# 速度不为0的数据'lat', 'lon', 'speed', 'direction'的统计特征
speed_neq_zero_group = speed_neq_zero.groupby('ID')[stat_cols].agg(stat_functions).reset_index()
speed_neq_zero_group.columns = ['ID']+ ['speed_neq_zero_group_{}_{}'.format(i,j) for i in stat_cols for j in stat_ways]

# 将这些统计特征合并到group_df中
group_df = group_df.merge(group_tmp, on='ID', how='left')
group_df = group_df.merge(lat_lon_neq_group, on='ID', how='left')
group_df = group_df.merge(speed_neq_zero_group, on='ID', how='left')

3、获取TOP频次位置信息

对时有出现的位置信息(经纬度)进行计数,得到出现次数排名前三的位置。

# 计算位置出现的次数
mode_df = df.groupby(['ID','lat','lon'])['time'].agg({'mode_cut': 'count'}).reset_index()
# 对ID分组,根据mode_cut进行排序
mode_df['rank'] = mode_df.groupby('ID')['mode_cut'].rank(method='first',ascending=False)
# 循环取出出现次数排名前三个的位置,并将其合并到group_df中
for i in range(1,4):
    tmp_df = mode_df[mode_df['rank']==i]
    del tmp_df['rank']
    tmp_df.columns = ['ID','rank_{}_mode_lat'.format(i),'rank_{}_mode_lon'.format(i),'rank{}_mode_cnt'.format(i)]
    group_df = group_df.merge(tmp_df,on='ID',how='left')

到此,统计信息计算结束,group_df维度为170。

接下来的特征工程是这个比赛的TOP1的创新之处。

基于轨迹序列绝对和相对位置的复合向量编码

将 文本化的经纬度和文本化的经纬度梯度 生成词频矩阵,进行LSA降维,降维后30维。

先定义下面三个工具函数:

(1)geohash_encode将经纬度坐标转换成字符串相近的位置字符串相同。

def geohash_encode(latitude, longitude, precision=12):
    """
    Encode a position given in float arguments latitude, longitude to
    a geohash which will have the character count precision.
    """
    lat_interval, lon_interval = (-90.0, 90.0), (-180.0, 180.0)
    base32 = '0123456789bcdefghjkmnpqrstuvwxyz'
    geohash = []
    bits = [16, 8, 4, 2, 1]
    bit = 0
    ch = 0
    even = True
    while len(geohash) < precision:
        if even:
            mid = (lon_interval[0] + lon_interval[1]) / 2
            if longitude > mid:
                ch |= bits[bit]
                lon_interval = (mid, lon_interval[1])
            else:
                lon_interval = (lon_interval[0], mid)
        else:
            mid = (lat_interval[0] + lat_interval[1]) / 2
            if latitude > mid:
                ch |= bits[bit]
                lat_interval = (mid, lat_interval[1])
            else:
                lat_interval = (lat_interval[0], mid)
        even = not even
        if bit < 4:
            bit += 1
        else:
            geohash += base32[ch]
            bit = 0
            ch = 0
    return ''.join(geohash)

(2)tfidf对文本进行tfidf词频特征 ,并利用LSA进行潜在语义分析,也可以理解为用svd降维。每个文本生成output_num维度的向量。



def tfidf(input_values, output_num, output_prefix, seed=1024):
    # 生成文本Tfidf矩阵,使用TF-IDF对文本进行预处理,将文本化为向量的表示形式,shape=(文本数,词汇数)
    tfidf_enc = TfidfVectorizer()
    tfidf_vec = tfidf_enc.fit_transform(input_values)
    # print('所有文本的关键字',tfidf_enc.get_feature_names())

    # print('词频矩阵的结果',tfidf_vec.toarray())
    
    # 对shape=(文本数,词汇数)的矩阵进行降维,
    #生成Matrix(文本数,主题数)xMatrix(文本数,主题数),主题数远小于词汇数
    # truncatedSVD作用于sklearn.feature_extraction.text向量化后返回的 term count/tf-idf矩阵。在这种情况下又被成为LSA( latent semantic analysis)
    # LSA潜在语义分析,生成主题数为30维,
    svd_tmp = TruncatedSVD(n_components=output_num, n_iter=20, random_state=seed)
    svd_tmp = svd_tmp.fit_transform(tfidf_vec)
    svd_tmp = pd.DataFrame(svd_tmp)
    svd_tmp.columns = ['{}_tfidf_{}'.format(output_prefix, i) for i in range(output_num)]
    return svd_tmp

(3)count2vec与tfidf功能相似,没有使用tfidf进行词频统计,直接使用计数对词频统计。

def count2vec(input_values, output_num, output_prefix, seed=1024):
    # CountVectorizer是通过fit_transform函数将文本中的词语转换为词频矩阵
    # 生成文本词频矩阵
    count_enc = CountVectorizer()
    count_vec = count_enc.fit_transform(input_values)
    # truncatedSVD作用于sklearn.feature_extraction.text向量化后返回的 term count/tf-idf矩阵。在这种情况下又被成为LSA( latent semantic analysis)
    # LSA潜在语义分析,生成主题数为30维,
    svd_tmp = TruncatedSVD(n_components=output_num, n_iter=20, random_state=seed)
    svd_tmp = svd_tmp.fit_transform(count_vec)
    svd_tmp = pd.DataFrame(svd_tmp)
    svd_tmp.columns = ['{}_countvec_{}'.format(output_prefix, i) for i in range(output_num)]
    return svd_tmp

利用gaohash_encode 将每条数据得经纬度转为长度为7得字符串,相近的位置字符串相近


df['lat_lon'] = df.apply(lambda x:geohash_encode(x['lat'], x['lon'], 7), axis=1)

# 将每一个ID对应的所有的lat_lon 存到list中,ID [xvkwu60,xvkwu60,xvkwu60,xvkwu60,vkwu60 xvkwu6]
tmp = df.groupby('ID')['lat_lon'].agg(list).reset_index() # agg聚合的结果是一个值(一个分组一个值)。
# list 转字符串 并以空格分隔, ID xvkwu60 xvkwu60 xvkwu60 xvkwu60 xvkwu60 xvkwu6
tmp['lat_lon'] = tmp['lat_lon'].apply(lambda x: ' '.join(x))
print('经纬度文本化的结果:',tmp)

在这里插入图片描述

将文本化的经纬度生成的矩阵,进行LSA降维,得到30维特征。

# 将文本化的经纬度生成词向量
tfidf_tmp = tfidf(tmp['lat_lon'], 30, 'lat_lon')
count_tmp = count2vec(tmp['lat_lon'], 30, 'lat_lon')

# 合并两个词嵌入的结果,60个特征
tfidf_df = pd.concat([tmp[['ID']],tfidf_tmp,count_tmp],axis=1)

# 将这60个特征加入到group_df中
group_df = group_df.merge(tfidf_df,on='ID',how='left')
print('geohash tfidf finished.特征个数',len(group_df.columns))

生成每个ID所有数据经度梯度_维度梯度组合特征

# agg聚合的结果是一个值(一个分组一个值),所以下面代码执行返回一个ID,以及ID对应的lat 的梯度list
# 0	 [0.0, 0.0, 3.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, ..
grad_df = df.groupby('ID')['lat'].apply(lambda x: np.gradient(x)).reset_index()
grad_df['lon'] = df.groupby('ID')['lon'].apply(lambda x: np.gradient(x)).reset_index()['lon']
grad_df['lat'] = grad_df['lat'].apply(lambda x: np.round(x, 4))
grad_df['lon'] = grad_df['lon'].apply(lambda x: np.round(x, 4))

# 注意下面这行format zip用法,每一时刻的经纬度的梯度会一一匹配。
# 最后结果 -0.0058_0.0034 -0.0058_0.0034 -0.0047_0.0044 -... 经度梯度_维度梯度
grad_df['grad'] = grad_df.apply( lambda x: ' '.join(['{}_{}'.format(z[0],z[1]) for z in zip(x['lat'],x['lon'])]),axis=1)
print(grad_df['grad'])

在这里插入图片描述

生成经纬度梯度tfidf矩阵,降维

tfidf_tmp = tfidf(grad_df['grad'], 30, 'grad')
grad_tfidf = pd.concat([grad_df[['ID']], tfidf_tmp], axis=1)
group_df = group_df.merge(grad_tfidf, on='ID', how='left')
print('gradient tfidf finished.')

随机抽取10%的经纬度文本,生成的矩阵,进行LSA降维,得到30维特征。

tmp = df.groupby('ID')['lat_lon'].apply(lambda x: x.sample(frac=0.1, random_state=1)).reset_index()
del tmp['level_1']
tmp.columns = ['ID','sample']
tmp = tmp.groupby('ID')['sample'].agg(list).reset_index()
tmp['sample'] = tmp['sample'].apply(lambda x: ' '.join(x))

tfidf_tmp = tfidf(tmp['sample'], 30, 'sample')
sample_tfidf= pd.concat([tmp[['ID']], tfidf_tmp], axis=1)
group_df = group_df.merge(sample_tfidf, on='ID', how='left')
print('sample tfidf finished.')    

Word2Vec

将df[‘lat_lon’]的文本作为词库,训练word2vec。

# 训练 word2vec模型
data_frame = df.groupby('ID')['lat_lon'].agg(list).reset_index()
model = Word2Vec(data_frame['lat_lon'].values, size=30, window=5, min_count=1, sg=1, hs=1,workers=1, iter=10, seed=1, hashfxn=hashfxn)

# data_frame['lat_lon']中的每一个值,都是一个(n,30)的dataframe,n是指渔船的所有轨迹的个数。
# 30 是指每个轨迹被嵌入到30维。

data_frame['lat_lon'] = data_frame['lat_lon'].apply(lambda x: pd.DataFrame([model[c] for c in x]))
# 对每个ID,计算lat_lon的dataframe的30个特征的每一个特征均值,作为最终结果。
for m in range(30):
     data_frame['w2v_{}_mean'.format(m)] = data_frame['lat_lon'].apply(lambda x: x[m].mean())
        
del data_frame['lat_lon']
w2v_df = data_frame

group_df = group_df.merge(w2v_df, on='ID', how='left')
print('word2vec finished.')

最终生成320个特征。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值