知識グラフに基づく簡単な映画とテレビドラマの推薦システム

バックグラウンド

これは、昨年9月に知識グラフと推奨事項を調査したときに行ったデモプロジェクトです。これは、githubで自動車業界向けの知識グラフのオープンソースプロジェクトを見つけることから始まりました主に知識グラフに基づいた映画やテレビドラマのレコメンデーションシステムに変更しました。

周囲

python3、フラスコフロントエンドフレームワーク、グラフデータベースneo4j(3.3.1)

オペレーティングシステムはwindows10です

プロジェクトフレームワーク

上記の自動車プロジェクトのクローンを作成した後、プロジェクト全体の構造を次の図に示します。

最初の受け入れと2番目の受け入れの2つのプロジェクトバージョンがあります。2つの主な違いは、異なるデータベースを使用することです。前者はmysqlを使用し、後者はneo4jを使用します。私は主に2回目の受け入れに基づいて変革を行いました。2回目の承認のためにプロジェクトを開きます。内部の構造は次の図のようになります。

プロセス分析

以下では、元のプロジェクトの作業プロセスを段階的に分析します。これは、この方法でのみ、プロジェクトの変換を完了することができるためです。

データの読み取りと挿入

まず、データをneo4jに挿入する必要があります。次に、neo4jを起動し、cmdを開いて、次のコマンドを入力する必要があります。

neo4j console

次に、cmdが次のメッセージを表示すると、neo4jが開始されます

 

最後の行に表示されている使用可能なアドレスhttp:// localhost:7474は、neo4jにアクセスし、ブラウザーを開いて、このアドレスをアドレスバーにコピーし、Enterキーを押すと、以下に示すように、neo4jコンソールインターフェイスが表示されます。示されている

データベースが起動したら、プロジェクトでkg \ kg.pyファイルを開くことができます。その中でのメインコードは次のとおりです。

    def data_init(self):
        # 连接图数据库
        print('开始数据预处理')
        self.graph = Graph('http://localhost:7474', user="neo4j", password="szc")
        self.selector = NodeSelector(self.graph)
        self.graph.delete_all()


    def insert_datas(self):
        print('开始插入数据')
        with open('../data/tuples/three_tuples_2.txt', 'r', encoding='utf-8') as f:
            lines, num = f.readlines(), -1
            for line in lines:
                num += 1
                if num % 500 == 0:
                    print('当前处理进度:{}/{}'.format(lines.index(line), len(lines)))

                line = line.strip().split(' ')
                if len(line) != 3:
                    print('insert_datas错误:', line)
                    continue
                self.insert_one_data(line)

    def insert_one_data(self, line):
        if '' in line:
            print('insert_one_data错误', line)
            return

        start = self.look_and_create(line[0])
        for name in self.get_items(line[2]):
            end = self.look_and_create(name)
            r = Relationship(start, line[1], end, name=line[1])
            self.graph.create(r)  # 当存在时不会创建新的

        # 查找节点是否不存,不存在就创建一个

    def look_and_create(self, name):
        end = self.graph.find_one(label="car_industry", property_key="name", property_value=name)
        if end == None:
            end = Node('car_industry', name=name)
        return end

    def get_items(self, line):
        if '{' not in line and '}' not in line:
            return [line]
        # 检查
        if '{' not in line or '}' not in line:
            print('get_items Error', line)
        lines = [w[1:-1] for w in re.findall('{.*?}', line)]
        return lines

上部のdata_init()関数は、neo4jデータベースに接続するために使用されます。データベースのアドレス、ユーザー名、およびパスワードを入力するだけです。次に、graph.delete_all()関数を呼び出します。データを挿入する前に、元のデータをクリアします。このステップでは、独自のビジネスシナリオに従ってデータを保持するかどうかを検討する必要があります。

次に、insert_datas()関数があります。この関数は、txtファイルを読み取り、各行をトラバースし、各行に対してinsert_one_data()関数を呼び出し、各行を分析し、ノードと関係を作成します。コードによると、各行のデータは、「渝北の安陽の場所」などの「開始点関係の終了点」の形式であることがわかります。これは、エンティティAnyangとエンティティYubeiの関係を意味します。は場所で、順序はAnyang->場所->渝北です。

insert_one_data()関数が呼び出されると、最初にデータベースに同じ名前のノードがあるかどうかを照会し、その結果に応じて既存のノードを再利用するか、新しいノードを構築するかを決定します。このプロセスは関数に対応します。 look_and_create()。

関数look_and_create()では、「car_industry」はデータベースのラベルであり(Mysqlの各データベースの名前に対応していることを理解しています。どちらを使用する場合でも、コマンドuse database some_databaseを呼び出します)、次にfind_one()関数を使用します。 、property_nameの値は、に対応します。ノードを作成する場合、Nodeのコンストラクターのパラメーター名はnameであり、property_valueは、エンティティーの名前であるNodeのコンストラクターのnameパラメーター値です。私の故郷であるAnyangCityエンティティを例にとると、neo4jのストレージ構造は、{property_name: "name"、property_value: "Anyang"}として理解できます。

最後のget_items()関数は、あまり解釈せずに、エンティティの合法性をチェックすることです。

サービスを実行する

すべてのデータがデータベースに挿入されたら、サービスを実行できます。ファイルはrun_server.pyに対応し、内部のコードは次のとおりです。

