一、基础部分

//@PASS,遇到有不会的再写,直接上手实战

二、信用卡欺诈检测实战 —— 监督学习

背景: 信用卡欺诈是指故意使用伪造、作废的信用卡,冒用他人的信用卡骗取财物,或用本人信用卡进行恶意透支的行为,见《信用卡欺诈》 - 百度百科,那么有信用卡欺诈,有没有储蓄卡欺诈呢?有的,比如储蓄卡被他人冒领。

2.1 下采样与过采样

这个项目里的样本数据,异常样本非常少,这时,正常样本和异常样本的比例是不平衡的,正常数据多不是件好事吗?从采集的角度上看是的,但从模型建立角度上看不是,因为这样会导致模型认为数据都很好,就无法因对异常情况,那么只要让正常和异常数据的比例接近即可,方法是下采样和过采样。
(1) 下采样。让正常样本变少一些,可以变得和异常样本数量一样多。
(2) 过采样。造一些异常样本,让异常样本变多一些,这个造的方法是SMOTE数据生成策略。

2.1.1 过采样数据生成策略SMOTE

SMOTE = Synthetic Minority Over-sampling Technique,即合成少数类过采样技术,少数类指的是样本数量较少的类别,且合成的数据不能是一模一样的,这个过程用到了KNN聚类算法,计算每个少数类样本的K近邻,在这个实战项目里就是异常类,此算法过程如下:
(1)对于每一个少数类样本,计算其与所有其他少数类样本之间的距离,并找到其K个最近邻居,这个K被称作采样倍率,K越大,挑选的邻居越多。
(2)从这K个最近邻居中随机选择一个样本,并计算该样本与当前样本的差异。
(3)根据差异比例,生成一个新的合成样本,该样本位于两个样本之间的连线上。合成新样本的计算公式是, x x x是自身样本数据, x ~ \tilde{x} x~是邻居的样本数据
x n e w = x + r a n d ( 0 , 1 ) ∗ ( x ~ − x ) x_{new} = x + rand(0,1)*(\tilde{x} - x) xnew=x+rand(0,1)(x~x)
写成下面的形式也是一样的,随着比例在0到1之间浮动,新生成的数据也在 x ~ \tilde{x} x~ x x x 之间浮动。
x n e w = x ~ + r a n d ( 0 , 1 ) ∗ ( x − x ~ ) x_{new} = \tilde{x} + rand(0,1)*(x - \tilde{x}) xnew=x~+rand(0,1)(xx~)
我觉得下面这种形式应该看起来更舒服,但损失了几何直观性
x n e w = a x + b x ~ , ( a + b = 1 , a > 0 , b > 0 ) x_{new} = ax + b\tilde{x}, \quad (a+b=1, a > 0, b > 0) xnew=ax+bx~,(a+b=1,a>0,b>0)

(4)重复上述步骤,生成指定数量的合成样本。

《解决样本不均衡问题:SMOTE过采样详解 - FAL金科应用研究院的文章 - 知乎》

2.2 逻辑回归

Logistic Regression (aka logit, MaxEnt) classifier.逻辑回归(又称为logit,最大熵分类器)

关键代码部分

# 指定算法模型,并且给定参数
lr = LogisticRegression(C = c_param, penalty = 'l1', solver='liblinear')
# 训练模型,注意索引不要给错了,训练的时候一定传入的是训练集,所以X和Y的索引都是0
lr.fit(x_train_data.iloc[indices[0],:],y_train_data.iloc[indices[0],:].values.ravel())

第一行代码新建了一个分类器lr
(1) C_param。惩罚力度,在sklearn包中,这个值越小,代表惩罚力度越大,值越大,惩罚力度越小,源代码的文档里是这么解释这个参数的:C: Inverse of regularization strength,正则化强度的反比。
(2) penalty = 'l1'。代表正则化惩罚项是 J = J 0 + α ∑ w ∣ w ∣ J = J_0 + \alpha \sum\limits_{w}{|w|} J=J0+αww
(3) solver='liblinear'。源代码文档里的描述是Solver: Algorithm to use in the optimization problem,即solver是一种使用在优化问题里的算法,短期内我不追求看懂什么是liblinear,liblinear论文是08年发的,所以教科书是肯定绝对没提过这些东西的,我只想知道liblinear究竟是用来做什么的,Scikit-learn solvers explained 这篇文章对solver的这些算法做了解释,我完全没有看懂,简要总结了两种

