TwoSigma2017预测用户对房产的兴趣等级

摘要:

合集:AI案例-ML-零售业
赛题:预测新房源收到的咨询数量
主办方:Two Sigma和RentHop
主页:https://www.kaggle.com/c/two-sigma-connect-rental-listing-inquiries/
AI问题:分类问题
数据集:房产数据集
数据集价值:预测用户对房产的兴趣等级
解决方案:LightGBM模型用于房产兴趣等级预测

一、赛题描述

寻找一个完美的新家不应仅仅是浏览无穷无尽的房源列表。RentHop通过使用数据按质量对租赁房源进行分类,使公寓搜索更加智能化。然而,寻找完美的公寓已经够难的了,以编程方式构建并理解所有可用的房地产数据更是难上加难。Two Sigma和RentHop(Two Sigma Ventures的投资组合公司,而Two Sigma Ventures是Two Sigma Investments的一个部门)邀请Kagglers发挥他们的创造力,在这个独特的招聘竞赛中发现商业价值。

Two Sigma邀请你在这个以RentHop的租赁房源数据为特色的招聘竞赛中施展才华。Kagglers将根据房源的创建日期和其他特征来预测新房源收到的咨询数量。这样做将有助于RentHop更好地控制欺诈、识别潜在的房源质量问题,并让业主和代理人更好地了解租户的需求和偏好。

Two Sigma一直处于将技术和数据科学应用于金融预测的前沿。虽然他们在金融领域的大数据、人工智能和机器学习方面的开创性进展一直在推动行业的发展,但与其他所有科学进步一样,他们也致力于不断取得进展。这次挑战是一个让参赛者提前了解Two Sigma在金融领域之外的数据科学工作的机会。

致谢

本次竞赛由Two Sigma和RentHop(Two Sigma Ventures的投资组合公司,而Two Sigma Ventures是Two Sigma Investments的一个部门)共同主办,旨在鼓励使用现实世界的数据解决日常问题的创造力。

二、数据集内容

房产数据集包括训练集(train.json)和测试集(test.json)。以下为字段信息:

浴室:浴室数量
卧室:卧室数量
建筑编号
创建时间
描述
显示地址
特色:关于这套公寓的一系列特色
纬度
房源编号
经度
管理员编号
照片:一系列图片链接。您可以自行从renthop网站上下载这些图片,但它们与imgs.zip中的图片相同。
价格:以美元计
街道地址
兴趣程度:这是目标变量。它有3个类别:“高”、“中”、“低”

三、解决方案样例

源码:lgb.py

解决方案

实现了一个基于LightGBM的分类模型,用于预测房产列表的兴趣等级(高、中、低)。

导入开发包

import numpy as np
import pandas as pd
from scipy import sparse
import xgboost as xgb
import lightgbm as lgb

import random
from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import log_loss
from sklearn.feature_extraction.text import CountVectorizer

工作流程如下:

1. 数据加载与预处理

从JSON文件加载训练集(train.json)和测试集(test.json):

train_df = pd.read_json("./data/train.json")
test_df = pd.read_json("./data/test.json")
  • 处理异常值:修正测试集中的浴室数量异常值,限制训练集中的价格上限为13000
  • 创建新特征:
    • 对数价格(logprice)
    • 半浴室数量(half_bathrooms)
    • 每间卧室的价格(price_t)
    • 房间总数(room_sum)
    • 每个房间的价格(price_per_room)
    • 照片数量(num_photos)
    • 特征数量(num_features)
    • 描述字数(num_description_words)
    • 创建时间的各个组成部分(年、月、日、小时、星期几)
    • 地理位置编码(pos)和密度(density)
test_df["bathrooms"].loc[19671] = 1.5
test_df["bathrooms"].loc[22977] = 2.0
test_df["bathrooms"].loc[63719] = 2.0
train_df["price"] = train_df["price"].clip(upper=13000)

train_df["logprice"] = np.log(train_df["price"])
test_df["logprice"] = np.log(test_df["price"])

train_df['half_bathrooms'] = train_df["bathrooms"] - train_df["bathrooms"].apply(int)
test_df['half_bathrooms'] = test_df["bathrooms"] - test_df["bathrooms"].apply(int)

train_df["price_t"] =train_df["price"]/train_df["bedrooms"]
test_df["price_t"] = test_df["price"]/test_df["bedrooms"]

train_df["room_sum"] = train_df["bedrooms"]+train_df["bathrooms"]
test_df["room_sum"] = test_df["bedrooms"]+test_df["bathrooms"]

