数据挖掘(九)
 
 
 
 
文章目录
 
  
 
 
 
获取新闻文章
 
 
使用Web API获取数据
 
- 使用Web API采集数据,我们需要注意授权方法、请求频率限制和API端点。
 
 
- 授权方法时数据提供方用来管理数据采集方的。数据提供方以此来了解谁正在采集数据,确保采集方抓取数据的频率没有超出上限,同时对采集方都采集了哪些数据可以做到监视。对于多数网站来说,普通个人账号就能用来采集数据,但也有部分网站要求采集方使用正式的开发者账号。
 - 采集频率限制规定了采集方在约定时间内的最大请求次数,特别是对于免费提供的服务。在使用数据获取接口时,一定要了解不同的网站可能有着不同的规定。即使同一个网站,不同的API调用也有不同的采集频率限制。
 - API端点是指用来抽取信息的实际网址。不同网站提供不同的接口,大部分Web API提供Restful接口。
 
 
从任意网站抽取文本
 
 
寻找任意网站网页中的主要内容
 
- 首先访问每个链接,下载各个网页,把它们保存到数据文件夹中事先建好的用于存放原始网页的文件夹raw,然后我们从这些原始网页中获取有用的信息。我们使用MD5散列算法为每篇报道创建一个唯一的文件名。散列函数将输入转换为一个看似随机产生的字符串。对于相同的输入,散列函数返回相同的结果,并且散列函数是单向函数,根据散列值无法得到原来的值。
 
 
import os
import hashlib
import requests
stories = [['“旅行者”来做客,“美食”多样!唯美生态画卷颜值不断被“刷新”', 'http://baijiahao.baidu.com/s?id=1815151290164989110', '12'], 
           ['网友称镇卫生院有人“吃空饷”,相关人员应被追责;珠海金湾区卫健局:重复领取', 'http://baijiahao.baidu.com/s?id=1815068325137550619', '14'],
           ['泼天流量说来就来,这次大学生“夜袭开封”', 'http://baijiahao.baidu.com/s?id=1815025933758571293', '18']]
data_folder = os.path.join(os.getcwd(), 'data_mining', 'news', 'raws')
number_errors = 0
for title, url, score in stories:
    
    output_filename = hashlib.md5(url.encode()).hexdigest()
    fullpath = os.path.join(data_folder, output_filename + '.txt')
    try: 
	
        response = requests.get(url)
        data = response.text
        with open(fullpath, 'w') as outf:
            outf.write(data)
    except Exception as e:
	number_errors += 1
	
	print(e)
 
- 获得原始网页后,我们需要找出每个网页中的新闻报道内容,有些在线资源使用数据挖掘方法来解决这个问题。从网页中抽取文本代码可以使用lxml包解析HTML文件,lxml的HTML解析器容错能力强,可以处理不规范的HTML代码。文本抽取首先遍历HTML文件的每个节点,抽取其中的文本内容;其次跳过JS、样式和注释节点,这些系欸但不太可能包含对我有有价值的信息。最后确保文本内容长度至少为100字符。文本抽取函数在任何子节点上调用自己来抽取子节点中的文本内容,最后返回拼接在一起的所有子节点的文本。如果一个节点没有任何子节点,文本抽取函数返回该节点的文本内容,如果该节点不包含任何内容,就返回空字符串。
 
 
filenames = [os.path.join(data_folder, filename) for filename in os.listdir(data_folder)]
from lxml import etree, html
skip_node_types = ['script', 'head', 'style', etree.Comment]
def get_text_from_file(filename):
    with open(filename) as inf:
        html_tree = html.parse(inf)
    
    return get_text_from_node(html_tree.getroot())
def get_text_from_node(node):
    if len(node) == 0:
        if node.text and len(node.text) > 100:
            return node.text
        else:
            return ''
    results = (get_text_from_node(child) for child in node if child.tag not in skip_node_types)
    
    return '\n'.join(r for r in results if len(r) > 1)
for filename in os.listdir(data_folder):
    text = get_text_from_file(os.path.join(data_folder, filename))
    with open(os.path.join(data_folder, filename), 'w') as outf:
        outf.write(text)
 
示例代码1
 
