R语言数学建模(二)—— tidymodels

R语言数学建模(二)—— tidymodels


文章目录

  • R语言数学建模(二)—— tidymodels
  • 前言
  • 一、示例数据集
  • 二、拆分数据集
    • 2.1 拆分数据集的常用方法
    • 2.2 验证集
    • 2.3 多层次数据
    • 2.4 其他需考虑问题
  • 三、`parsnip`用于拟合模型
    • 3.1 创建模型
    • 3.2 使用模型结果
    • 3.3 做出预测
    • 3.4 parsnip扩展包
    • 3.5 创建模型的型号与规则快捷
  • 总结


前言

上一节内容中,简单介绍了使用R语言对数据进行建模的一个工作流程。在R中,为了方便进行数学建模,有一个实用的包——tidymodels。这一节将通过学习这个包,体会其进行R语言建模的流程。

一、示例数据集

在上一节就讲到过,想要进行R建模,首先需要对数据有一定的了解。本节将通过一个实例来进行建模学习。让我们首先来认识一下这个示例数据集:Ames住房数据集。

library(modeldata) # tidymodels包也加载此包
data(ames)

# 或者这样加载
data(ames, package = "modeldata")

dim(ames)
#> [1] 2930   74

可以看到ames是一个2930行74列的数据集。该数据集包含的房产信息包括以下内容的列:

  • 房屋特征(bedrooms,garage,fireplace,pool,porch,etc.)

  • 位置(社区)

  • 地段信息

  • 状况质量评级

  • 销售价格

我们本次建模的目标是根据我们所拥有的其他信息预测房屋的销售价格。让我们先来观察一下房子销售价格的分布:

library(tidymodels)
#> ── Attaching packages ────────────────────────────────────── tidymodels 1.1.1 ──
#> ✔ broom        1.0.5     ✔ rsample      1.2.0
#> ✔ dials        1.2.0     ✔ tibble       3.2.1
#> ✔ dplyr        1.1.3     ✔ tidyr        1.3.0
#> ✔ ggplot2      3.4.4     ✔ tune         1.1.2
#> ✔ infer        1.0.5     ✔ workflows    1.1.3
#> ✔ parsnip      1.1.1     ✔ workflowsets 1.0.1
#> ✔ purrr        1.0.2     ✔ yardstick    1.2.0
#> ✔ recipes      1.0.8
#> ── Conflicts ───────────────────────────────────────── tidymodels_conflicts() ──
#> ✖ purrr::discard() masks scales::discard()
#> ✖ dplyr::filter()  masks stats::filter()
#> ✖ dplyr::lag()     masks stats::lag()
#> ✖ recipes::step()  masks stats::step()
#> • Learn how to get started at https://www.tidymodels.org/start/
tidymodels_prefer()

ggplot(ames, aes(x = Sale_Price)) +
  geom_histogram(bins = 50, col = "white")

在这里插入图片描述
曲线图可以看出,数据不对称,便宜的房子比昂贵的房子要多。处理这样的数据可以考虑使用对数变换。这种变换的好处在于,不会以负销售价格来预测任何房屋,且预测昂贵房屋的错误不会对模型产生影响。统计学的角度来讲,对数变换也可能以一种使推断更合理的方式稳定方差。我们可以使用类似步骤可视化转换后的数据:

ggplot(ames, aes(x = Sale_Price)) +
  geom_histogram(bins = 50, col = "white") +
  scale_x_log10()

在这里插入图片描述
转换结果的缺点主要涉及对模型结果的解释。另外对于转换后的模型系数的单位可能更难解释。例如,均方根误差(RMSE)是回归模型中常用的性能指标。计算中使用观测值与预测值间的差值。如果销售价格在对数标尺上,这些差额(残差)也在对数标尺上,这样的尺度上,RMSE为0.15的模型的质量将很难理解。

尽管如此,这里我们使用的模型使用了对数转换的结果,现在,将其进行对数转换:

ames <- ames |> mutate(Sale_Price = log10(Sale_Price))

