泰坦尼克号乘员数据分析

数据集来源

https://aistudio.baidu.com/aistudio/datasetdetail/6374

泰坦尼克号乘客数据分析

# Imports
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

%matplotlib inline
# Read titanic_data.csv
titanic_df = pd.read_csv('../data/data54777/train.csv')
# Test data information
titanic_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   PassengerId  891 non-null    int64  
 1   Survived     891 non-null    int64  
 2   Pclass       891 non-null    int64  
 3   Name         891 non-null    object 
 4   Sex          891 non-null    object 
 5   Age          714 non-null    float64
 6   SibSp        891 non-null    int64  
 7   Parch        891 non-null    int64  
 8   Ticket       891 non-null    object 
 9   Fare         891 non-null    float64
 10  Cabin        204 non-null    object 
 11  Embarked     889 non-null    object 
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB

可以观察到,PassengerId\Name\Ticket 等提供不了和生还有关的有效信息,在分析前可以先去掉。

# 删掉用处不大的数据
titanic_df = titanic_df.drop(['PassengerId','Name','Ticket'], axis = 1)
titanic_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 9 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   Survived  891 non-null    int64  
 1   Pclass    891 non-null    int64  
 2   Sex       891 non-null    object 
 3   Age       714 non-null    float64
 4   SibSp     891 non-null    int64  
 5   Parch     891 non-null    int64  
 6   Fare      891 non-null    float64
 7   Cabin     204 non-null    object 
 8   Embarked  889 non-null    object 
dtypes: float64(2), int64(4), object(3)
memory usage: 62.8+ KB

由此可见,Age/Cabin/Embarked 等字段有丢失值,在稍后的分析中应该先处理丢失值问题。

基本情况

#891人当中,共多少人生还?
total_survived_num = titanic_df['Survived'].sum()
total_no_survived_num = titanic_df.shape[0] - total_survived_num
print ("生还者 {} 人,{} 人未生还。".format(total_survived_num,total_no_survived_num))
生还者 342 人,549 人未生还。
plt.figure(figsize = (10,5))
plt.subplot(121)
sns.countplot(x='Survived', data=titanic_df)
plt.title('Survival count')

plt.subplot(122)
plt.pie([total_no_survived_num, total_survived_num],labels=['No Survived','Survived'],autopct='%1.0f%%')
plt.title('Survival rate') 

plt.show()

png

这891名乘客中,生还和未生还的比例分别为 38% 和 62%。

下面,分别分析 Pclass、Sex、Age、SibSp、Parch、Fare、Cabin 和 Embarked 等与“生还”的关系

Pclass 根据乘客客舱等级进行生存率分析

不同级别客舱的人数和比例

titanic_df[['Pclass','Survived']].groupby(['Pclass']).count()
Survived
Pclass
1 216
2 184
3 491
plt.figure(figsize= (10, 5))
plt.subplot(121)
sns.countplot(x='Pclass', data=titanic_df)
plt.title('Pclass Count') 
plt.subplot(122)
plt.pie(x = titanic_df[['Pclass','Survived']].groupby(['Pclass']).count()['Survived'], labels=['1','2','3'],autopct='%1.0f%%')
plt.title('Pclass Count') 
plt.show()

png

海难发生前,一等舱、二等舱、三等舱的乘客分别为216、184、491人,分别占总人数的 24%, 21%, 55%。

不同级别客舱生还人数和占总生还人数的比例

survived_df = titanic_df[titanic_df[ 'Survived'] == 1 ]
survived_df[['Pclass','Survived']].groupby(['Pclass']).sum()
Survived
Pclass
1 136
2 87
3 119
plt.figure(figsize= (10, 5))
plt.subplot(121)
sns.countplot(x='Pclass', data=survived_df)
plt.title('Pclass Survived') 
plt.ylabel('Survived Count')

plt.subplot(122)
plt.pie(x=survived_df[['Pclass','Survived']].groupby(['Pclass']).sum()['Survived'],\
        labels=['1','2','3'],autopct='%1.0f%%')
plt.show()

png

海难发生后,一等舱、二等舱、三等舱的乘客人数变为136、87、119人,分别占总人数的 40%, 25%, 35%。

不同客舱分别的生还和未生还人数及生还率

pclass1 = titanic_df[titanic_df['Pclass'] == 1]
pclass2 = titanic_df[titanic_df['Pclass'] == 2]
pclass3 = titanic_df[titanic_df['Pclass'] == 3]

plt.figure(figsize=(10,20))
plt.subplot(421)
sns.countplot(x = 'Survived', data = pclass1)
plt.title('Pclass 1') 

plt.subplot(422)
plt.pie([pclass1['Survived'][pclass1['Survived'] == 0].count(),pclass1['Survived'][pclass1['Survived'] == 1].count()],\
labels=['No Survived', 'Survived'],autopct='%1.0f%%')

plt.subplot(423)
sns.countplot(x = 'Survived', data = pclass2)
plt.title('Pclass 2') 

plt.subplot(424)
plt.pie([pclass2['Survived'][pclass2['Survived'] == 0].count(),pclass2['Survived'][pclass2['Survived'] == 1].count()],\
labels=['No Survived', 'Survived'],autopct='%1.0f%%')

plt.subplot(425)
sns.countplot(x = 'Survived', data = pclass3)
plt.title('Pclass 3') 

plt.subplot(426)
plt.pie([pclass3['Survived'][pclass3['Survived'] == 0].count(),pclass3['Survived'][pclass3['Survived'] == 1].count()],\
labels=['No Survived', 'Survived'],autopct='%1.0f%%')

plt.subplot(427)
survived_by_pclass = titanic_df.groupby('Pclass')['Survived'].mean()
survived_by_pclass.plot(kind = 'bar')

plt.show()


png

Sex 根据乘客性别进行生存率分析

船上男女人数及比例?

male_sum = titanic_df['Sex'][titanic_df['Sex'] == 'male'].count()
female_sum = titanic_df['Sex'][titanic_df['Sex'] == 'female'].count()
print ("male:female = {}:{}".format(male_sum,female_sum))
male:female = 577:314
plt.figure(figsize=(10,5))
plt.subplot(121)
sns.countplot(x='Sex', data=titanic_df)
plt.subplot(122)
plt.pie([male_sum,female_sum],\
        labels=['male', 'female'],autopct='%1.0f%%')
plt.show()

png

存活的男女数量及男女比例?

survived_male_sum = survived_df['Sex'][survived_df['Sex'] == 'male'].count()
survived_female_sum = survived_df['Sex'][survived_df['Sex'] == 'female'].count()
print ("survived_male:survived_femal = {}:{}".format(survived_male_sum, survived_female_sum))
survived_male:survived_femal = 109:233
plt.figure(figsize=(10,5))
plt.subplot(121)
sns.countplot(x='Sex', data=survived_df)
plt.subplot(122)
plt.pie([survived_male_sum, survived_female_sum],\
        labels=['male', 'female'],autopct='%1.0f%%')
plt.show()
# 如何改变countplot中的条形顺序?

png

男性的生还数量及生还率?

male_df = titanic_df[titanic_df['Sex'] == 'male']
male_df['Survived'][male_df['Survived'] == 1].count()
109
plt.figure(figsize=(10,5))
plt.subplot(121)
sns.countplot(x = 'Survived', data = male_df)
plt.subplot(122)
plt.pie([male_df['Survived'][male_df['Survived'] == 0].count(),male_df['Survived'][male_df['Survived'] == 1].count()],\
        labels=['No Survived', 'Survived'],autopct='%1.0f%%')
plt.show()

png

男性生还109人,生还率仅为 19%。

女性的生还数量及生还率?

female_df = titanic_df[titanic_df['Sex'] == 'female']

plt.figure(figsize=(10,5))
plt.subplot(121)
sns.countplot(x = 'Survived', data = female_df)
plt.subplot(122)
plt.pie([female_df['Survived'][female_df['Survived'] == 0].count(),female_df['Survived'][female_df['Survived'] == 1].count()],\
        labels=['No Survived', 'Survived'],autopct='%1.0f%%')
plt.show()


png

女性生还 233 人,生还率为 74%。远远高于男性的 19%。

survived_by_pclass = titanic_df.groupby('Sex')['Survived'].mean()
survived_by_pclass.plot(kind = 'bar')
plt.show()

png

Age 根据乘客年龄进行生存率分析

由于 Age 有丢失值,先处理丢失值问题。 Age 的丢失值较多,填充的年龄为年龄平均值的上下一个标准差范围内的随机数。

# 求年龄的平均值,标准差以及丢失值的数量
average_age_titanic   = titanic_df["Age"].mean()
std_age_titanic       = titanic_df["Age"].std()
count_nan_age_titanic = titanic_df["Age"].isnull().sum()

# 求年龄随机数,范围在 (mean - std, mean + std)
rand_1 = np.random.randint(average_age_titanic - std_age_titanic, average_age_titanic + std_age_titanic, size = count_nan_age_titanic)

# 将随机数填充进 Age 的丢失值中
titanic_df["Age"][np.isnan(titanic_df["Age"])] = rand_1
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/ipykernel_launcher.py:10: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  # Remove the CWD from sys.path while we load stuff.

年龄分布?

plt.figure(figsize=(12,5))
plt.subplot(121)
titanic_df['Age'].hist(bins = 70)
plt.xlabel('Age')
plt.ylabel('Num')

plt.subplot(122)
titanic_df.boxplot(column='Age', showfliers=False)

plt.show()

png

titanic_df['Age'].describe()
count    891.000000
mean      29.744299
std       13.522766
min        0.420000
25%       21.000000
50%       29.000000
75%       38.000000
max       80.000000
Name: Age, dtype: float64

按照年龄,将乘客划分为儿童、少年、成年人和老年人,分析四个群体生还情况

bins = [0, 12, 18, 65, 100]
titanic_df['Age_group'] = pd.cut(titanic_df['Age'], bins)
by_age = titanic_df.groupby('Age_group')['Survived'].mean()
by_age
Age_group
(0, 12]      0.579710
(12, 18]     0.393258
(18, 65]     0.366897
(65, 100]    0.125000
Name: Survived, dtype: float64
by_age.plot(kind = "bar")
<matplotlib.axes._subplots.AxesSubplot at 0x7faa1e0291d0>

png

SibSp 根据乘客是否有兄弟姐妹进行生存率分析

# 分为有兄弟姐妹和没有兄弟姐妹两组
sibsp_df = titanic_df[titanic_df['SibSp'] != 0]
no_sibsp_df = titanic_df[titanic_df['SibSp'] == 0]

有兄弟姐妹的乘客的生还人数和生还率

plt.figure(figsize=(10,5))
plt.subplot(121)
sns.countplot(x = 'Survived', data = sibsp_df )

plt.subplot(122)
plt.pie([sibsp_df['Survived'][sibsp_df['Survived'] == 0].count(),sibsp_df['Survived'].sum()],\
        labels=['No Survived', 'Survived'],autopct='%1.0f%%')
plt.show()
print(sibsp_df['Survived'][sibsp_df['Survived'] == 0].count(), sibsp_df['Survived'].sum())

png

151 132

有兄弟姐妹的乘客,生还 132 人,生还率为 47%。

没有兄弟姐妹的乘客的生还人数和生还率

plt.figure(figsize=(10,5))
plt.subplot(121)
sns.countplot(x = 'Survived', data = no_sibsp_df )

plt.subplot(122)
plt.pie([no_sibsp_df['Survived'][no_sibsp_df['Survived'] == 0].count(),no_sibsp_df['Survived'].sum()],\
        labels=['No Survived', 'Survived'],autopct='%1.0f%%')
plt.show()
print(no_sibsp_df['Survived'].sum())

png

210

没有兄弟姐妹的乘客,共生还 210 人,生还率为 35%。

Parch 根据乘客是否有父母子女进行生存率分析

# 分为有父母子女和没有父母子女两组
parch_df = titanic_df[titanic_df['Parch'] == 1]
no_parch_df = titanic_df[titanic_df['Parch'] == 0]

有父母子女的乘客的生还人数和生还率

plt.figure(figsize=(10,5))
plt.subplot(121)
sns.countplot(x = 'Survived', data = parch_df )

plt.subplot(122)
plt.pie([parch_df['Survived'][parch_df['Survived'] == 0].count(),parch_df['Survived'].sum()],\
        labels=['No Survived', 'Survived'],autopct='%1.0f%%')
plt.show()
print(parch_df['Survived'].sum())


png

65

有父母或子女同船的乘客,生还 65 人,生还率为 55%。

没有父母子女的乘客的生还人数和生还率

plt.figure(figsize=(10,5))
plt.subplot(121)
sns.countplot(x = 'Survived', data = no_parch_df)

plt.subplot(122)
plt.pie([no_parch_df['Survived'][no_parch_df['Survived'] == 0].count(),no_parch_df['Survived'].sum()],\
        labels=['No Survived', 'Survived'],autopct='%1.0f%%')
plt.show()
print(no_parch_df['Survived'].sum())


png

233

Fare 根据票价分布进行生存率分析

plt.figure(figsize=(15,5))
titanic_df['Fare'].hist(bins = 70)
plt.show()

png

titanic_df.boxplot(column='Fare', by='Pclass', showfliers=False)
plt.show()

png

titanic_df['Fare'].describe()
count    891.000000
mean      32.204208
std       49.693429
min        0.000000
25%        7.910400
50%       14.454200
75%       31.000000
max      512.329200
Name: Fare, dtype: float64
fare_not_survived = titanic_df["Fare"][titanic_df["Survived"] == 0]
fare_survived     = titanic_df["Fare"][titanic_df["Survived"] == 1]
avgerage_fare = pd.DataFrame([fare_not_survived.mean(), fare_survived.mean()])
std_fare      = pd.DataFrame([fare_not_survived.std(), fare_survived.std()])
avgerage_fare.plot(yerr=std_fare,kind='bar',legend=False,)
plt.show()
print(avgerage_fare)
print(std_fare)


png

           0
0  22.117887
1  48.395408
           0
0  31.388207
1  66.596998

左右两边分别是死亡乘客和生还乘客的平均票价. 黑色竖线表示正常值误差范围.

可见,票价与生还有一定相关性,生还者的平均票价要比未生还的高。

Cabin 根据船舱号进行生存率分析

titanic_df['Cabin'].describe()
count     204
unique    147
top        G6
freq        4
Name: Cabin, dtype: object

丢失值太多,不能用此数据分析出 Cabin 不同对生存率的影响,丢掉。

titanic_df.drop("Cabin",axis=1,inplace=True)

Embarked 根据乘客上船的港口位置进行生存率分析

titanic_df['Embarked'].describe()
count     889
unique      3
top         S
freq      644
Name: Embarked, dtype: object

Embarked 有两个丢失值, 可以用众数填充

titanic_df[['Survived', 'Embarked']].groupby('Embarked').sum()
Survived
Embarked
C 93
Q 30
S 219
titanic_df["Embarked"] = titanic_df["Embarked"].fillna("S")
embarked_survived_sums = titanic_df[['Survived', 'Embarked']].groupby('Embarked').sum()

不同港口上船的乘客是否与生还率有关系?

fig, (axis1,axis2,axis3) = plt.subplots(1,3,figsize=(15,5)) 

sns.countplot(x='Embarked', data=titanic_df, ax=axis1)
sns.countplot(x='Survived', hue="Embarked", data=titanic_df, order=[1,0], ax=axis2)

embark_perc = titanic_df[["Embarked", "Survived"]].groupby(['Embarked'],as_index=False).mean()
sns.barplot(x='Embarked', y='Survived', data=embark_perc,order=['S','C','Q'],ax=axis3)

plt.show()

png

从登船人数来看, S 港口人数最多,C 次之, Q 最少。
从生还率来看, C 港上船的生还率最高, Q 次之, S 生还率最低。

分析总结

总结分为两个部分,分别是本次数据分析得出的规律和对于分析的限制性进行讨论。

数据分析总结

本次分析主要探寻泰坦尼克号上的生还率和各因素(客舱等级、年龄、性别、上船港口等)的关系。

样本数量为 891,海难发生后,生还者还剩 342 人,生还率为 38%。

泰坦尼克号上有一\二\三等舱三种船舱类型。海难发生前,一等舱有 216 人,二等舱 184 人,三等舱 491 人,分别占总人数的 24%, 21%, 55%。海难发生后,一等舱、二等舱、三等舱的乘客人数变为136、87、119人,分别占总人数的 40%, 25%, 35%。一等舱生还率为 63%,二等舱为 47%,三等舱为 24%。可见客舱等级越高,生还率越高。

891 人中,891人中,男性共577人,女性314人,男女比例为 65% 和 35%。海难发生后,男性变为109人,女性变为233人,男女比例变为 32% 和 68%。男性生还109人,生还率仅为 19%。女性生还 233 人,生还率为 74%,远远高于男性的 19%。可见女性比男性在这次事故中更容易生还,表明“女士优先”的原则在本次事故中得到了发扬。

样本的 891 人中,平均年龄约为 30 岁, 标准差 15 岁,最小年龄为 0.42 ,最大年龄 80。按照儿童(0-12)、少年(12-18)、成人(18-65)、老年人(65及以上)划分为四类,四类人的生还率分别为58%, 48%, 39% 和 9%。可见年龄越大,生还率越低。“尊老爱幼”的原则在本次事故中没有很好体现。

有兄弟姐妹的乘客,生还 132 人,生还率为 47%,而没有兄弟姐妹的乘客,共生还 210 人,生还率为 35%。可见有兄弟姐妹同船的生还率比没有兄弟姐妹同船的生还率要高。

有父母或子女同船的乘客,生还 109 人,生还率为 51%。没有父母子女同船的乘客,生还 233 人,生还率仅为 34%。可见有父母或子女同船的生还率比没有的生还率要高。综合前一条分析,可以得出推论,有家人在船上的比没有家人在船上的生还概率要大。

票价与生还有一定相关性,生还者的平均票价要比未生还的高。

S 港口生还人数最多,C 次之, Q 最少。从生还率来看, C 港上船的生还率最高, Q 次之, S 生还率最低。

分析限制讨论

  1. 此数据并非全部乘客的数据,据了解,泰坦尼克号上共有乘客 2224 人,而本数据集共有 891 人。如果该数据集是从 2224 人中随机选出,根据中心极限定理,该样本也足够大,分析结果有代表性;如果不是随机选出,那么分析结果就不可靠了。
  2. 可能还有其他因素影响生还情况。比如乘客的国别是否与生还状况有关系?乘客的身高是否与生还状况有关系?乘客是否会游泳是否和生还情况有关系?

Views: 163

03 分类算法

数据集介绍与划分

学习目标

  • 目标
    • 知道数据集的分为训练集和测试集
    • 知道sklearn的分类、回归数据集

拿到的数据是否全部都用来训练一个模型?

数据集的划分

机器学习一般的数据集会划分为两个部分:

  • 训练数据:用于训练,构建模型
  • 测试数据:在模型检验时使用,用于评估模型是否有效

划分比例:

  • 训练集:70% 80% 75%
  • 测试集:30% 20% 30%

API

  • sklearn.model_selection.train_test_split(arrays, *options)
    • x 数据集的特征值
    • y 数据集的标签值
    • test_size 测试集的大小,一般为float
    • random_state 随机数种子,不同的种子会造成不同的随机采样结果。相同的种子采样结果相同。
    • return ,测试集特征训练集特征值值,训练标签,测试标签(默认随机取)

结合后面的数据集作介绍

sklearn数据集介绍

API

  • sklearn.datasets
    • 加载获取流行数据集
    • datasets.load_*()
      • 获取小规模数据集,数据包含在datasets里
    • datasets.fetch_*(data_home=None)
      • 获取大规模数据集,需要从网络上下载,函数的第一个参数是data_home,表示数据集下载的目录,默认是 ~/scikit_learn_data/

分类和回归数据集

  • 分类数据集
  • sklearn.datasets.fetch_20newsgroups(data_home=None,subset=‘train’)
    • subset: 'train'或者'test','all',可选,选择要加载的数据集.训练集的“训练”,测试集的“测试”,两者的“全部”
  • 回归数据集

