Search on the blog

2018年4月21日土曜日

A Survey on Real Time Bidding Advertising

元ネタ
A Survey on Real Time Bidding Advertising

気付き
- DSPはSSPから広告枠を買い付けていると思っていたが、厳密にはAd Exchangeから買っているらしい。SSPと思っていたサービスのサイトをよく見ると、確かにAd Exchangeと書いていた。

- 広告主からマージンをもらって儲けるOpen DSPと、自社の様々な事業の広告を配信して事業の売り上げ増加を目指すNative DSPでは戦略が違うので、その辺りを意識してサーベイする必要がある。

メモ
RTB広告の成長
- 2017年にはディスプレイ広告予算の29%を占める見込み
- 昔: プレミアムな在庫はオフラインで、残りはRTBで
- 最近: プレミアムな在庫もRTBで

DSPでは効率的な入札アルゴリズムが重要
- 最適な広告を選択する
- 最適な入札額を決める

DSPが目指すパフォーマンスの最大化
- インプレッション数
- クリック数
- コンバージョン数

DSPの時間制約
- 10〜100 milli sec
- 入札戦略を最適化するオフラインコンポーネント
- 前もって最適化された戦略を実行するオンラインコンポーネント

予算配分
- 複数の媒体
- DSP業者
- キャンペーン

オークション方式
- Vickery (セカンドプライス)
- OSP (オプショナルセカンドプライス)

フリークエンシーキャップの最適化
- RTBでは個々のクッキーに対してフリークエンシーを制御できる
- 広告主の予算の浪費を抑える

2018年4月19日木曜日

Apache Beam: 正常データ、異常データを別々に処理

 Beam 1.xのときは、side outputを使って異常系のデータをDLQに別途出力するということができた。Beam 2.xになってからside outputがなくなっていたので、どうやるかを試してみた。以下のようにPTransformの中でタグをつけて出力すれば良さそう。

 今回はDLQとしてキューじゃないけどGCSを使ってみた。UnboundedなデータをBoundedな場所に格納することになるのでややこしそうだけど、windowを使えば簡単に実現できる。

サンプルコード

package com.kenjih.sample.side_output;

import org.apache.beam.runners.dataflow.options.DataflowPipelineOptions;
import org.apache.beam.sdk.Pipeline;
import org.apache.beam.sdk.io.TextIO;
import org.apache.beam.sdk.io.gcp.pubsub.PubsubIO;
import org.apache.beam.sdk.options.PipelineOptionsFactory;
import org.apache.beam.sdk.transforms.DoFn;
import org.apache.beam.sdk.transforms.ParDo;
import org.apache.beam.sdk.transforms.windowing.FixedWindows;
import org.apache.beam.sdk.transforms.windowing.Window;
import org.apache.beam.sdk.values.PCollection;
import org.apache.beam.sdk.values.PCollectionTuple;
import org.apache.beam.sdk.values.TupleTag;
import org.apache.beam.sdk.values.TupleTagList;
import org.joda.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * GCS-based Dead letter queue example
 *
 * @see <a href="https://stackoverflow.com/questions/45173668/gcp-dataflow-2-0-pubsub-to-gcs/45256314#45256314">GCP Dataflow 2.0 PubSub to GCS</a>
 * @see <a href="https://cloud.google.com/blog/big-data/2016/01/handling-invalid-inputs-in-dataflow">Handling Invalid Inputs in Dataflow</a>
 */
public class Main {

  private static final Logger LOG = LoggerFactory.getLogger(Main.class);

  private static class ParseToIntDoFn extends DoFn<String, String> {

    private final TupleTag<String> successTag;
    private final TupleTag<String> invalidTag;

    public ParseToIntDoFn(TupleTag<String> successTag, TupleTag<String> invalidTag) {
      this.successTag = successTag;
      this.invalidTag = invalidTag;
    }

    @ProcessElement
    public void processElement(ProcessContext c) {
      String s = c.element();
      try {
        Integer i = Integer.parseInt(s);
        c.output(successTag, i.toString());
      } catch (Exception e) {
        LOG.error("cannot convert {} to an integer", s);
        c.output(invalidTag, s);
      }
    }
  }

  private static String createTopicPath(String projectId, String topicName) {
    return String.format("projects/%s/topics/%s", projectId, topicName);
  }