train_df['price_per_room'] = train_df['price']/train_df['room_sum']
test_df['price_per_room'] = test_df['price']/test_df['room_sum']

train_df["num_photos"] = train_df["photos"].apply(len)
test_df["num_photos"] = test_df["photos"].apply(len)

train_df["num_features"] = train_df["features"].apply(len)
test_df["num_features"] = test_df["features"].apply(len)

train_df["num_description_words"] = train_df["description"].apply(lambda x: len(x.split(" ")))
test_df["num_description_words"] = test_df["description"].apply(lambda x: len(x.split(" ")))

train_df["created"] = pd.to_datetime(train_df["created"])
test_df["created"] = pd.to_datetime(test_df["created"])
train_df["created_year"] = train_df["created"].dt.year
test_df["created_year"] = test_df["created"].dt.year
train_df["created_month"] = train_df["created"].dt.month
test_df["created_month"] = test_df["created"].dt.month
train_df["created_day"] = train_df["created"].dt.day
test_df["created_day"] = test_df["created"].dt.day
train_df["created_hour"] = train_df["created"].dt.hour
test_df["created_hour"] = test_df["created"].dt.hour

train_df["created_weekday"] = train_df["created"].dt.weekday
test_df["created_weekday"] = test_df["created"].dt.weekday
# train_df["created_week"] = train_df["created"].dt.week
# test_df["created_week"] = test_df["created"].dt.week

train_df["pos"] = train_df.longitude.round(3).astype(str) + '_' + train_df.latitude.round(3).astype(str)
test_df["pos"] = test_df.longitude.round(3).astype(str) + '_' + test_df.latitude.round(3).astype(str)

vals = train_df['pos'].value_counts()
dvals = vals.to_dict()
train_df["density"] = train_df['pos'].apply(lambda x: dvals.get(x, vals.min()))
test_df["density"] = test_df['pos'].apply(lambda x: dvals.get(x, vals.min()))

features_to_use=["bathrooms", "bedrooms", "latitude", "longitude", "price","price_t","price_per_room", "logprice", "density", "half_bathrooms",
"num_photos", "num_features", "num_description_words","listing_id", "created_year", "created_month", "created_day", "created_hour", "created_weekday"] # "created_week",

2. 特征工程

2.1 经理级别特征

  • 使用5折交叉验证方式计算每个经理(manager_id)的历史表现:
    • 计算每个经理管理的房产在不同兴趣等级(低、中、高)中的比例
    • 避免数据泄漏,使用交叉验证方式生成这些特征

2.2 类别特征编码

  • 对类别特征(“street_address”, “display_address”, “manager_id”, “building_id”)使用LabelEncoder进行编码

2.3 文本特征处理

  • 将房产特征(features)列表转换为空格连接的字符串
  • 使用CountVectorizer(词袋模型)将文本特征转换为稀疏矩阵,最多保留200个特征词
index=list(range(train_df.shape[0]))
random.shuffle(index)
a=[np.nan]*len(train_df)
b=[np.nan]*len(train_df)
c=[np.nan]*len(train_df)

for i in range(5):
   building_level={}
   for j in train_df['manager_id'].values:
       building_level[j]=[0,0,0]
   
   test_index=index[int((i*train_df.shape[0])/5):int(((i+1)*train_df.shape[0])/5)]
   train_index=list(set(index).difference(test_index))
   
   for j in train_index:
       temp=train_df.iloc[j]
       if temp['interest_level']=='low':
           building_level[temp['manager_id']][0]+=1
       if temp['interest_level']=='medium':
           building_level[temp['manager_id']][1]+=1
       if temp['interest_level']=='high':
           building_level[temp['manager_id']][2]+=1
           
   for j in test_index:
       temp=train_df.iloc[j]
       if sum(building_level[temp['manager_id']])!=0:
           a[j]=building_level[temp['manager_id']][0]*1.0/sum(building_level[temp['manager_id']])
           b[j]=building_level[temp['manager_id']][1]*1.0/sum(building_level[temp['manager_id']])
           c[j]=building_level[temp['manager_id']][2]*1.0/sum(building_level[temp['manager_id']])
           
train_df['manager_level_low']=a
train_df['manager_level_medium']=b
train_df['manager_level_high']=c

a=[]
b=[]
c=[]
building_level={}
for j in train_df['manager_id'].values:
   building_level[j]=[0,0,0]

for j in range(train_df.shape[0]):
   temp=train_df.iloc[j]
   if temp['interest_level']=='low':
       building_level[temp['manager_id']][0]+=1
   if temp['interest_level']=='medium':
       building_level[temp['manager_id']][1]+=1
   if temp['interest_level']=='high':
       building_level[temp['manager_id']][2]+=1

