Как я могу добавить новый узел JSON с вложенным неизвестным количеством уровней в существующий файл JSON? - PullRequest
2 голосов
/ 08 февраля 2020

Недавно я наткнулся на следующее изображение, описывающее консольное приложение, которое пытается угадать животное, о котором думает пользователь, задавая ряд вопросов и обновляя задаваемые вопросы, если оно угадывает неправильно:

algo

Несмотря на то, что ничего не знал о машинном обучении, я подумал, что это довольно простая программа для репликации с использованием деревьев решений, поэтому я собрал код ниже python:

import json

json_file = open("DecisionTree1.json", "r")
decision_tree = json.loads(json_file.read())
partial_decision_tree = decision_tree["start"]


def get_user_input(prompt, validation):
    if validation == "yes_no":
        print(prompt)
        while True:
            answer = input()
            if answer.lower() not in ('yes', 'no'):
                print("Please enter 'Yes' or 'No'")
            else:
                return answer.lower()
    elif validation == "not_empty":
        while True:
            answer = input(prompt + "\n")
            if answer != "":
                return answer.lower()


def create_new_node(guess):
    correct_answer = get_user_input("What animal were you thinking of?", "not_empty")
    new_question = get_user_input("Enter a question for which the answer is 'Yes' for " + correct_answer + " and 'No' for " + guess, "not_empty")
    new_node = json.loads('{"question": "' + new_question + '","children":{"yes": {"question": "Is it a ' + correct_answer + '?","children": null},"no": {"question": "Is it a rabbit?","children": null}}}')
    return json.dumps(new_node)


answer_array = list()


while partial_decision_tree["children"]:
    answer = get_user_input(partial_decision_tree["question"], "yes_no")
    answer_array.append(answer)
    partial_decision_tree = partial_decision_tree["children"][answer]

if get_user_input(partial_decision_tree["question"], "yes_no") == "no":
    select_conditions = '["start"]'
    for answer in answer_array:
        select_conditions += '["children"]["' + answer + '"]'
    query = "decision_tree" + select_conditions + " = '" + create_new_node(partial_decision_tree["question"].split(" ")[-1][0:len(partial_decision_tree["question"].split(" ")[-1])-1]) + "'"
    exec(query)

Файл JSON DecisionTree1. json содержит следующие данные, которые должны представлять (очень маленькое) дерево решений:

{
    "start":
    {
        "question": "Is it smaller than a bicycle?",
        "children": 
        {
            "yes": {
                "question": "Is it a rabbit?",
                "children": null
            },
            "no": {
                "question": "Is it an elephant?",
                "children": null
            }
        }
    }
}

Идея должна состоять в том, что если пользователь угадывает неправильно, то конечный узел, на который программа смотрит, когда она делает свое предположение, должен быть заменен новым внутренним узлом, который обеспечивает дополнительный уровень фильтрации при угадывании программ.

В терминах JSON, это означает:

  1. Замена атрибута «question» узла, содержащего t текущее предположение с вопросом, который пользователь указал
  2. Обновление атрибута "children" узла, чтобы вместо того, чтобы быть нулевым, содержало два новых узла, каждый из которых составляет предположение (то есть листовой узел)

У меня вопрос как я могу таким образом обновить JSON в файле?

В настоящее время переменная query в моем python обновляет JSON, так что значение атрибута "children" становится string , а не двумя подчиненными узлами.

EDIT: После комментария Мартино, вот пример как должен выглядеть JSON после обновления:

Предположим, что пользователь думает о черепахе. Как бы то ни было, программа будет неправильно угадывать их животное, чтобы быть кроликом. Когда их просят «напечатать вопрос, для которого ответ« да »для черепахи и« нет »для кролика», они могут задать вопрос «Есть ли у него раковина?». Существующий JSON (как показано выше) должен затем стать

{
    "start":
    {
        "question": "Is it smaller than a bicycle?",
        "children": 
        {
            "yes": {
                "question": "Does it have a shell?",
                "children":
                {
                    "yes": {
                        "question": "Is it a tortoise?",
                        "children": null
                    },
                    "no": {
                        "question": "Is it a rabbit?",
                        "children": null
                    }
                }
            },
            "no": {
                "question": "Is it an elephant?",
                "children": null
            }
        }
    }
}

1 Ответ

2 голосов
/ 08 февраля 2020