在任何建模开始前,探索数据是至关重要的。这些住房数据的特点对如何处理和建模数据提出了有趣的挑战。在这一阶段可以探索的问题包括:

  • 单个预测因子的分布有没有什么值得注意的地方?是否有很大的偏斜?

  • 预测因子间是否有很强的相关性?有些相关因素是多余的吗?

  • 预测因子与结果之间是否存在关联?

二、拆分数据集

创建有用的模型有几个步骤,包括参数估计、模型选择和调整以及性能评估。新项目开始前,通常会有一个初始的有限数据集可以用于不同的步骤或任务。建模时,如何使用数据是一个要首要考虑的因素。

当数据量很大时,明智的策略是为不同的任务分配特定的数据子集,而不是尽可能将最大数量的数据分配给模型参数估计。一种可能的策略是,使用特定的数据子集来确定哪些预测值是准确的。如果数据集不是特别大,那么在如何使用分配数据方面可能会有一些重叠,因此可靠的数据使用方式是重要的。

本节演示了基于不同目的的数据集的拆分。

2.1 拆分数据集的常用方法

经验模型验证的主要方法是将现有的数据集分为训练集和测试集。一部分数据被用来开发和优化模型,这些包含了大部分数据的是训练集。另一部分数据作为测试集,直到一两个模型被选出作为最可能的模型,测试集被用来作为最终仲裁器。

拆分数据集的方法往往是视具体情况而定的。假设将80%数据分给训练集,20%用于测试。常见的方法是简单随机抽样。rsample::initial_split()是为此创建的。它将数据框作为参数,将其放入训练中的比例。

# 设置随机数种子,以便日后可以重复结果
set.seed(501)

ames_split <- initial_split(ames, prop = 0.80)
ames_split
#> <Training/Testing/Total>
#> <2344/586/2930>

然而,ames_split只包含了分区的信息,为了获得结果的数据集,我们使用了另外的两个函数:

ames_train <- training(ames_split)
ames_test <- testing(ames_split)

dim(ames_train)
#> [1] 2344   74

许多情况下,简单随机抽样是合适的,但是也存在例外。当分类问题存在明显的类别不平衡时,一个类别的发生频率要明显高于或低于另一个分类的时候,使用简单随机抽样可能会将这些不常见的样本不成比例的分配到训练集或测试集中。为了避免此情况的发生,我们可以采用分层抽样,训练集和测试集在每个分类中进行抽取,然后进行整合。回归问题中,结果数据可以被人为的分为四分位数,然后进行四次不同的分层抽样。这样可以保证在训练集和测试集之间的分布相似。Ames数据的销售价格分布如图:
在这里插入图片描述
如前面讨论的,该销售价格是向右倾斜的。在此进行简单分割可能产生的担忧是,较为昂贵的房屋在训练集中不会得到很好的代表,这可能会增加模型在预测此类房产价格时的无效的风险。上图虚线把这些数据分为四个四分位数,分层随机样本在每个数据子集中进行80/20的分割,然后汇总结果。rsample中是使用strata参数实现的:

set.seed(502)
ames_split <- initial_split(ames, prop = 0.80, strata = Sale_Price)
ames_train <- training(ames_split)
ames_test <- testing(ames_split)

dim(ames_train)
#> [1] 2342   74

是否存在随机抽样不是最佳选择的情况呢?有的,一种情况是数据具有重要的时间成分,例如时间序列数据。这里,更常见的是使用最新的数据作为测试集。rsample包含一个initial_time_split()的函数,该函数与initial_split()类似。与随机抽样不同,属性参数prop表示将第一部分数据的多大比例用于训练集,该函数假定数据已经按照适当的顺序进行了预排序。

拆分数据的比例取决于具体问题。训练集数据太少会阻碍模型找到合适的参数估计的能力。相反测试集数据太多会降低性能估计的质量。统计界的一些人一般会避开测试集,因为他们认为所有数据都应该用于参数估计。虽然这一论点有可取之处,但将一组不偏不倚的观察结果作为模型质量的最终仲裁者是很好的建模实践。只有在数据非常小的情况下,才应该避免使用测试集。

2.2 验证集

