ML2021Spring-hw1(COVID-19 Cases Prediction)

文章目录

  • 前言
  • 代码一
  • 代码二
  • 对比

前言

💫你好,我是辰chen,本文旨在准备考研复试或就业
💫本篇博客内容来自:Machine Learning 2022 Spring
💫更多和本篇博客相关内容详见专栏:Machine Learning(李宏毅2022春) 以及 机器学习(Machine Learning)
💫欢迎大家的关注,我的博客主要关注于算法讲解、LLM大模型、通信感知等

以下的几个专栏是本人比较满意的专栏(大部分专栏仍在持续更新),欢迎大家的关注:

💥ACM-ICPC算法汇总【基础篇】
💥ACM-ICPC算法汇总【提高篇】
💥AIoT(人工智能+物联网)
💥考研
💥CSP认证考试历年题解

代码一

import torch
import torch.nn as nn
import torch.nn.functional as F

import numpy as np
import pandas as pd
import math

seed = 15
torch.backends.cudnn.deterministic = True   # 使用确定性算法,确保可重复性
torch.backends.cudnn.benkmark = False       # 关闭 cuDNN 的自动优化机制,确保可重复性
np.random.seed(seed)
torch.manual_seed(seed)                     # 设置 PyTorch 的随机种子
if torch.cuda.is_available():
    torch.cuda.manual_seed(seed)            # 设置所有 GPU 设备的随机种子,确保在多 GPU 环境下的结果一致
    print(True)
import os

# 查看当前工作目录
current_path = os.getcwd()
print(f"当前路径:{current_path}")
df = pd.read_csv('data/kaggle/ml2021spring-hw1/covid.train.csv')
test_df = pd.read_csv('data/kaggle/ml2021spring-hw1/covid.test.csv')

df.head()

在这里插入图片描述

df.shape
test_df.shape
# combined_df 仍会保留 95 列,test_df 缺失的第 95 列将被填充为 NaN
combined_df = pd.concat([df, test_df])

x = df[df.columns[1:94]]
y = df[df.columns[94]]
from sklearn.feature_selection import SelectKBest     # 用于特征选择,选择得分最高的 k 个特征
from sklearn.feature_selection import f_regression    # 用于计算每个特征与目标变量之间的相关性评分

"""
score_func=f_regression : 使用 F 检验(方差分析)来计算每个特征的相关性评分
k=14 : 选择得分最高的 14 个特征
Q:为什么选取的是 14 个特征?
A:下一个代码块运行的结果,即查看所有的得分,最高的 14 个特征得分明显高于第 15 个特征的得分
"""
bestfeatures = SelectKBest(score_func = f_regression, k = 14)
fit = bestfeatures.fit(x, y)
cols = bestfeatures.get_support(indices = True)
"""
使用 iloc 按列索引筛选原数据框 df 中的列,只保留得分最高的 14 个特征
选中列索引为 cols 的列,并更新 df
"""
df = df.iloc[:, cols]

df.head()

在这里插入图片描述

df.shape
# fit.scores_ 是 SelectKBest 拟合后的属性,它存储每个特征与目标变量之间的 F 值
dfscores = pd.DataFrame(fit.scores_)
dfcolumns = pd.DataFrame(x.columns)

featureScores = pd.concat([dfcolumns, dfscores], axis=1)
featureScores.columns = ['Space', 'Score']

print(featureScores.nlargest(15, 'Score'))

在这里插入图片描述

for col in df.columns:
    print(col)

标准化(Standardization)是机器学习中的一种常见预处理步骤,它通过对特征进行变换,使得每个特征的数值范围相似。这在一些算法中尤为重要,例如线性回归、支持向量机和神经网络等,它们对数据的尺度非常敏感。

"""
对数据进行标准化处理
将 pandas 数据框中的数据转换为 PyTorch 的 CUDA 张量
便于在 GPU 上进行训练
"""

mean_std = {}

for col in df.columns:   # 遍历数据框 df 中的每一列,对每个特征进行标准化
    mean_std[col] = (combined_df[col].mean(), combined_df[col].std())
    df[col] = (df[col] - mean_std[col][0]) / mean_std[col][1]
    
x = df[df.columns].values
y = y.values