solver含义讲解或参考论文
newton-cgcg = conjugate gradient 牛顿-共轭梯度法【数值分析6(3共轭梯度法)苏州大学】
liblinear大型线性分类库。
liblinear是一个用于解决线性分类和线性回归问题的开源软件库。它通过使用支持向量机(SVM)等算法来执行二元分类、多元分类和回归任务。liblinear支持L1和L2正则化,并能够处理高维特征。它被广泛应用于文本分类、生物信息学、图像分类和人脸识别等领域。—— 大模型的回答
1.LIBLINEAR: A Library for Large Linear Classification.pdf
2.LIBSVM - Wikipedia
3.LIBLINEAR算法解读,想看懂重点读
4.liblinear使用总结

继续看第二行,有了这个分类器lr后,开始对训练集进行fit拟合操作,y_train_data.iloc[indices[0],:].values.ravel()的意思是,将 Pandas DataFrame 转换为 NumPy 数组并展平,这是一个ravel()操作的demo:

df = pd.DataFrame({'num_legs': [2, 4, 4, 6],
                   'num_wings': [2, 0, 0, 0]})
print('example1:\n',df.iloc[df.index, :]) # better than two methods above.
print('example2:\n',df.iloc[df.index, :].values.ravel()) # better than two methods above.

# example1:
#     num_legs  num_wings
# 0         2          2
# 1         4          0
# 2         4          0
# 3         6          0
# example2:
#  [2 2 4 0 4 0 6 0]

2.3 混淆矩阵

代码调用部分,sklearn.metrics.confusion_matrix

# sklearn.metrics.confusion_matrix(真实标签值,预测标签值)
cnf_matrix = confusion_matrix(y_test_undersample,y_pred_undersample)
真实情况 \ 预测结果正例反例
正例PositiveTP(真正例)FN(假正例)此行可定义查全率 R = T P T P + F N R = \frac{TP}{TP+FN} R=TP+FNTP
反例NegativeFP(假反例)TN(真反例)
此列可定义查准率 P = T P T P + F P P=\frac{TP}{TP+FP} P=TP+FPTP

查准率:真实值里有多少是预测对的,所以真实值做分母,这个指标更关注你预测的准不准,如果你是三体人的巫师,你可以少说话,追求查准率,否则就要被脱水了。
查全率:预测值里有多少是确实真的,所以预测值做分母,这个指标更关注你找到的多不多,如果你是地球人的医生,那请你多查房,追求查全率,否则病人就要蔫了。

看看下面这个例子,假设一个人积极揽活并且干活,身兼销售和技术双职位,销售拿下拿不下项目,技术完成完不成项目,但假设一个人的精力和客户关系都有限,那么要么项目拿下的多成功的少,要么拿下的少成功的多,这样收益也反而均衡,一鸣惊人和积少成多都有的,不多,拿下一个大项目和拿下多个小项目可以认为是等价的。

人员风格 \ 项目目标大项目Big小项目Small
一鸣惊人TB(完成·大)FS(失败·小)此行可定义项目拿下率 P = T B T B + F S P=\frac{TB}{TB+FS} P=TB+FSTB
积小成多FB(失败·大)TS(完成·小)同上,可定义项目拿下率
此列可定义大项目成功率 R = T B T B + F B R = \frac{TB}{TB+FB} R=TB+FBTB同左,可定义小项目成功率

这里的拿下率,就可以类比为上面的查全率,关注的是项目拿下的多不多
这里的成功率,就可以类比为上面的查准率,关注的是项目完成的多不多

下面,我们来做一个对现实职场的探讨,我觉得大可以将这类情况往人际交往、情场、官场和其它地方延伸,去做一个验证,看下是不是在中国的社会环境里适用,国外也可以拿去套一套。
好员工”是做的多    又做的好,这类员工,可能会拿一些回扣,但领导对其带来的收益还是满意的,所以睁一只眼,闭一只眼。
清员工”是做的少所以做的好,在有些领导看来,虽然做的事情少,但每件事都狠抓落实,并且到位有成效,所以印象颇为不错。
差员工”是做的多所以难免犯错,这类员工虽然做的事情很多,也有很多事情有成效,但做的事多难免也踩的雷多,犯下的错多,虽然给公司带来了很多收益,也造成了不小的损失,时间一长,就容易给领导留下不好的印象,这种人在某些情况下容易被边缘化,甚至是开除。
平员工”是做的少却也会犯点小错,但在领导的印象里,这种人往往可能还要比上面那类“差员工”还要印象好些,毕竟犯错不多也犯不了大错,带来的损失不大,但要是论提拔却也谈不上,毕竟做的事不多。

