第2讲 机器学习初探
Optimization——优化问题
-
对于银行运钞车来说,哪条路线是最优路径?
-
对于一家公司而言,手里的钱是有限的,如何把这些金额分配到不同的项目组中?
-
如何选取合理的存货地点,货仓里的不同位置应该放哪些商品?
-
制造一部手机,如何选取合理的元器件成本从而达到最优利润?
-
物流、航空业、交通、外卖配送如何让用户满意度最高?
A Simple Optimization Example: Cut Rod Problem
例如8m的木头拆成6+2更赚钱。
From Dynamic Programming to Machine Learning
original_price = [1, 5, 8, 9, 10, 17, 17, 20, 24, 30, 33] # 长度为1到11的价钱
price = {i+1:p for i, p in enumerate(original_price)} # 将长度和价钱存成字典
solution = {}
def r(n): # the revenue of length n
candidates = [] # 用于记录对于长度为i的木材,所有切法的收益
for i in range(1, n):
candidates.append((r(i) + r(n-i), i))
candidates.append((price[n], 0))
max_price, split_point = max(candidates, key=lambda x: x[0]) # 按第一个元素找最大
global solution
solution[n] = (split_point, n-split_point)
return max_price
长度为8的最优解
r(8)
=========================================================
Output:
22
打印一下切割方法,8分成2+6,2是0+2,6是0+6
solution
{1: (0, 1),
2: (0, 2),
3: (0, 3),
4: (2, 2),
5: (2, 3),
6: (0, 6),
7: (1, 6),
8: (2, 6)}
当然上面代码的问题在于字典长度只能是11,如果要求分一根长度为12的木材则无能为力。
为了解决这个问题,在python中可以如下修改代码:
from collections import defaultdict
price = defaultdict(int)
for i, p in enumerate(original_price):
price[i+1] = p
但是这种解决问题的方法会导致子问题以指数级上升,使用%%time
可以发现增加一个木材长度运行耗时会显著增加。
Decorator——装饰器
为了能对程序运行时间有个准确的度量,python中通常会采用用下面的方式
from datetime import datetime
import time
def func_1(n):
begin = datetime.now()
time.sleep(0.1)
print('used time = {}'.format(datetime.now() - begin))
return n
def func_2(n):
begin = datetime.now()
sum_ = 0
for i in range(n**n):
sum_ += 1
print('used time = {}'.format(datetime.now() - begin))
return n
func_1(9)
func_2(9)
=========================================================
Output:
used time = 0:00:00.104540
9
used time = 0:00:57.352702
9
这种方法通常只用在debug中,若在实际情况下,通常使用下面这种方式
## Python 是一个可以面向函数编程的语言
def func_1(n):
time.sleep(0.1)
return n
def get_func_time(func):
def _wrap(n):
begin = datetime.now()
result = func(n)
print('used time = {}'.format(datetime.now() - begin))
return result
return _wrap
func_1_with_time = get_func_time(func_1)
func_1_with_time(9)
=========================================================
Output:
used time = 0:00:00.109891
9
这样做的好处是,如果以后想知道其他函数的运行时间,可以避免修改源代码,直接调用get_func_time函数即可。
在python中还可以进一步使用装饰器简化
"""
@another_func
def some_func():
pass
==> some_func = another_func(some_func)
"""
@get_func_time
def func_1(n):
time.sleep(0.1)
return n
def get_func_time(func):
def _wrap(n):
begin = datetime.now()
result = func(n)
print('used time = {}'.format(datetime.now() - begin))
return result
return _wrap
func_1(9)
那么装饰器如何来帮助我们动态规划呢?
我们用空间换时间,使用数组chache存储已经计算过的值避免重复计算
def memo(func):
cache = {}
def _wrap(n):
if n in cache:
result = cache[n]
else:
result = func(n)
cache[n] = result
return result
return _wrap
下面要执行memo®,使得每次调用函数r时都先访问cache数组,而我们要做的的,就是加上一句话@memo
。
solution = {}
@memo # 只要加一句话
def r(n):
candidates = []
for i in range(1, n):
candidates.append((r(i) + r(n-i), i))
candidates.append((price[n], 0))
max_price, split_point = max(candidates, key=lambda x: x[0])
global solution
solution[n] = (split_point, n-split_point)
return max_price
下面是原来的运行时间
%%time
r(14)
=========================================================
Output:
Wall time: 5.14 s
41
使用cache之后
%%time
r(14)
Output:
Wall time: 0 ns
41
%%time
r(256)
=========================================================
Output:
Wall time: 59.2 ms
768
效率上有了飞跃。
代码还可以进一步简化
solution = {}
@memo # 只要加一句话
def r(n):
# candidates = []
# for i in range(1, n):
# candidates.append((r(i) + r(n-i), i))
# candidates.append((price[n], 0))
# max_price, split_point = max(candidates, key=lambda x: x[0])
max_price, split_point = max([price[n], 0] + [(r(i)+r(n-i), i) for i in range(1, n)], key=lambda x:x[0])
global solution
solution[n] = (split_point, n-split_point)
return max_price
下面我们再来写一个解析函数
def not_cut(n): return n == 0
def parse_solution(target_length, revenue_solution):
left, right = revenue_solution[target_length]
if not_cut(left): return [right]
return parse_solution(left, revenue_solution) + parse_solution(right, revenue_solution)
parse_solution(111, solution)
=========================================================
Output:
[10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11]
r(111)
=========================================================
Output:
333
动态规划·解决问题——分治(Divide and Conquer)
- 识别子问题 Sub-Problems Dividing——这个问题可以被分割成若干子问题
- 识别子问题中的重叠特点 Over-Lapping Sub-Problem——子问题之间存在overlapping重合
- 存储子问题的答案 Cache Sub-Solutions——对重合的子问题进行cache(缓存)
- 合并问题答案 Combine Solutions——依据cache数据加速解决问题
- 解析答案 Parse Solutions——还要构建parse solution函数解析最终的结果
eg.
- Edit Distance 编辑距离
- 基因测序里边的相似基因判断
- 图形学里边的覆盖问题
- 路径规划问题
- ……
机器学习 Machine Learning
可是,如果我们现在的条件极其多,或者分解情况极其复杂,分治可能会很困难。
于是乎,就有了机器学习的出现。
监督式学习 Supervised Learning
可见,根据向量计算结果可以把机器学习分为好几类
在email语境下,
- 判断邮件内容真假的可能性是一个回归问题——Regression
- 判断邮件内容真假是真是假是一个分类问题——Binary Classification | Multiclass Classification
- 输入邮件向量并生成符合该邮件类型的一段文字——Sequence Learning
- 邮件重要性排序——Rank
非监督式学习 Unsupervised Learning
聚类
Framework 机器学习的通用框架
监督式学习和非监督式学习的区别
监督式学习会提供y
,其核心是梯度下降(Gradient Descent),用来使 loss function 最小,这与优化问题的思路是一致的。
梯度下降 Gradient Descent
通过梯度计算函数的最小值
下面以函数
f
(
x
)
=
10
x
2
+
37
x
+
9
f(x) = 10x^2+37x+9
f(x)=10x2+37x+9
为例
import numpy as np
def func(x):
return 10 * x**2 + 37*x + 9
import matplotlib.pyplot as plt
x = np.linspace(-10, 10)
plt.plot(x, func(x))
=========================================================
Output:
[<matplotlib.lines.Line2D at 0x143acd27e80>]
def gradient(x):
return 20*x + 37
import random
steps = []
x_optimal = random.choice(x)
x_optimal
=========================================================
Output:
2.6530612244897966
下面要做的就是不断更新x的值,其中 alpha 是学习率
x
=
x
+
(
−
1
)
∗
∂
y
∂
x
∗
α
x = x + (-1)*\frac{\partial{y}}{\partial{x}}*\alpha
x=x+(−1)∗∂x∂y∗α
alpha = 1e-4
for i in range(200):
x_optimal = x_optimal + (-1) * gradient(x_optimal) * alpha
steps.append(x_optimal)
for s in steps:
print(s, func(s))
=========================================================
Output:
2.5630000000000006 169.52069000000006
2.4747400000000006 161.80876067600005
2.3882452000000005 154.40222375323043
2.3034802960000005 147.28898569260252
2.2204106900800005 140.45743185917547
2.1390024762784003 133.8964075575521
2.0592224267528323 127.59519981827302
1.9810379782177756 121.54351990546942
1.90441721865342 115.73148651721283
...
-1.8499998932529995 -25.224999999999888
-1.8499998934664936 -25.224999999999888
-1.8499998936795607 -25.224999999999895
-1.8499998938922015 -25.22499999999988
结果稳定在x=-1.85左右
聚类 Cluster Problem
聚类是一种非监督学习
一个实际的问题:
淘宝国际上经常有境外人员从国外销售违禁违法物品,国家要求这些东西全部下架,但是这些人员会更换物品的名字。
例如:
枪支 - 狗子
赌博账号 - 米料
毒品 - 野狼这些我们称为“暗语”,但是我们只知道10 – 30个暗语,所以找到的内容非常有限。
K-Means聚类的思路是像下面这样的
方法论:非常重要的两个经验
-
算法工程师最重要的不是记住了多少算法,而是能把实际问题抽象成算法问题,然后得到答案的能力
-
机器学习方法很多时候是作为整个项目的一部分,单靠机器学习很难解决完整项目
向量a和向量b的偏好preference是更相近的
K-means Finding Center
import math
coordination_source = """
{name:'兰州', geoCoord:[103.73, 36.03]},
{name:'嘉峪关', geoCoord:[98.17, 39.47]},
{name:'西宁', geoCoord:[101.74, 36.56]},
{name:'成都', geoCoord:[104.06, 30.67]},
{name:'石家庄', geoCoord:[114.48, 38.03]},
{name:'拉萨', geoCoord:[102.73, 25.04]},
{name:'贵阳', geoCoord:[106.71, 26.57]},
{name:'武汉', geoCoord:[114.31, 30.52]},
{name:'郑州', geoCoord:[113.65, 34.76]},
{name:'济南', geoCoord:[117, 36.65]},
{name:'南京', geoCoord:[118.78, 32.04]},
{name:'合肥', geoCoord:[117.27, 31.86]},
{name:'杭州', geoCoord:[120.19, 30.26]},
{name:'南昌', geoCoord:[115.89, 28.68]},
{name:'福州', geoCoord:[119.3, 26.08]},
{name:'广州', geoCoord:[113.23, 23.16]},
{name:'长沙', geoCoord:[113, 28.21]},
{name:'海口', geoCoord:[110.35, 20.02]},
{name:'沈阳', geoCoord:[123.38, 41.8]},
{name:'长春', geoCoord:[125.35, 43.88]},
{name:'哈尔滨', geoCoord:[126.63, 45.75]},
{name:'太原', geoCoord:[112.53, 37.87]},
{name:'西安', geoCoord:[108.95, 34.27]},
{name:'台湾', geoCoord:[121.30, 25.03]},
{name:'北京', geoCoord:[116.46, 39.92]},
{name:'上海', geoCoord:[121.48, 31.22]},
{name:'重庆', geoCoord:[106.54, 29.59]},
{name:'天津', geoCoord:[117.2, 39.13]},
{name:'呼和浩特', geoCoord:[111.65, 40.82]},
{name:'南宁', geoCoord:[108.33, 22.84]},
{name:'西藏', geoCoord:[91.11, 29.97]},
{name:'银川', geoCoord:[106.27, 38.47]},
{name:'乌鲁木齐', geoCoord:[87.68, 43.77]},
{name:'香港', geoCoord:[114.17, 22.28]},
{name:'澳门', geoCoord:[113.54, 22.19]}
"""
首先通过提取出得到各城市的坐标
import re
pattern = re.compile(r"name:'(\w+)',\s+geoCoord:\[(\d+.\d+),\s(\d+.\d+)\]")
city_location = {}
for line in coordination_source.split('\n'):
city_info = pattern.findall(line)
if not city_info: continue
city_name, long, lat = city_info[0]
long, lat = float(long), float(lat)
city_location[city_name] = [long, lat]
city_location
=========================================================
Output:
{'兰州': [103.73, 36.03],
'嘉峪关': [98.17, 39.47],
'西宁': [101.74, 36.56],
'成都': [104.06, 30.67],
'石家庄': [114.48, 38.03],
...
'西藏': [91.11, 29.97],
'银川': [106.27, 38.47],
'乌鲁木齐': [87.68, 43.77],
'香港': [114.17, 22.28],
'澳门': [113.54, 22.19]}
有了各城市的坐标,我们可以通过球面地理距离计算得到两地距离
def geo_distance(origin, destination):
"""
Calculate the Haversine distance.
Parameters
----------
origin : tuple of float
(lat, long)
destination : tuple of float
(lat, long)
Returns
-------
distance_in_km : float
Examples
--------
>>> origin = (48.1372, 11.5756) # Munich
>>> destination = (52.5186, 13.4083) # Berlin
>>> round(distance(origin, destination), 1)
504.2
"""
lon1, lat1 = origin
lon2, lat2 = destination
radius = 6371 # km
dlat = math.radians(lat2 - lat1)
dlon = math.radians(lon2 - lon1)
a = (math.sin(dlat / 2) * math.sin(dlat / 2) +
math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) *
math.sin(dlon / 2) * math.sin(dlon / 2))
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
d = radius * c
return d
geo_distance(city_location['上海'], city_location['杭州'])
=========================================================
Output:
163.0760821403945
下面我们把各省会的地理位置画出来看看
from pylab import mpl
mpl.rcParams['font.sans-serif'] = ['FangSong'] # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题
import matplotlib.pyplot as plt
%matplotlib inline
import networkx as nx
city_graph = nx.Graph()
city_graph.add_nodes_from(list(city_location.keys()))
nx.draw(city_graph, city_location, with_labels=True, node_size=30)
下面我们来找到合理的能源中心
首先,我们需要初始化5个随机位置
import random
all_x = []
all_y = []
for _, location in city_location.items():
x, y = location
all_x.append(x)
all_y.append(y)
def get_random_center(all_x, all_y):
r_x = random.uniform(min(all_x), max(all_x))
r_y = random.uniform(min(all_y), max(all_y))
return r_x, r_y
K = 5
centers = {'{}'.format(i+1): get_random_center(all_x, all_y) for i in range(K)}
centers
=========================================================
Output:
{'1': (104.34863684999705, 42.66669683611392),
'2': (105.02489029295833, 24.957143321255987),
'3': (89.2924475965155, 33.698874993456734),
'4': (120.592499526194, 42.26908667672668),
'5': (123.67108212169859, 29.10021861496785)}
下面进入K-means的核心部分
from collections import defaultdict
changed = True # 中心点有无更新
threshold = 5 # 更新阈值
while changed:
closest_points = defaultdict(list)
# 以center为索引记录所有节点,表示节点距离哪个center最近
for x, y in zip(all_x, all_y):
closest_c, closest_dis = min([(k, geo_distance((x,y), centers[k])) for k in centers], key=lambda t:t[1])
# print('for {}, {} the closest center is {}'.format(x, y, closest_c))
closest_points[closest_c].append([x,y])
changed = False
# 对每个center找其neighber节点,根据是否大于阈值判断是否更新
for c in closest_points:
former_center = centers[c]
neighbors_belong_to_c = closest_points[c]
neighbors_center = np.mean(neighbors_belong_to_c, axis=0)
if geo_distance(neighbors_center, former_center) > threshold:
print('Updated: {} center'.format(c))
centers[c] = neighbors_center # 赋值新的中心点
changed = True
=========================================================
Output:
Updated: 1 center
Updated: 2 center
Updated: 4 center
Updated: 5 center
Updated: 3 center
Updated: 1 center
Updated: 4 center
Updated: 2 center
closest_points
=========================================================
Output:
defaultdict(list,
{'1': [[103.73, 36.03],
[98.17, 39.47],
[101.74, 36.56],
[104.06, 30.67],
[108.95, 34.27],
[106.27, 38.47]],
'4': [[114.48, 38.03],
[113.65, 34.76],
[117.0, 36.65],
[123.38, 41.8],
[125.35, 43.88],
[126.63, 45.75],
[112.53, 37.87],
[116.46, 39.92],
[117.2, 39.13],
[111.65, 40.82]],
'2': [[102.73, 25.04],
[106.71, 26.57],
[113.23, 23.16],
[113.0, 28.21],
[110.35, 20.02],
[106.54, 29.59],
[108.33, 22.84],
[114.17, 22.28],
[113.54, 22.19]],
'5': [[114.31, 30.52],
[118.78, 32.04],
[117.27, 31.86],
[120.19, 30.26],
[115.89, 28.68],
[119.3, 26.08],
[121.3, 25.03],
[121.48, 31.22]],
'3': [[91.11, 29.97], [87.68, 43.77]]})
centers
=========================================================
Output:
{'1': array([103.82 , 35.91166667]),
'2': array([109.84444444, 24.43333333]),
'3': array([89.395, 36.87 ]),
'4': array([117.833, 39.861]),
'5': array([118.565 , 29.46125])}
下面我们把这几个点可视化表现出来
plt.scatter(all_x, all_y)
plt.scatter([x for x,y in centers.values()], [y for x,y in centers.values()])
### 高级Python编程技巧:下面两句话与上面等价
### *可以理解为拆开,zip用于把x和y组合成坐标
# plt.scatter(*[all_x, all_y])
# plt.scatter(*zip(*centers.values()))
最后我们把结果放到地图上
city_location_with_station = {
'能源站-{}'.format(i): position for i, position in centers.items()
}
city_location_with_station
=========================================================
Output:
{'能源站-1': array([103.82 , 35.91166667]),
'能源站-2': array([109.84444444, 24.43333333]),
'能源站-3': array([89.395, 36.87 ]),
'能源站-4': array([117.833, 39.861]),
'能源站-5': array([118.565 , 29.46125])}
def draw_cities(cities, color=None):
city_graph = nx.Graph()
city_graph.add_nodes_from(list(cities.keys()))
nx.draw(city_graph, citise, node_color=color, with_labels=True, node_size=30)
plt.figure(1,figsize=(10,10))
draw_cities(city_location_with_station, color='green')
draw_cities(city_location, color='red')
我们保留一下初始随机的5个点就可以发现明显实现了聚类的效果
draw_cities({'初始点-{}'.format(int(i)+1):p for i,p in the_first_center.items()}, color='yellow')
最后我们用sklearning的聚类结果跟我们的结果比对一下
from sklearn.cluster import KMeans
Xs = np.array(list(city_location.values()))
kmeans = KMeans(n_clusters=5, random_state=0).fit(Xs)
draw_cities({'sk-求解点-{}'.format(int(i)+1):p for i,p in enumerate(kmeans.cluster_centers_)}, color='cyan')
sklearning的算法进行过一些优化,与我们结果有少许出入,但大体还是一致的。
KMeans文本聚类
1 数据集信息
## 澳大利亚广播公司 ABC 发布的新闻头条数据
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.feature_extraction import text
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.cluster import KMeans
from nltk.tokenize import RegexpTokenizer
from nltk.stem.snowball import SnowballStemmer
%matplotlib inline
字段过多的行(例如,带有太多逗号的csv行)会引发异常。如果为False,则将这些“坏行”从返回的DataFrame中删除
# 读取数据集
data = pd.read_csv("../abcnews-date-text/abcnews-date-text.csv",error_bad_lines=False,usecols =["headline_text"])
data.head()
data = data.head(10000) # 获取部分数据快速运行,你可以尝试修改使用的数据量查看后续的建模效果,不过注意使用的数据越多后续模型训练的时间越长
# 打印数据信息
# DataFrame的简短摘要
data.info()
=========================================================
Output:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10000 entries, 0 to 9999
Data columns (total 1 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 headline_text 10000 non-null object
dtypes: object(1)
memory usage: 78.2+ KB
1.1 删除重复数据
查看重复的数据行,pandas.DataFrame.duplicated 使用方法详见:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.duplicated.html
duplicate 用法:用True或False标记是否是重复行
keep 参数:
1.=‘first’,第一次出现为False其他为True;
2.=‘last’,最后一次出现为False其他为True;
3.=False,所有重复项均为False
data[data['headline_text'].duplicated(keep=False)].sort_values('headline_text').head(8)
# 删除重复行,pandas.DataFrame.drop_duplicates 使用方法详见:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.drop_duplicates.html
########## 第一题 ~ 1行 ##########
######### your code #########
data = data.drop_duplicates('headline_text')
2. 数据预处理
2.1 为向量化表示进行前处理
进行自然语言处理时,必须将单词转换为机器学习算法可以利用的向量。如果目标是对文本数据进行机器学习建模,例如电影评论或推文或其他任何内容,则需要将文本数据转换为数字。此过程称为“嵌入”或“向量化”。
进行向量化时,请务必记住,它不仅仅是将单个单词变成单个数字。单词可以转换为数字,整个文档就可以转换为向量。向量的维度往往不止一个,而且对于文本数据,向量通常是高维的。这是因为特征数据的每个维度将对应一个单词,而我们所处理的文档通常包含数千个单词。
2.2 TF-IDF
在信息检索中,tf–idf 或 TFIDF(term frequency–inverse document frequency)是一种数值统计,旨在反映单词对语料库中文档的重要性。在信息检索,文本挖掘和用户建模的搜索中,它通常用作加权因子。 tf-idf 值与单词在文档中出现的次数成正比,同时被单词在语料库中的出现频率所抵消,这有助于调整某些单词通常会更频繁出现的事实。 如今,tf-idf是最流行的术语加权方案之一。在数字图书馆领域,有83%的基于文本的推荐系统使用tf-idf。
搜索引擎经常使用tf–idf加权方案的变体作为在给定用户查询时对文档相关性进行评分和排名的主要工具。tf–idf可成功用于各种领域的停用词过滤,包括文本摘要和分类。
排名函数中最简单的是通过将每个查询词的tf–idf相加得出,许多更复杂的排名函数是此简单模型的变体
> TF-IDF(Term Frequency-inverse Document Frequency)是一种针对关键词的统计分析方法,用于评估一个词对一个文件集或者一个语料库的重要程度。一个词的重要程度跟它在文章中出现的次数成正比,跟它在语料库出现的次数成反比。
> 一个词在语料库中的 TF-IDF = TF * IDF
>> TF = 词频 / 当前文档中所有词的数量
IDF = log{ 所有文档的总数 / (包含该词有多少个文档数量+1)}
举个例子: 数据集中一共有三个文档: 【我 可 太喜欢 姚明 了】,【谁 的 老婆 是 叶莉 啊】,【叶莉 有 一个 女儿 和 一个 儿子】
【我 可 太喜欢 姚明 了】 中的 “姚明” TF-IDF = (1/5) * log( 3/(1+1) )
punc = ['.', ',', '"', "'", '?', '!', ':', ';', '(', ')', '[', ']', '{', '}',"%"]
# 取并集,去除停用词(词频高但无意义)可以减少计算量
stop_words = text.ENGLISH_STOP_WORDS.union(punc)
desc = data['headline_text'].values
############ 第二题 ~ 1行 ############
############ your code start ############
# TfidfVectorizer 使用方法详见:http://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfVectorizer.html
vectorizer = TfidfVectorizer(stop_words=stop_words) # 实例化
############ your code end ############
X = vectorizer.fit_transform(desc) # 拟合并向量化文本数据
word_features = vectorizer.get_feature_names() # 拿出特征(Tf-idf靠前的多少个词),每个词就是一个特征
print(len(word_features))
print(word_features[5000:5100])
=========================================================
Output:
9820
['later', 'latest', 'latham', 'latif', 'latrobe', 'laughing', 'launceston', 'launch', 'launched', 'launches', 'laundering', 'laureates', 'laureus', 'lavender', 'laverton', 'law', 'lawful', 'lawnmowers', 'laws', 'lawyer', 'lawyers', 'lax', 'lay', 'lazaridis', 'lazio', 'lazios', 'lead', 'leader', 'leaderboard', 'leaders', 'leadership', 'leading', 'leads', 'league', 'leagues', 'leak', 'leaks', 'leaney', 'leap', 'learn', 'learner', 'learning', 'learns', 'lease', 'leases', 'leave', 'leaves', 'leaving', 'lebouc', 'lecturer', 'led', 'lee', 'leeds', 'lees', 'left', 'leg', 'legal', 'legality', 'legend', 'legionella', 'legislation', 'legislative', 'legitimacy', 'lehmann', 'leicester', 'leisel', 'leisure', 'lemon', 'lend', 'length', 'leniency', 'lennox', 'lens', 'lent', 'lenton', 'leonard', 'leone', 'leopards', 'lesbian', 'lessen', 'lester', 'let', 'lethal', 'letter', 'letters', 'letting', 'levee', 'level', 'levels', 'leven', 'leverkusen', 'levy', 'lewdness', 'lewis', 'lewiss', 'lga', 'liability', 'liable', 'liaison', 'lib']
2.3 Stemming
stemming 是将单词还原为词干(即词根形式)的过程。 词根形式不一定是单词本身,而是可以通过连接正确的后缀来生成单词。 例如,“fish”,“fishes”和“fishing”这几个词的词干都是“fish”,这是一个正确的单词。 另一方面,“study”,“studies”和“studying”一词源于“studi”,这不是一个正确的英语单词。
对英文单词才需要用Stemming来还原词干
2.4 Tokenizing
Tokenization 将句子分解为单词和标点符号
中文对应的分词工具典型的有jieba,用法如下
# jieba分词 import jieba chinese = '南京市长江大桥' chi = =jieba.cut(chinese) li = [i for i in chi] # 列表推导式:变量 = 【变量或变量的处理结果 for i in 容器类型数据】 li
========================================================= Output: ['南京市', '长江大桥']
stemmer = SnowballStemmer('english') # SnowballStemmer 使用方法详见: https://www.kite.com/python/docs/nltk.SnowballStemmer
tokenizer = RegexpTokenizer(r'[a-zA-Z\']+') # RegexpTokenizer 使用方法详见: https://www.kite.com/python/docs/nltk.RegexpTokenizer
def tokenize(text):
"""先进行 stemming 然后 tokenize
params:
text: 一个句子
return:
tokens 列表
"""
############ 第三题 ~ 1行 (使用列表推导) ############
############ your code start ############
return [stemmer.stem(word) for word in tokenizer.tokenize(text.lower())]
############ your code end ############
2.5 使用停用词、stemming 和自定义的 tokenizing 进行 TFIDF 向量化
vectorizer2 = TfidfVectorizer(stop_words = stop_words, tokenizer = tokenize)
X2 = vectorizer2.fit_transform(desc)
word_features2 = vectorizer2.get_feature_names()
print(len(word_features2))
print(word_features2[:50])
=========================================================
Output:
6909
['aa', 'aac', 'ab', 'aba', 'abalon', 'abandon', 'abar', 'abattoir', 'abbott', 'abc', 'abdic', 'abduct', 'abid', 'abigroup', 'abil', 'abl', 'ablaz', 'aboard', 'abolit', 'aborigin', 'abort', 'abov', 'abreast', 'abroad', 'absenc', 'absent', 'abu', 'abund', 'abus', 'abysm', 'ac', 'aca', 'academ', 'academi', 'acapulco', 'acb', 'accc', 'accent', 'accept', 'access', 'accid', 'accident', 'accommod', 'account', 'accredit', 'accus', 'ace', 'aceh', 'achiev', 'acid']
vectorizer3 = TfidfVectorizer(stop_words = stop_words, tokenizer = tokenize, max_features = 1000)
X3 = vectorizer3.fit_transform(desc)
words = vectorizer3.get_feature_names()
print(len(words))
print(words[:50])
=========================================================
Output:
1000
['abattoir', 'aborigin', 'abus', 'accc', 'accept', 'access', 'accid', 'accus', 'act', 'action', 'ad', 'address', 'adelaid', 'adf', 'admit', 'advanc', 'affect', 'afl', 'africa', 'age', 'agre', 'agreement', 'ahead', 'aid', 'aim', 'air', 'airlin', 'airport', 'ajax', 'al', 'alert', 'alic', 'aliv', 'alleg', 'allow', 'alp', 'ama', 'ambassador', 'ambul', 'american', 'amid', 'amp', 'announc', 'anoth', 'answer', 'anti', 'apologis', 'appeal', 'appoint', 'approv']
3 K-Means聚类
3.1 使用手肘法选择聚类簇的数量
随着聚类数k的增大,样本划分会更加的精细,每个簇的聚合程度会逐渐提高,那么误差平方和SSE自然会逐渐变小,并且当k小于真实的簇类数时,由于k的增大会大幅增加每个簇的聚合程度,因此SSE的下降幅度会很大,而当k到达真实聚类数时,再增加k所得到的聚合程度回报会迅速变小,所以SSE的下降幅度会骤减,然后随着k值的继续增大而趋于平缓,也就是说SSE和k的关系类似于手肘的形状,而这个肘部对应的k值就是数据的真实聚类数。因此这种方法被称为手肘法。
S
S
E
=
∑
i
=
1
k
∑
p
∈
C
i
∣
p
−
m
i
∣
2
SSE=\sum_{i=1}^k\sum_{p\in C_i}|p-m_i|^2
SSE=i=1∑kp∈Ci∑∣p−mi∣2
SSE是聚类平方和,是所有样本的聚类误差,代表着聚类效果的好坏
Ci是第i个簇
p是Ci中的样本点
mi是ci的质心(Ci中所有样本的均值)
from sklearn.cluster import KMeans # 使用方法详见: http://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html
wcss = []
for i in range(1,11):
############ 第四题 ~ 1行 (初始化 KMeans) ############
############ your code start ############
kmeans = KMeans(n_clusters=i, init='k-means++', max_iter=300, n_init=10, random_state=0)
############ your code end ############
kmeans.fit(X3)
wcss.append(kmeans.inertia_)
plt.plot(range(1,11),wcss)
plt.title('The Elbow Method')
plt.xlabel('Number of clusters')
plt.ylabel('WCSS')
plt.savefig('elbow.png')
plt.show()
=========================================================
Output:
由于可能产生多个肘点,所以有时候不得不通过反复试验来选择合适数量的簇。下面展示不同数量簇的结果,以找出合适数量的簇。
如上图中的肘点就有3、5、6、8
3.2 Cluster等于3
kmeans = KMeans(n_clusters = 3, n_init = 20, n_jobs = 1) # n_init(number of iterations for clsutering) n_jobs(number of cpu cores to use)
kmeans.fit(X3)
# We look at 3 the clusters generated by k-means.
# argsort 使用方法详见: https://numpy.org/doc/stable/reference/generated/numpy.argsort.html
common_words = kmeans.cluster_centers_.argsort()[:,-1:-26:-1]
for num, centroid in enumerate(common_words):
print(str(num) + ' : ' + ', '.join(words[word] for word in centroid))
3.3 Cluster等于5
############ 第五题 ############
# 重复上面的做法,仅改变聚类簇的数量 (尝试将上述过程封装成一个函数,减少代码的重读性,方便传参调用)
kmeans = KMeans(n_clusters = 5, n_init = 20, n_jobs = 1) # n_init(number of iterations for clsutering) n_jobs(number of cpu cores to use)
kmeans.fit(X3)
# We look at 3 the clusters generated by k-means.
# argsort 使用方法详见: https://numpy.org/doc/stable/reference/generated/numpy.argsort.html
common_words = kmeans.cluster_centers_.argsort()[:,-1:-26:-1]
for num, centroid in enumerate(common_words):
print(str(num) + ' : ' + ', '.join(words[word] for word in centroid))
3.4 Cluster等于6
# 同上
kmeans = KMeans(n_clusters = 6, n_init = 20, n_jobs = 1) # n_init(number of iterations for clsutering) n_jobs(number of cpu cores to use)
kmeans.fit(X3)
# We look at 3 the clusters generated by k-means.
# argsort 使用方法详见: https://numpy.org/doc/stable/reference/generated/numpy.argsort.html
common_words = kmeans.cluster_centers_.argsort()[:,-1:-26:-1]
for num, centroid in enumerate(common_words):
print(str(num) + ' : ' + ', '.join(words[word] for word in centroid))
3.5 Cluster等于8
# 同上
kmeans = KMeans(n_clusters = 8, n_init = 20, n_jobs = 1) # n_init(number of iterations for clsutering) n_jobs(number of cpu cores to use)
kmeans.fit(X3)
# We look at 3 the clusters generated by k-means.
# argsort 使用方法详见: https://numpy.org/doc/stable/reference/generated/numpy.argsort.html
common_words = kmeans.cluster_centers_.argsort()[:,-1:-26:-1]
for num, centroid in enumerate(common_words):
print(str(num) + ' : ' + ', '.join(words[word] for word in centroid))
ns.cluster_centers_.argsort()[:,-1:-26:-1]
for num, centroid in enumerate(common_words):
print(str(num) + ’ : ’ + ', '.join(words[word] for word in centroid))
##### 3.4 Cluster等于6
```python
# 同上
kmeans = KMeans(n_clusters = 6, n_init = 20, n_jobs = 1) # n_init(number of iterations for clsutering) n_jobs(number of cpu cores to use)
kmeans.fit(X3)
# We look at 3 the clusters generated by k-means.
# argsort 使用方法详见: https://numpy.org/doc/stable/reference/generated/numpy.argsort.html
common_words = kmeans.cluster_centers_.argsort()[:,-1:-26:-1]
for num, centroid in enumerate(common_words):
print(str(num) + ' : ' + ', '.join(words[word] for word in centroid))
3.5 Cluster等于8
# 同上
kmeans = KMeans(n_clusters = 8, n_init = 20, n_jobs = 1) # n_init(number of iterations for clsutering) n_jobs(number of cpu cores to use)
kmeans.fit(X3)
# We look at 3 the clusters generated by k-means.
# argsort 使用方法详见: https://numpy.org/doc/stable/reference/generated/numpy.argsort.html
common_words = kmeans.cluster_centers_.argsort()[:,-1:-26:-1]
for num, centroid in enumerate(common_words):
print(str(num) + ' : ' + ', '.join(words[word] for word in centroid))