Facebook-RTB广告投放及策略工程师工作

我希望这是一篇有意思的博客,虽然内容里没有代码,没有技术名词。我想说说Facebook广告投放是怎么回事,然后说说作为策略工程师的一些感受。 首先说明,策略的内容不能谈,工作里确实见过不少,每个广告主都有不同的想法,我们也有自己的想法,但一是直接把这些放在这里不合适,毕竟不都是我的想法,二是看起来没有必胜的策略,所以如果想看这个就请忽略本文吧。

Facebook广告

因为我是做这个的,先谈谈对自动管理系统的认识:

我觉得一直存在一个误区,好像用了自动投放系统,效果就能特别好,量级又大成本又低。其实不是的,广告的效果本身是取决于产品(魔兽就是比一个不知名的MMORPG好投),其次取决于创意(美女图片ctr就是比一般产品高),自动投放系统只是控制广告的预算出价和开关,它没有能力把一个坏创意或者坏产品变成好的,它能做的,其实也只是在广告量价关系上的改善:把好创意尽量多投(高出价高预算),把坏创意尽量少投(及早关停)。

下面说正经的:

原理篇

Facebook有四种竞价方式:CPM, CPC, oCPM和CPA。前两种分别是对M(展现)和C(点击)出价,后两种都是为A(激活)出价,但结算方式不同。 从覆盖人群能力讲,CPM>CPC>oCPM>CPA,因为CPC相比CPM要做ctr的预估,这就会过滤到一批不合理的人群,oCPM与CPC同理。 通常而言,App广告主都会选择oCPM或CPA,但CPA要求广告账户有足够的安装积累为前提,因此并非一开始就可以用,所以oCPM相比而言用的最多。但题外一句,CPA对成本控制会更好。 但无论是哪种竞价方式,Facebook在内部都是会进行广告排序,然后决定对这个流量展现哪个广告,而广告排序,就必然要统一到CPM级别,CPC广告会根据预估的ctr和当前的C单价去计算一个CPM,oCPM和CPA则是要预估ctr和cvr,然后反算一个CPM。 Facebook的广告排序不仅考虑CPM(即谁的出价高),同时也要考虑广告的品质,即质量得分(relevance score),当用户Like转发品论广告时,relevance score会提高,反之当他们关闭广告时,relevance score会降低。 最终,每个广告会获得一个分数,这个分数取决于CPM和relevance score

score = CPM + a * relevance_score

当然,尽管Facebook对外暴露出的relevance score只有1-10,但它内部肯定有更详细的指标,只是我们可以把relevance score作为一个定性指标了。 从这个角度出发,提高广告曝光有两个途径:提高广告质量, 提高广告出价(无论是A,C,还是M)。 Facebook还有一个因素是daily budget,因为pacing算法的存在,daily budget其实也是对广告花费量级有影响的。

指标篇

当广告投起来后,我们就会看到展现,点击,安装,常用的几个指标:ctr, cvr, cpm, cpa, spend, roi

ctr:click/impression,点击率太低的广告,要想想素材第一眼的吸引力
cvr:install/click,转化率太低的广告,可能主要是素材和产品不匹配,用户点进去了发现不是那么回事
cpm:1000*spend/impression,其实表示你覆盖的人群类别,
cpa:成本,广告主关心问题之一
spend:花费,广告主关心问题值二
roi:(income-spend)/spend,其实就是由cpa和spend决定的

这些指标,重要的不仅是绝对值,还有相对值:

和昨天同时段的比较

通常而言,CPA在投放地域的凌晨都会很高,而量级都会很少,以东南亚为例,我们通常都是在白天看数据,那么直接比较今天和昨天的CPA并不信服,而是应该看看今天和昨天同时段的情况。同理,周末的成本也会和平时不一样,这点也需要在比较时参考。

和理论值的比较

CPA在不同投放地域的表现不同,欧美的成本就是远高于东南亚,cvr对新游戏老游戏不同,在投放地域存在基准值,可以用于判断ctr和cvr是否合理,以及素材本身是否需要改进。

看趋势

有些时候成本或量级不符合要求,我们做了某些调整,但如果只看总体的效果,一段时间后仍然不符合预期,这时候需要仔细去分析小时数据的变化,即调整后怎么样了,才能合理评估调整的效果。这里面的一个黑科技是,通常调整后的一个小时,成本都有可能飙高,所以一是不要调整太频繁,二是看3-4个小时的效果较好。

再说说绝对值的事情:

要看花费后再看CPA/ctr/cvr

花费只是几十美金的情况下,看CPA/ctr/cvr的绝对值高低参考意义不大。一定量级后再分析成本,包括离线模型也会滤除花费太少的ad。

CPM与ctr*cvr是正相关的

投出的CPM某种程度上代表了你买到的人群,抛去特定产品的因素,人群本身对游戏的转化率也是不同的,比如有些人群会更喜欢玩游戏,当然,对他们的竞争也更激烈,表现为CPM也更高。 所以这里的一个黑科技是,覆盖不同的人群,因为本质上说,CPM增加和ctr*cvr增加速率高低,决定了CPA的大小,所以如果能够在指定人群上取得增量收益,即虽然花的钱多,但ctr*cvr增加更多,你还是能够降低成本的。

设置篇

素材

做好素材,这不用多说了,原则上你素材够好,下面我写的都可以不用看了

多尝试不同的素材类型,比如轮播,视频,尤其是新广告形式刚刚上线的时候
文案意义不大,从目前的情况看,把语言搞对就行了

定向

不要在一个定向上设置多于4个创意,否则也会竞争
不要在一个地域定向里包含太多国家,除非有必要这样做(比如每个国家人群太小)
对太大的游戏设置wifi-only
设置排除app和page like
一版friend of connection没什么用
游戏性别基本就是男性,应用可以混着
受众比较有用
兴趣其实用一般都名词就好,太细了除非你有研究
。。。
保证你的受众人群不要太小,越小竞争肯定越激烈

设置

bid方法很多,我很推荐用我们的系统,简单来说,就按广告主的设置也可以,或者了解投放地域特点大致设置
daily budget 如果是应用,不要太高,如果是游戏,就看要不要量级
pause/start 创意级别开关可以的,但最好不要只看凌晨的数据
autobid: 有些时候结合daily budget可以,有些时候测试图不错,有些时候也会飞

策略工程师随想

其实最开始到RTB项目组,我们日常最花费精力的事情就是投放广告case的维护,现在想想,这应该是策略工程师的日常。

运营会反馈:“我的广告投不出去了”,“我的广告成本不行”,“我的广告花费速度比昨天慢了”,“我的广告被拒了”。。。

策略工程师的任务 第一步来说,是通过后台投放的逻辑和数据分析,去发现问题究竟出在哪里。