2.4 过采样实战

代码运行结果表示,下采样导致了许多样本被误杀,这种宁肯错杀一千,不肯放过一个的态度,在保卫人民财产的行动中固然值得尊敬,但给整个系统的负担太过繁重,并且在实际操作中,会浪费人力物力关注到实际没有问题的卡上,导致真正应该被重点关注的异常信用卡记录,可能没有及时被注意到,从而加剧了信用卡诈骗现象。上面已经提到了SMOTE生成数据的原理,下面是实操。

调用imblearn工具包使用SMOTE算法,没装可以用pip install imblearn安装下,关键代码含义

# 使用SMOTE算法来进行生成负例样本,random_state是随机数种子,作用是控制每次随机生成的结果一致
oversampler=SMOTE(random_state=0)
# 将特征数据和标签传入,得到oversample_features,oversample_labels
os_features,os_labels=oversampler.fit_resample(features_train,labels_train)

imblearn.over_sampling.SMOTE

2.5 实战总结

实战部分到此结束,总结下机器学习问题的处理过程,我们的妈妈才是大师。
(1) 分析问题,检查数据。(今天想吃什么菜,挑菜选菜)
(2) 清洗数据,数据预处理。(洗菜,切菜)
(3) 选择建模方法,进行建模。(蒸炸煎煮焖溜熬炖炒,下锅)
(4) 调参。(油盐酱醋放多少)
(5) 分析建模效果。(今天味道怎么样,吃的好不好,吃的饱不饱)

2.6 版本依赖排错

本项目的所有版本依赖错误的解决方法都在这:《机器学习 信用卡欺诈检测 程序迭代错误》

三、决策树

指标

信息熵与基尼(Gini)系数

信息熵(information entropy),物理原理,熵越小,系统越有序,此处指熵越小,分类所包含的结点纯度越高, D D D代表当前样本,随着节点越划越多,层数越划越深, D D D代表的当前样本也是变化的,应该越变越少。
E n t ( D ) = − ∑ k = 1 ∣ y ∣ p k ⋅ l o g 2 p k Ent(D) = - \sum_{k=1}^{|y|}{p_k·log_2p_k} Ent(D)=k=1ypklog2pk
有个两点分布的熵模型,概率对半开时,熵值最大:【十分钟 机器学习 系列课程】讲义(32):最大熵原理 - 简博士的文章 - 知乎,根据常识,打比赛时有两只实力接近的队伍,你会纠结不知道下哪边的注,这个时候脑子里的熵值根据此公式就在剧增,所以,国足熵值接近0?因为不确定性过低。
基尼(Gini)系数和信息熵的作用类似
G i n i ( p ) = ∑ k = 1 K p k ( 1 − p k ) = 1 − ∑ k = 1 K p k 2 Gini(p) = \sum_{k=1}^{K}{p_k(1-p_k)} = 1 - \sum_{k=1}^{K}{p_k^2} Gini(p)=k=1Kpk(1pk)=1k=1Kpk2

信息增益

信息增益(information gain),使用属性 a , a ∈ { a 1 , a 2 , . . . , a V } a, a \in \{a^1, a^2, ..., a^V\} a,a{a1,a2,...,aV} D D D进行划分,那么就有 v v v个分支结点,记 D V D^V DV为:第 v v v个分支结点下, D D D 在属性 a a a上所有取值为 a v a^v av的样本。信息增益越大,纯度提升越大。 I D 3 ID3 ID3决策树学习算法,以信息增益为准则来选择划分属性
G a i n ( D , a ) = E n t ( D ) − ∑ v = 1 V ∣ D v ∣ ∣ D ∣ E n t ( D v ) Gain(D,a) = Ent(D) - \sum_{v=1}^{V}\frac{|D^v|}{|D|}{Ent(D^v)} Gain(D,a)=Ent(D)v=1VDDvEnt(Dv)
信息熵和信息增益交替计算,不断选择划分属性并生成节点,直到决策树生成完毕

信息增益率

信息增益的计算方式会对品类较多的属性 a a a有所偏好,比如说,当属性为id时,每一个样本的编号都是唯一的,以id为属性划分,一个节点里只有一个样本,这样计算得到的熵值为0,但没任何意义, C 4.5 C4.5 C4.5决策树算法使用增益率来选择最优的划分属性。
G a i n _ r a t i o ( D , a ) = G a i n ( D , a ) I V ( a ) I V ( a ) = − ∑ v = 1 V ∣ D v ∣ ∣ D ∣ l o g 2 ∣ D v ∣ ∣ D ∣ Gain\_ratio(D,a) = \frac{Gain(D,a)}{IV(a)} \\ IV(a) = -\sum_{v=1}^{V}{ \frac{|D^v|}{|D|}log_2{\frac{|D^v|}{|D|}} } Gain_ratio(D,a)=IV(a)Gain(D,a)IV(a)=v=1VDDvlog2DDv
I V ( a ) IV(a) IV(a)是属性 a a a的固有值,
信息增益和信息增益率交替计算,生成节点,直到决策树生成完毕。