描述拆分数据目标时,我们挑选测试集用于正确评估最终模型的性能,那么,如果在测试集之前想要测量性能,要怎么办?

经常听到的答案时使用验证集,特别是在神经网络和深度学习的文献中。在神经网络的早期,研究者就意识到通过重新预测训练集样本来衡量性能会导致过于乐观的结果(significantly,不切实际)。这导致了模型的过度拟合,这意味着它们在训练集表现很好但在测试集表现不佳。为了解决这个问题,一个小的验证数据集被保留下来,用于网络训练时测量性能。一旦验证集错误率开始上升,训练将停止。换句话说,验证集是一种在使用测试集之前粗略了解模型表现的方法。

验证集取自训练集的重采样的情况将在后续内容讨论。如果使用验证集,可以从不同拆分功能开始:

set.seed(52)
# 将60%作为训练,20%作为验证,20%作为测试:
ames_val_split <- initial_validation_split(ames, prop = c(0.6, 0.2))
ames_val_split
#> <Training/Validation/Testing/Total>
#> <1758/586/586/2930>

使用各数据集:

ames_train <- training(ames_val_split)
ames_test <- testing(ames_val_split)
ames_validation <- validation(ames_val_split)

2.3 多层次数据

从统计学上讲,一个属性的数据是独立于其他属性的,这样的多层次数据种进行数据拆分应该在数据的独立试验单位级别进行。

2.4 其他需考虑问题

首先,要将测试集与任何建模活动隔离开来。其次,对训练集进行二次采样可以缓解特定问题,比如可以让分类更加均衡,测试集会更加全面反映模型在测试集外遇到的情况。还有,不要对不同的任务使用相同的数据。

三、parsnip用于拟合模型

parsnip数据包是tidymodels元包中的R包之一。具体来讲,我们将重点介绍fit()predict()

3.1 创建模型

一旦数据被编码成为准备用于建模算法的格式,例如数字矩阵,它们就可以在模型构建过程中使用。

假设线性回归模型是我们最初的选择。这就指定了结果数据是numeric的且预测因素以简单的斜率和截距与结果相关:

y i = β 0 + β 1 + x 1 i + ⋯ + β p x p i y_{i}=\beta _{0}+\beta _{1}+x_{1i}+\dots+\beta _{p}x_{pi} yi=β0+β1+x1i++βpxpi

可以采用多种方法来估计模型参数:

  • 普通线性回归采用传统的最小二乘法来求解模型参数

  • 正则化线性回归在最小二乘法的基础上增加了惩罚项,以鼓励模型简化,减少预测变量或将它们的系数缩小至零。这可以通过贝叶斯或非贝叶斯的技术来实现。

补充说明:在机器学习和统计建模中,惩罚项是指在损失函数中添加的一项,用于对模型的复杂度进行惩罚或控制。它可以帮助防止过拟合(overfitting)或者在模型中选择重要的特征,促使模型更加简单和泛化能力更强。惩罚项通常是正则化方法(通过添加惩罚/控制项限制模型参数的大小和数量)的一部分,常见的有L1范数和L2范数,分别对应Lasso和Ridge回归中的惩罚项。L1正则化(Lasso):在损失函数中增加模型参数的L1范数(参数的绝对值之和)作为惩罚项。这种方法倾向于使得部分参数变为零,因此可以用于特征选择。L2正则化(Ridge):在损失函数中增加模型参数的L2范数(参数的平方和)作为惩罚项。这种方法倾向于让参数的值变小但不一定为零,有助于减少参数的大小。通过调整正则化项的权重,可以控制模型对复杂性的敏感程度。这些方法在线性回归、逻辑回归和其他机器学习模型中广泛应用,有助于提高模型的泛化能力并避免过度拟合训练数据。

R中,stats包可以用于情况1,使用lm()函数的线性回归语法是:

model <- lm(formula, data, ...)

为了使用正则化进行估计,情况可以使用rstanarm包进行拟合Bayesian模型:

model <- stan_glm(formula, data, family = "gaussian", ...)

他和lm()一样只有formula项可用。

有种流行的非贝叶斯正则化回归方法是glmnet模型:

model <- glmnet(x = matrix, y = vector, family = "gussian", ...)

注意,这些函数接口再数据传给模型函数或以其参数方面是异质的。问题一在于,需要拟合不同的包装的模型,必须以不同的方式对数据进行格式化。问题二在于,不同函数接口不同。对于试图进行数据分析的人,需要记住每个软件包的语法,这会非常令人沮丧。

对于tidymodels,指定模型的方法旨在更加统一:

  1. 根据其数学结构(如线性回归、随机森林、KNN等)指定模型的类型。

  2. 指定用于拟合模型的工程。大多数情况下,这反映的是应该使用的软件包,如Stanglmnet。这些模型本身就是模型,parsnip通过将他们用于建模引擎来提供一致的接口。

  3. 如果需要,要声明模型的模式。该模式反应了预测结果的类型。对于数字结果,模式是回归(regression);对于定性结果,模式是分类(classifiction)。如果模型算法只能解决一种类型的预测结果,例如线性回归,则已经设置了模式。

这些规范是在不参考数据的情况下构建的。例如,对于我们概述的三个案例:

linear_reg() |> set_engine("lm")
#> Linear Regression Model Specification (regression)
#> 
#> Computational engine: lm

linear_reg() |> set_engine("glmnet")
#> Linear Regression Model Specification (regression)
#> 
#> Computational engine: glmnet

linear_reg() |> set_engine("stan")
#> Linear Regression Model Specification (regression)
#> 
#> Computational engine: stan

一旦制定了模型的详细信息,就可以使用fit()函数(使用formula)或者fit_xy()函数(当数据已经预处理)进行模型估计。parsnip包让用户不考虑底层接口,即使建模包只有x/y接口,也可以使用formula。

translate()函数可以展示出parsnip代码把用户代码转换为各种包的语法的详情:

linear_reg() |> set_engine("lm") |> translate()
#> Linear Regression Model Specification (regression)
#> 
#> Computational engine: lm 
#> 
#> Model fit template:
#> stats::lm(formula = missing_arg(), data = missing_arg(), weights = missing_arg())

linear_reg(penalty = 1) |> set_engine("glmnet") |> translate()
#> Linear Regression Model Specification (regression)
#> 
#> Main Arguments:
#>   penalty = 1
#> 
#> Computational engine: glmnet 
#> 
#> Model fit template:
#> glmnet::glmnet(x = missing_arg(), y = missing_arg(), weights = missing_arg(), 
#>     family = "gaussian")

linear_reg() |> set_engine("stan") |> translate()
#> Linear Regression Model Specification (regression)
#> 
#> Computational engine: stan 
#> 
#> Model fit template:
#> rstanarm::stan_glm(formula = missing_arg(), data = missing_arg(), 
#>     weights = missing_arg(), family = stats::gaussian, refresh = 0)

说明:我们为glmnet提供了penalty参数。此外,对于stanglmnet引擎,family参数被自动添加为默认参数,后续可以更改。

下面举个例子,让我们看看如何仅根据经纬度来预测Ames数据中的房屋销售价:

lm_model <- 
  linear_reg() |> 
  set_engine("lm")

lm_form_fit <- 
  lm_model |> 
  fit(Sale_Price ~ Longitude + Latitude, data = ames_train)

lm_xy_fit <- 
  lm_model |> 
  fit_xy(
    x = ames_train |> select(Longitude, Latitude),
    y = ames_train |> pull(Sale_Price)
  )

lm_form_fit
#> parsnip model object
#> 
#> 
#> Call:
#> stats::lm(formula = Sale_Price ~ Longitude + Latitude, data = data)
#> 
#> Coefficients:
#> (Intercept)    Longitude     Latitude  
#>    -313.623       -2.074        2.965
lm_xy_fit
#> parsnip model object
#> 
#> 
#> Call:
#> stats::lm(formula = ..y ~ ., data = data)
#> 
#> Coefficients:
#> (Intercept)    Longitude     Latitude  
#>    -313.623       -2.074        2.965