比如广告花费少,是因为内部排序的win\_rate低,那么是不是这个case单价过低,比如广告内部win\_rate不低,但在Ad Exchange上的win\_rate低,那么对比几天,看看是不是有土豪对手在竞价。。。再比如成本相差大,看看是不是ctr预估不准,是离线的问题,能不能加在线的因素控制,是在线的问题,看看会不会是时间窗口问题。。。

第二步,是根据发现的情况,给运营建议。因为在RTB投放里,运营看到的是一个黑盒系统,策略工程师实现系统,他可以了解问题出在哪里。

比如是创意不行,还是出价过低,定向人群过少,或者其它的一些设置不合理(初始探测预算较小),也有可能问题是系统本身的问题,简单说是bug,复杂说可能是系统现在的局限。比如新创意没有保护,是否考虑保护一段时间,至少让它有一定的展现,万一是ctr, cvr很高的case呢

第三步,是总结常见的问题,进行系统改进。策略工程师相比运营更加理性,同时可以了解更多运营的通用需求,这种情况下,去总结问题,将它变成策略。 策略要注意的是实验,条件允许的情况下,总要通过A/B测试去确定策略是否合理。 策略同样要注意case by case的特点,广告主的诉求彼此不同,通常在投放的不同阶段也不会相同,所以策略总是有使用条件的,甚至有些时候,为特定广告主特定调整参数,也是完全可能的。

第四步,和前三步不同,那就是问题未必来自运营的直接反馈,而是自己的主动发现。 广告的核心问题,就是量级和成本的关系。我们总是希望用少的成本,同时获取高的量级。

当然它们本身是矛盾的,但总有一个平衡点,比如广告的动态出价,依赖当前广告的实际成本,去反馈不一样的出价,当前成本高了,实际出价低些,反之高些

这些大概都是两年以前在RTB项目组做项目的体会,现在居然还有不少印象。我在RTB项目组只呆了半年的时间,后来新成立Facebook项目组,我是从零开始的参与,事情类别多了很多,比如要先开始搭广告管理和统计的服务,研究Facebook API,后来又做数据支持和应用,但很大一部分工作,仍然是在策略上。 Facebook的模式和RTB不太一样,RTB是对每个流量竞价,你有个内部广告库,要过滤后排序,然后给出一个流量的绝对出价;Facebook你看不到每个流量,也不需要你对广告自己做排序,但要你为每个定向决定出价。从模式上说,Facebook很像做SEO,从技术上说,Facebook模式里,我们扮演的是RTB运营的角色,内部广告系统对我们是黑盒,我们要决定该怎么参与其中。 关于Facebook投放中的一些指标,在上面已经提了,总体而言,因为Facebook的黑盒特点,策略工程师的事情更不容易做,但我也在想办法更好的去理解它。

Saiku安装及基础配置

背景

saiku是一个开源的OLAP分析平台,在我们的项目中逐渐发现,除了基础的数据报表需求,以及数据挖掘需求外,也需要考虑把部分查询交互起来,一方面可以减少报表方面的压力,另外一方面也可以快速对数据进行验证,毕竟通过界面拖拽的方法相比直接写SQL还是要方便很多的。 在公司的另外一个项目里,saiku已经有比较成熟的应用,因此我们选择saiku作为我们需求处理的工具(尽管客观说saiku社区版还是有自己的问题的)。

文档地址

http://wiki.meteorite.bi/display/SAIK/Saiku+Features

下载

通过wget下载最新的saiku社区版,目前版本是3.8.8:

wget www.meteorite.bi/downloads/saiku-latest.zip

启动安装注册