连续值的划分

对于数据特征是连续的样本来说,还是只能手动切成不连续的处理,比如年龄、身高和体重,这三个数据都是连续的,要判断健康与否,也是把这三个数据切成一段一段。世界的底层构造也是如此,一堆基本粒子表现出了宏观世界的万千现象

预剪枝和后剪枝

预剪枝是在建立决策树的过程中,进行剪枝,比较方便,
后剪枝是在建立完决策树后,进行剪枝,欠拟合风险小,泛化性能往往优于预剪枝

四、集成算法

bagging、boosting、stacking

算法特点
bagging(boostrap aggregating)相同算法模型,并行集成对任务进行决策
random forest(随机森林)bagging算法的扩展,为了让决策树变得多样,为了实现这种多样新,要进行数据随机采样有差别的选取特征
boosting相同算法模型,串行集成对任务进行决策,即:在之前的基础上,不停的加入可以减小整体模型损失的决策树
stacking不同算法模型,并行集成对任务进行决策

五、随机森林项目实战—气温预测

三项任务:
(1) 使用随机森林方法完成基本建模任务。处理数据、观察特征、完成建模并进行可视化展示分析
(2) 观察数据量和特征个数,对结果的影响。保证算法一致的前提下,加大数据个数,观察结果变换。重新考虑特征工程,引入新特征后观察结果走势。
(3) 对随机森林方法调参,找到最合适的参数。掌握两种经典调参方法:随机搜索和网络搜索。其它的调参方法还有贝叶斯优化,参考Hyperopt(Hyper-parameter Optimization)工具包:Hyperopt Documentation

5.1 基本任务

5.1.1 one_hot编码

独热编码,调用示例如下

df = pd.DataFrame({'A': ['a', 'b', 'd'], 'B': ['b', 'a', 'c'], 'C': [1, 2, 3]})
print(df)
pd.get_dummies(df, prefix=['col1', 'col2'])

# 输出结果
#   A  B  C
# 0  a  b  1
# 1  b  a  2
# 2  d  c  3
# 	C	col1_a	col1_b	col1_d	col2_a	col2_b	col2_c
# 0	1	1	0	0	0	1	0
# 1	2	0	1	0	1	0	0
# 2	3	0	0	1	0	0	1

5.1.2 评估指标

公式表达式
MSE(Mean Square Error,均方误差) M S E = 1 n ∑ i = 1 n ( y i ^ − y i ) 2 MSE = \frac{1}{n}\sum_{i=1}^{n}{(\hat{y_i} - y_i)^2} MSE=n1i=1n(yi^yi)2
RMSE(Root Mean Square Error,均方根误差) R M S E = 1 n ∑ i = 1 n ( y i ^ − y i ) 2 RMSE =\sqrt{ \frac{1}{n}\sum_{i=1}^{n}{(\hat{y_i} - y_i)^2} } RMSE=n1i=1n(yi^yi)2
MAPE(Mean Absolute Percentage Error,平均绝对百分误差) M A P E = 100 % n ∑ i = 1 n ∣ y i ^ − y i ∣ ∣ y i ∣ MAPE = \frac{100\%}{n}\sum_{i=1}^{n}{\frac{|\hat{y_i} - y_i|}{|y_i|}} MAPE=n100%i=1nyiyi^yi

其它评估指标可参考此博文:《机器学习之回归模型预测性能评估指标(RMSE、MSE、MAE、MAPE、SMAPE、R^2 Score、R^2 )》
关键代码

# 导入random forest算法
from sklearn.ensemble import RandomForestRegressor
# 建模
rf = RandomForestRegressor(n_estimators= 1000, random_state=42) # random_state:在机器学习中,随机数生成器用于初始化模型参数、特征选择等过程。通过设置random_state,可以确保每次运行代码时,随机数生成器产生的随机数序列相同,从而使得结果具有可重复性。
# 训练
rf.fit(train_features, train_labels)
# 预测结果
predictions = rf.predict(test_features)
# 计算误差
errors = abs(predictions - test_labels)
# mean absolute percentage error (MAPE)
mape = 100 * (errors / test_labels)
print ('MAPE:',np.mean(mape)) # MAPE: 6.011244187972058