if __name__ == '__main__':
    args=get_args()
    print('\nhttp_host:{},http_port:{}'.format('localhost',args.http_port))
    app.run(debug=True, host='210.41.97.169', port=8090)

実際、重要なのはapp.run()関数であり、IPとポートを自分のものに置き換えるだけです。

ページリクエストの処理

私たちのビジネスロジックは次のとおりです。ブラウザにURLとパラメータを入力して、関連する結果を取得します。

その中で、パラメータを処理するプロセスはファイルviews.pyに対応し、内部のメインコードは次のとおりです。

@app.route('/KnowGraph/v2',methods=["POST"])
def look_up():
    kg=KnowGraph(get_args())
    client_params=request.get_json(force=True)
    server_param={}
    if client_params['method'] == 'entry_to_entry':
        kg.lookup_entry2entry(client_params,server_param)
    elif client_params['method'] == 'entry_to_property':
        kg.lookup_entry2property(client_params,server_param)
    elif client_params['method'] == 'entry':
        kg.lookup_entry(client_params,server_param)
    elif client_params['method'] == 'statistics':
        kg.lookup_statistics(client_params,server_param)
    elif client_params['method'] == 'live':
        params={'success':'true'}
        server_param['result']=params    
    server_param['id']=client_params['id']
    server_param['jsonrpc']=client_params['jsonrpc']
    server_param['method']=client_params['method']
    print(server_param)
    return json.dumps(server_param, ensure_ascii=False).encode("utf-8")

ご覧のとおり、/ KnowGraph / v2パスのpostメソッドはlook_up関数にルーティングされます。この関数は、パラメーターメソッドの値に応じてkgオブジェクトのさまざまな関数を呼び出し、さまざまなクエリロジックを実行します。

ただし、ブラウザにパスとパラメータを入力してEnterキーを押した後、データベース情報を取得する必要があります。これは明らかに対応するgetメソッドです。さらに、フラスコテンプレートへのデータのルーティングは書き込まれないため、このファイルに大きな変更を加える必要があります。

データクエリ

私は、views.pyファイルがkgオブジェクトのさまざまな関数を呼び出して、パラメーターメソッドの値に応じてさまざまな結果を取得することを説明しました。

kgオブジェクトが属するKnowledgeGraphクラスは、ファイルmodules.pyにあります。最も単純で最も基本的なエンティティクエリを例として取り上げ、それがどのように実装されているかを見てみましょう。これは、lookup_entry関数に対応します。コードは次のとおりです。

    def lookup_entry(self,client_params,server_param):
        #支持设定网络查找的深度
        start_time = time.time()
        params=client_params["params"]
        edges=set()
        self.lookup_entry_deep(edges,params,0)
        if len(edges)==0:
            server_param['result']={"success":'false'}
        else:                
            server_param['result']={'edges':[list(i) for i in edges],"success":'true'}
            print('本次查找三元组的数量为:{},耗时:{}s'.format(len(edges),time.time()-start_time))

タイミングに加えて、検索するエンティティ名と検索深度を含むクライアントパラメータのパラメータが主に取り出され、lookup_entry_deep関数が呼び出されて検索され、結果がエッジコレクションに保存され、最後にそれぞれがエッジコレクションのアイテムはリストとして使用されますリストの各アイテムは、server_paramsの「results」アイテムの「edges」に格納されて返されます。

次に、lookup_entry_deep関数の実装を見てみましょう。コードは次のとおりです。

    def lookup_entry_deep(self,edges,params,deep):
        #当前查找深度不得等于要求的深度
        if deep >= params['deep']:
            return
        #正向查找
        result1=self.graph.data("match (s)-[r]->(e) where s.name='{}' return s.name,r.name,e.name".format(params['name']))
        result2=self.graph.data("match (e)<-[r]-(s) where e.name='{}' return s.name,r.name,e.name".format(params['name']))
        if len(result1)==0 and len(result2)==0:
            return
        for item in result1:
            edges.add((item['s.name'],item['r.name'],item['e.name']))
            if  item['s.name'] != item['e.name']:#避免出现:双面胶:中文名:双面胶的死循环
                params['name']=item['e.name']
                self.lookup_entry_deep(edges,params.copy(),deep+1)
 
        for item in result2:
            edges.add((item['s.name'],item['r.name'],item['e.name']))
            if  item['s.name'] != item['e.name']:#避免出现:双面胶:中文名:双面胶的死循环
                params['name']=item['e.name']
                self.lookup_entry_deep(edges,params.copy(),deep+1) 

まず、深さが制限を超えた場合は、直接戻ってください。次に、検索するエンティティの名前であるparamsのnameアイテムに対して、データベースで順方向および逆方向のクエリを実行し、各アイテムをエッジコレクションにタプルとして保存して、この関数を再帰的に呼び出します。同時に深さ+1

改造

既存のプロセスは上記の通りです。次に、映画やテレビドラマの推奨ビジネスシナリオを刷新します。

ユーザーがテレビシリーズ「AdmiralXXX」を視聴したと仮定すると、監督、俳優、場所、言語、ジャンルのタグに基づいて、ユーザーが興味を持つ可能性のある映画やテレビシリーズをお勧めできます。

データ形式

私たちのファイルはすべてwikiディレクトリに保存されています。それらはすべてtxtファイルであり、各行はjsonであり、1行は次のとおりです。

