## 16.1. Google Cloud Platform (GCP)

This chapter describes procedures that wrap Google Cloud Platform’s Natural Language API.

Google Cloud Platform’s Natural Language API lets users derive insights from unstructured text using Google machine learning. The procedures in this chapter act as a wrapper around calls to this API to extract entities, categories, or sentiment from text stored as node properties.

Each procedure has two modes:

- Stream - returns a map constructed from the JSON returned from the API
- Graph - creates a graph or virtual graph based on the values returned by the API

This section includes the following:

- Section 16.1.1, “Procedure Overview”
- Section 16.1.2, “Setting up API Key”
- Section 16.1.3, “Entity Extraction”
- Section 16.1.4, “Classification”

- Section 16.1.5, “Examples”

  - Section 16.1.5.1, “Entity Extraction”
  - Section 16.1.5.2, “Classification”

### 16.1.1. Procedure Overview

The procedures are described below:

type | qualified name | signature | description
--- | --- | --- | ---
procedure | `apoc.nlp.gcp.entities.graph` | `apoc.nlp.gcp.entities.graph(sourceNode :: NODE?, config = {} :: MAP?) :: (graph :: MAP?)` | Creates a (virtual) entity graph for provided text
procedure | `apoc.nlp.gcp.entities.stream` | `apoc.nlp.gcp.entities.stream(sourceNode :: NODE?, config = {} :: MAP?) :: (value :: MAP?)` | Returns a stream of entities for provided text
procedure | `apoc.nlp.gcp.classify.graph` | `apoc.nlp.gcp.classify.graph(sourceNode :: NODE?, config = {} :: MAP?) :: (graph :: MAP?)` | Classifies a document into categories.
procedure | `apoc.nlp.gcp.classify.stream` | `apoc.nlp.gcp.classify.stream(sourceNode :: NODE?, config = {} :: MAP?) :: (value :: MAP?)` | Classifies a document into categories.

### 16.1.2. Setting up API Key

We can generate an API Key that has access to the Cloud Natural Language API by going to console.cloud.google.com/apis/credentials. Once we’ve created a key, we can populate and execute the following command to create a parameter that contains these details.

The following defines the `apiKey` parameter.

    :param apiKey => ("<api-key-here>")

### 16.1.3. Entity Extraction

The entity extraction procedures (`apoc.nlp.gcp.entities.*`) are wrappers around the `documents.analyzeEntities` method of the Google Natural Language API. This API method finds named entities (currently proper names and common nouns) in the text along with entity types, salience, mentions for each entity, and other properties.

The procedures are described below:

type | qualified name | signature | description
--- | --- | --- | ---
procedure | `apoc.nlp.gcp.entities.graph` | `apoc.nlp.gcp.entities.graph(sourceNode :: NODE?, config = {} :: MAP?) :: (graph :: MAP?)` | Creates a (virtual) entity graph for provided text
procedure | `apoc.nlp.gcp.entities.stream` | `apoc.nlp.gcp.entities.stream(sourceNode :: NODE?, config = {} :: MAP?) :: (value :: MAP?)` | Returns a stream of entities for provided text

The procedures support the following config parameters:

Table 16.1. Config parameters
name | type | default | description
--- | --- | --- | ---
key | String | null | API Key for Google Natural Language API
nodeProperty | String | text | The property on the provided node that contains the unstructured text to be analyzed

In addition, `apoc.nlp.gcp.entities.graph` supports the following config parameters:

Table 16.2. Config parameters
name | type | default | description
--- | --- | --- | ---
write | Boolean | false | persist the graph of entities
relationshipType | String | ENTITY | relationship type for relationships from source node to entity nodes

Streaming mode.

    CALL apoc.nlp.gcp.entities.stream(sourceNode:Node, {
      key: String,
      nodeProperty: String
    })
    YIELD value

Graph mode.

    CALL apoc.nlp.gcp.entities.graph(sourceNode:Node, {
      key: String,
      nodeProperty: String,
      relationshipType: String,
      write: Boolean
    })
    YIELD graph

### 16.1.4. Classification

The entity extraction procedures (`apoc.nlp.gcp.classify.*`) are wrappers around the `documents.classifyText` method of the Google Natural Language API. This API method classifies a document into categories.

The procedures are described below:

type | qualified name | signature | description
--- | --- | --- | ---
procedure | `apoc.nlp.gcp.classify.graph` | `apoc.nlp.gcp.classify.graph(sourceNode :: NODE?, config = {} :: MAP?) :: (graph :: MAP?)` | Classifies a document into categories.
procedure | `apoc.nlp.gcp.classify.stream` | `apoc.nlp.gcp.classify.stream(sourceNode :: NODE?, config = {} :: MAP?) :: (value :: MAP?)` | Classifies a document into categories.

The procedures support the following config parameters:

Table 16.3. Config parameters
name | type | default | description
--- | --- | --- | ---
key | String | null | API Key for Google Natural Language API
nodeProperty | String | text | The property on the provided node that contains the unstructured text to be analyzed

In addition, `apoc.nlp.gcp.classify.graph` supports the following config parameters:

Table 16.4. Config parameters
name | type | default | description
--- | --- | --- | ---
write | Boolean | false | persist the graph of entities
relationshipType | String | CATEGORY | relationship type for relationships from source node to entity nodes

Streaming mode.

    CALL apoc.nlp.gcp.classify.stream(sourceNode:Node, {
      key: String,
      nodeProperty: String
    })
    YIELD value

Graph mode.

    CALL apoc.nlp.gcp.classify.graph(sourceNode:Node, {
      key: String,
      nodeProperty: String,
      relationshipType: String,
      write: Boolean
    })
    YIELD graph

### 16.1.5. Examples

The examples in this section are based on the following sample graph:

    CREATE (:Article {
     uri: "https://neo4j.com/blog/pokegraph-gotta-graph-em-all/",
     body: "These days I’m rarely more than a few feet away from my Nintendo Switch and I play board games, card games and role playing games with friends at least once or twice a week. I’ve even organised lunch-time Mario Kart 8 tournaments between the Neo4j European offices!"
    });

#### 16.1.5.1. Entity Extraction

Let’s start by extracting the entities from the Article node. The text that we want to analyze is stored in the `body` property of the node, so we’ll need to specify that via the `nodeProperty` configuration parameter.

The following streams the entities for the Pokemon article.

    MATCH (a:Article {uri: "https://neo4j.com/blog/pokegraph-gotta-graph-em-all/"})
    CALL apoc.nlp.gcp.entities.stream(a, {
      key: $apiKey,
      nodeProperty: "body"
    })
    YIELD value
    UNWIND value.entities AS entity
    RETURN entity;

Table 16.5. Results
entity
{name: "card games", salience: 0.17967656, metadata: {}, type: "CONSUMER_GOOD", mentions: [{type: "COMMON", text: {content: "card games", beginOffset: -1}}]}
{name: "role playing games", salience: 0.16441391, metadata: {}, type: "OTHER", mentions: [{type: "COMMON", text: {content: "role playing games", beginOffset: -1}}]}
{name: "Switch", salience: 0.143287, metadata: {}, type: "OTHER", mentions: [{type: "COMMON", text: {content: "Switch", beginOffset: -1}}]}
{name: "friends", salience: 0.13336793, metadata: {}, type: "PERSON", mentions: [{type: "COMMON", text: {content: "friends", beginOffset: -1}}]}
{name: "Nintendo", salience: 0.12601112, metadata: {mid: "/g/1ymzszlpz"}, type: "ORGANIZATION", mentions: [{type: "PROPER", text: {content: "Nintendo", beginOffset: -1}}]}
{name: "board games", salience: 0.08861496, metadata: {}, type: "CONSUMER_GOOD", mentions: [{type: "COMMON", text: {content: "board games", beginOffset: -1}}]}
{name: "tournaments", salience: 0.0603245, metadata: {}, type: "EVENT", mentions: [{type: "COMMON", text: {content: "tournaments", beginOffset: -1}}]}
{name: "offices", salience: 0.034420907, metadata: {}, type: "LOCATION", mentions: [{type: "COMMON", text: {content: "offices", beginOffset: -1}}]}
{name: "Mario Kart 8", salience: 0.029095741, metadata: {wikipedia_url: "https://en.wikipedia.org/wiki/Mario_Kart_8", mid: "/m/0119mf7q"}, type: "PERSON", mentions: [{type: "PROPER", text: {content: "Mario Kart 8", beginOffset: -1}}]}
{name: "European", salience: 0.020393685, metadata: {mid: "/m/02j9z", wikipedia_url: "https://en.wikipedia.org/wiki/Europe"}, type: "LOCATION", mentions: [{type: "PROPER", text: {content: "European", beginOffset: -1}}]}
{name: "Neo4j", salience: 0.020393685, metadata: {mid: "/m/0b76t3s", wikipedia_url: "https://en.wikipedia.org/wiki/Neo4j"}, type: "ORGANIZATION", mentions: [{type: "PROPER", text: {content: "Neo4j", beginOffset: -1}}]}
{name: "8", salience: 0, metadata: {value: "8"}, type: "NUMBER", mentions: [{type: "TYPE_UNKNOWN", text: {content: "8", beginOffset: -1}}]}

We get back 12 different entities. We could then apply a Cypher statement that creates one node per entity and an `ENTITY` relationship from each of those nodes back to the `Article` node.

