机器学习笔记(7.1)

19 篇文章 0 订阅
15 篇文章 0 订阅

机器学习系列文章目录

线性回归部分


十八节

经过了这么久的学习,我们终于结束了分类和聚类算法的相关内容,这一课时我将为你讲解关于回归算
法的内容。
从标题可以看出来,我们这次课程会涉及线性回归和逻辑回归,这两个回归有什么样的含义呢?虽然都
叫作回归,它们的处理方式有什么不同呢?带着这些问题,我们就开始本课时的学习吧。

一个例子

我们还是先从一个例子出发。想象你已经是一家公司的 CEO,你的公司旗下有着优质的明星产品——一
种新型的保健品 “星耀脑黄金”。为了让你的产品卖得更好,你要到处去投放广告,让大家都知道这个产
品,激发大家购买的欲望。我们都知道投放广告是要花钱的,投放得越多,钱花得越多;知道的人越
多,产品卖得越多。
根据历史累计的广告投放经费和销售额,我们可以画出一些点
从这个图上可以看出,有些点位的收益相对较高,有些点位的收益相对较低,但是总体是一个正相关的
关系。很自然地,你内心感觉需要画出一条线,就如下面图中的一样,来拟合投放广告经费和销售额的
关系。
虽然它对于每一个单点来说都不是那么精确,但是它却十分简洁,有了这条线,你只需要设定一个广告
费的数额,就一定可以算出一个销售额。这样当你在开股东大会的时候,就可以跟大家说:“我花这么
多广告费是有价值的,只要我们的投放广告费达到多少多少亿,销售额也就能达到多少多少亿。”
经过了上面的步骤,我们其实就已经完成了线性回归的模型构建,即根据已有的数据去寻找一条直线,
尽量接近这些数据,以用于对以后的数据进行预测,但是具体该怎么去找这条线呢?接下来我们看一下
线性回归的原理。
线性回归的算法原理
首先我们要明确一下,什么是线性,什么是非线性。
线性: 结果与特征之间是一次函数关系,比如表现在上面的例子中就是一条直线。
非线性: 结果与特征之间不是一次函数关系,比如二次函数、三次函数,表现在图中是一条曲线,就是
非线性的。
明白了这两个词,我们再来看回归模型是如何构建的。
比如在我们的例子中只有一个变量就是广告费,一个结果值是销售额。我们假设它们符合线性关系,那
么我们先预设一个方程:
所以我们只要根据数据求出 a 和 b 就完成了。
但是这里有一个困难,那就是我们的数据不是完全分布在这条线上的,那就需要一个方法来评估每次生
成的线的效果,然后去进行相应的调整,以达到最好的效果。提到优化,这里又需要引入两个概念,让
我慢慢介绍。
损失函数: 不要被这个高大上的名称吓到,用一句话来解释,就是计算每一个样本点的结果值和当前的
函数值的差值。当然具体到这里面,所使用的是残差平方和(Sum of Squares for Error),这是一种
最常用的损失函数。如果你对具体的公式感兴趣,可以在网上查到它的具体信息。
最小二乘法: 知道了损失函数,只要有一条线,我们就可以通过损失函数来计算假设结果为这条线的情
况下,损失值的大小。而这里的最小二乘法就是要找到一组 a、b 的值,使得损失值达到最小。这里的
二乘就是平方的意思。
到这里是不是对算法原理的理解就清晰很多了呢?那么我们再来看下它有哪些优缺点。

线性回归的优缺点

优点

运算速度快。由于算法很简单,而且符合非常简洁的数学原理,不管是建模速度,还是预测速度都
是非常快的。
可解释性强。由于最终我们可以得到一个函数公式,根据计算出的公式系数就可以很明确地知道每
个变量的影响大小。
对线性关系拟合效果好。当然,相比之下,如果数据是非线性关系,那么就不合适了。

缺点

预测的精确度较低。由于获得的模型只是要求最小的损失,而不是对数据良好的拟合,所以精确度
略低。
不相关的特征会影响结果。对噪声数据也比较难处理,所以在数据处理阶段需要剔除不相关的特征
以及噪声数据。
容易出现过拟合。尤其在数据量较少的情况下,可能出现这种问题。

逻辑回归(Logistic Regression)

到这里,你可能比较疑惑,为什么我标题里写的线性回归与逻辑回归,而上面一直在讲线性回归?别担
心,我们马上就讲到逻辑回归的相关内容。
如果你已经了解了上面的线性回归,其实你就已经掌握了回归的方法,对于不同的回归算法,只不过是
把对应的函数进行替换,把线性方程替换成非线性方程,或者其他各种各样的方程,以便更好地拟合数
据。
而这里为什么要把逻辑回归拎出来呢?因为我们常说的回归算法是对比分类算法来说的,回归与分类有
很多相似性,是有监督学习的两大分支。我们前面也介绍过,它们的区别是分类算法输出的是离散的分
类结果,而回归算法输出的是连续数值结果,这两种结果可以通过一定的方法进行转换。
而逻辑回归就是这样一个典型的例子。它虽然叫作回归,但实际上却是用来解决分类问题,只不过在中
间过程中,它使用的是回归的方法。
关于分类问题,假设一个二分类问题,只有一个变量 x 会引起结果标签的变化,那么把它俩表示在平面
上就是下图这样的效果,前半部分的结果都是 0,后半部分的结果都是 1。
在逻辑回归算法中,使用了 sigmoid 函数来拟合数据。可以看出 sigmoid 函数有点像是被掰弯的线性
函数直线,这样函数的取值范围被限定在了 0 和 1 之间,很明显,使用这个函数去拟合上面的二分类结
果要比线性直线好得多。
当然,选择 sigmoid 曲线并不是偶然的,而是通过了精密的计算之后得出的方案。这里涉及了概率对数
函数(Logit)的数学推导,这也是逻辑回归名字的来源。同时,在输出结果的时候,借助了极大似然估
计的方法来对预测出的结果进行损失的估计。
当然,逻辑回归里所涉及的内容还有很多,其中使用了大量的数学推导,这里就不再做过多的介绍,如
果你对推导过程感兴趣,可以进行更深入的学习。下面我们进入到动手环节,尝试在代码中使用线性回
归来解决问题。
尝试动手
在代码环节,今天我们仍然尝试自己来生成数据。
首先,仍然是引入我们需要用到的包。

import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
import numpy as np

然后,我们在这里生成数据。我假设了我们的数据偏移量为 2.128,并且生成了 100 个点,作为我们的
样本数据。

def generateData():
    X = []
    y = []

for i in range(0, 100):
    tem_x = []
    tem_x.append(i)
    X.append(tem_x)
    tem_y = []
    tem_y.append(i + 2.128 + np.random.uniform(-15,15))
    y.append(tem_y)
    plt.scatter(X, y, alpha=0.6)
    return X,y

在我们的主方法中,首先使用生成样本的方法生成了我们的数据,然后这次使用了 sklearn 中自带的数
据切割方法对数据进行了切分,80% 作为训练集、20% 作为测试集。然后调用了线性回归算法,并使
用预测方法对测试数据进行预测。