import os
import hashlib
import requests
from lxml import etree, html
stories = [['“旅行者”来做客,“美食”多样!唯美生态画卷颜值不断被“刷新”', 'http://baijiahao.baidu.com/s?id=1815151290164989110', '12'], 
           ['网友称镇卫生院有人“吃空饷”,相关人员应被追责;珠海金湾区卫健局:重复领取', 'http://baijiahao.baidu.com/s?id=1815068325137550619', '14'],
           ['泼天流量说来就来,这次大学生“夜袭开封”', 'http://baijiahao.baidu.com/s?id=1815025933758571293', '18']]
data_folder = os.path.join(os.getcwd(), 'data_mining', 'news', 'raws')
number_errors = 0
for title, url, score in stories:
    print(title, url, score)
    output_filename = hashlib.md5(url.encode()).hexdigest()
    fullpath = os.path.join(data_folder, output_filename + '.txt')
    try: 
        response = requests.get(url)
        response.encoding = 'utf-8'
        data = response.text  
        with open(fullpath, 'w') as outf:
            outf.write(data)
    except Exception as e:
        number_errors += 1
        print(e)
documents = [open(os.path.join(data_folder, filename), encoding='utf-8').read() for filename in os.listdir(data_folder)]    
skip_node_types = ['script', 'head', 'style', etree.Comment]
def get_text_from_file(filename):
    with open(filename, encoding='utf-8') as inf:
        html_tree = html.parse(inf)
    return get_text_from_node(html_tree.getroot())
def get_text_from_node(node):
    if len(node) == 0:
        if node.text and len(node.text) > 100:
            return node.text
        else:
            return ''
    results = (get_text_from_node(child) for child in node if child.tag not in skip_node_types)
    return '\n'.join(r for r in results if len(r) > 1)
for filename in os.listdir(data_folder):
    text = get_text_from_file(os.path.join(data_folder, filename))
    with open(os.path.join(data_folder, filename), 'w', encoding='utf-8') as outf:
        outf.write(text)
 
新闻语料聚类
 
 
k-means算法
 
 
- k-means算法分为两步,一是为每个数据点分配簇标签,二是更新各簇的质心点。一个数据点对应数据集中一条数据,把数据集看成样本,一条数据可以看作是一个个体。我们在分簇时为数据集中的个体设置一个标签,把它和最近的质心点联系起来。对于距离质心点1最近的个体,我们为它们分配标签1,以此类推。标签相同的个体属于同一个簇。更新环节计算各簇内所有数据点的均值,更新质心点。k-means算法会重复上述步骤,每次更新质心点时,所有质心点将会小范围移动。这个过程会执行知道条件不再满足位置。
 - k-means算法只有一个参数,在很多数据挖掘问题上效果很好,所以仍然被频繁使用。scikit-learn实现了k-means算法。创建数据分析流水线,第一步是特征抽取,第二步是调用k-means算法。
 
 
from sklearn.cluster import KMeans
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.pipeline import Pipeline
from collections import Counter
documents = [open(os.path.join(data_folder, filename), encoding='utf-8').read() for filename in os.listdir(data_folder)]   
n_clusters = 2
pipeline = Pipeline([('feature_extraction', TfidfVectorizer(max_df=0.4)), ('clusterer', KMeans(n_clusters))])
pipeline.fit(documents)
labels = pipeline.predict(documents)
c = Counter(labels)
for cluster_number in range(n_clusters):
    print('Cluster {} contains {} samples'.format(cluster_number, c[cluster_number]))
 
评估结果
 
- 聚类分析主要是探索性分析,因此很难有效地评估聚类算法结果的好坏。评估算法结果最直接的方式是根据它要学习的标准对其进行评价。对于k均值算法,寻找新质心点的标准是最小化每个数据点到最近质心点的距离,这叫做算法的惯性权重。
 
 
pipeline.named_steps['clusterer'].inertia_
inertia_scores = []
n_cluster_values = list(range(2, 20))
for n_clusters in n_cluster_values:
    cur_inertia_scores = []
    X = TfidfVectorizer(max_df=0.4).fit_transform(documents)
    for i in range(40):
        km = KMeans(n_clusters).fit(X)
        cur_inertia_scores.append(km.inertia_)
    inertia_scores.append(cur_inertia_scores)
from matplotlib import pyplot as plt
inertia_means = np.mean(inertia_scores, axis=1)
inertia_stderr = np.std(inertia_scores, axis=1)
fig = plt.figure(figsize=(40,20))
plt.errorbar(n_cluster_values, inertia_means, inertia_stderr, color='green')
plt.show()
 
从簇中抽取主题信息
 