5.2 加大训练数据量

5.2.1 特征工程

书中写到“尽可能多地选择有价值的特征,因为初始阶段能得到的信息越多,建模时可以利用的信息也越多”,这里的数据集是和温度相关的,但这份数据集没有季节这个特征,所以手动生成季节特征。
本节总结:增大数据量和特征列数,都能对提高训练出来的模型的准确度,但是在预测会花更多的时间,所以可以根据特征重要性,选择几个较为重要的特征进行训练,比如:若这几个特征的重要程度相加,超过95%,那么就拿这几个特征来训练和预测,准确率下降不多的同时,能降低预测花费的时间。

5.3 调参

调参调参,现代炼丹。
随机随机,点石成金。

以下只列核心代码,完整代码请看书籍配套资源,先看下对于随机森林而言,有哪些参数可调

from sklearn.ensemble import RandomForestRegressor
rf = RandomForestRegressor(random_state = 42)
from pprint import pprint
# 打印所有参数
pprint(rf.get_params())

# 输出结果
# {'bootstrap': True,
#  'ccp_alpha': 0.0,
#  'criterion': 'mse',
#  'max_depth': None,
#  'max_features': 'auto',
#  'max_leaf_nodes': None,
#  'max_samples': None,
#  'min_impurity_decrease': 0.0,
#  'min_impurity_split': None,
#  'min_samples_leaf': 1,
#  'min_samples_split': 2,
#  'min_weight_fraction_leaf': 0.0,
#  'n_estimators': 100,
#  'n_jobs': None, # 用来搜寻最优参数的多线程的数量
#  'oob_score': False,
#  'random_state': 42,
#  'verbose': 0,
#  'warm_start': False}

随机搜索调参RandomizedSearchCV

然后用RandomizedSearchCV进行随机选择参数并执行fit,用来随机搜索最优参数

# 随机选择最合适的参数组合
rf = RandomForestRegressor()
rf_random = RandomizedSearchCV(estimator=rf, param_distributions=random_grid,
                              n_iter = 100, scoring='neg_mean_absolute_error', 
                              cv = 3, verbose=2, random_state=42, n_jobs=-1) #cv是cross validation交叉验证折数
# 执行寻找操作
rf_random.fit(train_features, train_labels)
rf_random.best_params_

全局搜索调参GridSearchCV

from sklearn.model_selection import GridSearchCV

# 网络搜索
param_grid = {
    'bootstrap': [True],
    'max_depth': [8,10,12],
    'max_features': ['auto'],
    'min_samples_leaf': [2,3, 4, 5,6],
    'min_samples_split': [3, 5, 7],
    'n_estimators': [800, 900, 1000, 1200]
}

# 选择基本算法模型
rf = RandomForestRegressor()

# 网络搜索
grid_search = GridSearchCV(estimator = rf, param_grid = param_grid, 
                           scoring = 'neg_mean_absolute_error', cv = 3, 
                           n_jobs = -1, verbose = 2)
# 执行搜索
grid_search.fit(train_features, train_labels)
grid_search.best_params_

另一组参数的全局搜索

param_grid = {
    'bootstrap': [True],
    'max_depth': [12, 15, None],
    'max_features': [3, 4,'auto'],
    'min_samples_leaf': [5, 6, 7],
    'min_samples_split': [7,10,13],
    'n_estimators': [900, 1000, 1200]
} # 这里与上一段代码不一样

# 选择算法模型
rf = RandomForestRegressor()

# 继续寻找
grid_search_ad = GridSearchCV(estimator = rf, param_grid = param_grid, 
                           scoring = 'neg_mean_absolute_error', cv = 3, 
                           n_jobs = -1, verbose = 2)

grid_search_ad.fit(train_features, train_labels)
grid_search.best_params_

总结,用RandomizedSearchCV随机搜索和GridSearchCV全局搜索,去找到最好的参数

六、特征工程

6.1 数值特征

将原本是字符串的特征列,转换成数值类型的特征列

转换操作1 —— pandas.DataFrame.fit_transform()

from sklearn.preprocessing import LabelEncoder