parsnip不仅为不同的包提供了一致的模型接口,而且提供了模型参数的一致性。适合同一模型的不同函数具有不同参数名称是很常见的。随机森林就是一个很好的例子。三个常用参数是集合中树的数量、树内每个分裂时的随机采样的预测值的数目以及进行分裂所需的数据点的数目。对于不同的R包,参数如下表所示:

参数类型rangerrandomForestsparklyr
#预测值采样mtrymtryfeature_subset_stratey
#树num.treesntreenum_trees
#分裂数据点min.node.sizenodesizemin_instances_per_node

为减少参数指定的麻烦,parsnip在包内与包之间使用通用的参数,下表展示了随机森林中的参数使用:

参数类型parsnip
#预测值采样mtry
#树trees
#分裂数据点min_n

parsnip将一些原始参数名进行了更改。比如有些原始参数名称可能会很专业,比如为了指定glmnet模型中的正则化量,使用了希腊字母lambda,对于许多人来说这个lambda代表了什么并不明显,由于其代表的是正则化中使用的惩罚(penalty)所以parsnip将其重命名为了penalty。类似地,KNN模型中的邻居的数量称为kparsnip将其改回了neighbors。其大致命名规则就是如此人性化。

为了理解parsnip参数名称是如何映射到原始名称的,可以使用模型帮助文件(如?rand_forest)以及使用translate()函数:

rand_forest(trees = 1000, min_n = 5) |> 
  set_engine("ranger") |> 
  set_mode("regression") |> 
  translate()
#> Random Forest Model Specification (regression)
#> 
#> Main Arguments:
#>   trees = 1000
#>   min_n = 5
#> 
#> Computational engine: ranger 
#> 
#> Model fit template:
#> ranger::ranger(x = missing_arg(), y = missing_arg(), weights = missing_arg(), 
#>     num.trees = 1000, min.node.size = min_rows(~5, x), num.threads = 1, 
#>     verbose = FALSE, seed = sample.int(10^5, 1))

parsnip中建模函数将模型参数分为了两类:

  • 主参数比较常用,往往可以跨引擎使用。

  • 引擎参数要么用于特定的引擎,要么很少使用。

比如前面的随机森林代码的转换中,默认情况添加了参数num.threadsverboseseed。这些参数特定于随机森林模型中,并不是主要参数。可以在set_engine()中指定特定于引擎的参数。例如,要让ranger::ranger()函数打印出有关配合的详细信息:

rand_forest(trees = 1000, min_n = 5) |> 
  set_engine("ranger", verbose = TRUE) |> 
  set_mode("regression")
#> Random Forest Model Specification (regression)
#> 
#> Main Arguments:
#>   trees = 1000
#>   min_n = 5
#> 
#> Engine-Specific Arguments:
#>   verbose = TRUE
#> 
#> Computational engine: ranger

3.2 使用模型结果

创建拟合了模型后,我们就可以从各种方式使用结果;我们可能想要绘制、打印或以其他方式检查模型输出。几个量存储在parsnip模型对象中,包括拟合的模型。这可以在一个名为fit的元素中找到,它可以使用extract_fit_engine()函数返回:

lm_form_fit |> extract_fit_engine()
#> 
#> Call:
#> stats::lm(formula = Sale_Price ~ Longitude + Latitude, data = data)
#> 
#> Coefficients:
#> (Intercept)    Longitude     Latitude  
#>    -313.623       -2.074        2.965

可以对此对象应用常规方法如打印或绘图:

lm_form_fit |> extract_fit_engine() |> vcov()
#>             (Intercept)    Longitude     Latitude
#> (Intercept)  273.852441  2.052444651 -1.942540743
#> Longitude      2.052445  0.021122353 -0.001771692
#> Latitude      -1.942541 -0.001771692  0.042265807

Base R中一些现有方法的一个问题是,结果的存储方法可能不是最有用的。比如,lm对象的summary()方法可以用于打印模型拟合的结果,其中包含参数值、它们的不确定性估计和p值。还可以保存这些特定结果:

model_res <- 
  lm_form_fit |> 
  extract_fit_engine() |> 
  summary()

