このモジュールでは、レコメンドエンジンのデータモデルを設計します。

グラフデータベースは、リレーショナルデータベースなど、過去に使用したデータベースとは異なる場合があります。グラフデータベースで知っておくべき重要な用語がいくつかあります。

  • グラフ: これは、データベース全体を指します。他のデータベースの「テーブル」のようなものである場合もあります。
  • 頂点: 頂点 (ノードとも呼ばれます) は、グラフ内のアイテムを表します。通常、名詞や概念 (人、場所、用語など) を表すために使用されます。頂点 (vertex) の複数形は vertices であり、以下でその用語が使用されています。
  • エッジ: エッジは、2 つの頂点を接続するものです。多くの場合、エッジはエンティティ間の関係を表します。たとえば、一緒に働く 2 人の人が WorksWith エッジで接続されている場合があります。
  • ラベル: ラベルは、追加される頂点またはエッジのタイプを示すために使用することができます。たとえば、User というラベルが付けられた頂点を使用してアプリケーションのユーザーを示したり、Interest というラベルが付けられた頂点を使用して人々がフォローできる特定の興味対象を示したりすることができます。
  • プロパティ: 頂点とエッジに key-value ペアを追加できます。これらはプロパティと呼ばれます。たとえば、ユーザーの頂点には username プロパティがあります。

グラフを照会するに際して、多くの場合、特定の頂点から開始して、エッジをトラバースし、元の頂点との関係を見つけます。ユースケースでは、特定のユーザーがフォローしているすべての人を見つけるには、特定のユーザーから開始し、そのユーザーから Follow というラベルの付いたすべてのエッジをトラバースします。

次の手順では、いくつかの基本的なグラフクエリを完了します。まず、いくつかのサンプルデータをクラスターにロードします。次に、クエリを実行してユーザーの現在の興味対象を見つける方法を確認します。最後に、特定のユーザーに対するレコメンデーションを生成するクエリを確認します。