if __name__ == '__main__': np.random.seed(0) 
    X,y = generateData() 
    print(len(X)) 
    X_train,X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0) 
    regressor = LinearRegression() 
    regressor.fit(X_train, y_train) 

    plt.show()

在最后,根据预测的结果绘制出了算法学习到的直线,如下图显示:
通过上面的代码,我们成功实现了使用线性回归算法来学习数据的规律,并拟合出一条直线。当新数据
来了之后,直接把样本数据套入这个直线的方程中,就可以计算出结果。

总结

完成了动手环节,让我们再来回顾一下本课时的重点内容。在这节课中,我们介绍了回归方法,其中主
要讲解了线性回归,同时简单介绍了逻辑回归。它俩虽然都有 “回归” 这个字眼,却存在着一些区别,当
然,也有着一些相似。然后我们借助工具包实现了线性回归的代码调用,并绘制了相应的图像来展示回
归的效果。
回归方法是非常常用的数据分析和数据挖掘方法,它的原理简单、运行快速,在很多数值型的预测需求
中都发挥着巨大的价值。当然,除了这一小节中讲的线性回归和逻辑回归,还有很多不同的回归方程可
以使用,以解决不同的问题。

十九节

这次的实践是针对我们的回归算法进行的练习。我们依然从数据获取、模型训练以及效果评估几个步骤
来练习,看看如何使用线性回归来预测房价。

数据获取

与我们之前使用的鸢尾花数据集一样,波士顿房价数据集也是一个非常常用的公开数据集。你可以在下
面的页面中下载数据。当然,该数据集也被纳入了 sklearn 中,你可以使用 sklearn 中的数据加载方法
来获取该数据。
https://archive.ics.uci.edu/ml/machine-learning-databases/housing/
从该页面,我们可以下载两个文件,其中 housing.data 是所有的数据、housing.names 记录了对数据
的介绍。
从数据的介绍中我们可以知道,该数据集包含美国人口普查局收集的美国马萨诸塞州波士顿住房价格的
有关信息,其中包含了 506 条数据,每条数据有 14 个属性。我们这次要做的就是把前面 13 个当作特
征,最后一个房价中位数作为我们要预测的结果。下面的表格里,我列出了每个属性的名称和含义。
序号 属性名 属性含义
1 CRIM 人均犯罪率
2 ZN 超过 25000 平方英尺的规划住宅用地比例
3 INDUS 城镇非零售商业占地
4 CHAS 与查尔斯河之间的距离,在河边为 1,不在河边为 0
5 NOX 一氧化氮浓度
6 RM 每套住宅平均房间数目
7 AGE 1940 年前简称用房的居住率
8 DIS 与波士顿五大商务中心的加权距离
9 RAD 上快速路的难易程度
10 TAX 每一万美元全额财产税的税率
11 PTRATIO 城镇小学教师比例
12 B 计算方法为 1000(Bk-0.63)^2, 其中 Bk 小时黑人所占人口比例
13 LSTAT 低收入人群比例
14 MEDV 居住房屋的房价中位数(单位:千美元)
这次的实践是针对我们的回归算法进行的练习。我们依然从数据获取、模型训练以及效果评估几个步骤
来练习,看看如何使用线性回归来预测房价。
数据获取
与我们之前使用的鸢尾花数据集一样,波士顿房价数据集也是一个非常常用的公开数据集。你可以在下
面的页面中下载数据。当然,该数据集也被纳入了 sklearn 中,你可以使用 sklearn 中的数据加载方法
来获取该数据。
https://archive.ics.uci.edu/ml/machine-learning-databases/housing/
从该页面,我们可以下载两个文件,其中 housing.data 是所有的数据、housing.names 记录了对数据
的介绍。
从数据的介绍中我们可以知道,该数据集包含美国人口普查局收集的美国马萨诸塞州波士顿住房价格的
有关信息,其中包含了 506 条数据,每条数据有 14 个属性。我们这次要做的就是把前面 13 个当作特
征,最后一个房价中位数作为我们要预测的结果。下面的表格里,我列出了每个属性的名称和含义。
在代码中,我们首先还是引入我们需要用到的各种包。

from sklearn.datasets import load_boston 
import pandas as pd
from sklearn.model_selection import train_test_split 
import matplotlib.pyplot as plt
from sklearn import metrics 
import numpy as np 
from sklearn.linear_model import LinearRegression

接下来我们看一下数据。这里由于使用 sklearn 可以直接加载数据,所以我就没有使用上面下载的数
据。如果你想自己读取和处理数据,也可以直接从下载的文件进行加载。
使用上面的方法加载的数据集就会被存储在 “boston” 这个变量中,下面我们来看一下这是一个什么类
型的数据。
输出结果如下,可以看到输出的类型是 sklearn 中的一个类型 Bunch,经过查询我们可以知道 Bunch
实际上是一种字典类型。

<class ‘sklearn.utils.Bunch’>

那么接下来我们来查看一下该字典中的 key 值都有什么。
输出结果如下,我们可以看到字典的 key 值有 5 个,分别为数据、标签、特征名称、描述和文件名。其
中的数据和标签就是两个数组了。

dict_keys([‘data’, ‘target’, ‘feature_names’, ‘DESCR’, ‘filename’])

下面我们把特征名称输出出来看一下。

print(boston.feature_names)

从输出结果我们可以看到跟我最前面介绍的字段名称一样的结果,除了标签字段 “MEDV” 没有包含在这
里面。

[‘CRIM’ ‘ZN’ ‘INDUS’ ‘CHAS’ ‘NOX’ ‘RM’ ‘AGE’ ‘DIS’ ‘RAD’ ‘TAX’
‘PTRATIO’ ‘B’ ‘LSTAT’]

紧接着我们来看一下数据的规模,我们使用下面这个语句。
输出的结果如下,说明 data 字段有 506 行、13 列,这也跟我们之前介绍的一样。
接下来我们要开始构建训练集和测试集了。先把数据和标签取出来,使用 pandas 的 DataFrame 进行
封装。

X = boston.data 
y = boston.target 
df = pd.DataFrame(X, columns= boston.feature_names) 
print(df.head())
print(df.info()) #查看数据信息

查看描述功能输出的是对数据集中每个字段的一些统计结果,包括数量、最大最小值、均值、标准差等
内容。根据描述方法的结果,可以对每个字段的数据分布有一个大致的了解。

print(df.describe())

看完了具体的数据情况,使用我们前面已经使用过的分割数据集的方法,对数据进行切分,我这里设置
的测试集比例为 20%,我们总共有 506 条数据,那么就会有 101 条成为测试集,其余 405 条为训练
集,代码如下:

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,random_state=0)

模型训练

处理好数据集,接下来就可以进入到模型训练环节了。模型训练的语句很简单,而且训练的速度很快,
因为我们的数据量很小。

regressor = LinearRegression()
regressor.fit(X_train, y_train)