  public static void main(String[] args) {
    LOG.info("begin the application");

    DataflowPipelineOptions options = PipelineOptionsFactory.fromArgs(args).withValidation()
        .as(DataflowPipelineOptions.class);
    options.setStreaming(true);

    Pipeline pipeline = Pipeline.create(options);
    String projectId = options.getProject();

    final TupleTag<String> successTag = new TupleTag<String>() {};
    final TupleTag<String> invalidTag = new TupleTag<String>() {};

    PCollection<String> input =
        pipeline.apply("read",
            PubsubIO.readStrings().fromTopic(createTopicPath(projectId, "kh-test-in-1")));

    PCollectionTuple outputTuple =
        input.apply("parse", ParDo.of(new ParseToIntDoFn(successTag, invalidTag))
            .withOutputTags(successTag, TupleTagList.of(invalidTag)));

    // write successful data to PubSub
    PCollection<String> success = outputTuple.get(successTag);
    success
        .apply("success-write", PubsubIO.writeStrings()
            .to(createTopicPath(projectId, "kh-test-out-1")));

    // write invalid data to GCS
    PCollection<String> invalid = outputTuple.get(invalidTag);
    invalid
        .apply("windowing", Window.into(FixedWindows.of(Duration.standardMinutes(1))))
        .apply("invalid-write", TextIO.write()
            .to("gs://kh-test/dead-letter/")
            .withNumShards(3)
            .withWindowedWrites());

    pipeline.run();

    LOG.info("end the application");
  }


}

Dataflow Runnerでの実行した結果

2018年4月18日水曜日

español 18

¡Hola a todos!


今日はバールに行って注文する表現を勉強しました。
よく使いそうな単語には個別でメモをつけるようにしました。

Qué van a tomar?
注文は何にしましょうか?
  - qué = what
  - van (3rd person plural of "ir") = go
  - a = to
  - tomar = to take, to drink

Un café con leche, por favor.
ミルクコーヒーをください。

Para mí un té con limón y para mi amigo una limonada.
私はレモンティーで、友達にはレモネードをください。
  - para = for
  - con = with

Quiero un vino tinto (blanco).
赤(白)ワインが欲しいです。
 - quiero (1st person singular of "querer") = want, love

Otro zumo de naranja, por favor.
オレンジジュースをもう一つください。
  - otro = another

Otra Coca Cola, por favor.
コカコーラをもう一つください。
  - otra = otroの女性形

¿Se puede comer aqui?
ここで食べれますか?
  - puede (3rd person singular of "poder") = can

Nos (Me) trae la carta, por favor.
私たちに(私に)メニューを持ってきてください。

Quieren algo más?
他にも注文はありますか?
  - algo = something
  - más = more 

Nos trae más agua con (sin) gas, por favor.
炭酸の入っている(いない)水をもっとください。
  - con = with
  - sin = without

Nos trae la cuenta, por favor.
お会計お願いします。

Gracias y hasta pronto.

2018年4月17日火曜日

Apache Beam: 複数のパイプラインをあげる

 Apache Beamで構築するグラフはDAGじゃないといけないという縛りがありますが、DAGであれば連結なグラフじゃなくてもいいらしいです。(これに気づかなくて数ヶ月悩んでました。)
 
 実際にそのようなサンプルを書いて動作確認してみました。

package com.kenjih.multiple_pipe_line;

import org.apache.beam.runners.dataflow.options.DataflowPipelineOptions;
import org.apache.beam.sdk.Pipeline;
import org.apache.beam.sdk.io.gcp.pubsub.PubsubIO;
import org.apache.beam.sdk.options.PipelineOptionsFactory;
import org.apache.beam.sdk.transforms.DoFn;
import org.apache.beam.sdk.transforms.ParDo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {

  private static final Logger LOG = LoggerFactory.getLogger(Main.class);

  private static class AddPrefixDoFn extends DoFn<String, String> {

    private final String prefix;

    private AddPrefixDoFn(String prefix) {
      this.prefix = prefix;
    }

    @ProcessElement
    public void processElement(ProcessContext context) {
      String element = context.element();
      context.output(prefix + element);
    }
  }

  private String createTopicPath(String projectId, String topicName) {
    return String.format("projects/%s/topics/%s",
        projectId,
        topicName);
  }

  public void run(DataflowPipelineOptions options) {
    Pipeline pipeline = Pipeline.create(options);
    String projectId = options.getProject();

    // a data pipeline
    pipeline
        .apply("read-1",
            PubsubIO.readStrings().fromTopic(createTopicPath(projectId, "kh-test-in-1")))
        .apply("transform-1",
            ParDo.of(new AddPrefixDoFn("transform-1: ")))
        .apply("write-1",
            PubsubIO.writeStrings().to(createTopicPath(projectId, "kh-test-out-1")));

    // another data pipeline
    pipeline
        .apply("read-2",
            PubsubIO.readStrings().fromTopic(createTopicPath(projectId, "kh-test-in-2")))
        .apply("transform-2",
            ParDo.of(new AddPrefixDoFn("transform-2: ")))
        .apply("write-2",
            PubsubIO.writeStrings().to(createTopicPath(projectId, "kh-test-out-2")));

    // yet another data pipeline
    pipeline
        .apply("read-3",
            PubsubIO.readStrings().fromTopic(createTopicPath(projectId, "kh-test-in-3")))
        .apply("transform-3",
            ParDo.of(new AddPrefixDoFn("transform-3: ")))
        .apply("write-3",
            PubsubIO.writeStrings().to(createTopicPath(projectId, "kh-test-out-3")));

    pipeline.run();
  }

  public static void main(String[] args) {
    LOG.info("begin the application");

    DataflowPipelineOptions options = PipelineOptionsFactory.fromArgs(args).withValidation()
        .as(DataflowPipelineOptions.class);
    options.setStreaming(true);

    new Main().run(options);

    LOG.info("end the application");
  }

}