{
    .....  
    "title": "上将XXX", 
    "wikiData": {
        .....
        "wikiInfo": {
            "country": "中国大陆", 
            "language": "普通话", 
            "directors": [
                "安澜"
            ], 
            "actors": [
                "宋春丽", 
                "王伍福", 
                "张秋歌", 
                "范明", 
                "刘劲", 
                "陶慧敏", 
                "侯勇"
            ], 
            ....
        }, 
        ....
        "wikiTags": [
            "电视剧", 
            "历史", 
            "战争", 
            "军旅", 
            "革命", 
            "动作", 
            "热血", 
            "激昂", 
            "24-36", 
            "36-45", 
            "45-55", 
            "55-70", 
            "上星剧", 
            "传记"
        ]
    }
}

その中の有用な情報は、監督や俳優など、上記のようにフォーマットされています。

次に、プロジェクトを分析するときに整理されたプロセスに従って変換を実行できます

データの読み取りと挿入

これはkg.pyファイルに対応し、最初にディレクトリパスを定義します

data_dir = "C:\\Users\\songzeceng\\Desktop\\wiki\\"

次に、このディレクトリ内のファイルをトラバースし、各ファイルを読み取って解析します。コードは次のとおりです。

    def insert_data_from_txt(self, file_path):
        try:
            with open(file=file_path, mode="r", encoding="utf-8") as f:
                for line in f.readlines():
                    item = json.loads(line)
                    if 'title' not in item.keys():
                        continue

                    title = self.look_and_create(item['title'])

                    if 'wikiData' not in item.keys():
                        continue

                    wikiData = item['wikiData']

                    if 'wikiDesc' in wikiData.keys():
                        wikiDesc = self.look_and_create(wikiData['wikiDesc'])
                        self.create_sub_graph(entity1=title, entity2=wikiDesc, relation="desc")

                    if 'wikiTags' in wikiData.keys():
                        for tag in wikiData['wikiTags']:
                            tag = self.look_and_create(tag)
                            self.create_sub_graph(entity1=title, entity2=tag, relation="tag")

                    wikiInfo = wikiData['wikiInfo']

                    if 'country' in wikiInfo.keys():
                        country = self.look_and_create(wikiInfo['country'])
                        self.create_sub_graph(entity1=title, entity2=country, relation="country")

                    if 'language' in wikiInfo.keys():
                        language = self.look_and_create(wikiInfo['language'])
                        self.create_sub_graph(entity1=title, entity2=language, relation="language")

                    if 'actors' in wikiInfo.keys():
                        for actor in wikiInfo['actors']:
                            actor = self.look_and_create(actor)
                            self.create_sub_graph(entity1=title, entity2=actor, relation="actor")
                    if 'directors' in wikiInfo.keys():
                        for director in wikiInfo['directors']:
                            actor = self.look_and_create(director)
                            self.create_sub_graph(entity1=title, entity2=actor, relation="director")
            print(file_path, "读取完毕")
        except Exception as e:
            print("文件" + file_path + "读取异常:" + str(e))
            pass

 長く見ると、実際には各アイテムを解析し、最初に関数look_and_createに対応するエンティティを検索または作成します。私のpy2neoのバージョンは元のプロジェクトとは異なるため、この関数を書き直しました。コードは次のとおりです。

    def look_and_create(self, name):
        matcher = NodeMatcher(self.graph)
        end = matcher.match("car_industry", name=name).first()
        if end == None:
            end = Node('car_industry', name=name)
        return end

次に、関数create_sub_graphに対応するエンティティ関係を作成します。コードは次のとおりです。

    def create_sub_graph(self, entity1, relation, entity2):
        r = Relationship(entity1, relation, entity2, name=relation)
        self.graph.create(r)

kgファイルコード全体は次のとおりです

# coding:utf-8
'''
Created on 2018年1月26日

@author: qiujiahao

@email:[email protected]

'''
import sys
import re
import os

sys.path.append('..')
from conf import get_args
from py2neo import Node, Relationship, Graph, NodeMatcher
import pandas as pd
import json

import os

data_dir = "C:\\Users\\songzeceng\\Desktop\\wiki\\"