我们知道,线性回归模型可以根据数据拟合出一条直线,使得损失值最小。既然有一条直线,那一定有
截距和斜率,下面我们来看一下我们训练好的模型中,截距和斜率分别是多少。

print(regressor.intercept_)
coeff_df= pd.DataFrame(regressor.coef_, df.columns, columns=[‘Coefficient’]) print(coeff_df)

斜率也就是我们的特征系数,所以每一个特征都会有一个系数。如果系数是正的,说明这个属性对房价
提升有帮助;如果系数是负的,说明这个属性会导致房价下跌。
既然有了截距和斜率,下面我们就可以使用模型来对测试集进行预测了。为了便于了解预测的结果,我
们把预测结果和实际的结果一起输出,对照看一下预测的效果。

y_pred = regressor.predict(X_test) 
test_df = pd.DataFrame({'Actual': y_test.flatten(), 'Predicted':y_pred.flatten()}) 
print(test_df)

还可以使用下面的代码,用条形图的方式来展示对比,这里我们使用 top25 条数据

 test_df1 = test_df.head(25)
test_df1.plot(kind='bar',figsize=(16,10)) plt.grid(which='major', linestyle='-', linewidth='0.5', color='green') plt.grid(which='minor', linestyle=':', linewidth='0.5', color='black') plt.show()

生成的图像如下,其中蓝色的条纹是实际值,橘红色的条纹是预测值。整体看来,预测值与真实值的差
距不大。

效果评估

模型训练好了,预测结果也已经产出了,接下来就进入效果评估阶段。
关于回归模型的效果评估有一些常用的指标,其实我们在前面的课程中也涉及了一点,这里我把几个公
式也写在下面了,其实不是很难理解。
MAE(Mean Absolute Error)平均绝对误差。这个很简单,就是把预测值和实际值的差值计算出来然
后求平均。
MSE(Mean Squared Error)叫作均方误差。这个应该很熟悉了,在介绍线性回归时,这个就是我们
的损失函数。
test_df1.plot(kind=‘bar’,figsize=(16,10)) plt.grid(which=‘major’, linestyle=‘-’, linewidth=‘0.5’, color=‘green’) plt.grid(which=‘minor’, linestyle=‘:’, linewidth=‘0.5’, color=‘black’) plt.show()
RMSE(RootMean Squared Error)均方根误差。这个你可能觉得不就是在 MSE 上面开了个根号吗,
有什么价值?对于数值来说其实没有太大的区别,但是开根号的作用是让解释性更强,因为 MSE 是平
方后的结果。
就拿我们这一课时中预测的房价来说,单位是千美元,平方之后就不好解释了,再开根号就可以让计算
单位回到和数据一致的状态。比如下面的结果中,我们就可以说误差是 5.7 千美元。
当然,在 sklearn 中也已经有了封装好的方法供我们调用,具体可以参见下面的代码。

print('MeanAbsolute Error:', metrics.mean_absolute_error(y_test, y_pred)) 
print('MeanSquared Error:', metrics.mean_squared_error(y_test, y_pred))
print('RootMean Squared Error:', 
 np.sqrt(metrics.mean_squared_error(y_test, y_pred)))

总结

这节课中,我们使用了线性回归算法去实际处理了一个房价预测的问题,从数据的获取,数据的展示,
再到模型训练和效果评估,算是一个比较完整的处理过程了。同时这节课里面也涉及了比较多的辅助代
码,希望能够对你平时的工作或者学习有所帮助。
好了,这一节实践课就到此结束了,同时关于回归问题的讲解也告一段落。不知道你是否对这部分的内
容有了一定的掌握。其实回归算法的理念很容易理解,只不过要找到适合你的数据的回归算法需要一些
经验。

二十节

这一课时,我们进入第四种数据挖掘算法——关联分析的学习。关联分析是一种无监督学习,它的目标
就是从大数据中找出那些经常一起出现的东西,不管是商品还是其他什么 item,然后靠这些结果总结
出关联规则以用于后续的商业目的或者其他项目需求。

一个例子

不管你在哪一个数据挖掘课堂上,几乎都会听到这样一个 “都市传说”:在一个大型超市中,数据分析人
员整理了一整年的购物篮数据,来分析大家都买过什么样的东西。就在对购物篮的数据进行分析的时
候,分析人员惊奇地发现,与“尿不湿” 出现在一个购物小票上最频繁的商品竟然是啤酒。
这个结果背后的原因,是女人嘱托丈夫去超市给孩子买尿不湿,而丈夫通常会顺便买上一些自己喜欢的
啤酒。超市发现了这个神奇的组合,于是毫不犹豫地把尿布和啤酒摆在了一起进行销售,以起到互相促
进的作用。
关于这个故事是否真实发生过,我们保持怀疑的态度,但是这个故事确实反映了数据挖掘在商业运作中
的价值。同时,购物篮分析也早已经是大型商超必备的技术手段。那么,如何发现这些潜在的价值,就
用到了我们这一课时要讲的关联分析算法。

算法原理

要了解算法原理,首先我们需要对关联分析中的一些概念进行一下解释。
为了方便说明,我在这里编造了十条购物小票数据(如有雷同,纯属巧合),如下表所示。
表 1 购物小票数据
序号 购物小票
1 尿布,啤酒,奶粉,洋葱
2 尿布,啤酒,奶粉,洋葱
3 尿布,啤酒,苹果,洋葱
4 尿布,啤酒,苹果
5 尿布,啤酒,奶粉
6 尿布,啤酒,奶粉
7 尿布,啤酒,苹果
8 尿布,啤酒,苹果
9 尿布,奶粉,洋葱
10 奶粉,洋葱

项集(Item Set): 第一个要介绍的概念叫项集,中文名称有点拗口,还是看英文比较容易理解。项
集可以是单个的项,也可以是一系列项目的合集。在我们的例子中,项目就是啤酒、尿布等商品,一个
小票上的内容就可以看作一个项集,通过关联分析得到的经常一起出现的啤酒和尿布可以称为一个 “频
繁项集”。
关联规则: 根据频繁项集挖掘出的结果,例如 {尿布}→{啤酒},规则的左侧称为先导,右侧称为后继。
支持度: 支持度就是一个项集在数据中出现的比例。在我们的 10 条数据中,{尿布} 出现了 9 次,那它
的支持度就是 0.9;{啤酒} 出现了 8 次,啤酒的支持度就是 0.8。{尿布,啤酒} 的支持度是 8/10=0.8,
以此类推。支持度还可以用来判定一条规则是否还需要继续进行挖掘,如果支持度已经很低,再加入新
的项肯定会更低,挖掘的意义不大。
置信度: 置信度指的是在一条规则中,出现先导也出现后继的比例。我们可以用公式来看一下,
置信度表示的是一条规则的可靠程度。
提升度: 在置信度中,只考虑了规则中的先导与后继同时发生的情况,而对于后继单独发生的情况没有
加以考虑。所以又有人提出了一个提升度,用来衡量先导和后继的独立性。比如在前面我们算出 “尿布
→啤酒” 的置信度为 0.89,这说明买了 “尿布” 的人里有 89% 会买 “啤酒”,这看起来已经很高了。但是
如果在没有买“尿布” 的购物小票中,购买 “啤酒” 的概率仍然为 0.89,那其实购买 “尿布” 和购买 “啤酒”
并没有什么关系。所以,提升度的计算公式如下:
如果提升度大于 1,说明是有提升的。
确信度: 最后一个指标是确信度,确信度指的是对于一条规则,不发生先导而发生后继的概率与这条规
则错误的概率比值。公式如下:
上面的结果为 1.82,这说明该条规则是真的概率比它只是偶然发生的概率高 82%。
一口气看了这么多的概念和指标,下面终于进入我们的正题环节,看看在关联挖掘中的算法原理。