# Mapping each param into a digit.
gle = LabelEncoder()
genre_labels = gle.fit_transform(vg_df['Genre']) # fit_transform()是实际执行的操作,自动对属性特征进行映射操作
genre_mappings = {index: label for index, label in enumerate(gle.classes_)}
print(genre_mappings) # {0: 'Action', 1: 'Adventure', 2: 'Fighting', 3: 'Misc', 4: 'Platform', 5: 'Puzzle', 6: Racing', 7: 'Role-Playing', 8: 'Shooter', 9: 'Simulation', 10: 'Sports', 11: 'Strategy'}
vg_df['GenreLabel'] = genre_labels #这样就多出了一列数值类型的特征列

转换操作2 —— 手动map映射

poke_df = pd.read_csv('datasets/Pokemon.csv', encoding='utf-8')
poke_df.head()

# sample(random_state=1, frac=1)表示从poke_df中随机抽取一定比例的数据(这里比例为100%),即抽取整个DataFrame的所有数据。
# random_state=1是设置随机数生成器的种子,确保每次运行代码时都能得到相同的随机结果。
# 最后,reset_index(drop=True)用于重置索引,将原来的索引列删除,并重新生成一个新的索引列。

# 对DataFrame进行随机洗牌的目的通常是为了打乱数据的顺序,以实现数据的随机化处理。这样做可以消除数据的顺序相关性,使得模型训练更加稳定和可靠。
# 在某些情况下,数据集的顺序可能是有序的或者存在一定的规律性,这可能导致模型在训练过程中出现过拟合或者无法充分学习到数据的潜在特征。
# 通过随机洗牌,可以将数据集重新排列,使得每个样本都有可能出现在任意位置,从而增加了模型的泛化能力。
# 此外,随机洗牌还可以用于数据划分、交叉验证等任务中,以确保每个子集的数据分布相对均匀,避免因数据顺序导致的偏差。
poke_df = poke_df.sample(random_state=1, frac=1).reset_index(drop=True)

np.unique(poke_df['Generation']) # array(['Gen 1', 'Gen 2', 'Gen 3', 'Gen 4', 'Gen 5', 'Gen 6'], dtype=object)
gen_ord_map = {'Gen 1': 1, 'Gen 2': 2, 'Gen 3': 3, 
               'Gen 4': 4, 'Gen 5': 5, 'Gen 6': 6} # Writing a map set manually so you can assign the param-digit.
poke_df['GenerationLabel'] = poke_df['Generation'].map(gen_ord_map) #这样就多出了一列数值类型的特征列

转换操作3 —— onehot映射

from sklearn.preprocessing import OneHotEncoder, LabelEncoder

# 完成LabelEncoder
gen_le = LabelEncoder()
gen_labels = gen_le.fit_transform(poke_df['Generation'])
poke_df['Gen_Label'] = gen_labels

poke_df_sub = poke_df[['Name', 'Generation', 'Gen_Label', 'Legendary']]

# 完成OneHotEncoder
gen_ohe = OneHotEncoder()
gen_feature_arr = gen_ohe.fit_transform(poke_df[['Gen_Label']]).toarray()
gen_feature_labels = list(gen_le.classes_)
print(gen_feature_labels) # ['Gen 1', 'Gen 2', 'Gen 3', 'Gen 4', 'Gen 5', 'Gen 6']

# 将转换好的特征组合到dataframe中
gen_features = pd.DataFrame(gen_feature_arr, columns=gen_feature_labels) # gen_features is the onehot map of Gen_Label column.
poke_df_ohe = pd.concat([poke_df_sub, gen_features], axis=1) # Concating by axis 1(column)
#这样就多出了一列值为1的数据,可以用来表示数值特征,见下表
NameGenerationGen_LabelLegendaryGen 1Gen 2Gen 3Gen 4Gen 5Gen 6
0CharizardMega Charizard YGen 10False1.00.00.00.00.00.0
1AbomasnowGen 43False0.00.00.01.00.00.0
2SentretGen 21False0.01.00.00.00.00.0
3LitleoGen 65False0.00.00.00.00.01.0
4OctilleryGen 21False0.01.00.00.00.00.0

转换操作4 —— onehot映射,但使用get_dummies()

gen_onehot_features = pd.get_dummies(poke_df['Generation'],prefix = 'one-hot')
pd.concat([poke_df[['Name', 'Generation']], gen_onehot_features], axis=1).iloc[4:10]
NameGenerationone-hot_Gen 1one-hot_Gen 2one-hot_Gen 3one-hot_Gen 4one-hot_Gen 5one-hot_Gen 6
4OctilleryGen 2010000
5HelioptileGen 6000001
6DialgaGen 4000100
7DeoxysDefense FormeGen 3001000
8RapidashGen 1100000
9SwannaGen 5000010

