Skip to content

first solution knn #1

@vshandrikov

Description

@vshandrikov

import numpy as np
from collections import Counter
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris, load_digits
iris = load_iris()
digits=load_digits()

class AdvancedKNN:
"""
Расширенный KNN-классификатор с различными функциями весов
"""

def __init__(self, k=5, metric='euclidean', weighting='uniform', 
            weight_function='inverse', epsilon=1e-6, p=2):
    """
    Инициализация классификатора
    
    Parameters:
    -----------
    k : int, количество соседей
    metric : str, метрика расстояния ('euclidean', 'manhattan', 'minkowski', 'chebyshev')
    weighting : str, тип взвешивания ('uniform', 'weighted')
    weight_function : str, функция веса ('inverse', 'inverse_square', 'exponential', 'gaussian')
    epsilon : float, малая константа для избежания деления на ноль
    p : float, параметр для метрики Минковского
    """
    self.k = k
    self.metric = metric
    self.weighting = weighting
    self.weight_function = weight_function
    self.epsilon = epsilon
    self.p = p
    self.X_train = None
    self.y_train = None
    
def _calculate_distance(self, x1, x2):
    """Вычисление расстояния между двумя точками по различным метрикам"""
    diff = x1 - x2
    
    if self.metric == 'euclidean':
        # Евклидово расстояние (L2)
        return np.sqrt(np.sum(diff ** 2))
        
    elif self.metric == 'manhattan':
        # Манхэттенское расстояние (L1)
        return np.sum(np.abs(diff))
        
    elif self.metric == 'minkowski':
        # Метрика Минковского (обобщение Lp)
        return np.sum(np.abs(diff) ** self.p) ** (1.0 / self.p)
        
    elif self.metric == 'chebyshev':
        # Расстояние Чебышева (L∞)
        return np.max(np.abs(diff))
        
    elif self.metric == 'cosine':
        # Косинусное расстояние (1 - косинусная близость)
        dot_product = np.dot(x1, x2)
        norm1 = np.linalg.norm(x1)
        norm2 = np.linalg.norm(x2)
        if norm1 == 0 or norm2 == 0:
            return 1.0
        return 1.0 - dot_product / (norm1 * norm2)
        
    else:
        raise ValueError(f"Метрика '{self.metric}' не поддерживается")

def _calculate_weight(self, distance):
    """Вычисление веса соседа на основе расстояния"""
    if self.weight_function == 'inverse':
        # Обратная пропорциональность: w = 1/(d + ε)
        return 1.0 / (distance + self.epsilon)
        
    elif self.weight_function == 'inverse_square':
        # Обратный квадрат: w = 1/(d² + ε)
        return 1.0 / (distance ** 2 + self.epsilon)
        
    elif self.weight_function == 'exponential':
        # Экспоненциальное убывание: w = exp(-d)
        return np.exp(-distance)
        
    elif self.weight_function == 'gaussian':
        # Гауссово ядро: w = exp(-d²/2σ²)
        sigma = 1.0  # параметр ширины ядра
        return np.exp(-distance ** 2 / (2 * sigma ** 2))
        
    elif self.weight_function == 'linear':
        # Линейное убывание: w = max(0, 1 - d/d_max)
        # (требует знания максимального расстояния)
        return max(0, 1.0 - distance)
        
    else:
        raise ValueError(f"Функция веса '{self.weight_function}' не поддерживается")

def _get_k_neighbors(self, x):
    """Поиск k ближайших соседей для точки x"""
    distances = []
    
    for i, x_train in enumerate(self.X_train):
        dist = self._calculate_distance(x, x_train)
        distances.append((dist, i))
    
    distances.sort(key=lambda x: x[0])
    return distances[:self.k]