Apriori

关联挖掘的目标已经很明确了,而关联挖掘的步骤也就只有两个:第一步是找出频繁项集,第二步是从
频繁项集中提取规则。Apriori 算法的核心就是:如果某个项集是频繁项集,那么它的全部子集也都是
频繁项集。
为了方便了解算法的效果,我预先画出了数据集中所有可能存在的项集关系,就如下图显示:
首先,需要设定一个最小支持度阈值,假设我们设定为 0.5,那么高于 0.5 的就认为是频繁项集。然
后,我们计算出所有单个商品的支持度,如下表所示:
项集 支持度
尿布 0.9
啤酒 0.8
奶粉 0.6
洋葱 0.5
苹果 0.4
从这里可以看出,“苹果”的支持度达不到阈值,于是把它删掉。因此,所有跟 “苹果” 相关的父集也都是
低频的,在后续的计算也不会涉及了,就是下图标绿色的这些。
接下来我们再计算二阶的项集支持度。
项集 支持度
尿布,啤酒 0.8
尿布,奶粉 0.5
尿布,洋葱 0.4
啤酒,洋葱 0.3
啤酒,奶粉 0.4
洋葱,奶粉 0.4

到了这一步,后四个项集的支持度已经达不到我们的阈值 0.5,于是也删掉。
接下来再计算三阶的项集支持度,这个时候发现已经没有可用的三阶项集了,所以算法计算结束。这时
候我们得到了三组频繁项集 {尿布,啤酒}、{尿布,洋葱}、{尿布,奶粉}。接下来从这三个频繁项集,
我们可以得到三个关联关系:尿布→啤酒,尿布→洋葱,尿布→奶粉。根据前面的公式,分别计算这三
个关系的置信度、提升度和确信度。
表 4 三个关联关系的置信度、提升度和确信度
关系 支持度 置信度 提升度 确信度
尿布 0.9 |||
啤酒 0.8 |||
洋葱 0.5 |||
奶粉 0.6 |||
尿布→啤酒 0.8 0.89 1.1 1.82
尿布→奶粉 0.5 0.55 0.926 0.89
到了这里,再根据我们的需求设定阈值来筛选最终需要留下来的规则。

FP-Growth(Frequent Pattern Growth)

根据上面的 Apriori 计算过程,我们可以知道 Apriori 计算的过程中,会使用排列组合的方式列举出所
有可能的项集,每一次计算都需要重新读取整个数据集,从而计算本轮次的项集支持度。所以 Apriori
会耗费大量的计算资源,这时候就有了一个更高效的算法——FP-Growth 算法。
Apriori 算法一开始需要对所有的规则进行枚举,然后再进行计算,而 FP-Growth 则是首先使用数据生
成一棵 FP-Growth 树,然后再根据这棵树来生成频繁项集。下面我们来看一下如何构建 FP 树。
仍然使用上面编的购物小票数据,并对每一个小票里的项按统一的顺序进行排序,设置一个空集作为根
节点。我们把第一条数据录入这个树中,每一个单项作为上一个节点的叶子节点,旁边的数据代表该路
径访问的次数。下图就是我们录入第一条数据后的结果:
第二条数据与第一条一样,所以只有数字的变化,节点不会发生变化。
接着输入第三条数据。
输入完第二、三条数据之后的结果如下,其中由第一个洋葱和第二个洋葱直接画了一条虚线,标识它们
是同一项。
接下来我们把所有的数据都插入到这棵树上
这个时候我们得到的是完整的 FP 树,接下来我们要从这里面去寻找频繁项集。当然首先也要设定一个
最小频度的阈值,然后从叶子节点开始,也就是最低频的节点。如果频度高于阈值那么就收录所有以该
项结尾的项集,否则就向上继续检索。
至于后面的计算关联规则的方法跟上面就没有什么区别了,这里不再赘述。下面我们尝试使用代码来实
现关联规则的发现。

尝试动手

在我们之前使用的 sklearn 算法工具包中没有 apriori 算法,所以这次我们需要先安装一个 efficientapriori 算法包。而我们这次所使用的数据集,也就是我在上面提到的这个 “啤酒尿布” 数据。这个算法
包使用起来也极其简单。

from efficient_apriori import apriori # 设置数据集
data = [('尿布', '啤酒', '奶粉','洋葱'), ('尿布', '啤酒', '奶粉','洋葱'), ('尿布', '啤酒', '苹果','洋葱'), ('尿布', '啤酒', '苹果'),
('尿布', '啤酒', '奶粉'), ('尿布', '啤酒', '奶粉'), ('尿布', '啤酒', '苹果'), ('尿布', '啤酒', '苹果'), ('尿布', '奶粉', '洋葱'), ('奶粉', '洋葱') ]
itemsets, rules = apriori(data, min_support=0.4, min_confidence=1)
print(itemsets)
print(rules)

#把min_support设置成0.5输出结果
[{啤酒} -> {尿布}]
通过上面的代码,我们就成功使用了 apriori 算法,对我们自己编造的数据集完成了关联关系挖掘,当
在设置最小支持度为 0.4 的时候,我们找到了 7 条规则;而我们把最小支持度改为 0.5 以后,只剩下
{啤酒} -> {尿布} 这一条规则了。你学会了吗?

总结

这节课里,我们介绍了两种关联关系挖掘的方法,其中 Apriori 使用了穷举的方式,而 FP-Growth 使用
了树形结构来提高速度。关联关系挖掘通常使用的算法都非常简单,或者我们可以把关联关系挖掘转化
成分类问题、聚类问题来解决都是可以的。
在这节课中,我们还介绍了关联关系的评估指标,不管是用什么算法来挖掘的关联关系,都可以使用这
些指标来进行评估。
下一课时,我们会进入关联关系挖掘的实践课程,看看如何使用关联关系挖掘来解决业务中的问题

二十一节

在前面的实践课程中,有的是注重对数据挖掘流程的讲解,有的是注重对算法实施的讲解。在这节课
里,我们注重从实际的场景出发,使用数据挖掘流程来处理我们的景点与玩法的关系。接下来就让我们
一起走进场景中,看看如何解决业务中的实际问题吧。