class data(object):
    def __init__(self):
        self.args = get_args()
        self.data_process()

    def data_process(self):
        # 初始化操 # 插入数据
        self.data_init()
        print("数据预处理完毕")

    def data_init(self):
        # 连接图数据库
        print('开始数据预处理')
        self.graph = Graph('http://localhost:7474', user="neo4j", password="szc")
        # self.graph.delete_all()

        file_names = os.listdir(data_dir)
        for file_name in file_names:
            self.insert_data_from_txt(data_dir + file_name)

    def insert_data_from_txt(self, file_path):
        try:
            with open(file=file_path, mode="r", encoding="utf-8") as f:
                for line in f.readlines():
                    item = json.loads(line)
                    if 'title' not in item.keys():
                        continue

                    title = self.look_and_create(item['title'])

                    # id = self.look_and_create(item['id'])
                    #
                    # self.create_sub_graph(entity1=title, entity2=id, relation="title")

                    if 'wikiData' not in item.keys():
                        continue

                    wikiData = item['wikiData']

                    if 'wikiDesc' in wikiData.keys():
                        wikiDesc = self.look_and_create(wikiData['wikiDesc'])
                        self.create_sub_graph(entity1=title, entity2=wikiDesc, relation="desc")

                    if 'wikiTags' in wikiData.keys():
                        for tag in wikiData['wikiTags']:
                            tag = self.look_and_create(tag)
                            self.create_sub_graph(entity1=title, entity2=tag, relation="tag")

                    wikiInfo = wikiData['wikiInfo']

                    if 'country' in wikiInfo.keys():
                        country = self.look_and_create(wikiInfo['country'])
                        self.create_sub_graph(entity1=title, entity2=country, relation="country")

                    if 'language' in wikiInfo.keys():
                        language = self.look_and_create(wikiInfo['language'])
                        self.create_sub_graph(entity1=title, entity2=language, relation="language")

                    if 'actors' in wikiInfo.keys():
                        for actor in wikiInfo['actors']:
                            actor = self.look_and_create(actor)
                            self.create_sub_graph(entity1=title, entity2=actor, relation="actor")
                    if 'directors' in wikiInfo.keys():
                        for director in wikiInfo['directors']:
                            actor = self.look_and_create(director)
                            self.create_sub_graph(entity1=title, entity2=actor, relation="director")
            print(file_path, "读取完毕")
        except Exception as e:
            print("文件" + file_path + "读取异常:" + str(e))
            pass

    def create_sub_graph(self, entity1, relation, entity2):
        r = Relationship(entity1, relation, entity2, name=relation)
        self.graph.create(r)

    def look_and_create(self, name):
        matcher = NodeMatcher(self.graph)
        end = matcher.match("car_industry", name=name).first()
        if end == None:
            end = Node('car_industry', name=name)
        return end


if __name__ == '__main__':
    data = data()

 それを実行すると、コマンドライン出力は次の図のようになります

データは標準化されておらず、多くのファイルを読み取ることができません。とにかくデモです。次に、neo4jデータベースで25個のデータを取得すると、結果が次の図に示されます。

サービスを実行する

ここで、run_server.pyのIPとポートを独自のものに直接変更できます

リクエストの処理

このステップはviews.pyに対応します。

まず、/ KnowGraph / v2パスのgetリクエストをインターセプトする必要があるため、以下に示すようにアノテーション関数を追加する必要があります。

@app.route('/KnowGraph/v2', methods=["GET"])
def getInfoFromServer():
    pass

次に、この関数を実装し、最初にリクエストパラメータを処理します。リクエストの完全なURLは次のようになります 

http://localhost:8090/KnowGraph/v2?method=entry&jsonrpc=2.0&id=1&params=entry=上将许世友-deep=2

多くのパラメーターがあり、jsonrpc、idなど、それらの多くは固定されているので、私はそれらを単純化して

http://localhost:8090/KnowGraph/v2?name=上将许世友

 次に、getInfoFromServer()関数で、すべてのデフォルトパラメータを追加します。コードは次のとおりです。

def handle_args(originArgs):
    if 'name' not in originArgs.keys():
        return None

    args = {}
    for item in originArgs:
        key = item
        value = originArgs[key]
        if key == "params":
            kvs = str(value).split("-")
            kv_dic = {}
            for item in kvs:
                kv = item.split("=")
                k = kv[0]
                v = kv[1]
                if v.isnumeric():
                    kv_dic[k] = int(v)
                else:
                    kv_dic[k] = v
            args[key] = kv_dic
        else:
            if value.isnumeric():
                args[key] = int(value)
            else:
                args[key] = value

    if 'params' not in args.keys():
        args['params'] = {
            'name': args['name']
        }
        args.pop('name')

    args['params']['name'] = args['params']['name'].replace('\'', '\\\'')

    if 'method' not in args.keys():
        args['method'] = 'entry'
    if 'deep' not in args['params'].keys():
        args['params']['deep'] = 2
    if 'jsonrpc' not in args.keys():
        args['jsonrpc'] = 2.0
    if 'id' not in args.keys():
        args['id'] = 1
    return args

実際、それは主にトラバーサルと充填操作です

パラメータが処理された後、パラメータのメソッドフィールドに基づいてさまざまなクエリ操作を実行し、server_paramの結果フィールドから結果を取得し、それをフロントエンドに渡してページをレンダリングできます。したがって、getInfoFromServer()関数コードは次のように記述できます。

@app.route('/KnowGraph/v2', methods=["GET"])
def getInfoFromServer():
    args = handle_args(request.args.to_dict())

    kg = KnowGraph(args)
    client_params = args
    server_param = {}

    if client_params['method'] == 'entry':
        kg.lookup_entry(client_params, server_param)

    server_param['id'] = client_params['id']
    server_param['jsonrpc'] = client_params['jsonrpc']
    server_param['method'] = client_params['method']
    print("server_param:\n", server_param)

    global mydata
    if 'result' in server_param.keys():
        mydata = server_param['result']
    else:
        mydata = '{}'
    print("mydata:\n", mydata)
    return render_template("index.html")

ここでは、エンティティのクエリのみを扱います。これは、入力がユーザーが視聴した映画やテレビシリーズの名前であるためです。

インターフェイスをレンダリングするとき、データは/ KnowGraph / dataパスを介して取得されるため、それをインターセプトするためのコードは次のとおりです。

@app.route("/KnowGraph/data")
def data():
    print("data:", data)
    return mydata

 views.pyファイル全体は次のとおりです

