Source code for fuzzyops.prediction.linear

import torch
from fuzzyops.fuzzy_numbers import Domain, FuzzyNumber
from typing import List, Tuple


[docs] def fuzzy_distance(fn0: 'TriFNum', fn1: 'TriFNum') -> float: """ Calculates the distance between two triangular fuzzy numbers Args: fn0 (TriFNum): The first triangular fuzzy number fn1 (TriFNum): The second triangular fuzzy number Returns: float: The distance between two fuzzy numbers """ a0, b0 = fn0.a, fn0.b a1, b1 = fn1.a, fn1.b left_integral = ((b1 - b0 - a1 + a0) ** 2) / 3 - 2 * a0 * (((b1 - b0 - a1 + a0)) / 2 + a1) + a1 * ( b1 - b0 - a1 + a0) + a1 ** 2 + a0 ** 2 a0, b0 = fn0.b, fn0.c a1, b1 = fn1.b, fn1.c right_integral = ((b1 - b0 - a1 + a0) ** 2) / 3 - 2 * a0 * (((b1 - b0 - a1 + a0)) / 2 + a1) + a1 * ( b1 - b0 - a1 + a0) + a1 ** 2 + a0 ** 2 dist = (left_integral + right_integral) ** 0.5 if isinstance(dist, float): return dist return dist.item()
[docs] def integral_of_product(a_0: torch.Tensor, b_0: torch.Tensor, a_1: torch.Tensor, b_1: torch.Tensor) -> torch.Tensor: """ Calculates the integral of the product of two intervals Args: a_0 (torch.Tensor): The beginning of the first interval b_0 (torch.Tensor): The end of the first interval a_1 (torch.Tensor): The beginning of the second interval b_1 (torch.Tensor): The end of the second interval Returns: torch.Tensor: The result of the integral of the product """ return (1 * (((2 * b_0 - 2 * a_0) * b_1 - 2 * a_1 * b_0 + 2 * a_0 * a_1) * 1 ** 2 + ( 3 * a_0 * b_1 + 3 * a_1 * b_0 - 6 * a_0 * a_1) * 1 + 6 * a_0 * a_1)) / 6
[docs] def integrate_sum_squares(a_0: torch.Tensor, b_0: torch.Tensor, a_1: torch.Tensor, b_1: torch.Tensor) -> torch.Tensor: """ Calculates the integral of the sum of the squares of two intervals Args: a_0 (torch.Tensor): The beginning of the first interval b_0 (torch.Tensor): The end of the first interval a_1 (torch.Tensor): The beginning of the second interval b_1 (torch.Tensor): The end of the second interval Returns: torch.Tensor: The result of the integral of the sum of squares """ return ((b_1 - a_1) ** 2) / 3 + ((b_0 - a_0) ** 2) / 3 + a_1 * (b_1 - a_1) + a_0 * (b_0 - a_0) + a_1 ** 2 + a_0 ** 2
[docs] def convert_fuzzy_number_for_lreg(n: FuzzyNumber) -> 'TriFNum': """ Converts a fuzzy number of the FuzzyNumber class to a triangular fuzzy number TriNum Args: n (FuzzyNumber): A fuzzy number for conversion Returns: TriFNum: A transformed triangular fuzzy number """ vals = n.values first_increasing_idx = (vals[1:] > vals[:-1]).nonzero(as_tuple=True)[0][0] + 1 last_zero_before_increase = (vals[:first_increasing_idx] == 0).nonzero(as_tuple=True)[0][-1].item() max_idx = vals.argmax().item() first_zero_after_peak = (vals[max_idx + 1:] == 0).nonzero(as_tuple=True)[0][0] + max_idx + 1 a, b, c = n.domain.x[last_zero_before_increase], n.domain.x[max_idx], n.domain.x[first_zero_after_peak] return TriFNum(n.domain, a, b, c)
[docs] class TriFNum: """ Represents a triangular fuzzy number (TriFNum) for the fuzzy linear regression method Attributes: domain (Domain): The area of definition of a fuzzy number a (torch.Tensor): The left end of the triangle b (torch.Tensor): The peak of the triangle c (torch.Tensor): The right end of the triangle """ def __init__(self, domain: Domain, a: torch.Tensor, b: torch.Tensor, c: torch.Tensor): def left_mf(x): k = 1 / (b - a) m = -a / (b - a) return k * x + m def right_mf(x): k = 1 / (b - c) m = -c / (b - c) return k * x + m self.left_mf = left_mf self.right_mf = right_mf self.domain = domain self.a = a self.b = b self.c = c
[docs] def values(self) -> torch.Tensor: """ Calculates the values of a fuzzy number on a given definition area Returns: torch.Tensor: The values of the fuzzy number in the definition area """ left = self.left_mf(self.domain.x).clip(0, 1) right = self.right_mf(self.domain.x).clip(0, 1) values = torch.zeros_like(self.domain.x) p0 = torch.where(left < 1)[0] p1 = torch.where((left >= 1) & (right >= 1))[0] p2 = torch.where(right < 1)[0] values[p0] = left[p0] values[p1] = 1 values[p2] = right[p2] return values
def __add__(self, other): """ Defines the addition operation for triangular fuzzy numbers Args: other (TriFNum | int | float): Another operand for addition Returns: TriFNum: The result of the addition Raises: NotImplementedError: If there are other types of operands """ if isinstance(other, TriFNum): return TriFNum(self.domain, self.a + other.a, self.b + other.b, self.c + other.c) elif isinstance(other, int) or isinstance(other, float): return TriFNum(self.domain, self.a + other, self.b + other, self.c + other) else: raise NotImplementedError('can only add TriFNums or real numbers') def __sub__(self, other): """ Defines the subtraction operation for triangular fuzzy numbers Args: other (TriFNum): Another operand for subtraction Returns: TriFNum: The result of the subtraction Raises: NotImplementedError: If the operand type is different """ if isinstance(other, TriFNum): return TriFNum(self.domain, self.a - other.a, self.b - other.b, self.c - other.c) else: raise NotImplementedError('can only substract TriFNums') def __mul__(self, other): """ Defines the multiplication operation for triangular fuzzy numbers Args: other (int | float): Another operand for multiplication Returns: TriFNum: The result of the multiplication Raises: NotImplementedError: If the operand type is different """ if isinstance(other, int) or isinstance(other, float): if other > 0: return TriFNum(self.domain, self.a * other, self.b * other, self.c * other) else: return TriFNum(self.domain, self.c * other, self.b * other, self.a * other) else: raise NotImplementedError('can only multiply by real number')
[docs] def integrate_left(self) -> torch.Tensor: """ Calculates the integral for the left side of a triangular fuzzy number Returns: torch.Tensor: The value of the integral """ return (self.b - self.a) / 2 + self.a
[docs] def integrate_right(self) -> torch.Tensor: """ Calculates the integral for the right side of a triangular fuzzy number Returns: torch.Tensor: The value of the integral """ return (self.b - self.c) / 2 + self.b
[docs] def integrate(self) -> torch.Tensor: """ Calculates the integral (total area) under the curve of a triangular fuzzy number Returns: torch.Tensor: The value of the integral """ return self.integrate_right() - self.integrate_left()
[docs] def to_fuzzy_number(self) -> FuzzyNumber: """ Converts a triangular fuzzy number into its fuzzy representation Returns: FuzzyNumber: A fuzzy number created from a triangular fuzzy number """ return self.domain.create_number("triangular", self.a, self.b, self.c)
[docs] def fit_fuzzy_linear_regression(X: List[TriFNum], Y: List[TriFNum]) -> Tuple[float, float]: """ Implements fuzzy linear regression using triangular fuzzy numbers This function finds coefficients a and b for linear regression that minimize the distance between the predicted fuzzy values and the actual fuzzy values Implemented on the basis of materials https://ej.hse.ru/data/2014/09/03/1316474700/%D0%A8%D0%B2%D0%B5%D0%B4%D0%BE%D0%B2.pdf Args: X (List[TriFNum]): A list of triangular fuzzy numbers representing independent variables (features) Y (List[TriFNum]: A list of triangular fuzzy numbers representing the dependent variable (target variable) Returns: Tuple[float, float]: Coefficients a and b of linear regression, where a is the angular coefficient and b is the free term Raises: ValueError: If the lengths of the X and Y lists do not match Notes: Integration and calculation of various moments based on fuzzy numbers are used to perform calculations """ n = len(X) if isinstance(X[0], FuzzyNumber): # X = deepcopy(X) for i in range(n): X[i] = convert_fuzzy_number_for_lreg(X[i]) if isinstance(Y[0], FuzzyNumber): # Y = deepcopy(Y) for i in range(n): Y[i] = convert_fuzzy_number_for_lreg(Y[i]) I1 = sum([integral_of_product(Y[i].a, Y[i].b, X[i].a, X[i].b) for i in range(n)]) I2 = sum([integral_of_product(Y[i].b, Y[i].c, X[i].b, X[i].c) for i in range(n)]) J1 = sum([integral_of_product(Y[i].a, Y[i].b, X[i].b, X[i].c) for i in range(n)]) J2 = sum([integral_of_product(Y[i].b, Y[i].c, X[i].a, X[i].b) for i in range(n)]) K1 = sum([integral_of_product(X[i].a, X[i].b, X[i].a, X[i].b) for i in range(n)]) K2 = sum([integral_of_product(X[i].b, X[i].c, X[i].b, X[i].c) for i in range(n)]) L1 = sum([X[i].integrate_left() for i in range(n)]) L2 = sum([X[i].integrate_right() for i in range(n)]) M1 = sum([Y[i].integrate_left() for i in range(n)]) M2 = sum([Y[i].integrate_right() for i in range(n)]) N = sum([integrate_sum_squares(Y[i].a, Y[i].b, Y[i].b, Y[i].c) for i in range(n)]) a_pos = max(0, (2 * n * (I1 + I2) - (L1 + L2) * (M1 + M2)) / (2 * n * (K1 + K2) - (L1 + L2) ** 2)) a_neg = min(0, (2 * n * (J1 + J2) - (L1 + L2) * (M1 + M2)) / (2 * n * (K1 + K2) - (L1 + L2) ** 2)) b_pos = 1 / 2 / n * (M1 + M2) - 1 / 2 / n * a_pos * (L1 + L2) b_neg = 1 / 2 / n * (M1 + M2) - 1 / 2 / n * a_neg * (L1 + L2) H_pos = a_pos ** 2 * (K1 + K2) + 2 * a_pos * b_pos * (L1 + L2) + 2 * n * b_pos ** 2 - 2 * a_pos * ( I1 + I2) - 2 * b_pos * (M1 + M2) + N H_neg = a_neg ** 2 * (K1 + K2) + 2 * a_neg * b_neg * (L1 + L2) + 2 * n * b_neg ** 2 - 2 * a_neg * ( J1 + J2) - 2 * b_neg * (M1 + M2) + N if H_pos < H_neg: a, b = a_pos, b_pos else: a, b = a_neg, b_neg a, b = a.item(), b.item() errors = [] for i in range(len(X)): errors.append(fuzzy_distance(X[i] * a + b, Y[i])) return a, b, (sum(errors) / n) ** 0.5