The following streams the entities for the Pokemon article and then creates nodes for each entity.

    MATCH (a:Article {uri: "https://neo4j.com/blog/pokegraph-gotta-graph-em-all/"})
    CALL apoc.nlp.gcp.entities.stream(a, {
      key: $apiKey,
      nodeProperty: "body"
    })
    YIELD value
    UNWIND value.entities AS entity
    MERGE (e:Entity {name: entity.name})
    SET e.type = entity.type
    MERGE (a)-[:ENTITY]->(e)

Alternatively we can use the graph mode to automatically create the entity graph. As well as having the `Entity` label, each entity node will have another label based on the value of the `type` property. By default a virtual graph is returned.

The following returns a virtual graph of entities for the Pokemon article.

    MATCH (a:Article {uri: "https://neo4j.com/blog/pokegraph-gotta-graph-em-all/"})
    CALL apoc.nlp.gcp.entities.graph(a, {
      key: $apiKey,
      nodeProperty: "body",
      relationshipType: "ENTITY"
    })
    YIELD graph AS g
    RETURN g;

We can see a Neo4j Browser visualization of the virtual graph in Figure 16.1, “Pokemon entities graph”.

Figure 16.1. Pokemon entities graph

If we’re happy with this graph and would like to persist it in Neo4j, we can do this by specifying the `write: true` configuration.

The following creates a `HAS_ENTITY` relationship from the article to each entity.

    MATCH (a:Article {uri: "https://neo4j.com/blog/pokegraph-gotta-graph-em-all/"})
    CALL apoc.nlp.gcp.entities.graph(a, {
      key: $apiKey,
      nodeProperty: "body",
      relationshipType: "HAS_ENTITY",
      write: true
    })
    YIELD graph AS g
    RETURN g;

We can then write a query to return the entities that have been created.

The following returns articles and their entities.

    MATCH (article:Article)
    RETURN article.uri AS article,
           [(article)-[:HAS_ENTITY]->(e) | e.name] AS entities;

Table 16.6. Results
article | entities
--- | ---
"https://neo4j.com/blog/pokegraph-gotta-graph-em-all/" | ["tournaments", "Neo4j", "Switch", "card games", "board games", "Mario Kart 8", "Nintendo", "friends", "8", "offices", "European", "role playing games"]

#### 16.1.5.2. Classification

Now let’s extract categories from the Article node. The text that we want to analyze is stored in the `body` property of the node, so we’ll need to specify that via the `nodeProperty` configuration parameter.

The following streams the categories for the Pokemon article.

    MATCH (a:Article {uri: "https://neo4j.com/blog/pokegraph-gotta-graph-em-all/"})
    CALL apoc.nlp.gcp.classify.stream(a, {
      key: $apiKey,
      nodeProperty: "body"
    })
    YIELD value
    UNWIND value.categories AS category
    RETURN category;

Table 16.7. Results
category
{name: "/Games", confidence: 0.91}

We get back only one category We could then apply a Cypher statement that creates one node per category and a `CATEGORY` relationship from each of those nodes back to the `Article` node.

The following streams the categories for the Pokemon article and then creates nodes for each category.

    MATCH (a:Article {uri: "https://neo4j.com/blog/pokegraph-gotta-graph-em-all/"})
    CALL apoc.nlp.gcp.classify.stream(a, {
      key: $apiKey,
      nodeProperty: "body"
    })
    YIELD value
    UNWIND value.categories AS category
    MERGE (c:Category {name: category.name})
    MERGE (a)-[:CATEGORY]->(c)

Alternatively we can use the graph mode to automatically create the category graph. As well as having the `Category` label, each category node will have another label based on the value of the `type` property. By default a virtual graph is returned.

The following returns a virtual graph of categories for the Pokemon article.

    MATCH (a:Article {uri: "https://neo4j.com/blog/pokegraph-gotta-graph-em-all/"})
    CALL apoc.nlp.gcp.classify.graph(a, {
      key: $apiKey,
      nodeProperty: "body",
      relationshipType: "CATEGORY"
    })
    YIELD graph AS g
    RETURN g;

We can see a Neo4j Browser visualization of the virtual graph in Figure 16.2, “Pokemon categories graph”.

Figure 16.2. Pokemon categories graph

The following creates a `HAS_CATEGORY` relationship from the article to each entity.

    MATCH (a:Article {uri: "https://neo4j.com/blog/pokegraph-gotta-graph-em-all/"})
    CALL apoc.nlp.gcp.classify.graph(a, {
      key: $apiKey,
      nodeProperty: "body",
      relationshipType: "HAS_CATEGORY",
      write: true
    })
    YIELD graph AS g
    RETURN g;

We can then write a query to return the entities that have been created.

The following returns articles and their entities.

    MATCH (article:Article)
    RETURN article.uri AS article,
           [(article)-[:HAS_CATEGORY]->(c) | c.name] AS categories;

Table 16.8. Results
article | categories
--- | ---
"https://neo4j.com/blog/pokegraph-gotta-graph-em-all/" | ["/Games"]