- 我们尝试找到每个簇的主题,首先从特征提取这一步抽取词表。其次k均值算法可以用来简化特征。特征简化的方法有很多种,比如主要成分分析、潜在语义索引等,这些方法还能用来创建新特征。
 
 
terms = pipeline.named_steps['feature_extraction'].get_feature_names()
c = Counter(labels)
for cluster_number in range(n_clusters):
    print("Cluster {} contains {} samples".format(cluster_number, c[cluster_number]))
    print("  Most important terms")
    
    centroid = pipeline.named_steps['clusterer'].cluster_centers_[cluster_number]
    most_important = centroid.argsort()
    for i in range(5):
        term_index = most_important[-(i+1)]
        print("  {0}) {1} (score: {2:.4f})".format(i+1, terms[term_index], centroid[term_index]))
 
聚类融合
 
 
证据累积
 
- 基本的融合方法是对数据进行多次聚类,每次都记录各个数据点的簇标签,然后计算每两个数据点被分到同一个簇的次数。这就是证据累积算法的精髓。第一步是使用k-means等低水平的聚类算法对数据集进行多次聚类,记录每次跌倒两个数据点出现在同一簇的频率,将结果保存到共协矩阵中。第二步是是用另一种聚类算法,分级聚类对第一步得到的共协矩阵进行聚类分析。
 
 
from scipy.sparse import csr_matrix
def create_coassociation_matrix(labels):
    rows = []
    cols = []
    unique_labels = set(labels)
    for label in unique_labels:
        indices = np.where(labels == label)[0]
        for index1 in indices:
            for index2 in indices:
                rows.append(index1)
                cols.append(index2)
    data = np.ones((len(rows),))
    return csr_matrix((data, (rows, cols)), dtype='float')
C = create_coassociation_matrix(labels)
 
- 对共协矩阵进行分级聚类。我们需要找到该矩阵的最小生成树MST,删除权重低于阈值的边。
 
 
from scipy.sparse.csgraph import minimum_spanning_tree
mst = minimum_spanning_tree(C)
pipeline.fit(documents)
label2s = pipeline.predict(documents)
C2 = create_coassociation_matrix(labels2)
C_sum = (C + C2)/2
mst = minimum_spanning_tree(-C_sum)
mst.data[mst.data > -1] = 0
mst.eliminate_zeros()
from scipy.sparse.csgraph import connected_components
number_of_clusters, labels = connected_components(mst)
 
工作原理
 
- k-means算法不考虑特征的权重,假定所有的特征取值范围相同,寻找的是圆形簇。证据累积算法的工作原理是重新把特征映射到新空间,每次运行k-means算法相当于使用转换器对特征进行一次转换。证据累积算法只关心数据点之间的距离而不是它们在原来特征空间的位置。对于没有规范化过的特征,仍然存在问题。我们使用tf-idf规范特征值,从而使特征具有相同的值域。
 
 
from sklearn.base import BaseEstimator, ClusterMixin
class EAC(BaseEstimator, ClusterMixin):
    
    def __init__(self, n_clusterings=10, cut_threshold=0.5, n_clusters_range=(3, 10)):
        self.n_clusterings = n_clusterings
        self.cut_threshold = cut_threshold
        self.n_clusters_range = n_clusters_range
  
    def fit(self, X, y=None):
	
        C = sum((create_coassociation_matrix(self._single_clustering(X))
                 for i in range(self.n_clusterings)))
        mst = minimum_spanning_tree(-C)
        mst.data[mst.data > -self.cut_threshold] = 0
        mst.eliminate_zeros()
        self.n_components, self.labels_ = connected_components(mst)
        return self
  
    def _single_clustering(self, X):
        n_clusters = np.random.randint(*self.n_clusters_range)
        km = KMeans(n_clusters=n_clusters)
        return km.fit_predict(X)
  
    def fit_predict(self, X):
        self.fit(X)
        return self.labels_
 
线上学习
 
 
- scikit-learn提供了MiniBatchKBeans算法用来实现线上学习功能。
 
 
from sklearn.cluster import MiniBatchKMeans
vec = TfidfVectorizer(max_df=0.4)
X = vec.fit_transform(documents)
mbkm = MiniBatchKBeans(random_state=14, n_clusters=3)
batch_size = 10
for iteration in range(int(X.shape[0] / batch_size)):
    start = batch_size * iteration
    end = batch_size * (iteration + 1)
    mbkm.partial_fit(X[start:end])
labels_mbkm = mbkm.predict(X)
mbkm.inertia_