无监督学习是一种机器学习方法,其目标是在没有标记的数据中发现数据集的内在结构和模式。与监督学习不同,无监督学习不需要输入数据集的标记信息,而是通过对数据进行聚类、降维、关联规则挖掘等操作来发现数据的潜在结构和模式。
在无监督学习中,模型不会接收关于数据集的任何标签信息。相反,它会自行寻找数据集中的模式和结构,然后将数据划分为不同的组或聚类。这种方法非常有用,因为它可以在没有明确标签或分类的情况下,发现数据的隐藏结构,从而提供新的见解和发现。
无监督学习的主要应用包括数据降维、异常检测、数据聚类、关联规则挖掘等。例如,可以使用无监督学习来发现消费者购买行为中的模式,识别异常的信用卡交易,或者通过聚类分析来帮助企业发现市场细分和客户群体。
无监督学习算法主要包括以下几种:
聚类算法(Cluster Analysis):聚类算法是将数据集分成若干个互不相交的子集,每个子集被称为一个簇。常用的聚类算法有K-Means、层次聚类、DBSCAN等。
降维算法(Dimensionality Reduction):降维算法是将高维数据映射到低维空间的过程,以便更好地进行可视化或者加快计算速度。常用的降维算法有主成分分析(PCA)、独立成分分析(ICA)等。
关联规则挖掘算法(Association Rule Mining):关联规则挖掘算法是一种基于频繁项集的算法,通过挖掘数据集中项之间的关联关系,来发现有趣的规则。常用的关联规则挖掘算法有Apriori、FP-Growth等。
自组织映射算法(Self-Organizing Maps,SOM):自组织映射算法是一种基于神经网络的无监督学习算法,可以将高维数据映射到二维平面上,从而进行可视化。SOM算法常用于图像处理、文本分类等领域。
概率图模型算法(Probabilistic Graphical Model):概率图模型是一种描述变量间关系的图结构,在图中节点表示变量,边表示变量之间的关系。常用的概率图模型算法有朴素贝叶斯、隐马尔可夫模型(HMM)等。
独立成分分析算法(Independent Component Analysis,ICA):独立成分分析算法是一种将多个信号分解成独立成分的算法,常用于语音信号分离、脑电图信号分析等领域。
以上是常见的无监督学习算法,每个算法都有其独特的应用场景和优缺点。在实际应用中,需要根据具体问题的需求和数据特征,选择最适合的算法来解决问题。
对于聚类算法。
首先介绍KMeans算法
KMeans算法是一种常用的无监督学习算法,用于将数据集划分成k个不同的类别。KMeans算法的基本思想是:将数据集中的每个样本分配到距离其最近的k个质心所代表的类别中,然后重新计算每个类别的质心,不断重复以上过程,直到类别不再发生变化或达到预定的迭代次数为止。
KMeans算法的实现过程包括以下几个步骤:
随机选取k个样本作为初始质心;
计算每个样本与k个质心之间的距离,将每个样本分配到距离最近的质心所代表的类别中;
重新计算每个类别的质心,将其设置为该类别中所有样本的平均值;
不断重复以上过程,直到类别不再发生变化或达到预定的迭代次数为止。
KMeans算法的优点包括实现简单、计算速度快等,同时也具有对初始质心的敏感性、需要事先确定类别的数量k等缺点。在实际应用中,KMeans算法常用于图像分割、用户行为分析、市场细分等领域。
from sklearn.cluster import KMeans
import numpy as np
# 生成随机数据集
X = np.random.randn(100, 2)
# 定义K-Means算法模型
kmeans = KMeans(n_clusters=3)
# 训练模型并进行聚类
kmeans.fit(X)
# 获取聚类结果
labels = kmeans.labels_
# 输出聚类结果
print(labels)
以上代码中,首先使用numpy库生成了一个包含100个样本、2个特征的随机数据集X。然后,定义了一个KMeans对象,并将聚类数目设置为3。接下来,使用fit()方法训练模型,并使用labels_属性获取聚类结果。最后,输出聚类结果。
需要注意的是,K-Means算法对于初始聚类中心的选择比较敏感,因此在实际应用中,通常需要多次运行K-Means算法,并选择最优的聚类结果。可以使用sklearn库中的KMeans类的n_init参数来设置多次运行的次数,默认为10次。
层次聚类算法是一种基于树形结构进行聚类分析的无监督学习算法。它通过不断地将最近的样本或类别合并在一起,构建出一棵树形结构,从而实现对数据集的聚类。
层次聚类算法的基本思想是:将每个样本或类别看作一个单独的簇,然后将距离最近的两个簇合并成一个新的簇,不断重复以上过程,直到所有样本或类别被合并成一个簇或满足某个停止条件为止。这个过程可以用树形图或者树状图来表示,被称为“树状图聚类”。
层次聚类算法可以分为两种类型:凝聚型聚类和分裂型聚类。凝聚型聚类是从下往上合并簇,即将最近的两个样本或簇合并成一个新的簇;分裂型聚类是从上往下分裂簇,即将一个大的簇分裂成多个小的簇。
层次聚类算法具有可解释性强、无需事先确定聚类数量等优点,同时也具有计算复杂度高、对噪声和异常值敏感等缺点。在实际应用中,层次聚类算法常用于文本聚类、图像分割、生物信息学等领域。
层次聚类是一种无监督学习算法,可以对数据进行分层的聚类操作。下面是一个用Python实现的层次聚类算法:
import numpy as np
from scipy.cluster.hierarchy import linkage, dendrogram
# 生成测试数据
X = np.array([[5,3], [10,15], [15,12], [24,10], [30,30], [85,70], [71,80], [60,78], [70,55], [80,91]])
# 使用Ward方法进行层次聚类
Z = linkage(X, 'ward')
# 生成树状图
dendrogram(Z, leaf_rotation=90, leaf_font_size=8)
# 展示结果
import matplotlib.pyplot as plt
plt.show()
这个代码片段首先生成了一个测试数据集X,然后使用Scipy库中的linkage函数进行层次聚类操作。在这里,我们使用了Ward方法进行聚类,也可以使用其他方法,例如single、complete等等。最后,我们使用dendrogram函数生成一个树状图,并使用matplotlib库进行可视化展示。
DBSCAN算法是一种基于密度的聚类算法,它可以将具有高密度的样本聚成一类,并将较低密度的样本视为噪声或边界点。DBSCAN算法的全称是Density-Based Spatial Clustering of Applications with Noise。
DBSCAN算法的基本思想是:对于给定的数据集,如果一个点的密度达到给定的阈值(通常是一定半径内的点数),则认为它是一个核心点,将其作为一个簇的种子点。然后,将与该种子点密度可达的所有点都加入到该簇中,同时将其他核心点的密度可达点也加入到该簇中。最后,将剩余的点标记为噪声点或边界点,不属于任何簇。
DBSCAN算法具有对数据分布不敏感、能够发现任意形状的簇等优点,同时也具有对密度阈值和距离阈值的选择敏感、对高维数据的计算复杂度高等缺点。在实际应用中,DBSCAN算法常用于图像分割、异常检测、智能交通等领域。
DBSCAN(Density-Based Spatial Clustering of Applications with Noise)是一种基于密度的聚类算法,它可以自动识别数据集中的噪声点,并将非噪声点聚类成簇。下面是一个用Python实现的DBSCAN算法:
import numpy as np
from sklearn.neighbors import NearestNeighbors
def dbscan(X, eps, min_samples):
"""
X: 数据集,numpy数组,shape为(n_samples, n_features)
eps: 邻域半径
min_samples: 最小样本数
"""
# 初始化标签数组
labels = np.zeros(len(X))
# 初始化簇的数量
cluster_num = 0
# 计算数据集中每个点的邻域
neigh = NearestNeighbors(n_neighbors=min_samples)
neigh.fit(X)
distances, indices = neigh.kneighbors(X)
# 开始聚类
for i in range(len(X)):
if labels[i] != 0:
continue
# 找到当前点的邻域
neighbor_indices = indices[i][distances[i] <= eps]
# 如果当前点的邻域中的点数小于min_samples,则将当前点标记为噪声点
if len(neighbor_indices) < min_samples:
labels[i] = -1
else:
# 找到当前点的邻域中的所有密度可达的点,将它们放入同一个簇中
cluster_num += 1
labels[i] = cluster_num
for j in neighbor_indices:
if labels[j] == -1:
labels[j] = cluster_num
elif labels[j] == 0:
labels[j] = cluster_num
sub_neighbor_indices = indices[j][distances[j] <= eps]
if len(sub_neighbor_indices) >= min_samples:
neighbor_indices = np.concatenate((neighbor_indices, sub_neighbor_indices))
return labels
这个代码片段定义了一个名为dbscan的函数,它接受三个参数:数据集X、邻域半径eps和最小样本数min_samples。函数首先初始化标签数组和簇的数量,然后使用sklearn库中的NearestNeighbors函数计算数据集中每个点的邻域。接下来,函数开始聚类操作,对于每个未被标记的点,找到其邻域中的所有密度可达的点,将它们放入同一个簇中,并将簇的数量加1。如果当前点的邻域中的点数小于min_samples,则将当前点标记为噪声点。最后,函数返回标签数组,其中每个元素的值表示该点所属的簇的编号,如果该点被标记为噪声点,则值为-1。
下边介绍降维算法
PCA(Principal Component Analysis)算法是一种常见的数据降维算法,主要用于高维数据的分析和可视化。其核心思想是将高维数据转化为低维数据,同时尽可能地保留原始数据的信息。
具体而言,PCA算法将原始数据通过线性变换映射到一个新的坐标系中,使得数据在新的坐标系下具有最大的方差,即尽可能分散在新坐标系的各个方向上。这些新的坐标轴被称为主成分,其数量通常少于原始数据的维度。PCA算法的步骤包括:计算数据的协方差矩阵、求解协方差矩阵的特征值和特征向量、选取前k个最大的特征值对应的特征向量作为主成分,最后将数据映射到主成分上。
PCA算法可以用于数据压缩、数据可视化、降噪、特征提取等领域。在机器学习中,PCA算法可以作为预处理步骤,用于减少特征的数量和相关性,从而提高模型的精度和泛化能力。
PCA(Principal Component Analysis)是一种常用的降维算法,可以将高维数据转换为低维数据,同时保留数据的主要特征。下面是一个用Python实现的PCA算法:
import numpy as np
def pca(X, n_components):
"""
X: 数据集,numpy数组,shape为(n_samples, n_features)
n_components: 要保留的主成分数量
"""
# 中心化数据
X_mean = np.mean(X, axis=0)
X_centered = X - X_mean
# 计算协方差矩阵
cov_matrix = np.cov(X_centered, rowvar=False)
# 计算特征值和特征向量
eigenvalues, eigenvectors = np.linalg.eigh(cov_matrix)
# 将特征向量按照对应的特征值从大到小排序
idx = np.argsort(eigenvalues)[::-1]
eigenvectors = eigenvectors[:, idx]
# 选择前n_components个特征向量组成投影矩阵
projection_matrix = eigenvectors[:, :n_components]
# 对数据进行降维
X_pca = np.dot(X_centered, projection_matrix)
return X_pca
这个代码片段定义了一个名为pca的函数,它接受两个参数:数据集X和要保留的主成分数量n_components。函数首先中心化数据,然后计算协方差矩阵。接下来,函数计算协方差矩阵的特征值和特征向量,并将特征向量按照对应的特征值从大到小排序。函数选择前n_components个特征向量组成投影矩阵,并使用该投影矩阵对数据进行降维操作。最后,函数返回降维后的数据集X_pca。
ICA(Independent Component Analysis)算法是一种用于数据分离和特征提取的算法,它可以将混合在一起的信号分离成独立的成分信号。
ICA算法的核心思想是,假设观测到的信号是由若干个独立的成分信号线性组合而成,然后通过对混合矩阵进行逆变换,将原始信号分离出来。ICA算法的实现过程通常包括以下步骤:
对原始信号进行中心化处理,使其均值为0;
构造一个混合矩阵,将原始信号进行线性混合;
对混合矩阵进行逆变换,将混合信号分离出来;
对分离出来的信号进行重构,得到分离后的原始信号。
ICA算法的应用领域非常广泛,包括语音信号处理、图像分析、生物医学信号处理等。在语音信号处理领域,ICA算法可以用于语音信号的分离和降噪,提高语音识别的准确性;在图像处理领域,ICA算法可以用于图像特征提取和图像分割等任务。
ICA(Independent Component Analysis)是一种常用的盲源分离算法,可以从混合信号中恢复出独立的原始信号。下面是一个用Python实现的ICA算法:
import numpy as np
def ica(X, n_components, max_iter=200, tol=1e-4):
"""
X: 数据集,numpy数组,shape为(n_samples, n_features)
n_components: 要恢复的原始信号数量
max_iter: 最大迭代次数
tol: 收敛阈值
"""
# 中心化数据
X_mean = np.mean(X, axis=0)
X_centered = X - X_mean
# 初始化权重矩阵
W = np.random.rand(X.shape[1], n_components)
# 进行独立成分的估计
for i in range(max_iter):
# 计算梯度
Y = np.dot(X_centered, W)
g = np.tanh(Y)
g_prime = 1 - g ** 2
delta_W = np.dot(X_centered.T, g) / X.shape[0] - np.dot(g_prime.T, W)
# 更新权重矩阵
W += delta_W
# 检查收敛
if np.all(np.abs(delta_W) < tol):
break
# 得到恢复的原始信号
S = np.dot(X_centered, W)
return S
这个代码片段定义了一个名为ica的函数,它接受三个参数:数据集X、要恢复的原始信号数量n_components以及可选的max_iter和tol参数。函数首先中心化数据,然后初始化权重矩阵。接下来,函数进行独立成分的估计,使用随机初始化的权重矩阵进行迭代,计算梯度并更新权重矩阵,直到满足收敛条件。最后,函数得到恢复的原始信号S,并返回它。
关联规则挖掘算法
Apriori算法是一种挖掘频繁项集的算法,它可以从一个事务数据库中发现频繁出现的项集。该算法的基本思想是利用频繁项集的性质,即如果一个项集是频繁的,则它的所有子集也必须是频繁的。Apriori算法采用了一种迭代的方法,每次迭代都产生一些候选项集,并计算它们的支持度,然后根据最小支持度过滤掉不满足要求的候选项集,最终得到频繁项集。
Apriori算法的实现过程通常包括以下几个步骤:
扫描整个事务数据库,统计每个项集的支持度,得到1-项集的集合L1。
根据L1生成2-项集的候选集C2,计算其支持度,筛选出满足最小支持度要求的项集,得到2-项集的集合L2。
根据L2生成3-项集的候选集C3,计算其支持度,筛选出满足最小支持度要求的项集,得到3-项集的集合L3。
重复上述步骤,直到不能再生成满足要求的项集为止。
Apriori算法的优点是简单易实现,可以处理大规模数据集。其缺点是计算频繁项集的代价较高,而且可能会产生大量的候选项集。近年来,一些改进算法,如FP-growth算法、Eclat算法等也被提出来,用于提高频繁项集挖掘的效率。
Apriori算法是一种挖掘频繁项集的算法,它可以从一个事务数据库中发现频繁出现的项集。下面是一个用Python实现的Apriori算法:
def apriori(transactions, min_support):
"""
transactions: 事务数据库,列表的列表,每个列表表示一条事务
min_support: 最小支持度
"""
# 计算项集的支持度
def get_support(itemset):
count = 0
for transaction in transactions:
if set(itemset).issubset(set(transaction)):
count += 1
support = count / len(transactions)
return support
# 生成下一个候选项集
def generate_next_itemsets(itemsets, k):
next_itemsets = []
for i in range(len(itemsets)):
for j in range(i + 1, len(itemsets)):
itemset1 = itemsets[i]
itemset2 = itemsets[j]
if itemset1[:k-2] == itemset2[:k-2]:
next_itemset = itemset1 + [itemset2[-1]]
next_itemsets.append(next_itemset)
return next_itemsets
# 初始化候选项集
itemsets = []
for transaction in transactions:
for item in transaction:
if not [item] in itemsets:
itemsets.append([item])
itemsets.sort()
# 寻找频繁项集
k = 2
freq_itemsets = []
while True:
candidate_itemsets = generate_next_itemsets(itemsets, k)
freq_itemset = []
for itemset in candidate_itemsets:
support = get_support(itemset)
if support >= min_support:
freq_itemset.append(itemset)
if len(freq_itemset) == 0:
break
freq_itemsets += freq_itemset
itemsets = freq_itemset
k += 1
return freq_itemsets
这个代码片段定义了一个名为apriori的函数,它接受两个参数:事务数据库transactions和最小支持度min_support。函数首先定义了一个内部函数get_support,用于计算项集的支持度。接下来,函数定义了另一个内部函数generate_next_itemsets,用于生成下一个候选项集。函数初始化候选项集,然后使用generate_next_itemsets和get_support函数寻找频繁项集。最后,函数返回所有的频繁项集。
FP-Growth算法是一种用于发现频繁项集的数据挖掘算法。它通过构建FP树(Frequent Pattern Tree)来高效地发现频繁项集,并避免了传统Apriori算法中需要扫描数据集多次的缺点。
FP-Growth算法的主要步骤包括:
构建FP树:遍历数据集,统计每个项的出现次数,然后根据项出现次数构建FP树。
构建条件模式基:对于每个项,构建其条件模式基(即包含该项的所有前缀路径)。
递归挖掘FP树:从FP树的叶节点开始向上遍历,构建前缀路径,然后对每个前缀路径构建条件模式基,递归地挖掘FP树。
合并频繁项集:将每个项与其条件模式基中的项合并,得到频繁项集。
相比于传统的Apriori算法,FP-Growth算法的优势在于只需要扫描数据集两次,避免了多次扫描的开销,因此在处理大规模数据集时效率更高。
FP-Growth算法的应用领域包括购物篮分析、推荐系统、网络流量分析等。例如,在购物篮分析中,可以通过发现频繁项集来了解消费者的购买习惯,从而对商品进行推荐和促销。
以下是使用Python实现FP-Growth算法的示例代码,代码中使用了一个示例数据集:
class TreeNode:
def __init__(self, name_value, num_occur, parent_node):
self.name = name_value
self.count = num_occur
self.node_link = None
self.parent = parent_node
self.children = {}
def inc(self, num_occur):
self.count += num_occur
def display(self, ind=1):
print(' ' * ind, self.name, ' ', self.count)
for child in self.children.values():
child.display(ind + 1)
def create_tree(data_set, min_sup=1):
header_table = {}
for trans in data_set:
for item in trans:
header_table[item] = header_table.get(item, 0) + data_set[trans]
for k in list(header_table.keys()):
if header_table[k] < min_sup:
del (header_table[k])
freq_item_set = set(header_table.keys())
if len(freq_item_set) == 0:
return None, None
for k in header_table:
header_table[k] = [header_table[k], None]
ret_tree = TreeNode('Null Set', 1, None)
for tran_set, count in data_set.items():
local_d = {}
for item in tran_set:
if item in freq_item_set:
local_d[item] = header_table[item][0]
if len(local_d) > 0:
ordered_items = [v[0] for v in sorted(local_d.items(), key=lambda p: p[1], reverse=True)]
update_tree(ordered_items, ret_tree, header_table, count)
return ret_tree, header_table
def update_tree(items, in_tree, header_table, count):
if items[0] in in_tree.children:
in_tree.children[items[0]].inc(count)
else:
in_tree.children[items[0]] = TreeNode(items[0], count, in_tree)
if header_table[items[0]][1] is None:
header_table[items[0]][1] = in_tree.children[items[0]]
else:
update_header(header_table[items[0]][1], in_tree.children[items[0]])
if len(items) > 1:
update_tree(items[1::], in_tree.children[items[0]], header_table, count)
def update_header(node_to_test, target_node):
while node_to_test.node_link is not None:
node_to_test = node_to_test.node_link
node_to_test.node_link = target_node
def ascend_tree(leaf_node, prefix_path):
if leaf_node.parent is not None:
prefix_path.append(leaf_node.name)
ascend_tree(leaf_node.parent, prefix_path)
def find_prefix_path(base_pat, tree_node):
cond_pats = {}
while tree_node is not None:
prefix_path = []
ascend_tree(tree_node, prefix_path)
if len(prefix_path) > 1:
cond_pats[frozenset(prefix_path[1:])] = tree_node.count
tree_node = tree_node.node_link
return cond_pats
def mine_tree(in_tree, header_table, min_sup, pre_fix, freq_item_list):
big_l = [v[0] for v in sorted(header_table.items(), key=lambda p: p[1][0])]
for base_pat in big_l:
new_freq_set = pre_fix.copy()
new_freq_set.add(base_pat)
freq_item_list.append(new_freq_set)
cond_patt_bases = find_prefix_path(base_pat, header_table[base_pat][1])
my_cond_tree, my_head = create_tree(cond_patt_bases, min_sup)
if my_head is not None:
mine_tree(my_cond_tree, my_head, min_sup, new_freq_set, freq_item_list)
def load_data():
return [['r', 'z', 'h', 'j', 'p'],
['z', 'y', 'x', 'w', 'v', 'u', 't', 's'],
['z'],
['r', 'x', 'n', 'o', 's'],
['y', 'r', 'x', 'z', 'q', 't', 'p'],
['y', 'z', 'x', 'e', 'q', 's', 't', 'm']]
if __name__ == '__main__':
data = load_data()
data_set = {}
for trans in data:
data_set[frozenset(trans)] = 1
my_tree, my_head_table = create_tree(data_set, 3)
freq_items = []
mine_tree(my_tree, my_head_table, 3, set([]), freq_items)
print(freq_items)
在示例代码中,我们首先定义了TreeNode
类,用于表示FP树的节点。然后实现了create_tree
函数,用于构建FP树。在构建FP树时,我们先遍历数据集,统计每个项的出现次数,然后根据项出现次数构建FP树。构建FP树时,需要同时维护一个头指针表,用于记录每个项在FP树中的第一个出现位置。
接着,我们实现了find_prefix_path
函数,用于查找给定项的条件模式基。在查找条件模式基时,需要从给定项的头指针开始向上遍历FP树,构建前缀路径。最后,我们实现了mine_tree
函数,用于递归地挖掘FP树,得到频繁项集。
最后,在示例代码中我们使用了一个示例数据集进行测试,并打印出了频繁项集。需要注意的是,示例数据集中的每个项都是单个字符,实际应用中可能需要根据具体情况进行处理。
自组织映射算法
自组织映射算法(Self-Organizing Map,SOM)是一种用于数据聚类和可视化的无监督学习算法。它通过将高维数据映射到低维空间中,保持数据的拓扑结构,从而实现了对高维数据的可视化和分析。
SOM算法的核心思想是,将输入数据映射到一个二维(或三维)网格上,使得相似的数据映射到相邻的节点上。在映射的过程中,SOM算法会不断调整各个节点的权值向量,使其逐渐逼近输入数据。具体而言,SOM算法的实现过程包括以下步骤:
初始化权值向量:将每个节点的权值向量随机初始化为一个较小的值。
选择获胜节点:对于每个输入向量,计算其与各个节点的距离,选择距离最小的节点作为获胜节点。
更新权值向量:根据获胜节点的位置和邻居节点的位置,更新它们的权值向量,使其逐渐逼近输入向量。
调整学习率和邻域半径:随着迭代次数的增加,逐渐减小学习率和邻域半径,使权值向量的调整逐渐趋于稳定。
SOM算法可以用于数据聚类、可视化、特征提取等领域。在聚类方面,SOM算法可以将相似的数据映射到相邻的节点上,从而实现数据的聚类。在可视化方面,SOM算法可以将高维数据映射到二维空间中,用颜色或形状表示数据的不同特征,从而方便用户对数据进行可视化分析。
以下是使用Python实现自组织映射算法的示例代码,代码中使用了一个示例数据集:
import numpy as np
class SOM:
def __init__(self, input_dim, output_dim, learning_rate=0.1, sigma=None):
self.input_dim = input_dim
self.output_dim = output_dim
self.learning_rate = learning_rate
if sigma is None:
sigma = max(output_dim) / 2.0
self.sigma = sigma
self.weights = np.random.rand(output_dim[0], output_dim[1], input_dim)
def train(self, data, num_epochs):
for epoch in range(num_epochs):
for i, x in enumerate(data):
bmu = self.find_bmu(x)
self.update_weights(x, bmu, epoch)
def find_bmu(self, x):
min_dist = np.inf
bmu = None
for i in range(self.output_dim[0]):
for j in range(self.output_dim[1]):
w = self.weights[i, j, :]
dist = np.linalg.norm(x - w)
if dist < min_dist:
min_dist = dist
bmu = (i, j)
return bmu
def update_weights(self, x, bmu, epoch):
for i in range(self.output_dim[0]):
for j in range(self.output_dim[1]):
w = self.weights[i, j, :]
dist = np.linalg.norm(np.array(bmu) - np.array([i, j]))
lr = self.learning_rate * (1.0 - float(epoch) / num_epochs)
sigma = self.sigma * (1.0 - float(epoch) / num_epochs)
h = np.exp(-dist**2 / (2 * sigma**2))
self.weights[i, j, :] += lr * h * (x - w)
if __name__ == '__main__':
data = np.random.rand(100, 2)
som = SOM(input_dim=2, output_dim=(10, 10), learning_rate=0.1, sigma=None)
som.train(data, num_epochs=1000)
在示例代码中,我们首先定义了SOM
类,用于表示自组织映射模型。在模型初始化时,我们需要指定输入向量的维度、输出向量的维度、学习率和邻域半径。其中,邻域半径可以根据输出向量的维度自动计算。模型的主要方法包括:
train
方法:用于训练模型,接受一个数据集和训练轮数作为参数。
find_bmu
方法:用于寻找与给定输入向量最相似的输出向量。
update_weights
方法:用于更新模型的权值矩阵,使其逐渐逼近输入向量。
最后,在示例代码中我们使用了一个示例数据集进行测试,并训练了1000轮。需要注意的是,示例数据集中每个向量都是二维的,实际应用中可能需要根据具体情况进行处理。
概率图模型算法
隐马尔可夫模型(Hidden Markov Model,HMM)是一种用于建模序列数据的统计模型,主要用于自然语言处理、语音识别、生物信息学等领域。它假设序列中的每个状态都是由一个概率分布生成的,但这个概率分布是未知的,只能通过观察到的数据来推断。因此,HMM是一种基于观测数据和状态之间的概率关系,对未观测状态进行推断的模型。
HMM模型由三部分组成:状态序列、观测序列和模型参数。其中,状态序列表示系统内部的状态变化,每个状态对应一个输出符号;观测序列表示模型的输入,即我们能够观测到的符号序列;模型参数包括状态转移矩阵、观测概率矩阵和初始状态概率分布,用于描述状态之间的转移和观测符号的概率分布。
HMM模型有三个基本问题:
概率计算问题:给定模型和观测序列,计算观测序列出现的概率。
学习问题:给定观测序列,估计模型的参数。
预测问题:给定模型和观测序列,预测隐藏状态序列。
在解决这些问题时,通常采用前向算法、后向算法、Baum-Welch算法、Viterbi算法等。
HMM模型的应用非常广泛,包括语音识别、自然语言处理、手写识别、生物医学信号处理等领域。例如,在语音识别中,HMM模型可以用于将声音信号转化为文字;在自然语言处理中,HMM模型可以用于词性标注、命名实体识别等任务。
import numpy as np
class HMM:
def __init__(self, num_states, num_observations):
self.num_states = num_states
self.num_observations = num_observations
self.transition_prob = np.zeros((num_states, num_states))
self.emission_prob = np.zeros((num_states, num_observations))
self.initial_prob = np.zeros(num_states)
def forward(self, observations):
alpha = np.zeros((len(observations), self.num_states))
alpha[0, :] = self.initial_prob * self.emission_prob[:, observations[0]]
for t in range(1, len(observations)):
for j in range(self.num_states):
alpha[t, j] = np.sum(alpha[t - 1, :] * self.transition_prob[:, j]) * self.emission_prob[j, observations[t]]
return alpha
def backward(self, observations):
beta = np.zeros((len(observations), self.num_states))
beta[-1, :] = 1.0
for t in range(len(observations) - 2, -1, -1):
for i in range(self.num_states):
beta[t, i] = np.sum(self.transition_prob[i, :] * self.emission_prob[:, observations[t + 1]] * beta[t + 1, :])
return beta
def viterbi(self, observations):
delta = np.zeros((len(observations), self.num_states))
psi = np.zeros((len(observations), self.num_states), dtype=np.int)
delta[0, :] = self.initial_prob * self.emission_prob[:, observations[0]]
for t in range(1, len(observations)):
for j in range(self.num_states):
tmp = delta[t - 1, :] * self.transition_prob[:, j] * self.emission_prob[j, observations[t]]
delta[t, j] = np.max(tmp)
psi[t, j] = np.argmax(tmp)
path = np.zeros(len(observations), dtype=np.int)
path[-1] = np.argmax(delta[-1, :])
for t in range(len(observations) - 2, -1, -1):
path[t] = psi[t + 1, path[t + 1]]
return path
def train(self, observations, num_epochs=100, lr=0.1):
for epoch in range(num_epochs):
alpha = self.forward(observations)
beta = self.backward(observations)
gamma = alpha * beta / np.sum(alpha[-1, :])
xi = np.zeros((len(observations) - 1, self.num_states, self.num_states))
for t in range(len(observations) - 1):
xi[t, :, :] = alpha[t, :].reshape((-1, 1)) * self.transition_prob * self.emission_prob[:, observations[t + 1]].reshape((1, -1)) * beta[t + 1, :].reshape((1, -1))
xi[t, :, :] /= np.sum(xi[t, :, :])
self.initial_prob = gamma[0, :]
self.transition_prob = np.sum(xi, axis=0) / np.sum(gamma[:-1, :], axis=0).reshape((-1, 1))
self.emission_prob = np.zeros((self.num_states, self.num_observations))
for k in range(self.num_observations):
mask = (observations == k)
self.emission_prob[:, k] = np.sum(gamma[:, mask], axis=1) / np.sum(gamma, axis=1)
if epoch % 10 == 0:
print("Epoch: {}, Log-likelihood: {}".format(epoch, np.log(np.sum(alpha[-1, :]))))
def predict(self, observations):
return self.viterbi(observations)
if __name__ == '__main__':
np.random.seed(1234)
num_states = 2
num_observations = 3
hmm = HMM(num_states, num_observations)
hmm.initial_prob = np.random.rand(num_states)
hmm.initial_prob /= np.sum(hmm.initial_prob)
hmm.transition_prob = np.random.rand(num_states, num_states)
hmm.transition_prob /= np.sum(hmm.transition_prob, axis=1).reshape((-1, 1))
hmm.emission_prob = np.random.rand(num_states, num_observations)
hmm.emission_prob /= np.sum(hmm.emission_prob, axis=1).reshape((-1, 1))
observations = np.random.randint(num_observations, size=100)
hmm.train(observations, num_epochs=100)
print(hmm.predict(observations))
在示例代码中,我们首先定义了HMM
类,用于表示隐马尔可夫模型。在模型初始化时,我们需要指定状态数量和观测数量。模型的主要方法包括:
forward
方法:用于计算前向概率。
backward
方法:用于计算后向概率。
viterbi
方法:用于计算最优路径。
train
方法:用于训练模型,接受一个观测序列、训练轮数和学习率作为参数。
predict
方法:用于预测最优路径。
在解决这些问题时,我们分别使用了前向算法、后向算法和Viterbi算法。在训练模型时,我们使用Baum-Welch算法进行参数估计。
最后,在示例代码中我们使用了一个随机生成的HMM模型和一个随机生成的观测序列进行测试。需要注意的是,实际应用中需要根据具体问题进行模型的设计和参数的调整。