astamuse Lab

astamuse Labとは、アスタミューゼのエンジニアとデザイナーのブログです。アスタミューゼの事業・サービスを支えている知識と舞台裏の今を発信しています。

Spark でUnit Testを書く

こんにちは、朴と申します。

本日はSpark-testing-baseを使ってSpark処理の単体テストの書き方について触れてみたいと思います。
ローカルで並列処理の単体テスト動かすのは少しハードル高く感じるかもしれませんが、
ちょっとした設定でスムーズに動かせたので、設定から簡単なテストまで書いてみたいと思います。

Spark-testing-baseの概要

ローカルでSparkのプログラムの単体テストを書く為のフレームワークです
複雑な設定をせず簡単にSparkプログラムの初期化、起動ができます。
簡単な紹介など、以下のリンクから確認できます。
Spark-testing-base

設定

とりあえず、公式サイトにある通り設定を行います。

  • sbtプロジェクトのbuild.sbtに以下のdependencyを追加します。
    ※2.2.0の部分は実際使うSparkのバージョンに合わせてください。
"com.holdenkarau" %% "spark-testing-base" % "2.2.0_0.8.0" % "test"


  • OutOfMemoryError 防止の為以下の設定も追加して置きます、メモリは適宜調整してください。
fork in Test := true
javaOptions ++= Seq("-Xms512M", "-Xmx2048M", "-XX:MaxPermSize=2048M", "-XX:+CMSClassUnloadingEnabled")


  • テストでのparallel 実行を無効にする
parallelExecution in Test := false

以上で設定完了です。

書いてみる1

続いて公式サイトのwikiにあるサンプルを一個書いて、動作確認をします。

class test extends FunSuite with DatasetSuiteBase {
  test("simple test") {
    val sqlCtx = sqlContext
    import sqlCtx.implicits._

    val input1 = sc.parallelize(List(1, 2, 3)).toDS
    assertDatasetEquals(input1, input1) // equal

    val input2 = sc.parallelize(List(4, 5, 6)).toDS
    intercept[org.scalatest.exceptions.TestFailedException] {
        assertDatasetEquals(input1, input2) // not equal
    }
  }
}

IntelliJ IDEA起動して動かしますと以下のエラーが出てました。

java.lang.NoClassDefFoundError: org/apache/hadoop/hive/conf/HiveConf$ConfVars
...
...
Caused by: java.lang.ClassNotFoundException: org.apache.hadoop.hive.conf.HiveConf$ConfVars

エラー内容でググってみたらどうやらdependencyが足りないようなので、以下を追加します。
sparkVersionはご自身の環境に合わせてください。

"org.apache.spark" %% "spark-hive" % sparkVersion % "test"

今度無事起動するかと思ったら違うエラーが出てました。 どうやらHiveSession起動時にローカルだとうまく行かないみたいですね。

java.lang.IllegalArgumentException: Error while instantiating 'org.apache.spark.sql.hive.HiveSessionStateBuilder':
...
...
Caused by: java.lang.RuntimeException: java.lang.RuntimeException: Error while running command to get file permissions : java.io.IOException: (null) entry in command string: null ls -F C:\tmp\hive

Spark Hiveをoffにすることでエラーは回避できるみたいなので、
上記のテストケースの一番最初に以下を追加します。

class test extends FunSuite with DatasetSuiteBase {
  override implicit def enableHiveSupport: Boolean = false  // 追加
  test("simple test") {
    val sqlCtx = sqlContext
    import sqlCtx.implicits._

    val input1 = sc.parallelize(List(1, 2, 3)).toDS
    assertDatasetEquals(input1, input1) // equal

    val input2 = sc.parallelize(List(4, 5, 6)).toDS
    intercept[org.scalatest.exceptions.TestFailedException] {
        assertDatasetEquals(input1, input2) // not equal
    }
  }
}

これで無事起動できました。

 主なクラスの紹介

  • SharedSparkContext
    SparkContextをローカルで使える形で提供

  • RDDComparisons
    RDD比較用クラス

  • DataFrameSuiteBase
    DataFrameのテスト用

  • DatasetSuitBase
    Datasetのテスト用

  • StreamingSuiteBase
    streamのテスト用

各クラス使い方は公式サイトのwikiに乗ってる通り直感的で分かりやすいので、ここでは割愛します。

自分のテストを書いてみる

いよいよご自身のテストを書きます。
以下のようなクラウドファンディングデータを読み込んでそのデータが期待とおりとなってるか確認します。
以下データ一部※

{
  "raised": {
    "currency": "USD",
    "amount": 1685
  },
  "goal": {
    "currency": "USD",
    "amount": 2000
  },
  "short_description": "to find a better/safer way to diagnose, monitor and guide those with EGID's on their life journey",
  "name": "EoE Chompers",
  "project_id": "193451"
}
{
  "raised": {
    "currency": "USD",
    "amount": 50
  },
  "goal": {
    "currency": "USD",
    "amount": 12000
  },
  "short_description": "Help provide 70 children with a new roof to keep them healthy and a boundary wall to keep them safe!",
  "name": "A New Roof and Safety Wall for 70 children",
  "project_id": "193469"
}

テストコード

class MyTest1 extends FunSuite with DatasetSuiteBase {
  override implicit def enableHiveSupport: Boolean = false

  test("count should be equal") {
    val ds1 = spark.read.json("c:\\temp\\cf_example_projects.json.gz")
    assertTrue(ds1.count() == 38080) // 実際Datasetのレコード件数とファイルの行数が一致するか
  }

  test("id should be unique") {
    val ds1 = spark.read.json("c:\\temp\\cf_example_projects.json.gz")
    val projectIdCount = ds1.select("project_id").distinct().count()
    assertTrue(projectIdCount == 38080)
  }
}

ちょっと進化させてみる

上のファイルを読み込む部分は以下のようにメソッド化して、このメソッドについてテストを書きます

object MyObject {
  case class Cf(projectId : String, name : String, shortDesc : String, currency : String, goalAmount: Double,raisedAmount:Double)
  def json2ds(spark:SparkSession,path:String) : Dataset[Cf] = {
    import spark.implicits._
    val ds1 = spark.read.json(path)
    ds1.map { r =>
      Cf(
        r.getAs[String]("project_id"),
        r.getAs[String]("name"),
        r.getAs[String]("short_description"),
        r.getAs[Row]("goal").getAs[String]("currency"),
        r.getAs[Row]("goal").getAs[Double]("amount"),
        r.getAs[Row]("raised").getAs[Double]("amount")
      )
    }
  }
}


class MyTest2 extends FunSuite with DatasetSuiteBase {
  override implicit def enableHiveSupport: Boolean = false