返回类型

  • load和fetch返回的数据类型datasets.base.Bunch(字典格式)
    • data:特征数据数组,是 [n_samples * n_features] 的二维 numpy.ndarray 数组
    • target:标签数组,是 n_samples 的一维 numpy.ndarray 数组
    • DESCR:数据描述
    • feature_names:特征名,新闻数据,手写数字、回归数据集没有
    • target_names:标签名

sklearn转换器和估计器

学习目标

  • 知道sklearn的转换器和估计器流程

转换器和估计器

转换器

想一下之前做的特征工程的步骤?

  • 1、实例化 (实例化的是一个转换器类(Transformer))
  • 2、调用fit_transform(对于文档建立分类词频矩阵,不能同时调用)

我们把特征工程的接口称之为转换器,其中转换器调用有这么几种形式

  • fit_transform
  • fit
  • transform

这几个方法之间的区别是什么呢?我们看以下代码就清楚了

In [1]: from sklearn.preprocessing import StandardScaler

In [2]: std1 = StandardScaler()

In [3]: a = [[1,2,3], [4,5,6]]

In [4]: std1.fit_transform(a)
Out[4]:
array([[-1., -1., -1.],
       [ 1.,  1.,  1.]])

In [5]: std2 = StandardScaler()

In [6]: std2.fit(a)
Out[6]: StandardScaler(copy=True, with_mean=True, with_std=True)

In [7]: std2.transform(a)
Out[7]:
array([[-1., -1., -1.],
       [ 1.,  1.,  1.]])

从中可以看出,fit_transform的作用相当于transform加上fit。但是为什么还要提供单独的fit呢, 我们还是使用原来的std2来进行标准化看看

In [8]: b = [[7,8,9], [10, 11, 12]]

In [9]: std2.transform(b)
Out[9]:
array([[3., 3., 3.],
       [5., 5., 5.]])

In [10]: std2.fit_transform(b)
Out[10]:
array([[-1., -1., -1.],
       [ 1.,  1.,  1.]])

估计器(sklearn机器学习算法的实现)

在sklearn中,估计器(estimator)是一个重要的角色,是一类实现了算法的API

1、用于分类的估计器:

  • sklearn.neighbors k-近邻算法
  • sklearn.naive_bayes 贝叶斯
  • sklearn.linear_model.LogisticRegression 逻辑回归
  • sklearn.tree 决策树与随机森林

2、用于回归的估计器:

  • sklearn.linear_model.LinearRegression 线性回归
  • sklearn.linear_model.Ridge 岭回归

3、用于无监督学习的估计器

  • sklearn.cluster.KMeans 聚类

1.3 估计器工作流程

K-近邻算法

学习目标

  • 说明K-近邻算法的距离公式
  • 说明K-近邻算法的超参数K值以及取值问题
  • 说明K-近邻算法的优缺点
  • 应用KNeighborsClassifier实现分类
  • 了解分类算法的评估标准准确率

应用

  • Facebook签到位置预测

问题:回忆分类问题的判定方法

什么是K-近邻算法?

K-Nearest-Neighbors, 即选举出K个最临近的邻居的算法

地图K近邻算法
  • 通过你的“邻居”来推断出你的类别

K-近邻算法(KNN)

定义

如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。

来源:KNN算法最早是由Cover和Hart提出的一种分类算法

距离公式

两个样本的距离可以通过如下公式计算,又叫欧式距离

(也有其他距离的定义方式)

2、电影类型分析

假设我们有现在几部电影

其中? 号电影不知道类别,如何去预测?我们可以利用K近邻算法的思想

问题

  • 如果取的最近的电影数量不一样?会是什么结果?

K-近邻算法数据的特征工程处理

  • 结合前面的约会对象数据,分析K-近邻算法需要做什么样的处理

K-近邻算法API

  • sklearn.neighbors.KNeighborsClassifier(n_neighbors=5,algorithm='auto')
    • n_neighbors:int,可选(默认= 5),k_neighbors查询默认使用的邻居数
    • algorithm:{‘auto’,‘ball_tree’,‘kd_tree’,‘brute’},可选用于计算最近邻居的算法:
      • ‘ball_tree’将会使用 BallTree
      • ‘kd_tree’将使用 KDTree
      • ‘auto’将尝试根据传递给fit方法的值来决定最合适的算法。 (不同实现方式影响效率)

案例:鸢尾花分类预测

数据集介绍

使用sklearn的iris小数据集

# 模型调用
from sklearn.neighbors import KNeighborsClassifier
import sklearn.datasets

导入数据集:

data = sklearn.datasets.load_iris()

查看数据集描述:

data.DESCR

输出略

特征X:

X = data.data

打印X的形状:

X.shape

150行样本数据, 每行4列特征:

(150, 4)

标签y:

y = data.target

打印输出:

array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])

创建KNN分类器实例(k = 1)

# 创建实例
knn = KNeighborsClassifier(n_neighbors=1)

打印模型

knn

输出:

KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
                     metric_params=None, n_jobs=None, n_neighbors=1, p=2,
                     weights='uniform')

创建KNN分类器实例(k = 5)

# 创建另一个实例
knn_5 = KNeighborsClassifier()

确认模型

# 确认模型结构 n_neighbors默认是5
print(knn_5)

输出:

KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
                     metric_params=None, n_jobs=None, n_neighbors=5, p=2,
                     weights='uniform')

我们已经创建了两种模型(k=1, k=5), 下一步确定模型评估的策略

  • 使用整个数据集进行模型训练使用相同数据集测试
  • 将整个数据集拆分为训练数据集和测试数据集, 进行交叉验证

然后对比预测结果和实际结果(准确率)来评估模型表现

准确率

  • 正确预测的比率
  • 用于评估分类模型表现的常用指标

导入计算准确率的方法

from sklearn.metrics import accuracy_score

对全数据集进行训练和预测(k=1):

knn.fit(X, y)
y_pred = knn.predict(X)
print(accuracy_score(y, y_pred))

准确率为:

1.0

也可以这样比对每个元素:

y_pred == y

输出:

array([ True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True])

再次训练和预测(当k=5时):

knn_5.fit(X, y)
predct_y = knn_5.predict(X)

打印结果:

print(accuracy_score(y, y_pred))

仍然百分百正确:

1.0

这100%的准确率并不能说明当前的模型是最佳模型, 因为测试的都是训练过的数据.

k值取值过低会导致模型过于复杂, 会因为过拟合导致正确率反降.

为了解决这个问题, 我们一般要先将完全数据集分割成训练集和测试集两部分.

  • 训练集用于训练 (fit)
  • 测试集用于评估 (predict)

导入 train_test_split 用于分割数据集

from sklearn.model_selection import train_test_split

将数据集分割成训练集和测试集:

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4)

打印训练集和测试集的样本数量做对比:

print("train:test = {}:{}".format(X_train.shape[0], X_test.shape[0]))
train:test = 90:60

分离后训练集和测试集的预测 (k=5)

# 训练集和测试集分离后的预测 
knn_5_s = KNeighborsClassifier()
knn_5_s.fit(X_train, y_train)
y_train_pred = knn_5_s.predict(X_train)
y_test_pred = knn_5_s.predict(X_test)

打印训练集预测准确率 :

print("训练集预测准确率(knn5): ", accuracy_score(y_train, y_train_pred))
训练集预测准确率(knn5):  0.9777777777777777

I打印测试集预测准确率 :

print("测试集预测准确率(knn5): ", accuracy_score(y_test, y_test_pred))
测试集预测准确率(knn5):  0.9666666666666667

在测试k=1的情况

# 测试 k=1 的情况
knn_1_s = KNeighborsClassifier(n_neighbors=1)
knn_1_s.fit(X_train, y_train)
y_train_pred = knn_1_s.predict(X_train)
y_test_pred = knn_1_s.predict(X_test)

打印结果:

print("训练集预测准确率(knn1): ", accuracy_score(y_train, y_train_pred))
print("测试集预测准确率(knn1): ", accuracy_score(y_test, y_test_pred))
训练集预测准确率(knn1):  1.0
测试集预测准确率(knn1):  0.9333333333333333

那么到底k的取值为何时准确率最好呢?

可以让k的取值从1到25分别进行测试, 取准确率最高时的k值即可:

score_train = []
score_test = []
for  k in range(1, 26):
    knn_s = KNeighborsClassifier(n_neighbors=k)
    knn_s.fit(X_train, y_train)
    y_train_pred = knn_s.predict(X_train)
    y_test_pred = knn_s.predict(X_test)
    score_train.append(accuracy_score(y_train, y_train_pred))
    score_test.append(accuracy_score(y_test, y_test_pred))

for k in range(0, len(score_train) ):
    print("训练集预测准确率(k={}): {}".format(k + 1, score_train[k]))

print("===========")

for k in range(0, len(score_test) ):
    print("测试集预测准确率(k={}): {}".format(k + 1, score_test[k]))
训练集预测准确率(k=1): 1.0
训练集预测准确率(k=2): 1.0
训练集预测准确率(k=3): 0.9777777777777777
训练集预测准确率(k=4): 0.9777777777777777
训练集预测准确率(k=5): 0.9777777777777777
训练集预测准确率(k=6): 0.9777777777777777
训练集预测准确率(k=7): 0.9777777777777777
训练集预测准确率(k=8): 0.9666666666666667
训练集预测准确率(k=9): 0.9666666666666667
训练集预测准确率(k=10): 0.9666666666666667
训练集预测准确率(k=11): 0.9777777777777777
训练集预测准确率(k=12): 0.9666666666666667
训练集预测准确率(k=13): 0.9777777777777777
训练集预测准确率(k=14): 0.9666666666666667
训练集预测准确率(k=15): 0.9555555555555556
训练集预测准确率(k=16): 0.9666666666666667
训练集预测准确率(k=17): 0.9666666666666667
训练集预测准确率(k=18): 0.9666666666666667
训练集预测准确率(k=19): 0.9666666666666667
训练集预测准确率(k=20): 0.9666666666666667
训练集预测准确率(k=21): 0.9666666666666667
训练集预测准确率(k=22): 0.9666666666666667
训练集预测准确率(k=23): 0.9666666666666667
训练集预测准确率(k=24): 0.9666666666666667
训练集预测准确率(k=25): 0.9666666666666667
===========
测试集预测准确率(k=1): 0.9333333333333333
测试集预测准确率(k=2): 0.9166666666666666
测试集预测准确率(k=3): 0.9666666666666667
测试集预测准确率(k=4): 0.9666666666666667
测试集预测准确率(k=5): 0.9666666666666667
测试集预测准确率(k=6): 0.9666666666666667
测试集预测准确率(k=7): 0.9666666666666667
测试集预测准确率(k=8): 0.9666666666666667
测试集预测准确率(k=9): 0.9833333333333333
测试集预测准确率(k=10): 0.9666666666666667
测试集预测准确率(k=11): 0.9666666666666667
测试集预测准确率(k=12): 0.95
测试集预测准确率(k=13): 0.9333333333333333
测试集预测准确率(k=14): 0.9
测试集预测准确率(k=15): 0.9166666666666666
测试集预测准确率(k=16): 0.9
测试集预测准确率(k=17): 0.9
测试集预测准确率(k=18): 0.9
测试集预测准确率(k=19): 0.9
测试集预测准确率(k=20): 0.9
测试集预测准确率(k=21): 0.9
测试集预测准确率(k=22): 0.8833333333333333
测试集预测准确率(k=23): 0.8833333333333333
测试集预测准确率(k=24): 0.8833333333333333
测试集预测准确率(k=25): 0.8666666666666667
  • 对于训练集的评估, 由于是训练集训练得来的模型, 因此当k=1时可以达到100%准确率, 但是随着k值的增加准确率开始略有下降
  • 对测试集的评估, 由于测试集评估相当于是对未知数据进行评估,因此测试集评估的准确率更加重要, 根据经验合理的k值一般略低于训练样本数的平方根, 其准确率容易受数据的干扰,
import math
ideal_k = int(math.sqrt(X.shape[0]))
print("当k取值{}时, 测试集的评估准确率为{}".format(ideal_k, score_test[ideal_k - 1]))
当k取值12时, 测试集的评估准确率为0.95

为了找出最合适的k值可以对数据集进行多轮交叉验证, 选出综合准确率更高时的k值

也可以使用pyplot绘图实现可视化, 帮助你更直观找到合适的k值取值区间:

import matplotlib.pyplot as plt

绘 Training Accuracy折线图:

plt.plot(range(1, 26), score_train)
plt.xlabel('K(KNN Model)')
plt.ylabel('Training Accuracy')

输出:

Text(0,0.5,'Training Accuracy')

绘 Testing Accuracy折线图:

plt.plot(range(1, 26), score_test)
plt.xlabel('K(KNN Model)')
plt.ylabel('Testing Accuracy')

输出:

Text(0,0.5,'Testing Accuracy')

注意:

  • K 越小 模型越复杂 容易过拟合 对训练集训练准确率越高
  • K 越大 模型越简单 容易欠拟合

案例:签到位置预测(FaceBook)

FBLocation介绍

数据介绍:将根据用户的坐标位置,定位准确性和时间戳预测用户签到的地点ID。

train.csv,test.csv 
row_id:登记事件的ID
xy:坐标
准确性:定位准确性 
时间:时间戳
place_id:业务的ID,这是您预测的目标

官网:https://www.kaggle.com/navoshta/grid-knn/data

(Kaggle官网手机认证时需要梯子, 输入手机号前面加上+860)

FBlocation-飞桨AI Studio - 人工智能学习实训社区 (baidu.com)

(Baidu飞浆AI Studio里面有公开数据集可以下载, 无需登录)

分析

  • 对于数据做一些基本处理(这里所做的一些处理不一定达到很好的效果,我们只是简单尝试,有些特征我们可以根据一些特征选择的方式去做处理)
    • 缩小数据集范围 DataFrame.query()
    • 删除没用的日期数据 DataFrame.drop(可以选择保留)
    • 将签到位置少于n个用户的删除place_count = data.groupby('place_id').count()tf = place_count[place_count.row_id > 3].reset_index()data = data[data['place_id'].isin(tf.place_id)]
  • 分割数据集
  • 标准化处理
  • k-近邻预测

代码

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier

def knncls():
    """
    K近邻算法预测入住位置类别
    :return:
    """
    # 一、处理数据以及特征工程
    # 1、读取,缩小数据的范围
    data = pd.read_csv("./data/FBlocation/train.csv")

    # 数据逻辑筛选操作 df.query()
    data = data.query("x > 1.0 & x < 1.25 & y > 2.5 & y < 2.75")

    # 删除time这一列特征
    data = data.drop(['time'], axis=1)

    print(data)

    # 删除签到次数少于三次的位置
    place_count = data.groupby('place_id').count()

    tf = place_count[place_count.row_id > 3].reset_index()

    data = data[data['place_id'].isin(tf.place_id)]

    # 3、取出特征值和目标值
    y = data['place_id']
    # y = data[['place_id']]

    x = data.drop(['place_id', 'accuracy', 'row_id'], axis=1)

    # 4、数据分割与特征工程

    # (1)、数据分割
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3)

    # (2)、标准化
    std = StandardScaler()

    # 对训练集进行标准化操作
    x_train = std.fit_transform(x_train)
    print(x_train)

    # 进行测试集的标准化操作
    x_test = std.fit_transform(x_test)

    # 二、算法的输入训练预测
    # K值:算法传入参数不定的值    理论上:k = 根号(样本数)
    # K值:后面会使用参数调优方法,去轮流试出最好的参数[1,3,5,10,20,100,200]
    knn = KNeighborsClassifier(n_neighbors=1)

    # 调用fit()
    knn.fit(x_train, y_train)

    # 预测测试数据集,得出准确率
    y_predict = knn.predict(x_test)

    print("预测测试集类别:", y_predict)

    print("准确率为:", knn.score(x_test, y_test))

    return None

结果分析

准确率: 分类算法的评估之一

  • 1、k值取多大?有什么影响?

k值取很小:容易受到异常点的影响

k值取很大:受到样本均衡的问题

n_neighbors预测准确率备注
10.3421985815602837最差
30.39972419227738376
50.438140267927502
100.4676910953506698
200.49527186761229314
500.5027580772261623较好
1000.4923167848699764
2000.4858156028368794
实验结果(使用飞浆AI Studio)
  • 2、性能问题?

距离计算上面,时间复杂度高

K-近邻总结

  • 优点:
    • 简单,易于理解,易于实现,无需训练
  • 缺点:
    • 懒惰算法,对测试样本分类时的计算量大,内存开销大
    • 必须指定K值,K值选择不当则分类精度不能保证
  • 使用场景:小数据场景,几千~几万样本,具体场景具体业务去测试

在学习KNN后,我们需要考虑以下几个问题,当你把这些问题都解决了,KNN你已经掌握的差不多了。

1,问题描述:

  1,KNN的原理是什么?

  2,KNN算法的时间复杂度,和空间复杂度怎么样?

  3,K值如何选取,取多大合适?

  4,计算两个样本之间的距离,采用哪种距离计算方式好?

  5,类别如何判定最合适?

  6,计算量太大怎么办?

  7,假设样本中,类型分布非常不均匀,这又该怎么办?

2,K近邻原理:

  kNN原理很简单,通过计算待分类样本与已知类别样本之间的距离,找到距离待分类最近的K个已知类别的样本,然后根据少数“服从多数”的判决原则,统计K个样本中各类样本出现的次数,出现次数最多的样本即为待分类样本的类别。

近邻分类

上图中要确定测试样本绿色属于蓝色还是红色。显然,当K=3时,将以1:2的投票结果分类于红色;而K=5时,将以3:2的投票结果分类于蓝色。

我们不禁会反问,这样的分类准确吗?为什么不同的K值会得到不同的分类结果?你有什么理由说待分类的样本与K个样本中出现次数最多样本是同一类?

  我们都听说过这句话“同一类样本之间差异较小,不同类之间差异较大”,这差异通过什么来体现呢?在KNN算法中,样本特征之间差异,主要通过特征之间的“距离”来体现,距离体现了他们之间的相似性,在多维空间中,不同类的样本散布于多维空间之中,同类样本总是聚集在一起,本质是讲就是他们的特征存在相似性,不同类样本之间是彼此分离的。

因此通过计算待分类样本与已分类样本之间的距离来进行分类是可靠的。对于分类决策主要是根据“少数服从多数” 本质上讲就是哪类样本与待分类样本最相似,就分给哪类。(对于这种判决准则,不一定适用于所有情况)

关于为什么K取不同的值,分类结果可能不一致,这主要体现KNN在类边界上,分类结果会随K的取值不同,分类结果不稳定,但对于非类边界分类准确率还是很高的。 

K值选取:

关于K的取值是K-最近邻算法中的一大难题,没有特定的经验公式来告诉我们K应该取多大?K值如果选取的太小,模型太复杂,K值选取的太大的话,又会导致分类模糊。K的取值与实际背景有关,与你数据分析的目标……那么K值到底怎么选取呢?既然K值这么麻烦有没有一些常规选取K值的方法,答案是肯定的。

经验规则:k一般低于训练样本数的平方根。

常用方法有Cross Validation,贝叶斯准则, bootstrap……

上面提到了类与类之间的相似性通过距离来体现,下面我们看下有哪些距离:

3,距离定义:

1)欧式距离

欧氏距离是最常见的距离度量,衡量的是多维空间中各个点之间的绝对距离。

因为计算是基于各维度特征的绝对数值,所以欧氏度量需要保证各维度指标在相同的刻度级别,比如对身高(cm)和体重(kg)两个单位不同的指标使用欧式距离可能使结果失效。

2马氏距离:马氏距离能够缓解由于属性的线性组合带来的距离失真,是数据的协方差矩阵。

3)曼哈顿距离:

曼哈顿距离来源于城市区块距离,是将多个维度上的距离进行求和后的结果。

4)切比雪夫距离

切比雪夫距离起源于国际象棋中国王的走法,我们知道国际象棋国王每次只能往周围的8格中走一步,那么如果要从棋盘中A格(x1, y1)走到B格(x2, y2)最少需要走几步?扩展到多维空间,其实切比雪夫距离就是当p趋向于无穷大时的明氏距离。