二值特征与多项式特征

二值特征还是数值特征的一种,只是较为特殊

# 二值特征
############### 方法1
watched = np.array(popsong_df['listen_count']) 
watched[watched >= 1] = 1 # Watching this song many times equal to having had watched.
popsong_df['watched'] = watched
popsong_df.head(10)

############### 方法2
from sklearn.preprocessing import Binarizer
bn = Binarizer(threshold=0.9) # Feature values below or equal to 'threshold' are replaced by 0, above it by 1.
pd_watched = bn.transform([popsong_df['listen_count']])[0]
popsong_df['pd_watched'] = pd_watched
popsong_df.head(10)

多项式特征,为什么会用到多项式特征,看这篇文章会更加直观,举了一个圆形分布的数据点划分的例子,如果是原始数据特征,就不好划分,用到了平方项时,就会形成圆形,更易划分,这似乎与核函数类似:《机器学习基础学习-在逻辑回归中使用多项式特征》,这里我后面弄懂了再详细写//@TODO

from sklearn.preprocessing import PolynomialFeatures

pf = PolynomialFeatures(degree=2, interaction_only=False, include_bias=False)
res = pf.fit_transform(atk_def)

连续值离散化

例如年龄数据是连续的,那么划分出年龄阶段比如80后,90后就是一种连续值离散化

对数与时间变换

对数变换与cox-box变换

书上提到:“很多机器学习算法希望预测的结果值能够呈现高斯分布”,“并且经过对数变换之后,特征分布更接近高斯分布”,这两句话简直令人摸不着头脑。

(1)很多机器学习算法希望预测的结果值能够呈现高斯分布,这是因为高斯分布具有许多优点,如均值为0、方差为1等。这些特性使得高斯分布在概率统计和机器学习领域具有广泛的应用。这一点可以理解
(2)对数变换之后,特征分布更接近高斯分布的原因是对数变换可以将线性关系转化为非线性关系。对于一些线性关系较强的特征,对数变换后可以使其变为非线性关系,从而使特征分布更接近高斯分布。—— 大模型的回答
对数变换前,注意x轴坐标值
在这里插入图片描述
对数变换后,注意x轴坐标值,我似乎有点理解了这个对数变换为什么会使特征分布更接近高斯分布,由于对数函数的特点,随着x值不断变大,y值的变化越来越平滑,具体的解释还可以看这篇文章《机器学习中的特征工程锦囊:对数变换》,看完这篇文章后,发现这种对数变换应该只适合于长尾分布,而现实中的特征分布往往长尾分布又往往比较多,所以书上才会有“经过对数变换之后,特征分布更接近高斯分布”这句话。
在这里插入图片描述

与对数变换作用相似的还有cox-box变换,请看《Box-Cox变换详解》

时间变换

1.原始时间戳

time_stamps = ['2015-03-08 10:30:00.360000+00:00', '2017-07-13 15:45:05.755000-07:00',
               '2012-01-20 22:30:00.254000+05:30', '2016-12-25 00:30:00.000000+10:00']
df = pd.DataFrame(time_stamps, columns=['Time'])
print(df)
Time
02015-03-08 10:30:00.360000+00:00
12017-07-13 15:45:05.755000-07:00
22012-01-20 22:30:00.254000+05:30
32016-12-25 00:30:00.000000+10:00

2.将原始时间戳转换成pd.Timestamp对象

ts_objs = np.array([pd.Timestamp(item) for item in np.array(df.Time)])
df['TS_obj'] = ts_objs
print(ts_objs)

# array([Timestamp('2015-03-08 10:30:00.360000+0000', tz='UTC'),
#       Timestamp('2017-07-13 15:45:05.755000-0700', tz='pytz.FixedOffset(-420)'),
#       Timestamp('2012-01-20 22:30:00.254000+0530', tz='pytz.FixedOffset(330)'),
#       Timestamp('2016-12-25 00:30:00+1000', tz='pytz.FixedOffset(600)')],
#      dtype=object)

3.适用TImestamp.apply()方法进行转换,这里有一个lambda表达式,lambda是一种匿名函数,那么在这里,这些函数就是每接收一个d,就将其转换成d.xxx,而.apply()函数意思是将一个函数应用于一个数组或列表的每个元素,它的主要作用是对集合中的每个元素执行相同的操作,并返回一个新的集合。

# 检查 'TS_obj' 列中的每个元素是否为日期时间对象
df['is_datetime'] = df['TS_obj'].apply(lambda d: isinstance(d, pd.Timestamp))