x = torch.Tensor(x).cuda()
y = torch.Tensor(y).cuda()

标准化后的值 = 原始值 − 均值 标准差 标准化后的值=\frac{原始值-均值}{标准差} 标准化后的值=标准差原始值均值

在这段代码中,使用 combined_df 的均值和标准差对 df 进行标准化,这种做法是因为数据集需要在训练和测试数据的同一分布下进行标准化。这是常见的实践方法。

保持训练和测试数据的同一分布:

  • 在实际的机器学习中,标准化通常基于整个数据集的统计信息(即训练集和测试集的组合),而不是仅基于训练集或测试集。
  • 如果训练集和测试集分别使用自己的均值和标准差进行标准化,那么标准化后的分布可能会有偏差,导致模型在测试时表现不佳。
  • 通过使用合并后的数据集(combined_df)来计算均值和标准差,可以确保训练和测试数据在同一分布下被缩放,从而更好地泛化模型。

避免数据泄露:

  • 在训练模型时,我们不能使用测试数据来计算均值和标准差,因为这可能导致数据泄露。
  • 但在预处理的标准化阶段,如果是基于交叉验证或模型评估的场景下,使用整个数据集的统计信息来标准化数据是可以的,因为模型本身并没有在测试集上进行训练。

后续代码中同样会对 test_df 的列进行标准化,目前是为了训练模型,故代码不放在这里

df.head()

在这里插入图片描述

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(14, 32)
        self.fc2 = nn.Linear(32, 1)
        
    def forward(self, x):
        x = self.fc1(x)
        x = F.relu(x)
        x = self.fc2(x)
        return x
network = Net()
network.cuda()
# weight_decay=7e-3:设置权重衰减(L2 正则化),可以防止过拟合
optimizer = torch.optim.RAdam(network.parameters(), lr=7.5e-5, weight_decay=7e-3) 
epochs = 10000
batch_size = 128

train_losses = []
test_losses = []

# # Early Stopping parameters 
ES_patience = 500   # 当在连续 500 轮中模型在验证集上的损失不再下降时,训练会提前终止
ES_counter = 0      # 记录连续损失未下降的次数
best_epoch = 0      # 记录当前最好的轮数
best_loss = 1000    # 记录训练过程中的最小损失
def train(epoch):
    network.train()
    for i in range(len(x_train) // batch_size):
        # 在每个小批次训练开始前,先将模型的梯度缓存清零,以免前一次反向传播的梯度影响当前的更新
        optimizer.zero_grad()
        pred = network(x_train[batch_size * i : batch_size * (i + 1)])
        """
        pred.view(-1):将预测值展平为一维,以便与目标值形状匹配
        
        """
        loss = F.mse_loss(pred.view(-1), y_train[batch_size * i : batch_size * (i + 1)])
        loss.backward()
        optimizer.step()
        
    train_losses.append(loss.item())
def test(epoch):
    global best_epoch, best_loss, ES_counter
    
    network.eval()
    with torch.no_grad():
        pred = network(x_test)
        loss = F.mse_loss(pred.view(-1), y_test)
        
    # Early Stopping
    if best_loss > loss:
        ES_counter, best_epoch, best_loss = 0, epoch, loss
        torch.save(network.state_dict(), 'data/kaggle/ml2021spring-hw1/model.pth')
        print('Saving model (epoch = {:4d}, MSE loss = {:.4f})'.format(epoch, loss))
    else:
        ES_counter += 1
        if ES_counter == ES_patience:
            print('Early Stopping (Best epoch = {:4d}, Best MSE loss = {:4f})'.format(best_epoch, best_loss))
            
    test_losses.append(loss.item())
from sklearn.model_selection import train_test_split  # 用于将数据集拆分为训练集和测试集

for epoch in range(1, epochs + 1):
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=epoch % 10)
    
    train(epoch)
    test(epoch)
    
    if ES_counter == ES_patience:
        break

在这里插入图片描述

import matplotlib.pyplot as plt

fig = plt.figure()
plt.plot(range(len(train_losses)), train_losses, color='red')
plt.plot(range(len(train_losses)), test_losses, color='blue')
plt.legend(['Train Loss', 'Test Loss'], loc='upper right')
plt.xlabel('Epoch')
plt.ylabel('Loss')