5)闵氏距离:

闵氏距离是欧氏距离的推广,是对多个距离度量公式的概括性的表述。r取值为2式为曼哈顿距离 ;r取值为1时为欧式距离。

6)平均距离

7)弦距离:

8)测地距离

关于距离选择:

高维度对距离衡量的影响:众所周知当变量数越多,欧式距离的区分能力就越差。

变量值域对距离的影响:值域越大的变量常常会在距离计算中占据主导作用,因此应先对变量进行标准化。

比较常用的是选用欧式距离。可是这个距离真的具有普适性吗?《模式分类》中指出欧式距离对平移是敏感的,这点严重影响了判定的结果。在此必须选用一个对已知的变换(比如平移、旋转、尺度变换等)不敏感的距离度量。书中提出了采用切空间距离(tangent distance)来替代传统的欧氏距离。

最近邻法介绍:

切空间距离定义:

(引自:杨剑. 基于局部切距离的近邻法[A]. 中国自动化学会智能自动化专业委员会、中国科学院自动化研究所.2005年中国智能自动化会议论文集[C].中国自动化学会智能自动化专业委员会、中国科学院自动化研究所:,2005:6.)

4,算法实现步骤:

简单来说,KNN可以看成:有那么一堆你已经知道分类的数据,然后当一个新数据进入的时候,就开始跟训练数据里的每个点求距离,然后挑离这个训练数据最近的K个点看看这几个点属于什么类型,然后用少数服从多数的原则,给新数据归类。

具体步骤如下

  step.1---计算未知样本和每个训练样本的距离dist ;

  step.2---对dist从小到大排序;

  step.3---取出距离从小到大的K个训练样本,作为K-最近邻样本;

  step.4---统计K-最近邻样本中每个类标号出现的次数

  step.5---选择出现频率最大的类标号作为未知样本的类标号,

5,算法复杂度分析:

   KNN算法简单有效,但没有优化的暴力法那样效率容易达到瓶颈。如样本个数为N,特征维度为D的时候,该算法时间复杂度呈O(DN)增长。所以通常KNN的实现会把训练数据构建成K-D Tree(K-dimensional tree),构建过程很快,甚至不用计算D维欧氏距离,而搜索速度高达O(D*log(N))。不过当D维度过高,会产生所谓的”维度灾难“,最终效率会降低到与暴力法一样。因此通常D>20以后,最好使用更高效率的Ball-Tree,其时间复杂度为O(D*log(N))。人们经过长期的实践发现KNN算法虽然简单,但能处理大规模的数据分类,尤其适用于样本分类边界不规则的情况。最重要的是该算法是很多高级机器学习算法的基础。

(KNeighbors Classifier可以设置3种算法:‘brute’,‘kd_tree’,‘ball_tree’。如果不知道用哪个好,设置‘auto’让KNeighborsClassifier自己根据输入去决定。)

(引用:http://blog.csdn.net/lsldd/article/details/41357931)

6,类别如何判定最合理?

  投票法没有考虑近邻的距离的远近,距离更近的近邻也许更应该决定最终的分类,所以加权投票法更恰当一些。

模型选择与调优

  • 学习目标
    • 说明交叉验证过程
    • 说明超参数搜索过程
    • 应用GridSearchCV实现算法参数的调优
  • 具体应用
    • Facebook签到位置预测调优

为什么需要交叉验证

交叉验证目的:为了让被评估的模型更加准确可信

什么是交叉验证(cross validation)

交叉验证:将拿到的训练数据,分为训练和验证集。以下图为例:将数据分成5份,其中一份作为验证集。然后经过5次(组)的测试,每次都更换不同的验证集。即得到5组模型的结果,取平均值作为最终结果。又称5折交叉验证。

分析

我们之前知道数据分为训练集和测试集,但是为了让从训练得到模型结果更加准确。做以下处理

  • 训练集:训练集+验证集
  • 测试集:测试集

问题:那么这个只是对于参数得出更好的结果,那么怎么选择或者调优参数呢?

通常情况下,有很多参数是需要手动指定的(如k-近邻算法中的K值),这种叫超参数。但是手动过程繁杂,所以需要对模型预设几种超参数组合。每组超参数都采用交叉验证来进行评估。最后选出最优参数组合建立模型。

模型选择与调优

  • sklearn.model_selection.GridSearchCV(estimator, param_grid=None,cv=None)
    • 对估计器的指定参数值进行详尽搜索
    • estimator:估计器对象
    • param_grid:估计器参数(dict){“n_neighbors”:[1,3,5]}
    • cv:指定几折交叉验证
    • fit:输入训练数据
    • score:准确率
    • 结果分析:
      • bestscore:在交叉验证中验证的最好结果_
      • bestestimator:最好的参数模型
      • cvresults:每次交叉验证后的验证集准确率结果和训练集准确率结果

Facebook签到位置预测K值调优

使用网格搜索估计器

from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV

# 使用网格搜索估计器实现参数调优方法
# 从[1,3,5,10,20,100,200]中交叉验证轮流试出最好的参数
k_params = {"n_neighbors": [1,3,5,10,20,100,200]}

knn = KNeighborsClassifier(n_neighbors=50)
gc = GridSearchCV(knn, param_grid=k_params, cv=2)

gc.fit(x_train, y_train)

print("选择了某个模型测试集当中预测的准确率为:", gc.score(x_test, y_test))

# 训练验证集的结果
print("在交叉验证当中验证的最好结果:", gc.best_score_)
print("gc选择了的模型K值是:", gc.best_estimator_)
print("每次交叉验证的结果为:", gc.cv_results_)

输出

选择了某个模型测试集当中预测的准确率为: 0.48877068557919623
在交叉验证当中验证的最好结果: 0.4906265833474075
gc选择了的模型K值是: KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
                     metric_params=None, n_jobs=None, n_neighbors=20, p=2,
                     weights='uniform')
每次交叉验证的结果为: {'mean_fit_time': array([0.00542653, 0.00541699, 0.00546551, 0.00545597, 0.00556421,
       0.0055443 , 0.00544441]), 'std_fit_time': array([6.68764114e-05, 3.20672989e-05, 4.22000885e-05, 8.96453857e-05,
       1.06811523e-04, 1.09553337e-04, 3.01599503e-05]), 'mean_score_time': array([0.15212929, 0.20162416, 0.16866851, 0.17537868, 0.18768466,
       0.28859103, 0.42182934]), 'std_score_time': array([0.00433338, 0.03741288, 0.00232887, 0.00084627, 0.0002321 ,
       0.00352132, 0.0011555 ]), 'param_n_neighbors': masked_array(data=[1, 3, 5, 10, 20, 100, 200],
             mask=[False, False, False, False, False, False, False],
       fill_value='?',
            dtype=object), 'params': [{'n_neighbors': 1}, {'n_neighbors': 3}, {'n_neighbors': 5}, {'n_neighbors': 10}, {'n_neighbors': 20}, {'n_neighbors': 100}, {'n_neighbors': 200}], 'split0_test_score': array([0.35483871, 0.39739909, 0.44958622, 0.48099983, 0.49315994,
       0.48826212, 0.46123966]), 'split1_test_score': array([0.35179868, 0.39976355, 0.43523054, 0.47086641, 0.48809323,
       0.47762202, 0.45938186]), 'mean_test_score': array([0.3533187 , 0.39858132, 0.44240838, 0.47593312, 0.49062658,
       0.48294207, 0.46031076]), 'std_test_score': array([0.00152001, 0.00118223, 0.00717784, 0.00506671, 0.00253336,
       0.00532005, 0.0009289 ]), 'rank_test_score': array([7, 6, 5, 3, 1, 2, 4], dtype=int32)}

可以看出, 当 k=20 时, 最好的预测准确率为 0.49062658

朴素贝叶斯算法

  • 学习目标
    • 说明条件概率与联合概率
    • 说明贝叶斯公式、以及特征独立的关系
    • 记忆贝叶斯公式
    • 知道拉普拉斯平滑系数
    • 应用贝叶斯公式实现概率的计算
  • 具体应用
    • 20类新闻文章分类预测

什么是朴素贝叶斯分类方法

概率基础

概率(Probability)定义

  • 概率定义为一件事情发生的可能性
    • 扔出一个硬币,结果头像朝上
    • 某天是晴天
  • P(X) : 取值在[0, 1]

案例: 预测女神是否喜欢

在讲这两个概率之前我们通过一个例子,来计算一些结果:

问题如下:

  • 女神喜欢一个人的概率
  • 职业是程序员并且体型匀称的概率
  • 被女神喜欢职业又是程序员的概率
  • 被女神喜欢而职业是产品且体重超重的概率

那么这些问题该如何计算呢?

条件概率与联合概率

  • 联合概率:包含多个条件,且所有条件同时成立的概率
    • 记作:P(A,B)
    • 特性:P(A, B) = P(A)P(B)
  • 条件概率:就是事件A在另外一个事件B已经发生条件下的发生概率
    • 记作:P(A|B)
    • 特性:P(A1,A2|B) = P(A1|B)P(A2|B)

注意:此条件概率成立的前提,是由于A1,A2相互独立(完全不相关), 这也是为什么称之为朴素贝叶斯的原因. (这个假设在实际应用中往往是不成立的,在属性个数比较多或者属性之间相关性较大时,分类效果不好。)

而在属性相关性较小时,朴素贝叶斯性能最为良好。对于这一点,有半朴素贝叶斯之类的算法通过考虑部分关联性适度改进。

这样我们计算结果为:

p(程序员, 匀称) =  P(程序员)P(匀称) =3/7*(4/7) = 12/49 
P(产品, 超重|喜欢) = P(产品|喜欢)P(超重|喜欢)=1/2 *  1/4 = 1/8

那么,我们知道了这些知识之后,继续回到我们的主题中。朴素贝叶斯如何分类,这个算法经常会用在文本分类,那就来看文章分类是一个什么样的问题?

P(科技|文章1)

P(娱乐|文章1)

这个了类似一个条件概率,那么仔细一想,给定文章其实相当于给定什么?结合前面我们将文本特征抽取的时候讲的?所以我们可以理解为:

P(科技|文章1) = P(科技|词1, 词2, 词4, 词4 ... ...)

P(娱乐|文章1) = P(娱乐|词1, 词2, 词4, 词4 ... ...)

但是这个公式怎么求?前面并没有参考例子,其实是相似的,我们可以使用贝叶斯公式去计算

贝叶斯公式

公式

那么这个公式如果应用在文章分类的场景当中,我们可以这样看:

公式分为三个部分:

  • P(C):每个文档类别的概率(某文档类别数量/总文档数量)
  • P(W│C):给定类别下特征(被预测文档中出现的词)的概率
    • 计算方法:P(F1│C)=Ni/N (训练文档中去计算)
      • Ni为该F1词在C类别所有文档中出现的次数
      • N为所属类别C下的文档所有词出现的次数和
  • P(F1,F2,…) 预测文档中每个词的概率

如果在一篇文章中计算两个类别概率比较:

P(科技类|文章1) = P(文章1|科技类)P(科技类)/p(文章)

P(娱乐类|文章1) = P(文章1|娱乐类)P(娱乐类)/p(文章)

所以我们只要比较前面的大小就可以,得出谁的概率大

文章分类计算

假设我们从训练数据集得到如下信息:

训练集中一共90个文档, 其中30个是科技类, 60是娱乐类.第一列为某个在文档中出现的单词, 单词右边的列表示这个单词在某个分类下共有多少个文档中出现过这个单词

则通过贝叶斯公式可以计算:

该文档为科技类别的概率 :

P(科技|影院,支付宝,云计算) = 𝑃(影院,支付宝,云计算|科技)∗P(科技)=(8/100)∗(20/100)∗(63/100)∗(30/90) = 0.00456109

该文档为娱乐类别的概率 :

P(娱乐|影院,支付宝,云计算) = 𝑃(影院,支付宝,云计算|娱乐)∗P(娱乐)=(56/121)∗(15/121)∗(0/121)∗(60/90) = 0

思考:我们计算出来某个概率为0,合适吗?

拉普拉斯平滑系数

当每个类别未出现导致概率为0时,可以采用贝叶斯估计的方式来解决。

为了防止计算出的分类概率为0,可以生成一个接近于0的概率代替0,几乎不影响原有的先验概率分布。贝叶斯估计公式中,α称之为拉普拉斯平滑(Laplace smoothing), 为一个大于0的常数, 常取值为1。

P(娱乐|影院,支付宝,云计算) 
=P(影院,支付宝,云计算|娱乐)P(娱乐) 
=P(影院|娱乐)*P(支付宝|娱乐)*P(云计算|娱乐)P(娱乐)
=(56+1/121+4)(15+1/121+4)(0+1/121+1*4)(60/90) 
= 0.00002

API

  • sklearn.naive_bayes.MultinomialNB(alpha = 1.0)
    • 朴素贝叶斯分类
    • alpha:拉普拉斯平滑系数

案例: 从20个新闻组数据集进行分类训练

4.1 分析

  • 分割数据集
  • tfidf进行的特征抽取
  • 朴素贝叶斯预测

4.2 代码

from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import MultinomialNB
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import MultinomialNB

"""
朴素贝叶斯对新闻数据集进行预测
:return:
"""

# 获取新闻的数据,20个类别
news = fetch_20newsgroups(subset='all')
# 进行数据集分割
x_train, x_test, y_train, y_test = train_test_split(news.data, news.target, test_size=0.3)

# 对于文本数据,进行特征抽取
tf = TfidfVectorizer()

# 提取tfidf特征
x_train = tf.fit_transform(x_train)
# 这里打印出来的列表是:训练集当中的所有不同词的组成的一个列表
# print(tf.get_feature_names())
# print(x_train.toarray())

print(x_train.shape)
# nnz: number of none zero entries
print(x_train.nnz / float(x_train.shape[0]))

输出:

(13192, 136677)
156.75280473013947

我们从以上输出可以看出,提取的TF-IDF 向量是非常稀疏的,才有150多个非零特征.

# 不能调用fit_transform
x_test = tf.transform(x_test)

# 朴素贝叶斯估计器
mlb = MultinomialNB(alpha=1.0)
mlb.fit(x_train, y_train)

# 进行预测
y_predict = mlb.predict(x_test)

print("预测每篇文章的类别:", y_predict[:100])
print("真实类别为:", y_test[:100])

print("预测准确率为:", mlb.score(x_test, y_test))

注意:

数据预处理中的方法:

  • Fit(): Method calculates the parameters μ and σ and saves them as internal objects.

解释:简单来说,就是求得训练集X的均值啊,方差啊,最大值啊,最小值啊这些训练集X固有的属性。可以理解为一个训练过程, 返回了一个中间结果, 但是数据集不变.

  • Transform(): Method using these calculated parameters apply the transformation to a particular dataset.

解释:在Fit的基础上,进行标准化,降维,归一化等操作(看具体用的是哪个工具,如PCA,StandardScaler等)。

  • Fit_transform(): joins the fit() and transform() method for transformation of dataset.

解释:fit_transform是fit和transform的组合,既包括了训练又包含了转换。


transform()和fit_transform()二者的功能都是对数据进行某种统一处理(比如标准化~N(0,1),将数据缩放(映射)到某个固定区间,归一化,正则化等)

fit_transform(trainData)对部分数据先拟合fit,找到该part的整体指标,如均值、方差、最大值最小值等等(根据具体转换的目的),然后对该trainData进行转换transform,从而实现数据的标准化、归一化等等。

根据对之前部分trainData进行fit的整体指标,对剩余的数据(testData)使用同样的均值、方差、最大最小值等指标进行转换transform(testData),从而保证train、test处理方式相同。所以,一般都是这么用:

from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
sc.fit_tranform(X_train)
sc.tranform(X_test)

Note:

  • 必须先用fit_transform(trainData),之后再transform(testData)
  • 如果直接transform(testData),程序会报错
  • 如果fit_transfrom(trainData)后,使用fit_transform(testData)而不ransform(testData),虽然也能归一化,但是两个结果不是在同一个“标准”下的,具有明显差异。(一定要避免这种情况, 这也是为什么开头说不能对x_test使用fit_transform的原因)

输出:

预测每篇文章的类别: [15 13  7 15  8  0  9  7  3 18  7  5 12  2 13  1 18  1 16 12 13  5  4  9
  5 17 14  3 12 13 16  7 15  7 17 14  3 12  5 13 11 12 15 17  6  3  6 18
  6 18 11 15  2 16 13  7  8 14 11  2 17  8 15  2  3 14  3  4  5 15 14 15
 11  5  9 15  7  6 18 13  6 11 16 16 12  1  0 10 11  3 15 17 13  7  8  8
 13 15  2 11]
真实类别为: [15 13  7 15  8  0  9  7  3 18  7  5 12  2 13  1 18  5 11 12 13  5  4  9
  5 17 14  3 12 13 16  7 15  7 17 14  3 12  5 13 11 12 15 17  6  3  6 18
  6 18  6 15  2 16 13  7  8 14 11  2 17  8 15  2  6 12  3  4  5 19 14 15
 11  5  9 15  7  6 18 13  6 11 16 16 12  1  0 10 11  3 15 17 13  7  8  8
 13 19  2 11]
预测准确率为: 0.8514326140785284

总结

  • 优点:
    • 朴素贝叶斯模型发源于古典数学理论,有稳定的分类效率。
    • 对缺失数据不太敏感,算法也比较简单,常用于文本分类。
    • 分类准确度高,速度快
  • 缺点:
    • 由于使用了样本属性独立性的假设,所以如果特征属性有关联时其效果不好

决策树

  • 学习目标
    • 说明信息熵的公式以及作用
    • 说明信息增益的公式作用
    • 应用信息增益实现计算特征的不确定性减少程度
    • 了解决策树的三种算法实现
  • 具体应用
    • 泰坦尼克号乘客生存预测

认识决策树

决策树思想的来源非常朴素,程序设计中的条件分支结构就是if-then结构,最早的决策树就是利用这类结构分割数据的一种分类学习方法

怎么理解这句话?通过一个对话例子

想一想这个女生为什么把年龄放在最上面判断!!!!!!!!!

决策树分类原理详解

为了更好理解决策树具体怎么分类的,我们通过一个问题例子?

问题:如何对这些客户进行分类预测?你是如何去划分?

有可能你的划分是这样的

那么我们怎么知道这些特征哪个更好放在最上面,我们可以把最重要的判断放在前面:

为什么要这样做呢?

原理

需要用到信息论的知识, 下面通过例子引入信息熵:

熵(英语: entropy ):表示随机变量的不确定性, 单位比特(bit)。

条件熵:在一个条件下,随机变量的不确定性。

信息增益: 信息增益 = 熵 - 条件熵。表示在一个条件下,信息不确定性减少的程度。

通俗地讲,X(明天下雨)是一个随机变量,X的熵可以算出来, Y(明天阴天)也是随机变量,在阴天情况下下雨的信息熵我们如果也知道的话(此处需要知道其联合概率分布或是通过数据估计)即是条件熵。

X的熵减去Y条件下X的熵,就是信息增益。具体解释:原本明天下雨的信息熵是2,条件熵是0.01(因为如果知道明天是阴天,那么下雨的概率很大,信息量少),这样相减后为1.99。在获得阴天这个信息后,下雨信息不确定性减少了1.99,不确定减少了很多,所以信息增益大。也就是说,阴天这个信息对明天下午这一推断来说非常重要。

所以在特征选择的时候常常用信息增益,如果信息增益大的话那么这个特征对于分类来说很关键,下面我们要讲到的决策树就是这样来找特征的。

信息熵举例

那来玩个猜测游戏,猜猜这32支球队那个是冠军。并且猜测错误付出代价。每猜错一次给一块钱,告诉我是否猜对了,那么我需要掏多少钱才能知道谁是冠军? (前提是:不知道任意球队的信息、历史比赛记录、实力等)

为了使代价最小,可以使用二分法猜测:

我可以把球编上号,从1到32,然后提问:冠 军在1-16号吗?依次询问,只需要五次,就可以知道结果。

