Merge pull request 'romanova_adelina_lab_2 is ready' (#209) from romanova_adelina_lab_2 into main

Reviewed-on: http://student.git.athene.tech/Alexey/IIS_2023_1/pulls/209
This commit is contained in:
Alexey 2023-12-05 22:57:27 +04:00
commit c2101d3e00
4 changed files with 308 additions and 0 deletions

View File

@ -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, а не использовать коэффициенты признаков регрессионных моделей.

View File

@ -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)

View File

@ -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)

View File

@ -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