# coding:utf-8
'''
Created on 2018年1月9日

@author: qiujiahao

@email:[email protected]

'''

from flask import jsonify
from conf import *
from flask import Flask
from flask import request, render_template
from server.app import app
import tensorflow as tf
from server.module import KnowGraph
import json

mydata = ""

# http://210.41.97.89:8090/KnowGraph/v2?name=胜利之路
# http://113.54.234.209:8090/KnowGraph/v2?name=孤战
# http://localhost:8090/KnowGraph/v2?method=entry_to_property&jsonrpc=2.0&id=1&params=entry=水冶-property=位置
@app.route('/KnowGraph/v2', methods=["GET"])
def getInfoFromServer():
    args = handle_args(request.args.to_dict())

    kg = KnowGraph(args)
    client_params = args
    server_param = {}

    if client_params['method'] == 'entry':
        kg.lookup_entry(client_params, server_param)

    server_param['id'] = client_params['id']
    server_param['jsonrpc'] = client_params['jsonrpc']
    server_param['method'] = client_params['method']
    print("server_param:\n", server_param)

    global mydata
    if 'result' in server_param.keys():
        mydata = server_param['result']
    else:
        mydata = '{}'
    print("mydata:\n", mydata)
    return render_template("index.html")


def handle_args(originArgs):
    if 'name' not in originArgs.keys():
        return None

    args = {}
    for item in originArgs:
        key = item
        value = originArgs[key]
        if key == "params":
            kvs = str(value).split("-")
            kv_dic = {}
            for item in kvs:
                kv = item.split("=")
                k = kv[0]
                v = kv[1]
                if v.isnumeric():
                    kv_dic[k] = int(v)
                else:
                    kv_dic[k] = v
            args[key] = kv_dic
        else:
            if value.isnumeric():
                args[key] = int(value)
            else:
                args[key] = value

    if 'params' not in args.keys():
        args['params'] = {
            'name': args['name']
        }
        args.pop('name')

    args['params']['name'] = args['params']['name'].replace('\'', '\\\'')

    if 'method' not in args.keys():
        args['method'] = 'entry'
    if 'deep' not in args['params'].keys():
        args['params']['deep'] = 2
    if 'jsonrpc' not in args.keys():
        args['jsonrpc'] = 2.0
    if 'id' not in args.keys():
        args['id'] = 1
    return args


@app.route("/KnowGraph/data")
def data():
    print("data:", data)
    return mydata

データベースクエリ

最後に、module.pyのデータベースクエリと結果分析に力を注ぎます。

見やすくするために、結果をjsonファイルに保存します。そのため、クエリ結果はメモリ内の辞書に保存されます。各クエリの前に、辞書がクリアされ、クエリが実行されてから、さまざまな解析ロジックが実行されます。結果の有無に応じて実行されます。したがって、以下に示すようにlookup_entry関数を記述できます。

    def lookup_entry(self, client_params, server_param):
        # 支持设定网络查找的深度
        start_time = time.time()
        params = client_params["params"]
        edges = set()
        sim_dict.clear()

        self.lookup_entry_deep(edges, params, 0)
        if len(edges) == 0:
            server_param['success'] = 'false'
        else:
            self.handleResult(edges, server_param, start_time)

エンティティに関する問い合わせは、lookup_entry_deep()関数に配置されます。一般的に言って、私たちの深さは2つのレベルだけです。最初のレベルは、許世友将軍の監督など、ユーザーの映画やテレビシリーズのさまざまな属性を照会するためのものです。2番目のレベルでは、これに対応するエンティティを見つけます。クエリなど、各属性に応じた属性許世友提督の監督であり、主な映画やテレビドラマでもあります。明らかに、最初のレイヤーは順方向検索であり、2番目のレイヤーは逆方向検索です。

検索するとき、ユーザーが今見たばかりの映画やテレビシリーズを推奨しないようにするために、結果の重複を排除する必要もあります。たとえば、アドミラルXXXを検索します。アドミラルXXXのディレクターがアンランであることがわかった場合、アンランで逆検索を実行します。アンランがアドミラルXXXの作業のみを指示していることがわかった場合は、いいえ、許世友提督を推奨リストに追加してください。

他のエンティティが見つからない上記の状況に対応して、この戻り結果を「他に何もない」と定義しました。何も見つからない場合は「何も得られません」、深さが標準を超える場合は「深さ」になります。 ;すべてが正常です、それは「大丈夫」です。

最初に双方向クエリを実行します。コードは次のとおりです。

        result1 = self.graph.run(cypher='''match (s)-[r]->(e) where s.name='{}'
                                            return s.name,r.name,e.name'''.format(params['name'])).data()

        result2 = self.graph.run(cypher='''match (e)<-[r]-(s) where e.name='{}' 
                                            return s.name,r.name,e.name '''.format(params['name'])).data()

次に、2つの結果は空であると判断され、長さが0の場合、「nothinggot」が返されます。

        if len(result1) == 0 and len(result2) == 0:
            return 'nothing got'

 result2(つまり、逆検索の結果)にアイテムが1つしかない場合、このアイテムのs.name(つまり、映画とテレビの再生の名前)は引き続き入力エンティティ名であり、e.nameは引き続き入力されます。 (つまり、属性名)は元の属性名のままです。その後、直接「nothingelse」に戻ります。

        if len(result2) == 1:
            item = result2[0]
            if origin_tv_name is not None and origin_property_name is not None:
                if origin_property_name == item['e.name'] and origin_tv_name == item['s.name']:
                    return 'nothing else'