下载后进入saiku-server目录,通过./start-saiku.sh可以启动saiku(同理,通过./stop-saiku.sh关闭saiku)。如果正常,启动后通过你机器的8080端口 (浏览器里visit http://your_IP:8080),可以访问到的saiku的登陆页面。

默认情况下,saiku的管理员账户和密码均是admin,但直接登陆后,会提示:

Could not find license, please get a free license from http://licensing.meteorite.bi. You can upload it at http://server:8080/upload.html

这种情况是因为目前即使是社区版,也需要在saiku上注册:

http://licensing.meteorite.bi/licenses

访问上述网址,可能会提示用户登陆,需要先注册用户。 登陆后,可以看到:

LICENCE
    Create new licence
    List all licences
COMPANY
    Create new company
    List all companies

这样的目录结构,我的情况是,需要先创建一个company,然后再创建licence (company在licence中是必选的),licence的hostname就填写你机器的IP(我没有试过其它是否可以),下载licence文件到本地,然后再通过http://your_IP:8080/upload.html%E5%B0%86%E8%BF%99%E4%B8%AAlicence%E6%96%87%E4%BB%B6%E6%8F%90%E4%BA%A4%E4%B8%8A%E5%8E%BB%EF%BC%8C%E6%95%B4%E4%B8%AA%E6%B3%A8%E5%86%8C%E8%BF%87%E7%A8%8B%E5%B0%B1%E5%AE%8C%E6%88%90%E4%BA%86%E3%80%82 注册完成后,通过admin/admin可以登陆进入saiku。

端口设置

默认情况下,saiku启动在8080端口,如果需要更改,可以修改saiku-server/tomcat/conf/server.xml里的port配置,比如把8080全部替换为9090。

权限控制

saiku server的权限控制主要通过tomcat设置:

tomcat/webapps/saiku/WEB-INF

其中applicationContext-saiku.xml会引用applicationContext-spring-security.xml,后者默认会采用jdbc设置,在applicationContext-spring-security-jdbc.xml中配置dataSource:

      <bean id="dataSource"                class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver" />
            <property name="url"                        value="jdbc:mysql://your_IP:3306/admin" />
            <property name="username" value="your_name" />
            <property name="password" value="your_password" />
    </bean>

数据源连接

在saiku中,我们需要通过预定义的Cube来指定数据访问。Cube是OLAP中的一个逻辑概念,通过对物理数据表的描述,将数据划分为dimension和metric(在saiku中为measure)。saiku中的Cube通过xml描述,具体含义可以参考这里,后续有机会我会详细介绍。 因此,假设我们已经建立好Cube对应的xml,以及数据库中对应的fact表和dimension表,那么我们还需要告诉saiku如何找到它们。 通过在tomcat/webapps/saiku/WEB-INF/classes/saiku-datasources中新建文件配置:

type=OLAP
name=xxx
driver=mondrian.olap4j.MondrianOlap4jDriver
location=jdbc:mondrian:Jdbc=Jdbc:mysql://xxx/dbname;Catalog=<your_xml_path>

其中type一般是OLAP,name表示你Cube的名词,driver选择mondrian,它是saiku内部使用的数据处理引擎,支持MDX查询 (从这个角度说,saiku更像是查询平台),location里分别指定xml位置和mysql连接方式。

配置完成后,重启saiku,你需要的数据已导入。

广告成本(CPA)预测模型初步

广告成本(CPA)预测模型初步

实验背景

通过投放管理,我们收集了相当数量广告主的广告投放数据。Facebook广告在设定时包含相当数量的定向信息,最常用的比如国家,性别和年龄。广告投放往往在相同定向上有相似的表现效果(比如同一款游戏投放美国和东南亚,前者的CPA一定比后者高),因此我们可以尝试用历史数据预测广告CPA的未来表现。

实验目标

希望依据广告投放的历史数据,构建回归模型,预测广告当前的CPA表现,指导模型投放。

分析工具

考虑到数据源直接存储在Hive中,同时也考虑数据量可能的增长预测,我们采用Spark做数据处理和模型预测方面的工作。 Spark本身是基于RDD实现的内存计算框架,它的一个重要应用在于处理机器学习中迭代问题的优化,相对应的库为Spark ml。多说一句的是 ,Spark版本更新较快,相应机器学习库存在ml和mllib两个版本,分别基于DataFrame和RDD处理数据。实际上,DataFame可以视为对RDD的封装,在1.3版本前,它也被称为schema RDD,即包含元数据描述的RDD。 我们选择ml和DataFrame操作数据,因为它们在后续更新的优先级会高于mllib。

数据源介绍

我们把离线模块拥有的数据源信息划分为广告维度,产品维度,时间维度,广告特征维度,定向特征维度,创意维度和统计维度这几个部分。 技术上,它们来自Hive表offlin_data,表内容每天会定时加载更新。

广告维度字段 (字段名称我只补充了部分需要说明的,因为大多数字段都是自表意的) | id | description | |—|:—|:—:|—:| |Bm_id |Business Manager| |Account_id|
|Campaign_id|
|Adset_id|
|Ad_id|

产品维度

id description
Application_id / promotion_url App链接
Product_type App类型,提取自user_os
Game_type 应用类型,需单独处理

时间维度 | id | description | |—|:—|:—:|—:| |Dt / start_dt / end_dt| 数据分析的最小单位是天|

广告特征维度

id description
Bid_amount 广告的出价
Daily_budget 广告的日预算,如果设置
Lifetime_budget 广告的生命周期预算,如果设置
Is_autobid 是否是autobid
Relevance_score 广告的质量得分
Page_id 广告使用的page信息

定向特征维度 | id | description | |—|:—|:—:|—:| |Min_age / max_age
|gender
|country
|city 我们抓取了city信息,如果有的话 |Custom_audience
|Connection
|Exclude_connection
|Interests
|…
完整的定向特征保持与PE上adset可编辑的定向内容完全一致,这里不一一列出

创意维度 | id | description | |—|:—|:—:|—:| |Video_id / video_image_url |Video的FB ID |Image_url / image_hash |Image的链接

统计维度 | id | description | |—|:—|:—:|—:| |Impression / reach
|Clicks
|Actions
|Spend
|Unique_click/unique_impression

创意分为视频和图片两类,出价方式分为使用autobid和不使用autobid,本次分析只针对图片类创意,不使用autobid的游戏类广告。

Spark中对数据加载通过Hive QL,如下语句:

conf = SparkConf().setAppName("offline-train")
sc = SparkContext(conf=conf)
hc = HiveContext(sc)

# generate your query_sql
df = hc.sql(query_sql)

数据预处理

1.考虑在数据量极小时,CPA的绝对值没有意义(比如一共只有几十美分的花费就带来安装并不能说明说明当前定向下真实成本就是几十美分),因此我们过滤掉单日花费小于20$的广告,因为我们认为在太少的花费下,CPA是不可信的。 2.对autobid的广告,数据源中会把对应的bid_amount设置为0,同时,广告在应用和游戏上通常也有着不同的表现,目前我们没有方法直接判断一个promotion_url是游戏还是应用(后续拟添加支持),但通常应用的出价范围和游戏不一致,因此我们选择bid_amount>50美分的ad作为样本对象。 3.回归中较常用的误差衡量方式是RMSE,一些异常点可能对RMSE的结果影响较大,根据经验,因此我们选择天级别实际cpa小于20的ad作为样本,将cpa大于20的ad视为异常数据。 4.因为我们只分析图片创意,因此我们选择image_url非空的case 5.因为后续OneHotEncoder不接受空字符串,因此把空字符串均转为”unknown”

经过以上过滤后,7天内有效数据量占总记录条数的比例约为2%。

特征工程

首先,我们经数据预处理后,从数据源中选择了如下原始特征:

dimensions部分

    Image_url
    Dt
    Age_min
    Age_max
    Is_autobid
    Genders
    Product_type
    Countries
    Bid_amount
    Daily_budget
    Connection_ids
    Custom_audience_ids
    Excluded_connection_ids
    Interest_ids
    Billing_event

metric部分

    Spend
    Mobile_app_install

metric部分只用于计算cpa,通过Hive定义的udf完成。

UdfFunc = udf(udf_func, FloatType())
df = df.withColumn(“cpa”, UdfFunc(df[“spend”], df[“mobile_app_install”]))

1.把age_min和age_max合并为age_avg,然后在最终输入模型的训练特征中去除age_min和age_max:

age_avg = (age_min + age_max) / 2

2.把dt信息转为weekday,在最终模型中去除dt信息:

def weekday(dt):
    if not dt:
        return '-1'
    import datetime
    d = datetime.datetime.strptime(str(dt), '%Y%m%d')
    return int(d.weekday())

3.对于如countries, connection_ids,可能在一条记录中包含多个国家或多个connection,我们只抽取其中第一条记录视为记录的代表:

def countries_fill(countries):
    if (not countries) or (countries == 'null'):
        return 'unknown'
    countries = countries.replace('::',',')
    return countries[0]

特征编码

由于Spark.ml的树模型不接受字符串类型输入(与scikit-learn一致),因此我们对所有字符特征都作为string to index的转换,然后做了one hot encoder。

# countries -> one-hot encoder, category
countries_indexer = StringIndexer(inputCol="countries", outputCol="countries_index", handleInvalid="skip")
countries_onehot_indexer = OneHotEncoder(dropLast=False, inputCol="countries_index", outputCol="countries_vec")

Spark.ml支持流水线(pipeline)的数据处理模式,需要我们把相关处理特征加入pipeline中。

stages = [
        countries_indexer, countries_onehot_indexer,
]
# finally
pipeline = Pipeline(stages=stages)

最终将cpa视为label,将其它特征视为features:

assembler = VectorAssembler(
    inputCols = feature_columns,
    outputCol = "features"
)
stages.append(assembler)

模型选择

在Spark.ml的默认模型中,我们选择随机森林(Random Forest)作为回归预测模型。虽然从效果上讲,Random Forest可能不如其它ensemble方法,但它是强于decision tree的,考虑模型的可解释性,以及实现的便利性,我们直接选择RandomForestRegressor作为回归模型。

对于Random Forest,较为重要的两个参数是单棵树的深度(max depth of tree)和树的个数(num of trees),对这两个参数通过grid-search确定合理值。

# do cross-validation
paramGrid = ParamGridBuilder()\
    .addGrid(random_forest.maxDepth, [3, 5, 7, 9])\
    .addGrid(random_forest.numTrees, [100])\
    .build()
evaluator = RegressionEvaluator()

cv = CrossValidator(estimator=pipeline,
    estimatorParamMaps=paramGrid,
    evaluator=evaluator,
    numFolds=2)
model = cv.fit(df)
print model.avgMetrics

最终模型选择14天数据为训练数据,1天数据为预测数据,RMSE大约在1.59 预测cpa和实际cpa输出样例(前50条):

prediction label features
1.5217560913620043 1.4385713 (1042,[0,1,2,3,17…
2.3752164632800854 5.8022223 (1042,[0,1,2,3,19…
2.411657767271753 3.0307143 (1042,[0,1,2,3,42…
3.6899882643509763 3.5722387 (1042,[0,1,2,3,50…
4.092828817916082 2.2818182 (1042,[0,1,2,3,55…
4.197697715305045 3.366192 (1042,[0,1,2,3,52…
4.197697715305045 4.0157895 (1042,[0,1,2,3,46…
4.197697715305045 3.4369998 (1042,[0,1,2,3,49…
4.071716257839271 1.917647 (1042,[0,1,2,3,44…
3.9807423344404818 2.6338665 (1042,[0,1,2,3,42…
3.9807423344404818 3.5842857 (1042,[0,1,2,3,42…
4.177470410349176 1.9453847 (1042,[0,1,2,3,42…
4.071716257839271 1.8128395 (1042,[0,1,2,3,44…
1.9035986906299776 1.7442857 (1042,[0,1,2,3,22…
13.808040417768252 16.058 (1042,[0,1,2,3,27…
14.168610809794092 14.8975 (1042,[0,1,2,3,18…
11.971184107912041 10.89 (1042,[0,1,2,3,19…
11.962558073010637 8.89 (1042,[0,1,2,3,30…
2.3169264529890903 2.916579 (1042,[0,1,2,3,11…
2.3169264529890903 3.2772727 (1042,[0,1,2,3,11…
1.5221071103811983 6.3782606 (1042,[0,1,2,3,11…
1.3389387340836876 2.1707425 (1042,[0,1,2,3,11…
1.7074257356107376 1.8128985 (1042,[0,1,2,3,11…
1.8675894765662682 3.8840384 (1042,[0,1,2,3,11…
2.241272660641193 4.9881816 (1042,[0,1,2,3,11…
2.268769985708979 2.851852 (1042,[0,1,2,3,11…
2.16831561899445 3.3324242 (1042,[0,1,2,3,11…
2.16831561899445 4.152222 (1042,[0,1,2,3,11…
2.292079711361142 3.9308271 (1042,[0,1,2,3,11…
2.292079711361142 2.6325426 (1042,[0,1,2,3,11…
1.8987393163009587 2.9423833 (1042,[0,1,2,3,11…
1.8987393163009587 2.2502885 (1042,[0,1,2,3,11…
2.1668434345072054 4.4953847 (1042,[0,1,2,3,11…
2.1668434345072054 2.8725274 (1042,[0,1,2,3,11…
2.268769985708979 2.570139 (1042,[0,1,2,3,11…
2.268769985708979 3.188125 (1042,[0,1,2,3,11…
2.16831561899445 1.9479818 (1042,[0,1,2,3,11…
2.16831561899445 3.9414287 (1042,[0,1,2,3,11…
2.3165876740390625 2.620024 (1042,[0,1,2,3,11…
2.318769978387125 1.6355883 (1042,[0,1,2,3,11…
2.146834324914294 1.5435859 (1042,[0,1,2,3,11…
2.146834324914294 2.0905683 (1042,[0,1,2,3,11…
2.361828624187834 2.4541378 (1042,[0,1,2,3,11…
2.237440741142636 3.7431035 (1042,[0,1,2,3,11…
2.237440741142636 1.1768421 (1042,[0,1,2,3,11…
2.2990649947244433 2.50875 (1042,[0,1,2,3,11…
2.35518238420236 3.223077 (1042,[0,1,2,3,11…
1.9192676385516463 3.5867856 (1042,[0,1,2,3,11…
1.9192676385516463 2.2514102 (1042,[0,1,2,3,11…
2.3522288292714277 3.7342856 (1042,[0,1,2,3,11…

后续展望

就模型本身而言,在各个方面都还有很大的改进空间:

处理数据集本身的局限性
特征工程的方法
模型的选择和参数调试

Facebook电商广告投放初步

此内容基于一次电商广告投放的介绍。

因为是基于一次介绍的信息,因此内容可能看起来层次感会比较差,但从实践的角度讲,无论是技术还是运营,都可以得到一些帮助。

我们假设广告主在网站埋点的工作已完成,假设对install类广告有一定了解。

Facebook 电商广告分为两类:非DPA广告和DPA广告

创建篇 1.非DPA广告

Campaign:
    选择推广目标为Increase conversions on your website
Adset:
    大部分设置和install一致,区别在于需要首先设置Conversions,Conversions依赖用户在网站的埋点,预定义的行为包括Add To Cart,Purchase等,没有埋点的事件是不可选的,电商常用的事件就是Add To Cart和Purchase。
    投放技巧上,Adset的目标在投放早期最好设置为Add To Cart而不要直接设置为Purchase,因为早期购买数据积累较少,直接设置目标为Purchase很可能不会得到展现。在投放了一两个月后,再设置目标为Purchase。
Ad:
    同样设置方法和Install一致,但从投放技巧上,通常会选择轮播图或者轮播视频,让每个图是不同的产品,后续在统计查看时,找到转化好的产品(爆款),然后集中推广

2.DPA广告:

Campaign:
    选择推广目标为Promote a product catalog,同时要选择一个Product Catalog

AdSet:
    设置方法不同于Install和非DPA广告,一开始要从Product Catalog里选择一个Product Set,即推广产品的列表。
    在Audience方面,DPA提供了四种默认的Audience方式:
        Viewed or Added to Cart But Not Purchased
        Added to Cart But Not Purchased
        Upsell Products
        Cross-Sell Products
    同时也支持基于View和Add To Cart这些行为去进一步定义custom audience,从这些设置中可以看出,DPA主要是对老客户的reseller,因为它的Audience至少是View product过,而非DPA广告则是对新客户推广。
    在Placement方面,它默认的方式是automatic placement
Ad:
    由于是DPA广告,它在文案等内容上支持插入参数,这些参数来自Product Set的预定义

统计篇

无论是DPA还是非DPA广告,它们相比Install广告会多Add To Cart和Purchase两个指标,分别包含Add To Cart Number(加入购物车的用户数)和Add To Cart Spend(用户在购物车里加了多少钱),Purchase Number和Purchase Spend。
电商广告主通常优化的唯一目标是ROI,即广告花费spend amounnt相比Purchase Spend的数值。但由于Purchase行为是滞后于花费行为的,因此在投放初期很可能看到只有花费,而没有后续收入的情况,这时依赖产品的ctr和Add to cart spend / spend作为判断依据,即产品ctr较高,以及加入购物车的钱数较多时,后续收回成本的可能性更高。
另外电商相比Install特殊的一点是,因为它经常会在一个广告里放多个产品,因此在Ad级别breakdown数据观察很重要,对于非DPA广告可以观察每个广告图的投放情况,对于DPA广告,可以按Product ID观察。另外,由于Spend是基于Ad而不能细致到每个产品,因此主要观察Purchase和ctr作为产品判断的依据。

其它技巧及注意

1. 电商广告投放初期可能会有成本回收不及时的问题,因此要有心理预期,回收时间范围会比install更长
2. 考虑先用DPA一般投放,找到爆款后再单独建campaign投放指定产品
3. DPA广告的ROI通常会远高于非DPA广告,因为它投放的是老顾客,相对而言非DPA广告投放的是新顾客
4. 初期设置Add To Cart为目标,后期设置Purchase为目标
5. 不要相信Fb的suggest bid
6. 投放时可以采用小预算,尤其尝试新产品的时候

为Django博客添加Elasticsearch搜索

翻翻自己的markdown文件,发现上一篇关于Django的文章已经是大概两年前了。虽然文章没有更新,但由于前一阵开始把博客从Github上迁移回自己写的Django网站,相关工作还是做了一些的,这篇博客算是一篇关于Elasicsearch应用的快手博客。

简介 Elasticsearch是基于Luence开发的实时搜索框架,它提供分布式可扩展的服务,以RESTful API的形式提供对外服务,官网地址在这里

安装 下载地址在这里,安装步骤也可以按官网设置,启动服务后,Elasticsearch默认通过9200端口对外提供服务,我采用的版本是2.4.1

博客添加搜索 这是应用的主要内容,为了达到这一目的,我们要完成以下几件事: 1.将博客内容添加进Elasticsearch索引 2.在博客内添加搜索访问逻辑

假设我们的博客model如下:

class Blog(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    pub_date = models.DateField()
    tags = models.ManyToManyField(Tag)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
  1. 添加索引 采用Elasticsearch的Python客户端,创建index名为linpingta-blog,type为article的访问索引,使用bulk方法批量建立索引:

      from elasticsearch import Elasticsearch
      from elasticsearch import helpers
      from blogs.models import Blog
    
      if __name__ == '__main__':
          es = Elasticsearch()
    
          # create index
          blogs = Blog.objects.all()
          actions = []
          count = 0
          for blog in blogs:
                  print blog.id, blog.title.encode("utf-8")
                  action = {
                          "_index": "linpingta-blog",
                          "_type": "article",
                          "_id": blog.id,
                          "_source": {
                                  "title": blog.title,
                                  "content": blog.content,
                                  "author": blog.author.name
                          }
                  }
                  actions.append(action)
                  count = count + 1
                  if count > 500:
                          helpers.bulk(es, actions)
                          actions = []
                          count = 0
    
          if count > 0:
                  helpers.bulk(es, actions)
    

    索引建立完毕后,可以做简单的本地验证:(

      search_word = "python"
      res = es.search(index="linpingta-blog", body={
              "query": {
                      "match":{
                              "content": search_word
                      }
              }
      })
      print "Got %d Hits" % res["hits"]["total"]
      for hit in res['hits']['hits']:
              print ("%(author)s: %(title)s" % hit["_source"]).encode("utf-8")
    

输出结果:

Got 34 Hits
褚桐: Python修饰器
褚桐: excel_convertor
...

2.博客内添加访问逻辑 在相应页面内添加form,在接受form的views方法里调用Elasticsearch,再将搜索到的博客标题对应到博客,返回给展示页面

blog.html:

<form class="m-blog-search" action="/blogs/search/" method="post">
    {\% csrf_token \%}
    <input id="search_word" type="text" name="search_word" placeholder="博客标题搜索...">
    <input type="submit" value="搜索">
</form>

views.py

def search(request):
    if request.method == 'POST':
        search_word = request.POST['search_word']
        es = Elasticsearch()
        res = es.search(index="linpingta-blog", body={
                "query": {
                        "match":{
                                "title": search_word
                        }
                }
        })
        blogs = []
        for hit in res['hits']['hits']:
                title = hit['_source']['title']
                blog = Blog.objects.get(title=title)
                blogs.append(blog)
        context = { 'blogs': blogs }
            return render_to_response('blogs/search_result.html', context=context)
    else:
        return HttpResponseRedirect('/blogs/')

3.其它 Elasticsearch自带的中文分词功能比较简陋,网上一般采用ik提供中文搜索的支持。但因为网络原因,配合2.4.1版本的ik一直不能正常下载,考虑时间成本,最后只能暂时先放弃。如文章开头所说,这篇博客只是举了一个Elasticsearch的实例,没有涉及Elasticsearch的原理,这点有待以后有机会再做学习。

策略-数据应用简介

在项目组,除了广告管理(AdManagement),统计更新(Stats Update)和规则应用(Rule Management)这三部分直接与用户交互服务相关的工作外,我其余的时间都主要投入在策略和数据这两个大的方向。后续计划会有一系列文章介绍我在这些方向上究竟做了什么,这篇博客仅仅作为一个总纲,谈谈我对它们的理解。

一. 数据分析, 策略,数据挖掘,数据监控的异同 算是先挖一个大坑吧,首先说,实际上这些概念在很多方面本身并没有明确的界限区分,比如很多招聘里的数据挖掘很可能是数据分析工作,而数据工程师又可能在做报表,所以一定要分清这些概念未必有意义。我之所以在这里拎起这些名词,主要是想把我做的事做归类,因为它们大概可以覆盖我做的策略和数据工作 (下面的内容,也是混合了概念和我具体工作的内容)。

发起者\接收者 机器
数据分析 策略
机器 数据监控 数据挖掘

关于发起者的定义是:数据规则的制定者,或者对数据操作的执行人。 关于接收者的定义是:数据规则的执行者,或者对操作结果的观察者。

1.数据分析:它是一个人到人的操作。 它更多强调的是分析师的价值,通过查看DashBoard(高端的通过BI工具)进而分析数据。分析本身有一些特定的模式(比如漏斗),分析师作为对业务最了解的人,在数据分析中起最重要的作用,更多的是偏宏观和经验的处理问题。 作为技术人员,经常会忽视数据分析的作用(或者认为这个过程没有技术含量),但在实际问题里,数据分析作为最快捷的方式,其实往往是用处最大的,所谓用20%的时间解决80%的问题,数据分析要求业务经验,技术更多的是提供辅助工具:报表,分析工具(同样不要轻视辅助工具,看到看不到,多快看到对业务价值影响很大,BI的生存点主要在此)。 多说一句题外话,我毕业后的第一份工作是在一家BI厂商(Microstrategy),因为在研发中心不会直接接触客户,当时并不了解产品的价值。现在真的到做业务后,才明白好的分析系统对数据应用的价值。

具体到我们的业务,我也有一部分工作在客户数据需求输出上面,为此我开发了一个小的任务执行框架,减轻一些工作的负担。 我近期在做的是,如何输出一些能够吸引客户的,对客户有用的数据信息。为此我搭建了从FB抓取数据到存储的数据流,也给出了一些业务相关的数据应用方案 (简单举个例子,比如图片质量得分随天变化趋势),但我更想强调的是,在我想法里,如果想吸引用户,必须抓住两点:

1.提供给用户FB现有分析工具不能提供的数据
2.要把有用数据“推”给用户,而不是等用户去查

这两点提出的原因是,FB的分析工具是很多比我更优秀的工程师开发而成的,虽然我们的系统也有报表,但无论从数据内容和展现效果看都无法相提并论,那么,用户为什么要用我们的报表分析?

第一点,举个例子,ad出价bid_amount是一个状态变量,但它直接影响广告投放给什么人群(CPM),我们去记录bid_amount与CPM的关系,分析竞争情况,这是FB自带分析工具没有的。 第二点,只是把大量数据交给运营是没有意义的,比如广告账户分天的成本,能不能让系统找到里面成本异常的时候,及时告知用户,再多做一步分析。 这两点的内容,是我在这方面工作之后的方向。

2.策略:它是一个人到机器的过程 首先你得熟悉业务。举个例子,在实际管理过大量广告后统计发现:

(1)今天或者过去7天成本达到A的ad必须要关闭
(2)发现某类账户在某些国家下新ad必须要出高于目标1.5倍的价格才能投放,然后投放稳定后价格又要回收到某个价格
(3)广告预算需要缓慢增加,甚至一定时候降低

这些信息,有些是可以通过理论解释的(比如出价高量级大),有些是不能有理论解释但客观存在的(比如某些账号的成本就会更低),它们必须通过实际业务经营获取,换句话说,就是花钱花出来的。 这些经验,我们称之为规则,可能是一个规则,也可能是一组规则,但最终规则会被表述为策略的方式,交由服务来执行,因此我觉得策略是一个由人到机器的过程,因为它是由人发现总结出来的(广告成本超过A应该停止投放),而由机器去执行的(满足要求停止投放,无论现在是凌晨还是正午)。 但策略又不仅仅是规则,有可能不是人可以观察到的,具体到我做的部分,在线策略主要包括状态机和反馈系统,离线策略主要包括动态参数的调整。这些内容我计划后续会详细介绍,这里只提一个引子:

****状态机:广告有它的生命周期,初始,稳定,衰退,用一个自管理的系统去控制广告投放时,在不同阶段有不同的投放策略。比如出价,初始阶段,可能出价会高一些去获取展现,稳定阶段又要根据实际CPA反馈去调整出价,衰退阶段可能会去尝试保CPM,不行就做关停。这些阶段的定义,ad在阶段间的跳转条件,都是通过状态机实现的

****反馈:由于我们业务的特点(在FB上投放广告,不是RTB的模式,是在FB优化的基础上再做优化),我们不能直接控制每一份流量的出价,而只能去宏观调整ad目标,这种情况下完全准确预测广告出价不太可能,所以我把系统做成一个反馈系统,简单说,看最近一段时间(花费)的成本,高了就调低,低了就调高 (但实际问题里很多时候不是这么简单的,最简单的一个问题,你要是调低的太低花不出去钱了怎么办)

****动态调整:举个例子,对每个广告账户,它能投起来的初始出价是不一样的,我们可以根据经验去规定一个统一的初始倍数,但它未必合适,如果让系统去学习一个账户过去一段时间内的平均出价和CPA关系,再确定一个初始倍数,应该合理的多。

3.数据监控:机器发现一件事情发生,及时通知相关的业务人员 策略不是万能的,而且很多时候是差很远的,因为很多人为的信息并没有告知计算机,因此操作还需要人参与。 举个例子,一个广告的日预算是500$,它花到了,广告被暂停了,那么之后是应该扩大预算继续投放呢,还是保持预算暂停呢?这个问题,可能运营会先看成本,成本低于目标继续投放,高于暂停,但业务的情况是,广告主可能在这个广告定向上有每天投放的配额,这样即使预算花到成本OK,也不能继续投放了。 这些信息,不是机器能够了解的,也不可能交给机器自主决定后续方案。 关于数据监控,对我们的问题,我在如下层次上开发监控:

基础监控:它本身也包括运维类的监控,比如CPU,IO,内存,硬盘等,以及关键字、进程状态监控,很重要,但不是我工作的重点 业务设置监控:例如用户设置了过高的出价,过小的预算或者过小的定向人群 业务状态监控:例如账户是否被封禁,创意是否被拒审 业务效果监控:例如ROI不符合预期,突然剧烈变化

监控部分的输出,我还是认为用户不会主动去看我们的报表,因此从“推”的角度,主要考虑邮件和微信(算是中国特色)两种形式,我剥离了业务本身内容后,相关的代码可见这里这里

4.数据挖掘:机器到机器的过程 策略和数据挖掘其实很难划分,因为策略也有基于离线数据的处理,数据挖掘也有在线的算法,但前者更简单,可以认为是通过规则把数据分析和监控的结果机器化处理,业务本身的耦合性更强,后者更复杂,通过目标函数的优化去发现一些人无法发现或者解决人无法处理的问题。 这部分的工作,我在项目里去年做过一些尝试,但不太成功,原因首先在于:

对数据挖掘的效果太理想化,认为用和不用能有很明显的区别

客观来讲主要在于三点:

系统本身数据量不大
A/B测试没法做
业务人员应用意愿不强

具体来说,第一点是说,我们的广告主不多,反复就这么多数据,区分性不大,第二点是FB业务本身的特点,即使所有设置都一样的两个广告投放,也可能会因为彼此抢量而又不一样的效果,无法像RTB那样按idfa保证A/B测试条件,第三点在于,既然你的系统说不出好,那么业务人员为什么要使用呢? 这部分的教训,我后续应该会专门做下记录。

数据挖掘也是我近期工作的重点,因为我们有了额外的数据源,也有了可评估的历史数据,可以基于Spark做更多的分析,但结果,现在还是未知的。 依据之前在Kaggle的一个比赛,我开发了一个简单的基于python pandas的模型框架,在这里

二. 介绍 说完上述内容,我在这里列下之后博客文章的内容,希望一步步把它们补充完整。

1.自管理系统演化:介绍策略系统的演化过程,包括在线离线部分,以及理想和现实的差距 2.离线数据流建立/应用:介绍离线数据抓取到存储的过程,以及数据分析需求的解决 3.监控内容与方式梳理:业务监控的分类,输出方式,后台服务管理 4.数据挖掘的教训和更新:我做了什么,为什么失败,之后要怎么做

Google Server to Server 广告对接

一. 为什么进行广告对接 互联网广告相比传统广告的优势之一,就是它的效果可以被监控。只有通过监控,如Google这样的大型广告服务公司才能了解广告投放效果,并且进一步按照用户的需求(主要是成本)进行优化。

二. 广告对接形式及问题 最常用的对接形式是嵌入SDK,比如Google和Facebook都有自己的SDK,在应用中添加后,当指定事件发生时,SDK会向服务器汇报事件,作为统计的依据。 另外还有一些第三方的数据监控公司,比如TalkingData, Appflyers,提供数据监控服务。 嵌入SDK主要有三方面的问题: 1. SDK 大小。 应用大小本身对用户体验有直接影响,SDK作为一部分代码添加到应用中,会直接增加应用大小,这是应用开发者或者广告主最为担心的。另一方面,如果多家广告公司(Facebook, Google, 第三方)都有自己的SDK,而应用不得不将它们都添加到应用本身的话,肯定是一个不小的量级,尤其对非游戏类app,这点是难以接受的。 2. 效率。类似第一点,SDK在事件汇报时可能会占用资源,影响用户体验 3. 安全。这点也比较明显,毕竟在自己的产品里嵌入一段不懂的代码,很可能担心它有其他影响

由于上面的问题存在,现在有了一种通过server to server(s2s)方式完成事件汇报的方法。

三. s2s 形式

如名称所示,事件汇报不再是通过SDK由应用(client)直接汇报给广告服务公司(例如Google),而是由广告主自己监控事件后,发送给广告服务公司。 需要说明的是,这里的事件指的是如应用激活或者应用内行为等后续事件,不包括广告点击和展现事件。对于点击展现事件,广告公司可以直接监控到它们。 因此s2s的事件汇报,按另外一种说法,可以称为激活核对。广告公司有所有的点击事件,广告主有所有的激活事件,它们都可以对应到设备ID(对苹果是idfa,对安卓是aaid),下面要做的事就是确定哪些激活来自哪些广告。

假设广告主为甲方,广告公司为乙方,那么按汇报方向划分,s2s可以分为两种形式: 1. 甲->乙: 广告主把自己的所有激活设备ID告知广告公司,广告公司去做点击匹配,返回广告主其中哪些设备ID的激活是来自自己的广告 2. 乙->甲: 广告公司把自己的广告点击ID告知广告主,由广告主匹配激活,返回广告公司哪些激活是来自广告 通常一个设备激活可能会有多个点击存在时,按最后一次点击的广告确认为激活来源广告。

四. Google s2s方式 首先,Google s2s的方式只支持上述“甲->乙”,即广告主汇报事件给Google的形式。 具体而言,向Google的指定网址发送一个Get请求,格式如下:

GET https://www.googleleadservices.com/pagead/conversion/conversion_id/?label=conversion_label&bundleid=xx&idtype=xx&rdid=xx

具体解释其中的参数:

conversion_id和conversion_label:广告主在Adwords上面创建账户时,会填写这两部分内容,这里的请求中要对应填写,用于确定广告信息(这里我不太确定是否是每个campaign有不同的conversion_id,但我感觉应该是一个广告主身份的确认标志,而不是campaign级别的确认)

bundleid:对于应用而言,就是包名,比如com.example

idtype:确定应用类型,只有2个值可选,分别是idfa和aaid,因为ios和android的广告汇报方式一致,所以在这里告知Google应用的类型

rdid:设备的IDFA(对安卓就是aaid),这里填的是实际值

这里idfa和aaid都不要加密。 其它可选的输入参数还包括:

appversion:告知app版本信息
value:广告主定义的价值
currency:价值对应的货币单位

对于上述请求,Google服务器会返回相关信息到广告主指定的网址:

https://广告主网址?advertising_id=ad_id ...

其中广告主网址需要在Google Adwords中配置,配置后服务器才能将信息发送到网址,返回的参数(当然你也可以理解为对广告主服务器的请求)包括:

ad_id:未加密的android设备ID,或者加密的ios md5
click_url:广告点击网址,包含点击的所有信息
click_ts:广告点击时间
campaign_id, video_id:这里campaign并不是广告活动的ID,而是特指youtube的参数,video也是youtube特有的参数

在向Google服务器请求时,返回码可能有三种状态:

1.200:表示请求格式正确,但激活并不是来自Google广告
2.302:表示请求格式正确,且激活来自Google广告
3.400:表示请求格式不正确

五. 与广点通异同 这里有ppt就很清楚了,可惜现在还没有,只能大致说下:

广点通支持 甲->乙 和 乙->甲 两种对接方式,Google现在只支持一种
其它区别主要是字段含义方面,但大致信息是一致的

六. 测试 后续结束时有一个实操测试,测试指定IDFA是否来自Google服务器,但感觉还是骗人的成分多些,实际对接要复杂不少,附Python代码如下:

def get_idfa(idfa):
    try:
        url = "https://test-1470278950694.appspot.com"
        conversion_label = 'abCDEFG12hlJk3Lm4nO'
        conversion_id = ''
        bundleid = 'com.example'
        params = {
            'rdid': idfa,
            'label': conversion_label,
            'bundleid': bundleid,
            'idtype': 'idfa',
        }
        res = requests.get(url, params=params)
        print res.status_code
        return res.status_code
    except Exception as e:
        print 'error', e

Spark常用概念

这篇博客主要涵盖了我在Spark学习中遇到的一些较为重要的概念。这些概念对我理解服务运行有很大的帮助,但由于网上有更详细更精彩的解释,我在这里并不打算全面的覆盖每个概念的解释,而主要起到对概念索引的作用。如果需要具体查找相关概念,还请在网上搜索相关名词。

概念列表 1. RDD与DataFrame:RDD是Spark计算的基础单位,它是一个逻辑概念,从程序的角度讲,所有操作都包含在RDD transformation和RDD action之上。DataFrame又称为schema RDD,意为包含元数据定义的RDD,它自1.6版本引入,后续会成为MLlib操作的首选对象。 2. action与transformation:RDD的操作都可以划分为这两类行为。它们的区别在于,transformation的输出是另外一个RDD,action的输出是其它对象或文件。执行上讲,transformation操作并不会立即执行,它是lazy的,Spark只是记录它的执行路径,只有遇到action时,相关操作才会转为physical execution plan具体执行。 3. driver, executor, master, worker:有时候会容易把driver, master或者executor,worker混淆。首先,对Spark程序而言,它只有driver和executor两个概念,driver负责生成tasks,executor负责具体执行。master和worker是cluster manager的概念,cluster manager负责对集群的资源进行调度,因此无论driver还是executor,都是执行在worker node上的,同时driver和master的任务目标也是不同的。 4. cluster manager类别:Spark支持三种集群管理方式:standalone,Mesos和YARN,YARN里面又分为yarn-client和yarn-cluster两种模式。Mesos和YARN都是成熟的集群管理方式,在生产环境里用的更多些,也方便与Hadoop任务贡献资源,standalone是单独的集群,意味对资源的单独占用 5. yarn-client和yarn-cluster的区别:主要在于driver的未知,yarn-client的driver是运行的客户端的,如果客户端关闭,任务就结束了,yarn-cluster的driver是运行在集群ApplicatoinMaster上的,任务运行时不要求客户端active 6. ApplicationMaster是什么:这是YARN的一个概念,所有任务必须通过ApplicationMaster作为容器包装, 它负责与ResourceManager交互,申请资源 7. driver-memory和executor-memory应该如何设置:driver-memory通常不需要太大,因为driver上面不执行任务,但如果有collect操作还是要大一些,一般设置1G即可,executor-memory根据任务实际的需要可以大一些 8. Spark执行参数设置:董有一系列博客,涉及参数设置还有调优,对原理也讲得很多,建议看看 9. DAG:有向无环图,Spark执行任务的基础,每次遇到action时,它会根据依赖,去找所有祖先节点的任务,构成一个DAG后,再作为job执行 10. job,stage和task:相比于RDD是Spark的逻辑概念,job,stage和stask都是Spark物理执行中的概念。它们基本是包含关系,每个job可能会被拆分为多个stage,每个stage里包含多个task。它们都可以在Spark Web UI上查看执行情况。具体而言,每个action操作都会产生一个单独的job,job会根据情况划分stage,主要是根据任务是否可以在本地执行,或者说是否有shuffle,每次shuffle都会划分出两个stage,同一个stage里的任务可以并行执行parallel。 11. shuffle:从底层讲,Spark任何执行也可以划分为map类和reduce类,比如filter,map都是在本地操作,而reduceByKey则需要对key进行归并。每次reduce操作都会引起shuffle,shuffle包括shuffle read和shuffle write,都是对磁盘的操作,因此从性能角度出发,尽可能减少shuffle操作可以提高执行效率 12. Spark Web UI:查看任务执行情况,包括job, stage, storage, task,其中stage里面可以看到执行DAG情况,通常在提交任务机器的4040端口查看,查看只能在任务执行区间,任务结束后不能再查看 13. Spark SQL和Hive QL:区别还是在底层实现框架上,Spark SQL是采用Spark执行,而Hive QL是把任务翻译成map-reduce执行 14. Spark比Hadoop快:并不是绝对的,在某些任务上Spark不一定比Hadoop快。Spark快的主要原因在于:

    1.Spark把任务视为一个DAG执行,在任务间可以并行化,Hadoop是把每个任务作为map-reduce执行,并不会对任务间优化
    2.Spark的任务主要是在内存里计算的,而map-reduce每次都会有shuffle操作,因此对于迭代类任务,大量的读写会较慢
    3.Spark的executor在初始化时启动jvm,后续不需要每次都重新启动,Hadoop的jvm会为每个任务新启动,初始化时间消耗也是一个因素

15.Spark cache或者persist:默认情况下,每个action都会重新计算它链路上所有的RDD,但如果有RDD被多次重复使用,可以使用rdd.persist()来做缓存,避免重复计算 16.Spark与内存:Spark并不要求把所有数据都放到内存里才能计算

暂时想到的是这么多,在此记录下。

Xgboost-Spark应用调研

官方文档 https://spark.apache.org/docs/2.0.0/ml-guide.html 本文假设已正常安装spark和scala,其中spark为standalone模式。

1.官方的介绍资料在这里

2.下载地址:https://github.com/dmlc/xgboost

3.样例运行 (1) 在完成上述下载后,进入git项目根目录,执行make编译源文件生成lib/xgboost.so 在这一步中,如果遇到宏引用未定义错误,可能需要在对应文件中添加include或者宏定义。 (2) 进入jvm-package目录,执行mvn package编程生成jar包 不同于spark官网的scala项目管理文件用sbt完成,xgboost项目采用maven完成包管理,如果没有安装maven,需先安装maven2 (3) 运行纯scala示例 在上一步完成后,jvm-package/xgboost-example/target目录下已经生成了对应的jar文件,进入xgboost-example目录,以BoostFromPrediction为例,执行如下操作:

scala -cp target/xgboost4j-example-0.7-jar-with-dependencies.jar ml.dmlc.xgboost4j.scala.example.BoostFromPrediction

在这里遇到的问题可能是”scala error: main method is not static“,原因是scala的main函数需要放在object对象下面而不是class对象下面来实现static,因此解决方法是把对应源文件的class对应替换为object并重新编译打包,正常的输出如下:

[0]     train-error:0.046522    test-error:0.042831
[1]     train-error:0.022263    test-error:0.021726

(4) 运行spark示例 完成上述编译后,在spark目录下提交任务:

./bin/spark-submit --master local[4] --class ml.dmlc.xgboost4j.scala.example.spark.DistTrainWithSpark /home/test/xgboost/jvm-packages/xgboost4j-example/target/xgboost4j-example-0.7-jar-with-dependencies.jar 30 1 /home/test/xgboost/demo/data/agaricus.txt.train /home/test/xgboost/demo/data/agaricus.txt.test /home/test/xgboost/demo/data/result

xgboost目前只有一个spark示例,上述各项参数的含义在源码中有清晰描述.

Tools相关项目

tools

Github项目保存了一些独立的项目和脚本,像项目名称所表示的,它们是用于提高工作效率而开发的,并且可能会在适当的时候被提取划分为独立的项目。

  1. project_maker.sh :快速生成python常用项目的基本目录结构
  2. fabfile.py :基于fabric,保存常用的操作,包括git命令和远程目录操作
  3. template_maker : 用于模板信息的快速填充,基础基于Jinja2,主要是在工作中常用的离线模型和数据监控脚本自动生成
  4. model_trainer :用于machine learning项目的基本框架,我在参加Kaggle竞赛中使用了
  5. task_manager : 用于离线任务的调度,基于DAG执行db任务和用户定义任务