diff --git a/romanova_adelina_lab_2/README.md b/romanova_adelina_lab_2/README.md new file mode 100644 index 0000000..a2fcd7c --- /dev/null +++ b/romanova_adelina_lab_2/README.md @@ -0,0 +1,109 @@ +# Лабораторная работа №2. Вариант 21 + +## Тема: +Ранжирование признаков + +## Модели: + +- LinearRegression +- RandomizedLasso +- Recursive Feature Elimination (RFE) + +## Как запустить программу: +Установить *python, numpy, matplotlib, sklearn* +``` +python main.py --top_k=6 +``` + +## Какие технологии использовались: +Язык программирования Python, библиотеки numpy, matplotlib, sklearn + +Среда разработки VSCode + +# Что делает лабораторная работа: +Генерирует данные и обучает такие модели, как: ```LinearRegression, RandomizedLasso, Recursive Feature Elimination (RFE)``` + +Производиться ранжирование признаков с помощью моделей ```LinearRegression, RandomizedLasso, Recursive Feature Elimination (RFE)``` + +Отображение получившихся результатов: top_k самых важных признака по среднему значению, значения признаков для каждой модели + +Функция ```get_arguments()``` использует модуль argparse для обработки аргументов командной строки. В данном случае, скрипт ожидает аргумент --top_k, который по умолчанию установлен в 4. + +``` +def get_arguments(): + parser = argparse.ArgumentParser() + + parser.add_argument('--top_k', type=int, default=4, help='Кол-во самых выжных признаков') + + args = parser.parse_args() + return args +``` + +Функция ```data_gen()``` генерирует искусственные данные для регрессионной задачи. Здесь используется функция Фридмана для создания зависимости целевой переменной Y от признаков X. + +``` +def data_gen(): + # --- генерируем исходные данные: 750 строк-наблюдений и 14 столбцов-признаков --- + np.random.seed(0) + size = 750 + X = np.random.uniform(0, 1, (size, 14)) + #Задаем функцию-выход: регрессионную проблему Фридмана + Y = (10 * np.sin(np.pi*X[:,0]*X[:,1]) + 20*(X[:,2] - .5)**2 + + 10*X[:,3] + 5*X[:,4]**5 + np.random.normal(0,1)) + #Добавляем зависимость признаков + X[:,10:] = X[:,:4] + np.random.normal(0, .025, (size,4)) + + return X, Y +``` + +Функция ```rank_to_dict()```, которая преобразует ранги признаков в словарь, нормализуя их значения от 0 до 1. +``` +def rank_to_dict(ranks, names): + ranks = np.abs(ranks) + minmax = MinMaxScaler() + ranks = minmax.fit_transform(np.array(ranks).reshape(14,1)).ravel() + ranks = map(lambda x: round(x, 2), ranks) + return dict(zip(names, ranks)) +``` + +Функция ```print_sorted_data()``` печатает отсортированные оценки признаков. + +Функцию ```estimation()``` вычисляет средние оценки признаков на основе данных о рангах. + +В блоке ```if __name__=="__main__":``` сначала получает аргументы командной строки, затем генерирует данные, обучает линейную регрессию, применяет рекурсивное сокращение признаков и случайное Лассо, а затем оценивает и выводит наиболее важные признаки с использованием средних оценок. + +### Линейная регрессия (Linear Regression) + +Это простой метод машинного обучения, который используется для прогнозирования непрерывной переменной на основе одной или нескольких других переменных. В нашем случае линейная регрессия используется для обучения модели на данных, а затем коэффициенты модели используются для ранжирования важности признаков + +### Рекурсивное сокращение признаков (RFE) + +Это метод выбора признаков, который работает путем итеративного удаления признаков и переобучения модели, чтобы определить, какие признаки наиболее важны для предсказания. В коде RFE используется для обучения модели на данных, а затем ранжирование признаков модели используется для ранжирования важности признаков + +### Случайное Лассо (Randomized Lasso) + +Это метод регуляризации, который добавляет штраф к коэффициентам модели в зависимости от их величины. Это делается для предотвращения переобучения модели. В коде случайное Лассо используется для обучения модели на данных, а затем коэффициенты модели используются для ранжирования важности признаков + + +## Оценка работы моделей + +``` +{'x1': 0.33, 'x4': 0.23, 'x2': 0.2, 'x11': 0.2, 'x3': 0.17, 'x13': 0.16, 'x5': 0.06, 'x12': 0.06, 'x14': 0.04, 'x6': 0.01, 'x8': 0.01, 'x7': 0.0, 'x9': 0.0, 'x10': 0.0} +--------------------------------------------------------------------------- +Параметр - x1, значение - 0.33 +Параметр - x4, значение - 0.23 +Параметр - x2, значение - 0.2 +Параметр - x11, значение - 0.2 +--------------------------------------------------------------------------- + +Linear reg +[('x1', 1.0), ('x4', 0.69), ('x2', 0.61), ('x11', 0.59), ('x3', 0.51), ('x13', 0.48), ('x5', 0.19), ('x12', 0.19), ('x14', 0.12), ('x8', 0.03), ('x6', 0.02), ('x10', 0.01), ('x7', 0.0), ('x9', 0.0)] +RFE +[('x9', 1.0), ('x7', 0.86), ('x10', 0.71), ('x6', 0.57), ('x8', 0.43), ('x14', 0.29), ('x12', 0.14), ('x1', 0.0), ('x2', 0.0), ('x3', 0.0), ('x4', 0.0), ('x5', 0.0), ('x11', 0.0), ('x13', 0.0)] +RandomizedLasso +[('x4', 1.0), ('x2', 0.37), ('x1', 0.36), ('x5', 0.32), ('x6', 0.02), ('x8', 0.02), ('x3', 0.01), ('x7', 0.0), ('x9', 0.0), ('x10', 0.0), ('x11', 0.0), ('x12', 0.0), ('x13', 0.0), ('x14', 0.0)] +``` + +Хуже всех показала себя модель случайного Лассо, потеряв три значимые признака и добавив один лишний. Модели линейной регрессии и RFE допустили по одной ошибке, однако последняя не потеряла ни одного значимого признака. Значимость в среднем получилась неудовлетворительной и выдала три ошибки, как и первая модель. + +Исходя из этого, можно сделать вывод, что для ранжирования признаков лучше использовать специально созданные для этого инструменты по типу RFE, а не использовать коэффициенты признаков регрессионных моделей. \ No newline at end of file diff --git a/romanova_adelina_lab_2/RandomizedLass.py b/romanova_adelina_lab_2/RandomizedLass.py new file mode 100644 index 0000000..8ac9681 --- /dev/null +++ b/romanova_adelina_lab_2/RandomizedLass.py @@ -0,0 +1,76 @@ +from sklearn.utils import check_X_y, check_random_state +from sklearn.linear_model import Lasso +from scipy.sparse import issparse +from scipy import sparse + + +def _rescale_data(x, weights): + if issparse(x): + size = weights.shape[0] + weight_dia = sparse.dia_matrix((1 - weights, 0), (size, size)) + x_rescaled = x * weight_dia + else: + x_rescaled = x * (1 - weights) + + return x_rescaled + + +class RandomizedLasso(Lasso): + """ + Randomized version of scikit-learns Lasso class. + + Randomized LASSO is a generalization of the LASSO. The LASSO penalises + the absolute value of the coefficients with a penalty term proportional + to `alpha`, but the randomized LASSO changes the penalty to a randomly + chosen value in the range `[alpha, alpha/weakness]`. + + Parameters + ---------- + weakness : float + Weakness value for randomized LASSO. Must be in (0, 1]. + + See also + -------- + sklearn.linear_model.LogisticRegression : learns logistic regression models + using the same algorithm. + """ + def __init__(self, weakness=0.5, alpha=1.0, fit_intercept=True, + precompute=False, copy_X=True, max_iter=1000, + tol=1e-4, warm_start=False, positive=False, + random_state=None, selection='cyclic'): + self.weakness = weakness + super(RandomizedLasso, self).__init__( + alpha=alpha, fit_intercept=fit_intercept, precompute=precompute, copy_X=copy_X, + max_iter=max_iter, tol=tol, warm_start=warm_start, + positive=positive, random_state=random_state, + selection=selection) + + def fit(self, X, y): + """Fit the model according to the given training data. + + Parameters + ---------- + X : {array-like, sparse matrix}, shape = [n_samples, n_features] + The training input samples. + + y : array-like, shape = [n_samples] + The target values. + """ + if not isinstance(self.weakness, float) or not (0.0 < self.weakness <= 1.0): + raise ValueError('weakness should be a float in (0, 1], got %s' % self.weakness) + + X, y = check_X_y(X, y, accept_sparse=True) + + n_features = X.shape[1] + weakness = 1. - self.weakness + random_state = check_random_state(self.random_state) + + weights = weakness * random_state.randint(0, 1 + 1, size=(n_features,)) + + # TODO: I am afraid this will do double normalization if set to true + #X, y, _, _ = _preprocess_data(X, y, self.fit_intercept, normalize=self.normalize, copy=False, + # sample_weight=None, return_mean=False) + + # TODO: Check if this is a problem if it happens before standardization + X_rescaled = _rescale_data(X, weights) + return super(RandomizedLasso, self).fit(X_rescaled, y) diff --git a/romanova_adelina_lab_2/main.py b/romanova_adelina_lab_2/main.py new file mode 100644 index 0000000..9824bd8 --- /dev/null +++ b/romanova_adelina_lab_2/main.py @@ -0,0 +1,106 @@ +from RandomizedLass import RandomizedLasso +import argparse + +from sklearn.linear_model import LinearRegression, Ridge, Lasso +from sklearn.feature_selection import RFE + +from sklearn.preprocessing import MinMaxScaler +import numpy as np + +def get_arguments(): + parser = argparse.ArgumentParser() + + parser.add_argument('--top_k', type=int, default=4, help='Кол-во самых выжных признаков') + + args = parser.parse_args() + return args + + +def data_gen(): + # --- генерируем исходные данные: 750 строк-наблюдений и 14 столбцов-признаков --- + np.random.seed(0) + size = 750 + X = np.random.uniform(0, 1, (size, 14)) + #Задаем функцию-выход: регрессионную проблему Фридмана + Y = (10 * np.sin(np.pi*X[:,0]*X[:,1]) + 20*(X[:,2] - .5)**2 + + 10*X[:,3] + 5*X[:,4]**5 + np.random.normal(0,1)) + #Добавляем зависимость признаков + X[:,10:] = X[:,:4] + np.random.normal(0, .025, (size,4)) + + return X, Y + + +def rank_to_dict(ranks, names): + ranks = np.abs(ranks) + minmax = MinMaxScaler() + ranks = minmax.fit_transform(np.array(ranks).reshape(14,1)).ravel() + ranks = map(lambda x: round(x, 2), ranks) + return dict(zip(names, ranks)) + + +def print_sorted_data(ranks: dict): + print() + for key, value in ranks.items(): + ranks[key] = sorted(value.items(), key=lambda item: item[1], reverse=True) + for key, value in ranks.items(): + print(key) + print(value) + + +def estimation(ranks: dict, top_k): + #Создаем пустой список для данных + mean = {} + #«Бежим» по списку ranks + for key, value in ranks.items(): + #«Пробегаемся» по списку значений ranks, которые являются парой имя:оценка + for item in value.items(): + #имя будет ключом для нашего mean + #если элемента с текущим ключем в mean нет - добавляем + if (item[0] not in mean): + mean[item[0]] = 0 + #суммируем значения по каждому ключу-имени признака + mean[item[0]] += item[1] + + #находим среднее по каждому признаку + for key, value in mean.items(): + res=value/len(ranks) + mean[key] = round(res, 2) + + #сортируем и распечатываем список + mean_sorted = dict(sorted(mean.items(), key=lambda x:x[1], reverse=True)) + print("sorted MEAN") + print(mean_sorted, '---'*25, sep='\n') + + for item in list(mean_sorted.items())[:top_k]: + print(f'Параметр - {item[0]}, значение - {item[1]}') + print('---'*25) + + +if __name__=="__main__": + args = get_arguments() + + X,Y = data_gen() + + # Линейная модель + lr = LinearRegression() + lr.fit(X, Y) + + # Рекурсивное сокращение признаков + rfe = RFE(lr) + rfe.fit(X, Y) + + # Случайное Лассо + randomized_lasso = RandomizedLasso(alpha=.01) + randomized_lasso.fit(X, Y) + + names = ["x%s" % i for i in range(1,15)] + + ranks = {} + + ranks["Linear reg"] = rank_to_dict(lr.coef_, names) + ranks["RFE"] = rank_to_dict(rfe.ranking_, names) + ranks["RandomizedLasso"] = rank_to_dict(randomized_lasso.coef_, names) + + estimation(ranks, args.top_k) + + print_sorted_data(ranks) \ No newline at end of file diff --git a/romanova_adelina_lab_2/req.txt b/romanova_adelina_lab_2/req.txt new file mode 100644 index 0000000..ffb9d49 --- /dev/null +++ b/romanova_adelina_lab_2/req.txt @@ -0,0 +1,17 @@ +contourpy==1.2.0 +cycler==0.12.1 +fonttools==4.45.1 +importlib-resources==6.1.1 +joblib==1.3.2 +kiwisolver==1.4.5 +matplotlib==3.8.2 +numpy==1.26.2 +packaging==23.2 +Pillow==10.1.0 +pyparsing==3.1.1 +python-dateutil==2.8.2 +scikit-learn==1.3.2 +scipy==1.11.4 +six==1.16.0 +threadpoolctl==3.2.0 +zipp==3.17.0