我们来看这个式子:

  • 32支球队,log32=5比特 (log的底为2, 最多5次可以猜中)
  • 64支球队,log64=6比特 (最多6次可以猜中, 数据增加一倍只需要多猜一次)
信息熵的定义

信息熵公式:

H的专业术语称之为信息熵,单位为比特。信息熵H越大证明信息的不确定性越大.

在预测获胜球队的例子中,p为每个球队获胜的概率, 假设概率相等,都为1/32, 那么信息熵的计算

假设每个球队获胜概率相同,即 p1=p2=...=p32=1/32
H = -(p1logp1 + p2logp2 + ... + p32logp32) 
  = -1 * -1*log32
  = log32
  = 5

当这32支球队夺冠的几率相同时,对应的信息熵等于5比特

只要概率发生任意变化,信息熵都比5比特小, 举个极端的例子, 比如确定其中的一个球队100%能够获胜, 则信息熵为0, 即没有任何不确定性.

总结(重要)

信息和消除不确定性是相联系的

当我们得到的额外信息(球队历史比赛情况等等)越多的话,那么我们猜测的代价越小(猜测的不确定性减小)

问题: 回到我们前面的贷款案例,怎么去划分?可以利用当得知某个特征(比如是否有房子)之后,我们能够减少的不确定性大小。越大我们可以认为这个特征很重要。那怎么去衡量减少的不确定性大小呢?

决策树的划分依据之一 - 信息增益

定义与公式

特征A对训练数据集D的信息增益g(D,A): 定义为集合D的信息熵H(D)与特征A给定条件下D的信息条件熵H(D|A)之差,即公式为:

公式的详细解释:

注:信息增益表示得知特征X的信息, 随着不确定性减少的程度使得特征Y的信息熵减少的程度

贷款特征重要计算