  test("one of selected record should be exist") {
    val ds1 = MyObject.json2ds(spark,"c:\\temp\\cf_example_projects.json.gz")
    val result = ds1.where($"projectId".equalTo("193469"))
    val excepted = Seq(Cf("193469","A New Roof and Safety Wall for 70 children","Help provide 70 children with a new roof to keep them healthy and a boundary wall to keep them safe!","USD",15000d,50d)).toDS()
    assertDatasetEquals(excepted,result)
  }

  test("no such record") {
    val ds1 = MyObject.json2ds(spark,"c:\\temp\\cf_example_projects.json.gz")
    val result = ds1.where($"projectId".equalTo("xxxxxx"))
    val excepted = Seq(Cf("xxxxxx","A New Roof and Safety Wall for 70 children","Help provide 70 children with a new roof to keep them healthy and a boundary wall to keep them safe!","USD",12000d,50d)).toDS()
    intercept[org.scalatest.exceptions.TestFailedException] {
      assertDatasetEquals(result, excepted)
    }
  }
}

テストデータ作成用のクラス(Generator)も

使う場面がちょっと微妙ですが、テストデータ用のDataset、DataFrameを生成することも出来ます。

サンプルそのまま張り付けておきますが、こんな感じです。
動かすにはhttps://github.com/rickynils/scalacheck/blob/master/doc/UserGuide.mdというものが必要らしいです。

class SampleDatasetGeneratorTest extends FunSuite with SharedSparkContext with Checkers {
  test("test generating Datasets[String]") {
    val sqlContext = new SQLContext(sc)
    import sqlContext.implicits._

    val property =
      forAll(DatasetGenerator.genDataset[String](sqlContext)(Arbitrary.arbitrary[String])) {
        dataset => dataset.map(_.length).count() == dataset.count()
      }

    check(property)
  }

}

最後に

Sparkの単体テストについてはメジャーなやつがないのが現状だと認識してますので、
ここで紹介したのは一つの選択肢としてみて頂ければと思います
上記以外にもいくつか選択肢があるみたいですので、色々試してみるとご自身に合うものを見つけられるでしょう。

弊社ではエンジニア・デザイナー絶賛大募集中ですので、興味ある方は気軽にお話でも聞いてみませんか?

DB3分クッキング Neo4jではじめるグラフデータベース入門

f:id:astamuse:20180123182120j:plain

こんにちは、福田 a.k.a. FDKです。

バルトークのアレグロ・バルバロを聴きながらバルクロードを実行しています。

点と点をこねくり回していると、イノヴェイションが生まれることがあります。

そう、まさに “Connecting the Dots” の瞬間です。

はじめに

グラフは、点と点を線で繋いだ構造で、この世界の至るところに見られます。 点同士を結ぶ線に向きや重みを持たせて関係性を表現することで、様々な探索ができるようになります。

例えば、SNS。人を点、友達関係を線で表し、友達関係の連鎖を辿り新たな関係を知るといったことはイメージしやすいと思います。

ボキャブラリ。わたしたちがものごとを考えるとき、言葉は欠かせません。キーワード同士にも何らかの関係があり、豊かな語彙は文脈とともに思索の飛び石となります。

ここでは、特許文書に現れるキーワードとその近接関係、そしてキーワードが現れる文脈との関係データをグラフ化して探索をしてみます。

Neo4jとは

グラフデータベースは、グラフ構造のデータの格納と検索に特化したデータベースです。商用、オープンソース、様々なプロダクトがあり、広く使われています。

Neo4jは、Neo Technology社により開発されたデータベースで、2010年にバージョン1.0がリリースされました。2018年1月23日時点での最新の安定バージョンは3.3.2です。

特徴として以下があげられます。

  • Cypherと呼ばれる宣言型グラフ向けクエリ言語
  • ACIDトランザクションのサポート
  • プロパティグラフ(点と線それぞれにkey-valueの形式で複数の属性を持たせられる)
  • Javaで実装されている
  • Boltというバイナリプロトコル(3.0以降)
  • 豊富なドライバ(.NET、Java、JavaScript、Python、Ruby、 PHP、R、Go、Erlang、C/C++、Clojure、Perl, Haskellなど)
  • 内部的にLuceneを使用
  • コミュニティ版とエンタープライズ版
  • GPLv3 及び AGPLv3 / 商用

*1

レシピ編

データ

  • キーワードのデータ(4,722,057行)
  • キーワード同士の関連データ(136,802,075行)
  • 課題文脈のデータ(22,444行)
  • キーワードと課題の関連データ(114,832,650行)

手元の実験用テストデータ。日本国内特許公報データをもとに、Hadoop、Word2vecなどを使って生成したデータになります。アスタミューゼにはデータがたくさんあり、データからデータを生成できます。

環境・ミドルウェア

  • GCP n1-highmem-8 preemptible*2 (8 vCPU, 52GB RAM, SSD storage 100 GB)
  • Linux (Ubuntu 16.04.3 LTS)
  • Neo4j 3.4.0 alpha06*3

設計

Neo4jで扱うプロパティグラフの形式に落とし込みます。

まず、キーワードと、課題文脈のデータ*4をNodeに対応させます。

そして、Node同士の関係をRelationshipに対応させます。関係の向きと重み、ラベルを定義します。

角丸の四角がNode(点)、矢印がRelation(線)になります。

f:id:astamuse:20180123193603p:plain

準備編

マニュアル*5を参照しながらセットアップします。

ここではUbuntu 16.04.3 LTSにNeo4j 3.4.0 alpha06をインストールします。

インストール

$ wget -O - https://debian.neo4j.org/neotechnology.gpg.key | sudo apt-key add -
$ echo 'deb http://debian.neo4j.org/repo testing/' | sudo tee -a /etc/apt/sources.list.d/neo4j.list
$ sudo apt-get update
$ sudo apt-get install neo4j=3.4.0.alpha06

*6

設定

設定ファイル/etc/neo4j/neo4j.confの編集

35,36c35,36
< #dbms.memory.heap.initial_size=512m
< #dbms.memory.heap.max_size=512m
---
> dbms.memory.heap.initial_size=16G
> dbms.memory.heap.max_size=16G
46c46
< #dbms.memory.pagecache.size=10g
---
> dbms.memory.pagecache.size=32g
54c54
< #dbms.connectors.default_listen_address=0.0.0.0
---
> dbms.connectors.default_listen_address=0.0.0.0
< #dbms.threads.worker_count=
---
> dbms.threads.worker_count=8

調理編

CSVの作成

Neo4jには大量のデータをCSVから効率良くロードするためのneo4j-admin importコマンド*7があります。このコマンドはデータの初期ロード専用で、データベースディレクトリが空でないと実行できませんが、高速にデータをロードできます。既存のデータベースへのデータのバルクロードには別途LOAD CSVコマンド*8を使用します。