# 模型系数表可以通过`coef`方法访问
param_est <- coef(model_res)
class(param_est)
#> [1] "matrix" "array"
param_est
#>                Estimate Std. Error   t value     Pr(>|t|)
#> (Intercept) -313.622655 16.5484876 -18.95174 5.089063e-73
#> Longitude     -2.073783  0.1453353 -14.26896 8.697331e-44
#> Latitude       2.965370  0.2055865  14.42395 1.177304e-44

这一结果中,需要注意几点。首先,对象是一个数字矩阵。选择该数据结构很可能是因为所有的计算结果都是数值的,并且矩阵对象的存储效率比数据框更高。这一选择可能是在20世纪70年代末做出的,当时的计算效率非常的关键。其次,非数字数据包含在了行名中,这种行为非常复合原始S语言中的约定。

合理的下一步操作可能是创建参数值的可视化。要做到这一点,将参数矩阵转换为数据框是需要的。我们可以将行名作为列添加,以便可以在绘图中使用它们。然而,需要注意的是,一些现有的矩阵列名不是普通数据框的有效R列名(如"Pr(>|t|)")。另一个复杂的问题是列名的一致性。比如对于lm对象来说,p值的列名是"Pr(>|t|)"但是对于其他模型,可能就不是了(“Pr(>|z|)”)。

作为一种解决方案,broom包可以将许多类型的模型对象转换为整洁的结构。例如,使用tidy()

tidy(lm_form_fit)
#> # A tibble: 3 × 5
#>   term        estimate std.error statistic  p.value
#>   <chr>          <dbl>     <dbl>     <dbl>    <dbl>
#> 1 (Intercept)  -314.      16.5       -19.0 5.09e-73
#> 2 Longitude      -2.07     0.145     -14.3 8.70e-44
#> 3 Latitude        2.97     0.206      14.4 1.18e-44

3.3 做出预测

另一个parsnip与传统R建模函数不同的方面是predict()返回值的格式。对于预测来说,parsnip始终遵循以下规则:

  1. 结果是一个tibble

  2. tibble的列名是可预测的

  3. tibble的行数始终与输入数据集中的行数相同

比如:

ames_test_samll <- ames_test |> slice(1:5)
predict(lm_form_fit, new_data = ames_test_samll)
#> # A tibble: 5 × 1
#>   .pred
#>   <dbl>
#> 1  5.23
#> 2  5.29
#> 3  5.28
#> 4  5.27
#> 5  5.27

预测的行序始终与原始数据相同。

这三条规则使预测与原始数据的合并变得更加容易:

ames_test_samll |> 
  select(Sale_Price) |> 
  bind_cols(predict(lm_form_fit, ames_test_samll)) |> 
  bind_cols(predict(lm_form_fit, ames_test_samll, type = "pred_int"))
#> # A tibble: 5 × 4
#>   Sale_Price .pred .pred_lower .pred_upper
#>        <dbl> <dbl>       <dbl>       <dbl>
#> 1       5.33  5.23        4.92        5.54
#> 2       5.29  5.29        4.98        5.60
#> 3       5.25  5.28        4.97        5.59
#> 4       5.27  5.27        4.97        5.58
#> 5       5.33  5.27        4.96        5.58

规则2下的tidymodels,不同类型的预测的可预测名如下表所示:

type valuecolumn names
numeric.pred
class.pred_class
prob.pred_{class levels}
conf_int.pred_lower, .pred_upper
pred_int.pred_lower, .pred_upper

这些规则使得数据分析过程和语法同质化,使用户可以把时间花费在结果上,不必关注R包之间的语法差异。

3.4 parsnip扩展包

parsnip包本身包含许多模型的接口。然而,为了便于软件包的安装与维护,还有其他的tidymodels包,为其他模型提供了parsnip包的模型定义。discrim包有一组分类技术的模型定义,称为判别分析方法。通过这种方式减少了安装parsnip包所需要的包依赖性。可以在http://www.tidymodels.org/find上查找可以与parsnip一起使用的模型的列表。

3.5 创建模型的型号与规则快捷

记住许多模型的编写规则可能比较枯燥乏味。parsnip提供了一个RStudio addin帮助:

parsnip_addin()