# 只保留 'TS_obj' 列中为日期时间对象的行
df = df[df['is_datetime']]

df['Year'] = df['TS_obj'].apply(lambda d: d.year)
df['Month'] = df['TS_obj'].apply(lambda d: d.month)
df['Day'] = df['TS_obj'].apply(lambda d: d.day)
df['DayOfWeek'] = df['TS_obj'].apply(lambda d: d.dayofweek)
df['DayName'] = df['TS_obj'].apply(lambda d: d.day_name()) # weekday_name --> day_name()
df['DayOfYear'] = df['TS_obj'].apply(lambda d: d.dayofyear)
df['WeekOfYear'] = df['TS_obj'].apply(lambda d: d.weekofyear)
df['Quarter'] = df['TS_obj'].apply(lambda d: d.quarter)

df[['TS_obj','Time', 'Year', 'Month', 'Day', 'Quarter', 
    'DayOfWeek', 'DayName', 'DayOfYear', 'WeekOfYear']]
TS_objTimeYearMonthDayQuarterDayOfWeekDayNameDayOfYearWeekOfYear
02015-03-08 10:30:00.360000+00:002015-03-08 10:30:00.360000+00:0020153816Sunday6710
12017-07-13 15:45:05.755000-07:002017-07-13 15:45:05.755000-07:00201771333Thursday19428
22012-01-20 22:30:00.254000+05:302012-01-20 22:30:00.254000+05:30201212014Friday203
32016-12-25 00:30:00+10:002016-12-25 00:30:00.000000+10:002016122546Sunday36051

书不一定是可靠的,网上的解决方案也不一定是可靠的,源码才是可靠的。

6.2 文本特征

安装与配置nltk库

首先是了解nltk库,这是用来做自然语言处理的常用库,要想了解更多可以阅读下面的文章

参考文章
《跟着ChatGPT学习——NLTK库》
《Python自然语言处理:NLTK库详解》

如果在VSCode中执行nltk.download()有问题,报了Connection Error,那么请用Terminal手动进入python解释器,我这样做是成功的
在这里插入图片描述

若这样还不成功,再看看以下文章是否对你有用

参考解决方案
《NLTK安装过程中遇到的坑》
《Python自然语言处理:NLTK库详解》

改了数据下载目录后,要用一行代码去告诉nltk还有哪些目录可以加载他的corpora

nltk.data.path.append('/path/to/nltk_data')

该解决方案来自于这篇文章《Python 如何从代码中配置nltk数据目录》

词袋模型

名词含义
Stop Words(停用词)简单理解就是不重要的词,对结果没什么影响的词,见 Stop word - wiki
corpus语料库

词袋模型生成的向量即单词频率,书中说:“词袋模型的原理和操作都十分简单,但是这样做出来的向量是没有灵魂的。无论是一句话还是一篇文章,都是有先后顺序的,但在词袋模型中,却只考虑词频,并且每个词的重要程度完全和其出现的次数相关,通常情况下,文章向量会是一个非常大的稀疏矩阵,并不利于计算”。
词袋模型还能用在计算机视觉中:《计算机视觉CS131:专题10-识别&词袋模型》

TF-IDF特征

词袋模型只考虑了词频,没考虑词本身的含义

word2vec词向量模型(重点*)

书中说:“其基本原理是基于神经网络的。首先对每个词进行初始化操作,例如,每个词都是长度为10的一个随机向量。接下来,模型会对每个词及其上下文进行预测,例如输入是向量“回家”,输出就是“吃饭”,所有的输入数据和输出标签都是语料库中的上下文,所以标签并不需要特意指定。此时不只要通过优化算法选择合适的权重参数,例如梯度下降,输入的向量也会随之改变,也就是向量“回家”一开始是随机的,在每次迭代过程中都会不断改变,直到得到一个合适的结果。”
最常用的工具包是Gensim,

知识加油站

¥银行卡的分类

银行卡 借记卡(不可透支) 信用卡(可透支) 储值卡(无储蓄功能,金额固定,用完需再次充值,常用来送礼) 转账卡(转账、存取现金和消费) 专用卡(专门用途、特定区域)

中国人民银行已经规定商业银行发行全国使用的联名卡、IC卡、储值卡需要得到中国人民银行总行的审批,见:
《银行卡业务管理办法》 —— 中国人民银行
《银行卡种类》—— 西宁中心支行
【不是我针对谁,但在座的各位都不会用银行卡】—— bilibili
《预付卡、购物卡、储值卡有什么区别?卡券冷知识》

References

[1] 周志华机器学习

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