Автор|Кристоф Пере Компилировать|ВКонтакте Источник | К науке о данных
вводить
Я давно слышал, что задачи временных рядов можно решать только статистическими методами (AR[1], AM[2], ARMA[3], ARIMA[4]). Эти методы часто используются математиками, которые пытаются постоянно совершенствовать их, чтобы ограничить стационарные и нестационарные временные ряды.
Несколько месяцев назад мой друг (математик, профессор статистики, эксперт по нестационарным временным рядам) попросил меня исследовать, как проверить и улучшить методы реконструкции кривых звездного освещения. Фактически спутник Кеплер [11], как и многие другие спутники, не может непрерывно измерять интенсивность светового потока ближайших звезд. Спутники Kepler работали в период с 2009 по 2016 год, чтобы найти планеты за пределами нашей Солнечной системы, известные как экзопланеты или экзопланеты.
Как вы понимаете, мы собираемся пойти немного дальше нашей планеты Земля и использовать машинное обучение в галактическом путешествии. Астрофизика всегда была моей страстью.
Этот блокнот можно найти на Github:GitHub.com/Кристоф-…
RNN, LSTM, ГРУ, двунаправленный, CNN-x
Итак, на какой модели мы будем проводить это исследование? Мы будем использовать рекуррентные нейронные сети (RNN [5]), LSTM [6], GRU [7], Stacked LSTM, Stacked GRU, двунаправленный LSTM [8], двунаправленный GRU и CNN-LSTM [9].
Для тех, кто увлекается деревьями, вы можете найти здесь статью о XGBoost и временных рядах от jasonbrownley. Хороший репозиторий для временных рядов доступен на github:GitHub.com/Дженнифер Джейн 28…
Для тех, кто не знаком с семейством RNN, думайте о них как о методах обучения с эффектами памяти и способностями к забыванию. Двунаправленность исходит из архитектуры, которая относится к двум RNN, которые будут «считывать» данные в одном направлении (слева направо) и в другом (справа налево), чтобы иметь возможность наилучшим образом представлять долгосрочные зависимости.
данные
Как упоминалось ранее, эти данные соответствуют измерениям потоков для нескольких звезд. Фактически, в каждый момент времени (час) спутник измеряет поток от ближайших звезд. Этот поток, или интенсивность света, меняется со временем. Для этого есть несколько причин, правильное движение, вращение, перспектива и т. д. спутников будут различаться. Таким образом, измеренное количество фотонов меняется, звезда представляет собой расплавленный шар материи (слияние водорода и гелия), который имеет собственное движение, поэтому испускание фотонов зависит от его движения. Это соответствует колебаниям интенсивности света.
Однако могут быть и планеты, экзопланеты, которые интерферируют со звездами или даже проходят через луч зрения спутников из межзвездного пространства (транзитный метод [12]). Этот канал затеняет звезду, и спутники получают меньше фотонов, потому что они блокируются проходящими впереди планетами (конкретный пример — солнечное затмение, вызванное Луной).
Совокупность измерений потока называется кривой блеска. Как выглядит кривая блеска? Вот некоторые примеры:
Потоки сильно различаются между разными звездами. Одни очень шумные, другие очень стабильные. Поток все еще представляет собой аномалию. На кривой блеска видны дыры или отсутствующие измерения. Нашей целью было посмотреть, можно ли предсказать поведение кривых блеска без измерений.
сжатие данных
Чтобы иметь возможность использовать данные в модели, необходимо сокращение данных. Здесь будут представлены два метода: метод скользящего среднего и метод окна.
Скользящее среднее:
Скользящее среднее включает в себя взятие X последовательных точек и вычисление их среднего значения. Такой подход снижает изменчивость и устраняет шум. Это также уменьшает количество точек, что является методом понижения дискретизации.
Следующая функция позволяет нам вычислить скользящее среднее из списка точек, метод вычисляет количество средних значений и стандартное отклонение точек.
def moving_mean(time, flux, lag=5):
'''
该函数通过设定平均值,使数据去噪,减少数据量。
@param time: (list) 时间值列表
@param flux: (list) 浮点列表->恒星通量
@param lag: (int) 平均值个数,默认值5
@return X: (list) 时间调整
@return y: (list) 通量按平均值重新标定
@return y_std: (list) 标准差列表
'''
# 让我们做一些简单的代码
# 空列表
X = []
y = []
y_std = []
j = 0 # 增量
for i in range(int(len(flux)/lag)):
X.append(np.mean(time[(i+j):(i+j+lag)]))
y.append(np.mean(flux[(i+j):(i+j+lag)]))
y_std.append(np.std(flux[(i+j):(i+j+lag)]))
j+= lag
return X, y, y_stdn
Вы можете видеть, что функция принимает на вход 3 параметра. Время и поток — это x и y временного ряда.lag- количество данных, учитываемых при расчете среднего времени и потока, а также стандартного отклонения потока.
Теперь мы можем увидеть, как использовать эту функцию и результат, полученный путем ее преобразования.
# #导入所需的包
matplotlib inline
import scipy
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import sklearn
import tensorflow as tf
# 让我们看看进度条
from tqdm import tqdm
tqdm().pandas()
Теперь нам нужно импортировать данные. Файл kep_lightcurves.csv содержит данные. Каждая звезда имеет 4 столбца: исходный поток ("...ориг"), масштабированный поток представляет собой исходный поток за вычетом среднего потока ("...rscl'"), разность ("...diff"), и остаток ("...diff") _res"). Всего 52 колонки.
# 20个数据点
x, y, y_err = moving_mean(df.index,df["001724719_rscl"], 20)
df.index представляет время временного ряда
df["001724719_rscl"] перемасштабированный поток("001724719")
lag=20 — количество точек данных для расчета среднего и стандартного
Результаты для первых 3 кривых освещения:
оконный метод
Второй метод — метод окна, как он работает?
Вам нужно взять много точек, 20 в предыдущем примере, а затем вычислить среднее (никакой разницы с предыдущим методом), эта точка является началом нового временного ряда, который находится на позиции 20 (смещение на 19 пунктов). Однако вместо перемещения окна к следующим 20 точкам окно перемещается на одну точку, вычисляется среднее значение с предыдущими 20 точками, затем выполняется перемещение на один шаг назад и так далее.
Это не метод понижения дискретизации, а метод очистки, так как эффект заключается в сглаживании точек данных.
Давайте посмотрим код:
def mean_sliding_windows(time, flux, lag=5):
'''
该函数通过设定平均值,使数据去噪。
@param time: (list) 时间值列表
@param flux: (list) 浮点列表->恒星通量
@param lag: (int) 平均值个数,默认值5
@return X: (list) 时间调整
@return y: (list) 通量按平均值重新标定
@return y_std: (list) 标准差列表
'''
# 让我们做一些简单的代码
# 空列表
X = []
y = []
y_std = []
j = 0 # 增量
for i in range(int(len(flux)-lag)):
_flux = flux[i:(i+lag)]
_time = time[i:(i+lag)]
X.append(np.mean(_time))
y.append(np.mean(_flux))
y_std.append(np.std(_flux))
j+= 1 # 我们只移动一步
return X, y, y_std
Вы можете легко использовать его следующим образом:
# 使用20个点
x, y, y_err = mean_sliding_windows(df.index,df["001724719_rscl"], 40)
df.index представляет время временного ряда
df["001724719_rscl"] перемасштабированный поток("001724719")
lag=40 — количество точек данных для расчета среднего и стандартного
Теперь посмотрите на результаты:
Что ж, неплохо. Установка лага на 40 позволяет «предсказывать» или расширять новые временные ряды в небольших дырах. Однако, если вы присмотритесь, вы заметите расхождение в начале и конце красной линии. Функцию можно улучшить, чтобы избежать этих артефактов.
В следующем исследовании мы будем использовать временной ряд, полученный методом скользящего среднего.
Измените ось x со значений на даты:
Вы можете изменить ось, если хотите дату. Миссия Kepler стартовала 7 марта 2009 года и завершилась в 2017 году. В Pandas есть функция pd.data_range(). Эта функция позволяет создавать даты из постоянно растущего списка.
df.index = pd.date_range(‘2009–03–07’, periods=len(df.index), freq=’h’)
Эта строка кода создаст новый индекс с частотой часов. Результат печати показан ниже.
$ df.index
DatetimeIndex(['2009-03-07 00:00:00', '2009-03-07 01:00:00',
'2009-03-07 02:00:00', '2009-03-07 03:00:00',
'2009-03-07 04:00:00', '2009-03-07 05:00:00',
'2009-03-07 06:00:00', '2009-03-07 07:00:00',
'2009-03-07 08:00:00', '2009-03-07 09:00:00',
...
'2017-04-29 17:00:00', '2017-04-29 18:00:00',
'2017-04-29 19:00:00', '2017-04-29 20:00:00',
'2017-04-29 21:00:00', '2017-04-29 22:00:00',
'2017-04-29 23:00:00', '2017-04-30 00:00:00',
'2017-04-30 01:00:00', '2017-04-30 02:00:00'],
dtype='datetime64[ns]', length=71427, freq='H')
Теперь для исходного временного ряда у вас есть хорошая временная шкала.
Создать набор данных
Итак, теперь, когда функции обработки данных созданы, мы можем объединить их в другую функцию (показанную ниже), которая будет учитывать исходный набор данных и имена звезд в наборе данных (эту часть можно выполнить в функции).
def reduced_data(df,stars):
'''
Function to automatically reduced a dataset
@param df: (pandas dataframe) 包含所有数据的dataframe
@param stars: (list) 包含我们想要简化数据的每个恒星的名称的列表
@return df_mean: 包含由减少平均值的数据的dataframe
@return df_slide: 包含通过滑动窗口方法减少的数据
'''
df_mean = pd.DataFrame()
df_slide = pd.DataFrame()
for i in tqdm(stars):
x , y, y_std = moving_average(df.index, df[i+"_rscl"], lag=25)
df_mean[i+"_rscl_x"] = x
df_mean[i+"_rscl_y"] = y
df_mean[i+"_rscl_y_std"] = y_std
x , y, y_std = mean_sliding_windows(df.index, df[i+"_rscl"], lag=40)
df_slide[i+"_rscl_x"]= x
df_slide[i+"_rscl_y"]= y
df_slide[i+"_rscl_y_std"]= y_std
return df_mean, df_slide
Чтобы создать новый фрейм данных:
stars = df.columns
stars = list(set([i.split("_")[0] for i in stars]))
print(f"The number of stars available is: {len(stars)}")
> The number of stars available is: 13
У нас есть 13 звезд с 4 типами данных, соответствующими 52 столбцам.
df_mean, df_slide = reduced_data(df,stars)
Отлично, теперь у вас есть два новых набора данных со скользящим средним и оконным методом.
метод
Подготовьте данные:
Чтобы использовать алгоритмы машинного обучения для прогнозирования временных рядов, данные должны быть подготовлены соответствующим образом. Данные не могут быть просто установлены в (x, y) точках данных. Данные должны быть в виде последовательности [x1, x2, x3, ..., xn] и предсказанного значения y.
Следующая функция демонстрирует, как настроить набор данных:
def create_dataset(values, look_back=1):
'''
函数准备一列(x, y)数据指向用于时间序列学习的数据
@param values: (list) 值列表
@param look_back: (int) x列表的值[x1, x2, x3,…默认值1
@return _x: x时间序列的值
@return _y: y时间序列的值
'''
# 空列表
_x, _y = [], []
for i in range(len(values)-look_back-1):
a = values[i:(i+look_back)]
_x.append(a) # 集合x
_y.append(values[i + look_back]) # 集合y
return np.array(_x), np.array(_y)
Прежде чем мы начнем, есть две важные вещи.
1. Данные необходимо масштабировать
Когда данные находятся в диапазоне [0,1], алгоритмы глубокого обучения лучше работают для прогнозирования временных рядов. Для этого scikitlearn предоставляет функцию MinMaxScaler(). Вы можете настроить параметр feature_range, но значение по умолчанию — (0, 1). И очистите данные для значений nan (если вы не удалите значения nan, функция потерь выведет nan).
# 缩放数据
num = 2 # 选择数据集中的第三颗星
values = df_model[stars[num]+"_rscl_y"].values # 提取值
scaler = MinMaxScaler(feature_range=(0, 1)) # 创建MinMaxScaler的实例
dataset = scaler.fit_transform(values[~np.isnan(values)].reshape(-1, 1)) # 数据将清除nan值,重新缩放并改变形状
2. Нужно преобразовать данные в список x и y
Теперь мы будем использовать функцию create_values() для генерации данных для модели. Однако раньше я предпочитал сохранять необработанные данные через:
df_model = df_mean.save()
# 分成训练和测试集sets
train_size = int(len(dataset) * 0.8) # 生成80%的训练数据
train = dataset[:train_size] # 设置训练数据
test = dataset[train_size:] # 设置测试数据
#重塑为X=t和Y=t+1
look_back = 20
trainX, trainY = create_dataset(train, look_back)
testX, testY = create_dataset(test, look_back)
# 将输入重塑为[示例、时间点、特征]
trainX = np.reshape(trainX, (trainX.shape[0], trainX.shape[1], 1))
testX = np.reshape(testX, (testX.shape[0], testX.shape[1], 1))
увидеть результаты
trainX[0]
> array([[0.7414906],
[0.76628096],
[0.79901113],
[0.62779976],
[0.64012722],
[0.64934765],
[0.68549234],
[0.64054092],
[0.68075644],
[0.73782449],
[0.68319294],
[0.64330245],
[0.61339268],
[0.62758265],
[0.61779702],
[0.69994317],
[0.64737128],
[0.64122564],
[0.62016833],
[0.47867125]]) # x数据的第一个有20个值
trainY[0]
> array([0.46174275]) # 对应的y值
мера
Какие показатели мы используем для прогнозирования временных рядов? Мы можем использовать среднюю абсолютную ошибку и среднеквадратичную ошибку. Они задаются функциями:
def metrics_time_series(y_true, y_pred):
'''
从sklearn.metrics计算MAE和MSE度量
@param y_true: (list) 真实值列表
@param y_pred: (list) 预测值列表
@return mae, mse: (float), (float) mae和mse的度量值
'''
mae = round(mean_absolute_error(y_true, y_pred), 2)
mse = round(mean_squared_error(y_true, y_pred), 2)
print(f"The mean absolute error is: {mae}")
print(f"The mean squared error is: {mse}")
return mae, mse
Сначала вам нужно импортировать функцию:
from sklearn.metrics import mean_absolute_error, mean_squared_error
RNN:
Вы можете легко реализовать семейство RNN с помощью Keras, написав несколько строк кода. Здесь вы можете использовать эту функцию для настройки вашего RNN. Вам нужно сначала импортировать разные модели из Keras, например:
# 导入一些包
import tensorflow as tf
from keras.layers import SimpleRNN, LSTM, GRU, Bidirectional, Conv1D, MaxPooling1D, Dropout
Теперь у нас есть модель, импортированная из Keras. Следующая функция может генерировать простую модель (SimpleRNN, LSTM, GRU). В качестве альтернативы, две модели (идентичные) могут быть объединены в стек или использованы в стеке двунаправленных или двух двунаправленных моделей. Вы также можете добавить часть CNN (Conv1D) с MaxPooling1D и отсевом.
def time_series_deep_learning(x_train, y_train, x_test, y_test, model_dl=LSTM , unit=4, look_back=20, cnn=False, bidirection=False, stacked=False):
'''
生成不同组合的RNN模型。可以是简单的、堆叠的或双向的。模型也可以与CNN部分一起使用。
x是(样本、时间步长、特征)的训练数据
@param x_train: (matrix) 训练数据,维度为 (samples, time steps, features)
@param y_train: (list) 预测
@param x_test: (matrix) 测试数据,维度为 (samples, time steps, features)
@param y_test: (list) 预测
@param model_dl: (model) RNN类型
@param unit: (int) RNN单元数量
@param look_back: (int) x列表中值的数量,配置RNN的形状
@param cnn: (bool) 添加cnn部分模型,默认为false
@param bidirection: (bool) 为RNN添加双向模型,默认为false
@param stacked: (bool) 堆叠的两层RNN模型,默认为假
@return train_predict: (list) x_train的预测值
@return train_y: (list) 真实y值
@return test_predict: (list) x_test的预测值
@return test_y: (list) 真实y值
@return (dataframe) 包含模型和度量的名称
'''
#配置提前停止的回调,以避免过拟合
es = tf.keras.callbacks.EarlyStopping(
monitor='loss', patience=5, verbose=0, mode='auto',
)
# 序列模型的实例
model = Sequential()
if cnn: # 如果cnn部分是需要的
print("CNN")
model.add(Conv1D(128, 5, activation='relu'))
model.add(MaxPooling1D(pool_size=4))
model.add(Dropout(0.2))
if not bidirection and not stacked: # 如果需要简单的模型
print("Simple Model")
name = model_dl.__name__
model.add(model_dl(unit, input_shape=(look_back, 1)))
elif not bidirection: # 测试是否需要双向模型
print("Stacked Model")
name = "Stacked_"+model_dl.__name__
model.add(model_dl(unit, input_shape=(look_back, 1), return_sequences=True))
model.add(model_dl(unit, input_shape=(look_back, 1)))
elif not stacked: # 测试是否需要堆叠模型
print("Bidirectional Model")
name = "Bi_"+model_dl.__name__
model.add(Bidirectional(model_dl(unit, input_shape=(look_back, 1))))
else: # 测试是否需要双向和堆叠模型
print("Stacked Bidirectional Model")
name = "Stacked_Bi_"+model_dl.__name__
model.add(Bidirectional(model_dl(unit, input_shape=(look_back, 1), return_sequences=True)))
model.add(Bidirectional(model_dl(unit, input_shape=(look_back, 1))))
if cnn: # 更新名称与cnn部分
name = "CNN_"+name
# 添加单层稠密层和激活函数线性来预测连续值
model.add(Dense(1))
model.compile(loss='mean_squared_error', optimizer='adam') # MSE loss可以被'mean_absolute_error'替代
model.fit(trainX, trainY, epochs=1000, batch_size=100, callbacks=[es], verbose=0)
# 做出预测
train_predict = model.predict(x_train)
test_predict = model.predict(x_test)
# 反预测
train_predict = scaler.inverse_transform(train_predict)
train_y = scaler.inverse_transform(y_train)
test_predict = scaler.inverse_transform(test_predict)
test_y = scaler.inverse_transform(y_test)
# 计算度量
print("Train")
mae_train, mse_train = metrics_time_series( train_y, train_predict)
print("Test")
mae_test, mse_test = metrics_time_series( test_y, test_predict)
return train_predict, train_y, test_predict, test_y, pd.DataFrame([name, mae_train, mse_train, mae_test, mse_test], index=["Name", "mae_train", "mse_train", "mae_test", "mse_test"]).T
Эта функция вычисляет показатели для обучающей части и тестовой части и возвращает результаты в виде фрейма данных. Приведите пять примеров.
LSTM
# 训练模型并计算度量
> x_train_predict_lstm, y_train_lstm,x_test_predict_lstm, y_test_lstm, res= time_series_deep_learning(train_x, train_y, test_x, test_y, model_dl=LSTM , unit=12, look_back=20)
# 画出预测的结果
> plotting_predictions(dataset, look_back, x_train_predict_lstm, x_test_predict_lstm)
# 将每个模型的指标保存在数据框df_results中
> df_results = df_results.append(res)
GRU
# 训练模型并计算度量
> x_train_predict_lstm, y_train_lstm,x_test_predict_lstm, y_test_lstm, res= time_series_deep_learning(train_x, train_y, test_x, test_y, model_dl=GRU, unit=12, look_back=20)
Сложенные LSTM:
# 训练模型并计算度量
> x_train_predict_lstm, y_train_lstm,x_test_predict_lstm, y_test_lstm, res= time_series_deep_learning(train_x, train_y, test_x, test_y, model_dl=LSTM , unit=12, look_back=20, stacked=True)
Двунаправленный LSTM:
# 训练模型并计算度量
> x_train_predict_lstm, y_train_lstm,x_test_predict_lstm, y_test_lstm, res= time_series_deep_learning(train_x, train_y, test_x, test_y, model_dl=LSTM , unit=12, look_back=20, bidirection=True)
CNN-LSTM:
# 训练模型并计算度量
> x_train_predict_lstm, y_train_lstm,x_test_predict_lstm, y_test_lstm, res= time_series_deep_learning(train_x, train_y, test_x, test_y, model_dl=LSTM , unit=12, look_back=20, cnn=True)
результат
Судя по данным, результаты неплохие. Мы видим, что RNN с глубоким обучением может очень хорошо воспроизводить точность данных. На рисунке ниже показаны результаты прогнозирования модели LSTM.
Таблица 1. Результаты для различных моделей RNN с показателями MAE и MSE.
Name | MAE Train | MSE Train | MAE Test | MSE Test
--------------------------------------------------------------------
GRU | 4.24 | 34.11 | 4.15 | 31.47
LSTM | 4.26 | 34.54 | 4.16 | 31.64
Stack_GRU | 4.19 | 33.89 | 4.17 | 32.01
SimpleRNN | 4.21 | 34.07 | 4.18 | 32.41
LSTM | 4.28 | 35.1 | 4.21 | 31.9
Bi_GRU | 4.21 | 34.34 | 4.22 | 32.54
Stack_Bi_LSTM | 4.45 | 36.83 | 4.24 | 32.22
Bi_LSTM | 4.31 | 35.37 | 4.27 | 32.4
Stack_SimpleRNN | 4.4 | 35.62 | 4.27 | 33.94
SimpleRNN | 4.44 | 35.94 | 4.31 | 34.37
Stack_LSTM | 4.51 | 36.78 | 4.4 | 34.28
Stacked_Bi_GRU | 4.56 | 37.32 | 4.45 | 35.34
CNN_LSTM | 5.01 | 45.85 | 4.55 | 36.29
CNN_GRU | 5.05 | 46.25 | 4.66 | 37.17
CNN_Stack_GRU | 5.07 | 45.92 | 4.7 | 38.64
В таблице 1 показаны средняя абсолютная ошибка (MAE) и среднеквадратическая ошибка (MSE) обучающего и тестового наборов серии RNN. GRU показывает лучшие результаты на тестовом наборе с MAE 4,15 и MSE 31,47.
обсуждать
Результаты хорошие и воспроизводят кривые блеска разных звезд (см. блокнот). Однако флуктуации воспроизводятся не полностью, интенсивность пиков не одинакова, а поток немного смещен. Это можно исправить с помощью механизма внимания. Другой способ — настроить модель, количество слоев (стеков), количество блоков (юнитов), комбинацию различных алгоритмов RNN, новые функции потерь или функции активации и т. д.
в заключении
В данной работе показана возможность сочетания так называемых методов искусственного интеллекта с временными рядами. Мощь алгоритмов памяти (RNN, LSTM, GRU) позволяет точно воспроизводить случайные колебания событий. В нашем случае звездный поток демонстрирует довольно сильные и значительные флуктуации, которые удалось зафиксировать этими методами.
Это исследование показывает, что временные ряды больше не являются статистическим методом, подобным модели ARIMA [4].
ссылка
[1] Autoregressive model, Wikipedia [2] Moving-average model, Wikipedia [3] Peter Whittle, 1950. Hypothesis testing in time series analysis.Тезис [4] Альберто Лученьо и Даниэль Пенья, 2008 г.Autoregressive Integrated Moving Average (ARIMA) Modeling. Wiley Online Library. сделать i.org/10.1002/978… [5] Rumelhart, David E. et al., 1986. Learning representations by back-propagating errors. Nature. 323(6088): 533–536.1986Natur.323..533R. [6] Хохрайтер, Зепп и Шмидхубер, Юрген, 1997.Long Short-Term Memory. Neural Computation. 9(8): 1735–1780. doi: 10.1162/neco.1997.9.8.1735. [7] Чо, Кёнхёнet al., 2014. Empirical Evaluation of Gated Recurrent Neural Networks on Sequence Modeling. arXiv:1412.3555 [8] M. Schuster & K.K. Paliwal, 1997. Bidirectional recurrent neuralIEEE Transactions on Signal Processing, Volume: 45, Issue: 11, pp.2673–2681.DOI**:** 10.1109/78.650093 [9] Тара Н. Сайнатхet al., 2014. CONVOLUTIONAL, LONG SHORT-TERM MEMORY,FULLY CONNECTED DEEP NEURAL NETWORKS. static.Google user content.com/Media/Hot colors… [10] Ashish Vaswani et al., 2017. Attention is all you need. АР Вест V.org/ABS/1706.03… [11] Kepler mission, Nasa
Оригинальная ссылка:к data science.com/how-to-use-…
Добро пожаловать на сайт блога Panchuang AI:panchuang.net/
sklearn машинное обучение китайские официальные документы:sklearn123.com/
Добро пожаловать на станцию сводки ресурсов блога Panchuang:docs.panchuang.net/