def _predict_one(self, x):
    """Предсказание для одной точки"""
    neighbors = self._get_k_neighbors(x)
    
    if self.weighting == 'uniform':
        # Равномерное взвешивание
        neighbor_labels = [self.y_train[idx] for _, idx in neighbors]
        most_common = Counter(neighbor_labels).most_common(1)
        return most_common[0][0]
        
    elif self.weighting == 'weighted':
        # Взвешенное голосование
        label_weights = {}
        
        for dist, idx in neighbors:
            label = self.y_train[idx]
            weight = self._calculate_weight(dist)
            
            if label in label_weights:
                label_weights[label] += weight
            else:
                label_weights[label] = weight
        
        return max(label_weights.items(), key=lambda x: x[1])[0]
    
    else:
        raise ValueError(f"Тип взвешивания '{self.weighting}' не поддерживается")

def fit(self, X, y):
    """Обучение модели"""
    self.X_train = np.array(X)
    self.y_train = np.array(y)
    return self

def predict(self, X):
    """Предсказание меток для новых данных"""
    predictions = []
    for x in X:
        pred = self._predict_one(x)
        predictions.append(pred)
    return np.array(predictions)

def analyze_weight_functions():
"""Анализ различных функций весов"""
distances = np.linspace(0.1, 5, 100)

weight_functions = {
    'inverse': lambda d: 1.0 / (d + 1e-6),
    'inverse_square': lambda d: 1.0 / (d ** 2 + 1e-6),
    'exponential': lambda d: np.exp(-d),
    'gaussian': lambda d: np.exp(-d ** 2 / 2),
    'linear': lambda d: np.maximum(0, 1 - d/5)
}

plt.figure(figsize=(12, 8))

for name, func in weight_functions.items():
    weights = func(distances)
    plt.plot(distances, weights, label=name, linewidth=2)

plt.xlabel('Расстояние (d)', fontsize=12)
plt.ylabel('Вес соседа (w)', fontsize=12)
plt.title('Сравнение функций весов для KNN', fontsize=14)
plt.grid(True, alpha=0.3)
plt.legend(fontsize=11)
plt.tight_layout()
plt.show()

print("\nАнализ функций весов:")
print("-" * 60)
print("1. inverse (1/d):")
print("   - Быстро убывает при малых расстояниях")
print("   - Чувствительна к очень близким соседям")
print("   - Может быть нестабильной при d → 0")

print("\n2. inverse_square (1/d²):")
print("   - Убывает быстрее, чем 1/d")
print("   - Еще сильнее подчеркивает близких соседей")
print("   - Подавляет влияние дальних соседей")

print("\n3. exponential (exp(-d)):")
print("   - Плавное убывание")
print("   - Никогда не достигает нуля")
print("   - Хорошо работает при нормальном распределении ошибок")

print("\n4. gaussian (exp(-d²/2σ²)):")
print("   - Очень быстрое убывание")
print("   - Эффективно подавляет далекие точки")
print("   - Параметр σ контролирует ширину окрестности")

print("\n5. linear (max(0, 1-d/d_max)):")
print("   - Линейное убывание до нуля при d_max")
print("   - Простая интерпретация")
print("   - Требует знания или оценки максимального расстояния")

def test_weight_functions(dataset_name, X, y):
"""Тестирование различных функций весов на датасете"""
print(f"\n{'='*60}")
print(f"Тестирование функций весов на датасете {dataset_name}")
print('='*60)

# Разделение данных
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42, stratify=y
)

# Нормализация
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

results = {}
weight_functions = ['inverse', 'inverse_square', 'exponential', 'gaussian']

for wf in weight_functions:
    model = AdvancedKNN(k=5, metric='euclidean', 
                    weighting='weighted', weight_function=wf)
    model.fit(X_train_scaled, y_train)
    y_pred = model.predict(X_test_scaled)
    accuracy = accuracy_score(y_test, y_pred)
    results[wf] = accuracy
    
    print(f"{wf}: Точность = {accuracy:.4f}")

# Сравнение с равномерным взвешиванием
model_uniform = AdvancedKNN(k=5, metric='euclidean', weighting='uniform')
model_uniform.fit(X_train_scaled, y_train)
y_pred_uniform = model_uniform.predict(X_test_scaled)
accuracy_uniform = accuracy_score(y_test, y_pred_uniform)

print(f"\nРавномерное взвешивание: Точность = {accuracy_uniform:.4f}")

return results

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions