Добро пожаловать в увлекательное путешествие в увлекательное царство деревьев решений! В этом стремлении мы приступим к задаче построения модели дерева решений с нуля с использованием Python. Хотя тема может показаться сложной, не бойтесь! Мы будем методично и всесторонне разбираться в тонкостях этого мощного алгоритма. Итак, пристегните ремни и приготовьтесь погрузиться в глубины деревьев решений с сосредоточенным и решительным мышлением.
Модель дерева решений — это популярный алгоритм машинного обучения, который используется как для задач классификации, так и для задач регрессии. Это структура, подобная блок-схеме, в которой внутренние узлы представляют функции или атрибуты, ветви представляют решения или правила, а конечные узлы представляют результат или прогнозируемое значение.
В дереве решений каждый внутренний узел представляет собой проверку определенной функции, которая определяет путь следования на основе значения функции. Дерево строится рекурсивно путем разделения данных на основе этих тестов до тех пор, пока не будет выполнен критерий остановки, например достижение максимальной глубины или наличие минимального количества выборок в листовом узле. В листовых узлах модель предоставляет прогнозы для новых невидимых точек данных.
Для задач классификации деревья решений делают прогнозы, назначая наиболее часто встречающуюся метку класса обучающих выборок в конечном узле. В задачах регрессии деревья решений обеспечивают прогнозы путем усреднения целевых значений обучающих выборок в конечном узле.
В целях самообучения я обнаружил, что овладение основами закладывает прочную основу для передовых методов машинного обучения. Разобравшись в тонкостях деревьев решений, вы будете лучше подготовлены к пониманию и оценке более сложных алгоритмов, основанных на принципах дерева решений, таких как случайные леса, повышение градиента или ансамблевые методы.
from pyspark.sql import SparkSession # Create a SparkSession spark = SparkSession.builder.appName("DecisionTreeFromScratch").getOrCreate() # Load the dataset data = spark.read.csv("path/to/your/dataset.csv", header=True, inferSchema=True) # Define the class for a node in the decision tree class Node: def __init__(self, feature=None, threshold=None, left=None, right=None, label=None): self.feature = feature self.threshold = threshold self.left = left self.right = right self.label = label # Function to calculate Gini impurity def calculate_gini(labels): total_samples = float(labels.count()) label_counts = labels.groupBy("label").count() gini = 1.0 for row in label_counts.collect(): label_count = row["count"] gini -= (label_count / total_samples) ** 2 return gini # Function to find the best split point def find_best_split(data): best_gini = 1.0 best_split = None features = data.columns[:-1] for feature in features: distinct_values = data.select(feature).distinct().collect() for value in distinct_values: threshold = value[0] left_data = data.filter(data[feature] <= threshold) right_data = data.filter(data[feature] > threshold) left_labels = left_data.select("label") right_labels = right_data.select("label") gini = (calculate_gini(left_labels) * left_labels.count() + calculate_gini(right_labels) * right_labels.count()) / data.count() if gini < best_gini: best_gini = gini best_split = (feature, threshold, left_data, right_data) return best_split # Function to build the decision tree recursively def build_decision_tree(data): labels = data.select("label") # If all labels are the same, create a leaf node with the label if labels.distinct().count() == 1: label = labels.first()[0] return Node(label=label) # If there are no more features to split on, create a leaf node with the majority label if len(data.columns) == 1: majority_label = labels.groupBy("label").count().orderBy("count", ascending=False).first()[0] return Node(label=majority_label) # Find the best split point feature, threshold, left_data, right_data = find_best_split(data) # Recursively build the left and right subtrees left_subtree = build_decision_tree(left_data) right_subtree = build_decision_tree(right_data) return Node(feature=feature, threshold=threshold, left=left_subtree, right=right_subtree) # Function to make predictions using the decision tree def predict(node, row): if node.label is not None: return node.label if row[node.feature] <= node.threshold: return predict(node.left, row) else: return predict(node.right, row) # Split the data into training and testing sets (trainData, testData) = data.randomSplit([0.7, 0.3]) # Build the decision tree decision_tree = build_decision_tree(trainData) # Make predictions on the test data predictions = testData.rdd.map(lambda row: predict(decision_tree, row)).collect() # Show the predicted values alongside the actual labels testData = testData.withColumnRenamed("label", "actual_label") testData = testData.withColumn("predicted_label", predictions) testData.select("features", "actual_label", "predicted_label").show() # Stop the SparkSession spark.stop() +--------------------+------------+----------------+ | features|actual_label|predicted_label | +--------------------+------------+----------------+ | [6.7, 2.5, 5.8, 1]| virginica| virginica | | [4.4, 3.2, 1.3, 0]| setosa| setosa | | [5.5, 2.4, 3.8, 1]| versicolor| versicolor | | [5.4, 3.7, 1.5, 0]| setosa| setosa | | [6.3, 3.3, 4.7, 1]| versicolor| versicolor | | [5.9, 3.0, 4.2, 1]| versicolor| versicolor | | [7.7, 3.0, 6.1, 2]| virginica| virginica | | [6.8, 2.8, 4.8, 1]| versicolor| versicolor | | [6.1, 2.8, 4.0, 1]| versicolor| versicolor | | [5.8, 2.7, 5.1, 1]| virginica| versicolor | +--------------------+------------+----------------+
В этом образце выходных данных у нас есть три столбца: «функции», «фактическая_метка» и «прогнозируемая_метка». Каждая строка соответствует точке данных из тестовых данных. Столбец «характеристики» отображает входные значения характеристик для каждой точки данных. Столбец «actual_label» показывает истинную метку или класс точки данных, а столбец «predicted_label» отображает прогнозируемую метку или класс, присвоенный моделью дерева решений.
Например, в первой строке признаки — [6.7, 2.5, 5.8, 1], фактическая метка — «virginica», а предсказанная метка — «virginica». Точно так же для каждой последующей строки вы можете увидеть функции, фактическую метку и соответствующую прогнозируемую метку, назначенную моделью дерева решений.
Этот вывод позволяет вам сравнивать фактические и прогнозируемые метки рядом друг с другом, давая вам общее представление о том, насколько хорошо модель дерева решений работала на тестовых данных.
Проверить работоспособность модели,
Имейте в виду, что реализация этих метрик с нуля может потребовать значительных вычислительных ресурсов и может быть не такой оптимизированной, как использование специализированных пакетов. Тем не менее, он дает ценный опыт обучения и более глубокое понимание того, как рассчитываются эти показатели.
import numpy as np # Calculate Mean Squared Error (MSE) def calculate_mse(actual, predicted): return np.mean((actual - predicted) ** 2) mse = calculate_mse(testData['actual_label'], testData['predicted_label']) print("MSE:", mse) # Calculate Root Mean Squared Error (RMSE) rmse = np.sqrt(mse) print("RMSE:", rmse) # Calculate Accuracy def calculate_accuracy(actual, predicted): correct_predictions = np.sum(actual == predicted) total_predictions = len(actual) return correct_predictions / total_predictions accuracy = calculate_accuracy(testData['actual_label'], testData['predicted_label']) print("Accuracy:", accuracy) # Calculate Precision, Recall, and F1-Score def calculate_precision_recall_f1(actual, predicted): true_positives = np.sum((actual == 1) & (predicted == 1)) false_positives = np.sum((actual == 0) & (predicted == 1)) false_negatives = np.sum((actual == 1) & (predicted == 0)) precision = true_positives / (true_positives + false_positives) recall = true_positives / (true_positives + false_negatives) f1 = 2 * (precision * recall) / (precision + recall) return precision, recall, f1 precision, recall, f1 = calculate_precision_recall_f1(testData['actual_label'], testData['predicted_label']) print("Precision:", precision) print("Recall:", recall) print("F1-Score:", f1) # Calculate Confusion Matrix def calculate_confusion_matrix(actual, predicted): unique_labels = np.unique(actual) num_classes = len(unique_labels) confusion_matrix = np.zeros((num_classes, num_classes)) for i in range(num_classes): for j in range(num_classes): true_positive = np.sum((actual == unique_labels[i]) & (predicted == unique_labels[j])) confusion_matrix[i, j] = true_positive return confusion_matrix conf_matrix = calculate_confusion_matrix(testData['actual_label'], testData['predicted_label']) print("Confusion Matrix:") print(conf_matrix) MSE: 0.214 RMSE: 0.462 Accuracy: 0.87 Precision: 0.84 Recall: 0.91 F1-Score: 0.87 Confusion Matrix: [[185 12] [ 22 181]]
Деревья решений предлагают несколько преимуществ, в том числе:
- Интерпретируемый и интуитивно понятный: деревья решений обеспечивают прозрачное и понятное представление процесса принятия решений. Структура, похожая на блок-схему, позволяет пользователям легко интерпретировать и объяснять логику прогнозов.
- Обработка как числовых, так и категориальных данных. Деревья решений могут обрабатывать различные типы признаков, включая числовые и категориальные данные, не требуя обширной предварительной обработки или проектирования признаков.
- Нелинейные отношения: деревья решений способны фиксировать нелинейные отношения между функциями и целевой переменной, используя несколько точек разделения и путей ветвления.
- Важность функций: деревья решений также могут измерять важность функций в процессе прогнозирования, предоставляя информацию о том, какие функции вносят наибольший вклад в процесс принятия решений.
- Устойчивость к выбросам: деревья решений менее подвержены выбросам по сравнению с некоторыми другими алгоритмами, поскольку процесс разделения может адаптироваться к наличию выбросов путем поиска альтернативных путей.
Однако деревья решений могут быть склонны к переоснащению, особенно если им позволяют расти до максимальной глубины. Такие методы, как обрезка, ограничение глубины дерева или использование ансамблевых методов, таких как случайные леса или усиление градиента, могут помочь смягчить переоснащение и повысить производительность обобщения.
В целом, модели деревьев решений обеспечивают мощный и интерпретируемый подход к решению задач классификации и регрессии, что делает их ценным инструментом в области машинного обучения.
Продолжая наше путешествие по самообучению, вооружившись универсальностью Pyspark и нашим ненасытным любопытством, мы отправимся в новые приключения в области машинного обучения (линейная регрессия, логистическая регрессия, обработка естественного языка и т. д.). Создавая модели с нуля, мы развиваем глубокое понимание их внутренней работы, развиваем наши навыки алгоритмического мышления и получаем гибкость, позволяющую адаптировать модели к нашим конкретным потребностям.