我们以年龄特征来计算:

  • g(D, 年龄) = H(D) -H(D|年龄) = 0.971-[5/15H(青年)+5/15H(中年)+5/15H(老年]
  • H(D) = -(6/15log(6/15)+9/15log(9/15))=0.971
  • H(青年) = -(3/5log(3/5) +2/5log(2/5))
    H(中年)=-(3/5log(3/5) +2/5log(2/5))
    H(老年)=-(4/5log(4/5)+1/5log(1/5))

我们以A1、A2、A3、A4代表年龄、有工作、有自己的房子和贷款情况。最终计算的结果

  • g(D, A1) = 0.313
  • g(D, A2) = 0.324
  • g(D, A3) = 0.420
  • g(D, A4) = 0.363

所以我们选择A3 作为划分的第一个特征。这样我们就可以慢慢建立一棵树

决策树的三种算法实现

当然决策树的原理不止信息增益这一种,还有其他方法。但是原理都类似,我们就不去举例计算。

  • ID3
    • 信息增益最大的准则
  • C4.5
    • 信息增益比最大的准则
  • CART
    • 分类树: 基尼(gini)系数最小的准则 在sklearn中可以选择划分的默认原则
    • 优势:划分更加细致(从后面例子的树显示来理解)

决策树API

  • class sklearn.tree.DecisionTreeClassifier(criterion=’gini’, max_depth=None,random_state=None)
    • 决策树分类器
    • criterion:默认是’gini’系数,也可以选择信息增益的熵’entropy’
    • max_depth:树的深度大小
    • random_state:随机数种子
  • 其中会有些超参数:max_depth:树的深度大小
    • 其它超参数我们会结合随机森林讲解

案例:泰坦尼克号乘客生存预测

  • 泰坦尼克号数据

在泰坦尼克号和titanic2数据帧描述泰坦尼克号上的个别乘客的生存状态。这里使用的数据集是由各种研究人员开始的。其中包括许多研究人员创建的旅客名单,由Michael A. Findlay编辑。我们提取的数据集中的特征是票的类别,存活,乘坐班,年龄,登陆,home.dest,房间,票,船和性别。

1、乘坐班是指乘客班(1,2,3),是社会经济阶层的代表。

2、其中age数据存在缺失。

数据:

泰坦尼克号幸存者数据集(Titanic Dataset)-飞桨AI Studio - 人工智能学习实训社区 (baidu.com)

分析

  • 选择我们认为重要的几个特征 ['pclass', 'age', 'sex']
  • 填充缺失值
  • 特征中出现类别符号,需要进行one-hot编码处理(DictVectorizer)
    • x.to_dict(orient="records") 需要将数组特征转换成字典数据
  • 数据集划分
  • 决策树分类预测

代码

# Imports
import pandas as pd
from sklearn.tree import DecisionTreeClassifier
from sklearn.feature_extraction import DictVectorizer
from sklearn.model_selection import train_test_split


%matplotlib inline

# 1、获取数据
titan = pd.read_csv('../data/data54777/train.csv')

# 2、数据的处理
X = titan[['Pclass', 'Age', 'Sex']]
y = titan['Survived']

# print(x , y)
# 缺失值需要处理,将特征当中有类别的这些特征进行字典特征抽取
# inplace 替换原始数据
X['Age'].fillna(X['Age'].mean(), inplace=True)

# 对于x转换成字典数据x.to_dict(orient="records")
dict = DictVectorizer(sparse=False)

X = dict.fit_transform(X.to_dict(orient="records"))

# ['Age', 'Pclass', 'Sex=female', 'Sex=male']
print(dict.get_feature_names())
print(X)

['Age', 'Pclass', 'Sex=female', 'Sex=male']
[[22.          3.          0.          1.        ]
 [38.          1.          1.          0.        ]
 [26.          3.          1.          0.        ]
 ...
 [29.69911765  3.          1.          0.        ]
 [26.          1.          0.          1.        ]
 [32.          3.          0.          1.        ]]
# 分割训练集合测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

# 进行决策树的建立和预测
dc = DecisionTreeClassifier(max_depth=5)

dc.fit(X_train, y_train)

print("预测的准确率为:", dc.score(X_test, y_test))
预测的准确率为: 0.7947761194029851
运行时长:7毫秒结束时间:2021-12-30 22:26:20

由于决策树类似一个树的结构,我们可以保存到本地显示

保存树的结构到dot文件

  • 1、sklearn.tree.export_graphviz() 该函数能够导出DOT格式
    • tree.export_graphviz(estimator,out_file='tree.dot’,feature_names=[‘’,’’])
  • 2、工具:(能够将dot文件转换为pdf、png)
    • 安装graphviz
    • ubuntu:sudo apt-get install graphviz
    • Mac:brew install graphviz
  • 3、运行命令
    • 然后我们运行这个命令
    • dot -Tpng tree.dot -o tree.png
from sklearn.tree import export_graphviz

export_graphviz(dc, out_file="./tree.dot", feature_names=['Age', 'Pclass', 'Sex=female', 'Sex=male'])

./tree.dot

digraph Tree {
node [shape=box] ;
0 [label="Sex=male <= 0.5\ngini = 0.473\nsamples = 623\nvalue = [384, 239]"] ;
1 [label="Pclass <= 2.5\ngini = 0.379\nsamples = 224\nvalue = [57, 167]"] ;
0 -> 1 [labeldistance=2.5, labelangle=45, headlabel="True"] ;
2 [label="Age <= 2.5\ngini = 0.096\nsamples = 119\nvalue = [6, 113]"] ;
1 -> 2 ;
3 [label="gini = 0.0\nsamples = 1\nvalue = [1, 0]"] ;
2 -> 3 ;
4 [label="Age <= 24.5\ngini = 0.081\nsamples = 118\nvalue = [5, 113]"] ;
2 -> 4 ;
5 [label="gini = 0.0\nsamples = 37\nvalue = [0, 37]"] ;
4 -> 5 ;
6 [label="Age <= 27.5\ngini = 0.116\nsamples = 81\nvalue = [5, 76]"] ;
4 -> 6 ;
7 [label="gini = 0.469\nsamples = 8\nvalue = [3, 5]"] ;
6 -> 7 ;
8 [label="gini = 0.053\nsamples = 73\nvalue = [2, 71]"] ;
6 -> 8 ;
9 [label="Age <= 36.0\ngini = 0.5\nsamples = 105\nvalue = [51, 54]"] ;
1 -> 9 ;
10 [label="Age <= 1.5\ngini = 0.496\nsamples = 97\nvalue = [44, 53]"] ;
9 -> 10 ;
11 [label="gini = 0.0\nsamples = 3\nvalue = [0, 3]"] ;
10 -> 11 ;
12 [label="Age <= 3.5\ngini = 0.498\nsamples = 94\nvalue = [44, 50]"] ;
10 -> 12 ;
13 [label="gini = 0.32\nsamples = 5\nvalue = [4, 1]"] ;
12 -> 13 ;
14 [label="gini = 0.495\nsamples = 89\nvalue = [40, 49]"] ;
12 -> 14 ;
15 [label="Age <= 55.5\ngini = 0.219\nsamples = 8\nvalue = [7, 1]"] ;
9 -> 15 ;
16 [label="gini = 0.0\nsamples = 7\nvalue = [7, 0]"] ;
15 -> 16 ;
17 [label="gini = 0.0\nsamples = 1\nvalue = [0, 1]"] ;
15 -> 17 ;
18 [label="Age <= 6.5\ngini = 0.296\nsamples = 399\nvalue = [327, 72]"] ;
0 -> 18 [labeldistance=2.5, labelangle=-45, headlabel="False"] ;
19 [label="Pclass <= 2.5\ngini = 0.391\nsamples = 15\nvalue = [4, 11]"] ;
18 -> 19 ;
20 [label="gini = 0.0\nsamples = 6\nvalue = [0, 6]"] ;
19 -> 20 ;
21 [label="Age <= 2.5\ngini = 0.494\nsamples = 9\nvalue = [4, 5]"] ;
19 -> 21 ;
22 [label="Age <= 1.5\ngini = 0.48\nsamples = 5\nvalue = [3, 2]"] ;
21 -> 22 ;
23 [label="gini = 0.444\nsamples = 3\nvalue = [1, 2]"] ;
22 -> 23 ;
24 [label="gini = 0.0\nsamples = 2\nvalue = [2, 0]"] ;
22 -> 24 ;
25 [label="Age <= 3.5\ngini = 0.375\nsamples = 4\nvalue = [1, 3]"] ;
21 -> 25 ;
26 [label="gini = 0.0\nsamples = 1\nvalue = [0, 1]"] ;
25 -> 26 ;
27 [label="gini = 0.444\nsamples = 3\nvalue = [1, 2]"] ;
25 -> 27 ;
28 [label="Pclass <= 1.5\ngini = 0.267\nsamples = 384\nvalue = [323, 61]"] ;
18 -> 28 ;
29 [label="Age <= 37.5\ngini = 0.444\nsamples = 81\nvalue = [54, 27]"] ;
28 -> 29 ;
30 [label="Age <= 33.5\ngini = 0.494\nsamples = 45\nvalue = [25, 20]"] ;
29 -> 30 ;
31 [label="gini = 0.467\nsamples = 35\nvalue = [22, 13]"] ;
30 -> 31 ;
32 [label="gini = 0.42\nsamples = 10\nvalue = [3, 7]"] ;
30 -> 32 ;
33 [label="Age <= 47.5\ngini = 0.313\nsamples = 36\nvalue = [29, 7]"] ;
29 -> 33 ;
34 [label="gini = 0.0\nsamples = 14\nvalue = [14, 0]"] ;
33 -> 34 ;
35 [label="gini = 0.434\nsamples = 22\nvalue = [15, 7]"] ;
33 -> 35 ;
36 [label="Age <= 32.25\ngini = 0.199\nsamples = 303\nvalue = [269, 34]"] ;
28 -> 36 ;
37 [label="Age <= 30.5\ngini = 0.24\nsamples = 237\nvalue = [204, 33]"] ;
36 -> 37 ;
38 [label="gini = 0.208\nsamples = 221\nvalue = [195, 26]"] ;
37 -> 38 ;
39 [label="gini = 0.492\nsamples = 16\nvalue = [9, 7]"] ;
37 -> 39 ;
40 [label="Age <= 43.5\ngini = 0.03\nsamples = 66\nvalue = [65, 1]"] ;
36 -> 40 ;
41 [label="gini = 0.0\nsamples = 40\nvalue = [40, 0]"] ;
40 -> 41 ;
42 [label="gini = 0.074\nsamples = 26\nvalue = [25, 1]"] ;
40 -> 42 ;
}

文件里描述了决策树, 但这样看不是很直观, 可以安装 graphviz 工具将dot文件转成图片文件:

sudo apt-get install graphviz
dot -Tpng tree.dot -o tree.png

tree.png

决策树总结

  • 优点:
    • 简单的理解和解释,树木可视化。
  • 缺点:
    • 决策树学习者可以创建不能很好地推广数据的过于复杂的树,这被称为过拟合。
  • 改进:
    • 减枝cart算法(决策树API当中已经实现,随机森林参数调优有相关介绍)
    • 随机森林

注:企业重要决策,由于决策树很好的分析能力,在决策过程应用较多, 可以优先选择区分度大的特征.

集成学习方法之随机森林

  • 学习目标
    • 说名随机森林每棵决策树的建立过程
    • 知道为什么需要随机有放回(Bootstrap)的抽样
    • 说明随机森林的超参数
  • 具体应用
    • 泰坦尼克号乘客生存预测

什么是集成学习方法

集成学习通过建立几个模型组合的来解决单一预测问题。它的工作原理是生成多个分类器/模型,各自独立地学习和作出预测。这些预测最后结合成组合预测,因此优于任何一个单分类的做出预测。

什么是随机森林

在机器学习中,随机森林是一个包含多个决策树的分类器,并且其输出的类别是由个别树输出的类别的众数而定。

例如, 如果你训练了5个树, 其中有4个树的结果是True, 1个数的结果是False, 那么最终投票结果就是True

随机森林原理过程

学习算法根据下列算法而建造每棵树:

  • 用N来表示训练用例(样本)的个数,M表示特征数目。
    • 一次随机选出一个样本,重复N次, (有可能出现重复的样本)
    • 随机去选出m个特征, m <<M,建立决策树
  • 采取bootstrap抽样

为什么采用BootStrap抽样

  • 为什么要随机抽样训练集?  
    • 如果不进行随机抽样,每棵树的训练集都一样,那么最终训练出的树分类结果也是完全一样的
  • 为什么要有放回地抽样?
    • 如果不是有放回的抽样,那么每棵树的训练样本都是不同的,都是没有交集的,这样每棵树都是“有偏的”,都是绝对“片面的”(当然这样说可能不对),也就是说每棵树训练出来都是有很大的差异的;而随机森林最后分类取决于多棵树(弱分类器)的投票表决。

API

  • class sklearn.ensemble.RandomForestClassifier(n_estimators=10, criterion=’gini’, max_depth=None, bootstrap=True, random_state=None, min_samples_split=2)
    • 随机森林分类器
    • n_estimators:integer,optional(default = 10)森林里的树木数量120,200,300,500,800,1200
    • criteria:string,可选(default =“gini”)分割特征的测量方法
    • max_depth:integer或None,可选(默认=无)树的最大深度 5,8,15,25,30
    • max_features="auto”,每个决策树的最大特征数量
      • If "auto", then max_features=sqrt(n_features).
      • If "sqrt", then max_features=sqrt(n_features) (same as "auto").
      • If "log2", then max_features=log2(n_features).
      • If None, then max_features=n_features.
    • bootstrap:boolean,optional(default = True)是否在构建树时使用放回抽样
    • min_samples_split:节点划分最少样本数
    • min_samples_leaf:叶子节点的最小样本数
  • 超参数:n_estimator, max_depth, min_samples_split,min_samples_leaf

代码

import pandas as pd
from sklearn.feature_extraction import DictVectorizer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import train_test_split

# 1、获取数据
titan = pd.read_csv('../data/data54777/train.csv')

# 2、数据的处理
X = titan[['Pclass', 'Age', 'Sex']]
y = titan['Survived']

# print(x , y)
# 缺失值需要处理,将特征当中有类别的这些特征进行字典特征抽取
# inplace 替换原始数据
X['Age'].fillna(X['Age'].mean(), inplace=True)

# 对于x转换成字典数据x.to_dict(orient="records")
dict = DictVectorizer(sparse=False)

X = dict.fit_transform(X.to_dict(orient="records"))

# 分割训练集合测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

# 随机森林去进行预测
rf = RandomForestClassifier()

param = {"n_estimators": [120,200,300,500,800,1200], "max_depth": [5, 8, 15, 25, 30]}

# 超参数调优
gc = GridSearchCV(rf, param_grid=param, cv=2)

gc.fit(X_train, y_train)

print("随机森林预测的准确率为:", gc.score(X_test, y_test))
随机森林预测的准确率为: 0.7574626865671642

总结

  • 在当前所有算法中,具有极好的准确率
  • 能够有效地运行在大数据集上,处理具有高维特征的输入样本,而且不需要降维
  • 能够评估各个特征在分类问题上的重要性

回顾:

1、估计器的工作流程是什么?

答案:

第一步: 实例化估计器

第二步: 调用估计器的fit函数, 用训练集的特征值和目标值训练

第三步: 调用预测函数predict, 用测试集的特征值预测

2、决策树的划分依据是什么?(课程介绍的主要方法)

答案: 根据更具信息增益的属性来划分.

3、编程: 通过K近邻算法对鸢尾花数据集进行分类预测

Views: 180

02 特征工程

2.1 数据集

  • 目标
    • 知道数据集的分为训练集和测试集
    • 会使用sklearn的数据集

2.1.1 可用数据集

Kaggle网址:https://www.kaggle.com/datasets

UCI数据集网址: http://archive.ics.uci.edu/ml/

scikit-learn网址:http://scikit-learn.org/stable/datasets/index.html#datasets

1 Scikit-learn工具介绍

  • Python语言的机器学习工具
  • Scikit-learn包括许多知名的机器学习算法的实现
  • Scikit-learn文档完善,容易上手,丰富的API
  • 目前稳定版本0.19.1

2 安装

pip3 install Scikit-learn==0.19.1

安装好之后可以通过以下命令查看是否安装成功

import sklearn
  • 注:安装scikit-learn需要Numpy, Scipy等库

3 Scikit-learn包含的内容

  • 分类、聚类、回归
  • 特征工程
  • 模型选择、调优

2.1.2 sklearn数据集

1 scikit-learn数据集API介绍

  • sklearn.datasets
    • 加载获取流行数据集
    • datasets.load_*()
      • 获取小规模数据集,数据包含在datasets里
    • datasets.fetch_*(data_home=None)
      • 获取大规模数据集,需要从网络上下载,函数的第一个参数是data_home,表示数据集下载的目录,默认是 ~/scikit_learn_data/

2 sklearn小数据集

  • sklearn.datasets.load_iris() 加载并返回鸢尾花数据集
  • sklearn.datasets.load_boston() 加载并返回波士顿房价数据集

3 sklearn大数据集

  • sklearn.datasets.fetch_20newsgroups(data_home=None,subset=‘train’)
    • subset:'train'或者'test','all',可选,选择要加载的数据集。
    • 训练集用“train”,测试集用“test”,即作为训练集也作为测试机则用“all”

4 sklearn数据集的使用

  • 以鸢尾花数据集为例:
iris(鸢尾花) 数据集
Iris数据集

sklearn数据集返回值介绍

  • load和fetch返回的数据类型datasets.base.Bunch(继承字典格式,类似JSON)
    • data:特征数组,是 [n_samples * n_features] 的二维 numpy.ndarray 数组
    • target:标签数组,是 n_samples 的一维 numpy.ndarray 数组
    • DESCR:数据集描述
    • feature_names:特征名称. 新闻数据,手写数字、回归数据集没有
    • target_names:标签名称
from sklearn.datasets import load_iris
# 获取鸢尾花数据集
iris = load_iris()
# 返回值是一个继承自字典的Bunch
print("鸢尾花数据集的返回值:\n", iris)
# 打印数据集的所有属性
print("鸢尾花的特征值:\n", iris.data)   # 也可以 iris["data"] 方式进行索引
print("鸢尾花的目标值:\n", iris.target)
print("鸢尾花特征的名字:\n", iris.feature_names)
print("鸢尾花目标值的名字:\n", iris.target_names)
print("鸢尾花数据集的描述:\n", iris.DESCR)
输出

思考:拿到的数据是否全部都用来训练一个模型?

2.1.3 数据集的划分

机器学习一般的数据集会划分为两个部分:

  • 训练数据:用于训练,构建模型
  • 测试数据:在模型检验时使用,用于评估模型是否有效

划分比例:

  • 训练集:70% 80% 75%
  • 测试集:30% 20% 30%

数据集划分api

  • sklearn.model_selection.train_test_split(arrays, *options)
    • x 数据集的特征值
    • y 数据集的标签值
    • test_size 测试集的大小,一般为float
    • random_state 随机数种子,不同的种子会造成不同的随机采样结果。相同的种子采样结果相同。
    • return 测试集特征训练集特征值值,训练标签,测试标签(默认随机取)
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

def datasets_demo():
    """
    对鸢尾花数据集的演示
    :return: None
    """
    # 1、获取鸢尾花数据集
    iris = load_iris()
    print("鸢尾花数据集的返回值:\n", iris)
    # 返回值是一个继承自字典的Bench
    print("鸢尾花的特征值:\n", iris["data"])
    print("鸢尾花的目标值:\n", iris.target)
    print("鸢尾花特征的名字:\n", iris.feature_names)
    print("鸢尾花目标值的名字:\n", iris.target_names)
    print("鸢尾花的描述:\n", iris.DESCR)

    # 2、对鸢尾花数据集进行分割
    # 训练集的特征值x_train 测试集的特征值x_test 训练集的目标值y_train 测试集的目标值y_test
    x_train, x_test, y_train, y_test = train_test_split(iris.data, iris.target, random_state=22)
    print("x_train:\n", x_train.shape)
    # 随机数种子
    x_train1, x_test1, y_train1, y_test1 = train_test_split(iris.data, iris.target, random_state=6)
    x_train2, x_test2, y_train2, y_test2 = train_test_split(iris.data, iris.target, random_state=6)
    print("如果随机数种子不一致:\n", x_train == x_train1)
    print("如果随机数种子一致:\n", x_train1 == x_train2)

    return None
输出

2.2 特征工程介绍

学习目标

  • 目标
    • 了解特征工程在机器学习当中的重要性
    • 知道特征工程的分类

2.2.1 为什么需要特征工程(Feature Engineering)

机器学习领域的大神Andrew Ng(吴恩达)老师说“Coming up with features is difficult, time-consuming, requires expert knowledge. “Applied machine learning” is basically feature engineering. ” 注:业界广泛流传:数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。

2.2.2 什么是特征工程

特征工程是使用专业背景知识和技巧处理数据使得特征能在机器学习算法上发挥更好的作用的过程

  • 意义:会直接影响机器学习的效果

2.2.3 特征工程的位置与数据处理的比较

  • pandas:一个数据读取非常方便以及基本的处理格式的工具
  • sklearn:对于特征的处理提供了强大的接口

特征工程包含内容

  • 特征抽取
  • 特征预处理
  • 特征降维

2.3 特征提取

学习目标

  • 目标
    • 应用DictVectorizer实现对类别特征进行数值化、离散化
    • 应用CountVectorizer实现对文本特征进行数值化
    • 应用TfidfVectorizer实现对文本特征进行数值化
    • 说出两种文本特征提取的方式区别

什么是特征提取呢?

2.3.1 特征提取

1 将任意数据(如文本或图像)转换为可用于机器学习的数字特征

  • 字典特征提取(特征离散化,矢量化)
  • 文本特征提取
  • 图像特征提取(深度学习将介绍)

注:特征值化是为了计算机更好的去理解数据

2 特征提取API

sklearn.feature_extraction

2.3.2 字典特征提取

作用:对字典数据进行特征值化

  • sklearn.feature_extraction.DictVectorizer(sparse=True,…)
    • DictVectorizer.fit_transform(X) X:字典或者包含字典的迭代器返回值:返回sparse矩阵
    • DictVectorizer.inverse_transform(X) X:array数组或者sparse矩阵 返回值:转换之前数据格式
    • DictVectorizer.get_feature_names() 返回类别名称

1 应用

我们对以下数据进行特征提取

[{'city': '北京','temperature':100}
{'city': '上海','temperature':60}
{'city': '深圳','temperature':30}]

2 流程分析

  • 实例化类DictVectorizer
  • 调用fit_transform方法输入数据并转换(注意返回格式)
from sklearn.feature_extraction import DictVectorizer
​
def dict_demo():
    """
    对字典类型的列表数据进行特征抽取
    :return: None
    """
    data = [{'city': '北京','temperature':100}, {'city': '上海','temperature':60}, {'city': '深圳','temperature':30}]
    # 1、实例化一个转换器类
    transfer = DictVectorizer()
    # 2、调用fit_transform
    data = transfer.fit_transform(data)
    print("返回的结果:\n", data)
    # 打印特征名字
    print("特征名字:\n", transfer.get_feature_names())
​
    return None

dict_demo()

注意观察结果:

返回的结果: 
   (0, 1)   1.0
  (0, 3)    100.0
  (1, 0)    1.0
  (1, 3)    60.0
  (2, 2)    1.0
  (2, 3)    30.0
特征名字:
 ['city=上海', 'city=北京', 'city=深圳', 'temperature']

默认是返回稀疏矩阵,结果格式为: (行坐标,列坐标) 非零值 这个结果并不是我们想要看到的,所以加上sparse=False参数

transfer = DictVectorizer(sparse=False)
得到想要的结果(完整的矩阵):
返回的结果:
 [[  0.   1.   0. 100.]
 [  1.   0.   0.  60.]
 [  0.   0.   1.  30.]]
特征名字:
 ['city=上海', 'city=北京', 'city=深圳', 'temperature']
之前在学习pandas中的离散化的时候,也实现了类似的效果。 我们把这个处理数据的技巧叫做”one-hot“编码:
转化为:
one hot编码是将类别变量转换为机器学习算法易于利用的一种形式的过程. 假设“花”的特征可能的取值为daffodil(水仙)、lily(百合)、rose(玫瑰)。one hot编码将其转换为三个特征:is_daffodilis_lilyis_rose,这些特征都是二进制的(只有0和1两个取值)。

2.3 总结

对于特征当中存在类别信息的我们都会做one-hot编码处理

2.3.3 文本特征提取

作用:对文本数据进行特征值化
  • sklearn.feature_extraction.text.CountVectorizer(stop_words=[])
    • 返回词频矩阵
  • CountVectorizer.fit_transform(X) X:文本或者包含文本字符串的可迭代对象 返回值:返回sparse矩阵(默认)
  • CountVectorizer.inverse_transform(X) X:array数组或者sparse矩阵 返回值:转换之前的数据
  • CountVectorizer.get_feature_names() 返回值:单词列表
  • sklearn.feature_extraction.text.TfidfVectorizer

1 应用

我们对以下数据进行特征提取
["life is short,i like python",
"life is too long,i dislike python"]

2 流程分析

  • 实例化类CountVectorizer
  • 调用fit_transform方法输入数据并转换 (注意返回格式,利用toarray()进行sparse矩阵转换array数组)
from sklearn.feature_extraction.text import CountVectorizer

def text_count_demo():
    """
    对文本进行特征抽取,countvetorizer
    :return: None
    """
    data = ["life is short,i like like python", "life is too long,i dislike python"]
    # 1、实例化一个转换器类
    # transfer = CountVectorizer(sparse=False)
    transfer = CountVectorizer()
    # 2、调用fit_transform
    data = transfer.fit_transform(data)
    print("文本特征抽取的结果:\n", data.toarray())
    print("返回特征名字:\n", transfer.get_feature_names())

    return None
返回结果:
文本特征抽取的结果:
 [[0 1 1 2 0 1 1 0]
 [1 1 1 0 1 1 0 1]]
返回特征名字:
 ['dislike', 'is', 'life', 'like', 'long', 'python', 'short', 'too']

问题:如果我们将数据替换成中文?

"人生苦短,我喜欢Python" 
"生活太长久,我不喜欢Python"
那么最终得到的结果是
文本特征抽取的结果:
 [[1 0 1 0]
 [0 1 0 1]]
返回特征名字:
 ['人生苦短', '我不喜欢python', '我喜欢python', '生活太长久']
为什么会得到这样的结果呢,仔细分析之后会发现英文默认是以空格分开的。其实就达到了一个分词的效果,所以我们要对中文进行中文分词处理

3 jieba分词处理

  • jieba.cut()
    • 返回词语组成的生成器
需要安装下jieba库
pip3 install jieba

4 案例分析

对以下三句话进行特征值化
今天很残酷,明天更残酷,后天很美好,
但绝对大部分是死在明天晚上,所以每个人不要放弃今天。

我们看到的从很远星系来的光是在几百万年之前发出的,
这样当我们看到宇宙时,我们是在看它的过去。

如果只用一种方式了解某样事物,你就不会真正了解它。
了解事物真正含义的秘密取决于如何将其与我们所了解的事物相联系。
  • 分析
    • 准备句子,利用jieba.cut进行分词
    • 实例化CountVectorizer
    • 将分词结果变成字符串当作fit_transform的输入值
from sklearn.feature_extraction.text import CountVectorizer
import jieba

def cut_word(text):
    """
    对中文进行分词
    "我爱北京天安门"————>"我 爱 北京 天安门"
    :param text:
    :return: text
    """
    # 用jieba对中文字符串进行分词
    text = " ".join(list(jieba.cut(text)))

    return text

def text_chinese_count_demo2():
    """
    对中文进行特征抽取
    :return: None
    """
    data = ["一种还是一种今天很残酷,明天更残酷,后天很美好,但绝对大部分是死在明天晚上,所以每个人不要放弃今天。",
            "我们看到的从很远星系来的光是在几百万年之前发出的,这样当我们看到宇宙时,我们是在看它的过去。",
            "如果只用一种方式了解某样事物,你就不会真正了解它。了解事物真正含义的秘密取决于如何将其与我们所了解的事物相联系。"]
    # 将原始数据转换成分好词的形式
    text_list = []
    for sent in data:
        text_list.append(cut_word(sent))
    print(text_list)

    # 1、实例化一个转换器类
    # transfer = CountVectorizer(sparse=False)
    transfer = CountVectorizer()
    # 2、调用fit_transform
    data = transfer.fit_transform(text_list)
    print("文本特征抽取的结果:\n", data.toarray())
    print("返回特征名字:\n", transfer.get_feature_names())

    return None
返回结果:
Building prefix dict from the default dictionary ...
Dumping model to file cache /var/folders/mz/tzf2l3sx4rgg6qpglfb035_r0000gn/T/jieba.cache
Loading model cost 1.032 seconds.
['一种 还是 一种 今天 很 残酷 , 明天 更 残酷 , 后天 很 美好 , 但 绝对 大部分 是 死 在 明天 晚上 , 所以 每个 人 不要 放弃 今天 。', '我们 看到 的 从 很 远 星系 来 的 光是在 几百万年 之前 发出 的 , 这样 当 我们 看到 宇宙 时 , 我们 是 在 看 它 的 过去 。', '如果 只用 一种 方式 了解 某样 事物 , 你 就 不会 真正 了解 它 。 了解 事物 真正 含义 的 秘密 取决于 如何 将 其 与 我们 所 了解 的 事物 相 联系 。']
Prefix dict has been built succesfully.
文本特征抽取的结果:
 [[2 0 1 0 0 0 2 0 0 0 0 0 1 0 1 0 0 0 0 1 1 0 2 0 1 0 2 1 0 0 0 1 1 0 0 1 0]
 [0 0 0 1 0 0 0 1 1 1 0 0 0 0 0 0 0 1 3 0 0 0 0 1 0 0 0 0 2 0 0 0 0 0 1 0 1]
 [1 1 0 0 4 3 0 0 0 0 1 1 0 1 0 1 1 0 1 0 0 1 0 0 0 1 0 0 0 2 1 0 0 1 0 0 0]]
返回特征名字:
 ['一种', '不会', '不要', '之前', '了解', '事物', '今天', '光是在', '几百万年', '发出', '取决于', '只用', '后天', '含义', '大部分', '如何', '如果', '宇宙', '我们', '所以', '放弃', '方式', '明天', '星系', '晚上', '某样', '残酷', '每个', '看到', '真正', '秘密', '绝对', '美好', '联系', '过去', '还是', '这样']
但如果把这样的词语特征用于分类,会出现什么问题? 请看问题:
文章类型??
该如何处理某个词或短语在多篇文章中出现的次数高这种情况

5 Tf-idf文本特征提取

  • TF-IDF的主要思想是:如果某个词或短语在一篇文章中出现的概率高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。
  • TF-IDF作用:用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。
5.1 公式
  • 词频(term frequency,tf)指的是某一个给定的词语在该文件中出现的频率
  • 逆向文档频率(inverse document frequency,idf)是一个词语普遍重要性的度量。某一特定词语的idf,可以由总文件数目除以包含该词语之文件的数目,再将得到的商取以10为底的对数得到
最终得出结果可以理解为重要程度。
注:假如一篇文件的总词语数是100个,而词语"非常"出现了5次,那么"非常"一词在该文件中的词频就是5/100=0.05。而计算文件频率(IDF)的方法是以文件集的文件总数,除以出现"非常"一词的文件数。所以,如果文件总数是10,000,000份的话,"非常"一词在1,000份文件出现过,其逆向文件频率就是lg(10,000,000 / 1,000)=3。最后"非常"对于这篇文档的tf-idf的分数为0.05 * 3=0.15
5.2 案例
from sklearn.feature_extraction.text import TfidfVectorizer
import jieba

def cut_word(text):
    """
    对中文进行分词
    "我爱北京天安门"————>"我 爱 北京 天安门"
    :param text:
    :return: text
    """
    # 用jieba对中文字符串进行分词
    text = " ".join(list(jieba.cut(text)))

    return text

def text_chinese_tfidf_demo():
    """
    对中文进行特征抽取
    :return: None
    """
    data = ["一种还是一种今天很残酷,明天更残酷,后天很美好,但绝对大部分是死在明天晚上,所以每个人不要放弃今天。",
            "我们看到的从很远星系来的光是在几百万年之前发出的,这样当我们看到宇宙时,我们是在看它的过去。",
            "如果只用一种方式了解某样事物,你就不会真正了解它。了解事物真正含义的秘密取决于如何将其与我们所了解的事物相联系。"]
    # 将原始数据转换成分好词的形式
    text_list = []
    for sent in data:
        text_list.append(cut_word(sent))
    print(text_list)

    # 1、实例化一个转换器类
    # transfer = CountVectorizer(sparse=False)
    transfer = TfidfVectorizer(stop_words=['一种', '不会', '不要'])
    # 2、调用fit_transform
    data = transfer.fit_transform(text_list)
    print("文本特征抽取的结果:\n", data.toarray())
    print("返回特征名字:\n", transfer.get_feature_names())

    return None
返回结果:
Building prefix dict from the default dictionary ...
Loading model from cache /var/folders/mz/tzf2l3sx4rgg6qpglfb035_r0000gn/T/jieba.cache
Loading model cost 0.856 seconds.
Prefix dict has been built succesfully.
['一种 还是 一种 今天 很 残酷 , 明天 更 残酷 , 后天 很 美好 , 但 绝对 大部分 是 死 在 明天 晚上 , 所以 每个 人 不要 放弃 今天 。', '我们 看到 的 从 很 远 星系 来 的 光是在 几百万年 之前 发出 的 , 这样 当 我们 看到 宇宙 时 , 我们 是 在 看 它 的 过去 。', '如果 只用 一种 方式 了解 某样 事物 , 你 就 不会 真正 了解 它 。 了解 事物 真正 含义 的 秘密 取决于 如何 将 其 与 我们 所 了解 的 事物 相 联系 。']
文本特征抽取的结果:
 [[ 0.          0.          0.          0.43643578  0.          0.          0.
   0.          0.          0.21821789  0.          0.21821789  0.          0.
   0.          0.          0.21821789  0.21821789  0.          0.43643578
   0.          0.21821789  0.          0.43643578  0.21821789  0.          0.
   0.          0.21821789  0.21821789  0.          0.          0.21821789
   0.        ]
 [ 0.2410822   0.          0.          0.          0.2410822   0.2410822
   0.2410822   0.          0.          0.          0.          0.          0.
   0.          0.2410822   0.55004769  0.          0.          0.          0.
   0.2410822   0.          0.          0.          0.          0.48216441
   0.          0.          0.          0.          0.          0.2410822
   0.          0.2410822 ]
 [ 0.          0.644003    0.48300225  0.          0.          0.          0.
   0.16100075  0.16100075  0.          0.16100075  0.          0.16100075
   0.16100075  0.          0.12244522  0.          0.          0.16100075
   0.          0.          0.          0.16100075  0.          0.          0.
   0.3220015   0.16100075  0.          0.          0.16100075  0.          0.
   0.        ]]
返回特征名字:
 ['之前', '了解', '事物', '今天', '光是在', '几百万年', '发出', '取决于', '只用', '后天', '含义', '大部分', '如何', '如果', '宇宙', '我们', '所以', '放弃', '方式', '明天', '星系', '晚上', '某样', '残酷', '每个', '看到', '真正', '秘密', '绝对', '美好', '联系', '过去', '还是', '这样']

6 Tf-idf的重要性

tf-idf 是分类机器学习算法进行文章分类中前期数据主要处理方式
TF-IDF是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。TF-IDF加权的各种形式常被搜索引擎应用,作为文件与用户查询之间相关程度的度量或评级。除了TF-IDF以外,因特网上的搜索引擎还会使用基于链接分析的评级方法,以确定文件在搜寻结果中出现的顺序。

TF-IDF

2.4 特征预处理

学习目标

  • 目标
    • 了解数值型数据、类别型数据特点
    • 应用MinMaxScaler实现对特征数据进行归一化
    • 应用StandardScaler实现对特征数据进行标准化
什么是特征预处理?
特征预处理

2.4.1 什么是特征预处理

# scikit-learn的解释
provides several common utility functions and transformer classes to change raw feature vectors into a representation that is more suitable for the downstream estimators.
翻译过来:通过一些转换函数将特征数据转换成更加适合算法模型的特征数据过程 可以通过上面那张图来理解

1 包含内容

  • 数值型数据的无量纲化:
    • 归一化
    • 标准化

2 特征预处理API

sklearn.preprocessing
为什么我们要进行归一化/标准化?
  • 特征的单位或者大小相差较大,或者某特征的方差相比其他的特征要大出几个数量级容易影响(支配)目标结果,使得一些算法无法学习到其它的特征
以男性约会对象样本数据为例
特征(Features):
  • 里程数(milage): 越高说明工作性质需要频繁出差
  • 冰淇淋消耗公升数(Liters): 越高说明体态约丰满, 饮食无节制或者缺少运动
  • 玩游戏消耗时间占比(ConsumTime): 越高说明越爱玩游戏, 越宅, 缺少正常社交活动

标签(Target):

  • 非常喜欢(largeDoses)
  • 有些喜欢(smallDoses)
  • 不喜欢 ( didntLike)
具体数据如下:
我们需要用到一些方法进行归一化或者标准化,使不同规格的数据转换易于处理和分析的规模.

2.4.2 归一化

1 定义

通过对原始数据进行变换把数据映射到(默认为[0,1])之间

2 公式

作用于每一列,max为一列的最大值,min为一列的最小值,那么X’’为最终结果,mx,mi分别为指定区间值默认mx为1,mi为0

那么怎么理解这个过程呢?我们通过一个例子
这只是第一步, 还需要除以该列最大值和最小值的差值

3 API

  • sklearn.preprocessing.MinMaxScaler (feature_range=(0,1)… )
    • MinMaxScalar.fit_transform(X)
      • X:numpy array格式的数据[n_samples,n_features]
    • 返回值:转换后的形状相同的array

4 数据计算

我们对以下数据进行运算,在dating.txt中。保存的就是之前的约会对象数据
milage,Liters,Consumtime,target
40920,8.326976,0.953952,3
14488,7.153469,1.673904,2
26052,1.441871,0.805124,1
75136,13.147394,0.428964,1
38344,1.669788,0.134296,1
  • 分析
1、实例化MinMaxScalar 2、通过fit_transform转换
import pandas as pd
from sklearn.preprocessing import MinMaxScaler

def minmax_demo():
    """
    归一化演示
    :return: None
    """
    data = pd.read_csv("dating.txt")
    print(data)
    # 1、实例化一个转换器类
    transfer = MinMaxScaler(feature_range=(2, 3))
    # 2、调用fit_transform
    data = transfer.fit_transform(data[['milage','Liters','Consumtime']])
    print("最小值最大值归一化处理的结果:\n", data)

    return None
返回结果:
     milage     Liters  Consumtime  target
0     40920   8.326976    0.953952       3
1     14488   7.153469    1.673904       2
2     26052   1.441871    0.805124       1
3     75136  13.147394    0.428964       1
..      ...        ...         ...     ...
998   48111   9.134528    0.728045       3
999   43757   7.882601    1.332446       3

[1000 rows x 4 columns]
最小值最大值归一化处理的结果:
 [[ 2.44832535  2.39805139  2.56233353]
 [ 2.15873259  2.34195467  2.98724416]
 [ 2.28542943  2.06892523  2.47449629]
 ..., 
 [ 2.29115949  2.50910294  2.51079493]
 [ 2.52711097  2.43665451  2.4290048 ]
 [ 2.47940793  2.3768091   2.78571804]]
问题:如果数据中异常点较多,会有什么影响?
5 归一化总结

注意最大值最小值是变化的,另外,最大值与最小值非常容易受异常点影响,所以这种方法鲁棒性较差,只适合传统精确小数据场景。 怎么办?

2.4.3 标准化

1 定义

通过对原始数据进行变换把数据变换到均值为0,标准差为1范围内

2 公式

作用于每一列,mean为平均值,σ为标准差 标准差定义是总体各单位标准值与其平均数离差平方的算术平均数的平方根。它反映组内个体间的离散程度

所以回到刚才异常点的地方,我们再来看看标准化
  • 对于归一化来说:如果出现异常点,影响了最大值和最小值,那么结果显然会发生改变
  • 对于标准化来说:如果出现异常点,由于具有一定数据量,少量的异常点对于平均值的影响并不大,从而方差改变较小。

3 API

  • sklearn.preprocessing.StandardScaler( )
    • 处理之后每列来说所有数据都聚集在均值0附近标准差差为1
    • StandardScaler.fit_transform(X)
      • X:numpy array格式的数据[n_samples,n_features]
    • 返回值:转换后的形状相同的array

4 数据计算

同样对相亲男士样本数据进行分析, 这里把样本数据集增加到1000条, 适当根据比例添加一些异常数据作为干扰.
  • 分析
1、实例化StandardScaler 2、通过fit_transform转换
import pandas as pd
from sklearn.preprocessing import StandardScaler

def stand_demo():
    """
    标准化演示
    :return: None
    """
    data = pd.read_csv("dating.txt")
    print(data)
    # 1、实例化一个转换器类
    transfer = StandardScaler()
    # 2、调用fit_transform
    data = transfer.fit_transform(data[['milage','Liters','Consumtime']])
    print("标准化的结果:\n", data)
    print("每一列特征的平均值:\n", transfer.mean_)
    print("每一列特征的方差:\n", transfer.var_)

    return None
返回结果:
     milage     Liters  Consumtime  target
0     40920   8.326976    0.953952       3
1     14488   7.153469    1.673904       2
2     26052   1.441871    0.805124       1
..      ...        ...         ...     ...
997   26575  10.650102    0.866627       3
998   48111   9.134528    0.728045       3
999   43757   7.882601    1.332446       3

[1000 rows x 4 columns]
标准化的结果:
 [[ 0.33193158  0.41660188  0.24523407]
 [-0.87247784  0.13992897  1.69385734]
 [-0.34554872 -1.20667094 -0.05422437]
 ..., 
 [-0.32171752  0.96431572  0.06952649]
 [ 0.65959911  0.60699509 -0.20931587]
 [ 0.46120328  0.31183342  1.00680598]]
每一列特征的平均值:
 [  3.36354210e+04   6.55996083e+00   8.32072997e-01]
每一列特征的方差:
 [  4.81628039e+08   1.79902874e+01   2.46999554e-01]

5 标准化总结

可见在已有样本足够多的情况下比较稳定,适合现代嘈杂大数据场景。

2.5 特征降维

学习目标

  • 目标
    • 知道特征选择的嵌入式、过滤式以及包裹氏三种方式
    • 应用VarianceThreshold实现删除低方差特征
    • 了解相关系数的特点和计算
    • 应用相关性系数实现特征选择

2.5.1 降维

降维是指在某些限定条件下,降低随机变量(特征)个数,得到一组“不相关”主变量的过程
  • 降低随机变量的个数
降维
  • 相关特征(correlated feature)
    • 相对湿度与降雨量之间的相关
    • 等等
正是因为在进行训练的时候,我们都是使用特征进行学习。如果特征本身存在问题或者特征之间相关性较强,对于算法学习预测会影响较大

2.5.2 降维的两种方式

  • 特征选择
  • 主成分分析(可以理解一种特征提取的方式)

2.5.3 什么是特征选择

1 定义

数据中包含冗余或无关变量(或称特征、属性、指标等),旨在从原有特征中找出主要特征
鸟的特征选择

2 方法

  • Filter(过滤式):主要探究特征本身特点、特征与特征和目标值之间关联
    • 方差选择法:低方差特征过滤
    • 相关系数
  • Embedded (嵌入式):算法自动选择特征(特征与目标值之间的关联)
    • 决策树:信息熵、信息增益
    • 正则化:L1、L2
    • 深度学习:卷积等
对于Embedded方式,只能在讲解算法的时候在进行介绍,更好的去理解

3 模块

sklearn.feature_selection

4 过滤式

4.1 低方差特征过滤
删除低方差的一些特征,前面讲过方差的意义。再结合方差的大小来考虑这个方式的角度。 特征方差小:某个特征大多样本的值比较相近(特征不明显) 特征方差大:某个特征很多样本的值都有差别(很可能是主要特征)
4.1.1 API
sklearn.feature_selection.VarianceThreshold(threshold = 0.0)

删除所有低方差特征(将过于偏离正常数据的干扰样本删除)

Variance.fit_transform(X)
X:numpy array格式的数据[n_samples,n_features]

返回值:训练集差异低于threshold的特征将被删除。默认值是保留所有非零方差特征,即删除所有样本中具有相同值的特征。

4.1.2 数据计算
我们对某些股票的指标特征之间进行一个筛选,数据在"factor_regression_data/factor_returns.csv"文件当中,除去'index,'date','return'列不考虑(这些类型不匹配,也不是所需要指标) 一共这些特征:
pe_ratio,pb_ratio,market_cap,return_on_asset_net_profit,du_return_on_equity,ev,earnings_per_share,revenue,total_expense
factor_regression_data/factor_returns.csv 的前面5行数据:
index,pe_ratio,pb_ratio,market_cap,return_on_asset_net_profit,du_return_on_equity,ev,earnings_per_share,revenue,total_expense,date,return
000001.XSHE,5.9572,1.1818,85252550922,0.8008,14.9403,1.21144E+12,2.01,20701401000,10882540000,2012/1/31,0.027657228
000002.XSHE,7.0289,1.588,84113358168,1.6463,7.8656,3.00252E+11,0.326,29308369223,23783476901,2012/1/31,0.082351824
000008.XSHE,-262.7461,7.0003,517045520,-0.5678,-0.5943,770517752.6,-0.006,11679829.03,12030080.04,2012/1/31,0.099789003
000060.XSHE,16.476,3.7146,19680455995,5.6036,14.617,28009159185,0.35,9189386878,7935542726,2012/1/31,0.121594828
000069.XSHE,12.5878,2.5616,41727214853,2.8729,10.9097,81247380359,0.271,8951453490,7091397989,2012/1/31,-0.002680815
分析 1、初始化VarianceThreshold,指定阀值方差(为1) 2、调用fit_transform
from sklearn.feature_selection.variance_threshold import VarianceThreshold
import pandas as pd
def variance_demo():
    """
    删除低方差特征——特征选择
    :return: None
    """
    # 读取csv数据中的特征信息, 共12列(C1-C12)
    data = pd.read_csv("./factor_regression_data/factor_returns.csv")
    print(data)
    # 1、实例化一个转换器类
    # 阈值越大, 可能过滤掉的特征就越多
    transfer = VarianceThreshold(threshold=1)
    # 去掉前面的index,以及后面的date和return这几个无法处理或者无用的特征
    data = data.iloc[:, 1:10]
    # 2、调用fit_transform
    # 根据1的阈值去除低方差样本
    data = transfer.fit_transform(data)
    print("删除低方差特征的结果:\n", data)
    print("形状:\n", data.shape)

    return None
返回结果:
            index  pe_ratio  pb_ratio    market_cap  \
0     000001.XSHE    5.9572    1.1818  8.525255e+10   
1     000002.XSHE    7.0289    1.5880  8.411336e+10    
...           ...       ...       ...           ...   
2316  601958.XSHG   52.5408    2.4646  3.287910e+10   
2317  601989.XSHG   14.2203    1.4103  5.911086e+10   

      return_on_asset_net_profit  du_return_on_equity            ev  \
0                         0.8008              14.9403  1.211445e+12   
1                         1.6463               7.8656  3.002521e+11    
...                          ...                  ...           ...   
2316                      2.7444               2.9202  3.883803e+10   
2317                      2.0383               8.6179  2.020661e+11   

      earnings_per_share       revenue  total_expense        date    return  
0                 2.0100  2.070140e+10   1.088254e+10  2012-01-31  0.027657  
1                 0.3260  2.930837e+10   2.378348e+10  2012-01-31  0.082352  
2                -0.0060  1.167983e+07   1.203008e+07  2012-01-31  0.099789   
...                  ...           ...            ...         ...       ...  
2315              0.2200  1.789082e+10   1.749295e+10  2012-11-30  0.137134  
2316              0.1210  6.465392e+09   6.009007e+09  2012-11-30  0.149167  
2317              0.2470  4.509872e+10   4.132842e+10  2012-11-30  0.183629  

[2318 rows x 12 columns]
删除低方差特征的结果:
 [[  5.95720000e+00   1.18180000e+00   8.52525509e+10 ...,   1.21144486e+12
    2.07014010e+10   1.08825400e+10]
 [  7.02890000e+00   1.58800000e+00   8.41133582e+10 ...,   3.00252062e+11
    2.93083692e+10   2.37834769e+10]
 [ -2.62746100e+02   7.00030000e+00   5.17045520e+08 ...,   7.70517753e+08
    1.16798290e+07   1.20300800e+07]
 ..., 
 [  3.95523000e+01   4.00520000e+00   1.70243430e+10 ...,   2.42081699e+10
    1.78908166e+10   1.74929478e+10]
 [  5.25408000e+01   2.46460000e+00   3.28790988e+10 ...,   3.88380258e+10
    6.46539204e+09   6.00900728e+09]
 [  1.42203000e+01   1.41030000e+00   5.91108572e+10 ...,   2.02066110e+11
    4.50987171e+10   4.13284212e+10]]
形状:
 (2318, 8)
可以看出, earnings_per_share 这个特征被过滤掉了.

4.2 相关系数

皮尔逊相关系数(Pearson Correlation Coefficient) 反映变量之间相关关系密切程度的统计指标
4.2.2 公式计算案例(了解,不用记忆)
公式
比如说我们计算年广告费投入与月均销售额
那么之间的相关系数怎么计算
最终计算:
= 0.9942 所以我们最终得出结论是广告投入费与月平均销售额之间有高度的正相关关系。 
4.2.3 特点
相关系数的值介于–1与+1之间,即–1≤ r ≤+1。其性质如下:
  • 当r>0时,表示两变量正相关,r<0时,两变量为负相关
  • 当|r|=1时,表示两变量为完全相关,当r=0时,表示两变量间无相关关系
  • 当0<|r|<1时,表示两变量存在一定程度的相关。且|r|越接近1,两变量间线性关系越密切;|r|越接近于0,表示两变量的线性相关越弱
  • 一般可按三级划分:|r|<0.4为低度相关;0.4≤|r|<0.7为显著性相关;0.7≤|r|<1为高度线性相关
这个符号:|r|为r的绝对值, |-5| = 5

4.2.4 API
from scipy.stats import pearsonr
x : (N,) array_like
y : (N,) array_like Returns: (Pearson’s correlation coefficient, p-value)
4.2.5 案例:股票的财务指标相关性计算
我们刚才的股票的这些指标进行相关性计算, 假设我们以
factor = ['pe_ratio','pb_ratio','market_cap','return_on_asset_net_profit','du_return_on_equity','ev','earnings_per_share','revenue','total_expense']
这些特征当中的两两进行计算,得出相关性高的一些特征
  • 分析
    • 两两特征之间进行相关性计算
import pandas as pd
from scipy.stats import pearsonr

def pearsonr_demo():
    """
    相关系数计算
    :return: None
    """
    data = pd.read_csv("./factor_regression_data/factor_returns.csv")

    factor = ['pe_ratio', 'pb_ratio', 'market_cap', 'return_on_asset_net_profit', 'du_return_on_equity', 'ev',
              'earnings_per_share', 'revenue', 'total_expense']

    for i in range(len(factor)):
        for j in range(i, len(factor) - 1):
            print(
                "指标%s与指标%s之间的相关性大小为%f" % (factor[i], factor[j + 1], pearsonr(data[factor[i]], data[factor[j + 1]])[0]))

    return None
返回结果:
指标pe_ratio与指标pb_ratio之间的相关性大小为-0.004389
指标pe_ratio与指标market_cap之间的相关性大小为-0.068861
指标pe_ratio与指标return_on_asset_net_profit之间的相关性大小为-0.066009
指标pe_ratio与指标du_return_on_equity之间的相关性大小为-0.082364
指标pe_ratio与指标ev之间的相关性大小为-0.046159
指标pe_ratio与指标earnings_per_share之间的相关性大小为-0.072082
指标pe_ratio与指标revenue之间的相关性大小为-0.058693
指标pe_ratio与指标total_expense之间的相关性大小为-0.055551
指标pb_ratio与指标market_cap之间的相关性大小为0.009336
指标pb_ratio与指标return_on_asset_net_profit之间的相关性大小为0.445381
指标pb_ratio与指标du_return_on_equity之间的相关性大小为0.291367
指标pb_ratio与指标ev之间的相关性大小为-0.183232
指标pb_ratio与指标earnings_per_share之间的相关性大小为0.198708
指标pb_ratio与指标revenue之间的相关性大小为-0.177671
指标pb_ratio与指标total_expense之间的相关性大小为-0.173339
指标market_cap与指标return_on_asset_net_profit之间的相关性大小为0.214774
指标market_cap与指标du_return_on_equity之间的相关性大小为0.316288
指标market_cap与指标ev之间的相关性大小为0.565533
指标market_cap与指标earnings_per_share之间的相关性大小为0.524179
指标market_cap与指标revenue之间的相关性大小为0.440653
指标market_cap与指标total_expense之间的相关性大小为0.386550
指标return_on_asset_net_profit与指标du_return_on_equity之间的相关性大小为0.818697
指标return_on_asset_net_profit与指标ev之间的相关性大小为-0.101225
指标return_on_asset_net_profit与指标earnings_per_share之间的相关性大小为0.635933
指标return_on_asset_net_profit与指标revenue之间的相关性大小为0.038582
指标return_on_asset_net_profit与指标total_expense之间的相关性大小为0.027014
指标du_return_on_equity与指标ev之间的相关性大小为0.118807
指标du_return_on_equity与指标earnings_per_share之间的相关性大小为0.651996
指标du_return_on_equity与指标revenue之间的相关性大小为0.163214
指标du_return_on_equity与指标total_expense之间的相关性大小为0.135412
指标ev与指标earnings_per_share之间的相关性大小为0.196033
指标ev与指标revenue之间的相关性大小为0.224363
指标ev与指标total_expense之间的相关性大小为0.149857
指标earnings_per_share与指标revenue之间的相关性大小为0.141473
指标earnings_per_share与指标total_expense之间的相关性大小为0.105022
指标revenue与指标total_expense之间的相关性大小为0.995845
从中我们得出
  • 指标revenue与指标total_expense之间的相关性大小为0.995845
  • 指标return_on_asset_net_profit与指标du_return_on_equity之间的相关性大小为0.818697

我们也可以通过画图来观察结果

import matplotlib.pyplot as plt
plt.figure(figsize=(20, 8), dpi=100)
plt.scatter(data['revenue'], data['total_expense'])
plt.show()
这两对指标之间的相关性较大,可以做之后的处理,比如合成这两个指标。

2.6 主成分分析

学习目标:

  • 应用PCA实现特征的降维
实际应用:
  • 用户与物品类别之间主成分分析

2.6.1 什么是主成分分析(PCA)

主成分分析(principal component analysis)也称主分量分析,旨在利用降维的思想,把多指标转化为少数几个综合指标(即主成分),其中每个主成分都能够反映原始变量的大部分信息,且所含信息互不重复。
  • 定义:高维数据转化为低维数据的过程,在此过程中可能会舍弃原有数据、创造新的变量
  • 作用:是数据维数压缩,尽可能降低原数据的维数(复杂度),损失少量信息。
  • 应用:回归分析或者聚类分析当中
对于信息一词,在决策树中会进行介绍

PCA主要思想
  • PCA的基本原理就是将一个矩阵中的样本数据投影到一个新的空间中去。当把所有的数据 都投射到该新空间时,我们希望平均方误差能尽可地小。
PCA主要步骤
  • 将原始数据按行排列组成矩阵X
  • 对X进行数据均值化得到X'
  • 求X'的协方差矩阵C
  • 求协方差矩阵C的特征值和特征向量,并将特征向量按特征值由大到小排列,取前k个按行组成矩阵P(原因:对于一个矩阵来说,将其对角化即产生特征根及特征向量的过程,也是将其在标准正交基上投影的过程,而特征值对应的即为该特征向量方向上的投影长度,因此该方向上携带的原有数据的信息越多。)
  • 通过计算Y = PX',得到降维后数据Y
那么更好的理解这个过程呢?我们来看一张图

1 计算案例理解(了解,无需记忆)

假设对于给定5个点,数据如下
(-1,-2)
(-1, 0)
( 0, 0)
( 2, 1)
( 0, 1)

要求:将这个二维的数据简化成一维? 并且损失少量的信息

这个过程如何计算的呢?找到一个合适的直线,通过一个矩阵运算得出主成分分析的结果(不需要理解)

2 API

# 将数据分解为较低维数空间
sklearn.decomposition.PCA(n_components=None)

n_components: 
小数:表示保留百分之多少的信息
整数:减少到多少特征

PCA.fit_transform(X) 
X:numpy array格式的数据[n_samples,n_features]
返回值:转换后指定维度的array

3 数据计算

先拿个简单的数据计算一下
[[2,8,4,5],
[6,3,0,8],
[5,4,9,1]]
from sklearn.decomposition import PCA

def pca_demo():
    """
    对数据进行PCA降维
    :return: None
    """
    data = [[2,8,4,5], [6,3,0,8], [5,4,9,1]]

    # 1、实例化PCA, 小数——保留多少信息
    transfer = PCA(n_components=0.9)
    # 2、调用fit_transform
    data1 = transfer.fit_transform(data)

    print("保留90%的信息,降维结果为:\n", data1)

    # 1、实例化PCA, 整数——指定降维到的维数
    transfer2 = PCA(n_components=3)
    # 2、调用fit_transform
    data2 = transfer2.fit_transform(data)
    print("降维到3维的结果:\n", data2)

    return None
返回结果:
保留90%的信息,降维结果为:
 [[ -3.13587302e-16   3.82970843e+00]
 [ -5.74456265e+00  -1.91485422e+00]
 [  5.74456265e+00  -1.91485422e+00]]
降维到3维的结果:
 [[ -3.13587302e-16   3.82970843e+00   4.59544715e-16]
 [ -5.74456265e+00  -1.91485422e+00   4.59544715e-16]
 [  5.74456265e+00  -1.91485422e+00   4.59544715e-16]]

2.6.2 案例:探究用户对物品类别的喜好细分降维

Instacart
数据集下载, 一共是4个csv文件, 描述如下:
  • order_products__prior.csv:订单与商品信息
    • 字段:order_idproduct_id, add_to_cart_order, reordered
  • products.csv:商品信息
    • 字段:product_id, product_name, aisle_id, department_id
  • orders.csv:用户的订单信息
    • 字段:order_id,user_id,eval_set,order_number,….
  • aisles.csv:商品所属具体物品类别
    • 字段: aisle_idaisle

1 需求

2 分析

  • 合并表,使得user_idaisle在一张表当中
  • 进行交叉表变换
  • 进行降维

3 完整代码

import pandas as pd
from sklearn.decomposition import PCA

# 1、获取数据集
# ·商品信息- products.csv:
# Fields:product_id, product_name, aisle_id, department_id
# ·订单与商品信息- order_products__prior.csv:
# Fields:order_id, product_id, add_to_cart_order, reordered 
# ·用户的订单信息- orders.csv:
# Fields:order_id, user_id,eval_set, order_number,order_dow, order_hour_of_day, days_since_prior_order 
# ·商品所属具体物品类别- aisles.csv:
# Fields:aisle_id, aisle     
products = pd.read_csv("./instacart/products.csv")
order_products = pd.read_csv("./instacart/order_products__prior.csv")
orders = pd.read_csv("./instacart/orders.csv")
aisles = pd.read_csv("./instacart/aisles.csv")

# 2、合并表,将user_id和aisle放在一张表上
# 1)合并orders和order_products on=order_id tab1:order_id, product_id, user_id
tab1 = pd.merge(orders, order_products, on=["order_id", "order_id"])
# 2)合并tab1和products on=product_id tab2:aisle_id
tab2 = pd.merge(tab1, products, on=["product_id", "product_id"])
# 3)合并tab2和aisles on=aisle_id tab3:user_id, aisle
tab3 = pd.merge(tab2, aisles, on=["aisle_id", "aisle_id"])

# 3、交叉表处理,把user_id和aisle进行分组
table = pd.crosstab(tab3["user_id"], tab3["aisle"])

# 4、主成分分析的方法进行降维
# 1)实例化一个转换器类PCA
transfer = PCA(n_components=0.95)
# 2)fit_transform
data = transfer.fit_transform(table)

print(data.shape)
返回结果:
(206209, 44)
降维后特征的含义变得模糊, 因此一般用于无监督学习, 尤其适用于当特征数量特别多且相互之间存在相关性的时候减少特征数量的方法. 举一个直观的例子,比如去食堂吃饭,有蒸羊羔、蒸熊掌、蒸鹿尾儿、烧花鸭、烧雏鸡、烧子鹅、卤猪、卤鸭、酱鸡、腊肉、松花、小肚儿、晾肉、香肠儿……是不可能的,但是我们有米饭、馒头、面条、干锅花菜、番茄炒蛋、黄瓜炒蛋、红烧鸡腿、糖醋排骨等。那我想吃顿饭,我选择米饭、馒头、面条,看起来好像选了三种食物蛮丰富的,但是实际上这三个都是碳水化合物,营养不均衡。假设我们可以将所有食物中的营养元素比如碳水化合物、蛋白质、维生素等“主成分”提取出来,作为新的“食物”。这样如果我们选择三样“食物”,营养就均衡了,而具体吃的是米饭还是馒头并无所谓。 PCA算法一个主要的缺点是难以解读。比如我们在实际操作的过程中可能有非常非常多的特征,而使用主成分分析后就合成了几个特征,那谁也不知道合成之后的特征是什么意思,主成分分析的过程往往是个黑盒(Black Box)。

总结

回顾练习

1、数据集的结构是什么?

答案: 特征值+ 目标值

2、机器学习算法分成哪些类别? 如何分类

答案: 根据是否有目标值分为 监督学习和非监督学习监督学习 根据目标值的数据类型:目标值为离散值就是分类问题 ​ 目标值为连续值就是回归问题

3、什么是标准化? 和归一化相比有什么优点?

答案: 标准化是通过对原始数据进行变换把数据变换到均值为0,方差为1范围内 ​ 优点: 少量异常点, 不影响平均值和方差, 对转换影响小

Views: 225

01 机器学习概述

课程定位
  • 课程以算法、案例为驱动的学习,伴随浅显易懂的数学知识
  • 作为人工智能领域(数据挖掘/机器学习方向)的提升课程,掌握更深更有效的解决问题技能
课程 目标
  • 应用Scikit-learn实现数据集的特征工程
  • 掌握机器学习常见算法原理
  • 应用Scikit-learn实现机器学习算法的应用,结合场景解决实际问题

1. 机器学习概述

了解机器学习定义以及应用场景
说明机器学习算法监督学习与无监督学习的区别
说明监督学习中的分类、回归特点
说明机器学习算法目标值的两种数据类型
说明机器学习(数据挖掘)的开发流程

1.1 人工智能概述

1.1.1 机器学习与人工智能、深度学习

  • 机器学习和人工智能,深度学习的关系
    • 机器学习是人工智能的一个实现途径
    • 深度学习是机器学习的一个方法发展而来
  • 达特茅斯会议-人工智能的起点1956年8月,在美国汉诺斯小镇宁静的达特茅斯学院中,约翰·麦卡锡(John McCarthy)马文·闵斯基(Marvin Minsky,人工智能与认知学专家)克劳德·香农(Claude Shannon,信息论的创始人)艾伦·纽厄尔(Allen Newell,计算机科学家)赫伯特·西蒙(Herbert Simon,诺贝尔经济学奖得主)等科学家正聚在一起,讨论着一个完全不食人间烟火的主题:用机器来模仿人类学习以及其他方面的智能。会议足足开了两个月的时间,虽然大家没有达成普遍的共识,但是却为会议讨论的内容起了一个名字:人工智能因此,1956年也就成为了人工智能元年。

1.1.2 机器学习、深度学习能做些什么

机器学习的应用场景非常多,可以说渗透到了各个行业领域当中。医疗、航空、教育、物流、电商等等领域的各种场景。

  • 用在挖掘、预测领域:
    • 应用场景:店铺销量预测、量化投资、广告推荐、企业客户分类、SQL语句安全检测分类…
  • 用在图像领域:
    • 应用场景:街道交通标志检测、人脸识别等等
  • 用在自然语言处理领域:
    • 应用场景:文本分类、情感分析、自动聊天、文本检测等等

当前重要的是掌握一些机器学习算法等技巧,从某个业务领域切入解决问题。

1.1.3 人工智能阶段课程安排

1.2 什么是机器学习

1.2.1 定义

机器学习是从数据自动分析获得模型,并利用模型对未知数据进行预测。

1.2.2 解释

  • 我们人从大量的日常经验中归纳规律,当面临新的问题的时候,就可以利用以往总结的规律去分析现实状况,采取最佳策略。
  • 从数据(大量的猫和狗的图片)中自动分析获得模型(辨别猫和狗的规律),从而使机器拥有识别猫和狗的能力。
  • 从数据(房屋的各种信息)中自动分析获得模型(判断房屋价格的规律),从而使机器拥有预测房屋价格的能力。

从历史数据当中获得规律?这些历史数据是怎么的格式?

1.2.3 数据集构成

  • 结构:特征值+目标值

注:

  • 对于每一行数据我们可以称之为样本
  • 有些数据集可以没有目标值:

1.3 机器学习算法分类

学习目标

  • 目标
    • 说明机器学习算法监督学习与无监督学习的区别
    • 说明监督学习中的分类、回归特点

分析1.2中的例子:

  • 特征值:猫/狗的图片;目标值:猫/狗-类别
    • 分类问题
  • 特征值:房屋的各个属性信息;目标值:房屋价格-连续型数据
    • 回归问题
  • 特征值:人物的各个属性信息;目标值:无
    • 无监督学习

1.3.1 总结

1.3.2 练习

说一下它们具体问题类别:

1、预测明天的气温是多少度?

2、预测明天是阴、晴还是雨?

3、人脸年龄预测?

4、人脸识别?

1.3.3 机器学习算法分类

  • 监督学习(supervised learning)(预测)
  • 定义:输入数据是由输入特征值和目标值所组成。函数的输出可以是一个连续的值(称为回归),或是输出是有限个离散值(称作分类)。
    • 分类 k-近邻算法、贝叶斯分类、决策树与随机森林、逻辑回归、神经网络
    • 回归 线性回归、岭回归
  • 无监督学习(unsupervised learning)
  • 定义:输入数据是由输入特征值所组成。
    • 聚类 k-means

1.4 机器学习开发流程

  • 流程图:

1.5 学习框架和资料介绍

需明确几点问题:

(1)算法是核心,数据计算是基础

(2)找准定位

大部分复杂模型的算法设计都是算法工程师在做,而我们

  • 分析很多的数据
  • 分析具体的业务
  • 应用常见的算法
  • 特征工程、调参数、优化
注意: 参考书比较晦涩难懂, 不建议直接从头到尾阅读
  • 我们应该怎么做?
  • 学会分析问题,使用机器学习算法的目的,想要算法完成何种任务
  • 掌握算法基本思想,学会对问题用相应的算法解决
  • 学会利用库或者框架解决问题

当前重要的是掌握一些机器学习算法等技巧,从某个业务领域切入解决问题。

1.5.1 机器学习库与框架

1.5.2 书籍资料

1.5.3 提深内功(但不是必须)

1.5 开发环境和介绍

  • python
  • scikit-learn
  • jupyter notebook

1.6 机器学习开发环境部署

python 安装

Anaconda安装

安装后打开Anacoinda navigator

打开终端

创建sklearn环境

>conda create -n sklearn

cmd控制台切换环境

在cmd中可以使用conda --info --envs列出可用环境, 使用conda activate激活环境

C:\Users\Lenovo>conda info --envs
# conda environments:  
# 这里配置了python2(base)和python3(py3)共存
base                  *  D:\dev\Anaconda2    
py3                      D:\dev\Anaconda2\envs\py3
                         D:\dev\Anaconda2\envs\py3\envs\sklearn                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
C:\Users\Lenovo>conda activate py3                                                                                                           
(py3) C:\Users\Lenovo>conda activate sklearn                                                                                                                                                          
(sklearn) C:\Users\Lenovo>                                                               

Anaconda Navigator 切换环境

安装jupyter notebook

安装完毕之后点击lauch即进入jupyter notebook的WEB UI界面(使用默认浏览器)

创建测试文件(Python3)体验 jupyter notebook 的使用

快捷键: ctrl+enter执行当前代码块, alt+enter执行当前代码块并向下插入新的代码块

修改conda镜像源为清华源, 并取消SSL验证

找到C:\Users\你的用户名\.condarc,修改如下:

ssl_verify: false
channels:
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/menpo/
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/bioconda/
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/msys2/
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/
show_channel_urls: true

接下来保存并进入sklearn环境终端,安装依赖库numpy和sklearny以及pandas

(sklearn)>conda install numpy        #矩阵运算库
(sklearn)>conda install scikit-learn #机器学习库
(sklearn)>conda install pandas       #数据分析库

进入jupyter notebook 输入以下代码并运行, 没报错即为成功

import numpy as np
import sklearn
import pandas

d = np.eye(3)

print(d)

输出

[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

最后可以为jupyter notebook添加你喜欢的主题, 设置成你喜欢的风格

GitHub - dunovank/jupyter-themes: Custom Jupyter Notebook Themes

使用pip安装jupyter themes

>pip install jupyterthemes

进行主题风格配置

jt -t oceans16 -f fira -fs 17 -cellw 90% -ofs 14 -dfs 14 -T

进入jupyter notebook查看主题变化

至此, 开发环境准备就完成了!

Views: 180

Kafka-Storm 实时计算项目开发实战

项目架构

image-20211209020000874

基本要求

  1. 主题相关的WebAPP
    1. 1个首页和若干功能页面
    2. 有生成数据的能力(实际展示的时候,为了有更多丰富的数据, 允许使用脚本生成假数据)
      1. KafkaProducer 直接将需要采集的信息发送到Storm
    3. 分析结果的图表展示
      1. echarts, 或者其他图表库
        1. Jquery的ajax库
        2. json
    4. 其他要求:
      1. 参照Alibaba的Java开发手册的规约
      2. 代码中适当添加注释
  2. 部署到服务器(虚拟机或者购买的服务器)
    1. JavaWeb程序需要部署到 Tomcat
    2. Nginx 实现反向代理
  3. 实时数据分析
    1. kafka -> storm
    2. 持久化到数据库(HBase,MySQL,Redis)
    3. 最好每个团队成员都有一个完整的实时分析流程
    4. 每个团队最少有两种实时分析, 不同类型(不要过于简单,分析的内容要能体现实时性)

开发流程建议

  1. 确定需求和分工

  2. 学习使用版本控制工具(Gitee 码云 / Github)

  3. 统一消息格式,建议使用JSON

  4. 开发时先采用本地的Tomcat和Storm环境测试

  5. 本地环境测试时可以远程连接服务器上的的Kafka和Hbase

    没问题再使用Tomcat和Nginx部署到服务器

  6. 将拓扑上传到Storm集群中运行

  7. 联调所有模块

  8. 完善SRS报告

    记录整个项目的需求, 架构, 开发、部署、测试的详细过程

  9. 每个人准备PPT, 视频等交付资料

  10. 每周使用在线表格记录项目进度情况

WebApp开发

采用前后端分离的模式开发和部署

后端可以采用基于tomcat的普通JavaWeb应用或者SpringBoot项目

消息传递

一般项目可以使用kafka-clients依赖里的KafkaProducer API来发送消息到Kafka, 这样便于快速调试.

对于SpringBoot项目,可以使用spring-kafka依赖.

kafka相关配置

spring:
  kafka:
    bootstrap-servers: hadoop000:9092,hadoop000:9093,hadoop000:9094
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
      client-id: app-pro-cli
      acks: 1
      retries: 3
    consumer:
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      client-id: app-pro-cli
      group-id: g1

Kafka的初始化配置

package cn.delucia.project.conf;

import org.apache.kafka.clients.admin.NewTopic;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class KafkaInitConf {
    @Value("${app.kafka-topic}")
    private String kafkaTopic;
    @Value("${app.topic-partitions}")
    private Integer topicPartitions;
    // 创建一个Topic并设置分区数和副本数
    @Bean
    public NewTopic initialTopic() {
        return new NewTopic(kafkaTopic, 1, (short) 1);
    }
    // 如果要修改分区数,只需修改配置值重启项目即可
    // 修改分区数并不会导致数据的丢失,但是分区数只能增大不能减小
    @Bean
    public NewTopic updateTopic() {
        return new NewTopic(kafkaTopic, topicPartitions, (short) 1);
    }
}

在控制器类中注入kafkaTemplate并调用send方法发送消息, 例如:

@Slf4j
@RestController
public class GreetingController {

    @Autowired
    private KafkaTemplate<Object, String> kafkaTemplate;

    public Greeting greeting(@RequestParam(value = "name", defaultValue = "农场主") String name) {

        Greeting greeting = new Greeting(counter.incrementAndGet(), name);

        try {
            String s = new ObjectMapper().writeValueAsString(greeting);
            // 带回调的生产者
            kafkaTemplate.send(kafkaTopic, "Greeting:" + s).addCallback(success -> {
                // 消息发送到的topic
                String topic = Objects.requireNonNull(success).getRecordMetadata().topic();
                // 消息发送到的分区
                int partition = success.getRecordMetadata().partition();
                // 消息在分区内的offset
                long offset = success.getRecordMetadata().offset();
                log.info("发送消息成功: {}-{}-{}, Greeting:{}", topic, partition, offset, s);
            }, failure -> {
                log.info("发送消息失败: {}", failure.getMessage());
            });
        } catch (JsonProcessingException e) {
            log.info("解析Json格式失败: {}", e.getMessage());
        }
        ...
    }
}

安装Tomcat

准备:安装Java JDK1.8

这里安装openjdk是因为比较简单, 工作场合一定要使用oracle提供的jdk1.8

sudo yum -y install java-1.8.0-openjdk*

这样安装的好处就是环境变量都配好了

可以直接查看版本 java -version

Tomcat安装

下载页面: https://tomcat.apache.org/download-90.cgi

文档:https://tomcat.apache.org/tomcat-9.0-doc/index.html

下载链接

wget https://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-9/v9.0.41/bin/apache-tomcat-9.0.41.tar.gz

解压到后改名tomcat9

tar -zxvf apache-tomcat-9.0.41.tar.gz -C ~/app
cd ~/app
mv apache-tomcat-9.0.41/ tomcat9

默认tomcat端口是8080, 为了避免冲突, 这里修改为18080

 $ vi ~/app/tomcat9/conf/server.xml

 69     <Connector port="18080" protocol="HTTP/1.1"
 70                connectionTimeout="20000"
 71                redirectPort="8443" />

启动: 进入 /bin目录下 运行startup.sh脚本文件

$ ./startup.sh 
Using CATALINA_BASE:   /home/hadoop/app/tomcat9
Using CATALINA_HOME:   /home/hadoop/app/tomcat9
Using CATALINA_TMPDIR: /home/hadoop/app/tomcat9/temp
Using JRE_HOME:        /home/hadoop/app/jdk1.8.0_211
Using CLASSPATH:       /home/hadoop/app/tomcat9/bin/bootstrap.jar:/home/hadoop/app/tomcat9/bin/tomcat-juli.jar
Tomcat started.

检查tomcat进程信息

$ bin]$ ps -ef | grep tomcat
hadoop    15142      1  1 14:41 pts/0    00:00:05 /home/hadoop/app/jdk1.8.0_211/bin/java -Djava.util.logging.config.file=/home/hadoop/app/tomcat9/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs= -classpath /home/hadoop/app/tomcat9/bin/bootstrap.jar:/home/hadoop/app/tomcat9/bin/tomcat-juli.jar -Dcatalina.base=/home/hadoop/app/tomcat9 -Dcatalina.home=/home/hadoop/app/tomcat9 -Djava.io.tmpdir=/home/hadoop/app/tomcat9/temp org.apache.catalina.startup.Bootstrap start
hadoop    15296  15064  0 14:47 pts/0    00:00:00 grep --color=auto tomcat

检查对应的监听端口信息

netstat -anpt | grep 15142

网页访问虚拟机主机名或IP地址:18080

为了方便可以为tomcat的安装目录配置环境变量CATALINA_HOME,添加到PATH中

为tomcat添加用户和角色

修改conf/tomcat-users.xml, 添加如下内容

<!--  
  <role rolename="tomcat"/>
  <role rolename="role1"/>
  <user username="tomcat" password="tomcat" roles="tomcat"/>
  <user username="both" password="tomcat" roles="tomcat,role1"/>
  <user username="role1" password="tomcat" roles="role1"/>
-->
  <role rolename="manager-gui"/>
  <role rolename="manager-script" />
  <user username="admin" password="123123" roles="manager-gui,manager-script" />
</tomcat-users>

修改webapps/manager/META-INF目录下的context.xml,在allow行的末尾加上|\d+.\d+.\d+.\d+表示允许所有主机访问。

<Context antiResourceLocking="false" privileged="true" >
  <Valve className="org.apache.catalina.valves.RemoteAddrValve"
         allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1|\d+\.\d+\.\d+\.\d+" />
  <Manager sessionAttributeValueClassNameFilter="java\.lang\.(?:Boolean|Integer|Long|Number|String)|org\.apache\.catalina\.filters\.CsrfPreventionFilter\$LruCache(?:\$1)?|java\.util\.(?:Linked)?HashMap"/>
</Context>

重启tomcat生效

部署项目

一般的javaee项目可以直接build出一个war包进行上传服务器。

SpringBoot默认是打成jar包,如果需要打成war包, 需要修改pom.xml文件:

    <groupId>com.niit</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <!-- 这里打成war包 若打jar,需将war改为jar -->
    <packaging>war</packaging>

    <name>demo</name>
    <description>Demo project for Spring Boot</description>

然后使用mvn:package构建war包即可,然后上传到服务器的tomcat目录下的webapp文件夹之内。

tomcat 容器的运行机制👇

tomcat默认会加载tomcat目录下的webapp文件夹之内的文件,如

其中ROOT目录下为Tomcat的欢迎页

http://hadoop000/index.jsp

http://hadoop000/tomcat.gif

examples目录是一些官方示例

http://hadoop000/examples/

tomcat也会默认会加载tomcat目录下的webapp文件夹中下面的war包,并自动解压在webapp下面。

默认的访问方式就是 http://域名:端口号/war包名, 端口号默认是8080

启动tomcat, 浏览器访问 http://hadoop000:18080/demo/

部署SpringBoot项目

项目的服务器设置:

server:
  port: 18080
  servlet:
    context-path: "/project"
#debug: on

使用maven的springboot打包插件按照jar包的方式打包

   <groupId>com.niit</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <!-- 这里打成war包 若打jar,需将war改为jar -->
    <packaging>jar</packaging>

    <name>demo</name>
    <description>Demo project for Spring Boot</description>

然后使用mvn:package打包即可,将打包出来的jar文件重命名然后上传到服务器

在服务器启动项目(需要Java8以上环境)

[hadoop@hadoop000 webapps]$ java -jar demo-jar.jar 

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.0.RELEASE)

如果后台运行可以(推荐)

$ nohup java -jar demo-jar.jar 1> demo.log 2>&1 &

浏览器访问 http://hadoop000:18080/project/greeting

配置Nginx反向代理

但是这样带8080端口的访问并不是很好,因此一般都使用nginx反向代理.

通过反向代理将对nginx的80端口的访问请求转发到tomcat的8080端口

首先在物理机hosts文件创建虚拟机主机名和ip的映射

192.168.186.100 hadoop000

Nginx安装

安装前确认是否已经安装过

sudo yum search nginx

Nginx文档http://nginx.org/en/docs/

Installation on Linux, nginx packages from nginx.org can be used.

Installation instructions
RHEL/CentOS
Debian
Ubuntu
SLES
Alpine

以 CentOS为例进行安装

Install the prerequisites:

sudo yum install yum-utils

To set up the yum repository, create the file named /etc/yum.repos.d/nginx.repo with the following contents:

sudo vi /etc/yum.repos.d/nginx.repo

内容如下:

[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true

[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true

By default, the repository for stable nginx packages is used. If you would like to use mainline nginx packages, run the following command:

sudo yum-config-manager --enable nginx-mainline

To install nginx, run the following command:

sudo yum install -y nginx

When prompted to accept the GPG key, verify that the fingerprint matches 573B FD6B 3D8F BC64 1079 A6AB ABF5 BD82 7BD9 BF62, and if so, accept it.

Nginx配置

创建java.conf ,进行最简配置

$ cd /etc/nginx/conf.d/
$ sudo cp default.conf java.conf
$ vi java.conf

URL记住不要忘了加http://前缀

server {
    listen  80;
    server_name hadoop000;
    location / {
        proxy_pass http://127.0.0.1:18080;
    }
}

常用命令

解释 命令
安装服务 yum install nginx
启动服务 service nginx start
停止服务 service nginx stop
重载服务 service nginx reload

配置完成后启动服务

sudo service nginx start

如果服务器已经启动,当配置发生变化可以直接使用重载服务来更新配置,运维常用,因为不需要停止服务就可以重载新的配置。

如果启动失败可以查看错误日志

sudo vi /var/log/nginx/error.log 

经过反向代理配置之后,使用浏览器访问 http://hadoop000就相当于访问虚拟机hadoop000的本地服务http://127.0.0.1:18080的效果

如果发现502错误:

2020/11/18 16:12:39 [crit] 16376#16376: *1 connect() to 127.0.0.1:8080 failed (13: Permission denied) while connecting to upstream, client: 192.168.186.1, server: hadoop000, request: "GET /favicon.ico HTTP/1.1", upstream: "http://127.0.0.1:8080/favicon.ico", host: "hadoop000", referrer: "http://hadoop000/"

此时需要考虑把linux操作系统默认的强制访问安全限制设置为禁用。

关闭SElinux即可

  1. 临时关闭 SElinux

    sudo setenforce 0
  2. 永久关闭 SElinux

    sudo vim /etc/selinux/config
    SELINUX=disabled

修改之后就可以正常访问了

配置开机启动

[hadoop@hadoop000 download]$ chkconfig nginx
注意:正在将请求转发到“systemctl is-enabled nginx.service”。
disabled

[hadoop@hadoop000 download]$ chkconfig nginx on
注意:正在将请求转发到“systemctl enable nginx.service”。
==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-unit-files ===
Authentication is required to manage system service or unit files.
Authenticating as: root
Password: 
==== AUTHENTICATION COMPLETE ===
Created symlink from /etc/systemd/system/multi-user.target.wants/nginx.service to /usr/lib/systemd/system/nginx.service.
==== AUTHENTICATING FOR org.freedesktop.systemd1.reload-daemon ===
Authentication is required to reload the systemd state.
Authenticating as: root
Password: 
==== AUTHENTICATION COMPLETE ===

[hadoop@hadoop000 download]$ chkconfig nginx
注意:正在将请求转发到“systemctl is-enabled nginx.service”。
enabled

前后端分离

nginx是一个高性能服务器,除了配置反向代理之外,也非常适合部署静态资源并支持高并发请求, 并且也提供负载均衡的功能。

首先删除默认的配置

# sudo vim /etc/nginx/conf.d/default.conf

为了将反向代理请求和静态资源的请求分开,修改我们之前的配置如下:

upstream webapp.server {
    server hadoop000:18080;
}

server {
        listen  80;
        server_name localhost hadoop000;
        root /data/www/;

        # 静态资源
        location / {
            index index.html;
            access_log /var/log/nginx/java-hadoop.log main;
        }

        # 反向代理到本地JavaWeb的后台服务
        location ^~ /project/ {
            proxy_pass http://webapp.server/project/;
        }
}

一些说明如下:

  1. listen 80 是http协议的默认端口,:80 可以省略
  2. server_name 表示请求路径中的服务器主机名,可配置多个
  3. /data/www 为站点根目录,需手动创建,权限一般为755
  4. access_log 对应的路径是应用的服务器日志,文件夹不存在则需手动创建
  5. location 的匹配规则,优先匹配 /demo/,其次 /

一般来说前后端分离部署应该是部署在不同的服务器上的,这里放在一台服务器上只是为了演示方便。

前端部署

然后将项目的静态页面放到/data/www下即可

后端部署

SpringBoot项目移除静态资源,单独部署在tomcat上,并使用ngixn实现反向代理

实时数据采集

主要技术:Kafka

image-20211209020033601

实时数据流计算

主要技术:Kafka-clients,storm-hbase|storm-redis|storm-mysql

以热力图项目为例, 拓扑代码如下

package com.niit.project;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.StormSubmitter;
import org.apache.storm.hbase.bolt.HBaseBolt;
import org.apache.storm.hbase.bolt.mapper.SimpleHBaseMapper;
import org.apache.storm.kafka.spout.ByTopicRecordTranslator;
import org.apache.storm.kafka.spout.KafkaSpout;
import org.apache.storm.kafka.spout.KafkaSpoutConfig;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.TopologyBuilder;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Tuple;
import org.apache.storm.tuple.Values;

import java.util.HashMap;
import java.util.Map;

public class KafkaStormProjectApp {

    public static String topologyName = "project-topo";
    public static final String KAFKA_BROKER = "hadoop000:9092";
    public static final String INPUT_TOPIC = "storm-project";

    private static class SplitBolt extends BaseRichBolt {
        private OutputCollector outputCollector;

        @Override
        public void prepare(Map<String, Object> topoConf, TopologyContext context, OutputCollector collector) {
            this.outputCollector = collector;
        }

        @Override
        public void execute(Tuple input) {
            String id = input.getStringByField("line");
            String[] split = id.split(",");

            try {
                double lng = Double.parseDouble(split[0]);
                double lat = Double.parseDouble(split[1]);
                this.outputCollector.emit(new Values(id, lng, lat));
            } catch (NumberFormatException e) {
                System.err.println(e.getMessage());
            }
        }

        @Override
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
            declarer.declare(new Fields("id", "lng", "lat"));
        }
    }

    /**
     * 计数利用HBase的CountColumn特性
     */
    private static class CountBolt extends BaseRichBolt {

        private OutputCollector collector;
        private final HashMap<String, Long> counts = null;

        @Override
        public void prepare(Map<String, Object> topoConf, TopologyContext context, OutputCollector collector) {
            this.collector = collector;
        }

        @Override
        public void execute(Tuple input) {
            String id = input.getString(0);
            double lng = input.getDouble(1);
            double lat = input.getDouble(2);

            this.collector.emit(new Values(id, lng, lat, 1L));
        }

        @Override
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
            declarer.declare(new Fields("id", "lng", "lat", "count"));
        }
    }

    public static void main(String[] args) throws Exception {

        final TopologyBuilder builder = new TopologyBuilder();

        // storm conf
        Config conf = new Config();
        conf.setNumAckers(0);
        conf.setDebug(true);

        // kafka bolt
        ByTopicRecordTranslator<String, String> translator =
                new ByTopicRecordTranslator<>((r) -> new Values(r.value()), new Fields("line"));
        translator.forTopic(INPUT_TOPIC, (r) -> new Values(r.value()), new Fields("line"));

        KafkaSpoutConfig<String, String> kafkaSpoutConfig = KafkaSpoutConfig
                // bootstrapServers 以及topic
                .builder(KAFKA_BROKER, INPUT_TOPIC)
                // 设置group.id
                .setProp(ConsumerConfig.GROUP_ID_CONFIG, "location")
                // ensure at-least-once processing
                .setProp(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest")
                // 设置开始消费的起始位置
                // 设置提交消费边界的时长间隔
                .setOffsetCommitPeriodMs(10_000)
                //Translator
                .setRecordTranslator(translator)
                .build();

        KafkaSpout<String, String> kafkaSpout = new KafkaSpout<>(kafkaSpoutConfig);

        // hbase bolt
        Map<String, Object> hbConf = new HashMap<>();
        hbConf.put("hbase.rootdir", "hdfs://hadoop000:9000/hbase");
        hbConf.put("hbase.zookeeper.quorum", "hadoop000:2181");
        conf.put("hbase.conf", hbConf);
        conf.setNumWorkers(2);  // 设置为1个topology创建2个worker进程

        SimpleHBaseMapper mapper = new SimpleHBaseMapper()
                .withRowKeyField("id")
                .withColumnFields(new Fields("lng","lat"))
                .withCounterFields(new Fields("count"))
                .withColumnFamily("cf");

        HBaseBolt hbaseBolt = new HBaseBolt("project", mapper).withConfigKey("hbase.conf");

        // build topology
        builder.setSpout("kafka_spout", kafkaSpout);
        builder.setBolt("split-bolt", new SplitBolt(), 2)
                .setNumTasks(4)
                .shuffleGrouping("kafka_spout");
        builder.setBolt("count-bolt", new CountBolt())
                .fieldsGrouping("split-bolt", new Fields("id"));
        builder.setBolt("hbase-bolt", hbaseBolt).globalGrouping("count-bolt");

        if (args != null && args.length > 0) {
            topologyName = args[0];
            StormSubmitter.submitTopology(topologyName, conf, builder.createTopology());
        } else {
            LocalCluster localCluster = new LocalCluster();
            localCluster.submitTopology(topologyName, conf, builder.createTopology());
        }
    }

}

其中聚合操作是利用了HBase的CounterColumn特性

这里没有使用时间窗口Bolt来体现实时,而是利用了HBase表的TTL属性,TTL可以在创建表的时候指定,TTL设置为60即表示表中记录的存活时间为1分钟:

create "project",{NAME => 'cf', MIN_VERSIONS => '0',TTL => '60'}

也可以disable表之后使用alter语句对已有表进行修改。

由于插入数据的时候经纬度是采用了Double类型,而计数列采用了Long类型,但是HBase只有使用字节数组这样一种方式进行存储,所以需要程序员自己控制数据类型的转换。在查看表中记录的使用需要这样进行数据类型的转换:

scan 'project', {COLUMNS => ['cf:lng:toDouble','cf:lat:toDouble','cf:count:toLong']}

将上传到Storm集群

[kafka-storm-project]$ storm jar project-topology.jar com.niit.demo.KafkaStormProjectTopology project-topo

数据可视化

主要技术:百度echarts图表,异步请求图表渲染(Ajax & JSON)

这个热力图项目的前端比较简单,只有一个index.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>百度地图</title>
    <style>
        #main {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            width: 100%;
            height: 100%;
        }
    </style>
</head>
<body>

<!-- 为ECharts准备一个具备大小(宽高)的Dom -->
<div id="main"></div>

<!-- jquery 1.11.3 -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/jquery@1.11.3/dist/jquery.min.js"></script>
<!-- bootstrap 3.3.7-->
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
<!-- echarts 插件  -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script>
<!-- echarts 百度地图插件 -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts/dist/extension/bmap.js"></script>
<script type="text/javascript"
        src="https://api.map.baidu.com/api?v=2.0&ak=KOmVjPVUAey1G2E8zNhPiuQ6QiEmAwZu&__ec_v__=20190126"></script>
<script>

    var points = [];
    var myChart = echarts.init(document.getElementById('main'));

    myChart.setOption(option = {
        animation: false,
        bmap: {
            center: [110.337731, 20.064295],  // 海南大学
            zoom: 18, // 地图缩放等级
            roam: true
        },
        visualMap: {
            show: false,
            top: 'top',
            min: 0,
            max: 5,
            seriesIndex: 0,
            calculable: true,
            inRange: {
                color: ['blue', 'blue', 'green', 'yellow', 'red']
            }
        },
        series: [{
            type: 'heatmap',
            coordinateSystem: 'bmap',
            data: points,
            pointSize: 5,
            blurSize: 6
        }]
    });
    // 添加百度地图插件
    var bmap = myChart.getModel().getComponent('bmap').getBMap();
    bmap.addControl(new BMap.MapTypeControl());
    // 禁止拖拽和缩放
    bmap.disableDragging();
    bmap.disableScrollWheelZoom();

    bmap.addEventListener("click", function (e) {
        $.post('log', {
            lng: e.point.lng,
            lat: e.point.lat
        });
    });

    // 10秒更新一次地图
    window.setInterval(function () {
        $.get(
            "points",
            function (data) {
                for (var i = 0; i < data.length; i++) {
                        point = [];
                    for (var j = 0; j < data[i].count; j++) {
                        points.push([data[i].lng, data[i].lat, 1]);
                    }
                }
                option.series.data = points;
                myChart.setOption(option);
            }
        )
    }, 10000);

</script>
</body>
</html>

其中请求demo/points可以到达后端的SpringBoot项目,并且返回HBase的最新数据,对应的接口如下:

src\main\java\com\niit\demo\service\LocationService.java

package com.niit.demo.service;

import com.niit.demo.entity.Point;
import com.niit.demo.utils.HBaseHelper;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.util.Bytes;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class LocationService {

    public List<Point> getLocationList() {

        List<Point> list = new ArrayList<>();
        ResultScanner scanner = HBaseHelper.getScanner("project");

        if (scanner != null) {
            scanner.forEach(rowResult -> {

                Point point = new Point();
                for (Cell cell : rowResult.listCells()) {

                    String qualifier = Bytes.toString(CellUtil.cloneQualifier(cell));
                    switch (qualifier) {
                        case "lng":
                            point.setLongitude(Bytes.toDouble(CellUtil.cloneValue(cell)));
                            break;
                        case "lat":
                            point.setLatitude(Bytes.toDouble(CellUtil.cloneValue(cell)));
                            break;
                        case "count":
                            point.setCount(Bytes.toLong(CellUtil.cloneValue(cell)));
                            break;
                        default:
                            break;
                    }
                }
                list.add(point);
            });
        }
        return list;
    }

}

src\main\java\com\niit\demo\IndexController.java

package com.niit.demo;

import com.niit.demo.entity.Point;
import com.niit.demo.service.LocationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/")
public class IndexController {
    @Autowired
    public LocationService service;
    Logger logger = LoggerFactory.getLogger(IndexController.class);

    @GetMapping("/points")
    public List<Point> getPoints() {
        return service.getLocationList();
    }

    @PostMapping("/log")
    public void log(double lng, double lat) {
        logger.info("{},{}", lng, lat);
    }
}

常见问题:

Tomcat项目乱码

Tomcat中部署的JSP页面出现中文乱码问题:

  1. 修改tomcat/conf目录下的主配置文件server.xml,添加URIEncoding=“UTF-8”配置项,配置位置如下:

    <Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" URIEncoding="UTF-8" />
    
    <Connector protocol="AJP/1.3"
           address="::1"
           port="8009"
           redirectPort="8443" URIEncoding="UTF-8" />
  2. 修改tomcat/conf/web.xml,在 <servlet>节点中添加如下内容:

    <init-param>
           <param-name>fileEncoding</param-name>
           <param-value>UTF-8</param-value>
    </init-param>

3.重启服务

Tomcat设为开机自启

  1. 进入init.d目录

进入到/etc/init.d目录下,命令是:

cd /etc/init.d
  1. 新建一个名为tomcat的文件
vim tomcat
  1. 为/etc/init.d/tomcat文件添加可执行权限
chmod 755 tomcat
  1. 编辑tomcat文件,添加以下内容
vi tomcat

添加内容为: 注意CATALINA_HOME需要修改正确

#!/bin/bash
# processname: tomcat9
# chkconfig: 2345 86 16
# description: Tomcat9 start|restart|stop.

if [ -f /etc/init.d/functions ]; then
. /etc/init.d/functions
elif [ -f /etc/rc.d/init.d/functions ]; then
. /etc/rc.d/init.d/functions
else
echo -e "/atomcat: unable to locate functions lib. Cannot continue."
exit -1
fi

RETVAL=$?
CATALINA_HOME=/opt/pkg/tomcat9

case "$1" in
start)
if [ -f $CATALINA_HOME/bin/startup.sh ];
then
echo $"Starting Tomcat"
$CATALINA_HOME/bin/startup.sh
fi
;;
stop)
if [ -f $CATALINA_HOME/bin/shutdown.sh ];
then
echo $"Stopping Tomcat"
$CATALINA_HOME/bin/shutdown.sh
fi
;;
*)
echo $"Usage: $0 {start|stop}"
exit 1
;;
esac