在这里插入图片描述

best_network = Net().cuda()
network_state_dict = torch.load('data/kaggle/ml2021spring-hw1/model.pth')
best_network.load_state_dict(network_state_dict)
best_network.eval()

test_df = test_df.iloc[:, cols]

for col in test_df.columns:
    test_df[col] = (test_df[col] - mean_std[col][0]) / mean_std[col][1]

test_X = test_df.values                # Pandas to Numpy
test_X = torch.Tensor(test_X).cuda()   # Numpy to Tensor
pred = best_network(test_X)
import csv

print('Saving Results')
with open('data/kaggle/ml2021spring-hw1/my_submission.csv', "w", newline='') as fp:
    writer = csv.writer(fp)
    writer.writerow(['id', 'tested_positive'])
    for i, p in enumerate(pred):
        writer.writerow([i, p.item()])

代码二

import numpy as np
import pandas as pd
from tqdm import tqdm

import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.preprocessing import MinMaxScaler

from sklearn.feature_selection import mutual_info_regression as mir
from sklearn.metrics import mean_squared_error as mse
from sklearn.model_selection import KFold

# 下面三个包都是进行梯度提升决策树(GBDT)的机器学习框架,适合于分类、回归和排序等任务
from sklearn import linear_model
import xgboost as xgb
import lightgbm as lgbm
import catboost as cb
train = pd.read_csv('data/kaggle/ml2021spring-hw1/covid.train.csv', index_col='id')
test = pd.read_csv('data/kaggle/ml2021spring-hw1/covid.test.csv', index_col='id')
sample = pd.read_csv('data/kaggle/ml2021spring-hw1/sampleSubmission.csv', index_col='id')
train.shape

train.csv 结构 :

  • 前40列是 One Hot 编码
  • 后54列被分为三天的数据 (18 * 3)
    • 4列 : 类似 COVID 疾病
    • 8列 : 行为指标
    • 5列 : 心理健康指标
    • 1列 : 检测呈阳性的病例(目标预测)
train[train.columns[:40]]   # 展示了前 40 列的数据

在这里插入图片描述

day_one = train.columns[40:58]
day_two = train.columns[58:76]
day_three = train.columns[76:]
train[day_one].head()

在这里插入图片描述

train[day_two].head()

在这里插入图片描述

train[day_three].head()

在这里插入图片描述

def kfold_cv(model, X, y):
    kf = KFold(n_splits=5)
    scores = []
    for train_index, test_index in kf.split(X, y):
        """
        X_train 和 X_test:从 X 中提取训练和测试数据,使用 .iloc 根据索引进行切分
        y 只有一列,所以可以直接使用 y[train_idx] 和 y[test_idx] 来根据索引进行切分
        """
        X_train, X_test = X.iloc[train_index], X.iloc[test_index]
        y_train, y_test = y[train_index], y[test_index]
        model.fit(X_train, y_train)
        scores.append(mse(y_test, model.predict(X_test), squared=True))   # squared=True 表示计算的是均方误差(MSE),而非均方根误差(RMSE)
    return np.mean(scores)  # 返回所有折的 MSE 均值,即模型在 5 折交叉验证中的平均误差
"""
由于是三天的数据,所以会有三个tested_positive
当 CSV 文件被读取时,如果存在重复的列名,Pandas 会自动给这些列名加上后缀,后缀通常是 .1、.2

假设原始 CSV 文件中有以下列 :
id, tested_positive, tested_positive, tested_positive
在这种情况下,Pandas 读取后会自动重命名这些列为 :
id, tested_positive, tested_positive.1, tested_positive.2

我们最终的目标也是根据前两天去估计test中第三天的tested_positive
"""
X = train.drop(columns=['tested_positive.2'])
y = train['tested_positive.2']
"""
mir(X, y) 是一个函数,用于计算每个特征与目标变量 y 之间的相关性得分
index=X.columns:将 DataFrame 的索引设置为 X 的列名
.sort_values():用于对 DataFrame 的值进行排序
    by=0:表示按照第 0 列(也就是互信息得分列)进行排序
    ascending=False:表示按照降序排序,这样得分高的特征会排在上面
"""
top_features = pd.DataFrame(mir(X, y), index=X.columns).sort_values(by=0, ascending=False)
top_features.head(20)

