From b188f4720e8bf4eab1ef065548fa11d96dda24fe Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 03:29:10 +0000 Subject: [PATCH 01/25] =?UTF-8?q?=E6=96=B0=E5=BB=BA=20subject2-pre?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- subject2-pre/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 subject2-pre/.keep diff --git a/subject2-pre/.keep b/subject2-pre/.keep new file mode 100644 index 0000000..e69de29 -- Gitee From f522192630dfc0984787f327d197f2fcfd4cefda Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 03:42:07 +0000 Subject: [PATCH 02/25] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=88=E8=99=9A=E6=8B=9F=E6=95=B0=E6=8D=AE=E7=94=9F=E6=88=90?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> --- subject2-pre/tvhl.py | 200 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 subject2-pre/tvhl.py diff --git a/subject2-pre/tvhl.py b/subject2-pre/tvhl.py new file mode 100644 index 0000000..421a73c --- /dev/null +++ b/subject2-pre/tvhl.py @@ -0,0 +1,200 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from scipy.spatial.distance import cdist +import requests +import csv +from io import StringIO +import datetime + +def get_data_from_api(api_url): + """ + 从API获取数据的函数接口 + 参数: + - api_url: API的地址 + 返回值: + - 如果成功,返回API响应的JSON数据 + - 如果失败,返回None + """ + try: + # 发起GET请求 + response = requests.get(api_url) + + # 检查响应状态码 + if response.status_code == 200: + # 解析JSON数据并返回 + return response.json() + else: + # 打印错误信息,并返回None + print(f"Error: {response.status_code} - {response.text}") + return None + except requests.RequestException as e: + # 打印异常信息,并返回None + print(f"Request Error: {e}") + return None + +def json_data_to_csv(json_data): + try: + # 提取表头(CSV文件的列名) + header = list(json_data['pageData'][0].keys()) + # 创建一个内存中的文件对象,用于写入CSV数据 + csv_output = StringIO() + # 创建CSV写入器 + csv_writer = csv.DictWriter(csv_output, fieldnames=header) + # 写入表头 + csv_writer.writeheader() + # 写入数据 + csv_writer.writerows(json_data['pageData']) + # 获取CSV数据作为字符串 + csv_data = csv_output.getvalue() + print("转换成功:JSON 数据到 CSV 字符串") + return csv_data + except Exception as e: + print(f"转换失败:{e}") + return None + +#创建生成器 +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model +generator = build_generator() + +#创建鉴别器 +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model +discriminator = build_discriminator() + + +#建立损失函数 +def compute_joint_distribution_loss(real_samples, fake_samples): + real_relative_time = real_samples[:, 0] + fake_relative_time = fake_samples[:, 0] + real_samples = real_samples[:, 1:] + fake_samples = fake_samples[:, 1:] + distance =10 * cdist(real_samples, fake_samples, metric='euclidean') + conditional_distance = np.abs(real_relative_time - fake_relative_time) + joint_distribution_loss = np.mean(distance * conditional_distance) + return joint_distribution_loss + + +#训练模型 +def train_gan(noise_data, real_data, epochs, batch_size): + for epoch in range(epochs): + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + generated_samples = generator.predict(noise) + # print("Real Samples Shape:", real_samples.shape) + # print("Generated Samples Shape:", generated_samples.shape) + X = np.concatenate((real_samples, generated_samples)) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + # 计算联合分布差异项 + joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) + # 更新判别器的损失函数 + discriminator_loss = discriminator_loss +1*joint_distribution_loss + # 打印损失和精度 + if epoch % 100 == 0: + print( + f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + + +def gene_data(generated_data): + # 将时间恢复到24小时制 + generated_data['time'] = (generated_data['time'] * 24).astype(int) + + data_by_hour = {} + # 将generated_data按时间分组,放入字典中对应的键中 + for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + empty_df = pd.DataFrame() + + # 遍历1到23小时,检查每个小时的数据是否为空 + for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + + # 复制empty_df的第一行数据,并赋值给time_series_data + time_series_data = empty_df.iloc[:1].copy() + for i in range(1, 60): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data, empty_df.iloc[j * 24 + i:j * 24 + i + 1].copy()]) + time_series_data = time_series_data.copy() + time_series_data['time'] = range(len(time_series_data)) + return time_series_data + +def set_noise(): + uniform_noise = np.random.uniform(0, 1, size=(10000, 1)) + normal_noise = np.random.normal(0, 1, size=(10000, 6)) + noise = np.hstack((uniform_noise, normal_noise)) + return noise + + +#从API获取历史数据 +# api_url = "http://202.117.43.38:28081/jcce/v1.0/job/list" +# data = get_data_from_api(api_url) +data = pd.read_csv('pre_microsoft.csv') + +# 使用提取的数据进行CSV转换 +# data = json_data_to_csv(data) +# data = pd.read_csv(StringIO(data), parse_dates=["startTime", "endTime", "arriveTime"]) +current_time = datetime.datetime.now() +# print(current_time) +# data["startTime"] = np.round((data["startTime"] - current_time) / np.timedelta64(1, "h") + 100) +#提取特征 +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data[features] +# 将相同时间的数据合并 +# data = data.groupby("startTime").sum().reset_index() +data['time'] = data['time'] % 24 +#根据接口进行修改 + + + +#归一化处理 +data = data[features].values +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + +#设置数据生成参数 +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + +noise = set_noise() +#训练模型 +train_gan(data, noise, epochs=1000, batch_size=128) +# 数据生成和处理 +generated_data = pd.DataFrame(generator.predict(noise), columns=features) +generated_data = gene_data(generated_data) + +# 保存数据 +generated_data.to_csv('tvhl.csv', index=False) \ No newline at end of file -- Gitee From 74f775c9fdb50b81f9b58086554da4607102f2ac Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 03:43:00 +0000 Subject: [PATCH 03/25] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=88=E8=81=94=E9=82=A6=E5=AD=A6=E4=B9=A0=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E7=AB=AF=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> --- subject2-pre/client.py | 185 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 subject2-pre/client.py diff --git a/subject2-pre/client.py b/subject2-pre/client.py new file mode 100644 index 0000000..37a03f8 --- /dev/null +++ b/subject2-pre/client.py @@ -0,0 +1,185 @@ +import socket +import pickle +import numpy as np +import pandas as pd +import torch.optim as optim + +import torch +import torch.nn as nn +import argparse + +# 定义网络 +class GRU(nn.Module): + def __init__(self, feature_size, hidden_size, num_layers, output_size): + super(GRU, self).__init__() + self.hidden_size = hidden_size + self.num_layers = num_layers + self.gru = nn.GRU(feature_size, hidden_size, num_layers, batch_first=True) + self.fc = nn.Linear(hidden_size, output_size) + self.activation = nn.ReLU() # 添加激活函数 + + def forward(self, x, hidden=None): + batch_size = x.shape[0] + if hidden is None: + h_0 = x.data.new(self.num_layers, batch_size, self.hidden_size).fill_(0).float() + else: + h_0 = hidden + output, h_0 = self.gru(x, h_0) + output = self.fc(output[:, -1, :]) + output = self.activation(output) # 添加激活函数 + return output, h_0 + +# 定义学习任务的设置 +def parser_args(): + parser = argparse.ArgumentParser(description='GRU Model Parameters') + parser.add_argument('--timestep', type=int, default=4, help='Time step size') + parser.add_argument('--batch_size', type=int, default=32, help='Batch size') + parser.add_argument('--feature_size', type=int, default=6, help='Number of features') + parser.add_argument('--hidden_size', type=int, default=512, help='Size of the hidden layer') + parser.add_argument('--output_size', type=int, default=3, help='Size of the output layer') + parser.add_argument('--num_layers', type=int, default=2, help='Number of GRU layers') + parser.add_argument('--epochs', type=int, default=3, help='Number of training epochs') + parser.add_argument('--learning_rate', type=float, default=0.0001, help='Learning rate') + parser.add_argument('--model_name', type=str, default='gru', help='Name of the model') + parser.add_argument('--save_path', type=str, default='./{}.pth'.format('gru_a'), help='Path to save the best model') + parser.add_argument('--no_cuda', action='store_true', default=False, help='Disable CUDA') + args = parser.parse_args() + return args + +def train_model(model, x_train, y_train, config): + # 设置优化器和损失函数 + optimizer = optim.Adam(model.parameters(), lr=config.learning_rate) + # optimizer = optim.SGD(model.parameters(), lr=config.learning_rate) + criterion = nn.MSELoss() + # 设置初始隐藏状态(可根据需要调整) + # hidden = None + + # 进行本地训练 + for epoch in range(config.epochs): + optimizer.zero_grad() + + # 手动清除隐藏状态 + hidden = None + + # 前向传播 + output, hidden = model(x_train, hidden) + loss = criterion(output, y_train) + + # 反向传播和参数更新 + loss.backward(retain_graph=True) + optimizer.step() + + print(f"Epoch [{epoch + 1}/{config.epochs}], Loss: {loss.item()}") + + # 返回更新后的客户端模型参数 + return model + +# 划分数据 +def split_data(data, timestep,feature_size, target_size): + dataX = [] + dataY = [] + for index in range(len(data) - timestep): + dataX.append(data[index: index + timestep]) + dataY.append(data[index + timestep, :target_size]) + dataX = np.array(dataX) + dataY = np.array(dataY) + train_size = int(np.round(dataX.shape[0])) + x_train = dataX[:train_size, :] + y_train = dataY[:train_size, :] + x_test = dataX[train_size:, :] + y_test = dataY[train_size:, :] + + return x_train, y_train, x_test, y_test + + +# 数据加载 +def load_data(data_path): + dataset = pd.read_csv(data_path) + return dataset + +args = parser_args() + +data_path ='tvhl_a.csv' +dataset = load_data(data_path) + +selected_features = ['cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan', 'mem_plan'] +target_size = 3 # 三个目标列的大小 +selected_data = dataset[selected_features].values + +x_train,y_train, x_test, y_test = split_data(selected_data, args.timestep, args.feature_size,target_size) +x_train_tensor = torch.Tensor(x_train) +y_train_tensor = torch.Tensor(y_train) + +# 开始训练 +model = GRU(args.feature_size, args.hidden_size, args.num_layers, args.output_size).to('cpu') +# 创建优化器 +optimizer = optim.SGD(model.parameters(), lr=args.learning_rate) + +# 创建TCP客户端套接字 +client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + +# 连接到服务器 +# server_address = ('127.0.0.1', 4321) +server_address = ('202.117.43.11', 8080) +client.connect(server_address) + +ROUNDS = 20 +length = len(pickle.dumps(model.state_dict())) +chunk_size = 1024 + +for round in range(ROUNDS): + print("第 %d 轮" % (round), end='\n') + + size_message = client.recv(4096) + length = int.from_bytes(size_message, byteorder='big') + + # 从服务器接收模型权重 + received_weights = bytearray() + + while len(received_weights) < length: + # print('从服务器接收模型权重') + chunk = client.recv(4096) + received_weights += chunk + + print('全部接受完毕') + if len(received_weights) == length: + try: + received_weights = pickle.loads(received_weights) + print('解析成功') + # 设置模型权重 + model.load_state_dict(received_weights) + except EOFError: + print('解析失败: EOFError') + else: + print('接收到的数据长度不符合预期,可能存在问题') + + + # 在客户端本地训练模型 + print("开始训练") + train_model(model, x_train_tensor, y_train_tensor, args) + + # 获取更新后的模型权重 + updated_weights = model.state_dict() + print('获取更新后的模型权重') + # 序列化并将更新后的权重发送到服务器 + print('将更新后的权重发送到服务器') + updated_weights_serialized = pickle.dumps(updated_weights) + print(f"Serialized client model weights size: {len(updated_weights_serialized)} bytes") + + # size_message = str(len(updated_weights_serialized)).encode('utf-8') + # client.send(size_message) + size_message = len( updated_weights_serialized).to_bytes(4096, byteorder='big') + client.send(size_message) + + total_sent = 0 + for i in range(0, len(updated_weights_serialized), chunk_size): + chunk =updated_weights_serialized [i:i + chunk_size] + sent = client.send(chunk) + total_sent += sent + + + print('发送完毕') + +# 关闭客户端套接字 +client.close() +torch.save(model, args.save_path) \ No newline at end of file -- Gitee From 9bf3fcb4c7522f4511d74db44ed78487886a20eb Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 03:43:46 +0000 Subject: [PATCH 04/25] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=88=E8=81=94=E9=82=A6=E5=AD=A6=E4=B9=A0=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8=E7=AB=AF=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> --- subject2-pre/server.py | 164 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 subject2-pre/server.py diff --git a/subject2-pre/server.py b/subject2-pre/server.py new file mode 100644 index 0000000..bcb74aa --- /dev/null +++ b/subject2-pre/server.py @@ -0,0 +1,164 @@ +import socket +import pickle +import torch.nn as nn +import argparse +from concurrent.futures import ThreadPoolExecutor + + + +# 定义神经网络 +class GRU(nn.Module): + def __init__(self, feature_size, hidden_size, num_layers, output_size): + super(GRU, self).__init__() + self.hidden_size = hidden_size + self.num_layers = num_layers + self.gru = nn.GRU(feature_size, hidden_size, num_layers, batch_first=True) + self.fc = nn.Linear(hidden_size, output_size) + self.activation = nn.ReLU() + + def forward(self, x, hidden=None): + batch_size = x.shape[0] + if hidden is None: + h_0 = x.data.new(self.num_layers, batch_size, self.hidden_size).fill_(0).float() + else: + h_0 = hidden + output, h_0 = self.gru(x, h_0) + output = self.fc(output[:, -1, :]) + output = self.activation(output) + return output, h_0 + +# 定义学习任务的设置 +def parser_args(): + parser = argparse.ArgumentParser(description='GRU Model Parameters') + parser.add_argument('--timestep', type=int, default=4, help='Time step size') + parser.add_argument('--batch_size', type=int, default=32, help='Batch size') + parser.add_argument('--feature_size', type=int, default=6, help='Number of features') + parser.add_argument('--hidden_size', type=int, default=512, help='Size of the hidden layer') + parser.add_argument('--output_size', type=int, default=3, help='Size of the output layer') + parser.add_argument('--num_layers', type=int, default=2, help='Number of GRU layers') + parser.add_argument('--epochs', type=int, default=3, help='Number of training epochs') + parser.add_argument('--learning_rate', type=float, default=0.0001, help='Learning rate') + parser.add_argument('--model_name', type=str, default='gru', help='Name of the model') + parser.add_argument('--save_path', type=str, default='./{}.pth'.format('gru'), help='Path to save the best model') + parser.add_argument('--no_cuda', action='store_true', default=False, help='Disable CUDA') + args = parser.parse_args() + return args + +args = parser_args() +model = GRU(args.feature_size, args.hidden_size, args.num_layers, args.output_size) + +# 创建服务器套接字 +server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +# 绑定服务器地址和端口 +# server_address = ('127.0.0.1', 4321) +server_address = ('202.117.43.11', 8080) +server.bind(server_address) +# 开始监听,允许最多客户端连接 +server.listen(5) +clients = [] +c_weights = {} # 用于保存每个客户端的权重数据 +c_length= {} + + +while True: + print("等待客户端连接...") + client, addr = server.accept() + print(f"接受来自 {addr} 的连接") + clients.append(client) + if len(clients) == 2: + break + +ROUNDS = 20 +length = len(pickle.dumps(model.state_dict())) +chunk_size = 1024 + + +for round in range(ROUNDS): + + print("ROUND %d" % (round), end='\n') + # 获取当前模型的权重 + model_weights = model.state_dict() + model_weights_serialized = pickle.dumps(model_weights) + print(f"Serialized model weights size: {len(model_weights_serialized)} bytes") + + for client in clients: + print('接受客户端数据长度:') + print(client) + size_message = len(model_weights_serialized).to_bytes(4096, byteorder='big') # 将整数转换为字节数组 + client.send(size_message) + + # 向所有客户端发送当前模型的权重 + print('向客户端发送当前模型的权重') + for client in clients: + total_sent = 0 + for i in range(0, len(model_weights_serialized), chunk_size): + chunk = model_weights_serialized[i:i + chunk_size] + sent = client.send(chunk) + total_sent += sent + + + + def receive_data_from_client(client, c_length): + + print(f'接受客户端数据长度:{client}') + size_message = client.recv(4096) + length = int.from_bytes(size_message, byteorder='big') + c_length[client] = length + + print(f'接受客户端数据:{client}') + received_weights = b"" + while len(received_weights) < c_length[client]: + chunk = client.recv(4096) + received_weights += chunk + + if len(received_weights) == length: + try: + received_weights = pickle.loads(received_weights) + print('解析成功') + c_weights[client] = received_weights + except EOFError: + print('解析失败: EOFError') + c_weights[client] = c_weights[client] + else: + print('接收到的数据长度不符合预期,可能存在问题') + + + + # 创建ThreadPoolExecutor + with ThreadPoolExecutor(max_workers=len(clients)) as executor: + # 提交任务给线程池 + futures = [executor.submit(receive_data_from_client, client, c_length) for client in clients] + + # 等待所有任务完成 + for future in futures: + future.result() + + + # 计算平均权重 + aggregated_weights = model.state_dict() + # print(aggregated_weights) + + + # 将所有值清零 + for key in aggregated_weights.keys(): + aggregated_weights[key].zero_() + + # 累积权重 + for weights in c_weights.values(): + for key, value in weights.items(): + if key not in aggregated_weights: + aggregated_weights[key] = value.clone() + else: + aggregated_weights[key] += value + + # 计算平均值 + for key in aggregated_weights.keys(): + aggregated_weights[key] /= len(c_weights) + + # 将聚合后的权重应用到模型中 + model.load_state_dict(aggregated_weights) + print("权重聚合完毕") + +# 关闭服务器套接字 +server.close() + -- Gitee From 02f46d2fbdca7b2602f224b01d6dff90d645d326 Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 03:44:30 +0000 Subject: [PATCH 05/25] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=88=E4=B8=AA=E6=80=A7=E5=8C=96=E7=9F=A5=E8=AF=86=E8=92=B8?= =?UTF-8?q?=E9=A6=8F=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> --- subject2-pre/post_fl.py | 230 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 subject2-pre/post_fl.py diff --git a/subject2-pre/post_fl.py b/subject2-pre/post_fl.py new file mode 100644 index 0000000..fab6ad9 --- /dev/null +++ b/subject2-pre/post_fl.py @@ -0,0 +1,230 @@ +import numpy as np +import pandas as pd +import torch +import torch.nn as nn +from sklearn.preprocessing import StandardScaler, MinMaxScaler +import argparse + + +# 数据加载 +def load_data(data_path): + dataset = pd.read_csv(data_path) + return dataset +# 加载数据 +data_path = 'processed_alibaba.csv' +data = load_data(data_path) +# 选择需要训练和测试的特征列和目标列 +data = data[['cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan', 'mem_plan']] + + +# 标准化 +scaler = MinMaxScaler(feature_range=(0, 1)) +data = scaler.fit_transform(data.values) + +u = 0.1 +# 定义迁移学习的训练轮数 +num_transfer_epochs = 100 + +# 定义学习任务的设置 +def parser_args(): + parser = argparse.ArgumentParser(description='GRU Model Parameters') + parser.add_argument('--timestep', type=int, default=4, help='Time step size') + parser.add_argument('--batch_size', type=int, default=32, help='Batch size') + parser.add_argument('--feature_size', type=int, default=6, help='Number of features') + parser.add_argument('--hidden_size', type=int, default=512, help='Size of the hidden layer') + parser.add_argument('--output_size', type=int, default=3, help='Size of the output layer') + parser.add_argument('--num_layers', type=int, default=2, help='Number of GRU layers') + parser.add_argument('--epochs', type=int, default=10, help='Number of training epochs') + parser.add_argument('--learning_rate', type=float, default=0.001, help='Learning rate') + parser.add_argument('--model_name', type=str, default='gru', help='Name of the model') + parser.add_argument('--save_path', type=str, default='./{}.pth'.format('gru'), help='Path to save the best model') + parser.add_argument('--no_cuda', action='store_true', default=False, help='Disable CUDA') + args = parser.parse_args() + return args +args = parser_args() + + +#新模型 +class Config(): + + timestep = 4 # 时间步长,就是利用多少时间窗口 + batch_size = 32 # 批次大小 + feature_size = 6 # 每个步长对应的特征数量,这里使用8维特征 + hidden_size = 512 # 隐层大小 + output_size = 3 # 由于是单输出任务 + num_layers = 4 # gru的层数 + epochs = 32 # 迭代轮数 + best_loss = float('inf') # 记录损失,初始化为正无穷大 + learning_rate = 0.001 # 学习率 + model_name = 'gru' # 模型名称 + save_path = './{}.pth'.format(model_name) # 最优模型保存路径 + +config = Config() + +# 划分数据 +def split_data(data, timestep,target_size): + dataX = [] + dataY = [] + for index in range(len(data) - timestep): + dataX.append(data[index: index + timestep]) + dataY.append(data[index + timestep, :target_size]) + dataX = np.array(dataX) + dataY = np.array(dataY) + train_size = int(np.round(0.7*dataX.shape[0])) + x_train = dataX[:train_size, :] + y_train = dataY[:train_size, :] + x_test = dataX[train_size:, :] + y_test = dataY[train_size:, :] + + return x_train, y_train, x_test, y_test + +# 定义网络 +class GRU(nn.Module): + def __init__(self, feature_size, hidden_size, num_layers, output_size): + super(GRU, self).__init__() + self.hidden_size = hidden_size + self.num_layers = num_layers + self.gru = nn.GRU(feature_size, hidden_size, num_layers, batch_first=True) + self.fc = nn.Linear(hidden_size, output_size) + self.activation = nn.ReLU() # 添加激活函数 + + def forward(self, x, hidden=None): + batch_size = x.shape[0] + if hidden is None: + h_0 = x.data.new(self.num_layers, batch_size, self.hidden_size).fill_(0).float() + else: + h_0 = hidden + output, h_0 = self.gru(x, h_0) + output = self.fc(output[:, -1, :]) + output = self.activation(output) # 添加激活函数 + return output, h_0 + + + + +# 获取训练数据 +x_train, y_train, x_test, y_test = split_data(data, config.timestep, config.feature_size) +target_size = 3 # 三个目标列的大小 +x_train, y_train, x_test, y_test = split_data(data, args.timestep,target_size) + +#形成训练数据集 +x_train_tensor = torch.Tensor(x_train) +y_train_tensor = torch.Tensor(y_train) +x_test_tensor = torch.Tensor(x_test) +y_test_tensor = torch.Tensor(y_test) + +# 假设您的模型类是GRU,config中包含相关的模型配置信息 +pre_trained_model = GRU(args.feature_size, args.hidden_size, args.num_layers, args.output_size) +# 加载预训练模型的参数 +# 加载预训练模型的参数 +model_path = "gru_a.pth" +pre_trained_model = torch.load(model_path, map_location='cpu') + +transfer_model = GRU(config.feature_size, config.hidden_size, config.num_layers,config.output_size) # 定义GRU网络 + +loss_function = nn.MSELoss() # 定义损失函数 +optimizer_transfer = torch.optim.AdamW(transfer_model.parameters(), lr= config.learning_rate) # 定义优化器 + + +# 将公共模型的参数设为不可训练 +for param in pre_trained_model.parameters(): + param.requires_grad = False +print(y_train_tensor) + +# train_dataset = TensorDataset(x_train_tensor, y_train_tensor) +# # 创建 DataLoader +# train_loader = DataLoader(train_dataset, batch_size=config.batch_size, shuffle=True) + +for epoch in range(num_transfer_epochs): + + transfer_model.train() + running_loss = 0 + + optimizer_transfer.zero_grad() + # 手动清除隐藏状态 + hidden = None + + # 从数据中获取输入和目标张量 + inputs = x_train_tensor + targets = y_train_tensor + + y_train_pred,hidden = transfer_model(inputs) + y_pretrained_pred,hidden = pre_trained_model(inputs) + + loss = (1 - u) * loss_function(y_train_pred, targets) + u * loss_function(y_train_pred, y_pretrained_pred) + # 反向传播和参数更新 + loss.backward() + optimizer_transfer.step() + + running_loss += loss.item() + + # 将运行中的损失打印出来,以便监控训练过程 + print(f"Epoch {epoch+1}/{num_transfer_epochs}, Loss: {running_loss}") + + +# 保存迁移模型 +transfer_model_save_path = './ta_model.pth' # 定义迁移模型保存路径 +torch.save(transfer_model.state_dict(), transfer_model_save_path) + +print('Finished Transfer Learning') + +# 测试循环 +transfer_model.eval() # 将模型设置为评估模式 + +# with torch.no_grad(): # 在测试期间禁用梯度计算 +# test_inputs = x_test_tensor +# test_targets = y_test_tensor +# +# test_predictions, _ = transfer_model(test_inputs) +# test_loss = loss_function(test_predictions, test_targets) +# + + +with torch.no_grad(): # 在测试期间禁用梯度计算 + test_inputs = x_test_tensor + test_targets = y_test_tensor + + test_predictions, _ = transfer_model(test_inputs) + test_loss = loss_function(test_predictions, test_targets) + + + # 创建一个文件保存输入数据 + with open('input_data.txt', 'w') as input_file: + # 打印每次的数据输入和预测结果 + for i in range(len(test_inputs)): + input_data = test_inputs[i].numpy() + target_data = test_targets[i].numpy() + predicted_data = test_predictions[i].numpy() + + print(f"样本 {i + 1}:") + print("输入数据:") + for row in input_data: + print(" ".join(map(str, row))) + + print("目标数据:", target_data) + print("预测数据:", predicted_data) + print("----------") + + # 将输入数据写入文件 + input_file.write(f"样本 {i + 1}:\n") + input_file.write("输入数据:\n") + for row in input_data: + input_file.write(" ".join(map(str, row)) + "\n") + + input_file.write("目标数据:" + repr(target_data.tolist()) + "\n") + input_file.write("预测数据:" + repr(predicted_data.tolist()) + "\n") + input_file.write("----------\n") + +# print(f"测试损失: {test_loss.item()}") + +# # 将测试结果保存到CSV文件 +# result_df = pd.DataFrame({ +# 'Actual_cpu': test_targets[:, 0].numpy(), +# 'Actual_gpu': test_targets[:, 1].numpy(), +# 'Actual_mem': test_targets[:, 2].numpy(), +# 'Predicted_cpu': test_predictions[:, 0].numpy(), +# 'Predicted_gpu': test_predictions[:, 1].numpy(), +# 'Predicted_mem': test_predictions[:, 2].numpy(), +# }) +# +# result_df.to_csv('pa_results.csv', index=False) \ No newline at end of file -- Gitee From 811d062e52536c2ad4454e8cba7933ccc95cd23a Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 04:16:43 +0000 Subject: [PATCH 06/25] =?UTF-8?q?=E6=96=B0=E5=BB=BA=20all-process?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- subject2-pre/all-process/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 subject2-pre/all-process/.keep diff --git a/subject2-pre/all-process/.keep b/subject2-pre/all-process/.keep new file mode 100644 index 0000000..e69de29 -- Gitee From 26fa90a611604f23fc7589ea3672413ad13a247d Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 04:20:47 +0000 Subject: [PATCH 07/25] =?UTF-8?q?=E6=8F=8F=E8=BF=B0=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> --- subject2-pre/README.md | 88 +++++++ subject2-pre/environment.md | 478 ++++++++++++++++++++++++++++++++++ subject2-pre/environment.yaml | 478 ++++++++++++++++++++++++++++++++++ 3 files changed, 1044 insertions(+) create mode 100644 subject2-pre/README.md create mode 100644 subject2-pre/environment.md create mode 100644 subject2-pre/environment.yaml diff --git a/subject2-pre/README.md b/subject2-pre/README.md new file mode 100644 index 0000000..89186e6 --- /dev/null +++ b/subject2-pre/README.md @@ -0,0 +1,88 @@ +# JC-workload-predictor + + + +This repository provides the implementation of the paper "Joint Privacy-Preserving Cloud Workload Prediction Based on Federated Learning", which is published in XXX. +In this paper, we propose a joint privacy-preserving cloud workload prediction framework based on Federated Learning (FL), which enables the collaborative training of a workload prediction model across cloud providers with heterogeneous workloads and model preferences without exposing private workload data. +we first design a Generative Adversarial Network (GAN) based virtual workload dataset generation method that incorporates workloads' temporal and resource-usage-plan-related features via temporal-aware feature calibration. +Then, we design a FL approach based on a hybrid local model composed of a homogeneous public model and a heterogeneous personal model. +Finally, we design a Knowledge Distillation (KD) based approach to post-train the personal model with both private workload knowledge and shared workload knowledge learned from the trained global public model. +Extensive experiments on various real-world workloads demonstrate that compared with the state-of-the-art, our framework improves workload prediction accuracy by 45.5\% in average over all cloud providers. + + + + + + + + + + +
MSEs of memory utilization prediction results (lower is better).MSEs of CPU utilization prediction results (lower is better).
+ +Our framework consists of the following three key components: +

+ +

+ + + +## 1. Virtual Workload Dataset Generation + + + +

+ +