Node、RelationshipのCSVファイルを複数用意し、一括でロードします。それぞれのCSVファイルのヘッダに、ロード先のスキーマを表現します。詳しくはマニュアル*9にあります。

ここでは、前述の設計に従って、CSVファイルを用意します。

keyword.csv:KeywordのNodeデータ

id:ID(Keyword-ID),name:string,:LABEL
12852111,プリプリ感,Keyword
115168,プリプレグ,Keyword
10515289,プリプレグFRPシート,Keyword
10515208,プリプレグアセンブリ,Keyword
12852112,プリプレグカセット,Keyword
10515209,プリプレグクロス,Keyword
10515210,プリプレグシート,Keyword

issue_fterm_context.csv:FTermのNodeデータ

id:ID(FTerm-ID),code,context,:LABEL
1,2B022EA00,植物の栽培/化学物質の処理目的,FTerm
2,2B022EA01,植物の栽培/化学物質の処理目的/生長調節,FTerm
3,2B022EA02,植物の栽培/化学物質の処理目的/生長調節/種なし果実の作成,FTerm
4,2B022EA03,植物の栽培/化学物質の処理目的/果実、野菜の食味、色付きの向上,FTerm
5,2B022EA10,植物の栽培/化学物質の処理目的/その他,FTerm
6,2B026BB00,海藻の栽培/支柱の目的、機能,FTerm
7,2B026BB01,海藻の栽培/支柱の目的、機能/強度を増す,FTerm
8,2B026BB02,海藻の栽培/支柱の目的、機能/打ち込み引きぬき,FTerm
9,2B026BB03,海藻の栽培/支柱の目的、機能/倒伏防止,FTerm

Nodeデータのフォーマットは、(属性[:型],)+:LABELのような形になります。:IDは識別子であることを表し、Node毎に必ず指定します。:IDの後ろの(Keyword-ID)は、IDの名前空間を指定しています。デフォルトではNodeの名前空間は一つになります。リレーショナル・データベースでよくみられるように数値型のIDを持つエンティティが複数ある場合には、それぞれのIDの名前空間を分ける必要があります。

similarity.csv:SIMILAR_TOのRelationsipデータ

:START_ID(Keyword-ID),score:double,:END_ID(Keyword-ID),:TYPE
14384010,0.846703052521,14251003,SIMILAR_TO
14164776,0.840930700302,14251003,SIMILAR_TO
11690273,0.817037463188,14251003,SIMILAR_TO
10211878,0.808667063713,14251003,SIMILAR_TO
10446057,0.798547029495,14251003,SIMILAR_TO
13343895,0.795734345913,14251003,SIMILAR_TO
14335569,0.789334237576,14251003,SIMILAR_TO
12207055,0.788067042828,14251003,SIMILAR_TO
12698152,0.784716844559,14251003,SIMILAR_TO

keyword_issue.csv:RESOLVEのRelationshipデータ

:START_ID(Keyword-ID),n:int,:END_ID(FTerm-ID),:TYPE
12338970,1,2,RESOLVE
42045,1,2,RESOLVE
10181692,1,2,RESOLVE
12637528,1,2,RESOLVE
12676513,1,2,RESOLVE
109171,1,2,RESOLVE
10799840,1,2,RESOLVE
13335687,1,2,RESOLVE
11029079,8,2,RESOLVE

データロード

そして、以下のような簡易ロードスクリプトを用意しました。

#!/bin/bash

systemctl stop neo4j