在这里插入图片描述

# 从运行结果可以看出前14个特征值分数明显高于后面的分数,故仅选取前14个特征值
selected_features = top_features.index[:14]
X_selected = X[selected_features]
test_selected = test[selected_features]
# 从运行结果可以看出前14个特征值分数明显高于后面的分数,故仅选取前14个特征值
selected_features = top_features.index[:14]
X_selected = X[selected_features]
test_selected = test[selected_features]
"""
"xgboost": xgb.XGBRegressor(n_jobs=-1):
    XGBoost 是一种高效的梯度提升决策树模型。
        n_jobs=-1:使用所有可用的 CPU 核心进行并行计算(此模型被注释掉了)。
        
"catboost": cb.CatBoostRegressor(thread_count=-1, verbose=0):
    CatBoost 是一种高效的梯度提升模型,擅长处理类别特征(此模型也被注释掉了)。
        thread_count=-1:使用所有可用线程。
        verbose=0:不打印训练过程的详细信息。

"lightgbm": lgbm.LGBMRegressor(n_jobs=-1):
    LightGBM 是一种高效的梯度提升模型。
        n_jobs=-1:使用所有 CPU 核心进行并行计算。
    
"linear_regression": linear_model.LinearRegression():
    线性回归模型,用于线性关系的预测。
    
"ridgecv": linear_model.RidgeCV():
    带有交叉验证的 Ridge 回归模型。
    使用 L2 正则化来防止过拟合。
    
"lasso": linear_model.LassoCV():
    带有交叉验证的 Lasso 回归模型。
    使用 L1 正则化,自动选择最重要的特征。
    
"elasticnet": linear_model.ElasticNetCV():
    带有交叉验证的弹性网络回归模型。
    结合了 L1 和 L2 正则化的优点。

"ardregression": linear_model.ARDRegression():
    ARD 回归模型,自动相关确定性回归,属于贝叶斯回归的一种。

"lassolarscv": linear_model.LassoLarsCV():
    带有交叉验证的 LassoLars(最小角回归)模型,用于稀疏特征的选择。

"lassolarsic": linear_model.LassoLarsIC():
    基于信息准则(如 AIC 或 BIC)选择特征的 LassoLars 回归模型。

"bayesianridge": linear_model.BayesianRidge():
    贝叶斯 Ridge 回归模型,使用贝叶斯方法估计回归系数。
"""

model_list = {
#     "xgboost": xgb.XGBRegressor(n_jobs=-1),
#     "catboost": cb.CatBoostRegressor(thread_count=-1, verbose=0),
    "lightgbm": lgbm.LGBMRegressor(n_jobs=-1),
    "linear_regression": linear_model.LinearRegression(),
    "ridgecv": linear_model.RidgeCV(),
    "lasso": linear_model.LassoCV(),
    "elasticnet": linear_model.ElasticNetCV(),
    "ardregression": linear_model.ARDRegression(),
    "lassolarscv": linear_model.LassoLarsCV(),
    "lassolarsic": linear_model.LassoLarsIC(),
    "bayesianridge": linear_model.BayesianRidge(),
}
import warnings
warnings.filterwarnings('ignore')
# 通过交叉验证评估模型性能
for name, model in model_list.items():
    print(f'{name}: {kfold_cv(model, X_selected, y)}')

"""
对测试集进行预测和集成
把每个模型预测出的predictions数组的对应位置的对应元素直接相加
然后取平均作为最终的输出

这种取多模型预测平均值的方法是一种合理的集成学习策略,特别是当模型的性能相近时
在一些 Kaggle 比赛或回归问题中,简单平均常常能够提高成绩
可以根据实际情况尝试加权平均、Stacking 等方法来进一步提升性能
"""
predictions = np.zeros(893,)
for name, model in model_list.items():
    predictions += model.predict(test[selected_features])
    
predictions = predictions / len(model_list)

在这里插入图片描述

submission = pd.DataFrame({
    'id': test.index,
    'tested_positive': predictions
})

save_path = 'data/kaggle/ml2021spring-hw1/try_model_submission.csv'
submission.to_csv(save_path, index=False)