Ваша проблема c действительно довольно интересна. Я попробовал, но прежде чем объяснять решение, я хотел бы указать несколько концептуальных проблем, которые я увидел во время работы над вашим кодом. Вот изменения, которые я сделал:

  1. Ваша JSON структура , на мой взгляд, не совсем легко разобрать. Я выбрал что-то более удобное, удалив «дочерний» узел, так как всегда есть только два возможных варианта: да или нет

  2. Не запутайтесь: после загрузки сообщение JSON, такое как ваше, является не чем иным, как простым dict с другими встроенными dict. Вам не нужно ничего писать в файле JSON самостоятельно, потому что Python знает, как переводить такие структуры.

  3. Использование итерации в древовидной иерархии известно как плохой выбор с точки зрения производительности и простоты использования. Использовать рекурсию очень просто, и я настоятельно рекомендую вам узнать, как она может быть эффективной в некоторых случаях, таких как этот

  4. Я нашел подтверждение быть побочным эффектом вашего итеративного процесса. По моему мнению, вам не нужен этот дополнительный параметр validation везде, но вы можете свободно интегрировать его, когда он вам действительно понадобится

  5. Я "оптимизировал" ваш код, добавив некоторые вещи, такие как if __name__ == "__main__", цель которого - проверить, запущен ли файл Python как модуль или встроен в другое решение. Вы также неправильно использовали директиву open для чтения и записи файлов. Я исправил это для вас

Надеюсь, вы узнаете несколько хитростей, прочитав мое решение, по крайней мере, поэтому я пишу его. Я не претендую на то, что знаю все, поэтому могут быть и ошибки, но это должно помочь вам продвинуться вперед с вашим проектом. Обычно я ожидаю, что вы разделите обучающую часть сценария на другие функции.

Основной сценарий:

import json

def get_question_node(tree, question):
    """
    Finds the node which contains the given question, recursively.

    :param tree: the tree level from where to start
    :param question: the question to find
    """
    if 'question' in tree and tree['question'] == question:
        # If the current node contains the question, return it
        return tree
    if 'yes' in tree and tree['yes']:
        # If there is a 'yes' node, check its underlying question
        result = get_question_node(tree['yes'], question)
        if result:
            return result
    if 'no' in tree and tree['no']:
        # If there is a 'no' node, check its underlying question
        result = get_question_node(tree['no'], question)
        if result:
            return result

def guess(tree):
    """
    Guesses based on a user's input and the given decision tree.

    :param tree: the current node to go through for the user
    """
    # A question has been found
    question = tree['question']
    answer = input(question + '\n').lower()

    if answer == 'yes':
        if tree['yes']:
            # There are sub questions, ask them
            return guess(tree['yes'])
        else:
            # Final question answered correctly, so we won!
            print('Yay, I guessed correctly!')
            return True
    elif answer == 'no':
        if tree['no']:
            # There are sub questions, ask them
            return guess(tree['no'])
        else:
            # No more question, create a new one and "learn"
            correct_answer = input('What animal were you thinking of?\n')
            new_question = input('Enter a question for which the answer is "Yes" for {}\n'.format(correct_answer))

            # Return to the caller to fix the order of questions
            return {
                'old': tree['question'],
                'new': new_question,
                'correct_answer': correct_answer,
            }
    else:
        # Answer needs to be yes or no, any other answer loops back to the question
        print('Sorry, I didn\'t get that... let\'s try again!')
        return guess(tree)


if __name__ == '__main__':
    # Load and parse the decision tree
    with open("DecisionTree1.json", "r") as json_file:
        decision_tree = json.loads(json_file.read())

    # Start guessing
    partial_decision_tree = decision_tree["start"]
    result = guess(partial_decision_tree)

    if type(result) == dict:
        # Ah! We learned something new, let's swap questions
        question_node = get_question_node(partial_decision_tree, result['old'])
        new_yes = {
            'question': 'Is it a {}?'.format(result['correct_answer']),
            'yes': None,
            'no': None,
        }
        new_no = {
            'question': question_node['question'],
            'yes': question_node['yes'],
            'no': question_node['no'],
        }

        question_node['no'] = new_no
        question_node['yes'] = new_yes
        question_node['question'] = result['new']

    # Persist changes to the decision tree file
    with open('DecisionTree1.json', 'w') as tree_file:
        json.dump(decision_tree, tree_file, indent=4)

И улучшенный DecisionTree1.json:

{
    "start":
    {
        "question": "Is it smaller than a bicycle?",
        "yes": {
            "question": "Is it a rabbit?",
            "yes": null,
            "no": null
        },
        "no": {
            "question": "Is it an elephant?",
            "yes": null,
            "no": null
        }
    }
}

Надеюсь, я ответил на ваш вопрос.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...