机器学习网格搜索超参数优化实战(随机森林) ##4

文章目录

  • 基于Kaggle电信用户流失案例数据(可在官网进行下载)
    • 数据预处理模块
    • 时序特征衍生
    • 第一轮网格搜索
    • 第二轮搜索
    • 第三轮搜索
    • 第四轮搜索
    • 第五轮搜索

基于Kaggle电信用户流失案例数据(可在官网进行下载)

导入库

# 基础数据科学运算库
import numpy as np
import pandas as pd# 可视化库
import seaborn as sns
import matplotlib.pyplot as plt# 时间模块
import timeimport warnings
warnings.filterwarnings('ignore')# sklearn库
# 数据预处理
from sklearn import preprocessing
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OrdinalEncoder
from sklearn.preprocessing import OneHotEncoder# 实用函数
from sklearn.metrics import accuracy_score, recall_score, precision_score, f1_score, roc_auc_score
from sklearn.model_selection import train_test_split# 常用评估器
from sklearn.pipeline import make_pipeline
from sklearn.linear_model import LogisticRegression
from sklearn import tree
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier# 网格搜索
from sklearn.model_selection import GridSearchCV# 自定义评估器支持模块
from sklearn.base import BaseEstimator, TransformerMixin# re模块相关
import inspect, re# 其他模块
from tqdm import tqdm
import gc

数据预处理模块

然后进行数据清洗相关工作:

# 读取数据
tcc = pd.read_csv('WA_Fn-UseC_-Telco-Customer-Churn.csv')# 标注连续/离散字段
# 离散字段
category_cols = ['gender', 'SeniorCitizen', 'Partner', 'Dependents','PhoneService', 'MultipleLines', 'InternetService', 'OnlineSecurity', 'OnlineBackup', 'DeviceProtection', 'TechSupport', 'StreamingTV', 'StreamingMovies', 'Contract', 'PaperlessBilling','PaymentMethod']# 连续字段
numeric_cols = ['tenure', 'MonthlyCharges', 'TotalCharges']# 标签
target = 'Churn'# ID列
ID_col = 'customerID'# 验证是否划分能完全
assert len(category_cols) + len(numeric_cols) + 2 == tcc.shape[1]# 连续字段转化
tcc['TotalCharges']= tcc['TotalCharges'].apply(lambda x: x if x!= ' ' else np.nan).astype(float)
tcc['MonthlyCharges'] = tcc['MonthlyCharges'].astype(float)# 缺失值填补
tcc['TotalCharges'] = tcc['TotalCharges'].fillna(0)# 标签值手动转化 
tcc['Churn'].replace(to_replace='Yes', value=1, inplace=True)
tcc['Churn'].replace(to_replace='No',  value=0, inplace=True)features = tcc.drop(columns=[ID_col, target]).copy()
labels = tcc['Churn'].copy()

时序特征衍生

# 定义辅助函数
def colName(ColumnTransformer, numeric_cols, category_cols):col_name = []col_value = ColumnTransformer.named_transformers_['cat'].categories_for i, j in enumerate(category_cols):if len(col_value[i]) == 2:col_name.append(j)else:for f in col_value[i]:feature_name = j + '_' + fcol_name.append(feature_name)col_name.extend(numeric_cols)return(col_name)

进行简单的时序特征衍生:

# 划分训练集和测试集
train, test = train_test_split(tcc, random_state=22)X_train = train.drop(columns=[ID_col, target]).copy()
X_test = test.drop(columns=[ID_col, target]).copy()y_train = train['Churn'].copy()
y_test = test['Churn'].copy()X_train_seq = pd.DataFrame()
X_test_seq = pd.DataFrame()# 年份衍生
X_train_seq['tenure_year'] = ((72 - X_train['tenure']) // 12) + 2014
X_test_seq['tenure_year'] = ((72 - X_test['tenure']) // 12) + 2014# 月份衍生
X_train_seq['tenure_month'] = (72 - X_train['tenure']) % 12 + 1
X_test_seq['tenure_month'] = (72 - X_test['tenure']) % 12 + 1# 季度衍生
X_train_seq['tenure_quarter'] = ((X_train_seq['tenure_month']-1) // 3) + 1
X_test_seq['tenure_quarter'] = ((X_test_seq['tenure_month']-1) // 3) + 1# 独热编码
enc = preprocessing.OneHotEncoder()
enc.fit(X_train_seq)seq_new = list(X_train_seq.columns)# 创建带有列名称的独热编码之后的df
X_train_seq = pd.DataFrame(enc.transform(X_train_seq).toarray(), columns = cate_colName(enc, seq_new, drop=None))X_test_seq = pd.DataFrame(enc.transform(X_test_seq).toarray(), columns = cate_colName(enc, seq_new, drop=None))# 调整index
X_train_seq.index = X_train.index
X_test_seq.index = X_test.index
ord_enc = OrdinalEncoder()
ord_enc.fit(X_train[category_cols])X_train_OE = pd.DataFrame(ord_enc.transform(X_train[category_cols]), columns=category_cols)
X_train_OE.index = X_train.index
X_train_OE = pd.concat([X_train_OE, X_train[numeric_cols]], axis=1)X_test_OE = pd.DataFrame(ord_enc.transform(X_test[category_cols]), columns=category_cols)
X_test_OE.index = X_test.index
X_test_OE = pd.concat([X_test_OE, X_test[numeric_cols]], axis=1)

第一轮网格搜索

NameDescription
n_estimators决策树模型个数
criterion规则评估指标或损失函数,默认基尼系数,可选信息熵
splitter树模型生长方式,默认以损失函数取值减少最快方式生长,可选随机根据某条件进行划分
max_depth树的最大生长深度,类似max_iter,即总共迭代几次
min_samples_split内部节点再划分所需最小样本数
min_samples_leaf叶节点包含最少样本数
min_weight_fraction_leaf叶节点所需最小权重和
max_features在进行切分时候最多带入多少个特征进行划分规则挑选
random_state随机数种子
max_leaf_nodes叶节点最大个数
min_impurity_decrease数据集再划分至少需要降低的损失值
bootstrap是否进行自助抽样
oob_score是否输出袋外数据的测试结果
min_impurity_split数据集再划分所需最低不纯度,将在0.25版本中移除
class_weight各类样本权重
ccp_alpha决策树限制剪枝参数,相当于风险项系数
max_samples进行自助抽样时每棵树分到的样本量

  随机森林需要搜索的7个参数及其第一轮搜索时建议的参数空间如下:

params经验最优范围
min_samples_leaf[1, 4, 7]; range(1, 10, 3)
min_samples_split[1, 4, 7]; range(1, 10, 3)
max_depth[5, 10, 15]; range(5, 16, 5)
max_leaf_nodes[None, 20, 40, 60]; [None] + list(range(20, 70, 20))
n_estimators[10, 80, 150]; range(10, 160, 70)
max_features[‘sqrt’, ‘log2’] +[ l o g 2 ( m ) log_2{(m)} log2(m)*50%, m \sqrt{m} m *150%] 其中m为特征数量
max_samples[None, 0.4, 0.5, 0.6]
start = time.time()# 设置超参数空间
parameter_space = {"min_samples_leaf": range(1, 10, 3), "min_samples_split": range(1, 10, 3),"max_depth": range(5, 16, 5),"max_leaf_nodes": [None] + list(range(20, 70, 20)), "n_estimators": range(10, 160, 70), "max_features":['sqrt', 'log2'] + list(range(2, 7, 2)), "max_samples":[None, 0.4, 0.5, 0.6]}# 实例化模型与评估器
RF_0 = RandomForestClassifier(random_state=12)
grid_RF_0 = GridSearchCV(RF_0, parameter_space, n_jobs=15)# 模型训练
grid_RF_0.fit(X_train_OE, y_train)grid_RF_0.score(X_train_OE, y_train), grid_RF_0.score(X_test_OE, y_test)

对比之前逻辑回归结果显示:

ModelsCV.best_score_train_scoretest_score
Logistic+grid0.80450.80550.7932
RF+grid_R10.80840.85170.7848
# 查看超参数最优取值
grid_RF_0.best_params_

在这里插入图片描述

  • max_depth本轮最优取值为10,而原定搜索空间为[5, 10, 15],因此第二轮搜索时就可以以10为中心,缩小步长,进行更精准的搜索;
  • max_features本轮最优取值为sqrt,说明最优解极有可能在4附近,因此第二轮搜索时可以设置一组更加精准的在4附近的数值,搭配sqrt参数一起进行搜索;
  • max_leaf_nodes本轮最优取值为None,则有可能说明上一轮给出的其他备选数值不够激进,下一轮搜索时可以在一个更大的区间范围内设置备选数值;
  • max_samples本轮最优取值为0.4,下一轮可以以0.4为中心,设置一组跨度更小、精度更高的取值进行搜索;
  • min_samples_leaf本轮最优取值为1,下一轮可以设置range(1, 4)进行搜索(参数不能取得比1更小的值);
  • min_samples_split本轮最优取值为7,下一轮可以以7为中心,设置更小的范围进行搜索;
  • n_estimators本轮最优取值为80,下一轮可以以80为中心,设置更小的范围进行搜索,但需要注意的是,上一轮n_estimators取值搜索的跨度为70,下轮搜索时可以缩减到10。

第二轮搜索

start = time.time()# 设置超参数空间
parameter_space = {"min_samples_leaf": range(1, 4), "min_samples_split": range(6, 9),"max_depth": range(9, 12),"max_leaf_nodes": [None] + list(range(10, 100, 30)), "n_estimators": range(70, 100, 10), "max_features":['sqrt'] + list(range(2, 5)), "max_samples":[None, 0.35, 0.4, 0.45]}# 实例化模型与评估器
RF_0 = RandomForestClassifier(random_state=12)
grid_RF_0 = GridSearchCV(RF_0, parameter_space, n_jobs=15)# 模型训练
grid_RF_0.fit(X_train_OE, y_train)grid_RF_0.score(X_train_OE, y_train), grid_RF_0.score(X_test_OE, y_test)
ModelsCV.best_score_train_scoretest_score
Logistic+grid0.80450.80550.7932
RF+grid_R10.80840.85170.7848
RF+grid_R20.80880.84590.7922
grid_RF_0.best_params_

在这里插入图片描述

  • max_depth本轮最优取值为11,在原定搜索空间上界,下次搜索可以进一步向上拓展搜索空间;
  • max_features本轮最优取值为2,是原定搜索空间的下界,下次搜索可向下拓展搜索空间,也就是将1带入进行搜索。但需要注意的是,sqrt作为非数值型结果,仍然需要带入进行搜索,这轮被淘汰并不代表重新调整搜索空间后仍然被淘汰;
  • max_leaf_nodes本轮最优取值仍然为None,说明在一个更大的范围内进行更激进的搜索并没有达到预想的效果,下一轮可以反其道而行之,设置一个上一轮没有搜索到的数值较小的空间(1-20),来进行更加精准的搜索;
  • max_samples本轮最优取值仍然为0.4,基本可以确定最优取值就在0.4附近,下一轮可以进一步设置一个步长更小的区间进行搜索;
  • min_samples_leaf本轮最优取值为2,恰好落在本轮搜索空间的中间,下一轮搜索时不用调整取值;
  • min_samples_split本轮最优取值仍然为7,恰好落在本轮搜索空间的中间,下一轮搜索时不用调整取值;
  • n_estimators本轮最优取值为90,下一轮可以以90为中心,设置更小的范围进行搜索,但需要注意的是,上一轮n_estimators取值搜索的跨度为10,下轮搜索时可以缩减到4。

第三轮搜索

start = time.time()# 设置超参数空间
parameter_space = {"min_samples_leaf": range(1, 4), "min_samples_split": range(6, 9),"max_depth": range(10, 15),"max_leaf_nodes": [None] + list(range(1, 20, 2)), "n_estimators": range(85, 100, 4), "max_features":['sqrt'] + list(range(1, 4)), "max_samples":[None, 0.38, 0.4, 0.42]}# 实例化模型与评估器
RF_0 = RandomForestClassifier(random_state=12)
grid_RF_0 = GridSearchCV(RF_0, parameter_space, n_jobs=15)# 模型训练
grid_RF_0.fit(X_train_OE, y_train)grid_RF_0.score(X_train_OE, y_train), grid_RF_0.score(X_test_OE, y_test)
ModelsCV.best_score_train_scoretest_score
Logistic+grid0.80450.80550.7932
RF+grid_R10.80840.85170.7848
RF+grid_R20.8087850.84590.7922
RF+grid_R30.8087840.84150.7927
grid_RF_0.best_params_

在这里插入图片描述

  • max_depth本轮最优取值为10,在原定搜索空间下界,下次搜索可以进一步向下拓展搜索空间,当然,根据第二轮第三轮max_depth在9和10反复变动的现象,估计max_depth最终的最优取值也就是9、10左右;
  • max_features本轮最优取值又回到了sqrt,也就是4附近,结合第一轮sqrt的最优结果,预计max_features最终最优取值也就在4附近,接下来的搜索将是收尾阶段,我们可以设计一个sqrt+log2+4附近的搜索组合;
  • max_leaf_nodes本轮最优取值仍然为None,三轮搜索都没有改变max_leaf_nodes的最优取值,并且本轮还设置了非常多的备选取值,说明max_leaf_nodes的最优取值极有可能就是None,接下来我们只需保留None+大范围搜索的组合即可,以防其他参数变动时max_leaf_nodes的最优取值发生变化;
  • max_samples本轮最优取值变成了0.38,而训练集总样本数为5282,5282*0.38约为2007,下轮开始我们将把比例转化为具体的样本数,进行更加精准的搜索,及围绕2007附近的数值空间进行搜索;
  • min_samples_leaf本轮最优取值为3,恰好落在本轮搜索空间的上届,下一轮搜索时略微拓展搜索空间的上界;
  • min_samples_split本轮最优取值仍然为7,恰好落在本轮搜索空间的中间,下一轮搜索时不用调整取值;
  • n_estimators本轮最优取值为97,下一轮可以以97为中心,设置更小的范围进行搜索;

第四轮搜索

start = time.time()# 设置超参数空间
parameter_space = {"min_samples_leaf": range(2, 5), "min_samples_split": range(6, 9),"max_depth": range(8, 12),"max_leaf_nodes": [None] + list(range(10, 70, 20)), "n_estimators": range(95, 105, 2), "max_features":['sqrt', 'log2'] + list(range(1, 6, 2)), "max_samples":[None] + list(range(2002, 2011, 2))}# 实例化模型与评估器
RF_0 = RandomForestClassifier(random_state=12)
grid_RF_0 = GridSearchCV(RF_0, parameter_space, n_jobs=15)# 模型训练
grid_RF_0.fit(X_train_OE, y_train)grid_RF_0.score(X_train_OE, y_train), grid_RF_0.score(X_test_OE, y_test)
ModelsCV.best_score_train_scoretest_score
Logistic+grid0.80450.80550.7932
RF+grid_R10.80840.85170.7848
RF+grid_R20.8087850.84590.7922
RF+grid_R30.8087840.84150.7927
RF+grid_R40.8095420.84060.7882
grid_RF_0.best_params_

在这里插入图片描述- max_depth本轮最优取值为9,能够进一步肯定max_depth最终的最优取值也就是9、10左右;

  • max_features本轮最优取值变成了5,仍然在4附近变化,后续继续保留sqrt+log2+4附近的搜索组合;
  • max_leaf_nodes本轮最优取值仍然为None,并没有发生任何变化,后续仍然保留原定搜索范围;
  • max_samples本轮最优取值为2002,这是第一次围绕max_samples进行整数搜索,接下来可以以2002为中心,设置一个更小搜索空间;
  • min_samples_leaf本轮最优取值为3,恰好落在本轮搜索空间的上届,下一轮搜索时略微拓展搜索空间的上界;
  • min_samples_split本轮最优取值变成了8,根据之前的搜索结果,该参数最优取值基本都在7和8之间变动,因此可以设置一个6-9的搜索空间,确保下次如果再出现参数在7、8之间变动时,仍然在搜索范围内;
  • n_estimators本轮最优取值为99,结合之前搜索出来的97的结果,预计该参数最终的最优取值应该就是97-99之间,可以据此设置下一轮搜索空间;

第五轮搜索

# "min_samples_leaf":以3为中心
# "min_samples_split":重点搜索7、8两个值
# "max_depth":重点搜索9、10两个值
# "max_leaf_nodes":大概率为None
# "n_estimators": 重点搜索97、98、99三个值
# "max_features":5附近的值+['sqrt', 'log2']
# "max_samples":2002向下搜索,重点搜索2002、2001和2000三个值# 设置超参数空间
parameter_space = {"min_samples_leaf": range(2, 5), "min_samples_split": range(6, 10),"max_depth": range(8, 12),"max_leaf_nodes": [None], "n_estimators": range(96, 101), "max_features":['sqrt', 'log2'] + list(range(3, 7)), "max_samples":[None] + list(range(2000, 2005))}# 实例化模型与评估器
RF_0 = RandomForestClassifier(random_state=12)
grid_RF_0 = GridSearchCV(RF_0, parameter_space, n_jobs=15)# 模型训练
grid_RF_0.fit(X_train_OE, y_train)grid_RF_0.score(X_train_OE, y_train), grid_RF_0.score(X_test_OE, y_test)
ModelsCV.best_score_train_scoretest_score
Logistic+grid0.80450.80550.7932
RF+grid_R10.80840.85170.7848
RF+grid_R20.8087850.84590.7922
RF+grid_R30.8087840.84150.7927
RF+grid_R40.8095420.84060.7882
RF+grid_R50.8104880.84830.7955
grid_RF_0.best_params_

在这里插入图片描述
至此,大致得到最优结果,往后调优思路可大致照此.

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

系统架构27 - 软件架构设计(6)

基于架构的软件开发方法 基于架构的软件开发方法(ABSD)概述概念与术语开发模型体系结构需求体系结构设计体系结构文档化体系结构复审体系结构实现体系结构的演化 基于架构的软件开发方法(ABSD) 基于体系结构的软件设计 (Architec…

【JavaEE】_文件与IO

目录 1.文件概述 1.1 文件的概念 1.2 文件的存储 1.3 文件的分类 1.4 目录结构 1.5 文件操作 1.5.1 文件系统操作 1.5.2 文件内容操作 2. Java文件系统操作 2.1 File类所处的包 2.2 构造方法 2.3 方法 2.3.1 与文件路径、文件名有关的方法 2.3.2 文件是否存在与普…

openGauss学习笔记-221 openGauss性能调优-确定性能调优范围-分析作业是否被阻塞

文章目录 openGauss学习笔记-221 openGauss性能调优-确定性能调优范围-分析作业是否被阻塞221.1 操作步骤 openGauss学习笔记-221 openGauss性能调优-确定性能调优范围-分析作业是否被阻塞 数据库系统运行时,在某些业务场景下查询语句会被阻塞,导致语句…

【MySQL】操作库 —— 表的操作 -- 详解

一、增加表 1、创建表 mysql> create database [if not exists] table_name ( -> field1 datatype, -> field2 datatype, -> field3 datatype -> ) character set 字符集 collate 校验规则 engine 存储引擎; 注意 :最后一行也可以写成&#x…

Vue源码系列讲解——模板编译篇【二】(整体运行流程)

目录 1. 整体流程 2. 回到源码 3. 总结 1. 整体流程 上篇文章中我们说了&#xff0c;在模板解析阶段主要做的工作是把用户在<template></template>标签内写的模板使用正则等方式解析成抽象语法树&#xff08;AST&#xff09;。而这一阶段在源码中对应解析器&…

c语言操作符(上)

目录 ​编辑 原码、反码、补码 1、正数 2、负数 3、二进制计算1-1 移位操作符 1、<<左移操作符 2、>>右移操作符 位操作符&、|、^、~ 1、&按位与 2、|按位或 3、^按位异或 特点 4、~按位取反 原码、反码、补码 1、正数 原码 反码 补码相同…

STM32 HAL库 STM32CubeMX -- IWDG(独立看门狗)

STM32 HAL库 STM32CubeMX -- IWDG 一、IWDG简介二、独立看门狗的工作原理三、驱动函数初始化函数HAL IWDG Init()初始化函数HAL IWDG Init()其他宏函数 四、超时时间计算第一种办法第二种办法&#xff08;推荐&#xff09; 一、IWDG简介 看门狗(Watchdog)就是MCU上的一种特殊的…

【AIGC】Stable Diffusion的模型入门

下载好相关模型文件后&#xff0c;直接放入Stable Diffusion相关目录即可使用&#xff0c;Stable Diffusion 模型就是我们日常所说的大模型&#xff0c;下载后放入**\webui\models\Stable-diffusion**目录&#xff0c;界面上就会展示相应的模型选项&#xff0c;如下图所示。作者…

计算机网络——多媒体网络

前些天发现了一个巨牛的人工智能学习网站 通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff0c; 跳转到网站 小程一言 我的计算机网络专栏&#xff0c;是自己在计算机网络学习过程中的学习笔记与心得&#xff0c;在参考相关教材&#xff0c;网络搜素…

保育员考试怎么搜题找答案?9个不限次的公众号和软件分享啦! #经验分享#学习方法

学会运用各类学习辅助工具和资料&#xff0c;是大学生培养自主学习能力和信息获取能力的重要途径之一。 1.粉鹿搜题 这是一个公众号 使用方法也很简单&#xff0c;将题目发送至公众号后台&#xff0c;即可得到题目答案~ 下方附上一些测试的试题及答案 1、下列哪项是聚合式联…

DS Wannabe之5-AM Project: DS 30day int prep day18

Q1. What is Levenshtein Algorithm? Levenshtein算法&#xff0c;也称为编辑距离算法&#xff0c;是一种量化两个字符串之间差异的方法。它通过计算将一个字符串转换成另一个字符串所需的最少单字符编辑操作次数来实现。这些编辑操作包括插入、删除和替换字符。Levenshtein距…

掌握Go并发:Go语言并发编程深度解析

&#x1f3f7;️个人主页&#xff1a;鼠鼠我捏&#xff0c;要死了捏的主页 &#x1f3f7;️系列专栏&#xff1a;Golang全栈-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&…

第三讲 数据存储

面向磁盘的架构 DBMS 假定数据库的主要存储位置位于非易失性磁盘【non-volatile disk】上。 DBMS 的组件管理非易失性【non-volatile】和易失性【volatile】存储之间的数据移动。 为了理解来回移动数据的影响&#xff0c;我们首先要先理解存储层次结构是什么样的。 存储层次【…

文生图提示词:气候特征

天气和气候 --气候特征 Climate Features 气候特征的基本词汇&#xff0c;涵盖了温度和湿度的变化&#xff0c;以及它们在不同气候类型中的体现。 Hot 炎热 Cold 寒冷 Warm 温暖 Cool 凉爽 Humid 湿润 Dry 干燥 Mild 温和 Chilly 冷飕飕 Freezing 冰冻 Sweltering 酷热 Frosty …

RK3568平台开发系列讲解(实验篇)杂项设备驱动实验

🚀返回专栏总目录 文章目录 一、什么是杂项设备驱动二、杂项设备的注册和卸载三、杂项设备驱动实验代码沉淀、分享、成长,让自己和他人都能有所收获!😄 一、什么是杂项设备驱动 在 Linux 中,把无法归类的五花八门的设备定义成杂项设备。相较于字符设备,杂项设备有以下两…

企业数字化转型战略规划与实践:迈向未来的关键之举

在信息技术的不断革新和全球数字化浪潮的推动下&#xff0c;企业数字化转型已经成为当今商业世界中不可或缺的一项战略规划。随着技术的进步&#xff0c;企业必须积极应对数字化转型的挑战&#xff0c;并将其作为发展的关键驱动力。本文将探讨企业数字化转型的重要性&#xff0…

幻兽帕鲁联机服务器搭建新手小白教程

这里分为两种搭建方式&#xff0c;都是采用的一键搭建的傻瓜式教程&#xff0c;1分钟就可以搞定。 一、通过阿里云一键部署幻兽帕鲁服务器 以下教程基于阿里云服务器来搭建幻兽帕鲁游戏服务器&#xff0c;通过一键部署的方式&#xff0c;最快1分钟即可完成部署。 阿里云一键…

【白话前端】一篇文章区分js库和js框架

假定你选择自助游&#xff0c;你需要找不同服务商帮你解决吃住行的问题&#xff0c;这些服务商就是js库。你也可以选择旅行社&#xff0c;给你全解决&#xff0c;这是js框架。 JavaScript库和框架都是用于简化Web开发的工具&#xff0c;但它们之间有一些区别。 JavaScript库&a…

【Java多线程】Thread类的基本用法

目录 Thread类 1、创建线程 1.1、继承 Thread&#xff0c;重写run 1.2、实现 Runnable&#xff0c;重写run 1.3、使用匿名内部类&#xff0c;继承 Thread&#xff0c;重写run 1.4、使用匿名内部类&#xff0c;实现 Runnable&#xff0c;重写run 1.5、使用 lambda 表达式…

Swift Combine 级联多个 UI 更新,包括网络请求 从入门到精通十六

Combine 系列 Swift Combine 从入门到精通一Swift Combine 发布者订阅者操作者 从入门到精通二Swift Combine 管道 从入门到精通三Swift Combine 发布者publisher的生命周期 从入门到精通四Swift Combine 操作符operations和Subjects发布者的生命周期 从入门到精通五Swift Com…