对比

baseline 的设定如下图:
在这里插入图片描述

如果使用老师提供的未经优化的代码,分数如图:
在这里插入图片描述

使用代码一优化,分数如图:
在这里插入图片描述

使用代码二优化,分数如图:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/903807.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

STM32主从定时器输出个数、频率可调的脉冲

STM32中发出脉冲一般有两种方式: 1)利用定时中断输出脉冲,但是间隔的延时会影响其他主程序的进程,当控制多个电机的时候就非常不可取; 2)利用PWM脉宽调制,并通过主从定时器进行设定&#xff0…

研发效能度量核心三问:看什么、怎么看、怎么说服团队

问题 1:研发效能度量指标应该看什么? 在探讨研发效能度量时,首要步骤是广泛了解并学习业界认可的成熟指标及其指向性。明确指标的指向性后,面对众多度量项,应采用 GQM(Goal-Question-Metric)方…

java多线程父子参数传递失败问题

java在一个父线程中启动了一个子线程 但是运行过程中父线程的参数没有传递到子线程 原因&#xff1a;threadLocal不支持父子线程传递 解决&#xff1a;使用TransmittableThreadLocal --有问题的代码 private static final ThreadLocal<EventRuntimeContext> FLOW_CO…

TIFS-2024 细粒度表示和重组在换衣行人重识别中的应用

总体结论 本文提出了一种新的细粒度表示与重构&#xff08;FIRe2&#xff09;框架&#xff0c;用于解决布变人重识别问题。通过细粒度特征挖掘和属性重构&#xff0c;FIRe2在不依赖任何辅助信息的情况下&#xff0c;实现了最先进的性能。该方法在多个基准数据集上取得了显著的…

PostgreSQL的前世今生

PostgreSQL的起源可以追溯到1977年的加州大学伯克利分校&#xff08;UC Berkeley&#xff09;的Ingres项目。该项目由著名的数据库科学家Michael Stonebraker领导&#xff0c;他是2015年图灵奖的获得者。以下是PostgreSQL起源的详细概述&#xff1a; 一、早期发展 Ingres项目…

DAY43 ||322.零钱兑换 |279.完全平方数 |139.单词拆分

322.零钱兑换 题目&#xff1a;322. 零钱兑换 - 力扣&#xff08;LeetCode&#xff09; 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额&#xff0c;返回 -1。 你可以认为每种硬…

WebGL进阶(五)-可视域

理论基础&#xff1a; 顶点着色器 Vertex Shader 主要是负责处理顶点位置、顶点颜色、顶点向量等顶点的数据&#xff1b;处理一些顶点的变换&#xff1a;例如在进行视图变换和投影变换时MVP矩阵会改变顶点的位置信息。 输入&#xff1a; 顶点着色器输入部分主要是声明&…

gin入门教程(10):实现jwt认证

使用 github.com/golang-jwt/jwt 实现 JWT&#xff08;JSON Web Token&#xff09;可以有效地进行用户身份验证,这个功能往往在接口前后端分离的应用中经常用到。以下是一个基本的示例&#xff0c;演示如何在 Gin 框架中实现 JWT 认证。 目录结构 /hello-gin │ ├── cmd/ …

三星瞄准2026年推出400层垂直NAND技术,2030年前剑指1000层NAND闪存

据报道&#xff0c;三星计划在2026年前推出400层的垂直NAND闪存&#xff0c;并且目标是在2030年前实现1000层的NAND技术。随着人工智能&#xff08;AI&#xff09;浪潮的到来&#xff0c;高带宽内存&#xff08;HBM&#xff09;已经成为存储巨头之间的关键战场&#xff0c;而同…

端口号和ip地址一样吗?区别是什么

在网络通信的世界里&#xff0c;端口号和IP地址是两个不可或缺的概念&#xff0c;它们各自扮演着独特的角色&#xff0c;共同维系着数据在网络中的有序传输。然而&#xff0c;对于许多初学者而言&#xff0c;这两者往往容易被混淆&#xff0c;认为它们是同一事物的不同表述。那…

前端自学资料(笔记八股)分享—CSS(4)