+ +The code in the folder [code_generate_data](https://github.com/liyan2015/JC-workload-predictor/tree/main/code/generate_data) is the code to generate virtual data, including GAN, VHL and our TVHL. +The input is the path of the dataset. + + + + +## 2. Federated Learning with Virtual Workload Datasets + + +FedAvg was used for training, using different datasets. + +Folder [code_fedavg](https://github.com/liyan2015/JC-workload-predictor/tree/main/code/fedavg) is using the actual dataset. + +Folder [code_ourfl-nokd](https://github.com/liyan2015/JC-workload-predictor/tree/main/code/ourfl-nokd) is the use of virtual generated dataset. + + + +## 3. Post Training of Personal Models. + + +The code in the folde [code_ourfl_kd](https://github.com/liyan2015/JC-workload-predictor/tree/main/code/ourfl_kd) shows the training of the individual model after the local public model is trained. + + + +## Prerequisites + +To run the code, it needs some libraies: + +- Python >= 3.7 +- Pytorch >= 2.0.1 +- torchvision >= 0.15.2 + +Our environment is shown in the file, named [environment.yaml](https://github.com/liyan2015/JC-workload-predictor/blob/main/environment.yaml). + +## Citing + + + +If you use this repository, please cite: +```bibtex +@article{, + title={}, + author={Bin yang and Yan, Li}, + journal={}, + volume={}, + year={}, + publisher={} +} +``` + + diff --git a/subject2-pre/environment.md b/subject2-pre/environment.md new file mode 100644 index 0000000..17d56bf --- /dev/null +++ b/subject2-pre/environment.md @@ -0,0 +1,478 @@ +astropy 5.1 +asttokens 2.0.5 +astunparse 1.6.3 +atomicwrites 1.4.0 +attrs 22.1.0 +automat 20.2.0 +autopep8 1.6.0 +babel 2.11.0 +backcall 0.2.0 +backports 1.1 +backports.functools_lru_cache 1.6.4 +backports.tempfile 1.0 +backports.weakref 1.0.post1 +bcrypt 3.2.0 +beautifulsoup4 4.11.1 +binaryornot 0.4.4 +black 22.6.0 +blas 1.0 +bleach 4.1.0 +blosc 1.21.3 +bokeh 2.4.3 +boltons 23.0.0 +bottleneck 1.3.5 +brotli 1.0.9 +brotli-bin 1.0.9 +brotlipy 0.7.0 +bs4 0.0.1 +bzip2 1.0.8 +ca-certificates 2023.5.7 +cachetools 5.3.0 +certifi 2023.5.7 +cffi 1.15.1 +cfitsio 3.470 +chardet 4.0.0 +charls 2.2.0 +charset-normalizer 2.0.4 +click 8.0.4 +cloudpickle 2.0.0 +clyent 1.2.1 +colorama 0.4.6 +colorcet 3.0.1 +comm 0.1.2 +conda 23.3.1 +conda-build 3.24.0 +conda-content-trust 0.1.3 +conda-pack 0.6.0 +conda-package-handling 2.0.2 +conda-package-streaming 0.7.0 +conda-repo-cli 1.0.41 +conda-token 0.4.0 +conda-verify 3.4.2 +console_shortcut 0.1.1 +constantly 15.1.0 +contourpy 1.0.5 +cookiecutter 1.7.3 +cryptography 39.0.1 +cssselect 1.1.0 +cuda-cccl 12.1.109 +cuda-cudart 11.7.99 +cuda-cudart-dev 11.7.99 +cuda-cupti 11.7.101 +cuda-libraries 11.7.1 +cuda-libraries-dev 11.7.1 +cuda-nvrtc 11.7.99 +cuda-nvrtc-dev 11.7.99 +cuda-nvtx 11.7.91 +cuda-runtime 11.7.1 +curl 7.87.0 +cycler 0.11.0 +cytoolz 0.12.0 +daal4py 2023.0.2 +dal 2023.0.1 +dask 2022.7.0 +dask-core 2022.7.0 +datashader 0.14.4 +datashape 0.5.4 +debugpy 1.5.1 +decorator 5.1.1 +defusedxml 0.7.1 +diff-match-patch 20200713 +dill 0.3.6 +distributed 2022.7.0 +dm-tree 0.1.8 +docstring-to-markdown 0.11 +docutils 0.18.1 +easydict 1.10 +entrypoints 0.4 +et_xmlfile 1.1.0 +executing 0.8.3 +filelock 3.9.0 +flake8 6.0.0 +flask 2.2.2 +flatbuffers 23.5.9 +flit-core 3.6.0 +fonttools 4.25.0 +freetype 2.12.1 +fsspec 2022.11.0 +fst-pso 1.8.1 +future 0.18.3 +fuzzytm 2.0.5 +gast 0.4.0 +gensim 4.3.0 +giflib 5.2.1 +glib 2.69.1 +glob2 0.7 +google-auth 2.18.1 +google-auth-oauthlib 1.0.0 +google-pasta 0.2.0 +greenlet 2.0.1 +grpcio 1.54.2 +gst-plugins-base 1.18.5 +gstreamer 1.18.5 +h5py 3.7.0 +hdf5 1.10.6 +heapdict 1.0.1 +holoviews 1.15.4 +huggingface_hub 0.10.1 +hvplot 0.8.2 +hyperlink 21.0.0 +icc_rt 2022.1.0 +icu 58.2 +idna 3.4 +imagecodecs 2021.8.26 +imageio 2.26.0 +imagesize 1.4.1 +imbalanced-learn 0.10.1 +importlib-metadata 4.11.3 +importlib_metadata 4.11.3 +incremental 21.3.0 +inflection 0.5.1 +iniconfig 1.1.1 +intake 0.6.7 +intel-openmp 2021.4.0 +intervaltree 3.1.0 +ipykernel 6.19.2 +ipython 8.10.0 +ipython_genutils 0.2.0 +ipywidgets 7.6.5 +isort 5.9.3 +itemadapter 0.3.0 +itemloaders 1.0.4 +itsdangerous 2.0.1 +jax 0.4.10 +jedi 0.18.1 +jellyfish 0.9.0 +jinja2 3.1.2 +jinja2-time 0.2.0 +jmespath 0.10.0 +joblib 1.1.1 +jpeg 9e +jq 1.6 +json5 0.9.6 +jsonpatch 1.32 +jsonpointer 2.1 +jsonschema 4.17.3 +jupyter 1.0.0 +jupyter_client 7.3.4 +jupyter_console 6.6.2 +jupyter_core 5.2.0 +jupyter_server 1.23.4 +jupyterlab 3.5.3 +jupyterlab_pygments 0.1.2 +jupyterlab_server 2.19.0 +jupyterlab_widgets 1.0.0 +jxrlib 1.1 +keras 2.12.0 +keyring 23.4.0 +kiwisolver 1.4.4 +lazy-object-proxy 1.6.0 +lcms2 2.12 +lerc 3.0 +libaec 1.0.4 +libarchive 3.6.2 +libbrotlicommon 1.0.9 +libbrotlidec 1.0.9 +libbrotlienc 1.0.9 +libclang 16.0.0 +libcublas 11.10.3.66 +libcublas-dev 11.10.3.66 +libcufft 10.7.2.124 +libcufft-dev 10.7.2.124 +libcurand 10.3.2.106 +libcurand-dev 10.3.2.106 +libcurl 7.87.0 +libcusolver 11.4.0.1 +libcusolver-dev 11.4.0.1 +libcusparse 11.7.4.91 +libcusparse-dev 11.7.4.91 +libdeflate 1.17 +libffi 3.4.2 +libiconv 1.16 +liblief 0.12.3 +libnpp 11.7.4.75 +libnpp-dev 11.7.4.75 +libnvjpeg 11.8.0.2 +libnvjpeg-dev 11.8.0.2 +libogg 1.3.5 +libpng 1.6.39 +libsodium 1.0.18 +libspatialindex 1.9.3 +libssh2 1.10.0 +libtiff 4.5.0 +libuv 1.44.2 +libvorbis 1.3.7 +libwebp 1.2.4 +libwebp-base 1.2.4 +libxml2 2.9.14 +libxslt 1.1.35 +libzopfli 1.0.3 +llvmlite 0.39.1 +locket 1.0.0 +lxml 4.9.1 +lz4 3.1.3 +lz4-c 1.9.4 +lzo 2.10 +m2-msys2-runtime 2.5.0.17080.65c939c +m2-patch 2.7.5 +m2w64-libwinpthread-git 5.0.0.4634.697f757 +markdown 3.4.1 +markupsafe 2.1.1 +matplotlib 3.7.0 +matplotlib-base 3.7.0 +matplotlib-inline 0.1.6 +matplotlib-venn 0.11.9 +mccabe 0.7.0 +menuinst 1.4.19 +miniful 0.0.6 +mistune 0.8.4 +mkl 2021.4.0 +mkl-service 2.4.0 +mkl_fft 1.3.1 +mkl_random 1.2.2 +ml-dtypes 0.1.0 +mock 4.0.3 +mpmath 1.2.1 +msgpack-python 1.0.3 +msys2-conda-epoch 20160418 +multipledispatch 0.6.0 +munkres 1.1.4 +mypy_extensions 0.4.3 +navigator-updater 0.3.0 +nbclassic 0.5.2 +nbclient 0.5.13 +nbconvert 6.5.4 +nbformat 5.4.0 +nest-asyncio 1.5.6 +networkx 2.8.4 +ninja 1.10.2 +ninja-base 1.10.2 +nltk 3.7 +notebook 6.5.2 +notebook-shim 0.2.2 +numba 0.56.4 +numexpr 2.8.4 +numpy 1.23.0 +numpydoc 1.5.0 +oauthlib 3.2.2 +openjpeg 2.4.0 +openpyxl 3.0.10 +openssl 1.1.1t +opt-einsum 3.3.0 +packaging 22.0 +pandarallel 1.6.5 +pandas 1.5.3 +pandocfilters 1.5.0 +panel 0.14.3 +param 1.12.3 +paramiko 2.8.1 +parsel 1.6.0 +parso 0.8.3 +partd 1.2.0 +pathlib 1.0.1 +pathspec 0.10.3 +patsy 0.5.3 +pcre 8.45 +pep8 1.7.1 +pexpect 4.8.0 +pickleshare 0.7.5 +pillow 9.4.0 +pip 22.3.1 +pkginfo 1.9.6 +platformdirs 2.5.2 +plotly 5.9.0 +pluggy 1.0.0 +ply 3.11 +pooch 1.4.0 +powershell_shortcut 0.0.1 +poyo 0.5.0 +prometheus_client 0.14.1 +prompt-toolkit 3.0.36 +prompt_toolkit 3.0.36 +protego 0.1.16 +protobuf 4.23.1 +psutil 5.9.0 +ptyprocess 0.7.0 +pure_eval 0.2.2 +py 1.11.0 +py-lief 0.12.3 +pyasn1 0.4.8 +pyasn1-modules 0.2.8 +pycodestyle 2.10.0 +pycosat 0.6.4 +pycparser 2.21 +pyct 0.5.0 +pycurl 7.45.1 +pydispatcher 2.0.5 +pydocstyle 6.3.0 +pyerfa 2.0.0 +pyflakes 3.0.1 +pyfume 0.2.25 +pygments 2.11.2 +pyhamcrest 2.0.2 +pyjwt 2.4.0 +pylint 2.16.2 +pylint-venv 2.3.0 +pyls-spyder 0.4.0 +pynacl 1.5.0 +pyodbc 4.0.34 +pyopenssl 23.0.0 +pyparsing 3.0.9 +pyqt 5.15.7 +pyqt5-sip 12.11.0 +pyqtwebengine 5.15.7 +pyrsistent 0.18.0 +pysocks 1.7.1 +pytables 3.7.0 +pytest 6.2.5 +python 3.10.9 +python-dateutil 2.8.2 +python-fastjsonschema 2.16.2 +python-libarchive-c 2.9 +python-lsp-black 1.2.1 +python-lsp-jsonrpc 1.0.0 +python-lsp-server 1.7.1 +python-slugify 5.0.2 +python-snappy 0.6.1 +pytoolconfig 1.2.5 +pytorch 2.0.1 +pytorch-cuda 11.7 +pytorch-mutex 1.0 +pytz 2022.7 +pyviz_comms 2.0.2 +pywavelets 1.4.1 +pywin32 305 +pywin32-ctypes 0.2.0 +pywinpty 2.0.10 +pyyaml 6.0 +pyzmq 23.2.0 +qdarkstyle 3.0.2 +qstylizer 0.2.2 +qt-main 5.15.2 +qt-webengine 5.15.9 +qtawesome 1.2.2 +qtconsole 5.4.0 +qtpy 2.2.0 +qtwebkit 5.212 +queuelib 1.5.0 +regex 2022.7.9 +requests 2.28.1 +requests-file 1.5.1 +requests-mock 1.11.0 +requests-oauthlib 1.3.1 +requests-toolbelt 0.9.1 +rope 1.7.0 +rsa 4.9 +rtree 1.0.1 +ruamel.yaml 0.17.21 +ruamel.yaml.clib 0.2.6 +ruamel_yaml 0.17.21 +scikit-image 0.19.3 +scikit-learn 1.2.1 +scikit-learn-intelex 2023.0.2 +scipy 1.10.0 +scrapy 2.8.0 +seaborn 0.12.2 +send2trash 1.8.0 +service_identity 18.1.0 +setuptools 68.0.0 +simpful 2.11.0 +simplejson 3.19.1 +sip 6.6.2 +six 1.16.0 +smart_open 5.2.1 +snappy 1.1.9 +sniffio 1.2.0 +snowballstemmer 2.2.0 +sortedcontainers 2.4.0 +soupsieve 2.3.2.post1 +sphinx 5.0.2 +sphinxcontrib-applehelp 1.0.2 +sphinxcontrib-devhelp 1.0.2 +sphinxcontrib-htmlhelp 2.0.0 +sphinxcontrib-jsmath 1.0.1 +sphinxcontrib-qthelp 1.0.3 +sphinxcontrib-serializinghtml 1.1.5 +spyder 5.4.1 +spyder-kernels 2.4.1 +sqlalchemy 1.4.39 +sqlite 3.40.1 +stack_data 0.2.0 +statsmodels 0.13.5 +supervenn 0.4.1 +sympy 1.11.1 +tabulate 0.8.10 +tbb 2021.7.0 +tbb4py 2021.7.0 +tblib 1.7.0 +tenacity 8.0.1 +tensorboard 2.12.3 +tensorboard-data-server 0.6.1 +tensorboard-plugin-wit 1.8.1 +tensorflow 2.12.0 +tensorflow-estimator 2.11.0 +tensorflow-intel 2.12.0 +tensorflow-io-gcs-filesystem 0.31.0 +tensorflow-probability 0.19.0 +termcolor 2.3.0 +terminado 0.17.1 +text-unidecode 1.3 +textdistance 4.2.1 +threadpoolctl 2.2.0 +three-merge 0.1.1 +tifffile 2021.7.2 +tinycss2 1.2.1 +tk 8.6.12 +tldextract 3.2.0 +tokenizers 0.11.4 +toml 0.10.2 +tomli 2.0.1 +tomlkit 0.11.1 +toolz 0.12.0 +torchaudio 2.0.2 +torchvision 0.15.2 +tornado 6.1 +tqdm 4.64.1 +traitlets 5.7.1 +transformers 4.24.0 +tushare 1.2.89 +twisted 22.2.0 +twisted-iocpsupport 1.0.2 +typeguard 2.13.3 +typing-extensions 4.4.0 +typing_extensions 4.4.0 +tzdata 2023.3 +ujson 5.4.0 +unidecode 1.2.0 +upsetplot 0.8.0 +urllib3 1.26.14 +utils 1.0.1 +vc 14.2 +vs2015_runtime 14.27.29016 +w3lib 1.21.0 +watchdog 2.1.6 +wcwidth 0.2.5 +webencodings 0.5.1 +websocket-client 0.57.0 +werkzeug 2.2.2 +whatthepatch 1.0.2 +wheel 0.40.0 +widgetsnbextension 3.5.2 +win_inet_pton 1.1.0 +wincertstore 0.2 +winpty 0.4.3 +wrapt 1.14.1 +xarray 2022.11.0 +xlwings 0.29.1 +xz 5.2.10 +yaml 0.2.5 +yapf 0.31.0 +zeromq 4.3.4 +zfp 0.5.5 +zict 2.1.0 +zipp 3.11.0 +zlib 1.2.13 +zope 1.0 +zope.interface 5.4.0 +zstandard 0.19.0 +zstd 1.5.2 diff --git a/subject2-pre/environment.yaml b/subject2-pre/environment.yaml new file mode 100644 index 0000000..17d56bf --- /dev/null +++ b/subject2-pre/environment.yaml @@ -0,0 +1,478 @@ +astropy 5.1 +asttokens 2.0.5 +astunparse 1.6.3 +atomicwrites 1.4.0 +attrs 22.1.0 +automat 20.2.0 +autopep8 1.6.0 +babel 2.11.0 +backcall 0.2.0 +backports 1.1 +backports.functools_lru_cache 1.6.4 +backports.tempfile 1.0 +backports.weakref 1.0.post1 +bcrypt 3.2.0 +beautifulsoup4 4.11.1 +binaryornot 0.4.4 +black 22.6.0 +blas 1.0 +bleach 4.1.0 +blosc 1.21.3 +bokeh 2.4.3 +boltons 23.0.0 +bottleneck 1.3.5 +brotli 1.0.9 +brotli-bin 1.0.9 +brotlipy 0.7.0 +bs4 0.0.1 +bzip2 1.0.8 +ca-certificates 2023.5.7 +cachetools 5.3.0 +certifi 2023.5.7 +cffi 1.15.1 +cfitsio 3.470 +chardet 4.0.0 +charls 2.2.0 +charset-normalizer 2.0.4 +click 8.0.4 +cloudpickle 2.0.0 +clyent 1.2.1 +colorama 0.4.6 +colorcet 3.0.1 +comm 0.1.2 +conda 23.3.1 +conda-build 3.24.0 +conda-content-trust 0.1.3 +conda-pack 0.6.0 +conda-package-handling 2.0.2 +conda-package-streaming 0.7.0 +conda-repo-cli 1.0.41 +conda-token 0.4.0 +conda-verify 3.4.2 +console_shortcut 0.1.1 +constantly 15.1.0 +contourpy 1.0.5 +cookiecutter 1.7.3 +cryptography 39.0.1 +cssselect 1.1.0 +cuda-cccl 12.1.109 +cuda-cudart 11.7.99 +cuda-cudart-dev 11.7.99 +cuda-cupti 11.7.101 +cuda-libraries 11.7.1 +cuda-libraries-dev 11.7.1 +cuda-nvrtc 11.7.99 +cuda-nvrtc-dev 11.7.99 +cuda-nvtx 11.7.91 +cuda-runtime 11.7.1 +curl 7.87.0 +cycler 0.11.0 +cytoolz 0.12.0 +daal4py 2023.0.2 +dal 2023.0.1 +dask 2022.7.0 +dask-core 2022.7.0 +datashader 0.14.4 +datashape 0.5.4 +debugpy 1.5.1 +decorator 5.1.1 +defusedxml 0.7.1 +diff-match-patch 20200713 +dill 0.3.6 +distributed 2022.7.0 +dm-tree 0.1.8 +docstring-to-markdown 0.11 +docutils 0.18.1 +easydict 1.10 +entrypoints 0.4 +et_xmlfile 1.1.0 +executing 0.8.3 +filelock 3.9.0 +flake8 6.0.0 +flask 2.2.2 +flatbuffers 23.5.9 +flit-core 3.6.0 +fonttools 4.25.0 +freetype 2.12.1 +fsspec 2022.11.0 +fst-pso 1.8.1 +future 0.18.3 +fuzzytm 2.0.5 +gast 0.4.0 +gensim 4.3.0 +giflib 5.2.1 +glib 2.69.1 +glob2 0.7 +google-auth 2.18.1 +google-auth-oauthlib 1.0.0 +google-pasta 0.2.0 +greenlet 2.0.1 +grpcio 1.54.2 +gst-plugins-base 1.18.5 +gstreamer 1.18.5 +h5py 3.7.0 +hdf5 1.10.6 +heapdict 1.0.1 +holoviews 1.15.4 +huggingface_hub 0.10.1 +hvplot 0.8.2 +hyperlink 21.0.0 +icc_rt 2022.1.0 +icu 58.2 +idna 3.4 +imagecodecs 2021.8.26 +imageio 2.26.0 +imagesize 1.4.1 +imbalanced-learn 0.10.1 +importlib-metadata 4.11.3 +importlib_metadata 4.11.3 +incremental 21.3.0 +inflection 0.5.1 +iniconfig 1.1.1 +intake 0.6.7 +intel-openmp 2021.4.0 +intervaltree 3.1.0 +ipykernel 6.19.2 +ipython 8.10.0 +ipython_genutils 0.2.0 +ipywidgets 7.6.5 +isort 5.9.3 +itemadapter 0.3.0 +itemloaders 1.0.4 +itsdangerous 2.0.1 +jax 0.4.10 +jedi 0.18.1 +jellyfish 0.9.0 +jinja2 3.1.2 +jinja2-time 0.2.0 +jmespath 0.10.0 +joblib 1.1.1 +jpeg 9e +jq 1.6 +json5 0.9.6 +jsonpatch 1.32 +jsonpointer 2.1 +jsonschema 4.17.3 +jupyter 1.0.0 +jupyter_client 7.3.4 +jupyter_console 6.6.2 +jupyter_core 5.2.0 +jupyter_server 1.23.4 +jupyterlab 3.5.3 +jupyterlab_pygments 0.1.2 +jupyterlab_server 2.19.0 +jupyterlab_widgets 1.0.0 +jxrlib 1.1 +keras 2.12.0 +keyring 23.4.0 +kiwisolver 1.4.4 +lazy-object-proxy 1.6.0 +lcms2 2.12 +lerc 3.0 +libaec 1.0.4 +libarchive 3.6.2 +libbrotlicommon 1.0.9 +libbrotlidec 1.0.9 +libbrotlienc 1.0.9 +libclang 16.0.0 +libcublas 11.10.3.66 +libcublas-dev 11.10.3.66 +libcufft 10.7.2.124 +libcufft-dev 10.7.2.124 +libcurand 10.3.2.106 +libcurand-dev 10.3.2.106 +libcurl 7.87.0 +libcusolver 11.4.0.1 +libcusolver-dev 11.4.0.1 +libcusparse 11.7.4.91 +libcusparse-dev 11.7.4.91 +libdeflate 1.17 +libffi 3.4.2 +libiconv 1.16 +liblief 0.12.3 +libnpp 11.7.4.75 +libnpp-dev 11.7.4.75 +libnvjpeg 11.8.0.2 +libnvjpeg-dev 11.8.0.2 +libogg 1.3.5 +libpng 1.6.39 +libsodium 1.0.18 +libspatialindex 1.9.3 +libssh2 1.10.0 +libtiff 4.5.0 +libuv 1.44.2 +libvorbis 1.3.7 +libwebp 1.2.4 +libwebp-base 1.2.4 +libxml2 2.9.14 +libxslt 1.1.35 +libzopfli 1.0.3 +llvmlite 0.39.1 +locket 1.0.0 +lxml 4.9.1 +lz4 3.1.3 +lz4-c 1.9.4 +lzo 2.10 +m2-msys2-runtime 2.5.0.17080.65c939c +m2-patch 2.7.5 +m2w64-libwinpthread-git 5.0.0.4634.697f757 +markdown 3.4.1 +markupsafe 2.1.1 +matplotlib 3.7.0 +matplotlib-base 3.7.0 +matplotlib-inline 0.1.6 +matplotlib-venn 0.11.9 +mccabe 0.7.0 +menuinst 1.4.19 +miniful 0.0.6 +mistune 0.8.4 +mkl 2021.4.0 +mkl-service 2.4.0 +mkl_fft 1.3.1 +mkl_random 1.2.2 +ml-dtypes 0.1.0 +mock 4.0.3 +mpmath 1.2.1 +msgpack-python 1.0.3 +msys2-conda-epoch 20160418 +multipledispatch 0.6.0 +munkres 1.1.4 +mypy_extensions 0.4.3 +navigator-updater 0.3.0 +nbclassic 0.5.2 +nbclient 0.5.13 +nbconvert 6.5.4 +nbformat 5.4.0 +nest-asyncio 1.5.6 +networkx 2.8.4 +ninja 1.10.2 +ninja-base 1.10.2 +nltk 3.7 +notebook 6.5.2 +notebook-shim 0.2.2 +numba 0.56.4 +numexpr 2.8.4 +numpy 1.23.0 +numpydoc 1.5.0 +oauthlib 3.2.2 +openjpeg 2.4.0 +openpyxl 3.0.10 +openssl 1.1.1t +opt-einsum 3.3.0 +packaging 22.0 +pandarallel 1.6.5 +pandas 1.5.3 +pandocfilters 1.5.0 +panel 0.14.3 +param 1.12.3 +paramiko 2.8.1 +parsel 1.6.0 +parso 0.8.3 +partd 1.2.0 +pathlib 1.0.1 +pathspec 0.10.3 +patsy 0.5.3 +pcre 8.45 +pep8 1.7.1 +pexpect 4.8.0 +pickleshare 0.7.5 +pillow 9.4.0 +pip 22.3.1 +pkginfo 1.9.6 +platformdirs 2.5.2 +plotly 5.9.0 +pluggy 1.0.0 +ply 3.11 +pooch 1.4.0 +powershell_shortcut 0.0.1 +poyo 0.5.0 +prometheus_client 0.14.1 +prompt-toolkit 3.0.36 +prompt_toolkit 3.0.36 +protego 0.1.16 +protobuf 4.23.1 +psutil 5.9.0 +ptyprocess 0.7.0 +pure_eval 0.2.2 +py 1.11.0 +py-lief 0.12.3 +pyasn1 0.4.8 +pyasn1-modules 0.2.8 +pycodestyle 2.10.0 +pycosat 0.6.4 +pycparser 2.21 +pyct 0.5.0 +pycurl 7.45.1 +pydispatcher 2.0.5 +pydocstyle 6.3.0 +pyerfa 2.0.0 +pyflakes 3.0.1 +pyfume 0.2.25 +pygments 2.11.2 +pyhamcrest 2.0.2 +pyjwt 2.4.0 +pylint 2.16.2 +pylint-venv 2.3.0 +pyls-spyder 0.4.0 +pynacl 1.5.0 +pyodbc 4.0.34 +pyopenssl 23.0.0 +pyparsing 3.0.9 +pyqt 5.15.7 +pyqt5-sip 12.11.0 +pyqtwebengine 5.15.7 +pyrsistent 0.18.0 +pysocks 1.7.1 +pytables 3.7.0 +pytest 6.2.5 +python 3.10.9 +python-dateutil 2.8.2 +python-fastjsonschema 2.16.2 +python-libarchive-c 2.9 +python-lsp-black 1.2.1 +python-lsp-jsonrpc 1.0.0 +python-lsp-server 1.7.1 +python-slugify 5.0.2 +python-snappy 0.6.1 +pytoolconfig 1.2.5 +pytorch 2.0.1 +pytorch-cuda 11.7 +pytorch-mutex 1.0 +pytz 2022.7 +pyviz_comms 2.0.2 +pywavelets 1.4.1 +pywin32 305 +pywin32-ctypes 0.2.0 +pywinpty 2.0.10 +pyyaml 6.0 +pyzmq 23.2.0 +qdarkstyle 3.0.2 +qstylizer 0.2.2 +qt-main 5.15.2 +qt-webengine 5.15.9 +qtawesome 1.2.2 +qtconsole 5.4.0 +qtpy 2.2.0 +qtwebkit 5.212 +queuelib 1.5.0 +regex 2022.7.9 +requests 2.28.1 +requests-file 1.5.1 +requests-mock 1.11.0 +requests-oauthlib 1.3.1 +requests-toolbelt 0.9.1 +rope 1.7.0 +rsa 4.9 +rtree 1.0.1 +ruamel.yaml 0.17.21 +ruamel.yaml.clib 0.2.6 +ruamel_yaml 0.17.21 +scikit-image 0.19.3 +scikit-learn 1.2.1 +scikit-learn-intelex 2023.0.2 +scipy 1.10.0 +scrapy 2.8.0 +seaborn 0.12.2 +send2trash 1.8.0 +service_identity 18.1.0 +setuptools 68.0.0 +simpful 2.11.0 +simplejson 3.19.1 +sip 6.6.2 +six 1.16.0 +smart_open 5.2.1 +snappy 1.1.9 +sniffio 1.2.0 +snowballstemmer 2.2.0 +sortedcontainers 2.4.0 +soupsieve 2.3.2.post1 +sphinx 5.0.2 +sphinxcontrib-applehelp 1.0.2 +sphinxcontrib-devhelp 1.0.2 +sphinxcontrib-htmlhelp 2.0.0 +sphinxcontrib-jsmath 1.0.1 +sphinxcontrib-qthelp 1.0.3 +sphinxcontrib-serializinghtml 1.1.5 +spyder 5.4.1 +spyder-kernels 2.4.1 +sqlalchemy 1.4.39 +sqlite 3.40.1 +stack_data 0.2.0 +statsmodels 0.13.5 +supervenn 0.4.1 +sympy 1.11.1 +tabulate 0.8.10 +tbb 2021.7.0 +tbb4py 2021.7.0 +tblib 1.7.0 +tenacity 8.0.1 +tensorboard 2.12.3 +tensorboard-data-server 0.6.1 +tensorboard-plugin-wit 1.8.1 +tensorflow 2.12.0 +tensorflow-estimator 2.11.0 +tensorflow-intel 2.12.0 +tensorflow-io-gcs-filesystem 0.31.0 +tensorflow-probability 0.19.0 +termcolor 2.3.0 +terminado 0.17.1 +text-unidecode 1.3 +textdistance 4.2.1 +threadpoolctl 2.2.0 +three-merge 0.1.1 +tifffile 2021.7.2 +tinycss2 1.2.1 +tk 8.6.12 +tldextract 3.2.0 +tokenizers 0.11.4 +toml 0.10.2 +tomli 2.0.1 +tomlkit 0.11.1 +toolz 0.12.0 +torchaudio 2.0.2 +torchvision 0.15.2 +tornado 6.1 +tqdm 4.64.1 +traitlets 5.7.1 +transformers 4.24.0 +tushare 1.2.89 +twisted 22.2.0 +twisted-iocpsupport 1.0.2 +typeguard 2.13.3 +typing-extensions 4.4.0 +typing_extensions 4.4.0 +tzdata 2023.3 +ujson 5.4.0 +unidecode 1.2.0 +upsetplot 0.8.0 +urllib3 1.26.14 +utils 1.0.1 +vc 14.2 +vs2015_runtime 14.27.29016 +w3lib 1.21.0 +watchdog 2.1.6 +wcwidth 0.2.5 +webencodings 0.5.1 +websocket-client 0.57.0 +werkzeug 2.2.2 +whatthepatch 1.0.2 +wheel 0.40.0 +widgetsnbextension 3.5.2 +win_inet_pton 1.1.0 +wincertstore 0.2 +winpty 0.4.3 +wrapt 1.14.1 +xarray 2022.11.0 +xlwings 0.29.1 +xz 5.2.10 +yaml 0.2.5 +yapf 0.31.0 +zeromq 4.3.4 +zfp 0.5.5 +zict 2.1.0 +zipp 3.11.0 +zlib 1.2.13 +zope 1.0 +zope.interface 5.4.0 +zstandard 0.19.0 +zstd 1.5.2 -- Gitee From 5b7af232f85f0f3fe203634c7ecafef383a4815b Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 04:22:52 +0000 Subject: [PATCH 08/25] =?UTF-8?q?=E6=96=B0=E5=BB=BA=20Figs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- subject2-pre/Figs/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 subject2-pre/Figs/.keep diff --git a/subject2-pre/Figs/.keep b/subject2-pre/Figs/.keep new file mode 100644 index 0000000..e69de29 -- Gitee From 47d7bc34c20b94757494a00ea37fa5aa3c9f8df6 Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 04:24:17 +0000 Subject: [PATCH 09/25] =?UTF-8?q?=E6=8F=8F=E8=BF=B0=E6=96=87=E4=BB=B6?= =?UTF-8?q?=EF=BC=88=E5=9B=BE=E7=89=87=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> --- subject2-pre/Figs/framework.png | Bin 0 -> 162279 bytes subject2-pre/Figs/mem.png | Bin 0 -> 75320 bytes subject2-pre/Figs/ourgan.png | Bin 0 -> 169939 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 subject2-pre/Figs/framework.png create mode 100644 subject2-pre/Figs/mem.png create mode 100644 subject2-pre/Figs/ourgan.png diff --git a/subject2-pre/Figs/framework.png b/subject2-pre/Figs/framework.png new file mode 100644 index 0000000000000000000000000000000000000000..e4195d861c5474608eec34808519b4da09e6bbf5 GIT binary patch literal 162279 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

XHVi_1Y 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 K(Z7VWH~BC6VeS6_ literal 0 HcmV?d00001 diff --git a/subject2-pre/Figs/mem.png b/subject2-pre/Figs/mem.png new file mode 100644 index 0000000000000000000000000000000000000000..3d4272efc1f3b2ea8db5c0d11579cd037f1429b5 GIT binary patch literal 75320 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

)mLm)aIASS}Sj{cpA$L1S^FGig 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 NI^?l(m7+!P{{zT5;2!`0 literal 0 HcmV?d00001 diff --git a/subject2-pre/Figs/ourgan.png b/subject2-pre/Figs/ourgan.png new file mode 100644 index 0000000000000000000000000000000000000000..d1507144287c349fbbfa28fac3c65e8c5b76663a GIT binary patch literal 169939 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

ntiucf`n2c13z6Xv>@ge#jdnWs7vu9*TQSL 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 literal 0 HcmV?d00001 -- Gitee From 278f793827d85a8e6222f8597a3c71b1c51500a5 Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 04:27:27 +0000 Subject: [PATCH 10/25] =?UTF-8?q?=E6=8F=8F=E8=BF=B0=E6=96=87=E4=BB=B6?= =?UTF-8?q?=EF=BC=88=E5=9B=BE=E7=89=87=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> --- subject2-pre/Figs/cpu.png | Bin 0 -> 99845 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 subject2-pre/Figs/cpu.png diff --git a/subject2-pre/Figs/cpu.png b/subject2-pre/Figs/cpu.png new file mode 100644 index 0000000000000000000000000000000000000000..b8dcb9f6576e33103eca4eb7e75b05d387d174de GIT binary patch literal 99845 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 LF#%)!G>i7Q>NY2I}P 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 jS^oFMD+Bt6h9n;K*oxTcn>%mFz%L6^8 Date: Mon, 19 Feb 2024 04:28:15 +0000 Subject: [PATCH 11/25] =?UTF-8?q?=E6=96=B0=E5=BB=BA=20fedavg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- subject2-pre/all-process/fedavg/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 subject2-pre/all-process/fedavg/.keep diff --git a/subject2-pre/all-process/fedavg/.keep b/subject2-pre/all-process/fedavg/.keep new file mode 100644 index 0000000..e69de29 -- Gitee From d83ac1225e27e578eec6e455e85f85e966ddb77e Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 04:28:42 +0000 Subject: [PATCH 12/25] =?UTF-8?q?=E6=96=B0=E5=BB=BA=20generate=5Fdata?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- subject2-pre/all-process/generate_data/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 subject2-pre/all-process/generate_data/.keep diff --git a/subject2-pre/all-process/generate_data/.keep b/subject2-pre/all-process/generate_data/.keep new file mode 100644 index 0000000..e69de29 -- Gitee From a05d9f297bff431df0836a2ba3ce4fe8a19de6f3 Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 04:29:24 +0000 Subject: [PATCH 13/25] =?UTF-8?q?=E6=96=B0=E5=BB=BA=20ourfl=5Fkd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- subject2-pre/all-process/ourfl_kd/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 subject2-pre/all-process/ourfl_kd/.keep diff --git a/subject2-pre/all-process/ourfl_kd/.keep b/subject2-pre/all-process/ourfl_kd/.keep new file mode 100644 index 0000000..e69de29 -- Gitee From cd27adaa9f0f51500944eea2f38e50d6a8ed2bc0 Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 04:29:42 +0000 Subject: [PATCH 14/25] =?UTF-8?q?=E6=96=B0=E5=BB=BA=20ourfl=5Fnokd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- subject2-pre/ourfl_nokd/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 subject2-pre/ourfl_nokd/.keep diff --git a/subject2-pre/ourfl_nokd/.keep b/subject2-pre/ourfl_nokd/.keep new file mode 100644 index 0000000..e69de29 -- Gitee From 9d850678cce1c233843db358bdbeb19f7d1108ce Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 04:30:28 +0000 Subject: [PATCH 15/25] =?UTF-8?q?=E6=9C=AC=E5=9C=B0=E8=81=94=E9=82=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> --- subject2-pre/all-process/fedavg/fed_gan.py | 228 ++++++++++++++++++++ subject2-pre/all-process/fedavg/fed_tvhl.py | 225 +++++++++++++++++++ subject2-pre/all-process/fedavg/fed_vhl.py | 227 +++++++++++++++++++ subject2-pre/all-process/fedavg/fedavg.py | 224 +++++++++++++++++++ 4 files changed, 904 insertions(+) create mode 100644 subject2-pre/all-process/fedavg/fed_gan.py create mode 100644 subject2-pre/all-process/fedavg/fed_tvhl.py create mode 100644 subject2-pre/all-process/fedavg/fed_vhl.py create mode 100644 subject2-pre/all-process/fedavg/fedavg.py diff --git a/subject2-pre/all-process/fedavg/fed_gan.py b/subject2-pre/all-process/fedavg/fed_gan.py new file mode 100644 index 0000000..abfa9cb --- /dev/null +++ b/subject2-pre/all-process/fedavg/fed_gan.py @@ -0,0 +1,228 @@ +import numpy as np +import pandas as pd +import torch +import torch.nn as nn +from sklearn.preprocessing import MinMaxScaler +import torch.optim as optim # 添加导入torch.optim模块 + + +# 定义配置类 +class Config(): + timestep = 4 + batch_size = 32 + feature_size = 1 + hidden_size = 256 + output_size = 1 + num_layers = 2 + epochs = 4 + learning_rate = 0.0005 + model_name = 'gru' + save_path = './{}.pth'.format(model_name) + +config = Config() + +# 定义GRU模型 +class GRU(nn.Module): + def __init__(self, feature_size, hidden_size, num_layers, output_size): + super(GRU, self).__init__() + self.hidden_size = hidden_size + self.num_layers = num_layers + self.gru = nn.GRU(feature_size, hidden_size, num_layers, batch_first=True) + self.fc = nn.Linear(hidden_size, output_size) + + def forward(self, x, hidden=None): + batch_size = x.shape[0] + + if hidden is None: + h_0 = x.data.new(self.num_layers, batch_size, self.hidden_size).fill_(0).float() + else: + h_0 = hidden + + output, h_0 = self.gru(x, h_0) + + batch_size, timestep, hidden_size = output.shape + + output = output.reshape(-1, hidden_size) + + output = self.fc(output) + + output = output.reshape(batch_size, timestep, -1) + + return output[:, -1, :] + + +# 形成训练数据 +def split_data(data, timestep, feature_size): + dataX = [] + dataY = [] + + for index in range(len(data) - timestep): + dataX.append(data[index: index + timestep]) + dataY.append(data[index + timestep][0]) + + dataX = np.array(dataX) + dataY = np.array(dataY) + + train_size = int(np.round(0.8 * dataX.shape[0])) + + x_train = dataX[: train_size, :] + y_train = dataY[: train_size].reshape(-1, 1) + + x_test = dataX[train_size:, :] + y_test = dataY[train_size:].reshape(-1, 1) + + return [x_train, y_train, x_test, y_test] + + +data1 = pd.read_csv(r'F:\ourfl\genedate\GAN\gan_alibaba.csv') +data2 = pd.read_csv(r'F:\ourfl\genedate\GAN\gan_google.csv') +data3 = pd.read_csv(r'F:\ourfl\genedate\GAN\gan_mircosoft.csv') +data4 = pd.read_csv(r'F:\ourfl\genedate\GAN\gan_sugan1.csv') + +# 提取avg_mem列 +df1 = data1[['mem_use']] +df2 = data2[['mem_use']] +df3 = data3[['mem_use']] +df4 = data4[['mem_use']] +# 标准化数据 +scaler = MinMaxScaler(feature_range=(0, 1)) +data1 = scaler.fit_transform(df1.values) +data2 = scaler.fit_transform(df2.values) +data3 = scaler.fit_transform(df3.values) +data4 = scaler.fit_transform(df4.values) + +# 定义时间步数和特征数 +timestep = 4 # 您可以根据实际情况调整时间步数 +feature_size = 1 # 由于您已经提取了"mem_use"列作为特征,所以特征数为1 + +# 划分数据集 +x_train1, y_train1, x_test1, y_test1 = split_data(data1, timestep, feature_size) +x_train2, y_train2, x_test2, y_test2 = split_data(data2, timestep, feature_size) +x_train3, y_train3, x_test3, y_test3 = split_data(data3, timestep, feature_size) +x_train4, y_train4, x_test4, y_test4 = split_data(data4, timestep, feature_size) + + +class ClientGRU(nn.Module): + def __init__(self, config): + super(ClientGRU, self).__init__() + self.gru = nn.GRU(config.feature_size, config.hidden_size, config.num_layers, batch_first=True) + self.fc = nn.Linear(config.hidden_size, config.output_size) + + def forward(self, x, hidden=None): + output, hidden = self.gru(x, hidden) + output = self.fc(output[:, -1, :]) # 调整输出维度 + return output + + + + #定义全局GRU模型 +class GlobalGRU(nn.Module): + def __init__(self, config): + super(GlobalGRU, self).__init__() + self.gru = nn.GRU(config.feature_size, config.hidden_size, config.num_layers) + self.fc = nn.Linear(config.hidden_size, config.output_size) + + def forward(self, x, hidden=None): + # 自定义GlobalGRU类的前向传播过程,使用GRU和Linear层 + output, hidden = self.gru(x, hidden) + output = self.fc(output) + return output, hidden + + + + + +config = Config() +global_model = GlobalGRU(config) +client_models = [ClientGRU(config) for _ in range(4)] + + + +# 定义客户端训练函数 +def client_train(client_model, data, global_model_parameters, config): + # 将数据转换为PyTorch张量 + x_train, y_train, _, _ = data + x_train = torch.tensor(x_train, dtype=torch.float32) + y_train = torch.tensor(y_train, dtype=torch.float32) + # 设置优化器和损失函数 + optimizer = optim.Adam(client_model.parameters(), lr=config.learning_rate) + criterion = nn.MSELoss() + # 设置初始隐藏状态(可根据需要调整) + hidden = None + # 将全局模型参数加载到客户端模型中 + client_model.load_state_dict(global_model_parameters) + # 进行本地训练 + for epoch in range(config.epochs): + optimizer.zero_grad() + # 前向传播 + output, hidden = client_model(x_train, hidden) + loss = criterion(output, y_train) + # 反向传播和参数更新 + loss.backward() + optimizer.step() + # 返回更新后的客户端模型参数 + return client_model.state_dict() + + + +# 定义全局模型参数平均函数 +def global_average_parameters(client_parameters_list): + num_parameters = len(list(client_parameters_list[0].values())) + sum_parameters = [torch.zeros_like(param) for param in list(client_parameters_list[0].values())] + + for client_parameters in client_parameters_list: + for i, param in enumerate(client_parameters.values()): + sum_parameters[i] += param + + averaged_parameters = [param_sum / len(client_parameters_list) for param_sum in sum_parameters] + return averaged_parameters + +# ... (其他代码保持不变) + +# 设置全局epoch数 +global_epoch = 20 +# 将数据转换为PyTorch张量 +# 将数据转换为PyTorch张量并指定数据类型为float32 +x_train_list = [torch.tensor(x_train1, dtype=torch.float32), + torch.tensor(x_train2, dtype=torch.float32), + torch.tensor(x_train3, dtype=torch.float32), + torch.tensor(x_train4, dtype=torch.float32)] +y_train_list = [torch.tensor(y_train1, dtype=torch.float32), + torch.tensor(y_train2, dtype=torch.float32), + torch.tensor(y_train3, dtype=torch.float32), + torch.tensor(y_train4, dtype=torch.float32)] + +# 进行联邦学习的全局epoch循环 +for global_epoch in range(global_epoch): + updated_client_parameters_list = [] + + # 客户端训练并更新模型参数 + for client_model, x_train, y_train in zip(client_models, x_train_list, y_train_list): + optimizer = optim.Adam(client_model.parameters(), lr=config.learning_rate) + criterion = nn.MSELoss() + + # 进行本地模型的训练 + for local_epoch in range(config.epochs): + optimizer.zero_grad() + output = client_model(x_train) # 不再解包 + loss = criterion(output, y_train) + loss.backward() + optimizer.step() + + # 获取更新后的客户端模型参数 + updated_client_parameters_list.append(client_model.state_dict()) + + # 全局参数平均化 + averaged_parameters = global_average_parameters(updated_client_parameters_list) + + # 更新全局模型的参数为平均后的参数 + with torch.no_grad(): + for global_param, averaged_param in zip(global_model.parameters(), averaged_parameters): + global_param.copy_(averaged_param) + + +# 保存每个客户端模型 +for i, client_model in enumerate(client_models): + model_path = f"client_model_{i+1}.pth" + torch.save(client_model.state_dict(), model_path) + diff --git a/subject2-pre/all-process/fedavg/fed_tvhl.py b/subject2-pre/all-process/fedavg/fed_tvhl.py new file mode 100644 index 0000000..de7ca27 --- /dev/null +++ b/subject2-pre/all-process/fedavg/fed_tvhl.py @@ -0,0 +1,225 @@ +import numpy as np +import pandas as pd +import torch +import torch.nn as nn +from sklearn.preprocessing import MinMaxScaler +import torch.optim as optim # 添加导入torch.optim模块 + + +# 定义配置类 +class Config(): + timestep = 4 + batch_size = 32 + feature_size = 1 + hidden_size = 256 + output_size = 1 + num_layers = 2 + epochs = 10 + learning_rate = 0.0001 + model_name = 'gru' + save_path = './{}.pth'.format(model_name) + +config = Config() + +# 定义GRU模型 +class GRU(nn.Module): + def __init__(self, feature_size, hidden_size, num_layers, output_size): + super(GRU, self).__init__() + self.hidden_size = hidden_size + self.num_layers = num_layers + self.gru = nn.GRU(feature_size, hidden_size, num_layers, batch_first=True) + self.fc = nn.Linear(hidden_size, output_size) + + def forward(self, x, hidden=None): + batch_size = x.shape[0] + + if hidden is None: + h_0 = x.data.new(self.num_layers, batch_size, self.hidden_size).fill_(0).float() + else: + h_0 = hidden + + output, h_0 = self.gru(x, h_0) + + batch_size, timestep, hidden_size = output.shape + + output = output.reshape(-1, hidden_size) + + output = self.fc(output) + + output = output.reshape(batch_size, timestep, -1) + + return output[:, -1, :] + + +# 形成训练数据 +def split_data(data, timestep, feature_size): + dataX = [] + dataY = [] + + for index in range(len(data) - timestep): + dataX.append(data[index: index + timestep]) + dataY.append(data[index + timestep][0]) + + dataX = np.array(dataX) + dataY = np.array(dataY) + + train_size = int(np.round(0.6 * dataX.shape[0])) + + x_train = dataX[: train_size, :] + y_train = dataY[: train_size].reshape(-1, 1) + + x_test = dataX[train_size:, :] + y_test = dataY[train_size:].reshape(-1, 1) + + return [x_train, y_train, x_test, y_test] + +data1 = pd.read_csv(r'F:\ourfl\genedate\TVHL\tvhl_alibaba.csv') +data2 = pd.read_csv(r'F:\ourfl\genedate\TVHL\tvhl_google.csv') +data3 = pd.read_csv(r'F:\ourfl\genedate\TVHL\tvhl_mircosoft.csv') +data4 = pd.read_csv(r'F:\ourfl\genedate\TVHL\tvhl_sugan1.csv') +# 提取avg_mem列 +df1 = data1[['mem_use']] +df2 = data2[['mem_use']] +df3 = data3[['mem_use']] +df4 = data4[['mem_use']] +# 标准化数据 +scaler = MinMaxScaler(feature_range=(0, 1)) +data1 = scaler.fit_transform(df1.values) +data2 = scaler.fit_transform(df2.values) +data3 = scaler.fit_transform(df3.values) +data4 = scaler.fit_transform(df4.values) + +# 定义时间步数和特征数 +timestep = 4 # 您可以根据实际情况调整时间步数 +feature_size = 1 # 由于您已经提取了"mem_use"列作为特征,所以特征数为1 + +# 划分数据集 +x_train1, y_train1, x_test1, y_test1 = split_data(data1, timestep, feature_size) +x_train2, y_train2, x_test2, y_test2 = split_data(data2, timestep, feature_size) +x_train3, y_train3, x_test3, y_test3 = split_data(data3, timestep, feature_size) +x_train4, y_train4, x_test4, y_test4 = split_data(data4, timestep, feature_size) + + +class ClientGRU(nn.Module): + def __init__(self, config): + super(ClientGRU, self).__init__() + self.gru = nn.GRU(config.feature_size, config.hidden_size, config.num_layers, batch_first=True) + self.fc = nn.Linear(config.hidden_size, config.output_size) + + def forward(self, x, hidden=None): + output, hidden = self.gru(x, hidden) + output = self.fc(output[:, -1, :]) # 调整输出维度 + return output + + + + #定义全局GRU模型 +class GlobalGRU(nn.Module): + def __init__(self, config): + super(GlobalGRU, self).__init__() + self.gru = nn.GRU(config.feature_size, config.hidden_size, config.num_layers) + self.fc = nn.Linear(config.hidden_size, config.output_size) + + def forward(self, x, hidden=None): + # 自定义GlobalGRU类的前向传播过程,使用GRU和Linear层 + output, hidden = self.gru(x, hidden) + output = self.fc(output) + return output, hidden + + + + + +config = Config() +global_model = GlobalGRU(config) +client_models = [ClientGRU(config) for _ in range(4)] + + + +# 定义客户端训练函数 +def client_train(client_model, data, global_model_parameters, config): + # 将数据转换为PyTorch张量 + x_train, y_train, _, _ = data + x_train = torch.tensor(x_train, dtype=torch.float32) + y_train = torch.tensor(y_train, dtype=torch.float32) + # 设置优化器和损失函数 + optimizer = optim.Adam(client_model.parameters(), lr=config.learning_rate) + criterion = nn.MSELoss() + # 设置初始隐藏状态(可根据需要调整) + hidden = None + # 将全局模型参数加载到客户端模型中 + client_model.load_state_dict(global_model_parameters) + # 进行本地训练 + for epoch in range(config.epochs): + optimizer.zero_grad() + # 前向传播 + output, hidden = client_model(x_train, hidden) + loss = criterion(output, y_train) + # 反向传播和参数更新 + loss.backward() + optimizer.step() + # 返回更新后的客户端模型参数 + return client_model.state_dict() + + + +# 定义全局模型参数平均函数 +def global_average_parameters(client_parameters_list): + num_parameters = len(list(client_parameters_list[0].values())) + sum_parameters = [torch.zeros_like(param) for param in list(client_parameters_list[0].values())] + + for client_parameters in client_parameters_list: + for i, param in enumerate(client_parameters.values()): + sum_parameters[i] += param + + averaged_parameters = [param_sum / len(client_parameters_list) for param_sum in sum_parameters] + return averaged_parameters + +# ... (其他代码保持不变) + +# 设置全局epoch数 +global_epoch = 10 +# 将数据转换为PyTorch张量 +# 将数据转换为PyTorch张量并指定数据类型为float32 +x_train_list = [torch.tensor(x_train1, dtype=torch.float32), + torch.tensor(x_train2, dtype=torch.float32), + torch.tensor(x_train3, dtype=torch.float32), + torch.tensor(x_train4, dtype=torch.float32)] +y_train_list = [torch.tensor(y_train1, dtype=torch.float32), + torch.tensor(y_train2, dtype=torch.float32), + torch.tensor(y_train3, dtype=torch.float32), + torch.tensor(y_train4, dtype=torch.float32)] + +# 进行联邦学习的全局epoch循环 +for global_epoch in range(global_epoch): + updated_client_parameters_list = [] + # 客户端训练并更新模型参数 + for client_model, x_train, y_train in zip(client_models, x_train_list, y_train_list): + optimizer = optim.Adam(client_model.parameters(), lr=config.learning_rate) + criterion = nn.MSELoss() + + # 进行本地模型的训练 + for local_epoch in range(config.epochs): + optimizer.zero_grad() + output = client_model(x_train) # 不再解包 + loss = criterion(output, y_train) + loss.backward() + optimizer.step() + + # 获取更新后的客户端模型参数 + updated_client_parameters_list.append(client_model.state_dict()) + + # 全局参数平均化 + averaged_parameters = global_average_parameters(updated_client_parameters_list) + + # 更新全局模型的参数为平均后的参数 + with torch.no_grad(): + for global_param, averaged_param in zip(global_model.parameters(), averaged_parameters): + global_param.copy_(averaged_param) + + +# 保存每个客户端模型 +for i, client_model in enumerate(client_models): + model_path = f"tvhl_client_model_{i+1}.pth" + torch.save(client_model.state_dict(), model_path) + diff --git a/subject2-pre/all-process/fedavg/fed_vhl.py b/subject2-pre/all-process/fedavg/fed_vhl.py new file mode 100644 index 0000000..4b153f6 --- /dev/null +++ b/subject2-pre/all-process/fedavg/fed_vhl.py @@ -0,0 +1,227 @@ +import numpy as np +import pandas as pd +import torch +import torch.nn as nn +from sklearn.preprocessing import MinMaxScaler +import torch.optim as optim # 添加导入torch.optim模块 + + +# 定义配置类 +class Config(): + timestep = 1 + batch_size = 32 + feature_size = 1 + hidden_size = 256 + output_size = 1 + num_layers = 2 + epochs = 10 + learning_rate = 0.0001 + model_name = 'gru' + save_path = './{}.pth'.format(model_name) + +config = Config() + +# 定义GRU模型 +class GRU(nn.Module): + def __init__(self, feature_size, hidden_size, num_layers, output_size): + super(GRU, self).__init__() + self.hidden_size = hidden_size + self.num_layers = num_layers + self.gru = nn.GRU(feature_size, hidden_size, num_layers, batch_first=True) + self.fc = nn.Linear(hidden_size, output_size) + + def forward(self, x, hidden=None): + batch_size = x.shape[0] + + if hidden is None: + h_0 = x.data.new(self.num_layers, batch_size, self.hidden_size).fill_(0).float() + else: + h_0 = hidden + + output, h_0 = self.gru(x, h_0) + + batch_size, timestep, hidden_size = output.shape + + output = output.reshape(-1, hidden_size) + + output = self.fc(output) + + output = output.reshape(batch_size, timestep, -1) + + return output[:, -1, :] + + +# 形成训练数据 +def split_data(data, timestep, feature_size): + dataX = [] + dataY = [] + + for index in range(len(data) - timestep): + dataX.append(data[index: index + timestep]) + dataY.append(data[index + timestep][0]) + + dataX = np.array(dataX) + dataY = np.array(dataY) + + train_size = int(np.round(0.6 * dataX.shape[0])) + + x_train = dataX[: train_size, :] + y_train = dataY[: train_size].reshape(-1, 1) + + x_test = dataX[train_size:, :] + y_test = dataY[train_size:].reshape(-1, 1) + + return [x_train, y_train, x_test, y_test] + + +data1 = pd.read_csv(r'F:\ourfl\genedate\VHL\vhl_alibaba.csv') +data2 = pd.read_csv(r'F:\ourfl\genedate\VHL\vhl_google.csv') +data3 = pd.read_csv(r'F:\ourfl\genedate\VHL\vhl_mircosoft.csv') +data4 = pd.read_csv(r'F:\ourfl\genedate\VHL\vhl_sugan1.csv') + +# 提取avg_mem列 +df1 = data1[['mem_use']] +df2 = data2[['mem_use']] +df3 = data3[['mem_use']] +df4 = data4[['mem_use']] +# 标准化数据 +scaler = MinMaxScaler(feature_range=(0, 1)) +data1 = scaler.fit_transform(df1.values) +data2 = scaler.fit_transform(df2.values) +data3 = scaler.fit_transform(df3.values) +data4 = scaler.fit_transform(df4.values) + +# 定义时间步数和特征数 +timestep = 1 # 您可以根据实际情况调整时间步数 +feature_size = 1 # 由于您已经提取了"mem_use"列作为特征,所以特征数为1 + +# 划分数据集 +x_train1, y_train1, x_test1, y_test1 = split_data(data1, timestep, feature_size) +x_train2, y_train2, x_test2, y_test2 = split_data(data2, timestep, feature_size) +x_train3, y_train3, x_test3, y_test3 = split_data(data3, timestep, feature_size) +x_train4, y_train4, x_test4, y_test4 = split_data(data4, timestep, feature_size) + + +class ClientGRU(nn.Module): + def __init__(self, config): + super(ClientGRU, self).__init__() + self.gru = nn.GRU(config.feature_size, config.hidden_size, config.num_layers, batch_first=True) + self.fc = nn.Linear(config.hidden_size, config.output_size) + + def forward(self, x, hidden=None): + output, hidden = self.gru(x, hidden) + output = self.fc(output[:, -1, :]) # 调整输出维度 + return output + + + + #定义全局GRU模型 +class GlobalGRU(nn.Module): + def __init__(self, config): + super(GlobalGRU, self).__init__() + self.gru = nn.GRU(config.feature_size, config.hidden_size, config.num_layers) + self.fc = nn.Linear(config.hidden_size, config.output_size) + + def forward(self, x, hidden=None): + # 自定义GlobalGRU类的前向传播过程,使用GRU和Linear层 + output, hidden = self.gru(x, hidden) + output = self.fc(output) + return output, hidden + + + + + +config = Config() +global_model = GlobalGRU(config) +client_models = [ClientGRU(config) for _ in range(4)] + + + +# 定义客户端训练函数 +def client_train(client_model, data, global_model_parameters, config): + # 将数据转换为PyTorch张量 + x_train, y_train, _, _ = data + x_train = torch.tensor(x_train, dtype=torch.float32) + y_train = torch.tensor(y_train, dtype=torch.float32) + # 设置优化器和损失函数 + optimizer = optim.Adam(client_model.parameters(), lr=config.learning_rate) + criterion = nn.MSELoss() + # 设置初始隐藏状态(可根据需要调整) + hidden = None + # 将全局模型参数加载到客户端模型中 + client_model.load_state_dict(global_model_parameters) + # 进行本地训练 + for epoch in range(config.epochs): + optimizer.zero_grad() + # 前向传播 + output, hidden = client_model(x_train, hidden) + loss = criterion(output, y_train) + # 反向传播和参数更新 + loss.backward() + optimizer.step() + # 返回更新后的客户端模型参数 + return client_model.state_dict() + + + +# 定义全局模型参数平均函数 +def global_average_parameters(client_parameters_list): + num_parameters = len(list(client_parameters_list[0].values())) + sum_parameters = [torch.zeros_like(param) for param in list(client_parameters_list[0].values())] + + for client_parameters in client_parameters_list: + for i, param in enumerate(client_parameters.values()): + sum_parameters[i] += param + + averaged_parameters = [param_sum / len(client_parameters_list) for param_sum in sum_parameters] + return averaged_parameters + +# ... (其他代码保持不变) + +# 设置全局epoch数 +global_epoch = 10 +# 将数据转换为PyTorch张量 +# 将数据转换为PyTorch张量并指定数据类型为float32 +x_train_list = [torch.tensor(x_train1, dtype=torch.float32), + torch.tensor(x_train2, dtype=torch.float32), + torch.tensor(x_train3, dtype=torch.float32), + torch.tensor(x_train4, dtype=torch.float32)] +y_train_list = [torch.tensor(y_train1, dtype=torch.float32), + torch.tensor(y_train2, dtype=torch.float32), + torch.tensor(y_train3, dtype=torch.float32), + torch.tensor(y_train4, dtype=torch.float32)] + +# 进行联邦学习的全局epoch循环 +for global_epoch in range(global_epoch): + updated_client_parameters_list = [] + # 客户端训练并更新模型参数 + for client_model, x_train, y_train in zip(client_models, x_train_list, y_train_list): + optimizer = optim.Adam(client_model.parameters(), lr=config.learning_rate) + criterion = nn.MSELoss() + + # 进行本地模型的训练 + for local_epoch in range(config.epochs): + optimizer.zero_grad() + output = client_model(x_train) + loss = criterion(output, y_train) + loss.backward() + optimizer.step() + + # 获取更新后的客户端模型参数 + updated_client_parameters_list.append(client_model.state_dict()) + + # 全局参数平均化 + averaged_parameters = global_average_parameters(updated_client_parameters_list) + + # 更新全局模型的参数为平均后的参数 + with torch.no_grad(): + for global_param, averaged_param in zip(global_model.parameters(), averaged_parameters): + global_param.copy_(averaged_param) + + +# 保存每个客户端模型 +for i, client_model in enumerate(client_models): + model_path = f"vhl_client_model_{i+1}.pth" + torch.save(client_model.state_dict(), model_path) + diff --git a/subject2-pre/all-process/fedavg/fedavg.py b/subject2-pre/all-process/fedavg/fedavg.py new file mode 100644 index 0000000..e43a00b --- /dev/null +++ b/subject2-pre/all-process/fedavg/fedavg.py @@ -0,0 +1,224 @@ +import numpy as np +import pandas as pd +import torch +import torch.nn as nn +from sklearn.preprocessing import MinMaxScaler +import torch.optim as optim # 添加导入torch.optim模块 + + +# 定义配置类 +class Config(): + timestep = 1 + batch_size = 32 + feature_size = 1 + hidden_size = 256 + output_size = 1 + num_layers = 2 + epochs = 20 + learning_rate = 0.0001 + model_name = 'gru' + save_path = './{}.pth'.format(model_name) + +config = Config() + +# 定义GRU模型 +class GRU(nn.Module): + def __init__(self, feature_size, hidden_size, num_layers, output_size): + super(GRU, self).__init__() + self.hidden_size = hidden_size + self.num_layers = num_layers + self.gru = nn.GRU(feature_size, hidden_size, num_layers, batch_first=True) + self.fc = nn.Linear(hidden_size, output_size) + + def forward(self, x, hidden=None): + batch_size = x.shape[0] + + if hidden is None: + h_0 = x.data.new(self.num_layers, batch_size, self.hidden_size).fill_(0).float() + else: + h_0 = hidden + + output, h_0 = self.gru(x, h_0) + + batch_size, timestep, hidden_size = output.shape + + output = output.reshape(-1, hidden_size) + + output = self.fc(output) + + output = output.reshape(batch_size, timestep, -1) + + return output[:, -1, :] + +# 形成训练数据 +def split_data(data, timestep, feature_size): + dataX = [] + dataY = [] + + for index in range(len(data) - timestep): + dataX.append(data[index: index + timestep]) + dataY.append(data[index + timestep][0]) + + dataX = np.array(dataX) + dataY = np.array(dataY) + + train_size = int(np.round(0.6 * dataX.shape[0])) + + x_train = dataX[: train_size, :] + y_train = dataY[: train_size].reshape(-1, 1) + + x_test = dataX[train_size:, :] + y_test = dataY[train_size:].reshape(-1, 1) + + return [x_train, y_train, x_test, y_test] + +data1 = pd.read_csv(r"F:\ourfl\data\processed_alibaba.csv") + +data2 = pd.read_csv(r"F:\ourfl\data\pre_google.csv") +data3 = pd.read_csv(r"F:\ourfl\data\pre_microsoft.csv") +data4 = pd.read_csv(r"F:\ourfl\data\processed_hefei.csv") +# 提取avg_mem列 +df1 = data1[['mem_use']] +df2 = data2[['mem_use']] +df3 = data3[['mem_use']] +df4 = data4[['mem_use']] +# 标准化数据 +scaler = MinMaxScaler(feature_range=(0, 1)) +data1 = scaler.fit_transform(df1.values) +data2 = scaler.fit_transform(df2.values) +data3 = scaler.fit_transform(df3.values) +data4 = scaler.fit_transform(df4.values) + +# 定义时间步数和特征数 +timestep = 1 # 您可以根据实际情况调整时间步数 +feature_size = 1 # 由于您已经提取了"mem_use"列作为特征,所以特征数为1 + +# 划分数据集 +x_train1, y_train1, x_test1, y_test1 = split_data(data1, timestep, feature_size) +x_train2, y_train2, x_test2, y_test2 = split_data(data2, timestep, feature_size) +x_train3, y_train3, x_test3, y_test3 = split_data(data3, timestep, feature_size) +x_train4, y_train4, x_test4, y_test4 = split_data(data4, timestep, feature_size) + + +class ClientGRU(nn.Module): + def __init__(self, config): + super(ClientGRU, self).__init__() + self.gru = nn.GRU(config.feature_size, config.hidden_size, config.num_layers, batch_first=True) + self.fc = nn.Linear(config.hidden_size, config.output_size) + + def forward(self, x, hidden=None): + output, hidden = self.gru(x, hidden) + output = self.fc(output[:, -1, :]) # 调整输出维度 + return output + + + + #定义全局GRU模型 +class GlobalGRU(nn.Module): + def __init__(self, config): + super(GlobalGRU, self).__init__() + self.gru = nn.GRU(config.feature_size, config.hidden_size, config.num_layers) + self.fc = nn.Linear(config.hidden_size, config.output_size) + + def forward(self, x, hidden=None): + # 自定义GlobalGRU类的前向传播过程,使用GRU和Linear层 + output, hidden = self.gru(x, hidden) + output = self.fc(output) + return output, hidden + + + + +config = Config() +global_model = GlobalGRU(config) +client_models = [ClientGRU(config) for _ in range(4)] + + + +# 定义客户端训练函数 +def client_train(client_model, data, global_model_parameters, config): + # 将数据转换为PyTorch张量 + x_train, y_train, _, _ = data + x_train = torch.tensor(x_train, dtype=torch.float32) + y_train = torch.tensor(y_train, dtype=torch.float32) + # 设置优化器和损失函数 + optimizer = optim.Adam(client_model.parameters(), lr=config.learning_rate) + criterion = nn.MSELoss() + # 设置初始隐藏状态(可根据需要调整) + hidden = None + # 将全局模型参数加载到客户端模型中 + client_model.load_state_dict(global_model_parameters) + # 进行本地训练 + for epoch in range(config.epochs): + optimizer.zero_grad() + # 前向传播 + output, hidden = client_model(x_train, hidden) + loss = criterion(output, y_train) + # 反向传播和参数更新 + loss.backward() + optimizer.step() + # 返回更新后的客户端模型参数 + return client_model.state_dict() + + + +# 定义全局模型参数平均函数 +def global_average_parameters(client_parameters_list): + num_parameters = len(list(client_parameters_list[0].values())) + sum_parameters = [torch.zeros_like(param) for param in list(client_parameters_list[0].values())] + + for client_parameters in client_parameters_list: + for i, param in enumerate(client_parameters.values()): + sum_parameters[i] += param + + averaged_parameters = [param_sum / len(client_parameters_list) for param_sum in sum_parameters] + return averaged_parameters + +# ... (其他代码保持不变) + +# 设置全局epoch数 +global_epoch = 30 +# 将数据转换为PyTorch张量 +# 将数据转换为PyTorch张量并指定数据类型为float32 +x_train_list = [torch.tensor(x_train1, dtype=torch.float32), + torch.tensor(x_train2, dtype=torch.float32), + torch.tensor(x_train3, dtype=torch.float32), + torch.tensor(x_train4, dtype=torch.float32)] +y_train_list = [torch.tensor(y_train1, dtype=torch.float32), + torch.tensor(y_train2, dtype=torch.float32), + torch.tensor(y_train3, dtype=torch.float32), + torch.tensor(y_train4, dtype=torch.float32)] + +# 进行联邦学习的全局epoch循环 +for global_epoch in range(global_epoch): + updated_client_parameters_list = [] + # 客户端训练并更新模型参数 + for client_model, x_train, y_train in zip(client_models, x_train_list, y_train_list): + optimizer = optim.Adam(client_model.parameters(), lr=config.learning_rate) + criterion = nn.MSELoss() + + # 进行本地模型的训练 + for local_epoch in range(config.epochs): + optimizer.zero_grad() + output = client_model(x_train) # 不再解包 + loss = criterion(output, y_train) + loss.backward() + optimizer.step() + + # 获取更新后的客户端模型参数 + updated_client_parameters_list.append(client_model.state_dict()) + + # 全局参数平均化 + averaged_parameters = global_average_parameters(updated_client_parameters_list) + + # 更新全局模型的参数为平均后的参数 + with torch.no_grad(): + for global_param, averaged_param in zip(global_model.parameters(), averaged_parameters): + global_param.copy_(averaged_param) + + +# 保存每个客户端模型 +for i, client_model in enumerate(client_models): + model_path = f"client_model_{i+1}.pth" + torch.save(client_model.state_dict(), model_path) + -- Gitee From 9575921342a73ff7601de5f8d07fd8bd1e1e74e0 Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 04:31:56 +0000 Subject: [PATCH 16/25] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20su?= =?UTF-8?q?bject2-pre/ourfl=5Fnokd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- subject2-pre/ourfl_nokd/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 subject2-pre/ourfl_nokd/.keep diff --git a/subject2-pre/ourfl_nokd/.keep b/subject2-pre/ourfl_nokd/.keep deleted file mode 100644 index e69de29..0000000 -- Gitee From 5bc73e817e1fc88244c1f1c52b0c07f0175b9f1f Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 04:32:10 +0000 Subject: [PATCH 17/25] =?UTF-8?q?=E6=96=B0=E5=BB=BA=20ourfl=5Fnokd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- subject2-pre/all-process/ourfl_nokd/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 subject2-pre/all-process/ourfl_nokd/.keep diff --git a/subject2-pre/all-process/ourfl_nokd/.keep b/subject2-pre/all-process/ourfl_nokd/.keep new file mode 100644 index 0000000..e69de29 -- Gitee From 818a03c4d5a17f0000cb055508e57e78e4effd98 Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 04:33:05 +0000 Subject: [PATCH 18/25] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=88=E8=99=9A=E6=8B=9F=E6=95=B0=E6=8D=AE=E7=94=9F=E6=88=90?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> --- .../generate_data/GAN/gan_alibaba.py | 119 ++++++++++++++ .../generate_data/GAN/gan_google.py | 118 ++++++++++++++ .../generate_data/GAN/gan_hpc.py | 119 ++++++++++++++ .../generate_data/GAN/gan_mircosoft.py | 119 ++++++++++++++ .../generate_data/TVHL/tvhl_alibaba.py | 137 ++++++++++++++++ .../generate_data/TVHL/tvhl_google.py | 136 ++++++++++++++++ .../generate_data/TVHL/tvhl_mircosoft.py | 137 ++++++++++++++++ .../generate_data/TVHL/tvhl_sugan.py | 137 ++++++++++++++++ .../generate_data/VHL/vhl_alibaba.py | 141 ++++++++++++++++ .../generate_data/VHL/vhl_google.py | 141 ++++++++++++++++ .../generate_data/VHL/vhl_hpc.py | 141 ++++++++++++++++ .../generate_data/VHL/vhl_mircosoft.py | 152 ++++++++++++++++++ 12 files changed, 1597 insertions(+) create mode 100644 subject2-pre/all-process/generate_data/generate_data/GAN/gan_alibaba.py create mode 100644 subject2-pre/all-process/generate_data/generate_data/GAN/gan_google.py create mode 100644 subject2-pre/all-process/generate_data/generate_data/GAN/gan_hpc.py create mode 100644 subject2-pre/all-process/generate_data/generate_data/GAN/gan_mircosoft.py create mode 100644 subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_alibaba.py create mode 100644 subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_google.py create mode 100644 subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_mircosoft.py create mode 100644 subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_sugan.py create mode 100644 subject2-pre/all-process/generate_data/generate_data/VHL/vhl_alibaba.py create mode 100644 subject2-pre/all-process/generate_data/generate_data/VHL/vhl_google.py create mode 100644 subject2-pre/all-process/generate_data/generate_data/VHL/vhl_hpc.py create mode 100644 subject2-pre/all-process/generate_data/generate_data/VHL/vhl_mircosoft.py diff --git a/subject2-pre/all-process/generate_data/generate_data/GAN/gan_alibaba.py b/subject2-pre/all-process/generate_data/generate_data/GAN/gan_alibaba.py new file mode 100644 index 0000000..f11342c --- /dev/null +++ b/subject2-pre/all-process/generate_data/generate_data/GAN/gan_alibaba.py @@ -0,0 +1,119 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError + + +data = pd.read_csv(r'F:\ourfl\data\processed_alibaba.csv') + +data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] +data['time'] = data['time'] % 24 +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data[features].values + + +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + + +def train_gan(noise_data, real_data, epochs=3000, batch_size=32): + for epoch in range(epochs): + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + generated_samples = generator.predict(noise) + X = np.concatenate((real_samples, generated_samples)) + # 创建标签(真实数据样本为1,合成数据样本为0) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + # 训练判别器 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + # 训练生成器 + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + + # 打印损失和精度 + if epoch % 100 == 0: + print(f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + +noise = np.random.normal(0, 1, size=(num_samples, 7)) +# 使用 data1 作为噪声数据集,data2 作为训练数据集训练 GAN 模型 +train_gan(noise, data2_scaled, epochs=3000, batch_size=32) + +num_samples = 20000 +num_rounds = 30 +num_hours_per_round = 24 + +# 生成虚拟数据 +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() + +for i in range(1, 31): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) +generated_data = time_series_data + +generated_data.to_csv(r'F:\ourfl\genedate\GAN\gan_alibaba.csv', index=False) + + diff --git a/subject2-pre/all-process/generate_data/generate_data/GAN/gan_google.py b/subject2-pre/all-process/generate_data/generate_data/GAN/gan_google.py new file mode 100644 index 0000000..fbf5207 --- /dev/null +++ b/subject2-pre/all-process/generate_data/generate_data/GAN/gan_google.py @@ -0,0 +1,118 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError + + +data = pd.read_csv(r'F:\ourfl\data\pre_google.csv') +data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] +data['time'] = data['time'] % 24 +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data[features].values + + +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + + +def train_gan(noise_data, real_data, epochs=3000, batch_size=32): + for epoch in range(epochs): + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + generated_samples = generator.predict(noise) + X = np.concatenate((real_samples, generated_samples)) + # 创建标签(真实数据样本为1,合成数据样本为0) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + # 训练判别器 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + # 训练生成器 + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + + # 打印损失和精度 + if epoch % 100 == 0: + print(f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + +noise = np.random.normal(0, 1, size=(num_samples, 7)) +# 使用 data1 作为噪声数据集,data2 作为训练数据集训练 GAN 模型 +train_gan(noise, data2_scaled, epochs=3000, batch_size=32) + +num_samples = 20000 +num_rounds = 30 +num_hours_per_round = 24 + +# 生成虚拟数据 +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() + +for i in range(1, 31): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) +generated_data = time_series_data + +generated_data.to_csv(r'F:\ourfl\genedate\GAN\gan_google.csv', index=False) + + diff --git a/subject2-pre/all-process/generate_data/generate_data/GAN/gan_hpc.py b/subject2-pre/all-process/generate_data/generate_data/GAN/gan_hpc.py new file mode 100644 index 0000000..8cf3b33 --- /dev/null +++ b/subject2-pre/all-process/generate_data/generate_data/GAN/gan_hpc.py @@ -0,0 +1,119 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError + + +data = pd.read_csv(r'F:\ourfl\data\processed_hefei.csv') + +data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] +data['time'] = data['time'] % 24 +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data[features].values + + +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + + +def train_gan(noise_data, real_data, epochs=3000, batch_size=32): + for epoch in range(epochs): + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + generated_samples = generator.predict(noise) + X = np.concatenate((real_samples, generated_samples)) + # 创建标签(真实数据样本为1,合成数据样本为0) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + # 训练判别器 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + # 训练生成器 + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + + # 打印损失和精度 + if epoch % 100 == 0: + print(f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + +noise = np.random.normal(0, 1, size=(num_samples, 7)) +# 使用 data1 作为噪声数据集,data2 作为训练数据集训练 GAN 模型 +train_gan(noise, data2_scaled, epochs=3000, batch_size=32) + +num_samples = 20000 +num_rounds = 30 +num_hours_per_round = 24 + +# 生成虚拟数据 +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() + +for i in range(1, 31): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) +generated_data = time_series_data + +generated_data.to_csv(r'F:\ourfl\genedate\GAN\gan_sugan1.csv', index=False) + + diff --git a/subject2-pre/all-process/generate_data/generate_data/GAN/gan_mircosoft.py b/subject2-pre/all-process/generate_data/generate_data/GAN/gan_mircosoft.py new file mode 100644 index 0000000..a771474 --- /dev/null +++ b/subject2-pre/all-process/generate_data/generate_data/GAN/gan_mircosoft.py @@ -0,0 +1,119 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError + + +data = pd.read_csv(r'F:\ourfl\data\pre_microsoft.csv') + +data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] +data['time'] = data['time'] % 24 +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data[features].values + + +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + + +def train_gan(noise_data, real_data, epochs=3000, batch_size=32): + for epoch in range(epochs): + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + generated_samples = generator.predict(noise) + X = np.concatenate((real_samples, generated_samples)) + # 创建标签(真实数据样本为1,合成数据样本为0) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + # 训练判别器 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + # 训练生成器 + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + + # 打印损失和精度 + if epoch % 100 == 0: + print(f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + +noise = np.random.normal(0, 1, size=(num_samples, 7)) +# 使用 data1 作为噪声数据集,data2 作为训练数据集训练 GAN 模型 +train_gan(noise, data2_scaled, epochs=3000, batch_size=32) + +num_samples = 20000 +num_rounds = 30 +num_hours_per_round = 24 + +# 生成虚拟数据 +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() + +for i in range(1, 31): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) +generated_data = time_series_data + +generated_data.to_csv(r'F:\ourfl\genedate\GAN\gan_mircosoft.csv', index=False) + + diff --git a/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_alibaba.py b/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_alibaba.py new file mode 100644 index 0000000..b568705 --- /dev/null +++ b/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_alibaba.py @@ -0,0 +1,137 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError +from scipy.spatial.distance import cdist + + + +data = pd.read_csv(r'F:\ourfl\data\processed_alibaba.csv') + +data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] + + +data['time'] = data['time'] % 24 + +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data[features].values + +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + +def compute_joint_distribution_loss(real_samples, fake_samples): + # Extract the 'relative_time' column + real_relative_time = real_samples[:, 0] + fake_relative_time = fake_samples[:, 0] + + # Remove the 'relative_time' column from the samples + real_samples = real_samples[:, 1:] + fake_samples = fake_samples[:, 1:] + + # Calculate the Euclidean distance between samples without the 'relative_time' feature + distance =0.1* cdist(real_samples, fake_samples, metric='euclidean') + + # Compute the joint distribution loss with 'relative_time' as a condition + conditional_distance = np.abs(real_relative_time - fake_relative_time) + joint_distribution_loss = np.mean(distance * conditional_distance) + + return joint_distribution_loss + +#训练模型 +def train_gan(noise_data, real_data, epochs, batch_size): + for epoch in range(epochs): + noise = noise_data + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + generated_samples = generator.predict(noise) + X = np.concatenate((real_samples, generated_samples)) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + # 计算联合分布差异项 + joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) + # 更新判别器的损失函数 + discriminator_loss = discriminator_loss + 0.15 * joint_distribution_loss + # 打印损失和精度 + if epoch % 100 == 0: + print( + f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + +#设置高斯噪声(全局统一) +noise = np.random.normal(0, 1, size=(10000, 7)) +#训练模型 +train_gan(data, noise, epochs=3000, batch_size=32) +# 数据生成 +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() +for i in range(1, 31): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) +generated_data = time_series_data + +generated_data.to_csv(r'F:\ourfl\genedate\TVHL\tvhl_alibaba.csv', index=False) + + diff --git a/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_google.py b/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_google.py new file mode 100644 index 0000000..c12d4e8 --- /dev/null +++ b/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_google.py @@ -0,0 +1,136 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError +from scipy.spatial.distance import cdist + + + +data = pd.read_csv(r'F:\ourfl\data\pre_google.csv') + +data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] + + +data['time'] = data['time'] % 24 + +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data[features].values + +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + +def compute_joint_distribution_loss(real_samples, fake_samples): + # Extract the 'relative_time' column + real_relative_time = real_samples[:, 0] + fake_relative_time = fake_samples[:, 0] + + # Remove the 'relative_time' column from the samples + real_samples = real_samples[:, 1:] + fake_samples = fake_samples[:, 1:] + + # Calculate the Euclidean distance between samples without the 'relative_time' feature + distance =0.1* cdist(real_samples, fake_samples, metric='euclidean') + + # Compute the joint distribution loss with 'relative_time' as a condition + conditional_distance = np.abs(real_relative_time - fake_relative_time) + joint_distribution_loss = np.mean(distance * conditional_distance) + + return joint_distribution_loss + +#训练模型 +def train_gan(noise_data, real_data, epochs, batch_size): + for epoch in range(epochs): + noise = noise_data + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + generated_samples = generator.predict(noise) + X = np.concatenate((real_samples, generated_samples)) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + # 计算联合分布差异项 + joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) + # 更新判别器的损失函数 + discriminator_loss = discriminator_loss + 0.15 * joint_distribution_loss + # 打印损失和精度 + if epoch % 100 == 0: + print( + f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + +#设置高斯噪声(全局统一) +noise = np.random.normal(0, 1, size=(10000, 7)) +#训练模型 +train_gan(data, noise, epochs=3000, batch_size=32) +# 数据生成 +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() +for i in range(1, 31): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) +generated_data = time_series_data +generated_data.to_csv(r'F:\ourfl\genedate\TVHL\tvhl_google.csv', index=False) + + diff --git a/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_mircosoft.py b/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_mircosoft.py new file mode 100644 index 0000000..12d96bb --- /dev/null +++ b/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_mircosoft.py @@ -0,0 +1,137 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError +from scipy.spatial.distance import cdist + + + +data = pd.read_csv(r'F:\ourfl\data\pre_microsoft.csv') + +data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] + + +data['time'] = data['time'] % 24 + +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data[features].values + +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + +def compute_joint_distribution_loss(real_samples, fake_samples): + # Extract the 'relative_time' column + real_relative_time = real_samples[:, 0] + fake_relative_time = fake_samples[:, 0] + + # Remove the 'relative_time' column from the samples + real_samples = real_samples[:, 1:] + fake_samples = fake_samples[:, 1:] + + # Calculate the Euclidean distance between samples without the 'relative_time' feature + distance =0.1* cdist(real_samples, fake_samples, metric='euclidean') + + # Compute the joint distribution loss with 'relative_time' as a condition + conditional_distance = np.abs(real_relative_time - fake_relative_time) + joint_distribution_loss = np.mean(distance * conditional_distance) + + return joint_distribution_loss + +#训练模型 +def train_gan(noise_data, real_data, epochs, batch_size): + for epoch in range(epochs): + noise = noise_data + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + generated_samples = generator.predict(noise) + X = np.concatenate((real_samples, generated_samples)) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + # 计算联合分布差异项 + joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) + # 更新判别器的损失函数 + discriminator_loss = discriminator_loss + 0.15 * joint_distribution_loss + # 打印损失和精度 + if epoch % 100 == 0: + print( + f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + +#设置高斯噪声(全局统一) +noise = np.random.normal(0, 1, size=(10000, 7)) +#训练模型 +train_gan(data, noise, epochs=3000, batch_size=32) +# 数据生成 +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() +for i in range(1, 31): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) +generated_data = time_series_data + +generated_data.to_csv(r'F:\ourfl\genedate\VHL\vhl_microsoft.csv', index=False) + + diff --git a/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_sugan.py b/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_sugan.py new file mode 100644 index 0000000..7fda7ee --- /dev/null +++ b/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_sugan.py @@ -0,0 +1,137 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError +from scipy.spatial.distance import cdist + + + +data = pd.read_csv(r'F:\ourfl\data\processed_hefei.csv') + +data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] + + +data['time'] = data['time'] % 24 + +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data[features].values + +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + +def compute_joint_distribution_loss(real_samples, fake_samples): + # Extract the 'relative_time' column + real_relative_time = real_samples[:, 0] + fake_relative_time = fake_samples[:, 0] + + # Remove the 'relative_time' column from the samples + real_samples = real_samples[:, 1:] + fake_samples = fake_samples[:, 1:] + + # Calculate the Euclidean distance between samples without the 'relative_time' feature + distance =0.1* cdist(real_samples, fake_samples, metric='euclidean') + + # Compute the joint distribution loss with 'relative_time' as a condition + conditional_distance = np.abs(real_relative_time - fake_relative_time) + joint_distribution_loss = np.mean(distance * conditional_distance) + + return joint_distribution_loss + +#训练模型 +def train_gan(noise_data, real_data, epochs, batch_size): + for epoch in range(epochs): + noise = noise_data + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + generated_samples = generator.predict(noise) + X = np.concatenate((real_samples, generated_samples)) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + # 计算联合分布差异项 + joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) + # 更新判别器的损失函数 + discriminator_loss = discriminator_loss + 0.15 * joint_distribution_loss + # 打印损失和精度 + if epoch % 100 == 0: + print( + f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + +#设置高斯噪声(全局统一) +noise = np.random.normal(0, 1, size=(10000, 7)) +#训练模型 +train_gan(data, noise, epochs=3000, batch_size=32) +# 数据生成 +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() +for i in range(1, 31): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) +generated_data = time_series_data + +generated_data.to_csv(r'F:\ourfl\genedate\VHL\vhl_HPC.csv', index=False) + + diff --git a/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_alibaba.py b/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_alibaba.py new file mode 100644 index 0000000..e2ebbd2 --- /dev/null +++ b/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_alibaba.py @@ -0,0 +1,141 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError +from scipy.spatial.distance import cdist + + + +data = pd.read_csv(r'F:\ourfl\data\processed_alibaba.csv') + +data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] + + +data['time'] = data['time'] % 24 + +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data1[features].values + + +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + +def compute_joint_distribution_loss(real_samples, fake_samples): + # 计算真实样本和生成样本之间的欧氏距离 + distance = cdist(real_samples, fake_samples, metric='euclidean') + # 计算联合分布差异项 + joint_distribution_loss = distance.mean() + return joint_distribution_loss + +def train_gan(noise_data, real_data, epochs=3000, batch_size=16): + for epoch in range(epochs): + + noise = noise_data + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + + + generated_samples = generator.predict(noise) + X = np.concatenate((real_samples, generated_samples)) + + # 创建标签(真实数据样本为1,合成数据样本为0) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + + # 训练判别器 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + + # 训练生成器 + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + + # 计算联合分布差异项 + joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) + # 更新判别器的损失函数 + discriminator_loss = discriminator_loss + 0.1 * joint_distribution_loss + + # 打印损失和精度 + if epoch % 100 == 0: + print( + f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + +noise = np.random.normal(0, 1, size=(num_samples, 7)) +# 使用 data1 作为噪声数据集,data2 作为训练数据集训练 GAN 模型 +train_gan(data1_scaled, data2_scaled, epochs=3000, batch_size=32) + +num_samples = 20000 +num_rounds = 30 +num_hours_per_round = 24 + +# 生成虚拟数据 +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() +for i in range(1, 31): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) +generated_data = time_series_data + +generated_data.to_csv(r'F:\ourfl\genedate\VHL\vhl_alibaba.csv', index=False) + + diff --git a/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_google.py b/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_google.py new file mode 100644 index 0000000..70f2259 --- /dev/null +++ b/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_google.py @@ -0,0 +1,141 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError +from scipy.spatial.distance import cdist + + + +data = pd.read_csv(r'F:\ourfl\data\pre_google.csv') +data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] + + +data['time'] = data['time'] % 24 + +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data1[features].values + + +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + +def compute_joint_distribution_loss(real_samples, fake_samples): + # 计算真实样本和生成样本之间的欧氏距离 + distance = cdist(real_samples, fake_samples, metric='euclidean') + # 计算联合分布差异项 + joint_distribution_loss = distance.mean() + return joint_distribution_loss + +def train_gan(noise_data, real_data, epochs=3000, batch_size=16): + for epoch in range(epochs): + + noise = noise_data + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + + + generated_samples = generator.predict(noise) + X = np.concatenate((real_samples, generated_samples)) + + # 创建标签(真实数据样本为1,合成数据样本为0) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + + # 训练判别器 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + + # 训练生成器 + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + + # 计算联合分布差异项 + joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) + # 更新判别器的损失函数 + discriminator_loss = discriminator_loss + 0.1 * joint_distribution_loss + + # 打印损失和精度 + if epoch % 100 == 0: + print( + f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + +noise = np.random.normal(0, 1, size=(num_samples, 7)) +# 使用 data1 作为噪声数据集,data2 作为训练数据集训练 GAN 模型 +train_gan(data1_scaled, data2_scaled, epochs=3000, batch_size=32) + +num_samples = 20000 +num_rounds = 30 +num_hours_per_round = 24 + +# 生成虚拟数据 +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() +for i in range(1, 31): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) +generated_data = time_series_data + + +generated_data.to_csv(r'F:\ourfl\genedate\VHL\vhl_google.csv', index=False) + + diff --git a/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_hpc.py b/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_hpc.py new file mode 100644 index 0000000..9f057a4 --- /dev/null +++ b/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_hpc.py @@ -0,0 +1,141 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError +from scipy.spatial.distance import cdist + + +data2 = pd.read_csv(r'F:\ourfl\data\processed_hpc.csv') + + +data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] + + +data['time'] = data['time'] % 24 + +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data1[features].values + + +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + +def compute_joint_distribution_loss(real_samples, fake_samples): + # 计算真实样本和生成样本之间的欧氏距离 + distance = cdist(real_samples, fake_samples, metric='euclidean') + # 计算联合分布差异项 + joint_distribution_loss = distance.mean() + return joint_distribution_loss + +def train_gan(noise_data, real_data, epochs=3000, batch_size=16): + for epoch in range(epochs): + + noise = noise_data + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + + + generated_samples = generator.predict(noise) + X = np.concatenate((real_samples, generated_samples)) + + # 创建标签(真实数据样本为1,合成数据样本为0) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + + # 训练判别器 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + + # 训练生成器 + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + + # 计算联合分布差异项 + joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) + # 更新判别器的损失函数 + discriminator_loss = discriminator_loss + 0.1 * joint_distribution_loss + + # 打印损失和精度 + if epoch % 100 == 0: + print( + f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + +noise = np.random.normal(0, 1, size=(num_samples, 7)) +# 使用 data1 作为噪声数据集,data2 作为训练数据集训练 GAN 模型 +train_gan(data1_scaled, data2_scaled, epochs=3000, batch_size=32) + +num_samples = 20000 +num_rounds = 30 +num_hours_per_round = 24 + +# 生成虚拟数据 +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() +for i in range(1, 31): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) +generated_data = time_series_data + +generated_data.to_csv(r'F:\ourfl\genedate\VHL\vhl_hpc.csv', index=False) + + diff --git a/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_mircosoft.py b/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_mircosoft.py new file mode 100644 index 0000000..9222091 --- /dev/null +++ b/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_mircosoft.py @@ -0,0 +1,152 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError +from scipy.spatial.distance import cdist + +data1 = pd.read_csv(r'F:\ourfl\data\processed_alibaba.csv') +data2 = pd.read_csv(r'F:\ourfl\data\pre_microsoft.csv') + + + +# 检查 data2 是否缺少特征,如果是,则用 data1 中相应特征的平均值来填充 +for column in data1.columns: + if column not in data2.columns: + data2[column] = data1[column].mean() + +# data2 = data2['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] + +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data1 = data1[features].values +data2 = data2[features].values + + + +scaler = MinMaxScaler(feature_range=(0, 1)) +data1_scaled = scaler.fit_transform(data1) +data2_scaled = scaler.fit_transform(data2) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='mean_squared_error', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='mean_squared_error', optimizer='adam') + + +def compute_joint_distribution_loss(real_samples, fake_samples): + # 计算真实样本和生成样本之间的欧氏距离 + distance = cdist(real_samples, fake_samples, metric='euclidean') + # 计算联合分布差异项 + joint_distribution_loss = distance.mean() + return joint_distribution_loss + + +def train_gan(noise_data, real_data, epochs=3000, batch_size=32): + for epoch in range(epochs): + + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + + + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + + + generated_samples = generator.predict(noise) + + + X = np.concatenate((real_samples, generated_samples)) + + # 创建标签(真实数据样本为1,合成数据样本为0) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + + # 训练判别器 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + + # 训练生成器 + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + + # 计算联合分布差异项 + joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) + # 更新判别器的损失函数 + discriminator_loss = discriminator_loss + 0.1 * joint_distribution_loss + + # 打印损失和精度 + if epoch % 100 == 0: + print( + f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + + + +num_samples = 20000 +num_rounds = 30 +num_hours_per_round = 24 + + +noise = np.random.normal(0, 1, size=(num_samples, 7)) +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() +for i in range(0, 30): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data, empty_df.iloc[i * 24 + j:i * 24 + j + 1].copy()]) +generated_data = time_series_data + +generated_data.to_csv(r'F:\ourfl\genedate\VHL\vhl_mircosoft.csv', index=False) + + -- Gitee From 931e3521a977a967e7e13761fa9e34f1c408654b Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 04:33:56 +0000 Subject: [PATCH 19/25] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20su?= =?UTF-8?q?bject2-pre/all-process/generate=5Fdata?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- subject2-pre/all-process/generate_data/.keep | 0 .../generate_data/GAN/gan_alibaba.py | 119 -------------- .../generate_data/GAN/gan_google.py | 118 -------------- .../generate_data/GAN/gan_hpc.py | 119 -------------- .../generate_data/GAN/gan_mircosoft.py | 119 -------------- .../generate_data/TVHL/tvhl_alibaba.py | 137 ---------------- .../generate_data/TVHL/tvhl_google.py | 136 ---------------- .../generate_data/TVHL/tvhl_mircosoft.py | 137 ---------------- .../generate_data/TVHL/tvhl_sugan.py | 137 ---------------- .../generate_data/VHL/vhl_alibaba.py | 141 ---------------- .../generate_data/VHL/vhl_google.py | 141 ---------------- .../generate_data/VHL/vhl_hpc.py | 141 ---------------- .../generate_data/VHL/vhl_mircosoft.py | 152 ------------------ 13 files changed, 1597 deletions(-) delete mode 100644 subject2-pre/all-process/generate_data/.keep delete mode 100644 subject2-pre/all-process/generate_data/generate_data/GAN/gan_alibaba.py delete mode 100644 subject2-pre/all-process/generate_data/generate_data/GAN/gan_google.py delete mode 100644 subject2-pre/all-process/generate_data/generate_data/GAN/gan_hpc.py delete mode 100644 subject2-pre/all-process/generate_data/generate_data/GAN/gan_mircosoft.py delete mode 100644 subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_alibaba.py delete mode 100644 subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_google.py delete mode 100644 subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_mircosoft.py delete mode 100644 subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_sugan.py delete mode 100644 subject2-pre/all-process/generate_data/generate_data/VHL/vhl_alibaba.py delete mode 100644 subject2-pre/all-process/generate_data/generate_data/VHL/vhl_google.py delete mode 100644 subject2-pre/all-process/generate_data/generate_data/VHL/vhl_hpc.py delete mode 100644 subject2-pre/all-process/generate_data/generate_data/VHL/vhl_mircosoft.py diff --git a/subject2-pre/all-process/generate_data/.keep b/subject2-pre/all-process/generate_data/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/subject2-pre/all-process/generate_data/generate_data/GAN/gan_alibaba.py b/subject2-pre/all-process/generate_data/generate_data/GAN/gan_alibaba.py deleted file mode 100644 index f11342c..0000000 --- a/subject2-pre/all-process/generate_data/generate_data/GAN/gan_alibaba.py +++ /dev/null @@ -1,119 +0,0 @@ -import numpy as np -import pandas as pd -from tensorflow.keras.models import Sequential -from sklearn.preprocessing import MinMaxScaler -from tensorflow.keras.layers import Dense, LeakyReLU -import tensorflow as tf -from tensorflow.keras.losses import MeanSquaredError -from tensorflow.keras.metrics import RootMeanSquaredError - - -data = pd.read_csv(r'F:\ourfl\data\processed_alibaba.csv') - -data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] -data['time'] = data['time'] % 24 -features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] -data = data[features].values - - -scaler = MinMaxScaler(feature_range=(0, 1)) -data_scaled = scaler.fit_transform(data) - - -def build_generator(): - model = Sequential() - model.add(Dense(64, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(32)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(7, activation='sigmoid')) - return model - -generator = build_generator() - - -def build_discriminator(): - model = Sequential() - model.add(Dense(32, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(16)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(1, activation='sigmoid')) - return model - -discriminator = build_discriminator() - -discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) -discriminator.trainable = False -gan_input = tf.keras.Input(shape=(7,)) -gan_output = discriminator(generator(gan_input)) -gan = tf.keras.Model(gan_input, gan_output) -gan.compile(loss='binary_crossentropy', optimizer='adam') - - -def train_gan(noise_data, real_data, epochs=3000, batch_size=32): - for epoch in range(epochs): - noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] - real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] - generated_samples = generator.predict(noise) - X = np.concatenate((real_samples, generated_samples)) - # 创建标签(真实数据样本为1,合成数据样本为0) - y = np.ones(2 * batch_size) - y[batch_size:] = 0 - # 训练判别器 - discriminator.trainable = True - discriminator_loss = discriminator.train_on_batch(X, y) - # 训练生成器 - noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] - y_gen = np.ones(batch_size) - discriminator.trainable = False - generator_loss = gan.train_on_batch(noise, y_gen) - - # 打印损失和精度 - if epoch % 100 == 0: - print(f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') - -noise = np.random.normal(0, 1, size=(num_samples, 7)) -# 使用 data1 作为噪声数据集,data2 作为训练数据集训练 GAN 模型 -train_gan(noise, data2_scaled, epochs=3000, batch_size=32) - -num_samples = 20000 -num_rounds = 30 -num_hours_per_round = 24 - -# 生成虚拟数据 -generated_data = generator.predict(noise) -generated_data = pd.DataFrame(generated_data, columns=features) - -# 将时间恢复到24小时制 -generated_data['time'] = (generated_data['time'] * 24).astype(int) - -data_by_hour = {} -# 将generated_data按时间分组,放入字典中对应的键中 -for i in range(24): - data_by_hour[i] = generated_data[generated_data['time'] == i] - -empty_df = pd.DataFrame() -# 遍历1到23小时,检查每个小时的数据是否为空 -for i in range(0, 24): - if data_by_hour[i].empty: - next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 - data_by_hour[i] = data_by_hour[next_i].head(1) - data_length = len(data_by_hour[i]) - if data_length < 31: - data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) - -for i in range(0, 24): - empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) - -# 复制empty_df的第一行数据,并赋值给time_series_data -time_series_data = empty_df.iloc[:1].copy() - -for i in range(1, 31): - for j in range(0, 24): - time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) -generated_data = time_series_data - -generated_data.to_csv(r'F:\ourfl\genedate\GAN\gan_alibaba.csv', index=False) - - diff --git a/subject2-pre/all-process/generate_data/generate_data/GAN/gan_google.py b/subject2-pre/all-process/generate_data/generate_data/GAN/gan_google.py deleted file mode 100644 index fbf5207..0000000 --- a/subject2-pre/all-process/generate_data/generate_data/GAN/gan_google.py +++ /dev/null @@ -1,118 +0,0 @@ -import numpy as np -import pandas as pd -from tensorflow.keras.models import Sequential -from sklearn.preprocessing import MinMaxScaler -from tensorflow.keras.layers import Dense, LeakyReLU -import tensorflow as tf -from tensorflow.keras.losses import MeanSquaredError -from tensorflow.keras.metrics import RootMeanSquaredError - - -data = pd.read_csv(r'F:\ourfl\data\pre_google.csv') -data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] -data['time'] = data['time'] % 24 -features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] -data = data[features].values - - -scaler = MinMaxScaler(feature_range=(0, 1)) -data_scaled = scaler.fit_transform(data) - - -def build_generator(): - model = Sequential() - model.add(Dense(64, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(32)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(7, activation='sigmoid')) - return model - -generator = build_generator() - - -def build_discriminator(): - model = Sequential() - model.add(Dense(32, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(16)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(1, activation='sigmoid')) - return model - -discriminator = build_discriminator() - -discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) -discriminator.trainable = False -gan_input = tf.keras.Input(shape=(7,)) -gan_output = discriminator(generator(gan_input)) -gan = tf.keras.Model(gan_input, gan_output) -gan.compile(loss='binary_crossentropy', optimizer='adam') - - -def train_gan(noise_data, real_data, epochs=3000, batch_size=32): - for epoch in range(epochs): - noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] - real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] - generated_samples = generator.predict(noise) - X = np.concatenate((real_samples, generated_samples)) - # 创建标签(真实数据样本为1,合成数据样本为0) - y = np.ones(2 * batch_size) - y[batch_size:] = 0 - # 训练判别器 - discriminator.trainable = True - discriminator_loss = discriminator.train_on_batch(X, y) - # 训练生成器 - noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] - y_gen = np.ones(batch_size) - discriminator.trainable = False - generator_loss = gan.train_on_batch(noise, y_gen) - - # 打印损失和精度 - if epoch % 100 == 0: - print(f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') - -noise = np.random.normal(0, 1, size=(num_samples, 7)) -# 使用 data1 作为噪声数据集,data2 作为训练数据集训练 GAN 模型 -train_gan(noise, data2_scaled, epochs=3000, batch_size=32) - -num_samples = 20000 -num_rounds = 30 -num_hours_per_round = 24 - -# 生成虚拟数据 -generated_data = generator.predict(noise) -generated_data = pd.DataFrame(generated_data, columns=features) - -# 将时间恢复到24小时制 -generated_data['time'] = (generated_data['time'] * 24).astype(int) - -data_by_hour = {} -# 将generated_data按时间分组,放入字典中对应的键中 -for i in range(24): - data_by_hour[i] = generated_data[generated_data['time'] == i] - -empty_df = pd.DataFrame() -# 遍历1到23小时,检查每个小时的数据是否为空 -for i in range(0, 24): - if data_by_hour[i].empty: - next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 - data_by_hour[i] = data_by_hour[next_i].head(1) - data_length = len(data_by_hour[i]) - if data_length < 31: - data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) - -for i in range(0, 24): - empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) - -# 复制empty_df的第一行数据,并赋值给time_series_data -time_series_data = empty_df.iloc[:1].copy() - -for i in range(1, 31): - for j in range(0, 24): - time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) -generated_data = time_series_data - -generated_data.to_csv(r'F:\ourfl\genedate\GAN\gan_google.csv', index=False) - - diff --git a/subject2-pre/all-process/generate_data/generate_data/GAN/gan_hpc.py b/subject2-pre/all-process/generate_data/generate_data/GAN/gan_hpc.py deleted file mode 100644 index 8cf3b33..0000000 --- a/subject2-pre/all-process/generate_data/generate_data/GAN/gan_hpc.py +++ /dev/null @@ -1,119 +0,0 @@ -import numpy as np -import pandas as pd -from tensorflow.keras.models import Sequential -from sklearn.preprocessing import MinMaxScaler -from tensorflow.keras.layers import Dense, LeakyReLU -import tensorflow as tf -from tensorflow.keras.losses import MeanSquaredError -from tensorflow.keras.metrics import RootMeanSquaredError - - -data = pd.read_csv(r'F:\ourfl\data\processed_hefei.csv') - -data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] -data['time'] = data['time'] % 24 -features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] -data = data[features].values - - -scaler = MinMaxScaler(feature_range=(0, 1)) -data_scaled = scaler.fit_transform(data) - - -def build_generator(): - model = Sequential() - model.add(Dense(64, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(32)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(7, activation='sigmoid')) - return model - -generator = build_generator() - - -def build_discriminator(): - model = Sequential() - model.add(Dense(32, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(16)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(1, activation='sigmoid')) - return model - -discriminator = build_discriminator() - -discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) -discriminator.trainable = False -gan_input = tf.keras.Input(shape=(7,)) -gan_output = discriminator(generator(gan_input)) -gan = tf.keras.Model(gan_input, gan_output) -gan.compile(loss='binary_crossentropy', optimizer='adam') - - -def train_gan(noise_data, real_data, epochs=3000, batch_size=32): - for epoch in range(epochs): - noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] - real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] - generated_samples = generator.predict(noise) - X = np.concatenate((real_samples, generated_samples)) - # 创建标签(真实数据样本为1,合成数据样本为0) - y = np.ones(2 * batch_size) - y[batch_size:] = 0 - # 训练判别器 - discriminator.trainable = True - discriminator_loss = discriminator.train_on_batch(X, y) - # 训练生成器 - noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] - y_gen = np.ones(batch_size) - discriminator.trainable = False - generator_loss = gan.train_on_batch(noise, y_gen) - - # 打印损失和精度 - if epoch % 100 == 0: - print(f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') - -noise = np.random.normal(0, 1, size=(num_samples, 7)) -# 使用 data1 作为噪声数据集,data2 作为训练数据集训练 GAN 模型 -train_gan(noise, data2_scaled, epochs=3000, batch_size=32) - -num_samples = 20000 -num_rounds = 30 -num_hours_per_round = 24 - -# 生成虚拟数据 -generated_data = generator.predict(noise) -generated_data = pd.DataFrame(generated_data, columns=features) - -# 将时间恢复到24小时制 -generated_data['time'] = (generated_data['time'] * 24).astype(int) - -data_by_hour = {} -# 将generated_data按时间分组,放入字典中对应的键中 -for i in range(24): - data_by_hour[i] = generated_data[generated_data['time'] == i] - -empty_df = pd.DataFrame() -# 遍历1到23小时,检查每个小时的数据是否为空 -for i in range(0, 24): - if data_by_hour[i].empty: - next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 - data_by_hour[i] = data_by_hour[next_i].head(1) - data_length = len(data_by_hour[i]) - if data_length < 31: - data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) - -for i in range(0, 24): - empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) - -# 复制empty_df的第一行数据,并赋值给time_series_data -time_series_data = empty_df.iloc[:1].copy() - -for i in range(1, 31): - for j in range(0, 24): - time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) -generated_data = time_series_data - -generated_data.to_csv(r'F:\ourfl\genedate\GAN\gan_sugan1.csv', index=False) - - diff --git a/subject2-pre/all-process/generate_data/generate_data/GAN/gan_mircosoft.py b/subject2-pre/all-process/generate_data/generate_data/GAN/gan_mircosoft.py deleted file mode 100644 index a771474..0000000 --- a/subject2-pre/all-process/generate_data/generate_data/GAN/gan_mircosoft.py +++ /dev/null @@ -1,119 +0,0 @@ -import numpy as np -import pandas as pd -from tensorflow.keras.models import Sequential -from sklearn.preprocessing import MinMaxScaler -from tensorflow.keras.layers import Dense, LeakyReLU -import tensorflow as tf -from tensorflow.keras.losses import MeanSquaredError -from tensorflow.keras.metrics import RootMeanSquaredError - - -data = pd.read_csv(r'F:\ourfl\data\pre_microsoft.csv') - -data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] -data['time'] = data['time'] % 24 -features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] -data = data[features].values - - -scaler = MinMaxScaler(feature_range=(0, 1)) -data_scaled = scaler.fit_transform(data) - - -def build_generator(): - model = Sequential() - model.add(Dense(64, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(32)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(7, activation='sigmoid')) - return model - -generator = build_generator() - - -def build_discriminator(): - model = Sequential() - model.add(Dense(32, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(16)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(1, activation='sigmoid')) - return model - -discriminator = build_discriminator() - -discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) -discriminator.trainable = False -gan_input = tf.keras.Input(shape=(7,)) -gan_output = discriminator(generator(gan_input)) -gan = tf.keras.Model(gan_input, gan_output) -gan.compile(loss='binary_crossentropy', optimizer='adam') - - -def train_gan(noise_data, real_data, epochs=3000, batch_size=32): - for epoch in range(epochs): - noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] - real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] - generated_samples = generator.predict(noise) - X = np.concatenate((real_samples, generated_samples)) - # 创建标签(真实数据样本为1,合成数据样本为0) - y = np.ones(2 * batch_size) - y[batch_size:] = 0 - # 训练判别器 - discriminator.trainable = True - discriminator_loss = discriminator.train_on_batch(X, y) - # 训练生成器 - noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] - y_gen = np.ones(batch_size) - discriminator.trainable = False - generator_loss = gan.train_on_batch(noise, y_gen) - - # 打印损失和精度 - if epoch % 100 == 0: - print(f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') - -noise = np.random.normal(0, 1, size=(num_samples, 7)) -# 使用 data1 作为噪声数据集,data2 作为训练数据集训练 GAN 模型 -train_gan(noise, data2_scaled, epochs=3000, batch_size=32) - -num_samples = 20000 -num_rounds = 30 -num_hours_per_round = 24 - -# 生成虚拟数据 -generated_data = generator.predict(noise) -generated_data = pd.DataFrame(generated_data, columns=features) - -# 将时间恢复到24小时制 -generated_data['time'] = (generated_data['time'] * 24).astype(int) - -data_by_hour = {} -# 将generated_data按时间分组,放入字典中对应的键中 -for i in range(24): - data_by_hour[i] = generated_data[generated_data['time'] == i] - -empty_df = pd.DataFrame() -# 遍历1到23小时,检查每个小时的数据是否为空 -for i in range(0, 24): - if data_by_hour[i].empty: - next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 - data_by_hour[i] = data_by_hour[next_i].head(1) - data_length = len(data_by_hour[i]) - if data_length < 31: - data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) - -for i in range(0, 24): - empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) - -# 复制empty_df的第一行数据,并赋值给time_series_data -time_series_data = empty_df.iloc[:1].copy() - -for i in range(1, 31): - for j in range(0, 24): - time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) -generated_data = time_series_data - -generated_data.to_csv(r'F:\ourfl\genedate\GAN\gan_mircosoft.csv', index=False) - - diff --git a/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_alibaba.py b/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_alibaba.py deleted file mode 100644 index b568705..0000000 --- a/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_alibaba.py +++ /dev/null @@ -1,137 +0,0 @@ -import numpy as np -import pandas as pd -from tensorflow.keras.models import Sequential -from sklearn.preprocessing import MinMaxScaler -from tensorflow.keras.layers import Dense, LeakyReLU -import tensorflow as tf -from tensorflow.keras.losses import MeanSquaredError -from tensorflow.keras.metrics import RootMeanSquaredError -from scipy.spatial.distance import cdist - - - -data = pd.read_csv(r'F:\ourfl\data\processed_alibaba.csv') - -data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] - - -data['time'] = data['time'] % 24 - -features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] -data = data[features].values - -scaler = MinMaxScaler(feature_range=(0, 1)) -data_scaled = scaler.fit_transform(data) - - -def build_generator(): - model = Sequential() - model.add(Dense(64, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(32)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(7, activation='sigmoid')) - return model - -generator = build_generator() - - -def build_discriminator(): - model = Sequential() - model.add(Dense(32, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(16)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(1, activation='sigmoid')) - return model - -discriminator = build_discriminator() - -discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) -discriminator.trainable = False -gan_input = tf.keras.Input(shape=(7,)) -gan_output = discriminator(generator(gan_input)) -gan = tf.keras.Model(gan_input, gan_output) -gan.compile(loss='binary_crossentropy', optimizer='adam') - -def compute_joint_distribution_loss(real_samples, fake_samples): - # Extract the 'relative_time' column - real_relative_time = real_samples[:, 0] - fake_relative_time = fake_samples[:, 0] - - # Remove the 'relative_time' column from the samples - real_samples = real_samples[:, 1:] - fake_samples = fake_samples[:, 1:] - - # Calculate the Euclidean distance between samples without the 'relative_time' feature - distance =0.1* cdist(real_samples, fake_samples, metric='euclidean') - - # Compute the joint distribution loss with 'relative_time' as a condition - conditional_distance = np.abs(real_relative_time - fake_relative_time) - joint_distribution_loss = np.mean(distance * conditional_distance) - - return joint_distribution_loss - -#训练模型 -def train_gan(noise_data, real_data, epochs, batch_size): - for epoch in range(epochs): - noise = noise_data - real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] - generated_samples = generator.predict(noise) - X = np.concatenate((real_samples, generated_samples)) - y = np.ones(2 * batch_size) - y[batch_size:] = 0 - discriminator.trainable = True - discriminator_loss = discriminator.train_on_batch(X, y) - noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] - y_gen = np.ones(batch_size) - discriminator.trainable = False - generator_loss = gan.train_on_batch(noise, y_gen) - # 计算联合分布差异项 - joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) - # 更新判别器的损失函数 - discriminator_loss = discriminator_loss + 0.15 * joint_distribution_loss - # 打印损失和精度 - if epoch % 100 == 0: - print( - f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') - -#设置高斯噪声(全局统一) -noise = np.random.normal(0, 1, size=(10000, 7)) -#训练模型 -train_gan(data, noise, epochs=3000, batch_size=32) -# 数据生成 -generated_data = generator.predict(noise) -generated_data = pd.DataFrame(generated_data, columns=features) - -# 将时间恢复到24小时制 -generated_data['time'] = (generated_data['time'] * 24).astype(int) - -data_by_hour = {} -# 将generated_data按时间分组,放入字典中对应的键中 -for i in range(24): - data_by_hour[i] = generated_data[generated_data['time'] == i] - -empty_df = pd.DataFrame() -# 遍历1到23小时,检查每个小时的数据是否为空 -for i in range(0, 24): - if data_by_hour[i].empty: - next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 - data_by_hour[i] = data_by_hour[next_i].head(1) - data_length = len(data_by_hour[i]) - if data_length < 31: - data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) - -for i in range(0, 24): - empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) - -# 复制empty_df的第一行数据,并赋值给time_series_data -time_series_data = empty_df.iloc[:1].copy() -for i in range(1, 31): - for j in range(0, 24): - time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) -generated_data = time_series_data - -generated_data.to_csv(r'F:\ourfl\genedate\TVHL\tvhl_alibaba.csv', index=False) - - diff --git a/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_google.py b/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_google.py deleted file mode 100644 index c12d4e8..0000000 --- a/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_google.py +++ /dev/null @@ -1,136 +0,0 @@ -import numpy as np -import pandas as pd -from tensorflow.keras.models import Sequential -from sklearn.preprocessing import MinMaxScaler -from tensorflow.keras.layers import Dense, LeakyReLU -import tensorflow as tf -from tensorflow.keras.losses import MeanSquaredError -from tensorflow.keras.metrics import RootMeanSquaredError -from scipy.spatial.distance import cdist - - - -data = pd.read_csv(r'F:\ourfl\data\pre_google.csv') - -data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] - - -data['time'] = data['time'] % 24 - -features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] -data = data[features].values - -scaler = MinMaxScaler(feature_range=(0, 1)) -data_scaled = scaler.fit_transform(data) - - -def build_generator(): - model = Sequential() - model.add(Dense(64, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(32)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(7, activation='sigmoid')) - return model - -generator = build_generator() - - -def build_discriminator(): - model = Sequential() - model.add(Dense(32, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(16)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(1, activation='sigmoid')) - return model - -discriminator = build_discriminator() - -discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) -discriminator.trainable = False -gan_input = tf.keras.Input(shape=(7,)) -gan_output = discriminator(generator(gan_input)) -gan = tf.keras.Model(gan_input, gan_output) -gan.compile(loss='binary_crossentropy', optimizer='adam') - -def compute_joint_distribution_loss(real_samples, fake_samples): - # Extract the 'relative_time' column - real_relative_time = real_samples[:, 0] - fake_relative_time = fake_samples[:, 0] - - # Remove the 'relative_time' column from the samples - real_samples = real_samples[:, 1:] - fake_samples = fake_samples[:, 1:] - - # Calculate the Euclidean distance between samples without the 'relative_time' feature - distance =0.1* cdist(real_samples, fake_samples, metric='euclidean') - - # Compute the joint distribution loss with 'relative_time' as a condition - conditional_distance = np.abs(real_relative_time - fake_relative_time) - joint_distribution_loss = np.mean(distance * conditional_distance) - - return joint_distribution_loss - -#训练模型 -def train_gan(noise_data, real_data, epochs, batch_size): - for epoch in range(epochs): - noise = noise_data - real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] - generated_samples = generator.predict(noise) - X = np.concatenate((real_samples, generated_samples)) - y = np.ones(2 * batch_size) - y[batch_size:] = 0 - discriminator.trainable = True - discriminator_loss = discriminator.train_on_batch(X, y) - noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] - y_gen = np.ones(batch_size) - discriminator.trainable = False - generator_loss = gan.train_on_batch(noise, y_gen) - # 计算联合分布差异项 - joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) - # 更新判别器的损失函数 - discriminator_loss = discriminator_loss + 0.15 * joint_distribution_loss - # 打印损失和精度 - if epoch % 100 == 0: - print( - f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') - -#设置高斯噪声(全局统一) -noise = np.random.normal(0, 1, size=(10000, 7)) -#训练模型 -train_gan(data, noise, epochs=3000, batch_size=32) -# 数据生成 -generated_data = generator.predict(noise) -generated_data = pd.DataFrame(generated_data, columns=features) - -# 将时间恢复到24小时制 -generated_data['time'] = (generated_data['time'] * 24).astype(int) - -data_by_hour = {} -# 将generated_data按时间分组,放入字典中对应的键中 -for i in range(24): - data_by_hour[i] = generated_data[generated_data['time'] == i] - -empty_df = pd.DataFrame() -# 遍历1到23小时,检查每个小时的数据是否为空 -for i in range(0, 24): - if data_by_hour[i].empty: - next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 - data_by_hour[i] = data_by_hour[next_i].head(1) - data_length = len(data_by_hour[i]) - if data_length < 31: - data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) - -for i in range(0, 24): - empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) - -# 复制empty_df的第一行数据,并赋值给time_series_data -time_series_data = empty_df.iloc[:1].copy() -for i in range(1, 31): - for j in range(0, 24): - time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) -generated_data = time_series_data -generated_data.to_csv(r'F:\ourfl\genedate\TVHL\tvhl_google.csv', index=False) - - diff --git a/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_mircosoft.py b/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_mircosoft.py deleted file mode 100644 index 12d96bb..0000000 --- a/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_mircosoft.py +++ /dev/null @@ -1,137 +0,0 @@ -import numpy as np -import pandas as pd -from tensorflow.keras.models import Sequential -from sklearn.preprocessing import MinMaxScaler -from tensorflow.keras.layers import Dense, LeakyReLU -import tensorflow as tf -from tensorflow.keras.losses import MeanSquaredError -from tensorflow.keras.metrics import RootMeanSquaredError -from scipy.spatial.distance import cdist - - - -data = pd.read_csv(r'F:\ourfl\data\pre_microsoft.csv') - -data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] - - -data['time'] = data['time'] % 24 - -features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] -data = data[features].values - -scaler = MinMaxScaler(feature_range=(0, 1)) -data_scaled = scaler.fit_transform(data) - - -def build_generator(): - model = Sequential() - model.add(Dense(64, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(32)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(7, activation='sigmoid')) - return model - -generator = build_generator() - - -def build_discriminator(): - model = Sequential() - model.add(Dense(32, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(16)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(1, activation='sigmoid')) - return model - -discriminator = build_discriminator() - -discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) -discriminator.trainable = False -gan_input = tf.keras.Input(shape=(7,)) -gan_output = discriminator(generator(gan_input)) -gan = tf.keras.Model(gan_input, gan_output) -gan.compile(loss='binary_crossentropy', optimizer='adam') - -def compute_joint_distribution_loss(real_samples, fake_samples): - # Extract the 'relative_time' column - real_relative_time = real_samples[:, 0] - fake_relative_time = fake_samples[:, 0] - - # Remove the 'relative_time' column from the samples - real_samples = real_samples[:, 1:] - fake_samples = fake_samples[:, 1:] - - # Calculate the Euclidean distance between samples without the 'relative_time' feature - distance =0.1* cdist(real_samples, fake_samples, metric='euclidean') - - # Compute the joint distribution loss with 'relative_time' as a condition - conditional_distance = np.abs(real_relative_time - fake_relative_time) - joint_distribution_loss = np.mean(distance * conditional_distance) - - return joint_distribution_loss - -#训练模型 -def train_gan(noise_data, real_data, epochs, batch_size): - for epoch in range(epochs): - noise = noise_data - real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] - generated_samples = generator.predict(noise) - X = np.concatenate((real_samples, generated_samples)) - y = np.ones(2 * batch_size) - y[batch_size:] = 0 - discriminator.trainable = True - discriminator_loss = discriminator.train_on_batch(X, y) - noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] - y_gen = np.ones(batch_size) - discriminator.trainable = False - generator_loss = gan.train_on_batch(noise, y_gen) - # 计算联合分布差异项 - joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) - # 更新判别器的损失函数 - discriminator_loss = discriminator_loss + 0.15 * joint_distribution_loss - # 打印损失和精度 - if epoch % 100 == 0: - print( - f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') - -#设置高斯噪声(全局统一) -noise = np.random.normal(0, 1, size=(10000, 7)) -#训练模型 -train_gan(data, noise, epochs=3000, batch_size=32) -# 数据生成 -generated_data = generator.predict(noise) -generated_data = pd.DataFrame(generated_data, columns=features) - -# 将时间恢复到24小时制 -generated_data['time'] = (generated_data['time'] * 24).astype(int) - -data_by_hour = {} -# 将generated_data按时间分组,放入字典中对应的键中 -for i in range(24): - data_by_hour[i] = generated_data[generated_data['time'] == i] - -empty_df = pd.DataFrame() -# 遍历1到23小时,检查每个小时的数据是否为空 -for i in range(0, 24): - if data_by_hour[i].empty: - next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 - data_by_hour[i] = data_by_hour[next_i].head(1) - data_length = len(data_by_hour[i]) - if data_length < 31: - data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) - -for i in range(0, 24): - empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) - -# 复制empty_df的第一行数据,并赋值给time_series_data -time_series_data = empty_df.iloc[:1].copy() -for i in range(1, 31): - for j in range(0, 24): - time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) -generated_data = time_series_data - -generated_data.to_csv(r'F:\ourfl\genedate\VHL\vhl_microsoft.csv', index=False) - - diff --git a/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_sugan.py b/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_sugan.py deleted file mode 100644 index 7fda7ee..0000000 --- a/subject2-pre/all-process/generate_data/generate_data/TVHL/tvhl_sugan.py +++ /dev/null @@ -1,137 +0,0 @@ -import numpy as np -import pandas as pd -from tensorflow.keras.models import Sequential -from sklearn.preprocessing import MinMaxScaler -from tensorflow.keras.layers import Dense, LeakyReLU -import tensorflow as tf -from tensorflow.keras.losses import MeanSquaredError -from tensorflow.keras.metrics import RootMeanSquaredError -from scipy.spatial.distance import cdist - - - -data = pd.read_csv(r'F:\ourfl\data\processed_hefei.csv') - -data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] - - -data['time'] = data['time'] % 24 - -features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] -data = data[features].values - -scaler = MinMaxScaler(feature_range=(0, 1)) -data_scaled = scaler.fit_transform(data) - - -def build_generator(): - model = Sequential() - model.add(Dense(64, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(32)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(7, activation='sigmoid')) - return model - -generator = build_generator() - - -def build_discriminator(): - model = Sequential() - model.add(Dense(32, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(16)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(1, activation='sigmoid')) - return model - -discriminator = build_discriminator() - -discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) -discriminator.trainable = False -gan_input = tf.keras.Input(shape=(7,)) -gan_output = discriminator(generator(gan_input)) -gan = tf.keras.Model(gan_input, gan_output) -gan.compile(loss='binary_crossentropy', optimizer='adam') - -def compute_joint_distribution_loss(real_samples, fake_samples): - # Extract the 'relative_time' column - real_relative_time = real_samples[:, 0] - fake_relative_time = fake_samples[:, 0] - - # Remove the 'relative_time' column from the samples - real_samples = real_samples[:, 1:] - fake_samples = fake_samples[:, 1:] - - # Calculate the Euclidean distance between samples without the 'relative_time' feature - distance =0.1* cdist(real_samples, fake_samples, metric='euclidean') - - # Compute the joint distribution loss with 'relative_time' as a condition - conditional_distance = np.abs(real_relative_time - fake_relative_time) - joint_distribution_loss = np.mean(distance * conditional_distance) - - return joint_distribution_loss - -#训练模型 -def train_gan(noise_data, real_data, epochs, batch_size): - for epoch in range(epochs): - noise = noise_data - real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] - generated_samples = generator.predict(noise) - X = np.concatenate((real_samples, generated_samples)) - y = np.ones(2 * batch_size) - y[batch_size:] = 0 - discriminator.trainable = True - discriminator_loss = discriminator.train_on_batch(X, y) - noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] - y_gen = np.ones(batch_size) - discriminator.trainable = False - generator_loss = gan.train_on_batch(noise, y_gen) - # 计算联合分布差异项 - joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) - # 更新判别器的损失函数 - discriminator_loss = discriminator_loss + 0.15 * joint_distribution_loss - # 打印损失和精度 - if epoch % 100 == 0: - print( - f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') - -#设置高斯噪声(全局统一) -noise = np.random.normal(0, 1, size=(10000, 7)) -#训练模型 -train_gan(data, noise, epochs=3000, batch_size=32) -# 数据生成 -generated_data = generator.predict(noise) -generated_data = pd.DataFrame(generated_data, columns=features) - -# 将时间恢复到24小时制 -generated_data['time'] = (generated_data['time'] * 24).astype(int) - -data_by_hour = {} -# 将generated_data按时间分组,放入字典中对应的键中 -for i in range(24): - data_by_hour[i] = generated_data[generated_data['time'] == i] - -empty_df = pd.DataFrame() -# 遍历1到23小时,检查每个小时的数据是否为空 -for i in range(0, 24): - if data_by_hour[i].empty: - next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 - data_by_hour[i] = data_by_hour[next_i].head(1) - data_length = len(data_by_hour[i]) - if data_length < 31: - data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) - -for i in range(0, 24): - empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) - -# 复制empty_df的第一行数据,并赋值给time_series_data -time_series_data = empty_df.iloc[:1].copy() -for i in range(1, 31): - for j in range(0, 24): - time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) -generated_data = time_series_data - -generated_data.to_csv(r'F:\ourfl\genedate\VHL\vhl_HPC.csv', index=False) - - diff --git a/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_alibaba.py b/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_alibaba.py deleted file mode 100644 index e2ebbd2..0000000 --- a/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_alibaba.py +++ /dev/null @@ -1,141 +0,0 @@ -import numpy as np -import pandas as pd -from tensorflow.keras.models import Sequential -from sklearn.preprocessing import MinMaxScaler -from tensorflow.keras.layers import Dense, LeakyReLU -import tensorflow as tf -from tensorflow.keras.losses import MeanSquaredError -from tensorflow.keras.metrics import RootMeanSquaredError -from scipy.spatial.distance import cdist - - - -data = pd.read_csv(r'F:\ourfl\data\processed_alibaba.csv') - -data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] - - -data['time'] = data['time'] % 24 - -features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] -data = data1[features].values - - -scaler = MinMaxScaler(feature_range=(0, 1)) -data_scaled = scaler.fit_transform(data) - - -def build_generator(): - model = Sequential() - model.add(Dense(64, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(32)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(7, activation='sigmoid')) - return model - -generator = build_generator() - - -def build_discriminator(): - model = Sequential() - model.add(Dense(32, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(16)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(1, activation='sigmoid')) - return model - -discriminator = build_discriminator() - -discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) -discriminator.trainable = False -gan_input = tf.keras.Input(shape=(7,)) -gan_output = discriminator(generator(gan_input)) -gan = tf.keras.Model(gan_input, gan_output) -gan.compile(loss='binary_crossentropy', optimizer='adam') - -def compute_joint_distribution_loss(real_samples, fake_samples): - # 计算真实样本和生成样本之间的欧氏距离 - distance = cdist(real_samples, fake_samples, metric='euclidean') - # 计算联合分布差异项 - joint_distribution_loss = distance.mean() - return joint_distribution_loss - -def train_gan(noise_data, real_data, epochs=3000, batch_size=16): - for epoch in range(epochs): - - noise = noise_data - real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] - - - generated_samples = generator.predict(noise) - X = np.concatenate((real_samples, generated_samples)) - - # 创建标签(真实数据样本为1,合成数据样本为0) - y = np.ones(2 * batch_size) - y[batch_size:] = 0 - - # 训练判别器 - discriminator.trainable = True - discriminator_loss = discriminator.train_on_batch(X, y) - - # 训练生成器 - noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] - y_gen = np.ones(batch_size) - discriminator.trainable = False - generator_loss = gan.train_on_batch(noise, y_gen) - - # 计算联合分布差异项 - joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) - # 更新判别器的损失函数 - discriminator_loss = discriminator_loss + 0.1 * joint_distribution_loss - - # 打印损失和精度 - if epoch % 100 == 0: - print( - f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') - -noise = np.random.normal(0, 1, size=(num_samples, 7)) -# 使用 data1 作为噪声数据集,data2 作为训练数据集训练 GAN 模型 -train_gan(data1_scaled, data2_scaled, epochs=3000, batch_size=32) - -num_samples = 20000 -num_rounds = 30 -num_hours_per_round = 24 - -# 生成虚拟数据 -generated_data = generator.predict(noise) -generated_data = pd.DataFrame(generated_data, columns=features) - -# 将时间恢复到24小时制 -generated_data['time'] = (generated_data['time'] * 24).astype(int) - -data_by_hour = {} -# 将generated_data按时间分组,放入字典中对应的键中 -for i in range(24): - data_by_hour[i] = generated_data[generated_data['time'] == i] - -empty_df = pd.DataFrame() -# 遍历1到23小时,检查每个小时的数据是否为空 -for i in range(0, 24): - if data_by_hour[i].empty: - next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 - data_by_hour[i] = data_by_hour[next_i].head(1) - data_length = len(data_by_hour[i]) - if data_length < 31: - data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) - -for i in range(0, 24): - empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) - -# 复制empty_df的第一行数据,并赋值给time_series_data -time_series_data = empty_df.iloc[:1].copy() -for i in range(1, 31): - for j in range(0, 24): - time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) -generated_data = time_series_data - -generated_data.to_csv(r'F:\ourfl\genedate\VHL\vhl_alibaba.csv', index=False) - - diff --git a/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_google.py b/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_google.py deleted file mode 100644 index 70f2259..0000000 --- a/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_google.py +++ /dev/null @@ -1,141 +0,0 @@ -import numpy as np -import pandas as pd -from tensorflow.keras.models import Sequential -from sklearn.preprocessing import MinMaxScaler -from tensorflow.keras.layers import Dense, LeakyReLU -import tensorflow as tf -from tensorflow.keras.losses import MeanSquaredError -from tensorflow.keras.metrics import RootMeanSquaredError -from scipy.spatial.distance import cdist - - - -data = pd.read_csv(r'F:\ourfl\data\pre_google.csv') -data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] - - -data['time'] = data['time'] % 24 - -features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] -data = data1[features].values - - -scaler = MinMaxScaler(feature_range=(0, 1)) -data_scaled = scaler.fit_transform(data) - - -def build_generator(): - model = Sequential() - model.add(Dense(64, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(32)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(7, activation='sigmoid')) - return model - -generator = build_generator() - - -def build_discriminator(): - model = Sequential() - model.add(Dense(32, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(16)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(1, activation='sigmoid')) - return model - -discriminator = build_discriminator() - -discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) -discriminator.trainable = False -gan_input = tf.keras.Input(shape=(7,)) -gan_output = discriminator(generator(gan_input)) -gan = tf.keras.Model(gan_input, gan_output) -gan.compile(loss='binary_crossentropy', optimizer='adam') - -def compute_joint_distribution_loss(real_samples, fake_samples): - # 计算真实样本和生成样本之间的欧氏距离 - distance = cdist(real_samples, fake_samples, metric='euclidean') - # 计算联合分布差异项 - joint_distribution_loss = distance.mean() - return joint_distribution_loss - -def train_gan(noise_data, real_data, epochs=3000, batch_size=16): - for epoch in range(epochs): - - noise = noise_data - real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] - - - generated_samples = generator.predict(noise) - X = np.concatenate((real_samples, generated_samples)) - - # 创建标签(真实数据样本为1,合成数据样本为0) - y = np.ones(2 * batch_size) - y[batch_size:] = 0 - - # 训练判别器 - discriminator.trainable = True - discriminator_loss = discriminator.train_on_batch(X, y) - - # 训练生成器 - noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] - y_gen = np.ones(batch_size) - discriminator.trainable = False - generator_loss = gan.train_on_batch(noise, y_gen) - - # 计算联合分布差异项 - joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) - # 更新判别器的损失函数 - discriminator_loss = discriminator_loss + 0.1 * joint_distribution_loss - - # 打印损失和精度 - if epoch % 100 == 0: - print( - f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') - -noise = np.random.normal(0, 1, size=(num_samples, 7)) -# 使用 data1 作为噪声数据集,data2 作为训练数据集训练 GAN 模型 -train_gan(data1_scaled, data2_scaled, epochs=3000, batch_size=32) - -num_samples = 20000 -num_rounds = 30 -num_hours_per_round = 24 - -# 生成虚拟数据 -generated_data = generator.predict(noise) -generated_data = pd.DataFrame(generated_data, columns=features) - -# 将时间恢复到24小时制 -generated_data['time'] = (generated_data['time'] * 24).astype(int) - -data_by_hour = {} -# 将generated_data按时间分组,放入字典中对应的键中 -for i in range(24): - data_by_hour[i] = generated_data[generated_data['time'] == i] - -empty_df = pd.DataFrame() -# 遍历1到23小时,检查每个小时的数据是否为空 -for i in range(0, 24): - if data_by_hour[i].empty: - next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 - data_by_hour[i] = data_by_hour[next_i].head(1) - data_length = len(data_by_hour[i]) - if data_length < 31: - data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) - -for i in range(0, 24): - empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) - -# 复制empty_df的第一行数据,并赋值给time_series_data -time_series_data = empty_df.iloc[:1].copy() -for i in range(1, 31): - for j in range(0, 24): - time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) -generated_data = time_series_data - - -generated_data.to_csv(r'F:\ourfl\genedate\VHL\vhl_google.csv', index=False) - - diff --git a/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_hpc.py b/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_hpc.py deleted file mode 100644 index 9f057a4..0000000 --- a/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_hpc.py +++ /dev/null @@ -1,141 +0,0 @@ -import numpy as np -import pandas as pd -from tensorflow.keras.models import Sequential -from sklearn.preprocessing import MinMaxScaler -from tensorflow.keras.layers import Dense, LeakyReLU -import tensorflow as tf -from tensorflow.keras.losses import MeanSquaredError -from tensorflow.keras.metrics import RootMeanSquaredError -from scipy.spatial.distance import cdist - - -data2 = pd.read_csv(r'F:\ourfl\data\processed_hpc.csv') - - -data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] - - -data['time'] = data['time'] % 24 - -features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] -data = data1[features].values - - -scaler = MinMaxScaler(feature_range=(0, 1)) -data_scaled = scaler.fit_transform(data) - - -def build_generator(): - model = Sequential() - model.add(Dense(64, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(32)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(7, activation='sigmoid')) - return model - -generator = build_generator() - - -def build_discriminator(): - model = Sequential() - model.add(Dense(32, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(16)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(1, activation='sigmoid')) - return model - -discriminator = build_discriminator() - -discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) -discriminator.trainable = False -gan_input = tf.keras.Input(shape=(7,)) -gan_output = discriminator(generator(gan_input)) -gan = tf.keras.Model(gan_input, gan_output) -gan.compile(loss='binary_crossentropy', optimizer='adam') - -def compute_joint_distribution_loss(real_samples, fake_samples): - # 计算真实样本和生成样本之间的欧氏距离 - distance = cdist(real_samples, fake_samples, metric='euclidean') - # 计算联合分布差异项 - joint_distribution_loss = distance.mean() - return joint_distribution_loss - -def train_gan(noise_data, real_data, epochs=3000, batch_size=16): - for epoch in range(epochs): - - noise = noise_data - real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] - - - generated_samples = generator.predict(noise) - X = np.concatenate((real_samples, generated_samples)) - - # 创建标签(真实数据样本为1,合成数据样本为0) - y = np.ones(2 * batch_size) - y[batch_size:] = 0 - - # 训练判别器 - discriminator.trainable = True - discriminator_loss = discriminator.train_on_batch(X, y) - - # 训练生成器 - noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] - y_gen = np.ones(batch_size) - discriminator.trainable = False - generator_loss = gan.train_on_batch(noise, y_gen) - - # 计算联合分布差异项 - joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) - # 更新判别器的损失函数 - discriminator_loss = discriminator_loss + 0.1 * joint_distribution_loss - - # 打印损失和精度 - if epoch % 100 == 0: - print( - f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') - -noise = np.random.normal(0, 1, size=(num_samples, 7)) -# 使用 data1 作为噪声数据集,data2 作为训练数据集训练 GAN 模型 -train_gan(data1_scaled, data2_scaled, epochs=3000, batch_size=32) - -num_samples = 20000 -num_rounds = 30 -num_hours_per_round = 24 - -# 生成虚拟数据 -generated_data = generator.predict(noise) -generated_data = pd.DataFrame(generated_data, columns=features) - -# 将时间恢复到24小时制 -generated_data['time'] = (generated_data['time'] * 24).astype(int) - -data_by_hour = {} -# 将generated_data按时间分组,放入字典中对应的键中 -for i in range(24): - data_by_hour[i] = generated_data[generated_data['time'] == i] - -empty_df = pd.DataFrame() -# 遍历1到23小时,检查每个小时的数据是否为空 -for i in range(0, 24): - if data_by_hour[i].empty: - next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 - data_by_hour[i] = data_by_hour[next_i].head(1) - data_length = len(data_by_hour[i]) - if data_length < 31: - data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) - -for i in range(0, 24): - empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) - -# 复制empty_df的第一行数据,并赋值给time_series_data -time_series_data = empty_df.iloc[:1].copy() -for i in range(1, 31): - for j in range(0, 24): - time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) -generated_data = time_series_data - -generated_data.to_csv(r'F:\ourfl\genedate\VHL\vhl_hpc.csv', index=False) - - diff --git a/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_mircosoft.py b/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_mircosoft.py deleted file mode 100644 index 9222091..0000000 --- a/subject2-pre/all-process/generate_data/generate_data/VHL/vhl_mircosoft.py +++ /dev/null @@ -1,152 +0,0 @@ -import numpy as np -import pandas as pd -from tensorflow.keras.models import Sequential -from sklearn.preprocessing import MinMaxScaler -from tensorflow.keras.layers import Dense, LeakyReLU -import tensorflow as tf -from tensorflow.keras.losses import MeanSquaredError -from tensorflow.keras.metrics import RootMeanSquaredError -from scipy.spatial.distance import cdist - -data1 = pd.read_csv(r'F:\ourfl\data\processed_alibaba.csv') -data2 = pd.read_csv(r'F:\ourfl\data\pre_microsoft.csv') - - - -# 检查 data2 是否缺少特征,如果是,则用 data1 中相应特征的平均值来填充 -for column in data1.columns: - if column not in data2.columns: - data2[column] = data1[column].mean() - -# data2 = data2['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] - -features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] -data1 = data1[features].values -data2 = data2[features].values - - - -scaler = MinMaxScaler(feature_range=(0, 1)) -data1_scaled = scaler.fit_transform(data1) -data2_scaled = scaler.fit_transform(data2) - - -def build_generator(): - model = Sequential() - model.add(Dense(64, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(32)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(7, activation='sigmoid')) - return model - -generator = build_generator() - - -def build_discriminator(): - model = Sequential() - model.add(Dense(32, input_dim=7)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(16)) - model.add(LeakyReLU(alpha=0.2)) - model.add(Dense(1, activation='sigmoid')) - return model - -discriminator = build_discriminator() - -discriminator.compile(loss='mean_squared_error', optimizer='adam', metrics=['accuracy']) -discriminator.trainable = False -gan_input = tf.keras.Input(shape=(7,)) -gan_output = discriminator(generator(gan_input)) -gan = tf.keras.Model(gan_input, gan_output) -gan.compile(loss='mean_squared_error', optimizer='adam') - - -def compute_joint_distribution_loss(real_samples, fake_samples): - # 计算真实样本和生成样本之间的欧氏距离 - distance = cdist(real_samples, fake_samples, metric='euclidean') - # 计算联合分布差异项 - joint_distribution_loss = distance.mean() - return joint_distribution_loss - - -def train_gan(noise_data, real_data, epochs=3000, batch_size=32): - for epoch in range(epochs): - - noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] - - - real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] - - - generated_samples = generator.predict(noise) - - - X = np.concatenate((real_samples, generated_samples)) - - # 创建标签(真实数据样本为1,合成数据样本为0) - y = np.ones(2 * batch_size) - y[batch_size:] = 0 - - # 训练判别器 - discriminator.trainable = True - discriminator_loss = discriminator.train_on_batch(X, y) - - # 训练生成器 - noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] - y_gen = np.ones(batch_size) - discriminator.trainable = False - generator_loss = gan.train_on_batch(noise, y_gen) - - # 计算联合分布差异项 - joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) - # 更新判别器的损失函数 - discriminator_loss = discriminator_loss + 0.1 * joint_distribution_loss - - # 打印损失和精度 - if epoch % 100 == 0: - print( - f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') - - - -num_samples = 20000 -num_rounds = 30 -num_hours_per_round = 24 - - -noise = np.random.normal(0, 1, size=(num_samples, 7)) -generated_data = generator.predict(noise) -generated_data = pd.DataFrame(generated_data, columns=features) - -# 将时间恢复到24小时制 -generated_data['time'] = (generated_data['time'] * 24).astype(int) - -data_by_hour = {} -# 将generated_data按时间分组,放入字典中对应的键中 -for i in range(24): - data_by_hour[i] = generated_data[generated_data['time'] == i] - -empty_df = pd.DataFrame() -# 遍历1到23小时,检查每个小时的数据是否为空 -for i in range(0, 24): - if data_by_hour[i].empty: - next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 - data_by_hour[i] = data_by_hour[next_i].head(1) - data_length = len(data_by_hour[i]) - if data_length < 31: - data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) - -for i in range(0, 24): - empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) - -# 复制empty_df的第一行数据,并赋值给time_series_data -time_series_data = empty_df.iloc[:1].copy() -for i in range(0, 30): - for j in range(0, 24): - time_series_data = pd.concat([time_series_data, empty_df.iloc[i * 24 + j:i * 24 + j + 1].copy()]) -generated_data = time_series_data - -generated_data.to_csv(r'F:\ourfl\genedate\VHL\vhl_mircosoft.csv', index=False) - - -- Gitee From 604645345d9cf8da2b0a48bd882664c13a90d806 Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 04:34:08 +0000 Subject: [PATCH 20/25] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20su?= =?UTF-8?q?bject2-pre/all-process/ourfl=5Fkd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- subject2-pre/all-process/ourfl_kd/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 subject2-pre/all-process/ourfl_kd/.keep diff --git a/subject2-pre/all-process/ourfl_kd/.keep b/subject2-pre/all-process/ourfl_kd/.keep deleted file mode 100644 index e69de29..0000000 -- Gitee From fd638ec6599a7e19312ece6a7529d0004a72b61b Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 04:34:13 +0000 Subject: [PATCH 21/25] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20su?= =?UTF-8?q?bject2-pre/all-process/ourfl=5Fnokd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- subject2-pre/all-process/ourfl_nokd/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 subject2-pre/all-process/ourfl_nokd/.keep diff --git a/subject2-pre/all-process/ourfl_nokd/.keep b/subject2-pre/all-process/ourfl_nokd/.keep deleted file mode 100644 index e69de29..0000000 -- Gitee From 7d106c953caefdcc3521a54afafdd2cc3f7c7902 Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 04:34:28 +0000 Subject: [PATCH 22/25] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=88=E8=99=9A=E6=8B=9F=E6=95=B0=E6=8D=AE=E7=94=9F=E6=88=90?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> --- .../generate_data/GAN/gan_alibaba.py | 119 ++++++++++++++ .../generate_data/GAN/gan_google.py | 118 ++++++++++++++ .../all-process/generate_data/GAN/gan_hpc.py | 119 ++++++++++++++ .../generate_data/GAN/gan_mircosoft.py | 119 ++++++++++++++ .../generate_data/TVHL/tvhl_alibaba.py | 137 ++++++++++++++++ .../generate_data/TVHL/tvhl_google.py | 136 ++++++++++++++++ .../generate_data/TVHL/tvhl_mircosoft.py | 137 ++++++++++++++++ .../generate_data/TVHL/tvhl_sugan.py | 137 ++++++++++++++++ .../generate_data/VHL/vhl_alibaba.py | 141 ++++++++++++++++ .../generate_data/VHL/vhl_google.py | 141 ++++++++++++++++ .../all-process/generate_data/VHL/vhl_hpc.py | 141 ++++++++++++++++ .../generate_data/VHL/vhl_mircosoft.py | 152 ++++++++++++++++++ 12 files changed, 1597 insertions(+) create mode 100644 subject2-pre/all-process/generate_data/GAN/gan_alibaba.py create mode 100644 subject2-pre/all-process/generate_data/GAN/gan_google.py create mode 100644 subject2-pre/all-process/generate_data/GAN/gan_hpc.py create mode 100644 subject2-pre/all-process/generate_data/GAN/gan_mircosoft.py create mode 100644 subject2-pre/all-process/generate_data/TVHL/tvhl_alibaba.py create mode 100644 subject2-pre/all-process/generate_data/TVHL/tvhl_google.py create mode 100644 subject2-pre/all-process/generate_data/TVHL/tvhl_mircosoft.py create mode 100644 subject2-pre/all-process/generate_data/TVHL/tvhl_sugan.py create mode 100644 subject2-pre/all-process/generate_data/VHL/vhl_alibaba.py create mode 100644 subject2-pre/all-process/generate_data/VHL/vhl_google.py create mode 100644 subject2-pre/all-process/generate_data/VHL/vhl_hpc.py create mode 100644 subject2-pre/all-process/generate_data/VHL/vhl_mircosoft.py diff --git a/subject2-pre/all-process/generate_data/GAN/gan_alibaba.py b/subject2-pre/all-process/generate_data/GAN/gan_alibaba.py new file mode 100644 index 0000000..f11342c --- /dev/null +++ b/subject2-pre/all-process/generate_data/GAN/gan_alibaba.py @@ -0,0 +1,119 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError + + +data = pd.read_csv(r'F:\ourfl\data\processed_alibaba.csv') + +data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] +data['time'] = data['time'] % 24 +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data[features].values + + +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + + +def train_gan(noise_data, real_data, epochs=3000, batch_size=32): + for epoch in range(epochs): + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + generated_samples = generator.predict(noise) + X = np.concatenate((real_samples, generated_samples)) + # 创建标签(真实数据样本为1,合成数据样本为0) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + # 训练判别器 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + # 训练生成器 + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + + # 打印损失和精度 + if epoch % 100 == 0: + print(f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + +noise = np.random.normal(0, 1, size=(num_samples, 7)) +# 使用 data1 作为噪声数据集,data2 作为训练数据集训练 GAN 模型 +train_gan(noise, data2_scaled, epochs=3000, batch_size=32) + +num_samples = 20000 +num_rounds = 30 +num_hours_per_round = 24 + +# 生成虚拟数据 +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() + +for i in range(1, 31): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) +generated_data = time_series_data + +generated_data.to_csv(r'F:\ourfl\genedate\GAN\gan_alibaba.csv', index=False) + + diff --git a/subject2-pre/all-process/generate_data/GAN/gan_google.py b/subject2-pre/all-process/generate_data/GAN/gan_google.py new file mode 100644 index 0000000..fbf5207 --- /dev/null +++ b/subject2-pre/all-process/generate_data/GAN/gan_google.py @@ -0,0 +1,118 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError + + +data = pd.read_csv(r'F:\ourfl\data\pre_google.csv') +data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] +data['time'] = data['time'] % 24 +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data[features].values + + +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + + +def train_gan(noise_data, real_data, epochs=3000, batch_size=32): + for epoch in range(epochs): + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + generated_samples = generator.predict(noise) + X = np.concatenate((real_samples, generated_samples)) + # 创建标签(真实数据样本为1,合成数据样本为0) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + # 训练判别器 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + # 训练生成器 + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + + # 打印损失和精度 + if epoch % 100 == 0: + print(f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + +noise = np.random.normal(0, 1, size=(num_samples, 7)) +# 使用 data1 作为噪声数据集,data2 作为训练数据集训练 GAN 模型 +train_gan(noise, data2_scaled, epochs=3000, batch_size=32) + +num_samples = 20000 +num_rounds = 30 +num_hours_per_round = 24 + +# 生成虚拟数据 +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() + +for i in range(1, 31): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) +generated_data = time_series_data + +generated_data.to_csv(r'F:\ourfl\genedate\GAN\gan_google.csv', index=False) + + diff --git a/subject2-pre/all-process/generate_data/GAN/gan_hpc.py b/subject2-pre/all-process/generate_data/GAN/gan_hpc.py new file mode 100644 index 0000000..8cf3b33 --- /dev/null +++ b/subject2-pre/all-process/generate_data/GAN/gan_hpc.py @@ -0,0 +1,119 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError + + +data = pd.read_csv(r'F:\ourfl\data\processed_hefei.csv') + +data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] +data['time'] = data['time'] % 24 +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data[features].values + + +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + + +def train_gan(noise_data, real_data, epochs=3000, batch_size=32): + for epoch in range(epochs): + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + generated_samples = generator.predict(noise) + X = np.concatenate((real_samples, generated_samples)) + # 创建标签(真实数据样本为1,合成数据样本为0) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + # 训练判别器 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + # 训练生成器 + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + + # 打印损失和精度 + if epoch % 100 == 0: + print(f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + +noise = np.random.normal(0, 1, size=(num_samples, 7)) +# 使用 data1 作为噪声数据集,data2 作为训练数据集训练 GAN 模型 +train_gan(noise, data2_scaled, epochs=3000, batch_size=32) + +num_samples = 20000 +num_rounds = 30 +num_hours_per_round = 24 + +# 生成虚拟数据 +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() + +for i in range(1, 31): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) +generated_data = time_series_data + +generated_data.to_csv(r'F:\ourfl\genedate\GAN\gan_sugan1.csv', index=False) + + diff --git a/subject2-pre/all-process/generate_data/GAN/gan_mircosoft.py b/subject2-pre/all-process/generate_data/GAN/gan_mircosoft.py new file mode 100644 index 0000000..a771474 --- /dev/null +++ b/subject2-pre/all-process/generate_data/GAN/gan_mircosoft.py @@ -0,0 +1,119 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError + + +data = pd.read_csv(r'F:\ourfl\data\pre_microsoft.csv') + +data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] +data['time'] = data['time'] % 24 +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data[features].values + + +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + + +def train_gan(noise_data, real_data, epochs=3000, batch_size=32): + for epoch in range(epochs): + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + generated_samples = generator.predict(noise) + X = np.concatenate((real_samples, generated_samples)) + # 创建标签(真实数据样本为1,合成数据样本为0) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + # 训练判别器 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + # 训练生成器 + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + + # 打印损失和精度 + if epoch % 100 == 0: + print(f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + +noise = np.random.normal(0, 1, size=(num_samples, 7)) +# 使用 data1 作为噪声数据集,data2 作为训练数据集训练 GAN 模型 +train_gan(noise, data2_scaled, epochs=3000, batch_size=32) + +num_samples = 20000 +num_rounds = 30 +num_hours_per_round = 24 + +# 生成虚拟数据 +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() + +for i in range(1, 31): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) +generated_data = time_series_data + +generated_data.to_csv(r'F:\ourfl\genedate\GAN\gan_mircosoft.csv', index=False) + + diff --git a/subject2-pre/all-process/generate_data/TVHL/tvhl_alibaba.py b/subject2-pre/all-process/generate_data/TVHL/tvhl_alibaba.py new file mode 100644 index 0000000..b568705 --- /dev/null +++ b/subject2-pre/all-process/generate_data/TVHL/tvhl_alibaba.py @@ -0,0 +1,137 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError +from scipy.spatial.distance import cdist + + + +data = pd.read_csv(r'F:\ourfl\data\processed_alibaba.csv') + +data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] + + +data['time'] = data['time'] % 24 + +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data[features].values + +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + +def compute_joint_distribution_loss(real_samples, fake_samples): + # Extract the 'relative_time' column + real_relative_time = real_samples[:, 0] + fake_relative_time = fake_samples[:, 0] + + # Remove the 'relative_time' column from the samples + real_samples = real_samples[:, 1:] + fake_samples = fake_samples[:, 1:] + + # Calculate the Euclidean distance between samples without the 'relative_time' feature + distance =0.1* cdist(real_samples, fake_samples, metric='euclidean') + + # Compute the joint distribution loss with 'relative_time' as a condition + conditional_distance = np.abs(real_relative_time - fake_relative_time) + joint_distribution_loss = np.mean(distance * conditional_distance) + + return joint_distribution_loss + +#训练模型 +def train_gan(noise_data, real_data, epochs, batch_size): + for epoch in range(epochs): + noise = noise_data + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + generated_samples = generator.predict(noise) + X = np.concatenate((real_samples, generated_samples)) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + # 计算联合分布差异项 + joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) + # 更新判别器的损失函数 + discriminator_loss = discriminator_loss + 0.15 * joint_distribution_loss + # 打印损失和精度 + if epoch % 100 == 0: + print( + f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + +#设置高斯噪声(全局统一) +noise = np.random.normal(0, 1, size=(10000, 7)) +#训练模型 +train_gan(data, noise, epochs=3000, batch_size=32) +# 数据生成 +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() +for i in range(1, 31): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) +generated_data = time_series_data + +generated_data.to_csv(r'F:\ourfl\genedate\TVHL\tvhl_alibaba.csv', index=False) + + diff --git a/subject2-pre/all-process/generate_data/TVHL/tvhl_google.py b/subject2-pre/all-process/generate_data/TVHL/tvhl_google.py new file mode 100644 index 0000000..c12d4e8 --- /dev/null +++ b/subject2-pre/all-process/generate_data/TVHL/tvhl_google.py @@ -0,0 +1,136 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError +from scipy.spatial.distance import cdist + + + +data = pd.read_csv(r'F:\ourfl\data\pre_google.csv') + +data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] + + +data['time'] = data['time'] % 24 + +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data[features].values + +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + +def compute_joint_distribution_loss(real_samples, fake_samples): + # Extract the 'relative_time' column + real_relative_time = real_samples[:, 0] + fake_relative_time = fake_samples[:, 0] + + # Remove the 'relative_time' column from the samples + real_samples = real_samples[:, 1:] + fake_samples = fake_samples[:, 1:] + + # Calculate the Euclidean distance between samples without the 'relative_time' feature + distance =0.1* cdist(real_samples, fake_samples, metric='euclidean') + + # Compute the joint distribution loss with 'relative_time' as a condition + conditional_distance = np.abs(real_relative_time - fake_relative_time) + joint_distribution_loss = np.mean(distance * conditional_distance) + + return joint_distribution_loss + +#训练模型 +def train_gan(noise_data, real_data, epochs, batch_size): + for epoch in range(epochs): + noise = noise_data + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + generated_samples = generator.predict(noise) + X = np.concatenate((real_samples, generated_samples)) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + # 计算联合分布差异项 + joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) + # 更新判别器的损失函数 + discriminator_loss = discriminator_loss + 0.15 * joint_distribution_loss + # 打印损失和精度 + if epoch % 100 == 0: + print( + f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + +#设置高斯噪声(全局统一) +noise = np.random.normal(0, 1, size=(10000, 7)) +#训练模型 +train_gan(data, noise, epochs=3000, batch_size=32) +# 数据生成 +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() +for i in range(1, 31): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) +generated_data = time_series_data +generated_data.to_csv(r'F:\ourfl\genedate\TVHL\tvhl_google.csv', index=False) + + diff --git a/subject2-pre/all-process/generate_data/TVHL/tvhl_mircosoft.py b/subject2-pre/all-process/generate_data/TVHL/tvhl_mircosoft.py new file mode 100644 index 0000000..12d96bb --- /dev/null +++ b/subject2-pre/all-process/generate_data/TVHL/tvhl_mircosoft.py @@ -0,0 +1,137 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError +from scipy.spatial.distance import cdist + + + +data = pd.read_csv(r'F:\ourfl\data\pre_microsoft.csv') + +data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] + + +data['time'] = data['time'] % 24 + +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data[features].values + +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + +def compute_joint_distribution_loss(real_samples, fake_samples): + # Extract the 'relative_time' column + real_relative_time = real_samples[:, 0] + fake_relative_time = fake_samples[:, 0] + + # Remove the 'relative_time' column from the samples + real_samples = real_samples[:, 1:] + fake_samples = fake_samples[:, 1:] + + # Calculate the Euclidean distance between samples without the 'relative_time' feature + distance =0.1* cdist(real_samples, fake_samples, metric='euclidean') + + # Compute the joint distribution loss with 'relative_time' as a condition + conditional_distance = np.abs(real_relative_time - fake_relative_time) + joint_distribution_loss = np.mean(distance * conditional_distance) + + return joint_distribution_loss + +#训练模型 +def train_gan(noise_data, real_data, epochs, batch_size): + for epoch in range(epochs): + noise = noise_data + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + generated_samples = generator.predict(noise) + X = np.concatenate((real_samples, generated_samples)) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + # 计算联合分布差异项 + joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) + # 更新判别器的损失函数 + discriminator_loss = discriminator_loss + 0.15 * joint_distribution_loss + # 打印损失和精度 + if epoch % 100 == 0: + print( + f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + +#设置高斯噪声(全局统一) +noise = np.random.normal(0, 1, size=(10000, 7)) +#训练模型 +train_gan(data, noise, epochs=3000, batch_size=32) +# 数据生成 +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() +for i in range(1, 31): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) +generated_data = time_series_data + +generated_data.to_csv(r'F:\ourfl\genedate\VHL\vhl_microsoft.csv', index=False) + + diff --git a/subject2-pre/all-process/generate_data/TVHL/tvhl_sugan.py b/subject2-pre/all-process/generate_data/TVHL/tvhl_sugan.py new file mode 100644 index 0000000..7fda7ee --- /dev/null +++ b/subject2-pre/all-process/generate_data/TVHL/tvhl_sugan.py @@ -0,0 +1,137 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError +from scipy.spatial.distance import cdist + + + +data = pd.read_csv(r'F:\ourfl\data\processed_hefei.csv') + +data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] + + +data['time'] = data['time'] % 24 + +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data[features].values + +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + +def compute_joint_distribution_loss(real_samples, fake_samples): + # Extract the 'relative_time' column + real_relative_time = real_samples[:, 0] + fake_relative_time = fake_samples[:, 0] + + # Remove the 'relative_time' column from the samples + real_samples = real_samples[:, 1:] + fake_samples = fake_samples[:, 1:] + + # Calculate the Euclidean distance between samples without the 'relative_time' feature + distance =0.1* cdist(real_samples, fake_samples, metric='euclidean') + + # Compute the joint distribution loss with 'relative_time' as a condition + conditional_distance = np.abs(real_relative_time - fake_relative_time) + joint_distribution_loss = np.mean(distance * conditional_distance) + + return joint_distribution_loss + +#训练模型 +def train_gan(noise_data, real_data, epochs, batch_size): + for epoch in range(epochs): + noise = noise_data + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + generated_samples = generator.predict(noise) + X = np.concatenate((real_samples, generated_samples)) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + # 计算联合分布差异项 + joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) + # 更新判别器的损失函数 + discriminator_loss = discriminator_loss + 0.15 * joint_distribution_loss + # 打印损失和精度 + if epoch % 100 == 0: + print( + f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + +#设置高斯噪声(全局统一) +noise = np.random.normal(0, 1, size=(10000, 7)) +#训练模型 +train_gan(data, noise, epochs=3000, batch_size=32) +# 数据生成 +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() +for i in range(1, 31): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) +generated_data = time_series_data + +generated_data.to_csv(r'F:\ourfl\genedate\VHL\vhl_HPC.csv', index=False) + + diff --git a/subject2-pre/all-process/generate_data/VHL/vhl_alibaba.py b/subject2-pre/all-process/generate_data/VHL/vhl_alibaba.py new file mode 100644 index 0000000..e2ebbd2 --- /dev/null +++ b/subject2-pre/all-process/generate_data/VHL/vhl_alibaba.py @@ -0,0 +1,141 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError +from scipy.spatial.distance import cdist + + + +data = pd.read_csv(r'F:\ourfl\data\processed_alibaba.csv') + +data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] + + +data['time'] = data['time'] % 24 + +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data1[features].values + + +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + +def compute_joint_distribution_loss(real_samples, fake_samples): + # 计算真实样本和生成样本之间的欧氏距离 + distance = cdist(real_samples, fake_samples, metric='euclidean') + # 计算联合分布差异项 + joint_distribution_loss = distance.mean() + return joint_distribution_loss + +def train_gan(noise_data, real_data, epochs=3000, batch_size=16): + for epoch in range(epochs): + + noise = noise_data + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + + + generated_samples = generator.predict(noise) + X = np.concatenate((real_samples, generated_samples)) + + # 创建标签(真实数据样本为1,合成数据样本为0) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + + # 训练判别器 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + + # 训练生成器 + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + + # 计算联合分布差异项 + joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) + # 更新判别器的损失函数 + discriminator_loss = discriminator_loss + 0.1 * joint_distribution_loss + + # 打印损失和精度 + if epoch % 100 == 0: + print( + f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + +noise = np.random.normal(0, 1, size=(num_samples, 7)) +# 使用 data1 作为噪声数据集,data2 作为训练数据集训练 GAN 模型 +train_gan(data1_scaled, data2_scaled, epochs=3000, batch_size=32) + +num_samples = 20000 +num_rounds = 30 +num_hours_per_round = 24 + +# 生成虚拟数据 +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() +for i in range(1, 31): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) +generated_data = time_series_data + +generated_data.to_csv(r'F:\ourfl\genedate\VHL\vhl_alibaba.csv', index=False) + + diff --git a/subject2-pre/all-process/generate_data/VHL/vhl_google.py b/subject2-pre/all-process/generate_data/VHL/vhl_google.py new file mode 100644 index 0000000..70f2259 --- /dev/null +++ b/subject2-pre/all-process/generate_data/VHL/vhl_google.py @@ -0,0 +1,141 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError +from scipy.spatial.distance import cdist + + + +data = pd.read_csv(r'F:\ourfl\data\pre_google.csv') +data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] + + +data['time'] = data['time'] % 24 + +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data1[features].values + + +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + +def compute_joint_distribution_loss(real_samples, fake_samples): + # 计算真实样本和生成样本之间的欧氏距离 + distance = cdist(real_samples, fake_samples, metric='euclidean') + # 计算联合分布差异项 + joint_distribution_loss = distance.mean() + return joint_distribution_loss + +def train_gan(noise_data, real_data, epochs=3000, batch_size=16): + for epoch in range(epochs): + + noise = noise_data + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + + + generated_samples = generator.predict(noise) + X = np.concatenate((real_samples, generated_samples)) + + # 创建标签(真实数据样本为1,合成数据样本为0) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + + # 训练判别器 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + + # 训练生成器 + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + + # 计算联合分布差异项 + joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) + # 更新判别器的损失函数 + discriminator_loss = discriminator_loss + 0.1 * joint_distribution_loss + + # 打印损失和精度 + if epoch % 100 == 0: + print( + f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + +noise = np.random.normal(0, 1, size=(num_samples, 7)) +# 使用 data1 作为噪声数据集,data2 作为训练数据集训练 GAN 模型 +train_gan(data1_scaled, data2_scaled, epochs=3000, batch_size=32) + +num_samples = 20000 +num_rounds = 30 +num_hours_per_round = 24 + +# 生成虚拟数据 +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() +for i in range(1, 31): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) +generated_data = time_series_data + + +generated_data.to_csv(r'F:\ourfl\genedate\VHL\vhl_google.csv', index=False) + + diff --git a/subject2-pre/all-process/generate_data/VHL/vhl_hpc.py b/subject2-pre/all-process/generate_data/VHL/vhl_hpc.py new file mode 100644 index 0000000..9f057a4 --- /dev/null +++ b/subject2-pre/all-process/generate_data/VHL/vhl_hpc.py @@ -0,0 +1,141 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError +from scipy.spatial.distance import cdist + + +data2 = pd.read_csv(r'F:\ourfl\data\processed_hpc.csv') + + +data = data[['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan']] + + +data['time'] = data['time'] % 24 + +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data = data1[features].values + + +scaler = MinMaxScaler(feature_range=(0, 1)) +data_scaled = scaler.fit_transform(data) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='binary_crossentropy', optimizer='adam') + +def compute_joint_distribution_loss(real_samples, fake_samples): + # 计算真实样本和生成样本之间的欧氏距离 + distance = cdist(real_samples, fake_samples, metric='euclidean') + # 计算联合分布差异项 + joint_distribution_loss = distance.mean() + return joint_distribution_loss + +def train_gan(noise_data, real_data, epochs=3000, batch_size=16): + for epoch in range(epochs): + + noise = noise_data + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + + + generated_samples = generator.predict(noise) + X = np.concatenate((real_samples, generated_samples)) + + # 创建标签(真实数据样本为1,合成数据样本为0) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + + # 训练判别器 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + + # 训练生成器 + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + + # 计算联合分布差异项 + joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) + # 更新判别器的损失函数 + discriminator_loss = discriminator_loss + 0.1 * joint_distribution_loss + + # 打印损失和精度 + if epoch % 100 == 0: + print( + f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + +noise = np.random.normal(0, 1, size=(num_samples, 7)) +# 使用 data1 作为噪声数据集,data2 作为训练数据集训练 GAN 模型 +train_gan(data1_scaled, data2_scaled, epochs=3000, batch_size=32) + +num_samples = 20000 +num_rounds = 30 +num_hours_per_round = 24 + +# 生成虚拟数据 +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() +for i in range(1, 31): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data,empty_df.iloc[j*24+i:j*24+i+1].copy()]) +generated_data = time_series_data + +generated_data.to_csv(r'F:\ourfl\genedate\VHL\vhl_hpc.csv', index=False) + + diff --git a/subject2-pre/all-process/generate_data/VHL/vhl_mircosoft.py b/subject2-pre/all-process/generate_data/VHL/vhl_mircosoft.py new file mode 100644 index 0000000..9222091 --- /dev/null +++ b/subject2-pre/all-process/generate_data/VHL/vhl_mircosoft.py @@ -0,0 +1,152 @@ +import numpy as np +import pandas as pd +from tensorflow.keras.models import Sequential +from sklearn.preprocessing import MinMaxScaler +from tensorflow.keras.layers import Dense, LeakyReLU +import tensorflow as tf +from tensorflow.keras.losses import MeanSquaredError +from tensorflow.keras.metrics import RootMeanSquaredError +from scipy.spatial.distance import cdist + +data1 = pd.read_csv(r'F:\ourfl\data\processed_alibaba.csv') +data2 = pd.read_csv(r'F:\ourfl\data\pre_microsoft.csv') + + + +# 检查 data2 是否缺少特征,如果是,则用 data1 中相应特征的平均值来填充 +for column in data1.columns: + if column not in data2.columns: + data2[column] = data1[column].mean() + +# data2 = data2['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] + +features = ['time', 'cpu_use', 'gpu_use', 'mem_use', 'cpu_plan', 'gpu_plan','mem_plan'] +data1 = data1[features].values +data2 = data2[features].values + + + +scaler = MinMaxScaler(feature_range=(0, 1)) +data1_scaled = scaler.fit_transform(data1) +data2_scaled = scaler.fit_transform(data2) + + +def build_generator(): + model = Sequential() + model.add(Dense(64, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(32)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(7, activation='sigmoid')) + return model + +generator = build_generator() + + +def build_discriminator(): + model = Sequential() + model.add(Dense(32, input_dim=7)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(16)) + model.add(LeakyReLU(alpha=0.2)) + model.add(Dense(1, activation='sigmoid')) + return model + +discriminator = build_discriminator() + +discriminator.compile(loss='mean_squared_error', optimizer='adam', metrics=['accuracy']) +discriminator.trainable = False +gan_input = tf.keras.Input(shape=(7,)) +gan_output = discriminator(generator(gan_input)) +gan = tf.keras.Model(gan_input, gan_output) +gan.compile(loss='mean_squared_error', optimizer='adam') + + +def compute_joint_distribution_loss(real_samples, fake_samples): + # 计算真实样本和生成样本之间的欧氏距离 + distance = cdist(real_samples, fake_samples, metric='euclidean') + # 计算联合分布差异项 + joint_distribution_loss = distance.mean() + return joint_distribution_loss + + +def train_gan(noise_data, real_data, epochs=3000, batch_size=32): + for epoch in range(epochs): + + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + + + real_samples = real_data[np.random.randint(0, real_data.shape[0], size=batch_size)] + + + generated_samples = generator.predict(noise) + + + X = np.concatenate((real_samples, generated_samples)) + + # 创建标签(真实数据样本为1,合成数据样本为0) + y = np.ones(2 * batch_size) + y[batch_size:] = 0 + + # 训练判别器 + discriminator.trainable = True + discriminator_loss = discriminator.train_on_batch(X, y) + + # 训练生成器 + noise = noise_data[np.random.randint(0, noise_data.shape[0], size=batch_size)] + y_gen = np.ones(batch_size) + discriminator.trainable = False + generator_loss = gan.train_on_batch(noise, y_gen) + + # 计算联合分布差异项 + joint_distribution_loss = compute_joint_distribution_loss(real_samples, generated_samples) + # 更新判别器的损失函数 + discriminator_loss = discriminator_loss + 0.1 * joint_distribution_loss + + # 打印损失和精度 + if epoch % 100 == 0: + print( + f'Epoch: {epoch}/{epochs}, Discriminator Loss: {discriminator_loss}, Generator Loss: {generator_loss}') + + + +num_samples = 20000 +num_rounds = 30 +num_hours_per_round = 24 + + +noise = np.random.normal(0, 1, size=(num_samples, 7)) +generated_data = generator.predict(noise) +generated_data = pd.DataFrame(generated_data, columns=features) + +# 将时间恢复到24小时制 +generated_data['time'] = (generated_data['time'] * 24).astype(int) + +data_by_hour = {} +# 将generated_data按时间分组,放入字典中对应的键中 +for i in range(24): + data_by_hour[i] = generated_data[generated_data['time'] == i] + +empty_df = pd.DataFrame() +# 遍历1到23小时,检查每个小时的数据是否为空 +for i in range(0, 24): + if data_by_hour[i].empty: + next_i = (i + 1) % 24 # 计算下一个小时的索引,使用取模运算确保最后一个小时的数据获取方式是循环到小时1的数据 + data_by_hour[i] = data_by_hour[next_i].head(1) + data_length = len(data_by_hour[i]) + if data_length < 31: + data_by_hour[i] = pd.concat([data_by_hour[i], data_by_hour[5].head(30)]) + +for i in range(0, 24): + empty_df = pd.concat([empty_df, data_by_hour[i].head(30)]) + +# 复制empty_df的第一行数据,并赋值给time_series_data +time_series_data = empty_df.iloc[:1].copy() +for i in range(0, 30): + for j in range(0, 24): + time_series_data = pd.concat([time_series_data, empty_df.iloc[i * 24 + j:i * 24 + j + 1].copy()]) +generated_data = time_series_data + +generated_data.to_csv(r'F:\ourfl\genedate\VHL\vhl_mircosoft.csv', index=False) + + -- Gitee From fe860f335ca19c8f5928e85915590401feee862a Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 04:35:26 +0000 Subject: [PATCH 23/25] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> --- subject2-pre/all-process/ourfl_kd/a1.py | 300 +++++++++++++++++++++++ subject2-pre/all-process/ourfl_kd/g1.py | 301 ++++++++++++++++++++++++ subject2-pre/all-process/ourfl_kd/m1.py | 299 +++++++++++++++++++++++ subject2-pre/all-process/ourfl_kd/s1.py | 299 +++++++++++++++++++++++ 4 files changed, 1199 insertions(+) create mode 100644 subject2-pre/all-process/ourfl_kd/a1.py create mode 100644 subject2-pre/all-process/ourfl_kd/g1.py create mode 100644 subject2-pre/all-process/ourfl_kd/m1.py create mode 100644 subject2-pre/all-process/ourfl_kd/s1.py diff --git a/subject2-pre/all-process/ourfl_kd/a1.py b/subject2-pre/all-process/ourfl_kd/a1.py new file mode 100644 index 0000000..a5ddab0 --- /dev/null +++ b/subject2-pre/all-process/ourfl_kd/a1.py @@ -0,0 +1,300 @@ +import numpy as np +import pandas as pd +import torch +import torch.nn as nn +from sklearn.preprocessing import StandardScaler, MinMaxScaler +from torch.utils.data import TensorDataset +from tqdm import tqdm +from sklearn.metrics import mean_squared_error, mean_absolute_error + + +data1 = pd.read_csv(r'F:\ourfl\data\processed_alibaba.csv') +df = data1[['cpu_use']] +# 2.将数据进行标准化 +scaler = MinMaxScaler(feature_range=(0, 1)) +data = scaler.fit_transform(df.values) + + + +class Config(): + + timestep = 2 # 时间步长,就是利用多少时间窗口 + batch_size = 32 # 批次大小 + feature_size = 1 # 每个步长对应的特征数量,这里使用8维特征 + hidden_size = 256 # 隐层大小 + output_size = 1 # 由于是单输出任务,最终输出层大小为1,预测未来1天的avg_mem + num_layers = 2 # gru的层数 + epochs = 32 # 迭代轮数 + best_loss = float('inf') # 记录损失,初始化为正无穷大 + learning_rate = 0.0001 # 学习率 + model_name = 'gru' # 模型名称 + save_path = './{}.pth'.format(model_name) # 最优模型保存路径 + +config = Config() + +#新模型 +class newConfig(): + + timestep = 2 # 时间步长,就是利用多少时间窗口 + batch_size = 32 # 批次大小 + feature_size = 1 # 每个步长对应的特征数量,这里使用8维特征 + hidden_size = 512 # 隐层大小 + output_size = 1 # 由于是单输出任务,最终输出层大小为1,预测未来1天的avg_mem + num_layers = 2 # gru的层数 + epochs = 32 # 迭代轮数 + best_loss = float('inf') # 记录损失,初始化为正无穷大 + learning_rate = 0.0001 # 学习率 + model_name = 'gru' # 模型名称 + save_path = './{}.pth'.format(model_name) # 最优模型保存路径 + +newconfig = newConfig() + +# 形成训练数据 +def split_data1(data, timestep, feature_size): + dataX = [] # 保存X + dataY = [] # 保存Y + + # 将整个窗口的数据保存到X中,将未来一天保存到Y中 + for index in range(len(data) - timestep): + dataX.append(data[index: index + timestep]) + dataY.append(data[index + timestep][0]) # 目标列avg_mem为第5列,索引为4 + + dataX = np.array(dataX) + dataY = np.array(dataY) + + return dataX, dataY + +# 形成训练数据 +def split_data(data, timestep, feature_size): + dataX = [] # 保存X + dataY = [] # 保存Y + + # 将整个窗口的数据保存到X中,将未来一天保存到Y中 + for index in range(len(data) - timestep): + dataX.append(data[index: index + timestep]) + dataY.append(data[index + timestep][0]) # 目标列avg_mem为第5列,索引为4 + + dataX = np.array(dataX) + dataY = np.array(dataY) + + # 获取训练集大小 + train_size = int(np.round(0.6 * dataX.shape[0])) + + # 划分训练集、测试集 + x_train = dataX[: train_size, :] + y_train = dataY[: train_size].reshape(-1, 1) + + x_test = dataX[train_size:, :] + y_test = dataY[train_size:].reshape(-1, 1) + + return [x_train, y_train, x_test, y_test] + +# 定义GRU网络 +class GRU(nn.Module): + def __init__(self, feature_size, hidden_size, num_layers, output_size): + super(GRU, self).__init__() + self.hidden_size = hidden_size # 隐层大小 + self.num_layers = num_layers # gru层数 + self.gru = nn.GRU(feature_size, hidden_size, num_layers, batch_first=True) + self.fc = nn.Linear(hidden_size, output_size) + + def forward(self, x, hidden=None): + batch_size = x.shape[0] + + # 初始化隐层状态 + if hidden is None: + h_0 = x.data.new(self.num_layers, batch_size, self.hidden_size).fill_(0).float() + else: + h_0 = hidden + + # GRU运算 + output, h_0 = self.gru(x, h_0) + + # 获取GRU输出的维度信息 + batch_size, timestep, hidden_size = output.shape + + # 将output变成 batch_size * timestep, hidden_dim + output = output.reshape(-1, hidden_size) + + # 全连接层 + output = self.fc(output) + + # 转换维度,用于输出 + output = output.reshape(batch_size, timestep, -1) + + # 返回最后一个时间步的数据 + return output[:, -1, :] + + +# 获取训练数据 +x_train, y_train, x_test, y_test = split_data(data, config.timestep, config.feature_size) + +x_test1, y_test1 = split_data1(data, config.timestep, config.feature_size) + +# 4.将数据转为tensor +x_train_tensor = torch.from_numpy(x_train).to(torch.float32) +y_train_tensor = torch.from_numpy(y_train).to(torch.float32) +x_test_tensor = torch.from_numpy(x_test).to(torch.float32) +y_test_tensor = torch.from_numpy(y_test).to(torch.float32) + +# 5.形成训练数据集 +train_data = TensorDataset(x_train_tensor, y_train_tensor) +test_data = TensorDataset(x_test_tensor, y_test_tensor) + +# 6.将数据加载成迭代器 +train_loader = torch.utils.data.DataLoader(train_data, + config.batch_size, + shuffle=False) + +test_loader = torch.utils.data.DataLoader(test_data, + config.batch_size, + shuffle=False) + +# 假设您的模型类是GRU,config中包含相关的模型配置信息 +pre_trained_model = GRU(config.feature_size, config.hidden_size, config.num_layers, config.output_size) + +# 加载预训练模型的参数 +# model_path = r"F:\ourfl\fedavg\tvhl_client_model_1.pth" +model_path = r"F:\ourfl\fedavg\client_model_1.pth" + +pre_trained_model.load_state_dict(torch.load(model_path)) + +transfer_model = GRU(newconfig.feature_size, newconfig.hidden_size, newconfig.num_layers, newconfig.output_size) # 定义GRU网络 +loss_function = nn.MSELoss() # 定义损失函数 +optimizer_transfer = torch.optim.AdamW(transfer_model.parameters(), lr= newconfig.learning_rate) # 定义优化器 + +# 定义生成随机矩阵的数量 +num_random_matrices = 30 + +# 获取预训练模型和新模型的参数 +pre_trained_params = pre_trained_model.state_dict() +transfer_params = transfer_model.state_dict() # 使用之前定义的模型 model,而不是新的 transfer_model + +# 计算预训练模型参数的 L2 范数 +pre_trained_l2_norm = torch.norm(torch.stack([torch.norm(param) for param in pre_trained_params.values()])) + +# 计算新模型参数的 L2 范数 +transfer_l2_norm = torch.norm(torch.stack([torch.norm(param) for param in transfer_params.values()])) + +# 初始化最小 L2 范数和对应的随机矩阵 +min_l2_norm = float('inf') +min_l2_norm_random_matrix = None + +# 循环生成随机矩阵,并计算 L2 范数 +for i in range(num_random_matrices): + # 生成随机矩阵 + random_matrix = torch.rand(pre_trained_l2_norm.size()) + + # 应用随机矩阵到预训练模型参数 + for name, param in pre_trained_params.items(): + if "gru" in name: + name = name.replace("gru.", "") # 移除模型名称前缀 + if name in transfer_params: + transfer_params[name].copy_(param * random_matrix) + + # 计算使用当前随机矩阵映射后的新模型参数的 L2 范数 + transfer_l2_norm_after_mapping = torch.norm(torch.stack([torch.norm(param) for param in transfer_params.values()])) + + # 更新最小 L2 范数和对应的随机矩阵 + if transfer_l2_norm_after_mapping < min_l2_norm: + min_l2_norm = transfer_l2_norm_after_mapping + min_l2_norm_random_matrix = random_matrix + +# 将最优的随机矩阵应用于预训练模型参数 +for name, param in pre_trained_params.items(): + if "gru" in name: + name = name.replace("gru.", "") # 移除模型名称前缀 + if name in transfer_params: + transfer_params[name].copy_(param * min_l2_norm_random_matrix) + + +num_transfer_epochs = 30 # 根据需要设置合适的训练轮数 + + +for param in pre_trained_model.parameters(): + param.requires_grad = False + + +for epoch in range(num_transfer_epochs): + transfer_model.train() + running_loss = 0 + train_bar = tqdm(train_loader) # 使用之前定义的数据加载器 train_loader + for data in train_bar: + x_train, y_train = data + optimizer_transfer.zero_grad() + y_train_pred = transfer_model(x_train) + y_pretrained_pred = pre_trained_model(x_train) + loss = loss_function(y_train_pred, y_train) + loss_function(y_train_pred, y_pretrained_pred) + loss.backward() + optimizer_transfer.step() + + running_loss += loss.item() + train_bar.set_description("Transfer Learning epoch[{}/{}] loss:{:.3f}".format(epoch + 1, num_transfer_epochs, loss.item())) + + + +transfer_model_save_path = './transfer_model.pth' +torch.save(transfer_model.state_dict(), transfer_model_save_path) + +print('Finished Transfer Learning') + +transfer_model.eval() + +# Combine the training and test data to use the entire dataset +x_combined = np.concatenate([x_train, x_test], axis=0) +y_combined = np.concatenate([y_train, y_test], axis=0) + +# Convert the combined data to tensors +x_combined_tensor = torch.from_numpy(x_combined).to(torch.float32) +y_combined_tensor = torch.from_numpy(y_combined).to(torch.float32) + +# Create a new DataLoader for the combined data +combined_data = TensorDataset(x_combined_tensor, y_combined_tensor) +combined_loader = torch.utils.data.DataLoader(combined_data, batch_size=config.batch_size, shuffle=False) + +with torch.no_grad(): + y_true = [] # Ground truth labels + y_pred = [] # Predicted labels + for data in test_loader: # Using the test data loader + x_test1, y_test1 = data + predictions = transfer_model(x_test1) + y_true.extend(y_test1.numpy().flatten()) + y_pred.extend(predictions.numpy().flatten()) + +# Convert the lists to numpy arrays for easier calculations +y_true = np.array(y_true) +y_pred = np.array(y_pred) + +# Calculate evaluation metrics +mse = mean_squared_error(y_true, y_pred) +rmse = np.sqrt(mse) +mae = mean_absolute_error(y_true, y_pred) + +# Calculate MAPE and SMAPE with checks for division by zero +mape_denominator = np.maximum(np.abs(y_true), 1e-8) # Avoid division by zero +mape = np.mean(np.abs((y_true - y_pred) / mape_denominator)) * 100 + +smape_denominator = np.maximum(np.abs(y_true) + np.abs(y_pred), 1e-8) # Avoid division by zero +smape = 2.0 * np.mean(np.abs(y_pred - y_true) / smape_denominator) * 100 + + +print("Mean Squared Error (MSE):", mse) +print("Root Mean Squared Error (RMSE):", rmse) +print("Mean Absolute Error (MAE):", mae) +print("Mean Absolute Percentage Error (MAPE):", mape) +print("Symmetric Mean Absolute Percentage Error (SMAPE):", smape) + +# Save predictions and metrics to CSV +result_df = pd.DataFrame({'Truth': y_true, 'Predicted': y_pred}) +result_df.to_csv('transfer_learning_predictions.csv', index=False) + +metrics_df = pd.DataFrame({ + 'MSE': [mse], + 'RMSE': [rmse], + 'MAE': [mae], + 'MAPE': [mape], + 'SMAPE': [smape] +}) +metrics_df.to_csv('transfer_learning_metrics.csv', index=False) + +print("Transfer learning predictions and metrics saved successfully!") diff --git a/subject2-pre/all-process/ourfl_kd/g1.py b/subject2-pre/all-process/ourfl_kd/g1.py new file mode 100644 index 0000000..46aef3e --- /dev/null +++ b/subject2-pre/all-process/ourfl_kd/g1.py @@ -0,0 +1,301 @@ +import numpy as np +import pandas as pd +import torch +import torch.nn as nn +from sklearn.preprocessing import StandardScaler, MinMaxScaler +from torch.utils.data import TensorDataset +from tqdm import tqdm +from sklearn.metrics import mean_squared_error, mean_absolute_error + + +data1 = pd.read_csv(r'F:\ourfl\data\pre_google.csv') +df = data1[['cpu_use']] +# 2.将数据进行标准化 +scaler = MinMaxScaler(feature_range=(0, 1)) +data = scaler.fit_transform(df.values) + +u = 0.5 + +num_transfer_epochs = 30 # 根据需要设置合适的训练轮数 + + +class Config(): + + timestep = 2 # 时间步长,就是利用多少时间窗口 + batch_size = 32 # 批次大小 + feature_size = 1 # 每个步长对应的特征数量,这里使用8维特征 + hidden_size = 256 # 隐层大小 + output_size = 1 # 由于是单输出任务,最终输出层大小为1,预测未来1天的avg_mem + num_layers = 2 # gru的层数 + epochs = 32 # 迭代轮数 + best_loss = float('inf') # 记录损失,初始化为正无穷大 + learning_rate = 0.0005 # 学习率 + model_name = 'gru' # 模型名称 + save_path = './{}.pth'.format(model_name) # 最优模型保存路径 + +config = Config() + +#新模型 +class newConfig(): + + timestep = 2 # 时间步长,就是利用多少时间窗口 + batch_size = 32 # 批次大小 + feature_size = 1 # 每个步长对应的特征数量,这里使用8维特征 + hidden_size = 512 # 隐层大小 + output_size = 1 # 由于是单输出任务,最终输出层大小为1,预测未来1天的avg_mem + num_layers = 4 # gru的层数 + epochs = 32 # 迭代轮数 + best_loss = float('inf') # 记录损失,初始化为正无穷大 + learning_rate = 0.0001 # 学习率 + model_name = 'gru' # 模型名称 + save_path = './{}.pth'.format(model_name) # 最优模型保存路径 + +newconfig = newConfig() + +# 形成训练数据 +def split_data1(data, timestep, feature_size): + dataX = [] # 保存X + dataY = [] # 保存Y + + # 将整个窗口的数据保存到X中,将未来一天保存到Y中 + for index in range(len(data) - timestep): + dataX.append(data[index: index + timestep]) + dataY.append(data[index + timestep][0]) # 目标列avg_mem为第5列,索引为4 + + dataX = np.array(dataX) + dataY = np.array(dataY) + + return dataX, dataY + +# 形成训练数据 +def split_data(data, timestep, feature_size): + dataX = [] # 保存X + dataY = [] # 保存Y + + # 将整个窗口的数据保存到X中,将未来一天保存到Y中 + for index in range(len(data) - timestep): + dataX.append(data[index: index + timestep]) + dataY.append(data[index + timestep][0]) # 目标列avg_mem为第5列,索引为4 + + dataX = np.array(dataX) + dataY = np.array(dataY) + + # 获取训练集大小 + train_size = int(np.round(0.6 * dataX.shape[0])) + + # 划分训练集、测试集 + x_train = dataX[: train_size, :] + y_train = dataY[: train_size].reshape(-1, 1) + + x_test = dataX[train_size:, :] + y_test = dataY[train_size:].reshape(-1, 1) + + return [x_train, y_train, x_test, y_test] + +# 定义GRU网络 +class GRU(nn.Module): + def __init__(self, feature_size, hidden_size, num_layers, output_size): + super(GRU, self).__init__() + self.hidden_size = hidden_size # 隐层大小 + self.num_layers = num_layers # gru层数 + self.gru = nn.GRU(feature_size, hidden_size, num_layers, batch_first=True) + self.fc = nn.Linear(hidden_size, output_size) + + def forward(self, x, hidden=None): + batch_size = x.shape[0] + + # 初始化隐层状态 + if hidden is None: + h_0 = x.data.new(self.num_layers, batch_size, self.hidden_size).fill_(0).float() + else: + h_0 = hidden + + # GRU运算 + output, h_0 = self.gru(x, h_0) + + # 获取GRU输出的维度信息 + batch_size, timestep, hidden_size = output.shape + + # 将output变成 batch_size * timestep, hidden_dim + output = output.reshape(-1, hidden_size) + + # 全连接层 + output = self.fc(output) + + # 转换维度,用于输出 + output = output.reshape(batch_size, timestep, -1) + + # 返回最后一个时间步的数据 + return output[:, -1, :] + + +# 获取训练数据 +x_train, y_train, x_test, y_test = split_data(data, config.timestep, config.feature_size) + +x_test1, y_test1 = split_data1(data, config.timestep, config.feature_size) + +# 4.将数据转为tensor +x_train_tensor = torch.from_numpy(x_train).to(torch.float32) +y_train_tensor = torch.from_numpy(y_train).to(torch.float32) +x_test_tensor = torch.from_numpy(x_test).to(torch.float32) +y_test_tensor = torch.from_numpy(y_test).to(torch.float32) + +# 5.形成训练数据集 +train_data = TensorDataset(x_train_tensor, y_train_tensor) +test_data = TensorDataset(x_test_tensor, y_test_tensor) + +# 6.将数据加载成迭代器 +train_loader = torch.utils.data.DataLoader(train_data, + config.batch_size, + shuffle=False) + +test_loader = torch.utils.data.DataLoader(test_data, + config.batch_size, + shuffle=False) + +# 假设您的模型类是GRU,config中包含相关的模型配置信息 +pre_trained_model = GRU(config.feature_size, config.hidden_size, config.num_layers, config.output_size) + +# 加载预训练模型的参数 +# model_path = r"F:\ourfl\fedavg\tvhl_client_model_1.pth" +model_path = r"F:\ourfl\fedavg\client_model_2.pth" + +pre_trained_model.load_state_dict(torch.load(model_path)) + +transfer_model = GRU(newconfig.feature_size, newconfig.hidden_size, newconfig.num_layers, newconfig.output_size) # 定义GRU网络 +loss_function = nn.MSELoss() # 定义损失函数 +optimizer_transfer = torch.optim.AdamW(transfer_model.parameters(), lr= newconfig.learning_rate) # 定义优化器 + +# 定义生成随机矩阵的数量 +num_random_matrices = 100 + +# 获取预训练模型和新模型的参数 +pre_trained_params = pre_trained_model.state_dict() +transfer_params = transfer_model.state_dict() # 使用之前定义的模型 model,而不是新的 transfer_model + +# 计算预训练模型参数的 L2 范数 +pre_trained_l2_norm = torch.norm(torch.stack([torch.norm(param) for param in pre_trained_params.values()])) + +# 计算新模型参数的 L2 范数 +transfer_l2_norm = torch.norm(torch.stack([torch.norm(param) for param in transfer_params.values()])) + +# 初始化最小 L2 范数和对应的随机矩阵 +min_l2_norm = float('inf') +min_l2_norm_random_matrix = None + +# 循环生成随机矩阵,并计算 L2 范数 +for i in range(num_random_matrices): + # 生成随机矩阵 + random_matrix = torch.rand(pre_trained_l2_norm.size()) + + # 应用随机矩阵到预训练模型参数 + for name, param in pre_trained_params.items(): + if "gru" in name: + name = name.replace("gru.", "") # 移除模型名称前缀 + if name in transfer_params: + transfer_params[name].copy_(param * random_matrix) + + # 计算使用当前随机矩阵映射后的新模型参数的 L2 范数 + transfer_l2_norm_after_mapping = torch.norm(torch.stack([torch.norm(param) for param in transfer_params.values()])) + + # 更新最小 L2 范数和对应的随机矩阵 + if transfer_l2_norm_after_mapping < min_l2_norm: + min_l2_norm = transfer_l2_norm_after_mapping + min_l2_norm_random_matrix = random_matrix + +# 将最优的随机矩阵应用于预训练模型参数 +for name, param in pre_trained_params.items(): + if "gru" in name: + name = name.replace("gru.", "") # 移除模型名称前缀 + if name in transfer_params: + transfer_params[name].copy_(param * min_l2_norm_random_matrix) + + +# 将已有模型的参数设为不可训练 +for param in pre_trained_model.parameters(): + param.requires_grad = False + + +for epoch in range(num_transfer_epochs): + transfer_model.train() + running_loss = 0 + train_bar = tqdm(train_loader) # 使用之前定义的数据加载器 train_loader + for data in train_bar: + x_train, y_train = data + optimizer_transfer.zero_grad() + y_train_pred = transfer_model(x_train) + y_pretrained_pred = pre_trained_model(x_train) + loss = (1-u)*loss_function(y_train_pred, y_train) + u * loss_function(y_train_pred, y_pretrained_pred) + loss.backward() + optimizer_transfer.step() + + running_loss += loss.item() + train_bar.set_description("Transfer Learning epoch[{}/{}] loss:{:.3f}".format(epoch + 1, num_transfer_epochs, loss.item())) + + + +transfer_model_save_path = './transfer_model2.pth' +torch.save(transfer_model.state_dict(), transfer_model_save_path) + +print('Finished Transfer Learning') + +transfer_model.eval() + +# Combine the training and test data to use the entire dataset +x_combined = np.concatenate([x_train, x_test], axis=0) +y_combined = np.concatenate([y_train, y_test], axis=0) + +# Convert the combined data to tensors +x_combined_tensor = torch.from_numpy(x_combined).to(torch.float32) +y_combined_tensor = torch.from_numpy(y_combined).to(torch.float32) + +# Create a new DataLoader for the combined data +combined_data = TensorDataset(x_combined_tensor, y_combined_tensor) +combined_loader = torch.utils.data.DataLoader(combined_data, batch_size=config.batch_size, shuffle=False) + +with torch.no_grad(): + y_true = [] # Ground truth labels + y_pred = [] # Predicted labels + for data in test_loader: # Using the test data loader + x_test1, y_test1 = data + predictions = transfer_model(x_test1) + y_true.extend(y_test1.numpy().flatten()) + y_pred.extend(predictions.numpy().flatten()) + +# Convert the lists to numpy arrays for easier calculations +y_true = np.array(y_true) +y_pred = np.array(y_pred) + +# Calculate evaluation metrics +mse = mean_squared_error(y_true, y_pred) +rmse = np.sqrt(mse) +mae = mean_absolute_error(y_true, y_pred) + +# Calculate MAPE and SMAPE with checks for division by zero +mape_denominator = np.maximum(np.abs(y_true), 1e-8) # Avoid division by zero +mape = np.mean(np.abs((y_true - y_pred) / mape_denominator)) * 100 + +smape_denominator = np.maximum(np.abs(y_true) + np.abs(y_pred), 1e-8) # Avoid division by zero +smape = 2.0 * np.mean(np.abs(y_pred - y_true) / smape_denominator) * 100 + + +print("Mean Squared Error (MSE):", mse) +print("Root Mean Squared Error (RMSE):", rmse) +print("Mean Absolute Error (MAE):", mae) +print("Mean Absolute Percentage Error (MAPE):", mape) +print("Symmetric Mean Absolute Percentage Error (SMAPE):", smape) + +# Save predictions and metrics to CSV +result_df = pd.DataFrame({'Truth': y_true, 'Predicted': y_pred}) +result_df.to_csv('transfer_learning_predictions2.csv', index=False) + +metrics_df = pd.DataFrame({ + 'MSE': [mse], + 'RMSE': [rmse], + 'MAE': [mae], + 'MAPE': [mape], + 'SMAPE': [smape] +}) +metrics_df.to_csv('transfer_learning_metrics2.csv', index=False) + +print("Transfer learning predictions and metrics saved successfully!") diff --git a/subject2-pre/all-process/ourfl_kd/m1.py b/subject2-pre/all-process/ourfl_kd/m1.py new file mode 100644 index 0000000..2e4f66c --- /dev/null +++ b/subject2-pre/all-process/ourfl_kd/m1.py @@ -0,0 +1,299 @@ +import numpy as np +import pandas as pd +import torch +import torch.nn as nn +from sklearn.preprocessing import StandardScaler, MinMaxScaler +from torch.utils.data import TensorDataset +from tqdm import tqdm +from sklearn.metrics import mean_squared_error, mean_absolute_error + + +data1 = pd.read_csv(r'F:\ourfl\data\pre_microsoft.csv') +df = data1[['cpu_use']] +# 2.将数据进行标准化 +scaler = MinMaxScaler(feature_range=(0, 1)) +data = scaler.fit_transform(df.values) + +u = 0.5 +num_transfer_epochs = 30 # 根据需要设置合适的训练轮数 + +class Config(): + + timestep = 2 # 时间步长,就是利用多少时间窗口 + batch_size = 32 # 批次大小 + feature_size = 1 # 每个步长对应的特征数量,这里使用8维特征 + hidden_size = 256 # 隐层大小 + output_size = 1 # 由于是单输出任务,最终输出层大小为1,预测未来1天的avg_mem + num_layers = 2 # gru的层数 + epochs = 32 # 迭代轮数 + best_loss = float('inf') # 记录损失,初始化为正无穷大 + learning_rate = 0.0005 # 学习率 + model_name = 'gru' # 模型名称 + save_path = './{}.pth'.format(model_name) # 最优模型保存路径 + +config = Config() + +#新模型 +class newConfig(): + + timestep = 2 # 时间步长,就是利用多少时间窗口 + batch_size = 32 # 批次大小 + feature_size = 1 # 每个步长对应的特征数量,这里使用8维特征 + hidden_size = 256 # 隐层大小 + output_size = 1 # 由于是单输出任务,最终输出层大小为1,预测未来1天的avg_mem + num_layers = 4 # gru的层数 + epochs = 32 # 迭代轮数 + best_loss = float('inf') # 记录损失,初始化为正无穷大 + learning_rate = 0.0001 # 学习率 + model_name = 'gru' # 模型名称 + save_path = './{}.pth'.format(model_name) # 最优模型保存路径 + +newconfig = newConfig() + +# 形成训练数据 +def split_data1(data, timestep, feature_size): + dataX = [] # 保存X + dataY = [] # 保存Y + + # 将整个窗口的数据保存到X中,将未来一天保存到Y中 + for index in range(len(data) - timestep): + dataX.append(data[index: index + timestep]) + dataY.append(data[index + timestep][0]) # 目标列avg_mem为第5列,索引为4 + + dataX = np.array(dataX) + dataY = np.array(dataY) + + return dataX, dataY + +# 形成训练数据 +def split_data(data, timestep, feature_size): + dataX = [] # 保存X + dataY = [] # 保存Y + + # 将整个窗口的数据保存到X中,将未来一天保存到Y中 + for index in range(len(data) - timestep): + dataX.append(data[index: index + timestep]) + dataY.append(data[index + timestep][0]) # 目标列avg_mem为第5列,索引为4 + + dataX = np.array(dataX) + dataY = np.array(dataY) + + # 获取训练集大小 + train_size = int(np.round(0.6 * dataX.shape[0])) + + # 划分训练集、测试集 + x_train = dataX[: train_size, :] + y_train = dataY[: train_size].reshape(-1, 1) + + x_test = dataX[train_size:, :] + y_test = dataY[train_size:].reshape(-1, 1) + + return [x_train, y_train, x_test, y_test] + +# 定义GRU网络 +class GRU(nn.Module): + def __init__(self, feature_size, hidden_size, num_layers, output_size): + super(GRU, self).__init__() + self.hidden_size = hidden_size # 隐层大小 + self.num_layers = num_layers # gru层数 + self.gru = nn.GRU(feature_size, hidden_size, num_layers, batch_first=True) + self.fc = nn.Linear(hidden_size, output_size) + + def forward(self, x, hidden=None): + batch_size = x.shape[0] + + # 初始化隐层状态 + if hidden is None: + h_0 = x.data.new(self.num_layers, batch_size, self.hidden_size).fill_(0).float() + else: + h_0 = hidden + + # GRU运算 + output, h_0 = self.gru(x, h_0) + + # 获取GRU输出的维度信息 + batch_size, timestep, hidden_size = output.shape + + # 将output变成 batch_size * timestep, hidden_dim + output = output.reshape(-1, hidden_size) + + # 全连接层 + output = self.fc(output) + + # 转换维度,用于输出 + output = output.reshape(batch_size, timestep, -1) + + # 返回最后一个时间步的数据 + return output[:, -1, :] + + +# 获取训练数据 +x_train, y_train, x_test, y_test = split_data(data, config.timestep, config.feature_size) + +x_test1, y_test1 = split_data1(data, config.timestep, config.feature_size) + +# 4.将数据转为tensor +x_train_tensor = torch.from_numpy(x_train).to(torch.float32) +y_train_tensor = torch.from_numpy(y_train).to(torch.float32) +x_test_tensor = torch.from_numpy(x_test).to(torch.float32) +y_test_tensor = torch.from_numpy(y_test).to(torch.float32) + +# 5.形成训练数据集 +train_data = TensorDataset(x_train_tensor, y_train_tensor) +test_data = TensorDataset(x_test_tensor, y_test_tensor) + +# 6.将数据加载成迭代器 +train_loader = torch.utils.data.DataLoader(train_data, + config.batch_size, + shuffle=False) + +test_loader = torch.utils.data.DataLoader(test_data, + config.batch_size, + shuffle=False) + +# 假设您的模型类是GRU,config中包含相关的模型配置信息 +pre_trained_model = GRU(config.feature_size, config.hidden_size, config.num_layers, config.output_size) + +# 加载预训练模型的参数 +# model_path = r"F:\ourfl\fedavg\tvhl_client_model_1.pth" +model_path = r"F:\ourfl\fedavg\client_model_3.pth" + +pre_trained_model.load_state_dict(torch.load(model_path)) + +transfer_model = GRU(newconfig.feature_size, newconfig.hidden_size, newconfig.num_layers, newconfig.output_size) # 定义GRU网络 +loss_function = nn.MSELoss() # 定义损失函数 +optimizer_transfer = torch.optim.AdamW(transfer_model.parameters(), lr= newconfig.learning_rate) # 定义优化器 + +# 定义生成随机矩阵的数量 +num_random_matrices = 100 + +# 获取预训练模型和新模型的参数 +pre_trained_params = pre_trained_model.state_dict() +transfer_params = transfer_model.state_dict() # 使用之前定义的模型 model,而不是新的 transfer_model + +# 计算预训练模型参数的 L2 范数 +pre_trained_l2_norm = torch.norm(torch.stack([torch.norm(param) for param in pre_trained_params.values()])) + +# 计算新模型参数的 L2 范数 +transfer_l2_norm = torch.norm(torch.stack([torch.norm(param) for param in transfer_params.values()])) + +# 初始化最小 L2 范数和对应的随机矩阵 +min_l2_norm = float('inf') +min_l2_norm_random_matrix = None + +# 循环生成随机矩阵,并计算 L2 范数 +for i in range(num_random_matrices): + # 生成随机矩阵 + random_matrix = torch.rand(pre_trained_l2_norm.size()) + + # 应用随机矩阵到预训练模型参数 + for name, param in pre_trained_params.items(): + if "gru" in name: + name = name.replace("gru.", "") # 移除模型名称前缀 + if name in transfer_params: + transfer_params[name].copy_(param * random_matrix) + + # 计算使用当前随机矩阵映射后的新模型参数的 L2 范数 + transfer_l2_norm_after_mapping = torch.norm(torch.stack([torch.norm(param) for param in transfer_params.values()])) + + # 更新最小 L2 范数和对应的随机矩阵 + if transfer_l2_norm_after_mapping < min_l2_norm: + min_l2_norm = transfer_l2_norm_after_mapping + min_l2_norm_random_matrix = random_matrix + +# 将最优的随机矩阵应用于预训练模型参数 +for name, param in pre_trained_params.items(): + if "gru" in name: + name = name.replace("gru.", "") # 移除模型名称前缀 + if name in transfer_params: + transfer_params[name].copy_(param * min_l2_norm_random_matrix) + + +# 将已有模型的参数设为不可训练 +for param in pre_trained_model.parameters(): + param.requires_grad = False + + +for epoch in range(num_transfer_epochs): + transfer_model.train() + running_loss = 0 + train_bar = tqdm(train_loader) # 使用之前定义的数据加载器 train_loader + for data in train_bar: + x_train, y_train = data + optimizer_transfer.zero_grad() + y_train_pred = transfer_model(x_train) + y_pretrained_pred = pre_trained_model(x_train) + loss = (1-u)*loss_function(y_train_pred, y_train) + u * loss_function(y_train_pred, y_pretrained_pred) + loss.backward() + optimizer_transfer.step() + + running_loss += loss.item() + train_bar.set_description("Transfer Learning epoch[{}/{}] loss:{:.3f}".format(epoch + 1, num_transfer_epochs, loss.item())) + + + +transfer_model_save_path = './transfer_model3.pth' +torch.save(transfer_model.state_dict(), transfer_model_save_path) + +print('Finished Transfer Learning') + +transfer_model.eval() + +# Combine the training and test data to use the entire dataset +x_combined = np.concatenate([x_train, x_test], axis=0) +y_combined = np.concatenate([y_train, y_test], axis=0) + +# Convert the combined data to tensors +x_combined_tensor = torch.from_numpy(x_combined).to(torch.float32) +y_combined_tensor = torch.from_numpy(y_combined).to(torch.float32) + +# Create a new DataLoader for the combined data +combined_data = TensorDataset(x_combined_tensor, y_combined_tensor) +combined_loader = torch.utils.data.DataLoader(combined_data, batch_size=config.batch_size, shuffle=False) + +with torch.no_grad(): + y_true = [] # Ground truth labels + y_pred = [] # Predicted labels + for data in test_loader: # Using the test data loader + x_test1, y_test1 = data + predictions = transfer_model(x_test1) + y_true.extend(y_test1.numpy().flatten()) + y_pred.extend(predictions.numpy().flatten()) + +# Convert the lists to numpy arrays for easier calculations +y_true = np.array(y_true) +y_pred = np.array(y_pred) + +# Calculate evaluation metrics +mse = mean_squared_error(y_true, y_pred) +rmse = np.sqrt(mse) +mae = mean_absolute_error(y_true, y_pred) + +# Calculate MAPE and SMAPE with checks for division by zero +mape_denominator = np.maximum(np.abs(y_true), 1e-8) # Avoid division by zero +mape = np.mean(np.abs((y_true - y_pred) / mape_denominator)) * 100 + +smape_denominator = np.maximum(np.abs(y_true) + np.abs(y_pred), 1e-8) # Avoid division by zero +smape = 2.0 * np.mean(np.abs(y_pred - y_true) / smape_denominator) * 100 + + +print("Mean Squared Error (MSE):", mse) +print("Root Mean Squared Error (RMSE):", rmse) +print("Mean Absolute Error (MAE):", mae) +print("Mean Absolute Percentage Error (MAPE):", mape) +print("Symmetric Mean Absolute Percentage Error (SMAPE):", smape) + +# Save predictions and metrics to CSV +result_df = pd.DataFrame({'Truth': y_true, 'Predicted': y_pred}) +result_df.to_csv('transfer_learning_predictions3.csv', index=False) + +metrics_df = pd.DataFrame({ + 'MSE': [mse], + 'RMSE': [rmse], + 'MAE': [mae], + 'MAPE': [mape], + 'SMAPE': [smape] +}) +metrics_df.to_csv('transfer_learning_metrics3.csv', index=False) + +print("Transfer learning predictions and metrics saved successfully!") diff --git a/subject2-pre/all-process/ourfl_kd/s1.py b/subject2-pre/all-process/ourfl_kd/s1.py new file mode 100644 index 0000000..2f4050a --- /dev/null +++ b/subject2-pre/all-process/ourfl_kd/s1.py @@ -0,0 +1,299 @@ +import numpy as np +import pandas as pd +import torch +import torch.nn as nn +from sklearn.preprocessing import StandardScaler, MinMaxScaler +from torch.utils.data import TensorDataset +from tqdm import tqdm +from sklearn.metrics import mean_squared_error, mean_absolute_error + + +data1 = pd.read_csv(r'F:\ourflc\data\processed_hefei.csv') +df = data1[['cpu_use']] +# 2.将数据进行标准化 +scaler = MinMaxScaler(feature_range=(0, 1)) +data = scaler.fit_transform(df.values) + +u = 0.5 +num_transfer_epochs = 30 # 根据需要设置合适的训练轮数 + +class Config(): + + timestep = 2 # 时间步长,就是利用多少时间窗口 + batch_size = 32 # 批次大小 + feature_size = 1 # 每个步长对应的特征数量,这里使用8维特征 + hidden_size = 256 # 隐层大小 + output_size = 1 # 由于是单输出任务,最终输出层大小为1,预测未来1天的avg_mem + num_layers = 2 # gru的层数 + epochs = 32 # 迭代轮数 + best_loss = float('inf') # 记录损失,初始化为正无穷大 + learning_rate = 0.0001 # 学习率 + model_name = 'gru' # 模型名称 + save_path = './{}.pth'.format(model_name) # 最优模型保存路径 + +config = Config() + +#新模型 +class newConfig(): + + timestep = 2 # 时间步长,就是利用多少时间窗口 + batch_size = 32 # 批次 + feature_size = 1 # 每个步长对应的特征数量,这里使用8维特征 + hidden_size = 256 # 隐层大小 + output_size = 1 # 由于是单输出任务,最终输出层大小为1,预测未来1天的avg_mem + num_layers = 4 # gru的层数 + epochs = 32 # 迭代轮数 + best_loss = float('inf') # 记录损失,初始化为正无穷大 + learning_rate = 0.0001 # 学习率 + model_name = 'gru' # 模型名称 + save_path = './{}.pth'.format(model_name) # 最优模型保存路径 + +newconfig = newConfig() + +# 形成训练数据 +def split_data1(data, timestep, feature_size): + dataX = [] # 保存X + dataY = [] # 保存Y + + # 将整个窗口的数据保存到X中,将未来一天保存到Y中 + for index in range(len(data) - timestep): + dataX.append(data[index: index + timestep]) + dataY.append(data[index + timestep][0]) # 目标列avg_mem为第5列,索引为4 + + dataX = np.array(dataX) + dataY = np.array(dataY) + + return dataX, dataY + +# 形成训练数据 +def split_data(data, timestep, feature_size): + dataX = [] # 保存X + dataY = [] # 保存Y + + # 将整个窗口的数据保存到X中,将未来一天保存到Y中 + for index in range(len(data) - timestep): + dataX.append(data[index: index + timestep]) + dataY.append(data[index + timestep][0]) # 目标列avg_mem为第5列,索引为4 + + dataX = np.array(dataX) + dataY = np.array(dataY) + + # 获取训练集大小 + train_size = int(np.round(0.6 * dataX.shape[0])) + + # 划分训练集、测试集 + x_train = dataX[: train_size, :] + y_train = dataY[: train_size].reshape(-1, 1) + + x_test = dataX[train_size:, :] + y_test = dataY[train_size:].reshape(-1, 1) + + return [x_train, y_train, x_test, y_test] + +# 定义GRU网络 +class GRU(nn.Module): + def __init__(self, feature_size, hidden_size, num_layers, output_size): + super(GRU, self).__init__() + self.hidden_size = hidden_size # 隐层大小 + self.num_layers = num_layers # gru层数 + self.gru = nn.GRU(feature_size, hidden_size, num_layers, batch_first=True) + self.fc = nn.Linear(hidden_size, output_size) + + def forward(self, x, hidden=None): + batch_size = x.shape[0] + + # 初始化隐层状态 + if hidden is None: + h_0 = x.data.new(self.num_layers, batch_size, self.hidden_size).fill_(0).float() + else: + h_0 = hidden + + # GRU运算 + output, h_0 = self.gru(x, h_0) + + # 获取GRU输出的维度信息 + batch_size, timestep, hidden_size = output.shape + + # 将output变成 batch_size * timestep, hidden_dim + output = output.reshape(-1, hidden_size) + + # 全连接层 + output = self.fc(output) + + # 转换维度,用于输出 + output = output.reshape(batch_size, timestep, -1) + + # 返回最后一个时间步的数据 + return output[:, -1, :] + + +# 获取训练数据 +x_train, y_train, x_test, y_test = split_data(data, config.timestep, config.feature_size) + +x_test1, y_test1 = split_data1(data, config.timestep, config.feature_size) + +# 4.将数据转为tensor +x_train_tensor = torch.from_numpy(x_train).to(torch.float32) +y_train_tensor = torch.from_numpy(y_train).to(torch.float32) +x_test_tensor = torch.from_numpy(x_test).to(torch.float32) +y_test_tensor = torch.from_numpy(y_test).to(torch.float32) + +# 5.形成训练数据集 +train_data = TensorDataset(x_train_tensor, y_train_tensor) +test_data = TensorDataset(x_test_tensor, y_test_tensor) + +# 6.将数据加载成迭代器 +train_loader = torch.utils.data.DataLoader(train_data, + config.batch_size, + shuffle=False) + +test_loader = torch.utils.data.DataLoader(test_data, + config.batch_size, + shuffle=False) + +# 假设您的模型类是GRU,config中包含相关的模型配置信息 +pre_trained_model = GRU(config.feature_size, config.hidden_size, config.num_layers, config.output_size) + +# 加载预训练模型的参数 +# model_path = r"F:\ourfl\fedavg\tvhl_client_model_1.pth" +model_path = r"F:\ourfl\fedavg\client_model_4.pth" + +pre_trained_model.load_state_dict(torch.load(model_path)) + +transfer_model = GRU(newconfig.feature_size, newconfig.hidden_size, newconfig.num_layers, newconfig.output_size) # 定义GRU网络 +loss_function = nn.MSELoss() # 定义损失函数 +optimizer_transfer = torch.optim.AdamW(transfer_model.parameters(), lr= newconfig.learning_rate) # 定义优化器 + +# 定义生成随机矩阵的数量 +num_random_matrices = 100 + +# 获取预训练模型和新模型的参数 +pre_trained_params = pre_trained_model.state_dict() +transfer_params = transfer_model.state_dict() # 使用之前定义的模型 model,而不是新的 transfer_model + +# 计算预训练模型参数的 L2 范数 +pre_trained_l2_norm = torch.norm(torch.stack([torch.norm(param) for param in pre_trained_params.values()])) + +# 计算新模型参数的 L2 范数 +transfer_l2_norm = torch.norm(torch.stack([torch.norm(param) for param in transfer_params.values()])) + +# 初始化最小 L2 范数和对应的随机矩阵 +min_l2_norm = float('inf') +min_l2_norm_random_matrix = None + +# 循环生成随机矩阵,并计算 L2 范数 +for i in range(num_random_matrices): + # 生成随机矩阵 + random_matrix = torch.rand(pre_trained_l2_norm.size()) + + # 应用随机矩阵到预训练模型参数 + for name, param in pre_trained_params.items(): + if "gru" in name: + name = name.replace("gru.", "") # 移除模型名称前缀 + if name in transfer_params: + transfer_params[name].copy_(param * random_matrix) + + # 计算使用当前随机矩阵映射后的新模型参数的 L2 范数 + transfer_l2_norm_after_mapping = torch.norm(torch.stack([torch.norm(param) for param in transfer_params.values()])) + + # 更新最小 L2 范数和对应的随机矩阵 + if transfer_l2_norm_after_mapping < min_l2_norm: + min_l2_norm = transfer_l2_norm_after_mapping + min_l2_norm_random_matrix = random_matrix + +# 将最优的随机矩阵应用于预训练模型参数 +for name, param in pre_trained_params.items(): + if "gru" in name: + name = name.replace("gru.", "") # 移除模型名称前缀 + if name in transfer_params: + transfer_params[name].copy_(param * min_l2_norm_random_matrix) + + +# 将已有模型的参数设为不可训练 +for param in pre_trained_model.parameters(): + param.requires_grad = False + + +for epoch in range(num_transfer_epochs): + transfer_model.train() + running_loss = 0 + train_bar = tqdm(train_loader) # 使用之前定义的数据加载器 train_loader + for data in train_bar: + x_train, y_train = data + optimizer_transfer.zero_grad() + y_train_pred = transfer_model(x_train) + y_pretrained_pred = pre_trained_model(x_train) + loss = (1-u)*loss_function(y_train_pred, y_train) + u * loss_function(y_train_pred, y_pretrained_pred) + loss.backward() + optimizer_transfer.step() + + running_loss += loss.item() + train_bar.set_description("Transfer Learning epoch[{}/{}] loss:{:.3f}".format(epoch + 1, num_transfer_epochs, loss.item())) + + + +transfer_model_save_path = './transfer_model4.pth' +torch.save(transfer_model.state_dict(), transfer_model_save_path) + +print('Finished Transfer Learning') + +transfer_model.eval() + +# Combine the training and test data to use the entire dataset +x_combined = np.concatenate([x_train, x_test], axis=0) +y_combined = np.concatenate([y_train, y_test], axis=0) + +# Convert the combined data to tensors +x_combined_tensor = torch.from_numpy(x_combined).to(torch.float32) +y_combined_tensor = torch.from_numpy(y_combined).to(torch.float32) + +# Create a new DataLoader for the combined data +combined_data = TensorDataset(x_combined_tensor, y_combined_tensor) +combined_loader = torch.utils.data.DataLoader(combined_data, batch_size=config.batch_size, shuffle=False) + +with torch.no_grad(): + y_true = [] # Ground truth labels + y_pred = [] # Predicted labels + for data in test_loader: # Using the test data loader + x_test1, y_test1 = data + predictions = transfer_model(x_test1) + y_true.extend(y_test1.numpy().flatten()) + y_pred.extend(predictions.numpy().flatten()) + +# Convert the lists to numpy arrays for easier calculations +y_true = np.array(y_true) +y_pred = np.array(y_pred) + +# Calculate evaluation metrics +mse = mean_squared_error(y_true, y_pred) +rmse = np.sqrt(mse) +mae = mean_absolute_error(y_true, y_pred) + +# Calculate MAPE and SMAPE with checks for division by zero +mape_denominator = np.maximum(np.abs(y_true), 1e-8) # Avoid division by zero +mape = np.mean(np.abs((y_true - y_pred) / mape_denominator)) * 100 + +smape_denominator = np.maximum(np.abs(y_true) + np.abs(y_pred), 1e-8) # Avoid division by zero +smape = 2.0 * np.mean(np.abs(y_pred - y_true) / smape_denominator) * 100 + + +print("Mean Squared Error (MSE):", mse) +print("Root Mean Squared Error (RMSE):", rmse) +print("Mean Absolute Error (MAE):", mae) +print("Mean Absolute Percentage Error (MAPE):", mape) +print("Symmetric Mean Absolute Percentage Error (SMAPE):", smape) + +# Save predictions and metrics to CSV +result_df = pd.DataFrame({'Truth': y_true, 'Predicted': y_pred}) +result_df.to_csv('transfer_learning_predictions4.csv', index=False) + +metrics_df = pd.DataFrame({ + 'MSE': [mse], + 'RMSE': [rmse], + 'MAE': [mae], + 'MAPE': [mape], + 'SMAPE': [smape] +}) +metrics_df.to_csv('transfer_learning_metrics4.csv', index=False) + +print("Transfer learning predictions and metrics saved successfully!") -- Gitee From 8b09667d9243087b0f76be4942a788cdbda9dc6d Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 04:35:53 +0000 Subject: [PATCH 24/25] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> --- .../all-process/ourfl-nokd/ourfl_gan.py | 237 +++++++++++++++++ .../all-process/ourfl-nokd/ourfl_tvhl.py | 238 ++++++++++++++++++ .../all-process/ourfl-nokd/ourfl_vhl.py | 238 ++++++++++++++++++ 3 files changed, 713 insertions(+) create mode 100644 subject2-pre/all-process/ourfl-nokd/ourfl_gan.py create mode 100644 subject2-pre/all-process/ourfl-nokd/ourfl_tvhl.py create mode 100644 subject2-pre/all-process/ourfl-nokd/ourfl_vhl.py diff --git a/subject2-pre/all-process/ourfl-nokd/ourfl_gan.py b/subject2-pre/all-process/ourfl-nokd/ourfl_gan.py new file mode 100644 index 0000000..849332a --- /dev/null +++ b/subject2-pre/all-process/ourfl-nokd/ourfl_gan.py @@ -0,0 +1,237 @@ +import numpy as np +import pandas as pd +import torch +import torch.nn as nn +from sklearn.preprocessing import MinMaxScaler +import torch.optim as optim # 添加导入torch.optim模块 + + +class Config(): + timestep = 1 # 时间步长,就是利用多少时间窗口 + batch_size = 32 # 批次大小 + feature_size = 4 # 每个步长对应的特征数量,这里使用8维特征 + hidden_size = 256 # 隐层大小 + output_size = 1 # 由于是单输出任务,最终输出层大小为1,预测未来1天的avg_mem + num_layers = 2 # gru的层数 + epochs = 20 # 迭代轮数 + best_loss = float('inf') # 记录损失,初始化为正无穷大 + learning_rate = 0.0005 # 学习率 + model_name = 'gru' # 模型名称 + save_path = './{}.pth'.format(model_name) # 最优模型保存路径 + +config = Config() + + +# 7.定义GRU网络 +class GRU(nn.Module): + def __init__(self, feature_size, hidden_size, num_layers, output_size): + super(GRU, self).__init__() + self.hidden_size = hidden_size # 隐层大小 + self.num_layers = num_layers # gru层数 + self.gru = nn.GRU(feature_size, hidden_size, num_layers, batch_first=True) + self.fc = nn.Linear(hidden_size, output_size) + + def forward(self, x, hidden=None): + batch_size = x.shape[0] + + # 初始化隐层状态 + if hidden is None: + h_0 = x.data.new(self.num_layers, batch_size, self.hidden_size).fill_(0).float() + else: + h_0 = hidden + + # GRU运算 + output, h_0 = self.gru(x, h_0) + + # 获取GRU输出的维度信息 + batch_size, timestep, hidden_size = output.shape + + # 将output变成 batch_size * timestep, hidden_dim + output = output.reshape(-1, hidden_size) + + # 全连接层 + output = self.fc(output) + + # 转换维度,用于输出 + output = output.reshape(batch_size, timestep, -1) + + # 返回最后一个时间步的数据 + return output[:, -1, :] + + +# 形成训练数据 +def split_data(data, timestep, feature_size): + dataX = [] + dataY = [] + + for index in range(len(data) - timestep): + dataX.append(data[index: index + timestep]) + dataY.append(data[index + timestep][0]) + + dataX = np.array(dataX) + dataY = np.array(dataY) + + train_size = int(np.round(0.6 * dataX.shape[0])) + + x_train = dataX[: train_size, :] + y_train = dataY[: train_size].reshape(-1, 1) + + x_test = dataX[train_size:, :] + y_test = dataY[train_size:].reshape(-1, 1) + + return [x_train, y_train, x_test, y_test] + + +data1 = pd.read_csv(r"F:\ourfl\data\processed_alibaba.csv") +data2 = pd.read_csv(r"F:\ourfl\data\pre_google.csv") +data3 = pd.read_csv(r"F:\ourfl\data\pre_microsoft.csv") +data4 = pd.read_csv(r"F:\ourfl\data\processed_hefei.csv") + +# 提取avg_mem列 +df1 = data1[['mem_use','cpu_plan','gpu_plan','mem_plan']] +df2 = data2[['mem_use','cpu_plan','gpu_plan','mem_plan']] +df3 = data3[['mem_use','cpu_plan','gpu_plan','mem_plan']] +df4 = data4[['mem_use','cpu_plan','gpu_plan','mem_plan']] + +# 标准化数据 +scaler = MinMaxScaler(feature_range=(0, 1)) +data1 = scaler.fit_transform(df1.values) +data2 = scaler.fit_transform(df2.values) +data3 = scaler.fit_transform(df3.values) +data4 = scaler.fit_transform(df4.values) + +# 定义时间步数和特征数 +timestep = 1 # 您可以根据实际情况调整时间步数 +feature_size = 4 # 由于您已经提取了"mem_use"列作为特征,所以特征数为1 + +# 划分数据集 +x_train1, y_train1, x_test1, y_test1 = split_data(data1, timestep, feature_size) +x_train2, y_train2, x_test2, y_test2 = split_data(data2, timestep, feature_size) +x_train3, y_train3, x_test3, y_test3 = split_data(data3, timestep, feature_size) +x_train4, y_train4, x_test4, y_test4 = split_data(data4, timestep, feature_size) + + +class ClientGRU(nn.Module): + def __init__(self, config): + super(ClientGRU, self).__init__() + self.gru = nn.GRU(config.feature_size, config.hidden_size, config.num_layers, batch_first=True) + self.fc = nn.Linear(config.hidden_size, config.output_size) + + def forward(self, x, hidden=None): + output, hidden = self.gru(x, hidden) + output = self.fc(output[:, -1, :]) # 调整输出维度 + return output + + + + #定义全局GRU模型 +class GlobalGRU(nn.Module): + def __init__(self, config): + super(GlobalGRU, self).__init__() + self.gru = nn.GRU(config.feature_size, config.hidden_size, config.num_layers) + self.fc = nn.Linear(config.hidden_size, config.output_size) + + def forward(self, x, hidden=None): + # 自定义GlobalGRU类的前向传播过程,使用GRU和Linear层 + output, hidden = self.gru(x, hidden) + output = self.fc(output) + return output, hidden + + + + +# 使用您之前定义的Config类来配置全局GRU模型和客户端GRU模型 +config = Config() +global_model = GlobalGRU(config) +client_models = [ClientGRU(config) for _ in range(4)] + + + +# 定义客户端训练函数 +def client_train(client_model, data, global_model_parameters, config): + # 将数据转换为PyTorch张量 + x_train, y_train, _, _ = data + x_train = torch.tensor(x_train, dtype=torch.float32) + y_train = torch.tensor(y_train, dtype=torch.float32) + # 设置优化器和损失函数 + optimizer = optim.Adam(client_model.parameters(), lr=config.learning_rate) + criterion = nn.MSELoss() + # 设置初始隐藏状态(可根据需要调整) + hidden = None + # 将全局模型参数加载到客户端模型中 + client_model.load_state_dict(global_model_parameters) + # 进行本地训练 + for epoch in range(config.epochs): + optimizer.zero_grad() + # 前向传播 + output, hidden = client_model(x_train, hidden) + loss = criterion(output, y_train) + # 反向传播和参数更新 + loss.backward() + optimizer.step() + # 返回更新后的客户端模型参数 + return client_model.state_dict() + + + +# 定义全局模型参数平均函数 +def global_average_parameters(client_parameters_list): + num_parameters = len(list(client_parameters_list[0].values())) + sum_parameters = [torch.zeros_like(param) for param in list(client_parameters_list[0].values())] + + for client_parameters in client_parameters_list: + for i, param in enumerate(client_parameters.values()): + sum_parameters[i] += param + + averaged_parameters = [param_sum / len(client_parameters_list) for param_sum in sum_parameters] + return averaged_parameters + +# ... (其他代码保持不变) + +# 设置全局epoch数 +global_epoch = 30 +# 将数据转换为PyTorch张量 +# 将数据转换为PyTorch张量并指定数据类型为float32 +x_train_list = [torch.tensor(x_train1, dtype=torch.float32), + torch.tensor(x_train2, dtype=torch.float32), + torch.tensor(x_train3, dtype=torch.float32), + torch.tensor(x_train4, dtype=torch.float32)] +y_train_list = [torch.tensor(y_train1, dtype=torch.float32), + torch.tensor(y_train2, dtype=torch.float32), + torch.tensor(y_train3, dtype=torch.float32), + torch.tensor(y_train4, dtype=torch.float32)] + +# 进行联邦学习的全局epoch循环 +for global_epoch in range(global_epoch): + updated_client_parameters_list = [] + + # 客户端训练并更新模型参数 + for client_model, x_train, y_train in zip(client_models, x_train_list, y_train_list): + optimizer = optim.Adam(client_model.parameters(), lr=config.learning_rate) + criterion = nn.MSELoss() + + # 进行本地模型的训练 + for local_epoch in range(config.epochs): + optimizer.zero_grad() + output = client_model(x_train) # 不再解包 + loss = criterion(output, y_train) + loss.backward() + optimizer.step() + + # 获取更新后的客户端模型参数 + updated_client_parameters_list.append(client_model.state_dict()) + + # 全局参数平均化 + averaged_parameters = global_average_parameters(updated_client_parameters_list) + + # 更新全局模型的参数为平均后的参数 + with torch.no_grad(): + for global_param, averaged_param in zip(global_model.parameters(), averaged_parameters): + global_param.copy_(averaged_param) + + +# 保存每个客户端模型 +for i, client_model in enumerate(client_models): + model_path = f"flclient_model_{i+1}.pth" + torch.save(client_model.state_dict(), model_path) + diff --git a/subject2-pre/all-process/ourfl-nokd/ourfl_tvhl.py b/subject2-pre/all-process/ourfl-nokd/ourfl_tvhl.py new file mode 100644 index 0000000..7781db2 --- /dev/null +++ b/subject2-pre/all-process/ourfl-nokd/ourfl_tvhl.py @@ -0,0 +1,238 @@ +import numpy as np +import pandas as pd +import torch +import torch.nn as nn +from sklearn.preprocessing import MinMaxScaler +import torch.optim as optim # 添加导入torch.optim模块 + + +class Config(): + data_path = './data/wind_dataset.csv' + timestep = 4 # 时间步长,就是利用多少时间窗口 + batch_size = 32 # 批次大小 + feature_size = 4 # 每个步长对应的特征数量,这里使用8维特征 + hidden_size = 256 # 隐层大小 + output_size = 1 # 由于是单输出任务,最终输出层大小为1,预测未来1天的avg_mem + num_layers = 2 # gru的层数 + epochs = 20 # 迭代轮数 + best_loss = float('inf') # 记录损失,初始化为正无穷大 + learning_rate = 0.0001 # 学习率 + model_name = 'gru' # 模型名称 + save_path = './{}.pth'.format(model_name) # 最优模型保存路径 + +config = Config() + + +# 7.定义GRU网络 +class GRU(nn.Module): + def __init__(self, feature_size, hidden_size, num_layers, output_size): + super(GRU, self).__init__() + self.hidden_size = hidden_size # 隐层大小 + self.num_layers = num_layers # gru层数 + self.gru = nn.GRU(feature_size, hidden_size, num_layers, batch_first=True) + self.fc = nn.Linear(hidden_size, output_size) + + def forward(self, x, hidden=None): + batch_size = x.shape[0] + + # 初始化隐层状态 + if hidden is None: + h_0 = x.data.new(self.num_layers, batch_size, self.hidden_size).fill_(0).float() + else: + h_0 = hidden + + # GRU运算 + output, h_0 = self.gru(x, h_0) + + # 获取GRU输出的维度信息 + batch_size, timestep, hidden_size = output.shape + + # 将output变成 batch_size * timestep, hidden_dim + output = output.reshape(-1, hidden_size) + + # 全连接层 + output = self.fc(output) + + # 转换维度,用于输出 + output = output.reshape(batch_size, timestep, -1) + + # 返回最后一个时间步的数据 + return output[:, -1, :] + + +# 形成训练数据 +def split_data(data, timestep, feature_size): + dataX = [] + dataY = [] + + for index in range(len(data) - timestep): + dataX.append(data[index: index + timestep]) + dataY.append(data[index + timestep][0]) + + dataX = np.array(dataX) + dataY = np.array(dataY) + + train_size = int(np.round(0.6 * dataX.shape[0])) + + x_train = dataX[: train_size, :] + y_train = dataY[: train_size].reshape(-1, 1) + + x_test = dataX[train_size:, :] + y_test = dataY[train_size:].reshape(-1, 1) + + return [x_train, y_train, x_test, y_test] + + +data1 = pd.read_csv(r'F:\ourfl\genedate\TVHL\tvhl_alibaba.csv') +data2 = pd.read_csv(r'F:\ourfl\genedate\TVHL\tvhl_google.csv') +data3 = pd.read_csv(r'F:\ourfl\genedate\TVHL\tvhl_mircosoft.csv') +data4 = pd.read_csv(r'F:\ourfl\genedate\TVHL\tvhl_sugan1.csv') + +# 提取avg_mem列 +df1 = data1[['mem_use','cpu_plan','gpu_plan','mem_plan']] +df2 = data2[['mem_use','cpu_plan','gpu_plan','mem_plan']] +df3 = data3[['mem_use','cpu_plan','gpu_plan','mem_plan']] +df4 = data4[['mem_use','cpu_plan','gpu_plan','mem_plan']] + +# 标准化数据 +scaler = MinMaxScaler(feature_range=(0, 1)) +data1 = scaler.fit_transform(df1.values) +data2 = scaler.fit_transform(df2.values) +data3 = scaler.fit_transform(df3.values) +data4 = scaler.fit_transform(df4.values) + +# 定义时间步数和特征数 +timestep = 4 # 您可以根据实际情况调整时间步数 +feature_size = 4 # 由于您已经提取了"mem_use"列作为特征,所以特征数为1 + +# 划分数据集 +x_train1, y_train1, x_test1, y_test1 = split_data(data1, timestep, feature_size) +x_train2, y_train2, x_test2, y_test2 = split_data(data2, timestep, feature_size) +x_train3, y_train3, x_test3, y_test3 = split_data(data3, timestep, feature_size) +x_train4, y_train4, x_test4, y_test4 = split_data(data4, timestep, feature_size) + + +class ClientGRU(nn.Module): + def __init__(self, config): + super(ClientGRU, self).__init__() + self.gru = nn.GRU(config.feature_size, config.hidden_size, config.num_layers, batch_first=True) + self.fc = nn.Linear(config.hidden_size, config.output_size) + + def forward(self, x, hidden=None): + output, hidden = self.gru(x, hidden) + output = self.fc(output[:, -1, :]) # 调整输出维度 + return output + + + + #定义全局GRU模型 +class GlobalGRU(nn.Module): + def __init__(self, config): + super(GlobalGRU, self).__init__() + self.gru = nn.GRU(config.feature_size, config.hidden_size, config.num_layers) + self.fc = nn.Linear(config.hidden_size, config.output_size) + + def forward(self, x, hidden=None): + # 自定义GlobalGRU类的前向传播过程,使用GRU和Linear层 + output, hidden = self.gru(x, hidden) + output = self.fc(output) + return output, hidden + + + + +# 使用您之前定义的Config类来配置全局GRU模型和客户端GRU模型 +config = Config() +global_model = GlobalGRU(config) +client_models = [ClientGRU(config) for _ in range(4)] + + + +# 定义客户端训练函数 +def client_train(client_model, data, global_model_parameters, config): + # 将数据转换为PyTorch张量 + x_train, y_train, _, _ = data + x_train = torch.tensor(x_train, dtype=torch.float32) + y_train = torch.tensor(y_train, dtype=torch.float32) + # 设置优化器和损失函数 + optimizer = optim.Adam(client_model.parameters(), lr=config.learning_rate) + criterion = nn.MSELoss() + # 设置初始隐藏状态(可根据需要调整) + hidden = None + # 将全局模型参数加载到客户端模型中 + client_model.load_state_dict(global_model_parameters) + # 进行本地训练 + for epoch in range(config.epochs): + optimizer.zero_grad() + # 前向传播 + output, hidden = client_model(x_train, hidden) + loss = criterion(output, y_train) + # 反向传播和参数更新 + loss.backward() + optimizer.step() + # 返回更新后的客户端模型参数 + return client_model.state_dict() + + + +# 定义全局模型参数平均函数 +def global_average_parameters(client_parameters_list): + num_parameters = len(list(client_parameters_list[0].values())) + sum_parameters = [torch.zeros_like(param) for param in list(client_parameters_list[0].values())] + + for client_parameters in client_parameters_list: + for i, param in enumerate(client_parameters.values()): + sum_parameters[i] += param + + averaged_parameters = [param_sum / len(client_parameters_list) for param_sum in sum_parameters] + return averaged_parameters + +# ... (其他代码保持不变) + +# 设置全局epoch数 +global_epoch = 30 +# 将数据转换为PyTorch张量 +# 将数据转换为PyTorch张量并指定数据类型为float32 +x_train_list = [torch.tensor(x_train1, dtype=torch.float32), + torch.tensor(x_train2, dtype=torch.float32), + torch.tensor(x_train3, dtype=torch.float32), + torch.tensor(x_train4, dtype=torch.float32)] +y_train_list = [torch.tensor(y_train1, dtype=torch.float32), + torch.tensor(y_train2, dtype=torch.float32), + torch.tensor(y_train3, dtype=torch.float32), + torch.tensor(y_train4, dtype=torch.float32)] + +# 进行联邦学习的全局epoch循环 +for global_epoch in range(global_epoch): + updated_client_parameters_list = [] + + # 客户端训练并更新模型参数 + for client_model, x_train, y_train in zip(client_models, x_train_list, y_train_list): + optimizer = optim.Adam(client_model.parameters(), lr=config.learning_rate) + criterion = nn.MSELoss() + + # 进行本地模型的训练 + for local_epoch in range(config.epochs): + optimizer.zero_grad() + output = client_model(x_train) # 不再解包 + loss = criterion(output, y_train) + loss.backward() + optimizer.step() + + # 获取更新后的客户端模型参数 + updated_client_parameters_list.append(client_model.state_dict()) + + # 全局参数平均化 + averaged_parameters = global_average_parameters(updated_client_parameters_list) + + # 更新全局模型的参数为平均后的参数 + with torch.no_grad(): + for global_param, averaged_param in zip(global_model.parameters(), averaged_parameters): + global_param.copy_(averaged_param) + + +# 保存每个客户端模型 +for i, client_model in enumerate(client_models): + model_path = f"fltvhlclient_model_{i+1}.pth" + torch.save(client_model.state_dict(), model_path) + diff --git a/subject2-pre/all-process/ourfl-nokd/ourfl_vhl.py b/subject2-pre/all-process/ourfl-nokd/ourfl_vhl.py new file mode 100644 index 0000000..8b36f5f --- /dev/null +++ b/subject2-pre/all-process/ourfl-nokd/ourfl_vhl.py @@ -0,0 +1,238 @@ +import numpy as np +import pandas as pd +import torch +import torch.nn as nn +from sklearn.preprocessing import MinMaxScaler +import torch.optim as optim # 添加导入torch.optim模块 + + +class Config(): + data_path = './data/wind_dataset.csv' + timestep = 1 # 时间步长,就是利用多少时间窗口 + batch_size = 32 # 批次大小 + feature_size = 4 # 每个步长对应的特征数量,这里使用8维特征 + hidden_size = 256 # 隐层大小 + output_size = 1 # 由于是单输出任务,最终输出层大小为1,预测未来1天的avg_mem + num_layers = 2 # gru的层数 + epochs = 10 # 迭代轮数 + best_loss = float('inf') # 记录损失,初始化为正无穷大 + learning_rate = 0.0003 # 学习率 + model_name = 'gru' # 模型名称 + save_path = './{}.pth'.format(model_name) # 最优模型保存路径 + +config = Config() + + +# 7.定义GRU网络 +class GRU(nn.Module): + def __init__(self, feature_size, hidden_size, num_layers, output_size): + super(GRU, self).__init__() + self.hidden_size = hidden_size # 隐层大小 + self.num_layers = num_layers # gru层数 + self.gru = nn.GRU(feature_size, hidden_size, num_layers, batch_first=True) + self.fc = nn.Linear(hidden_size, output_size) + + def forward(self, x, hidden=None): + batch_size = x.shape[0] + + # 初始化隐层状态 + if hidden is None: + h_0 = x.data.new(self.num_layers, batch_size, self.hidden_size).fill_(0).float() + else: + h_0 = hidden + + # GRU运算 + output, h_0 = self.gru(x, h_0) + + # 获取GRU输出的维度信息 + batch_size, timestep, hidden_size = output.shape + + # 将output变成 batch_size * timestep, hidden_dim + output = output.reshape(-1, hidden_size) + + # 全连接层 + output = self.fc(output) + + # 转换维度,用于输出 + output = output.reshape(batch_size, timestep, -1) + + # 返回最后一个时间步的数据 + return output[:, -1, :] + + +# 形成训练数据 +def split_data(data, timestep, feature_size): + dataX = [] + dataY = [] + + for index in range(len(data) - timestep): + dataX.append(data[index: index + timestep]) + dataY.append(data[index + timestep][0]) + + dataX = np.array(dataX) + dataY = np.array(dataY) + + train_size = int(np.round(0.6 * dataX.shape[0])) + + x_train = dataX[: train_size, :] + y_train = dataY[: train_size].reshape(-1, 1) + + x_test = dataX[train_size:, :] + y_test = dataY[train_size:].reshape(-1, 1) + + return [x_train, y_train, x_test, y_test] + + +data1 = pd.read_csv(r'F:\ourfl\genedate\VHL\vhl_alibaba.csv') +data2 = pd.read_csv(r'F:\ourfl\genedate\VHL\vhl_google.csv') +data3 = pd.read_csv(r'F:\ourfl\genedate\VHL\vhl_mircosoft.csv') +data4 = pd.read_csv(r'F:\ourfl\genedate\VHL\vhl_sugan1.csv') + +# 提取avg_mem列 +df1 = data1[['mem_use','cpu_plan','gpu_plan','mem_plan']] +df2 = data2[['mem_use','cpu_plan','gpu_plan','mem_plan']] +df3 = data3[['mem_use','cpu_plan','gpu_plan','mem_plan']] +df4 = data4[['mem_use','cpu_plan','gpu_plan','mem_plan']] + +# 标准化数据 +scaler = MinMaxScaler(feature_range=(0, 1)) +data1 = scaler.fit_transform(df1.values) +data2 = scaler.fit_transform(df2.values) +data3 = scaler.fit_transform(df3.values) +data4 = scaler.fit_transform(df4.values) + +# 定义时间步数和特征数 +timestep = 1 # 您可以根据实际情况调整时间步数 +feature_size = 4 # 由于您已经提取了"mem_use"列作为特征,所以特征数为1 + +# 划分数据集 +x_train1, y_train1, x_test1, y_test1 = split_data(data1, timestep, feature_size) +x_train2, y_train2, x_test2, y_test2 = split_data(data2, timestep, feature_size) +x_train3, y_train3, x_test3, y_test3 = split_data(data3, timestep, feature_size) +x_train4, y_train4, x_test4, y_test4 = split_data(data4, timestep, feature_size) + + +class ClientGRU(nn.Module): + def __init__(self, config): + super(ClientGRU, self).__init__() + self.gru = nn.GRU(config.feature_size, config.hidden_size, config.num_layers, batch_first=True) + self.fc = nn.Linear(config.hidden_size, config.output_size) + + def forward(self, x, hidden=None): + output, hidden = self.gru(x, hidden) + output = self.fc(output[:, -1, :]) # 调整输出维度 + return output + + + + #定义全局GRU模型 +class GlobalGRU(nn.Module): + def __init__(self, config): + super(GlobalGRU, self).__init__() + self.gru = nn.GRU(config.feature_size, config.hidden_size, config.num_layers) + self.fc = nn.Linear(config.hidden_size, config.output_size) + + def forward(self, x, hidden=None): + # 自定义GlobalGRU类的前向传播过程,使用GRU和Linear层 + output, hidden = self.gru(x, hidden) + output = self.fc(output) + return output, hidden + + + + +# 使用您之前定义的Config类来配置全局GRU模型和客户端GRU模型 +config = Config() +global_model = GlobalGRU(config) +client_models = [ClientGRU(config) for _ in range(4)] + + + +# 定义客户端训练函数 +def client_train(client_model, data, global_model_parameters, config): + # 将数据转换为PyTorch张量 + x_train, y_train, _, _ = data + x_train = torch.tensor(x_train, dtype=torch.float32) + y_train = torch.tensor(y_train, dtype=torch.float32) + # 设置优化器和损失函数 + optimizer = optim.Adam(client_model.parameters(), lr=config.learning_rate) + criterion = nn.MSELoss() + # 设置初始隐藏状态(可根据需要调整) + hidden = None + # 将全局模型参数加载到客户端模型中 + client_model.load_state_dict(global_model_parameters) + # 进行本地训练 + for epoch in range(config.epochs): + optimizer.zero_grad() + # 前向传播 + output, hidden = client_model(x_train, hidden) + loss = criterion(output, y_train) + # 反向传播和参数更新 + loss.backward() + optimizer.step() + # 返回更新后的客户端模型参数 + return client_model.state_dict() + + + +# 定义全局模型参数平均函数 +def global_average_parameters(client_parameters_list): + num_parameters = len(list(client_parameters_list[0].values())) + sum_parameters = [torch.zeros_like(param) for param in list(client_parameters_list[0].values())] + + for client_parameters in client_parameters_list: + for i, param in enumerate(client_parameters.values()): + sum_parameters[i] += param + + averaged_parameters = [param_sum / len(client_parameters_list) for param_sum in sum_parameters] + return averaged_parameters + +# ... (其他代码保持不变) + +# 设置全局epoch数 +global_epoch = 50 +# 将数据转换为PyTorch张量 +# 将数据转换为PyTorch张量并指定数据类型为float32 +x_train_list = [torch.tensor(x_train1, dtype=torch.float32), + torch.tensor(x_train2, dtype=torch.float32), + torch.tensor(x_train3, dtype=torch.float32), + torch.tensor(x_train4, dtype=torch.float32)] +y_train_list = [torch.tensor(y_train1, dtype=torch.float32), + torch.tensor(y_train2, dtype=torch.float32), + torch.tensor(y_train3, dtype=torch.float32), + torch.tensor(y_train4, dtype=torch.float32)] + +# 进行联邦学习的全局epoch循环 +for global_epoch in range(global_epoch): + updated_client_parameters_list = [] + + # 客户端训练并更新模型参数 + for client_model, x_train, y_train in zip(client_models, x_train_list, y_train_list): + optimizer = optim.Adam(client_model.parameters(), lr=config.learning_rate) + criterion = nn.MSELoss() + + # 进行本地模型的训练 + for local_epoch in range(config.epochs): + optimizer.zero_grad() + output = client_model(x_train) # 不再解包 + loss = criterion(output, y_train) + loss.backward() + optimizer.step() + + # 获取更新后的客户端模型参数 + updated_client_parameters_list.append(client_model.state_dict()) + + # 全局参数平均化 + averaged_parameters = global_average_parameters(updated_client_parameters_list) + + # 更新全局模型的参数为平均后的参数 + with torch.no_grad(): + for global_param, averaged_param in zip(global_model.parameters(), averaged_parameters): + global_param.copy_(averaged_param) + + +# 保存每个客户端模型 +for i, client_model in enumerate(client_models): + model_path = f"flvhlclient_model_{i+1}.pth" + torch.save(client_model.state_dict(), model_path) + -- Gitee From e1d261e1bf5059823566b87520239fd67c2dc20c Mon Sep 17 00:00:00 2001 From: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> Date: Mon, 19 Feb 2024 04:39:03 +0000 Subject: [PATCH 25/25] update subject2-pre/README.md. Signed-off-by: yangbin-xijiao <13345886+yangbin-xijiao@user.noreply.gitee.com> --- subject2-pre/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/subject2-pre/README.md b/subject2-pre/README.md index 89186e6..e6c61b9 100644 --- a/subject2-pre/README.md +++ b/subject2-pre/README.md @@ -35,7 +35,7 @@ Our framework consists of the following three key components:

-The code in the folder [code_generate_data](https://github.com/liyan2015/JC-workload-predictor/tree/main/code/generate_data) is the code to generate virtual data, including GAN, VHL and our TVHL. +The code in the folder [code_generate_data](https://gitee.com/jointcloud_computing/joint-cloud-computing/tree/subject2/subject2-pre/all-process/generate_data) is the code to generate virtual data, including GAN, VHL and our TVHL. The input is the path of the dataset. @@ -46,16 +46,16 @@ The input is the path of the dataset. FedAvg was used for training, using different datasets. -Folder [code_fedavg](https://github.com/liyan2015/JC-workload-predictor/tree/main/code/fedavg) is using the actual dataset. +Folder [code_fedavg](https://gitee.com/jointcloud_computing/joint-cloud-computing/tree/subject2/subject2-pre/all-process/fedavg) is using the actual dataset. -Folder [code_ourfl-nokd](https://github.com/liyan2015/JC-workload-predictor/tree/main/code/ourfl-nokd) is the use of virtual generated dataset. +Folder [code_ourfl-nokd](https://gitee.com/jointcloud_computing/joint-cloud-computing/tree/subject2/subject2-pre/all-process/ourfl-nokd) is the use of virtual generated dataset. ## 3. Post Training of Personal Models. -The code in the folde [code_ourfl_kd](https://github.com/liyan2015/JC-workload-predictor/tree/main/code/ourfl_kd) shows the training of the individual model after the local public model is trained. +The code in the folde [code_ourfl_kd](https://gitee.com/jointcloud_computing/joint-cloud-computing/tree/subject2/subject2-pre/all-process/ourfl_kd) shows the training of the individual model after the local public model is trained. @@ -67,7 +67,7 @@ To run the code, it needs some libraies: - Pytorch >= 2.0.1 - torchvision >= 0.15.2 -Our environment is shown in the file, named [environment.yaml](https://github.com/liyan2015/JC-workload-predictor/blob/main/environment.yaml). +Our environment is shown in the file, named [environment.yaml](https://gitee.com/jointcloud_computing/joint-cloud-computing/edit/subject2/subject2-pre/environment.yaml). ## Citing -- Gitee