这将会在RStudio IDE查看面板打开一个窗口,其中包含了每个模型模式的可能列表,可以直接写入源代码面板。

总结

至此,我们学习了tidymodels包中进行数学建模的基础做法。包括了如何对数据集进行拆分以用于模型拟合,数据可以根据需要拆分为训练集、测试集和验证集。在数据拆分的时候可以根据数据结构的不同,对数据进行不同层次的划分。当进行数学建模时,通过parsnip包可以使得建模过程更加的同质化,这就是tidymodels的优势,它可以让用户更加关注建模的结果,而不是把心思浪费在学习如何使用建模函数的过程上。

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

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

相关文章

腾讯云优惠券领取的三个渠道,先领券再下单!

腾讯云代金券领取渠道有哪些&#xff1f;腾讯云官网可以领取、官方媒体账号可以领取代金券、完成任务可以领取代金券&#xff0c;大家也可以在腾讯云百科蹲守代金券&#xff0c;因为腾讯云代金券领取渠道比较分散&#xff0c;腾讯云百科txybk.com专注汇总优惠代金券领取页面&am…

禁止safari浏览器网页双击缩放功能

普通浏览器 普通浏览器&#xff0c;只需要增加meta标签禁止缩放功能就行了 <meta content"widthdevice-width, initial-scale1.0, maximum-scale1.0, user-scalable0;" name"viewport" /> user-scalableno或0 //禁止双指缩放页面initial-scale1.0…

企业文件图纸加密有哪些?图纸文件加密防泄密软件如何选?

在现在的市场发展中&#xff0c;对于企业的图纸文件安全问题越来越重视&#xff0c;如设计图纸&#xff0c;重要文件等&#xff0c;一旦泄漏就会给企业造成巨大的经济损失。所以对企业管理者来讲&#xff0c;如何才能选择一款好用的适合本企业的图纸文件加密软件是非常重要的&a…

【C++】手把手教你手搓模拟实现string类

前言 string类一直都是C的经典问题&#xff0c;之前的文章已经对string类做了一个基本的介绍&#xff08;string类的基本常用接口&#xff09;&#xff0c;为了更好理解string类的功能&#xff0c;此篇文章将手把手教你带你手搓模拟实现string类&#xff0c;快来一起学习吧&am…

Spring 事务传播机制

事务传播机制&#xff1a;多个事务⽅法存在调⽤关系时, 事务是如何在这些⽅法间进⾏传播的。 ⽐如&#xff1a;有两个⽅法A&#xff0c;B都被 Transactional 修饰,&#xff0c;A⽅法调⽤B⽅法 A⽅法运⾏时, 会开启⼀个事务。当A调⽤B时&#xff0c; B⽅法本⾝也有事务&#xf…

Pod 进阶

目录 资源限制 健康检查 资源限制 当定义 Pod 时可以选择性地为每个容器设定所需要的资源数量。 最常见的可设定资源是 CPU 和内存大小&#xff0c;以及其他类型的资源。 当为 Pod 中的容器指定了 request 资源时&#xff0c;代表容器运行所需的最小资源量&#xff0c;调度器…

打造去中心化透明储蓄罐:Solidity智能合约的又一实践

一、案例背景 传统的储蓄罐通常是由个人或家庭使用&#xff0c;用于存放硬币或小额纸币。然而&#xff0c;这样的储蓄罐缺乏透明性&#xff0c;用户无法实时了解储蓄情况&#xff0c;也无法确保资金的安全性。 通过Solidity智能合约&#xff0c;我们可以构建一个去中心化…

外汇天眼:外汇市场有哪些功能?

外汇市场有三个方面的功能&#xff0c;一是实现购买力的国际转移&#xff0c;二是提供资金融通&#xff0c;三是提供外汇保值和投机的市场机制。 实现购买力的国际转移 国际贸易和国际资金融通至少涉及到两种货币&#xff0c;而不同的货币对不同的国家形成购买力&#xff0c;…

02| JVM堆中垃圾回收的大致过程