for i in test_df['manager_id'].values:
   if i not in building_level.keys():
       a.append(np.nan)
       b.append(np.nan)
       c.append(np.nan)
   else:
       a.append(building_level[i][0]*1.0/sum(building_level[i]))
       b.append(building_level[i][1]*1.0/sum(building_level[i]))
       c.append(building_level[i][2]*1.0/sum(building_level[i]))
test_df['manager_level_low']=a
test_df['manager_level_medium']=b
test_df['manager_level_high']=c

features_to_use.append('manager_level_low')
features_to_use.append('manager_level_medium')
features_to_use.append('manager_level_high')

categorical = ["street_address", "display_address", "manager_id", "building_id"]
for f in categorical:
       if train_df[f].dtype=='object':
           lbl = LabelEncoder()
           lbl.fit(list(train_df[f].values) + list(test_df[f].values))
           train_df[f] = lbl.transform(list(train_df[f].values))
           test_df[f] = lbl.transform(list(test_df[f].values))
           features_to_use.append(f)

train_df['features'] = train_df["features"].apply(lambda x: " ".join(["_".join(i.split(" ")) for i in x]))
test_df['features'] = test_df["features"].apply(lambda x: " ".join(["_".join(i.split(" ")) for i in x]))

3. 模型构建与训练

3.1 LightGBM参数配置

def runXGB(train_X, train_y, test_X, test_y=None, feature_names=None, seed_val=321, num_rounds=1800):
   params = {
       'learning_rate': 0.03,
       'min_child_samples': 4,
       'max_depth': 6,
       'lambda_l1': 1.5,
       'boosting': 'gbdt',
       'objective': 'multiclass',
       'metric': 'multi_logloss',
       'num_class': 3,
       # 'feature_fraction': .85,
       # 'bagging_fraction': .7,
       'seed': 99,
       'num_threads': 10,
       'verbose': 0
  }

   # plst = list(param.items())
   clf = lgb.train(params, lgb.Dataset(train_X, label=train_y), 2000,)

   pred_test_y = clf.predict(test_X)
   return pred_test_y, clf

3.2 模型训练

  • 使用LightGBM的train函数进行训练
  • 输入数据转换为稀疏矩阵格式以节省内存
  • 训练2000轮(虽然参数中指定了num_rounds=1800,但实际使用的是硬编码的2000)

4. 预测与输出

  • 对测试集进行预测,得到每个房产属于三个兴趣等级的概率
  • 将预测结果保存为CSV文件(“cz.csv”),包含listing_id和三个类别的概率
tfidf = CountVectorizer(stop_words='english', max_features=200)
tr_sparse = tfidf.fit_transform(train_df["features"])
te_sparse = tfidf.transform(test_df["features"])

train_X = sparse.hstack([train_df[features_to_use], tr_sparse]).tocsr()
test_X = sparse.hstack([test_df[features_to_use], te_sparse]).tocsr()

target_num_map = {'high':0, 'medium':1, 'low':2}
train_y = np.array(train_df['interest_level'].apply(lambda x: target_num_map[x]))

preds, model = runXGB(train_X, train_y, test_X, num_rounds=1800)
out_df = pd.DataFrame(preds)
out_df.columns = ["high", "medium", "low"]
out_df["listing_id"] = test_df.listing_id.values
out_df.to_csv("cz.csv", index=False)

关键特点

  1. 特征工程丰富:创建了大量手工特征,特别是与价格、房间数量和时间相关的特征
  2. 避免数据泄漏:使用交叉验证方式生成经理级别特征
  3. 处理稀疏数据:使用稀疏矩阵存储特征,适合处理高维稀疏数据
  4. 多分类问题:预测三个类别的概率,使用多分类对数损失作为评估指标

输出

highmediumlowlisting_id
0.09270937082941690.476131370784453670.43115925838612947142618
0.0118659244123739180.0129727428681107350.97516133271951547210040
0.0043276215748530620.0263120405942561630.96936033783089077174566
0.412566460492901130.48319383932596740.10423970018113147191391
0.0030179463744831710.194554427894267970.80242762573124897171695
0.00232324572123073250.1951397433383920.80253701094037737225206
4.226042856613732e-060.00053771520228883630.99945805875485457200075
0.383536909227658930.462605929346657230.15385716142568387145074

四、获取案例套装

需要登录后才允许下载文件包。登录

发表评论