モジュールの所要時間: 30 分


  • ステップ 1:サンプルデータをロードする

    まず、いくつかのサンプルデータを Neptune のデータベースにロードします。

    データモデルには、UserInterest の 2 つの種類の頂点があります。Follow (ある User が別の User をフォローしていることを表します) と InterestedIn (定義された興味対象の 1 つに興味を示している User を表します) という 2 つの種類のエッジもあります。

    scripts/ ディレクトリには、50 のサンプルの Users と 6 つのサンプルの Interests を含む vertices.json ファイルがあります。また、サンプルの頂点を読み取って、それらを Neptune にロードする insertVertices.js というスクリプトもあります。

    insertVertices.js スクリプトの内容は次のとおりです。

    const fs = require('fs');
    const path = require('path');
    
    const gremlin = require('gremlin');
    const DriverRemoteConnection = gremlin.driver.DriverRemoteConnection;
    const Graph = gremlin.structure.Graph;
    
    const connection = new DriverRemoteConnection(`wss://${process.env.NEPTUNE_ENDPOINT}:8182/gremlin`,{});
    
    const graph = new Graph();
    const g = graph.traversal().withRemote(connection);
    
    const createUser = async (username) => {
      return g.addV('User').property('username', username).next()
    }
    
    const createInterest = async (interest) => {
      return g.addV('Interest').property('interest', interest).next()
    }
    
    const raw = fs.readFileSync(path.resolve( __dirname, 'vertices.json'));
    const vertices = JSON.parse(raw)
    
    const vertexPromises = vertices.map((vertex) => {
      if (vertex.label === 'User') {
        return createUser(vertex.username)
      } else if (vertex.label === 'Interest') {
        return createInterest(vertex.name)
      }
    })
    
    Promise.all(vertexPromises).then(() => {
      console.log('Loaded vertices successfully!')
      connection.close()
    })

    スクリプトは、テストデータベーススクリプトのように、必要なライブラリをインポートし、Neptune 接続を初期化します。次に、createUser および createInterest という 2 つの関数を定義します。それぞれ User および Interest の頂点を作成します。スクリプトはサンプルユーザーを読み取り、サンプルデータを反復処理して、User と Interests を作成します。

    次のコマンドでスクリプトを実行できます。

    node scripts/insertVertices.js

    ターミナルに次の出力が表示されます。

    Loaded vertices successfully!

    スクリプトフォルダには、insertEdges.js というスクリプトもあります。edges.json ファイルからいくつかのサンプルエッジを読み込み、それらを Neptune データベースに挿入します。

    次のコマンドでスクリプトを実行できます。

    node scripts/insertEdges.js

    ターミナルに次の出力が表示されます。

    Edges loaded successfully!

    これで、サンプルの頂点とエッジが読み込まれました。最後のモジュールから testDatabase.js スクリプトを実行しようとすると、グラフにいくつかの頂点があることがわかります。

    { value: 56, done: false }

    成功しました! 56 の頂点、つまり 50 の User と 6 の Interests が見つかりました。

    次のステップでは、特定の User のすべての Interests をクエリする方法を学びます。

  • ステップ 2:ユーザーの興味対象を取得する

    Neptune にデータが読み込まれたので、クエリを実行していくつかの質問に答えることができます。

    Gremlin のクエリ言語は最初は理解しにくい場合があるので、サンプルを見てみましょう。アプリケーションが特定の User のすべての Interests を取得して返そうとしている場面を想像してください。

    scripts/ ディレクトリには、次の内容の findUserInterests.js ファイルがあります。

    const gremlin = require('gremlin');
    const DriverRemoteConnection = gremlin.driver.DriverRemoteConnection;
    const Graph = gremlin.structure.Graph;
    
    const connection = new DriverRemoteConnection(`wss://${process.env.NEPTUNE_ENDPOINT}:8182/gremlin`,{});
    
    const graph = new Graph();
    const g = graph.traversal().withRemote(connection);
    
    const findUserInterests = async (username) => {
      return g.V()
        .has('User', 'username', username)
        .out('InterestedIn')
        .values('interest')
        .toList()
    }
    
    findUserInterests('amy81').then((resp) => {
      console.log(resp)
      connection.close()
    })

    ファイルの先頭にも、グラフデータベースに接続するためのインポートおよび初期化のコードが含まれています。重要なのは、findUserInterests 関数の部分です。これは、アプリケーションで使用する内部関数に似ています。これは、username 引数を受け取り、そのユーザーの Interests を返します。

    関数の実際のクエリを見ていきましょう。5 行あります。それぞれがどのように機能しているかを見てみましょう。

    まず、g.V() で始まります。g 変数はグラフインスタンスです。その後の V() 演算子を使用することは、(エッジではなく) 頂点で操作していることを示唆します。

    次の行は .has('User', 'username', username) です。この部分は、クエリをグラフ内のすべての頂点ではなく、特定の頂点に絞り込みます。User のラベルを持つ頂点 (has() 演算子の最初の引数) を必要としていることを指定します。次に、username のプロパティを特定のユーザー名の値にしたいと仮定します (has() 演算子の 2 番目と 3 番目の引数)。

    User の頂点を選択したら、User の興味対象を見つけることにしましょう。これは out() 演算子で行います。out() 演算子は、特定の頂点から別の頂点までのエッジをトラバースするように指示します。このクエリでは、InterestedIn のラベルが付いているエッジに絞り込みました。

    この時点で、特定の User の InterestIn の関係におけるオブジェクトである頂点の配列ができています。これらの頂点のわかりやすい名前を返すため、values() 演算子を使用します。interest プロパティを返すように指示します。

    最後に、toList() 演算子を呼び出してトラバーサル操作を実行し、結果を配列として収集します。

    スクリプトの下部で、サンプルユーザーの 1 つを使用して findUserInterests 関数を呼び出します。次のコマンドでスクリプトを実行できます。

    node scripts/findUserInterests.js

    ターミナルに次の出力が表示されます。

    [ 'Nature', 'Sports', 'Woodworking', 'Cooking' ]

    成功しました! これによると、ユーザー amy81 は、スポーツ、木工細工、料理、自然という 4 つの興味対象に関心があることがわかります。

    次のステップでは、アプリケーションで友人のレコメンデーションを生成する方法を学びます。

  • ステップ 3:ユーザー向けのレコメンデーションを生成する

    基本的なグラフトラバーサルがわかったところで、もう少し難しいことに挑戦してみましょう。

    アプリケーションで、ユーザーがフォローすべき他のユーザーに関するユーザー固有のレコメンデーションを生成したいと考えています。これらのレコメンデーションを生成する一般的な方法は、ユーザーがフォローしているのと同じような人々をフォローしている他のユーザーを探すことです。これらの類似したユーザーが共通してフォローしている他のユーザーがいる場合、ユーザーがそれらのユーザーもフォローしたいと考える可能性があることを示唆しています。

    たとえば、次の図を見てください。

    friend-rec-diagram

    この図は、楕円で表される Users と、楕円から楕円への矢印で示される Follow の関係を示しています。この例では、左端の User である MyUser が上部の User である PopularPolly をフォローしています。また、SimilarSamMirrorMax という 2 人の別のユーザーが PopularPolly をフォローしていることがわかります。これら 2 人の User はどちらも、InterestingIngrid という名前の別の User をフォローしています。SimilarSamMirrorMax がフォローしている何人かを MyUser もフォローしているため、MyUserInterestingIngrid をフォローすることに興味を持つ可能性は高いと言えます。

    scripts/ フォルダには、findFriendsOfFriends.js というファイルがあります。そのファイルの内容は次のとおりです。

    const gremlin = require('gremlin');
    const DriverRemoteConnection = gremlin.driver.DriverRemoteConnection;
    const Graph = gremlin.structure.Graph;
    const neq = gremlin.process.P.neq
    const without = gremlin.process.P.without
    const order = gremlin.process.order
    const local = gremlin.process.scope.local
    const values = gremlin.process.column.values
    const desc = gremlin.process.order.desc
    
    const connection = new DriverRemoteConnection(`wss://${process.env.NEPTUNE_ENDPOINT}:8182/gremlin`,{});
    
    const graph = new Graph();
    const g = graph.traversal().withRemote(connection);
    
    const findFriendsOfFriends = async (username) => {
      return g.V()
        .has('User', 'username', username).as('user')
        .out('Follows').aggregate('friends')
        .in_('Follows')
        .out('Follows').where(without('friends'))
        .where(neq('user'))
        .values('username')
        .groupCount()
        .order(local)
        .by(values, desc)
        .limit(local, 10)
        .next()
    }
    
    findFriendsOfFriends('davidmiller').then((resp) => {
      console.log(resp.value)
      connection.close()
    })

    他と同様に、始めるためには、大量のインポートと初期化の作業をこなす必要があります。興味深いのは、ファイルで定義されている findFriendsOfFriends 関数です。これは、役立つレコメンデーションを生成するために「友人の友人」を探すアプリケーションの関数に似ています。

    この複雑なグラフクエリについて、順を追っておさらいしてみましょう。

    まず、g.V() .has('User', 'username', username).as('user') 部分を使用して、グラフ内で関連する User を見つけます。この部分は、適切な User の頂点を見つけるために特定のユーザー名を使用します。as 関数を使用してその頂点を「ユーザー」として保存し、後ほどクエリでその頂点を参照できるようにします。

    次に、このユーザーが現在フォローしている全員を検索します。これを行うには、 .out('Follows').aggregate('friends') を使用します。これにより、User の頂点からの Follow のエッジをトラバースして、フォローされているユーザーを見つけることができます。次に、これらを friends と呼ばれる変数に集約します。これは、後ほどクエリで参照できます。

    次に、これらの同じユーザーをフォローしている他のユーザーを検索します。.in_('Follows') を使用すると、これらのユーザーに Follows のエッジポイントを持つすべての頂点を検索できます。

    これで、要求されたユーザーに似たユーザーを検索することができました。次のステップでは、これらのユーザーがフォローしている他のユーザーを検索します。元のユーザーがこれらのユーザーに興味を抱く可能性が高いためです。これらを検索するには、.out('Follows').where(without('friends')) を使用します。これは、ユーザーからの Follows のラベルが付いたエッジを追います。where 句に留意してください。これは、元のユーザーの保存された friends 変数を使用して、元のユーザーがすでにフォローしているユーザーを除外します。既存の友達をレコメンデーションに含めたくないですよね!

    次に、ユーザーが自身をフォローすることをレコメンドしないように、.where(neq('user')) 句を使用して元のユーザーを除外します。次に、.values('username') 句を使用して、検出された各ノードのユーザー名を取得します。

    この時点で、グラフには、類似したユーザーから別のユーザーへの「Follows」のエッジごとに 1 つのエントリが含まれています。これは、結果に重複があることを意味します。すなわち、2 人の類似したユーザーが同じユーザーをフォローした場合、フォローされたユーザーは 2 回表示されます。あるユーザーが類似したユーザーによってフォローされた回数に基づき、そのフォローされたユーザーをグループ化できるので、これは非常に有用です。類似性がより高いユーザーによってフォローされているユーザーは、元のユーザーに関連している可能性がより高いと言えます。

    ターミナルで次のコマンドを実行して、スクリプトを実行できます。

    node scripts/findFriendsOfFriends.js

    ターミナルに次の出力が表示されます。

    Map {
      'paullaurie' => 23,
      'thardy' => 20,
      'ocarrillo' => 18,
      'toddjones' => 18,
      'michaelunderwood' => 17,
      'ihensley' => 17,
      'paulacruz' => 17,
      'annette32' => 17,
      'morenojason' => 16,
      'bergjames' => 16 }

    素晴らしい! これらは、特定のユーザーに対してレコメンドされるユーザーの上位 10 名です。これらのユーザーをフォローしていた類似のユーザーの数が示されています。

  • ステップ 4:新規ユーザーのためにレコメンドを生成する

    前のステップでは、既に何人かをフォローしているユーザーに対するレコメンデーションを生成する方法を学びました。しかし、レコメンドエンジンの課題の 1 つは、新規ユーザーの利用開始をサポートすることにあります。ユーザーが誰もフォローしていない場合に、そのユーザーのためのレコメンデーションをどのように生成すればよいでしょうか?

    前のステップのクエリは、誰のフォローもしていないユーザーにはレコメンデーションを返しません。これらのユーザーにいくつかのレコメンデーションを生成するために、フォールバックして、ユーザーが指定した興味対象を調べることとすることができます。

    scripts/ ディレクトリには、findFriendsWithInterests.js スクリプトがあります。そのスクリプトの内容は次のとおりです。

    const gremlin = require('gremlin');
    const DriverRemoteConnection = gremlin.driver.DriverRemoteConnection;
    const Graph = gremlin.structure.Graph;
    const neq = gremlin.process.P.neq
    const order = gremlin.process.order
    const local = gremlin.process.scope.local
    const values = gremlin.process.column.values
    const desc = gremlin.process.order.desc
    
    const connection = new DriverRemoteConnection(`wss://${process.env.NEPTUNE_ENDPOINT}:8182/gremlin`,{});
    
    const graph = new Graph();
    const g = graph.traversal().withRemote(connection);
    
    const findFriendsWithInterests = async (username) => {
      return g.V()
        .has('User', 'username', username).as('user')
        .out('InterestedIn')
        .in_('InterestedIn')
        .out('Follows')
        .where(neq('user'))
        .values('username')
        .groupCount()
        .order(local)
        .by(values, desc)
        .limit(local, 10)
        .next()
    }
    
    findFriendsWithInterests('alistephanie').then((resp) => {
      console.log(resp.value)
      connection.close()
    })

    これは前のステップに似ています。アプリケーションの内部関数と類似する findFriendsWithInterests 関数があります。特定のユーザーについて、同じ興味を持つ他のユーザーを検索します。次に、これらの類似ユーザーによって最も多くフォローされているユーザーを返します。

    ファイルの下部で、現在誰のフォローもしていないユーザーで関数が呼び出されます。

    次のコマンドを使用してスクリプトを実行できます。

    node scripts/findFriendsWithInterests.js

    次の出力が表示されます。

    Map {
      'thardy' => 27,
      'paullaurie' => 26,
      'michaelunderwood' => 26,
      'paulacruz' => 20,
      'petersonchristina' => 19,
      'annette32' => 19,
      'ocarrillo' => 18,
      'evanewing' => 18,
      'hortonamy' => 18,
      'rodriguezjoseph' => 18 }
    

    成功しました! そのユーザーがどのユーザーのフォローもしていなくても、そのユーザーが他のユーザーをフォローするために、関連するレコメンデーションを生成できます。


    このモジュールでは、グラフデータベースの用語とクエリの仕組みについて学びました。これらの学習に基づいて、データベースにデータをロードして基本的なクエリを実行しました。次に、グラフをトラバースし、類似ユーザーを探して、ユーザーへのレコメンデーションを生成する方法を確認しました。 

    次のモジュールでは、Amazon Cognito を使用してアプリケーションの認証を設定します。