理解业务

在马蜂窝平台,有数以千万计的用户写下了他们旅行的感受,记录了他们旅行的瞬间;更有数以亿计的
用户在浏览这些旅行相关的内容,以启发自己的旅行计划,制定可靠的旅行攻略。
但是在这些内容里,也存在着很多信息不准确、信息过时,或者有价值的信息过少等问题。用户想要从
这些内容里找到自己所需要的信息,往往需要翻阅大量的内容,自己去判断哪些信息是准确的、有价值
的,哪些信息是过时的、无意义的,这给有强烈的旅行需求的用户带来了一丝不便。
在旅行场景下,目的地和 POI(Point of Information)是非常重要的特征。因为旅行是一定要依托于
一个具体的地理位置,去到一座城、进入一家店、感受一片风光。而这些地理位置所具备的特征往往是
不容易随着时间的变化而变化的,也不会因为某个人的特定感受而发生改变。
那么有没有一种可能,我们在浩如云海的数据中去挖掘那些景点的特色,并把这些信息沉淀下来,形成
更加简洁和有价值的信息,以帮助用户提升效率呢?
这就是我们的业务需求,即如何用我们现有的数据去挖掘景点与玩法的关系,从而把景点与玩法关联起
来。

理解数据

了解了我们的业务需求点,接下来就是要看我们都有一些什么样的数据。

POI 信息

首先最重要的当然是 POI 信息,一份标准的 POI 名称是一个良好的开始。围绕着 POI 名称,还有很多
POI 的其他信息,比如经纬度、POI 的热度、POI 所属目的地的上下级关系等,这些信息可以帮助我们
对 POI 进行筛选。
如下图所示,目的地 “丽江” 下属又有很多的 POI,比如丽江古城、束河古镇,这些都是已经结构化好的
数据

内容文本标签与 POI

像之前提到的,在我们积累的数据中,最核心的是用户写的各种游记、攻略等文本信息。这些内容都是
用户出行的真实体验,但正由于是个人的体验,所以即便是对于同一个景点,不同的人体验也会不同。
比如你在景点恰好碰到一个小偷,或者脾气暴躁的工作人员,那你肯定体验不好;又或者是你去沙漠恰
好遇到了下雨,去森林恰好遇到了寒流 / 叶子全都落了等情况,每一个旅行的人都会有自己的感受。
但是,我们希望的是通过大量的数据进行分析,这样既可以获得大多数人的体验效果,又能够兼顾到小
部分人的特殊感受,从用户的角度出发去评价一个景点,而不是一个景点本身的属性。
在内容方面,我们早前就已经建立了一套完整的标签体系,这套标签体系中包含了大大小小六百多个标
签,并按照层级结构划分成三层,标签粒度从粗到细。
第一层标签诸如旅行景色、旅行时间、旅行玩法、美食等;第二层包含城市景观、人文景观、冰雪运
动、户外运动等;第三层级包含滑雪、爬山、自驾等最具体的标签,这里面已经收纳了比较完善的玩法
标签。
当用户上传了自己写的内容之后,我们的算法处理流程会使用自然语言处理算法和图像处理算法来分析
每一篇内容,并为其标注标签
在我们的用户所写内容中,通常有这样两种情况:
第一种,如上面这个用户写的,其中只涉及一个景点 “绍兴鲁迅故里景区”,在我们的后台可以看到,通
过我们的算法挖掘之后,这条内容已经关联上“新攻略、名人故居、人文景观、观光景点、9 月” 这样几
个标签。其中的 “名人故居、人文景观、观光景点” 都属于玩法类标签。由于这条内容介绍的景点很集
中,所以这条内容上的标签迁移到 POI 上面也相对较准确。
第二种情况,我们再来看一条数据。下图显示的这条内容讲解的是镇江一日游,其中涉及了很多的景
点,还有出行路线等很多内容。
因此,在我们的后台可以看到,经过算法的挖掘之后,这条内容能够标注的标签也比较多:一日游、寺
庙、日程、行程规划、观光景点、人文景观、公园、6 月、春季、城市景观、博物馆等,都被标在了这
条内容上面。
这里面的标签如果都跟金山寺做关联,那准确度肯定相对比较低,但是好在我们有众多的数据,可以进
行交叉校验和筛选,最终那些出现较少的标签将会被筛掉
了解了我们的业务和数据,我大致想到两个比较简单的构建景点与玩法关系的方案。
方案一:使用词向量模型构建词关系。
首先,不管是景点还是玩法,都是一个词汇,而这些词汇会经常出现在用户写的内容中,同时我们有大
量的内容可供我们去构建预训练语言模型。
当然,现在网上有很多已经训练好的开源模型供大家使用, 但是网上的预训练模型往往使用的是比较常
见的新闻语料,对于我们这种垂直领域的内容适配效果不太好。
在模型的选取上,我们还是使用了前面课时所用到的 Word2Vec 词向量模型,而没有选取比如 BERT,
主要是受到机器条件的限制,如果要自行训练一个 BERT 模型,需要耗费大量的时间,而在效果上的差
距不足以弥补时间上的损失。
关于 Word2Vec 的训练方式在实践 2(如何使用 word2vec 和 k-means 聚类寻找相似的城市)中已经
讲解过,而这个算法我们也会在后面的课时里进行更详细的介绍。如果训练好了 Word2Vec 词向量模
型,我们就可以使用在里面出现过的景点词汇与玩法词汇向量来计算关系的远近。
方案二:基于统计的关联关系挖掘。
除了上面的方案,第二个想到的方案就是根据我们算法,在每篇文章已经标注的玩法标签与景点信息
中,计算景点与玩法共同出现的次数,然后通过关系挖掘中的支持度、置信度、提升度、确信度等指标
进行筛选,以提升准确率。
当然,除了这两种方法,还有很多其他的方案,也可以把这种关联关系挖掘转换成分类任务等,这里暂
且不涉及那些部分。

准备数据与模型训练

清楚了我们的业务需求和数据情况,并且已经有了一个大致的方案,那么接下来就是准备数据环节了。
首先我们需要准备好景点与玩法的词汇表,方便后面的筛选。玩法词相对比较简单,而景点词需要多费
点心思,因为在景点 POI 中有很多重名的,或者别名,这需要在处理的时候特别注意
针对方案一,我们需要准备的是所有分好词的内容,当然我们的分词系统需要加入景点与玩法词汇,以
保证这些词可以优先被分出来。有了这些语料,就可以训练 Word2Vec 模型,然后根据景点 POI 和玩
法词汇表,把 Word2Vec 中对应的词向量取出来,计算景点 POI 与每一个玩法词的距离,最终获得一
个与 POI 词汇数一样的长度、与玩法词数一样维度的数据表,每一个维度存储 POI 与当前玩法词的向
量距离。
这个方案的筛选,对于每一个 POI,首先保留前 30 个距离最近的标签,然后依赖于人工观察,以确认
我们所需要保留的最小距离,然后把小于我们所设定阈值的标签去掉。
针对方案二,由于所有 POI 和标签特征都已经做好结构化,我们只需要去遍历数据库中的所有数据,并
把对应的 POI 和标签逐个进行加法运算,最终获得每个 POI 对应共同出现过的标签及其次数。
在筛选的过程中,我们会参考每个标签和 POI 出现的绝对次数,配合支持度、置信度等几个指标设定阈
值,最终保留我们认为足够置信的结果。下图就是我们对 “洪崖洞” 这个 POI 统计后的结果