ここでのorigin_tv_nameとorigin_property_nameは、どちらもlookup_entry_deep関数のパラメーターの1つであり、デフォルトはNoneです。

次に、最初にフォワードクエリのresult1をトラバースし、属性値(e.name)、属性名(r.name)、および元の映画とテレビドラマ(s.name)を内部に連結し、それらをトリプルとして保存します。エッジコレクション。

        for item in result1:
            tv_name = item['s.name']
            property_name = item['e.name']

            has_result = False
            if tv_name != property_name:  # 避免出现:双面胶:中文名:双面胶的死循环
                if oldName != property_name:
                    params['name'] = property_name
                    has_result = self.lookup_entry_deep(edges, params.copy(), deep + 1,
                                                        origin_tv_name=tv_name,
                                                        origin_property_name=property_name)

oldNameは、このクエリのエンティティの名前です。無限ループを回避するために、判断が追加されています。実際、このシナリオでは、この判断を確立する必要があります。

次に、逆検索の結果を分析します。新しい映画とテレビドラマが見つかった場合、この関係の類似性は、最初に新しい映画とテレビドラマとその属性との関係に基づいています。次に、新しい映画とテレビシリーズ、同じ属性名、類似の辞書とエッジコレクションへの類似性を累積的または新しい方法で追加します。コードは次のとおりです。

        for item in result2:
            tv_name = item['s.name']
            property_name = item['e.name']
            relation_name = item['r.name']

            
            if tv_name != origin_tv_name:
                 score = get_sim_score_accroding_to_relation(relation_name)

                 if tv_name not in sim_dict.keys():
                     sim_dict[tv_name] = {
                         relation_name: [property_name],
                         "similarity": score
                     }
                 else:
                     item_dict = sim_dict[tv_name]
                     if relation_name in item_dict.keys() and \
                             property_name in item_dict.values():
                        continue

                     if relation_name in item_dict.keys():
                        item_dict[relation_name].append(property_name)
                     else:
                        item_dict[relation_name] = [property_name]
                     item_dict["similarity"] += score
                 edges.add((tv_name, relation_name, property_name))

その中で、関係に従って類似性を取得するための関数get_sim_score_accroding_to_relation()コードは次のとおりです。

def get_sim_score_accroding_to_relation(relation_name):
    if relation_name in ['actor', 'director', 'tag']:
        return 1.0
    elif relation_name in ['language', 'country']:
        return 0.5
    return 0.0

完全なlookup_entry_deep()関数を以下に示します

    # 限制深度的查找
    def lookup_entry_deep(self, edges, params, deep, origin_tv_name=None, origin_property_name=None):
        # 当前查找深度不得等于要求的深度
        if deep >= params['deep']:
            return 'deep out'
        # 正向查找
        oldName = str(params['name'])
        if oldName.__contains__("\'") and not oldName.__contains__("\\\'"):
            params['name'] = oldName.replace("\'", "\\\'")

        result1 = self.graph.run(cypher='''match (s)-[r]->(e) where s.name='{}'
                                            return s.name,r.name,e.name'''.format(params['name'])).data()

        result2 = self.graph.run(cypher='''match (e)<-[r]-(s) where e.name='{}' 
                                            return s.name,r.name,e.name '''.format(params['name'])).data()

        if len(result1) == 0 and len(result2) == 0:
            return 'nothing got'

        if len(result2) == 1:
            item = result2[0]
            if origin_tv_name is not None and origin_property_name is not None:
                if origin_property_name == item['e.name'] and origin_tv_name == item['s.name']:
                    return 'nothing else'

        for item in result1:
            tv_name = item['s.name']
            property_name = item['e.name']

            if tv_name != property_name:  # 避免出现:双面胶:中文名:双面胶的死循环
                if oldName != property_name:
                    params['name'] = property_name
                    has_result = self.lookup_entry_deep(edges, params.copy(), deep + 1,
                                                        origin_tv_name=tv_name,
                                                        origin_property_name=property_name)

        for item in result2:
            has_result = False
            tv_name = item['s.name']
            property_name = item['e.name']
            relation_name = item['r.name']

            if tv_name != origin_tv_name:
                score = get_sim_score_accroding_to_relation(relation_name)

                if tv_name not in sim_dict.keys():
                    sim_dict[tv_name] = {
                        relation_name: [property_name],
                        "similarity": score
                    }
                else:
                    item_dict = sim_dict[tv_name]
                    if relation_name in item_dict.keys() and \
                            property_name in item_dict.values():
                        continue

                    if relation_name in item_dict.keys():
                        item_dict[relation_name].append(property_name)
                    else:
                        item_dict[relation_name] = [property_name]
                    item_dict["similarity"] += score
                edges.add((tv_name, relation_name, property_name))

        return 'ok'