如果一直在创建对象&#xff0c;堆中年轻代中Eden区会逐渐放满&#xff0c;如果Eden放满&#xff0c;会触发minor GC回收&#xff0c;创建对象的时GC Roots&#xff0c;如果存在于里面的对象&#xff0c;则被视为非垃圾对象&#xff0c;不会被此次gc回收&#xff0c;就会被移入…

逆向案例一:AES解密基于数位观察城市数据

import requests import json from Crypto.Cipher import AES # 开始解密 from Crypto.Util.Padding import unpad #去填充的逻辑 import base64 url https://app.swguancha.com/client/v1/cPublic/consumer/baseInfo data {current: 1,"dimensionTime": "20…

数据结构--B树

B树的预备知识 我们平常查找比如用搜索二叉树哈希表这些数据结构&#xff0c;一般都是数据在内存中的&#xff0c;这样的话访问数据的速度就很快&#xff0c;这种查找也叫做内查找。二分查找或者直接顺序查找也适合内查找。 但是当数据量非常大时&#xff0c;比如有100G的数据…

redis启动错误

错误&#xff1a; Creating Server TCP listening socket 127.0.0.1:6379: bind: No error redis-server.exe redis.windows.conf redis-cli.exe shutdown auth "yourpassword"

【kubernetes】关于云原生之k8s集群中pod的容器资源限制和三种探针

目录 一、关于pod容器的资源限制 1.1资源限制的单位 CPU 资源单位 内存 资源单位 二、关于QOS服务质量&#xff08;pod的调度和驱逐有限制&#xff09; 2.1QoS服务质量分类 guaranteed验证 burstable验证 besteffort验证 2.2驱逐顺序 三、关于pod容器的三种探针 3.…

mobile app 安全扫描工具MobSF了解下

可以干啥&#xff1a; static 静态分析 dynamic 动态分析 可以用来渗透了 如何docker安装 docker image 下载地址https://hub.docker.com/r/opensecurity/mobile-security-framework-mobsf/ setup 两行即可 1 docker pull opensecurity/mobile-security-framework-mobsf…

Python:练习:编写一个程序,录入一个美元数量(int),然后显示出增加%5税率后的相应金额。

案例&#xff1a; 编写一个程序&#xff0c;录入一个美元数量&#xff08;int&#xff09;&#xff0c;然后显示出增加%5税率后的相应金额。格式如下所示&#xff1a; Enter an amount:100 With tax added:$105.0 思考&#xff1a; 1、录入一个美元数量&#xff0c;录入&am…

[Flutter]TextButton自定义样式

在Flutter中&#xff0c;TextButton是一个简单的文本按钮&#xff0c;它遵循当前的Theme。如果你想要修改TextButton的样式&#xff0c;可以通过设置其style属性来实现&#xff0c;该属性接收一个ButtonStyle对象。 给TextButton和里面Text添加背景后&#xff0c;效果如下。可…

【软件测试】接口调不通排查分析+常遇面试题总结

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、接口调不通&am…

【MySQL】MySQL数据管理——DDL数据操作语言(数据表)

目录 创建数据表语法列类型字段属性SQL示例创建学生表 查看表和查看表的定义表类型设置表的类型 面试题&#xff1a;MyISAM和InnoDB的区别设置表的字符集删除表语法示例 修改表修改表名语法示例 添加字段语法示例 修改字段语法示例 删除字段语法示例 数据完整性实体完整性域完整…

petalinux烧写image.ub报错

xinlinx SDK烧写petalinux生成的BOOT.BIN和image.ub时&#xff0c;BOOT.BIN烧写正常&#xff0c;image.ub烧写报错如下 Erase Operation failed. INFO: [Xicom 50-44] Elapsed time 0 sec.ERROR: Flash Operation Failed串口助手操作擦除flash如图&#xff1a; 解决方法&am…

JVM内存分配与垃圾收集流程

3.8 实战&#xff1a;内存分配与回收策略 3.8.1 对象优先在Eden分配 大多数情况下&#xff0c;对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时&#xff0c;虚拟机将发起一次Minor GC。 3.8.2 大对象直接进入老年代 HotSpot虚拟机提供了-XX&#xff1a;Prete…