评估与应用

说起来两个关联挖掘的方案都不算太难,不过结果却是非常有价值的。当然,只产出了上面的内容只能
算是一个良好的开端,这些东西最终要落地应用,还需要经过很多步骤。
首先,我们与运营一起对两个方案进行了效果的评估,这个评估主要依赖于人工校验的方式,借助人工
的先验知识,来抽样检查结果的准确性。其中,方案二所挖掘到的效果相对较好,所以我们把它的结果
作为基准数据,同时把方案一的结果作为辅助校验与方案二的结果融合在一起。
完成了评估,我们也就可以用这些数据去做一些有意思的事情,比如使用这些数据构建一个知识图谱。
既然是针对大量的景点 POI 与玩法挖掘的关联关系,一个景点可以与多个玩法发生关系,一个玩法也可
以与多个景点发生关系,一个目的地下可以有多个景点,这就构成了一张庞大的网状结构。所以可以想
到的就是把这些数据沉淀在图数据库中,去建设一个知识图谱,以方便我们进行查阅。
下面这张图展示的就是我们构建的目的地知识图谱的展示页面,当然知识图谱中还包括了大量的其他技
术手段,本小节所用到的关联关系只是其中的很小一部分,或者算是初步的骨架,如果后面有机会我们
再去深入讲解相关的课程。
当然,景点与玩法的关系应用不只是图谱这一种形式,我们通过挖掘的手段获取了大量景点的玩法信
息,这些信息来自无数的作者贡献的内容。其中所蕴含的丰富知识是有限的运营人员所无法覆盖的,所
以这在我们的景点 POI 榜单生产、内容生产供需分析,甚至是推荐搜索系统中,都在持续发挥着作用。

总结

这一小节讲到这里就告一段落了,这节课里面没有涉及任何代码,而是主要从整个业务流程上讲解了具
体去做一个关联分析项目的过程。
我们从提出去寻找景点与玩法的业务需求开始,深入理解了我们的业务与数据情况,接下来是制定我们
的方案并实施。关联分析的方案几乎是基于统计来进行计算,所用到的方法通常都非常简单,只要我们
解决了工程性的问题就不会有太大的难度。但是它所蕴藏的价值是巨大的,在最后,我简单介绍了我们
所获得的结果与应用,并展示了我们目前的知识图谱页面。
通过这一小节的学习,不知道你是否又获得了一些启发呢?在你的日常工作中,是否也存在着可以进行
关联关系挖掘的场景呢?

二十二节

其实到上一节,数据挖掘的四种常见问题和算法,我们已经学习完了,从这一小节,我们进入到自然语
言处理的相关学习。
我觉得自然语言处理是数据挖掘领域最有意思、最有深度的部分。与我们前面算法所处理的结构化数据
不同,自然语言是由人们自由表达的内容,显然是一些非格式化的数据,并且存在着歧义、多义、无序
等特点,所以要从这些语言文字中挖掘出有价值的信息也不是一件简单的事情。
但是算法就是如此美妙,能够完成这种看似不可能的任务,虽然目前的效果还不能说尽善尽美,不过随
着技术的进步,我们也确实看到这些算法在不断地演化、进步,相信在不久的将来,一定能够达到足够
优秀的水准。

自然语言处理的历史

一切技术的发展都是螺旋式上升的,自然语言处理技术也依循了这样一个路径。
早期阶段(1956 年以前):自从提出了著名的 “图灵测试”,人工智能的概念就逐渐进入了研究者的视
野。人类的语言,作为人类及其特殊的智慧结晶,让机器理解人类语言当然也是人们迫不及待想要攻克
的难题。香农曾提出过概率模型来描述语言,而乔姆斯基提出了基于规则的上下文无关文法。这一阶段
还没有太明确的产出,只有一些简单的拼凑。
快速发展(1957-1970):随后自然语言处理进入了快速发展期,这个阶段两大派别分别从概率模型和
规则模型分别进行了深入的研究。同时,一些非常有影响力的概念也在这个时期提了出来,比如神经网
络方法和知识库方法(也就是现在知识图谱的原型)。在这个阶段,基于规则的方法占据了主要地位,
使用规则构建机器翻译已经小有成效。但是,自然语言处理的难度仍然十分高,众多研究者的热情随着
效果的持续不佳逐渐降低了下来。
瓶颈期(1971-1993):进入 70 年代以后,很多相关的研究都停滞了,不过仍然有小部分研究者在持
续进行研究,这个阶段产出的隐马尔科夫模型(HMM)为后面的复苏奠定了基础。
再次爆发(1994 年之后):到了 90 年代,自然语言处理上的两大困难得到了极大的突破,第一个是随
着硬件的升级,运算和存储能力大大增强,原来对硬件设备要求很高的算法可以很快地运行了;第二个
是随着互联网的兴起,获取语料变得十分简单,而且对这方面的需求也大大增加,所以自然语言处理再
次进入研究的爆发期。尤其是最近几年,深度神经网络不断成熟,自然语言处理技术也随之飞速地发
展,各种各样的自然语言处理任务的处理效果都有了很大的提升。
我们这一小节要介绍的 TF-IDF 算法就是自然语言处理早期的产物,而下一小节要介绍的 Word2Vec 算
法则是当前最流行的预训练语言模型的前身。好了,接下来让我们进入正题,看一下 TF-IDF 是如何计
算的。

算法原理

TF-IDF(Term frequency–inverse document frequency),中文翻译就是词频 - 逆文档频率,是一种
用来计算关键词的传统方法。
在早期处理诸如新闻文本的时候,由于文本的长度一般都很长,要想从内容中获取有价值的信息就需要
从中提取关键词,进而使用关键词来表示这篇内容的核心价值。同时,提取关键词也可以看作一种降维
的方式,提取完关键词之后,使用关键词再进行文本分类等算法。
那么 TF-IDF 为什么能够较好地提取关键词呢?下面我们就来看一下。
考虑一下,我们是一家新闻机构,每天都有大量的新闻要发布,根据这些内容,我们来看一下如何计算
TF-IDF。
TF(Term Frequency):TF 的意思就是词频,是指某个词(Term)在一篇新闻中出现的次数。当
然,新闻的长度各不相同,为了更加公平,我们可以对 TF 做一下标准化,用词频除以这篇新闻的总词
数。
但是,单纯地使用词频来作为关键词的特征明显是不合理的,考虑一些非常常见的词,比如说 “你的”“我 的”“今天” 这种词,可能在一篇新闻中会反复出现,但是这种词明显并不重要,于是有了 IDF。
IDF(Inverse Document Frequency):IDF 称为逆文档频率,这个词我们用公式来看一下可能更容
易理解。
如果一个词越普通,那它越可能出现在所有的新闻中,那么分母就越大,IDF 的值就越接近 0。
当然,单纯地使用 IDF 作为关键词也不靠谱,如果你自己随便造了一个没人听过的词加在新闻中,比如
说 “墎碶”,那么上式的分母非常小,所得到的 IDF 值会非常大,但是这个词明显没有什么意义。
于是,TF-IDF 就是结合了这两个结果,使用 TF×IDF 作为最终的结果,可以看到,TF-IDF 与一个词在新
闻中出现的次数成正比,与该词在整个语料上出现的次数成反比。
经过上面这两个值的计算,并把两个值乘起来,TF-IDF 就计算结束了,是不是非常简单明了?接下来我
们看一下 TF-IDF 的优缺点。