クエリが完了すると、結果がある場合は、handle_result()関数に移動して、結果、戻り値、または出力を処理します。これは主に、高から低にソートし、上位20を取り出して、jsonファイルに書き込む類似性に基づいています。コードのこの部分は次のとおりです。

    def handleResult(self, edges, server_param, start_time):
        ....
        sorted_sim_list = sorted(sim_dict.items(), key=lambda x: x[1]['similarity'], reverse=True)
        ret = {}
        for i in range(len(sorted_sim_list)):
            if i >= 20:
                break
            ret[sorted_sim_list[i][0]] = sorted_sim_list[i][1]

        mydata = json.dumps(ret, ensure_ascii=False)
        print('Json路径是:%s' % (fname))
        self.clear_and_write_file(fname, mydata)

    def clear_and_write_file(self, fname, mydata):
        with open(fname, 'w', encoding='utf-8') as f:
            f.write(str(""))
        with open(fname, 'a', encoding='utf-8') as f:
            f.write(str(mydata))

さらに、結果をフロントエンドインターフェイスに出力するために使用されるserver_paramにも保存しました。コードのこの部分は、次のとおりです。

        ret = []
        for result in edges:
            ret.append({
                "source": result[0],
                "target": result[2],
                "relation": result[1],
                "label": "relation"
            })
        print("ret:", ret)
        server_param['result'] = {"edges": ret}
        server_param['success'] = 'true'
        print('本次查找三元组的数量为:{},耗时:{}s'.format(len(ret), time.time() - start_time))

 完全な結果処理機能コードは次のとおりです。

    def handleResult(self, edges, server_param, start_time):
        ret = []
        for result in edges:
            ret.append({
                "source": result[0],
                "target": result[2],
                "relation": result[1],
                "label": "relation"
            })
        print("ret:", ret)
        server_param['result'] = {"edges": ret}
        server_param['success'] = 'true'
        print('本次查找三元组的数量为:{},耗时:{}s'.format(len(ret), time.time() - start_time))

        sorted_sim_list = sorted(sim_dict.items(), key=lambda x: x[1]['similarity'], reverse=True)
        ret = {}
        for i in range(len(sorted_sim_list)):
            if i >= 20:
                break
            ret[sorted_sim_list[i][0]] = sorted_sim_list[i][1]

        mydata = json.dumps(ret, ensure_ascii=False)
        print('Json路径是:%s' % (fname))
        self.clear_and_write_file(fname, mydata)

運転結果

最初にサービスを開始し、run_server.pyを実行してから、ブラウザーのアドレスバーに次のURLを入力します(XXXは入力した名前です)。

http://210.41.97.169:8090/KnowGraph/v2?name=XXX

 その場合、ページ出力は次のようになります。

結果は非常に複雑です。jsonファイルの最初の20個の出力を見てみましょう。結果は次のとおりです。