GCPのコンソール画面で見ると以下のようになっています。
BigQueryIOのDynamicDestinationsのようなものがPubSubIOにはなくて困っていましたが、上のように独立したパイプラインを作ることができるので、それを応用することでDynamicDestinations的なことができそうです(パイプライン生成時に静的に決めないといけないのでDynamicとは違いますが、複数の可変的な汎用パイプラインをうまく扱えそうという意味です)。

2018年4月13日金曜日

español 17

Hola a todos.
Me llamo Kenji. So de Japón.
Estoy aprendiendo español.


今日は場所に関する表現を勉強した。
これでスペイン語圏に旅行に行っても迷子になることはないはずだ。

Dónde está el museo?
博物館はどこですか?

¿Por favor, dónde está el baño?
すみません。トイレはどこですか?

Siga todo recto.
まっすぐ行ってください。

Doble a la derecha.
右に曲がってください。

Doble a la izquierda.
左に曲がってください。

¿Hay un restaurante por aquí?
近くにレストランはありますか?

Mi casa está cerca del mercado.
私の家は市場の近くにあります。

El hotel está lejos del aeropuerto.
ホテルは空港から遠いです。

El banco está frente a la cafetería.
銀行はカフェテリアの前にあります。

El bar está al lado de la farmacia.
バーは薬局の隣にあります。

Adiós y gracias.

2018年4月12日木曜日

español 16

¡Hola, buenas noches!

今日は、曜日、月、季節の単語を勉強した。

曜日
月曜日: lunes
火曜日: martes
水曜日: miércoles
木曜日: jueves
金曜日: viernes
土曜日: sábado
日曜日: domingo

Hoy es lunes. 今日は月曜日です。


1月: enero
2月: febrero
3月: marzo
4月: abril
5月: mayo
6月: junio
7月: julio
8月: agosto
9月: septiembre
10月: octubre
11月: noviembre
12月: diciembre

Mi cumpleaños es el doce de abril. 私の誕生日は4/12です。

季節
春: primavera
夏: verano
秋: otoño
冬: invierno

La primavera empieza en marzo. 春は3月から始まる。

2018年4月7日土曜日

GCP上に無料でプロキシーサーバを立てる

 GCPの無料枠が余っていたので、プロキシーサーバを立てて遊んでみた。

1. VMインスタンスを作る
- VMの名前はsquidとしておく
- ゾーンはus-east1-bにしておく

2. 作成したVMにログイン
- gcloud compute ssh squid --zone=us-east1-b

3. squidをインストール
- sudo yum install -y squid

4. 設定ファイルを変更
- sudo diff -u /etc/squid/squid.conf.default /etc/squid/squid.conf

--- /etc/squid/squid.conf.default
+++ /etc/squid/squid.conf
@@ -10,6 +10,7 @@
 acl localnet src fc00::/7       # RFC 4193 local private network range
 acl localnet src fe80::/10      # RFC 4291 link-local (directly plugged) machines
+acl myhome src xxx.xxx.xxx.xxx  # my home IP

 acl SSL_ports port 443
 acl Safe_ports port 80  # http
@@ -51,6 +52,7 @@
 # from where browsing should be allowed
 http_access allow localnet
 http_access allow localhost
+http_access allow myhome

 # And finally deny all other access to this proxy
 http_access deny all
@@ -71,3 +73,14 @@
 refresh_pattern ^gopher: 1440 0% 1440
 refresh_pattern -i (/cgi-bin/|\?) 0 0% 0
 refresh_pattern .  0 20% 4320
+
+# prevent squid from being detected
+forwarded_for off
+request_header_access Referer deny all
+request_header_access X-Forwarded-For deny all
+request_header_access Via deny all
+request_header_access Cache-Control deny all
+
+# hostname
+visible_hostname squid
+

5. squidを起動
- sudo systemctl start squid

6. firewallの設定
- tcp:3128を開けておく

7. ブラウザ(chrome on Mac)の設定
- 設定 >詳細設定 >プロキシ設定を開く >Webプロキシ(HTTP)
- squidサーバのパブリックIP、ポート(3128)を入力

8. アクセス確認
- ブラウザから適当なサイトにアクセス
- squidのログ確認
- sudo tail -f /var/log/squid/access.log