算法优缺点

非常明显,TF-IDF 的优点就是算法简单,十分容易理解,而且运算速度非常快。
TF-IDF 也有比较明显的缺点,比如在文本比较短的时候几乎无效,如果一篇内容中每个词都只出现了一
次,那么用 TF-IDF 很难得到有效的关键词信息;另外 TF-IDF 无法应对一词多义的情况,尤其是博大精
深的汉语,对于词的顺序特征也没办法表达。
当然,在传统的基于统计的自然语言处理时代,TF-IDF 仍然是一种十分强大有效的关键词提取方法。
了解了 TF-IDF 算法的基本原理,我们接下来就动动手,用代码来实现 TF-IDF 算法。这次我们使用的是
Gensim 算法工具包,这个工具包我们在前面也介绍过,其中包含了 Word2Vec 等自然语言处理常用的
算法工具。同时,里面也内置了一些语料供我们测试使用。

尝试动手

这里我们使用 text8 数据包,是一个来自 Wikipedia 的语料,大小有 30M 多,总共 1701 条数据。第一
次使用需要下载,可能要花费一点点时间。


import gensim.downloader as api
from gensim.corpora import Dictionary
dataset = api.load("text8")
dct = Dictionary(dataset)
new_corpus = [dct.doc2bow(line) for line in dataset]
from gensim import models
tfidf = models.TfidfModel(new_corpus)
tfidf.save("tfidf.model")
tfidf = models.TfidfModel.load("tfidf.model")
tfidf_vec = []
for i in range(len(new_corpus)):
    string_tfidf = tfidf[new_corpus[i]]
    tfidf_vec.append(string_tfidf)
print(tfidf_vec)

在输出的结果中,一条数据就是一篇内容的所有词的 TF-IDF 值,其中每一个单元有两个值,左边是这
个词的 ID,右边是这个词的 TF-IDF 值。细心的你可能会发现,我们输入的内容长度可能会与输出的结
果长度不一致,这是因为 Gensim 中的 TF-IDF 算法会过滤停用词、去掉单个的字母等,所以最终结果
会缺失一部分。当然,那些词我们认为也不会是关键词,所以在提取关键词的时候也不需要关心。
我们在例子中使用的是英文的语料,如果你想要处理中文,可以安装一个 jieba 分词工具,然后使用
jieba 分词工具把中文语料进行切分,之后的处理步骤就与前面的过程一样了。

import jieba
seg_list = jieba.cut("这是一句话,看你切成啥",cut_all=False)
print("Default Mode: " + " ".join(seg_list))

在提取关键词方面,单纯地使用 TF-IDF 的效果还不是特别好,可以在 TF-IDF 的基础上,叠加词性、词
长度、词位置等特征信息以进行优化。除了 TF-IDF,还有很多关键词提取的方法,如果你对这方面有兴
趣,可以在网上进行更深入的查阅。

总结

这一小节,我们开始涉及了一点关于自然语言处理的知识。我在这一小节讲解了一个比较古老,但是很
实用的关键词提取算法 TF-IDF,它的原理十分简单、易于理解,通过 TF-IDF 的计算,保留了那些出现
频率高的词汇,同时又能够打压那些比较普通的词汇,即便是现在,这个算法仍然有比较广泛的应用。
下一小节,我们将讲一下更加前沿的词嵌入技术 “Word2Vec” 算法。

二十三节

在上一节课,我们简单介绍了自然语言处理的发展历史,然后讲解了 TF-IDF 算法的计算过程,那是一
个非常古老的关键词计算方法。今天,我们要学习自然语言处理的再次爆发期产生的一种新算法:词嵌
入算法。
简单来说,词嵌入算法就是使用一个低维度的向量来表示一个词,并且距离相近的向量在实际的词含义
上也是相近的,比如说 “炸鸡” 的向量与 “啤酒” 的向量距离就要比 “炸鸡” 的向量与 “收音机” 的向量要
近。
不仅如此,词嵌入获得的向量还具备数学运算的关系,比如 “女人” 词向量 + “王冠”词向量 ≈ “女王”词向
量。这是不是很神奇?所以这个到底是怎么实现的呢?我们要先从词向量的表达说起。

独热编码

在计算机中的所有运算都是数学运算,怎么把我们的自然语言处理转换成一种数学形态,一直是一个困
难的问题。文本的长度本身就不确定,而且文本中的词每次也都不一样,因此很难有非常有效的编码方
式来表达文本。
不过有一种使用了很长时间的编码方式就是独热编码(One Hot),独热编码是一位有效编码,其中的
每一位都用来存储一个状态。
现在来举个例子,假设我们的数据里只有 “男”“女” 两个字,那么我们的独热编码就像下面写的这样,一
共有两位。
男 01
女 10
独热编码的好处就是能够让我们的离散的文本数据都变成定长的数据,方便各种算法的处理。但是它的
缺点也很明显,那就是过于稀疏。假设我们的语料库是最近一年的新闻,那可能会有上千万的词,哪怕
经过了各种过滤,仍然会有上百万的长度,每一个词的向量只有一位是 1,其他几十万位都是 0。
比如:
特朗普 [1,0,0,0,0,0,……,0,0,0]
拜登 [0,1,0,0,0,0,……,0,0,0]
希拉里 [0,0,1,0,0,0,……,0,0,0]
克林顿 [0,0,0,1,0,0,……,0,0,0]
奥巴马 [0,0,0,0,0,1,……,0,0,0]
这样的维度对于我们的算法来说实在是一个大灾难,不管是存储还是运算的开销都非常大。同时,独热
编码也没办法记录词与词之间的关系。
随着研究的深入,产生了一种分布式表示的方法来解决独热编码的问题。它的想法是通过对文本词的训
练,把每个词都映射到一个比较短、但是稠密的向量上来。所有的词向量构成了向量空间,从而可以使
用统计学方法来研究词之间的关系。具体要把词向量的维度设为多少,需要我们在训练的时候指定。
比如我们用几个维度来表示上面的几个人物名词
特朗普 拜登 希拉里 克林顿 奥巴马
性别 0.99 0.99 0.01 0.99 0.99
年龄 0.7 0.9 0.7 0.8 0.6
肤色 0.3 0.1 0.2 0.2 0.9
权力 0.8 0.3 0.4 0.9 0.8
财富 0.8 0.7 0.4 0.9 0.2
经过上面表达的转换,我们就已经把几个词向量由十分稀疏的独热编码转换成了一种稠密的向量。当
然,在我们的模型中没有办法去解释每个维度都是什么意思,并且每一个向量只对应一个词,每个词的
表达向量是不同的,映射之后的向量包含了原来的信息。
把原本的词向量映射到这个相对低维空间的过程就称为词嵌入(Word Embedding)。但是词嵌入之所
以能够记录词与词之间的关系,主要是因为在训练的时候使用了上下文关系来进行学习。比如说,在我
们的语料中经常出现 “我的宠物猫是一只可爱的小动物”“我的宠物狗是一只可爱的小动物”,那么“宠物
猫” 和“宠物狗”这两个词的词向量就会非常接近,这是因为它们的上下文都很类似。
说到这里,基本铺垫都已经介绍完了,你大概能够明白词嵌入和词向量的概念,那么接下来我们要进入
正题,看看 Word2Vec 算法是如何计算出我们的词向量的。