rm -rf /var/lib/neo4j/data/databases/graph.db/*

NEO4J_DEBUG=true HEAP_SIZE=16g neo4j-admin import --nodes issue_fterm_context.csv --nodes keyword.csv --relation
ships similarity.csv --relationships keyword_issue.csv --ignore-missing-nodes=true --ignore-duplicate-nodes=true
-id-type=INTEGER

chown -R neo4j:neo4j /var/lib/neo4j/data/databases/graph.db/

systemctl start neo4j

実行すると、進捗とサマリーが表示されます。

Neo4j version: 3.4.0-alpha06
Importing the contents of these files into /var/lib/neo4j/data/databases/graph.db:
Nodes:
 /var/tmp/blog/issue_fterm_context.csv

 /var/tmp/blog/keyword.csv
Relationships:
 /var/tmp/blog/similarity.csv

 /var/tmp/blog/keyword_issue.csv

Available resources:
 Total machine memory: 51.10 GB
 Free machine memory: 49.98 GB
 Max heap memory : 15.33 GB
 Processors: 8
 Configured max memory: 31.18 GB

Import starting 2018-01-23 04:05:24.396+0000
 Estimated number of nodes: 7.29 M
 Estimated number of node properties: 14.63 M
 Estimated number of relationships: 145.48 M
 Estimated number of relationship properties: 145.48 M
 Estimated disk space usage: 10.86 GB
 Estimated required memory usage: 569.89 MB

Interactive command list (end with ENTER):
 c: Print more detailed information about current stage
 i: Print more detailed information

(1/4) Node import 2018-01-23 04:05:24.418+0000
 Estimated number of nodes: 7.29 M
 Estimated disk space usage: 710.50 MB
 Estimated required memory usage: 569.89 MB
.......... .......... .......... .......... .......... 5%
.......... .......... .......... .......... .......... 10%
.......... .......... .......... .......... .......... 15%
.......... .......... .......... .......... .......... 20%
.......... .......... .......... .......... .......... 25%
.......... .......... .......... .......... .......... 30%
.......... .......... .......... .......... .......... 35%
.......... .......... .......... .......... .......... 40%
.......... .......... .......... .......... .......... 45%
.......... .......... .......... .......... .......... 50%
.......... .......... .......... .......... .......... 55%
.......... .......... .......... .......... .......... 60%
.......... .......... .......... .......... .......... 65%
.......... .......... .......... .......... .......... 70%
.......... .......... .......... .......... .......... 75%
.......... .......... .......... .......... .......... 80%
.......... .......... .......... .......... .......... 85%
.......... .......... .......... .......... .......... 90%
.......... .......... .......... .......... .......... 95%
.......... .......... .......... .......... .......... 100%

(2/4) Relationship import 2018-01-23 04:05:29.298+0000
 Estimated number of relationships: 145.48 M
 Estimated disk space usage: 10.16 GB
 Estimated required memory usage: 534.38 MB
.......... .......... .......... .......... .......... 5%
.......... .......... .......... .......... .......... 10%
.......... .......... .......... .......... .......... 15%
.......... .......... .......... .......... .......... 20%
.......... .......... .......... .......... .......... 25%
.......... .......... .......... .......... .......... 30%
.......... .......... .......... .......... .......... 35%
.......... .......... .......... .......... .......... 40%
.......... .......... .......... .......... .......... 45%
.......... .......... .......... .......... .......... 50%
.......... .......... .......... .......... .......... 55%
.......... .......... .......... .......... .......... 60%
.......... .......... .......... .......... .......... 65%
.......... .......... .......... .......... .......... 70%
.......... .......... .......... .......... .......... 75%
.......... .......... .......... .......... .......... 80%
.......... .......... .......... .......... .......... 85%
.......... .......... .......... .......... .......... 90%
.......... .......... .......... .......... .......... 95%
.......... .......... .......... .......... .........(3/4) Relationship linking 2018-01-23 04:08:40.284+0000
 Estimated required memory usage: 527.90 MB
.......... .......... .......... .......... .......... 5%
.......... .......... .......... .......... .......... 10%
.......... .......... .......... .......... .......... 15%
.......... .......... .......... .......... .......... 20%
.......... .......... .......... .......... .......... 25%
.......... .......... .......... .......... .......... 30%
.......... .......... .......... .......... .......... 35%
.......... .......... .......... .......... .......... 40%
.......... .......... .......... .......... .......... 45%
.......... .......... .......... .......... .......... 50%
.......... .......... .......... .......... .......... 55%
.......... .......... .......... .......... .......... 60%
.......... .......... .......... .......... .......... 65%
.......... .......... .......... .......... .......... 70%
.......... .......... .......... .......... .......... 75%
.......... .......... .......... .......... .......... 80%
.......... .......... .......... .......... .......... 85%
.......... .......... .......... .......... .......... 90%
.......... .......... .......... .......... .......... 95%
.......... .......... .......... .......... .......... 100%

(4/4) Post processing 2018-01-23 04:09:46.174+0000
 Estimated required memory usage: 478.13 MB
.......... .......... .......... .......... .......... 5%
.......... .......... .......... .......... .......... 10%
.......... .......... .......... .......... .......... 15%
.......... .......... .......... .......... .......... 20%
.......... .......... .......... .......... .......... 25%
.......... .......... .......... .......... .......... 30%
.......... .......... .......... .......... .......... 35%
.......... .......... .......... .......... .......... 40%
.......... .......... .......... .......... .......... 45%
.......... .......... .......... .......... .......... 50%
.......... .......... .......... .......... .......... 55%
.......... .......... .......... .......... .......... 60%
.......... .......... .......... .......... .......... 65%
.......... .......... .......... .......... .......... 70%
.......... .......... .......... .......... .......... 75%
.......... .......... .......... .......... .......... 80%
.......... .......... .......... .......... .......... 85%
.......... .......... .......... .......... .......... 90%
.......... .......... .......... .......... .......... 95%
.......... .......... .......... .......... .......... 100%


IMPORT DONE in 4m 35s 487ms.  
Imported:
 4744499 nodes
 145399772 relationships
 154911213 properties
Peak memory usage: 632.11 MB

4分半程でロードが完了しました。単純計算で約93万行/秒のスループットです。

また、以前のバージョンよりも表示がわかりやすく、進捗の視認性がよくなっています。

インデックスの作成

neo4j-admin importで初期ロードしたデータには、インデックスは作成されていないため、個別に作成します。

cypher-shellというインタラクティブなシェルを使います。*10

$ cypher-shell  
username: neo4j
password: ********
Connected to Neo4j 3.4.0-alpha06 at bolt://localhost:7687 as user neo4j.
Type :help for a list of available commands or :exit to exit the shell.
Note that Cypher queries must end with a semicolon.
neo4j> MATCH (k: Keyword {name: "プリプレグ"}) RETURN k;
+----------------------------------------+
| k                                      |
+----------------------------------------+
| (:Keyword {name: "プリプレグ", id: 115168}) |
+----------------------------------------+

1 row available after 113 ms, consumed after another 4509 ms
neo4j> MATCH (k: Keyword {name: "プリプレグ"}) RETURN k;
+----------------------------------------+
| k                                      |
+----------------------------------------+
| (:Keyword {name: "プリプレグ", id: 115168}) |
+----------------------------------------+

1 row available after 5 ms, consumed after another 3888 ms
neo4j> CREATE INDEX ON :Keyword(name);
0 rows available after 54 ms, consumed after another 0 ms
Added 1 indexes

CREATE INDEXを実行すると、インデックスは非同期で作成されます。

ステータスはログに出力されます。

2018-01-23 04:13:24.092+0000 INFO [o.n.k.i.a.i.IndexPopulationJob] Index population started: [:Keyword(name) [pr
ovider: {key=lucene+native, version=1.0}]]
2018-01-23 04:13:40.480+0000 INFO [o.n.k.i.a.i.IndexPopulationJob] Completed node store scan. Flushing all pendi
ng updates.
BatchingMultipleIndexPopulator{activeTasks=5, executor=java.util.concurrent.ThreadPoolExecutor@2b76dd88[Running,
pool size = 7, active threads = 5, queued tasks = 0, completed tasks = 468], batchedUpdates = [0 updates], queu
edUpdates = 0}
2018-01-23 04:13:40.481+0000 INFO [o.n.k.i.a.i.IndexPopulationJob] Shutting down executor.
BatchingMultipleIndexPopulator{activeTasks=5, executor=java.util.concurrent.ThreadPoolExecutor@2b76dd88[Running,
pool size = 7, active threads = 5, queued tasks = 0, completed tasks = 469], batchedUpdates = [0 updates], queu
edUpdates = 0}
2018-01-23 04:13:46.163+0000 INFO [o.n.k.i.a.i.IndexPopulationJob] Index population completed. Index is now onli
ne: [:Keyword(name) [provider: {key=lucene+native, version=1.0}]]

インデックスの作成が完了したところで、再度検索してみます。

neo4j> MATCH (k: Keyword {name: "プリプレグ"}) RETURN k;
+----------------------------------------+
| k                                      |
+----------------------------------------+
| (:Keyword {name: "プリプレグ", id: 115168}) |
+----------------------------------------+

1 row available after 62 ms, consumed after another 1 ms
neo4j> MATCH (k: Keyword {name: "プリプレグ"}) RETURN k;
+----------------------------------------+
| k                                      |
+----------------------------------------+
| (:Keyword {name: "プリプレグ", id: 115168}) |
+----------------------------------------+

1 row available after 2 ms, consumed after another 1 ms
neo4j>

試食編

プリプレグ

ところで、「プリプレグ」とは何でしょう。食べ物ではなさそうです。あまり耳馴染みがない方も多いかもしれません。私もその一人です。

初めて聞くキーワードを、周辺のキーワードと、キーワードが寄与している文脈から、おおよその見当をつけられるかもしれません。

早速、WebUI*11にアクセスし、クエリを実行してみます。

MATCH (k:Keyword {name: "プリプレグ"})-[s:SIMILAR_TO *1..3]-(l) RETURN DISTINCT s,l,k LIMIT 10

f:id:astamuse:20180123191853p:plain

朧げながら、プリプレグの人となりが見えてきます。わりと硬派な印象です。

今度は、周辺のキーワードに手を伸ばしつつ、関連する課題を探索してみます。

MATCH (k:Keyword {name: "プリプレグ"})-[s:SIMILAR_TO]-(l:Keyword)
WITH k, s, l ORDER BY s.score DESC LIMIT 100
MATCH (k)-[s]-(l)-[r:RESOLVE]->(f:FTerm)
WITH DISTINCT k, s, l, r, f ORDER BY r.n DESC LIMIT 20
RETURN k, s, l, r, f

グラフ表示

f:id:astamuse:20180123183837p:plain

テキスト表示

╒════════════════════════════╤════════════════════════╤═══════════════════════════════╤════════╤══════════════════════════════════════════════════════════════════════╕
│"k"                         │"s"                     │"l"                            │"r"     │"f"                                                                   │
╞════════════════════════════╪════════════════════════╪═══════════════════════════════╪════════╪══════════════════════════════════════════════════════════════════════╡
│{"name":"プリプレグ","id":115168}│{"score":0.641988456249}│{"name":"熱硬化性樹脂","id":13947024}│{"n":50}│{"context":"接着剤、接着方法/物理(化学)的性質又は目的、効果/機械的特性","code":"4J040LA06","id":1│
│                            │                        │                               │        │4260}                                                                 │
├────────────────────────────┼────────────────────────┼───────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"プリプレグ","id":115168}│{"score":0.641988456249}│{"name":"熱硬化性樹脂","id":13947024}│{"n":45}│{"context":"接着剤、接着方法/物理(化学)的性質又は目的、効果/電気磁気特性","code":"4J040LA09","id":│
│                            │                        │                               │        │14263}                                                                │
├────────────────────────────┼────────────────────────┼───────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"プリプレグ","id":115168}│{"score":0.641988456249}│{"name":"熱硬化性樹脂","id":13947024}│{"n":45}│{"context":"接着剤、接着方法/物理(化学)的性質又は目的、効果/温度特性又は熱特性","code":"4J040LA08","i│
│                            │                        │                               │        │d":14262}                                                             │
├────────────────────────────┼────────────────────────┼───────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"プリプレグ","id":115168}│{"score":0.641988456249}│{"name":"熱硬化性樹脂","id":13947024}│{"n":43}│{"context":"多層プリント配線板の製造/目的課題効果/機械的特性に関するもの","code":"5E346HH11","id":1│
│                            │                        │                               │        │7583}                                                                 │
├────────────────────────────┼────────────────────────┼───────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"プリプレグ","id":115168}│{"score":0.641988456249}│{"name":"熱硬化性樹脂","id":13947024}│{"n":30}│{"context":"接着剤、接着方法/物理(化学)的性質又は目的、効果/分子量又はその関連特性","code":"4J040LA01",│
│                            │                        │                               │        │"id":14255}                                                           │
├────────────────────────────┼────────────────────────┼───────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"プリプレグ","id":115168}│{"score":0.641988456249}│{"name":"熱硬化性樹脂","id":13947024}│{"n":29}│{"context":"電場又は磁場に対する装置又は部品の遮蔽/目的/電(磁)波シールド電磁シールド","code":"5E321GG05"│
│                            │                        │                               │        │,"id":17466}                                                          │
├────────────────────────────┼────────────────────────┼───────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"プリプレグ","id":115168}│{"score":0.641988456249}│{"name":"熱硬化性樹脂","id":13947024}│{"n":29}│{"context":"印刷回路の非金属質の保護被覆/目的効果/その他","code":"5E314GG26","id":17427}   │
├────────────────────────────┼────────────────────────┼───────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"プリプレグ","id":115168}│{"score":0.584715306759}│{"name":"ワニス","id":145746}     │{"n":29}│{"context":"電動機、発電機の製造/目的/製造,組立","code":"5H615AA01","id":20027}       │
├────────────────────────────┼────────────────────────┼───────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"プリプレグ","id":115168}│{"score":0.641988456249}│{"name":"熱硬化性樹脂","id":13947024}│{"n":28}│{"context":"印刷回路に対する電気部品等の電気的接続/目的効果/はんだ付け性の改良","code":"5E319GG03","id│
│                            │                        │                               │        │":17455}                                                              │
├────────────────────────────┼────────────────────────┼───────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"プリプレグ","id":115168}│{"score":0.641988456249}│{"name":"熱硬化性樹脂","id":13947024}│{"n":27}│{"context":"多層プリント配線板の製造/目的課題効果/製造生産に関するもの/歩留生産性の向上","code":"5E346HH33│
│                            │                        │                               │        │","id":17596}                                                         │
├────────────────────────────┼────────────────────────┼───────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"プリプレグ","id":115168}│{"score":0.641988456249}│{"name":"熱硬化性樹脂","id":13947024}│{"n":26}│{"context":"接着剤、接着方法/物理(化学)的性質又は目的、効果/結晶関連特性","code":"4J040LA02","id":│
│                            │                        │                               │        │14256}                                                                │
├────────────────────────────┼────────────────────────┼───────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"プリプレグ","id":115168}│{"score":0.791278362274}│{"name":"積層板","id":348376}     │{"n":25}│{"context":"プリント基板への印刷部品(厚膜薄膜部品)/目的,効果/その他","code":"4E351GG20","id":13│
│                            │                        │                               │        │245}                                                                  │
├────────────────────────────┼────────────────────────┼───────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"プリプレグ","id":115168}│{"score":0.641988456249}│{"name":"熱硬化性樹脂","id":13947024}│{"n":25}│{"context":"電動機、発電機の製造/目的/製造,組立","code":"5H615AA01","id":20027}       │
├────────────────────────────┼────────────────────────┼───────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"プリプレグ","id":115168}│{"score":0.791278362274}│{"name":"積層板","id":348376}     │{"n":23}│{"context":"プリント配線の製造(2)/目的効果/密着性の改良/導体とプリント板の基板","code":"5E343GG02","│
│                            │                        │                               │        │id":17545}                                                            │
├────────────────────────────┼────────────────────────┼───────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"プリプレグ","id":115168}│{"score":0.641988456249}│{"name":"熱硬化性樹脂","id":13947024}│{"n":23}│{"context":"多層プリント配線板の製造/目的課題効果/電気的特性に関するもの/電気的接続性","code":"5E346HH07"│
│                            │                        │                               │        │,"id":17581}                                                          │
├────────────────────────────┼────────────────────────┼───────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"プリプレグ","id":115168}│{"score":0.641988456249}│{"name":"熱硬化性樹脂","id":13947024}│{"n":20}│{"context":"繊維製品への有機化合物の付着処理/目的,効果/接着","code":"4L033AC11","id":14648} │
├────────────────────────────┼────────────────────────┼───────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"プリプレグ","id":115168}│{"score":0.641988456249}│{"name":"熱硬化性樹脂","id":13947024}│{"n":19}│{"context":"多層プリント配線板の製造/目的課題効果/その他","code":"5E346HH40","id":17597}   │
├────────────────────────────┼────────────────────────┼───────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"プリプレグ","id":115168}│{"score":0.791278362274}│{"name":"積層板","id":348376}     │{"n":17}│{"context":"多層プリント配線板の製造/目的課題効果/機械的特性に関するもの","code":"5E346HH11","id":1│
│                            │                        │                               │        │7583}                                                                 │
├────────────────────────────┼────────────────────────┼───────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"プリプレグ","id":115168}│{"score":0.641988456249}│{"name":"熱硬化性樹脂","id":13947024}│{"n":17}│{"context":"印刷回路の非金属質の保護被覆/目的効果/生産性向上","code":"5E314GG24","id":17426} │
├────────────────────────────┼────────────────────────┼───────────────────────────────┼────────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"プリプレグ","id":115168}│{"score":0.641988456249}│{"name":"熱硬化性樹脂","id":13947024}│{"n":15}│{"context":"半導体又は固体装置の封緘,被覆構造と材料/用途又は特殊目的/光半導体用","code":"4M109GA01","i│
│                            │                        │                               │        │d":14812}                                                             │
└────────────────────────────┴────────────────────────┴───────────────────────────────┴────────┴──────────────────────────────────────────────────────────────────────┘

クエリの結果を解釈して評価するためには、別途何らかの判断基準が必要そうです。

門外漢の私には少々荷が重いので、いささか心得のある、ラーメンというキーワードに換えてクエリを実行してみることにします。

ラーメン

MATCH (k:Keyword {name: "ラーメン"})-[s:SIMILAR_TO]-(l:Keyword)
WITH k, s, l ORDER BY s.score DESC LIMIT 100
MATCH (k)-[s]-(l)-[r:RESOLVE]->(f:FTerm)
WITH DISTINCT k, s, l, r, f ORDER BY r.n DESC LIMIT 20
RETURN k, s, l, r, f

グラフ表示 f:id:astamuse:20180123183933p:plain

テキスト表示

╒═══════════════════════════╤════════════════════════╤═══════════════════════════════════╤═══════╤══════════════════════════════════════════════════════════════════════╕
│"k"                        │"s"                     │"l"                                │"r"    │"f"                                                                   │
╞═══════════════════════════╪════════════════════════╪═══════════════════════════════════╪═══════╪══════════════════════════════════════════════════════════════════════╡
│{"name":"ラーメン","id":137294}│{"score":0.617753505707}│{"name":"包装食品","id":10958102}      │{"n":3}│{"context":"包装体/目的,機能(1)特定の環境の維持/その他/包装体/その他/包装体/その他/包装体/その他","code":"│
│                           │                        │                                   │       │3E067GD10","id":5974}                                                 │
├───────────────────────────┼────────────────────────┼───────────────────────────────────┼───────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"ラーメン","id":137294}│{"score":0.336226522923}│{"name":"喫食用容器入り冷凍麺","id":13427756}│{"n":3}│{"context":"穀類誘導製品3(麺類)/目的/即席化","code":"4B046LC11","id":12085}        │
├───────────────────────────┼────────────────────────┼───────────────────────────────────┼───────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"ラーメン","id":137294}│{"score":0.617753505707}│{"name":"包装食品","id":10958102}      │{"n":2}│{"context":"食品の保存(凍結・冷却・乾燥を除く)/使用目的/抗微生物","code":"4B021MC01","id":1180│
│                           │                        │                                   │       │3}                                                                    │
├───────────────────────────┼────────────────────────┼───────────────────────────────────┼───────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"ラーメン","id":137294}│{"score":0.492829829454}│{"name":"ゲル状調味料","id":10169798}    │{"n":2}│{"context":"ゼリ−、ジャム、シロップ/目的/テクスチャーの改善","code":"4B041LC03","id":12012} │
├───────────────────────────┼────────────────────────┼───────────────────────────────────┼───────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"ラーメン","id":137294}│{"score":0.492829829454}│{"name":"ゲル状調味料","id":10169798}    │{"n":2}│{"context":"種実、スープ、その他の食品/目的/味,香りの改善","code":"4B036LC01","id":11983}  │
├───────────────────────────┼────────────────────────┼───────────────────────────────────┼───────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"ラーメン","id":137294}│{"score":0.617753505707}│{"name":"包装食品","id":10958102}      │{"n":2}│{"context":"袋/目的機能/内容物の保存性向上","code":"3E064EA18","id":5883}           │
├───────────────────────────┼────────────────────────┼───────────────────────────────────┼───────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"ラーメン","id":137294}│{"score":0.617753505707}│{"name":"包装食品","id":10958102}      │{"n":1}│{"context":"肉類、卵、魚製品/目的/呈味の改善","code":"4B042AC03","id":12021}         │
├───────────────────────────┼────────────────────────┼───────────────────────────────────┼───────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"ラーメン","id":137294}│{"score":0.617753505707}│{"name":"包装食品","id":10958102}      │{"n":1}│{"context":"高周波加熱[構造]/目的/電磁波の漏洩防止","code":"3K090LA04","id":10994}     │
├───────────────────────────┼────────────────────────┼───────────────────────────────────┼───────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"ラーメン","id":137294}│{"score":0.617753505707}│{"name":"包装食品","id":10958102}      │{"n":1}│{"context":"肉類、卵、魚製品/目的/保存性の改善","code":"4B042AC06","id":12024}        │
├───────────────────────────┼────────────────────────┼───────────────────────────────────┼───────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"ラーメン","id":137294}│{"score":0.617753505707}│{"name":"包装食品","id":10958102}      │{"n":1}│{"context":"基本的包装技術VII(真空包装)/目的/その他","code":"3E053JA10","id":5838}    │
├───────────────────────────┼────────────────────────┼───────────────────────────────────┼───────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"ラーメン","id":137294}│{"score":0.617753505707}│{"name":"包装食品","id":10958102}      │{"n":1}│{"context":"高周波加熱[構造]/目的/加熱効率向上又は省電力","code":"3K090AA02","id":10977}  │
├───────────────────────────┼────────────────────────┼───────────────────────────────────┼───────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"ラーメン","id":137294}│{"score":0.617753505707}│{"name":"包装食品","id":10958102}      │{"n":1}│{"context":"基本的包装技術VII(真空包装)/目的/高速化","code":"3E053JA01","id":5829}    │
├───────────────────────────┼────────────────────────┼───────────────────────────────────┼───────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"ラーメン","id":137294}│{"score":0.617753505707}│{"name":"包装食品","id":10958102}      │{"n":1}│{"context":"袋/目的機能/その他","code":"3E064EA30","id":5888}                 │
├───────────────────────────┼────────────────────────┼───────────────────────────────────┼───────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"ラーメン","id":137294}│{"score":0.617753505707}│{"name":"包装食品","id":10958102}      │{"n":1}│{"context":"基本的包装技術VII(真空包装)/目的/ガス置換率の向上","code":"3E053JA03","id":5831│
│                           │                        │                                   │       │}                                                                     │
├───────────────────────────┼────────────────────────┼───────────────────────────────────┼───────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"ラーメン","id":137294}│{"score":0.617753505707}│{"name":"包装食品","id":10958102}      │{"n":1}│{"context":"袋/目的機能/排出容易性","code":"3E064EA12","id":5877}               │
├───────────────────────────┼────────────────────────┼───────────────────────────────────┼───────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"ラーメン","id":137294}│{"score":0.617753505707}│{"name":"包装食品","id":10958102}      │{"n":1}│{"context":"袋/目的機能/経済性","code":"3E064EA01","id":5867}                 │
├───────────────────────────┼────────────────────────┼───────────────────────────────────┼───────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"ラーメン","id":137294}│{"score":0.617753505707}│{"name":"包装食品","id":10958102}      │{"n":1}│{"context":"加熱調理器/目的、効果/衛生性、外観","code":"4B055BA51","id":12144}        │
├───────────────────────────┼────────────────────────┼───────────────────────────────────┼───────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"ラーメン","id":137294}│{"score":0.617753505707}│{"name":"包装食品","id":10958102}      │{"n":1}│{"context":"誘導加熱調理器/目的、効果〔回路装置〕/回路素子/インバータ/表示,監視装置/誘導加熱調理器/非物理量※/基準値/構│
│                           │                        │                                   │       │造対象の処理態様※/使い勝手,判り易さ","code":"3K051AD39","id":10504}                   │
├───────────────────────────┼────────────────────────┼───────────────────────────────────┼───────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"ラーメン","id":137294}│{"score":0.617753505707}│{"name":"包装食品","id":10958102}      │{"n":1}│{"context":"包装体/目的,機能(1)特定の環境の維持/その他/包装体/その他/包装体/その他/包装体/風味,香気保持","cod│
│                           │                        │                                   │       │e":"3E067GD02","id":5966}                                             │
├───────────────────────────┼────────────────────────┼───────────────────────────────────┼───────┼──────────────────────────────────────────────────────────────────────┤
│{"name":"ラーメン","id":137294}│{"score":0.617753505707}│{"name":"包装食品","id":10958102}      │{"n":1}│{"context":"食品の調整及び処理一般/目的/その他","code":"4B035LC16","id":11981}        │
└───────────────────────────┴────────────────────────┴───────────────────────────────────┴───────┴──────────────────────────────────────────────────────────────────────┘

インスタントラーメンの製造に関する課題であることがわかります。

以上、簡単ですが、グラフ構造に対してクエリを投げ、周辺の語彙と文脈を照らすことができました。

まとめ

最新のNeo4jをセットアップし、インポートツールを使用してデータの初期ロードを行いました。

特許文書から抽出したキーワード同士の関連と、キーワードが現れる文脈としての分類データのグラフを、あるキーワードを起点として探索しました。

グラフ構造は複雑で、データの量が多くなると全てを把握し見通すことは困難となり、合理的なコストで探索できることがなにより重要になってきます。その点でNeo4jは扱いやすく、一定の規模のデータに対して探索的なクエリ実行ができるので、強力な武器になりそうです。

スマートスピーカー向けに、ブレインストーミングの相手をしてくれるようなアプリケーションを開発してみても面白いかもしれません。

*1:https://neo4j.com

Neo4jをより詳しく知りたい方向けの参考書籍

グラフ型データベース入門 - Neo4jを使う Neo4jユーザーグループ

グラフデータベース ―Neo4jによるグラフデータモデルとグラフデータベース入門 Ian Robinson

O’Reilly’s Graph Databases(無料ダウンロード・英語)

*2:プリエンプティブ仮想マシンを指定することで少し余裕のあるインスタンスをリーズナブルなコストで使用できます

*3:Neo4jの2018年1月23日時点で最新のα版。安定版の最新は3.3.2です

*4:国内の特許公報文書には、案件毎に複数の分類体系のコードが付与され、特許の先行技術調査を行う際に利用されます。Fタームという分類体系もその一つです。

*5:https://neo4j.com/docs/operations-manual/current/installation/linux/

*6:安定版をインストールする場合はリポジトリのtestingをstableにします。

*7:https://neo4j.com/docs/operations-manual/current/tools/import/

*8:http://neo4j.com/docs/developer-manual/current/cypher/clauses/load-csv/

*9:https://neo4j.com/docs/operations-manual/current/tools/import/file-header-format/

*10:以前のバージョンではneo4j-shellというコマンドでした。こちらはまだ存在はしますが、cypher-shellが後継となります

*11:Web UI(デフォルトで7474ポート)にアクセスしてクエリを実行すると、ビジュアライゼーションされたグラフがきれいに表示されます。結果はテーブル形式、テキスト形式、JSONコード形式に切替えられます。また、グラフ表示でノードをダブルクリックすると周辺のノードが展開されます。クエリを投げながら探索の方向性を探るといった使い方ができます。

マーケティングとエンジニアとの関わりについて

はじめまして。 アスタミューゼで事業開発・マーケティングを担当しているボーダー☆キングと申します。開発部長の並河さんより、当ブログの執筆という大役を頂きました。緊張しています(笑)。

参考)この人形が執筆者に廻ってきます…。(通称:白い悪魔) f:id:astamuse:20180117185726j:plain

このブログはエンジニアとデザイナーのブログなので技術的なお話が多いと思うのですが、今回は趣向を変えて、企画からリリースまでのマーケティングとエンジニアの関わりや、ベンチャーならではの関わり方だなー、と思ったことについて記してみたいと思います。

1.簡単な略歴

その前に、私の経歴について触れておいた方が判りやすいですかね。
私はアスタミューゼが3社目の会社になります。

1社目:リクルート
リクルートでは、WEB商品を中心に新規商品の策定などを行っていました。割とヒットしたのは「街の不動産屋さんでも扱える、簡単CMSツール」というコンセプトで企画・開発した「ホームページサービス」でしょうか。担当した1年で年間売上1.5億の商品になりました。

例)ホームページサービス
https://business.suumo.jp/chintai/lineup/hpservice.html f:id:astamuse:20180117185921j:plain

【企画にあたり、主にやったこと】
・競合調査と商品優位性の規定
・Information Architectureとワイヤーフレーム作成
・営業とエンジニアの間に立っての仕様調整
・既存物件DBとの繋ぎこみ設計

2社目:カカクコム
カカクコムでは、商品比較サイト「価格.com」のコミュニティ担当としてサイト改善やリニューアルを担当していました。大きい仕事では「ランキングページの8年ぶりのリニューアル」ですかね。リリース後、ランキングページのUUを1.2倍まで持って行くことに成功しました。

例)ランキングページ
http://kakaku.com/kaden/lcd-tv/ranking_2041/ f:id:astamuse:20180117190035j:plain

【企画にあたり、主にやったこと】
・ユーザーヒアリングに基づいたコンセプト設計
・ワイヤーフレーム作成
・各画面の詳細仕様設計と策定
・エンジニアとの仕様調整

アスタミューゼでは「転職ナビ」というサービスを主に担当しています。
過去に企画で行ってきた作業をベースにしながら、顧客対応や各種有料集客施策といったフロントの部分から売り上げを最大化するための運用面まで幅広く見渡しながら日々過ごしています。

2.転職ナビとは

「転職ナビ」とは、様々な特許情報や技術情報を持っているアスタミューゼが、そこに携わる人材の支援もトータルで行いたい、という思想で生まれた転職サイトです。2010年のサービスを開始より、今では年間あたり3万人以上の登録を頂くサイトに成長しています。

indeedやDODAといった大手の総合型のサイトと異なる強みは何か、というと特定の技術分野に特化していることがひとつとして挙げられます。

例えばですが
 ・有機EL転職ナビ
 ・核融合転職ナビ
 ・Scala転職ナビ

f:id:astamuse:20180117192848j:plain
一例)Scala転職ナビ
といった、かなりニッチな(笑)サイトが400近くあります。
「転職ナビ」はScalaを開発言語で活用していますが、Scala転職ナビ経由で登録された方は確実にその言語に対する技術を持っていらっしゃるため、総合型サイトと比較しても格段のマッチング精度で募集することが出来る、いうことがプロダクトとしての強みだと思います。

3.転職ナビのビジネスモデル上の課題

次は、転職ナビのビジネスモデルとその課題についてご説明します。
転職ナビはサイト毎にパーソルキャリアやジェイエイシーリクルートメントなどの大手人材紹介会社と提携をしており、基本的にサイト毎の提携先に登録者を紹介させて頂いています。
ただし、紹介した方が既に他のルートで人材紹介会社に登録済みの場合、他の提携先に送客させて頂く場合があり、それは登録者全体の3割程度が該当します。
年間にすると1万人程度の人数となり、その方たちには他の提携先の求人状況やサービス範囲などの条件と照らし合わせて、最適な提携先を紹介する必要があります。
非常に重要な業務なのですが、多岐にわたる提携先の特徴を視野に入れながら、提携先も求職者も喜んでいただける紹介選定は職人芸に依るところが大きく、ナレッジの共有化と継続性、労働工数という観点で大きな課題となっていました。

イメージ)再送客先選定は一子相伝の職人芸 f:id:astamuse:20180117190413j:plain

4.機械学習を活用した判定結果の導入

「こんなことで困っているんですよねー」
ある日飲み会の席で開発部長の並河さんに軽く相談したところ、「毎年3万人以上のデータが整っているのであれば、機械学習を活用すれば、ある程度判定を自動化することもできるんじゃないですかねー」と提案を頂きました。

イメージ)仕事のことも気軽に相談できる懇親会 f:id:astamuse:20180117190454j:plain

後日、提携先ごとに数千から数万存在する過去の判定結果(この人はカウンセリングの対象かどうか)を並河さんにお渡しし、数日経ったシミュレーションの結果がこちらになります。

参考)ある提携先のシミュレーション結果 f:id:astamuse:20180117190528j:plain

登録者の個人情報は「年齢」や「職種」など様々な要素で構成されていますが、ある提携先がカウンセリングしたくなる人は、どの項目と相関性が強いかを表しています。
(クリーム色に網掛けしてある部分が相関性の強い構成要素の組み合わせになります)

こうした試行錯誤をしていく中で、企画とエンジニアの「こうした要素の関連が強いのではないか」というやり取りを頻繁に行い、結果として「年齢」「都道府県」「職種小分類」を活用したものを採択しました。

実際の運用としては、既存登録者の個人情報を機械学習の判定プログラムにかけると、それぞれの人にスコアと判定がついて戻ってきます。

例えば
Aさん…スコア:0.6 判定:○
Bさん…スコア:0.3 判定:×
というような形です。

このプログラムのスコア0.6で「○」が付いたAさんは99.6%以上の確率でカウンセリングへと繋がることが分かっているため、この提携先に紹介すれば良いですし、同様にBさんは98.6%の確率でカウンセリングに繋がらないので、他に適した提携先を紹介すれば良いことになります。

参考)スコアと○×の正解率一覧表
f:id:astamuse:20180117190558j:plain

こうして確実に、スピーディーに新しい提携先に登録者を紹介することが可能となりました。
以前はこの運用に毎日150分という時間をかけていましたが、30分で終了できるようになり、提携先の信頼を担保しながら、大幅な運用工数の削減をすることができました。

また、現在はこの考え方を活用して、過去に登録した方に対して再登録を促すメール送信する対象者を選定するなど、様々な施策への応用を行っています。

5.おわりに

ある案件を参考に、マーケティングと開発の関わりを簡単ですが記してみました。
過去に経験した2社はいずれも大手企業に属する会社だと思いますが、最後にそれらとアスタミューゼの違いみたいなものを書いておきたいと思います。

a.コミュニケーションコストが低くて済む
私がいたころのリクルートは、開発といえば丸投げで、開発会社が膨大なコミュニケーションコストをかけて内容をキャッチアップする、という構図でした。営業サイドもITに明るい人が少なかったため、ものすごくコミュニケーションに時間を割いていました。アスタミューゼは基本自社開発ですので、プロダクトのことを熟知した方が多く、阿吽の呼吸で物事を進めることができるのは心地良いと思います。

b.まずやってみる、という気軽さ
カカクコムは一つのサイトが巨大すぎて影響範囲を確認するだけでも大変で、企画から承認まで6カ月くらいかかることがザラでした。今回採り上げたお話しもそうですが、エンジニアの課題解決力と新しい技術への好奇心から「まずやってみる」、というフットワークの軽さがあるのはベンチャー企業ならではではないでしょうか。

8年以上続く成熟した自社プロダクトを持ちながら、ベンチャーならではの足回りの速さを持ち合わせている開発現場だからこそ、マーケティングやエンジニアやデザイナーも日々様々な挑戦が出来ているのではないかなー、と思います。

最後になりましたが、 アスタミューゼでは現在、エンジニア・デザイナーに限らず幅広い職種を募集中です。 興味のある方はぜひ採用サイトからご応募ください。

採用情報|アスタミューゼ株式会社

ではまた。

Copyright © astamuse company, ltd. all rights reserved.