{
  "XXX元帅": {
    "actor": [
      "侯勇",
      "刘劲"
    ],
    "similarity": 14.0,
    "language": [
      "普通话"
    ],
    "country": [
      "中国大陆"
    ],
    "tag": [
      "传记",
      "上星剧",
      "55-70",
      "45-55",
      "36-45",
      "24-36",
      "热血",
      "革命",
      "战争",
      "历史",
      "电视剧"
    ]
  },
  "BBB": {
    "actor": [
      "刘劲",
      "王伍福"
    ],
    "similarity": 14.0,
    "language": [
      "普通话"
    ],
    "country": [
      "中国大陆"
    ],
    "tag": [
      "传记",
      "上星剧",
      "55-70",
      "45-55",
      "36-45",
      "24-36",
      "热血",
      "革命",
      "战争",
      "历史",
      "电视剧"
    ]
  },
  "长征大会师": {
    "actor": [
      "刘劲",
      "王伍福"
    ],
    "similarity": 14.0,
    "language": [
      "普通话"
    ],
    "country": [
      "中国大陆"
    ],
    "tag": [
      "上星剧",
      "55-70",
      "45-55",
      "36-45",
      "24-36",
      "激昂",
      "热血",
      "革命",
      "战争",
      "历史",
      "电视剧"
    ]
  },
  "战将": {
    "language": [
      "普通话"
    ],
    "similarity": 13.0,
    "country": [
      "中国大陆"
    ],
    "tag": [
      "传记",
      "上星剧",
      "55-70",
      "45-55",
      "36-45",
      "24-36",
      "热血",
      "动作",
      "革命",
      "战争",
      "历史",
      "电视剧"
    ]
  },
  "炮神": {
    "language": [
      "普通话"
    ],
    "similarity": 13.0,
    "country": [
      "中国大陆"
    ],
    "tag": [
      "上星剧",
      "55-70",
      "45-55",
      "36-45",
      "24-36",
      "激昂",
      "动作",
      "革命",
      "军旅",
      "战争",
      "历史",
      "电视剧"
    ]
  },
  "独立纵队": {
    "language": [
      "普通话"
    ],
    "similarity": 13.0,
    "country": [
      "中国大陆"
    ],
    "tag": [
      "上星剧",
      "55-70",
      "45-55",
      "36-45",
      "24-36",
      "激昂",
      "热血",
      "动作",
      "革命",
      "战争",
      "历史",
      "电视剧"
    ]
  },
  "女子军魂": {
    "language": [
      "普通话"
    ],
    "similarity": 13.0,
    "country": [
      "中国大陆"
    ],
    "tag": [
      "上星剧",
      "55-70",
      "45-55",
      "36-45",
      "24-36",
      "激昂",
      "热血",
      "革命",
      "军旅",
      "战争",
      "历史",
      "电视剧"
    ]
  },
  "热血军旗": {
    "actor": [
      "侯勇"
    ],
    "similarity": 12.0,
    "language": [
      "普通话"
    ],
    "country": [
      "中国大陆"
    ],
    "tag": [
      "上星剧",
      "55-70",
      "45-55",
      "36-45",
      "热血",
      "动作",
      "革命",
      "战争",
      "历史",
      "电视剧"
    ]
  },
  "擒狼": {
    "language": [
      "普通话"
    ],
    "similarity": 12.0,
    "country": [
      "中国大陆"
    ],
    "tag": [
      "上星剧",
      "55-70",
      "45-55",
      "36-45",
      "24-36",
      "激昂",
      "动作",
      "革命",
      "战争",
      "历史",
      "电视剧"
    ]
  },
  "信者无敌": {
    "language": [
      "普通话"
    ],
    "similarity": 12.0,
    "country": [
      "中国大陆"
    ],
    "tag": [
      "上星剧",
      "55-70",
      "45-55",
      "36-45",
      "24-36",
      "激昂",
      "热血",
      "革命",
      "战争",
      "历史",
      "电视剧"
    ]
  },
  "我的抗战之猎豹突击": {
    "language": [
      "普通话"
    ],
    "similarity": 12.0,
    "country": [
      "中国大陆"
    ],
    "tag": [
      "上星剧",
      "55-70",
      "45-55",
      "36-45",
      "24-36",
      "激昂",
      "热血",
      "革命",
      "战争",
      "历史",
      "电视剧"
    ]
  },
  "魔都风云": {
    "language": [
      "普通话"
    ],
    "similarity": 12.0,
    "country": [
      "中国大陆"
    ],
    "tag": [
      "上星剧",
      "55-70",
      "45-55",
      "36-45",
      "24-36",
      "激昂",
      "热血",
      "动作",
      "革命",
      "战争",
      "电视剧"
    ]
  },
  "英雄戟之影子战士": {
    "language": [
      "普通话"
    ],
    "similarity": 12.0,
    "country": [
      "中国大陆"
    ],
    "tag": [
      "55-70",
      "45-55",
      "36-45",
      "24-36",
      "激昂",
      "热血",
      "动作",
      "革命",
      "战争",
      "历史",
      "电视剧"
    ]
  },
  "第一声枪响": {
    "language": [
      "普通话"
    ],
    "similarity": 12.0,
    "country": [
      "中国大陆"
    ],
    "tag": [
      "上星剧",
      "55-70",
      "45-55",
      "36-45",
      "24-36",
      "激昂",
      "热血",
      "革命",
      "战争",
      "历史",
      "电视剧"
    ]
  },
  "亮剑": {
    "language": [
      "普通话"
    ],
    "similarity": 12.0,
    "country": [
      "中国大陆"
    ],
    "tag": [
      "上星剧",
      "45-55",
      "36-45",
      "24-36",
      "激昂",
      "热血",
      "动作",
      "革命",
      "战争",
      "历史",
      "电视剧"
    ]
  },
  "飞虎队": {
    "language": [
      "普通话"
    ],
    "similarity": 12.0,
    "country": [
      "中国大陆"
    ],
    "tag": [
      "上星剧",
      "45-55",
      "36-45",
      "24-36",
      "激昂",
      "热血",
      "动作",
      "革命",
      "战争",
      "历史",
      "电视剧"
    ]
  },
  "伟大的转折": {
    "language": [
      "普通话"
    ],
    "similarity": 12.0,
    "country": [
      "中国大陆"
    ],
    "tag": [
      "上星剧",
      "55-70",
      "45-55",
      "36-45",
      "24-36",
      "激昂",
      "热血",
      "革命",
      "战争",
      "历史",
      "电视剧"
    ]
  },
  "太行英雄传": {
    "language": [
      "普通话"
    ],
    "similarity": 12.0,
    "country": [
      "中国大陆"
    ],
    "tag": [
      "上星剧",
      "45-55",
      "36-45",
      "24-36",
      "激昂",
      "热血",
      "动作",
      "革命",
      "战争",
      "历史",
      "电视剧"
    ]
  },
  "雪豹": {
    "language": [
      "普通话"
    ],
    "similarity": 12.0,
    "country": [
      "中国大陆"
    ],
    "tag": [
      "上星剧",
      "55-70",
      "45-55",
      "36-45",
      "24-36",
      "激昂",
      "革命",
      "军旅",
      "战争",
      "历史",
      "电视剧"
    ]
  },
  "宜昌保卫战": {
    "actor": [
      "侯勇"
    ],
    "similarity": 11.0,
    "language": [
      "普通话"
    ],
    "country": [
      "中国大陆"
    ],
    "tag": [
      "上星剧",
      "45-55",
      "36-45",
      "24-36",
      "激昂",
      "革命",
      "战争",
      "历史",
      "电视剧"
    ]
  }
}

上位は、入力との相関性が高い映画やテレビシリーズで、類似性や属性も同じで、効果は悪くないようです。

結論

これは、レコメンデーションシステムでの知識グラフの適用を体験するための単なるデモです。

最後に、元のプロジェクトの作者に改めて感謝したいと思います。彼の努力によって構築されたフレームワークがなければ、実践の第一歩を踏み出すことは困難です。

元のプロジェクトのアドレスをもう一度入力してくださいhttps//github.com/qiu997018209/KnowledgeGraph

 

 

おすすめ

転載: blog.csdn.net/qq_37475168/article/details/100709201