更多详情&#xff1a;爱米的前端小笔记&#xff08;csdn~xitujuejin~zhiHu~Baidu~小红shu&#xff09;同步更新&#xff0c;等你来看&#xff01;都是利用下班时间整理的&#xff0c;整理不易&#xff0c;大家多多&#x1f44d;&#x1f49b;➕&#x1f914;哦&#xff01;你们…

JavaScript字符串不可变性与ES6 新增字符串方法详解

非 VIP 用户可前往公众号“前端基地”进行免费阅读,文章链接如下: JavaScript字符串不可变性与ES6 新增字符串方法详解本文介绍了 JavaScript 中字符串的不可变性以及 ES6 新增的字符串方法。包括判断是否包含、以指定字符串开头或结尾,还有重复指定次数等方法,并结合案例…

鸿蒙开发:arkTS FolderStack容器组件

ArkTS(也称为Ark TypeScript)是鸿蒙生态的应用开发语言&#xff0c;它在TypeScript(简称TS)的基础上进行了优化和定制&#xff0c;以满足鸿蒙系统的开发需求。今天给大家分享arkTS FolderStack容器组件技术知识&#xff0c;如果有所帮助&#xff0c;大家点点关注支持一下&#…

SSL/TLS 密码套件漏洞分析以及修复方法

1. 前言 在当今数字化时代&#xff0c;网络安全至关重要。SSL/TLS 协议作为保障网络通信安全的重要手段&#xff0c;广泛应用于各类网络应用中。然而&#xff0c;如同任何技术一样&#xff0c;SSL/TLS 也并非绝对安全&#xff0c;存在着一些可能被攻击者利用的漏洞。本文将深入…

如何加密电脑磁盘?电脑本地磁盘加密方法介绍

随着信息技术的不断发展&#xff0c;电脑磁盘加密已经成为保护个人隐私和数据安全的重要手段。本文将介绍几种常见的电脑本地磁盘加密方法&#xff0c;帮助用户保护自己的数据安全。 文件夹只读加密专家 文件夹只读加密专家不仅可以加密电脑中的文件夹&#xff0c;还可以加密保…

已解决Navicat 选择Mysql表 报错unkonow internal error: Access violation - no RTTI data

已解决Navicat 选择Mysql表 报错unkonow internal error&#xff1a; Access violation - no RTTI data 报错信息截图&#xff1a; 使用Navicat Premium15 选择sql server表时 出现大量弹窗报错&#xff0c;导致sql文件执行不了&#xff0c;右键数据库执行外部文件也失败了。弹…

【机器学习】揭秘XGboost:高效梯度提升算法的实践与应用

目录 &#x1f354; XGBoost 原理 1.1 目标函数确定和树的复杂度介绍 1.2 XGBoost目标函数的推导 1.3 泰勒公式展开 1.4 化简目标函数 1.5 问题再次转换 1.6 对叶子结点求导 1.7 XGBoost的回归树构建方法 &#x1f354; XGBoost API 2.1 通用参数 2.2 Booster 参数 …

基于vue框架的的高校学习资源共享系统5ym3y(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;学生,学校信息,课程分类,班课信息,班课申请,学习资源,班课评价,班课投诉,投诉学生,教师,班级 开题报告内容 基于Vue框架的高校学习资源共享系统开题报告 一、项目背景与意义 随着信息技术的飞速发展和教育改革的深入推进&#xff0c;…

Hadoop生态圈框架部署(二)- 配置IP地址映射为主机名及免密登录

文章目录 前言一、配置IP地址映射为主机名1. 虚拟机hadoop1配置主机名与 IP 地址的映射关系2. 虚拟机hadoop2配置主机名与 IP 地址的映射关系3. 虚拟机hadoop3配置主机名与 IP 地址的映射关系 二、配置免密登录1. 配置虚拟机hadoop1免密登录到hadoop1、hadoop2和hadoop32. 配置…

基于JSP的篮球系列网上商城系统【附源码】

基于JSP的篮球系列网上商城系统 效果如下&#xff1a; 系统首页界面 商品信息界面 购物车界面 购物车界面 管理员登录界面 管理员功能界面 用户注册界面 我的收藏界面 研究背景 21世纪&#xff0c;我国早在上世纪就已普及互联网信息&#xff0c;互联网对人们生活中带来了无限…