算法原理

我们前面讲过人工神经网络,而 Word2Vec 最核心的其实就是采用了神经网络算法,当然它是浅层神经
网络,只包含了一层隐藏层。它的网络结构如下图:
输入层是我们去掉了某些部分的语料编码,在这里也就是 One Hot 编码,输出层的维度与输入层一
样,所需要预测输出的是在输入层被去掉的部分。
所以这里有两种方案,第一个是去掉某个词,输入层是这个词的上下文,这种方法叫作
CBOW(Continuous Bag Of Word),输出层也就是要去预测这个词;第二种方案是去掉上下文,用
这个词作为输入层,这种方法叫作 Skip-Gram,输出层需要预测上下文。
对于这个网络中,从输入层到隐藏层是没有激活函数的,也就是一个线性关系。这里需要注意的是
Word2Vec 所获得的模型并不是我们这个网络的最终结果,而是在训练完成之后,把隐藏层的权重矩阵
获取出来,即形成了我们的 Word2Vec 算法的结果。
通过这个方法,我们语料中的每一个词都获得了一个预设维度的向量,由于隐藏层的维度要比 onehot
向量的维度小很多,这也起到了很好的降维作用。
讲到这里,关于 Word2Vec 最浅显的原理已经介绍完了,其余的部分就是关于神经网络的一些原理,我
们已经在前面的课程介绍过。当然了,关于这个算法的细节部分这里也没有进行太多的讲解,这些内容
你如果有兴趣可以通过相关的论文来进行学习。

推荐论文:
xinrong:《word2vec Parameter Learning Explained》
Quoc Le,Tomas Mikolov:《Distributed Representations of Sentences and Documents》 Tomas Mikolov,Ilya Sutskever,Kai Chen:《Distributed Representations of Words and
Phrases and their Compositionality》

算法优缺点

比起很多传统方法,Word2Vec 考虑了一些上下文信息,以及词语的顺序信息,所以比起传统的基于词
语统计的方法要准确很多。而且 Word2Vec 方法成功降低了向量维度,可以适配后续的各种自然语言处
理任务,通用性很强。同时,这种预训练的思想为大家的下一步研究提供了良好的思路。
当然,Word2Vec 产生的词向量是一对一的,所以一词多义的问题仍然没有办法解决

尝试动手

对于 Word2Vec 的代码使用方法,我们前面其实已经介绍过了,在实践课 2 中我们使用了 Word2Vec
为 K-means 聚类算法提供了聚类的向量材料。这里我把代码粘过来,再回顾一下使用的过程。

import gensim
import os
import re
import sys
import multiprocessing
from time import time
class getSentence(object):
    def __init__(self, dirname):
        self.dirname = dirname

    def __iter__(self):
        for root, dirs, files in os.walk(self.dirname):# 用于通过在目录树中游走输出在目录中的文件名,向上或者向下。
            for filename in files:
                file_path = root + '/' + filename
                for line in open(file_path):
                    try:
                        s_line = line.strip()#用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列。
                        if s_line == "":
                            continue
                        word_line = [word for word in s_line.split()]
                        yield word_line
                    except Exception:
                        print("catch exception")
                        yield ""

if __name__ == '__main__':
    begin = time()
    sentences = getSentence("traindata")
    model = gensim.models.Word2Vec(sentences, size=200, window=15, min_count=10, workers=multiprocessing.cpu_count())
    model.save("model/word2vec_gensim")
    model.wv.save_word2vec_format("model/word2vec_org", "model/vocabulary", binary=False)
    end = time()
    print("Total procesing time: %d seconds" % (end - begin))


#训练K_means
import gensim
from sklearn.cluster import KMeans
from sklearn.externals import joblib
from time import time
def load_model():
    model = gensim.models.Word2Vec.load('../word2vec/model/word2vec_gensim')
    return model
def load_filterword():
        fd = open("mddwords.txt", "r")
        filterword = []
        for line in fd.readlines():
            line = line.strip()
            filterword.append(line)
            return filterword
        if __name__ == "__main__": start = time()
        model = load_model()
        filterword = load_filterword()
        print(len(filterword))
        wordvector = []
        filterkey = {}
        for word in filterword: wordvector.append(model[word])
        filterkey[word] = model[word]
        print(len(wordvector))
        clf = KMeans(n_clusters=2000, max_iter=100, n_jobs=10)
        s = clf.fit_predict(wordvector)
        joblib.dump(clf, "kmeans_mdd2000.pkl")
        labels = clf.labels_
        labellist = labels.tolist()
        print(clf.inertia_)
        fp = open("label_mdd2000", 'w')
        fp.write(str(labellist))
        fp.close()
        fp1 = open("keys_mdd2000", 'w')
        for key in filterkey: fp1.write(key + '\n')
        print("over")
        end = time()
        print("use time")
        print(end - start)

总结

看完了代码部分,这节课又将告一段落了。这是我们关于自然语言处理的第二节课程,当然这两节课程
只是介绍了自然语言处理浩如烟海的知识中很小的一部分,但是我希望通过这两小节课程的学习,你能
够对自然语言处理有一个初步的了解。
在这节课里面,我们介绍了 Word2Vec 算法,从原来的 OneHot 编码讲起,到 Word2Vec 的基本原理
以及 Word2Vec 的两种工作模式。不过,这里所介绍的都是最浅显的部分,关于 Word2Vec 算法还有
很多的细节我们没有涉及。当然了,在自然语言预训练模型方面,现在已经有了很多更加先进和优秀的
算法,比如 BERT、GPT 等,如果你对这方面有兴趣,可以继续学习自然语言处理相关的课程。
下一课时,是我们最后一节实践课——使用 fastText 进行新闻文本分类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值