exit $RETVAL
  1. 把tomcat这个脚本添加到开机启动项里面
chkconfig --add tomcat
chkconfig tomcat on
  1. 如果想看看是否添加成功
chkconfig --list

netconsole      0:关 1:关 2:关 3:关 4:关 5:关 6:关
network         0:关 1:关 2:开 3:开 4:开 5:开 6:关
tomcat          0:关 1:关 2:开 3:开 4:开 5:开 6:关
  1. 在tomcat/bin下创建一个setenv.sh文件,加入以下环境变量, 并赋予执行权限
[root@hadoop000 tomcat9]# vi bin/setenv.sh

export JAVA_HOME=/opt/pkg/jdk1.8.0_261
export JRE_HOME=/opt/pkg/jdk1.8.0_261/jre
export CATALINA_HOME=/opt/pkg/tomcat9
export CATALINA_BASE=/opt/pkg/tomcat9

[root@hadoop000 tomcat9]# chmod a+x bin/setenv.sh
  1. 查看看是否开机启动

使用命令重启机器,命令是:

reboot
  1. 查看网络状态,执行命令,查看8080端口是否启动
netstat   -lntup
  1. 查看tomcat进程
 ps -ef |grep tomcat
  1. 如果不需要开机启动,从启动脚本删除即可
chkconfig --del tomcat

Views: 418