chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:30:25 +08:00
commit f19b2512d7
562 changed files with 38082 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

+33
View File
@@ -0,0 +1,33 @@
**代码目录**
第1章 统计学习方法概论
第2章 感知机
第3章 k近邻法
第4章 朴素贝叶斯
第5章 决策树
第6章 逻辑斯谛回归
第7章 支持向量机
第8章 提升方法
第9章 EM算法及其推广
第10章 隐马尔可夫模型
第11章 条件随机场
-----------
参考:
https://github.com/wzyonggege/statistical-learning-method
https://github.com/WenDesi/lihang_book_algorithm
https://blog.csdn.net/tudaodiaozhale
代码整理和修改:机器学习初学者 (微信公众号,ID:ai-start-com
@@ -0,0 +1,310 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"原文代码作者:https://blog.csdn.net/tudaodiaozhale\n",
"\n",
"中文注释制作:机器学习初学者(微信公众号:ID:ai-start-com)\n",
"\n",
"配置环境:python 3.6\n",
"\n",
"代码全部测试通过。\n",
"![gongzhong](../gongzhong.jpg)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 第10章 隐马尔可夫模型"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"class HiddenMarkov:\n",
" def forward(self, Q, V, A, B, O, PI): # 使用前向算法\n",
" N = len(Q) # 状态序列的大小\n",
" M = len(O) # 观测序列的大小\n",
" alphas = np.zeros((N, M)) # alpha值\n",
" T = M # 有几个时刻,有几个观测序列,就有几个时刻\n",
" for t in range(T): # 遍历每一时刻,算出alpha值\n",
" indexOfO = V.index(O[t]) # 找出序列对应的索引\n",
" for i in range(N):\n",
" if t == 0: # 计算初值\n",
" alphas[i][t] = PI[t][i] * B[i][indexOfO] # P17610.15\n",
" print('alpha1(%d)=p%db%db(o1)=%f' % (i, i, i, alphas[i][t]))\n",
" else:\n",
" alphas[i][t] = np.dot([alpha[t - 1] for alpha in alphas], [a[i] for a in A]) * B[i][\n",
" indexOfO] # 对应P17610.16\n",
" print('alpha%d(%d)=[sigma alpha%d(i)ai%d]b%d(o%d)=%f' % (t, i, t - 1, i, i, t, alphas[i][t]))\n",
" # print(alphas)\n",
" P = np.sum([alpha[M - 1] for alpha in alphas]) # P176(10.17)\n",
" # alpha11 = pi[0][0] * B[0][0] #代表a1(1)\n",
" # alpha12 = pi[0][1] * B[1][0] #代表a1(2)\n",
" # alpha13 = pi[0][2] * B[2][0] #代表a1(3)\n",
"\n",
" def backward(self, Q, V, A, B, O, PI): # 后向算法\n",
" N = len(Q) # 状态序列的大小\n",
" M = len(O) # 观测序列的大小\n",
" betas = np.ones((N, M)) # beta\n",
" for i in range(N):\n",
" print('beta%d(%d)=1' % (M, i))\n",
" for t in range(M - 2, -1, -1):\n",
" indexOfO = V.index(O[t + 1]) # 找出序列对应的索引\n",
" for i in range(N):\n",
" betas[i][t] = np.dot(np.multiply(A[i], [b[indexOfO] for b in B]), [beta[t + 1] for beta in betas])\n",
" realT = t + 1\n",
" realI = i + 1\n",
" print('beta%d(%d)=[sigma a%djbj(o%d)]beta%d(j)=(' % (realT, realI, realI, realT + 1, realT + 1),\n",
" end='')\n",
" for j in range(N):\n",
" print(\"%.2f*%.2f*%.2f+\" % (A[i][j], B[j][indexOfO], betas[j][t + 1]), end='')\n",
" print(\"0)=%.3f\" % betas[i][t])\n",
" # print(betas)\n",
" indexOfO = V.index(O[0])\n",
" P = np.dot(np.multiply(PI, [b[indexOfO] for b in B]), [beta[0] for beta in betas])\n",
" print(\"P(O|lambda)=\", end=\"\")\n",
" for i in range(N):\n",
" print(\"%.1f*%.1f*%.5f+\" % (PI[0][i], B[i][indexOfO], betas[i][0]), end=\"\")\n",
" print(\"0=%f\" % P)\n",
"\n",
" def viterbi(self, Q, V, A, B, O, PI):\n",
" N = len(Q) # 状态序列的大小\n",
" M = len(O) # 观测序列的大小\n",
" deltas = np.zeros((N, M))\n",
" psis = np.zeros((N, M))\n",
" I = np.zeros((1, M))\n",
" for t in range(M):\n",
" realT = t+1\n",
" indexOfO = V.index(O[t]) # 找出序列对应的索引\n",
" for i in range(N):\n",
" realI = i+1\n",
" if t == 0:\n",
" deltas[i][t] = PI[0][i] * B[i][indexOfO]\n",
" psis[i][t] = 0\n",
" print('delta1(%d)=pi%d * b%d(o1)=%.2f * %.2f=%.2f'%(realI, realI, realI, PI[0][i], B[i][indexOfO], deltas[i][t]))\n",
" print('psis1(%d)=0' % (realI))\n",
" else:\n",
" deltas[i][t] = np.max(np.multiply([delta[t-1] for delta in deltas], [a[i] for a in A])) * B[i][indexOfO]\n",
" print('delta%d(%d)=max[delta%d(j)aj%d]b%d(o%d)=%.2f*%.2f=%.5f'%(realT, realI, realT-1, realI, realI, realT, np.max(np.multiply([delta[t-1] for delta in deltas], [a[i] for a in A])), B[i][indexOfO], deltas[i][t]))\n",
" psis[i][t] = np.argmax(np.multiply([delta[t-1] for delta in deltas], [a[i] for a in A]))\n",
" print('psis%d(%d)=argmax[delta%d(j)aj%d]=%d' % (realT, realI, realT-1, realI, psis[i][t]))\n",
" print(deltas)\n",
" print(psis)\n",
" I[0][M-1] = np.argmax([delta[M-1] for delta in deltas])\n",
" print('i%d=argmax[deltaT(i)]=%d' % (M, I[0][M-1]+1))\n",
" for t in range(M-2, -1, -1):\n",
" I[0][t] = psis[int(I[0][t+1])][t+1]\n",
" print('i%d=psis%d(i%d)=%d' % (t+1, t+2, t+2, I[0][t]+1))\n",
" print(I)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 习题10.1"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"#习题10.1\n",
"Q = [1, 2, 3]\n",
"V = ['红', '白']\n",
"A = [[0.5, 0.2, 0.3], [0.3, 0.5, 0.2], [0.2, 0.3, 0.5]]\n",
"B = [[0.5, 0.5], [0.4, 0.6], [0.7, 0.3]]\n",
"# O = ['红', '白', '红', '红', '白', '红', '白', '白']\n",
"O = ['红', '白', '红', '白'] #习题10.1的例子\n",
"PI = [[0.2, 0.4, 0.4]]"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"delta1(1)=pi1 * b1(o1)=0.20 * 0.50=0.10\n",
"psis1(1)=0\n",
"delta1(2)=pi2 * b2(o1)=0.40 * 0.40=0.16\n",
"psis1(2)=0\n",
"delta1(3)=pi3 * b3(o1)=0.40 * 0.70=0.28\n",
"psis1(3)=0\n",
"delta2(1)=max[delta1(j)aj1]b1(o2)=0.06*0.50=0.02800\n",
"psis2(1)=argmax[delta1(j)aj1]=2\n",
"delta2(2)=max[delta1(j)aj2]b2(o2)=0.08*0.60=0.05040\n",
"psis2(2)=argmax[delta1(j)aj2]=2\n",
"delta2(3)=max[delta1(j)aj3]b3(o2)=0.14*0.30=0.04200\n",
"psis2(3)=argmax[delta1(j)aj3]=2\n",
"delta3(1)=max[delta2(j)aj1]b1(o3)=0.02*0.50=0.00756\n",
"psis3(1)=argmax[delta2(j)aj1]=1\n",
"delta3(2)=max[delta2(j)aj2]b2(o3)=0.03*0.40=0.01008\n",
"psis3(2)=argmax[delta2(j)aj2]=1\n",
"delta3(3)=max[delta2(j)aj3]b3(o3)=0.02*0.70=0.01470\n",
"psis3(3)=argmax[delta2(j)aj3]=2\n",
"delta4(1)=max[delta3(j)aj1]b1(o4)=0.00*0.50=0.00189\n",
"psis4(1)=argmax[delta3(j)aj1]=0\n",
"delta4(2)=max[delta3(j)aj2]b2(o4)=0.01*0.60=0.00302\n",
"psis4(2)=argmax[delta3(j)aj2]=1\n",
"delta4(3)=max[delta3(j)aj3]b3(o4)=0.01*0.30=0.00220\n",
"psis4(3)=argmax[delta3(j)aj3]=2\n",
"[[0.1 0.028 0.00756 0.00189 ]\n",
" [0.16 0.0504 0.01008 0.003024]\n",
" [0.28 0.042 0.0147 0.002205]]\n",
"[[0. 2. 1. 0.]\n",
" [0. 2. 1. 1.]\n",
" [0. 2. 2. 2.]]\n",
"i4=argmax[deltaT(i)]=2\n",
"i3=psis4(i4)=2\n",
"i2=psis3(i3)=2\n",
"i1=psis2(i2)=3\n",
"[[2. 1. 1. 1.]]\n"
]
}
],
"source": [
"HMM = HiddenMarkov()\n",
"# HMM.forward(Q, V, A, B, O, PI)\n",
"# HMM.backward(Q, V, A, B, O, PI)\n",
"HMM.viterbi(Q, V, A, B, O, PI)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 习题10.2"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"Q = [1, 2, 3]\n",
"V = ['红', '白']\n",
"A = [[0.5, 0.2, 0.3], [0.3, 0.5, 0.2], [0.2, 0.3, 0.5]]\n",
"B = [[0.5, 0.5], [0.4, 0.6], [0.7, 0.3]]\n",
"O = ['红', '白', '红', '红', '白', '红', '白', '白']\n",
"PI = [[0.2, 0.3, 0.5]]"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"alpha1(0)=p0b0b(o1)=0.100000\n",
"alpha1(1)=p1b1b(o1)=0.120000\n",
"alpha1(2)=p2b2b(o1)=0.350000\n",
"alpha1(0)=[sigma alpha0(i)ai0]b0(o1)=0.078000\n",
"alpha1(1)=[sigma alpha0(i)ai1]b1(o1)=0.111000\n",
"alpha1(2)=[sigma alpha0(i)ai2]b2(o1)=0.068700\n",
"alpha2(0)=[sigma alpha1(i)ai0]b0(o2)=0.043020\n",
"alpha2(1)=[sigma alpha1(i)ai1]b1(o2)=0.036684\n",
"alpha2(2)=[sigma alpha1(i)ai2]b2(o2)=0.055965\n",
"alpha3(0)=[sigma alpha2(i)ai0]b0(o3)=0.021854\n",
"alpha3(1)=[sigma alpha2(i)ai1]b1(o3)=0.017494\n",
"alpha3(2)=[sigma alpha2(i)ai2]b2(o3)=0.033758\n",
"alpha4(0)=[sigma alpha3(i)ai0]b0(o4)=0.011463\n",
"alpha4(1)=[sigma alpha3(i)ai1]b1(o4)=0.013947\n",
"alpha4(2)=[sigma alpha3(i)ai2]b2(o4)=0.008080\n",
"alpha5(0)=[sigma alpha4(i)ai0]b0(o5)=0.005766\n",
"alpha5(1)=[sigma alpha4(i)ai1]b1(o5)=0.004676\n",
"alpha5(2)=[sigma alpha4(i)ai2]b2(o5)=0.007188\n",
"alpha6(0)=[sigma alpha5(i)ai0]b0(o6)=0.002862\n",
"alpha6(1)=[sigma alpha5(i)ai1]b1(o6)=0.003389\n",
"alpha6(2)=[sigma alpha5(i)ai2]b2(o6)=0.001878\n",
"alpha7(0)=[sigma alpha6(i)ai0]b0(o7)=0.001411\n",
"alpha7(1)=[sigma alpha6(i)ai1]b1(o7)=0.001698\n",
"alpha7(2)=[sigma alpha6(i)ai2]b2(o7)=0.000743\n",
"beta8(0)=1\n",
"beta8(1)=1\n",
"beta8(2)=1\n",
"beta7(1)=[sigma a1jbj(o8)]beta8(j)=(0.50*0.50*1.00+0.20*0.60*1.00+0.30*0.30*1.00+0)=0.460\n",
"beta7(2)=[sigma a2jbj(o8)]beta8(j)=(0.30*0.50*1.00+0.50*0.60*1.00+0.20*0.30*1.00+0)=0.510\n",
"beta7(3)=[sigma a3jbj(o8)]beta8(j)=(0.20*0.50*1.00+0.30*0.60*1.00+0.50*0.30*1.00+0)=0.430\n",
"beta6(1)=[sigma a1jbj(o7)]beta7(j)=(0.50*0.50*0.46+0.20*0.60*0.51+0.30*0.30*0.43+0)=0.215\n",
"beta6(2)=[sigma a2jbj(o7)]beta7(j)=(0.30*0.50*0.46+0.50*0.60*0.51+0.20*0.30*0.43+0)=0.248\n",
"beta6(3)=[sigma a3jbj(o7)]beta7(j)=(0.20*0.50*0.46+0.30*0.60*0.51+0.50*0.30*0.43+0)=0.202\n",
"beta5(1)=[sigma a1jbj(o6)]beta6(j)=(0.50*0.50*0.21+0.20*0.40*0.25+0.30*0.70*0.20+0)=0.116\n",
"beta5(2)=[sigma a2jbj(o6)]beta6(j)=(0.30*0.50*0.21+0.50*0.40*0.25+0.20*0.70*0.20+0)=0.110\n",
"beta5(3)=[sigma a3jbj(o6)]beta6(j)=(0.20*0.50*0.21+0.30*0.40*0.25+0.50*0.70*0.20+0)=0.122\n",
"beta4(1)=[sigma a1jbj(o5)]beta5(j)=(0.50*0.50*0.12+0.20*0.60*0.11+0.30*0.30*0.12+0)=0.053\n",
"beta4(2)=[sigma a2jbj(o5)]beta5(j)=(0.30*0.50*0.12+0.50*0.60*0.11+0.20*0.30*0.12+0)=0.058\n",
"beta4(3)=[sigma a3jbj(o5)]beta5(j)=(0.20*0.50*0.12+0.30*0.60*0.11+0.50*0.30*0.12+0)=0.050\n",
"beta3(1)=[sigma a1jbj(o4)]beta4(j)=(0.50*0.50*0.05+0.20*0.40*0.06+0.30*0.70*0.05+0)=0.028\n",
"beta3(2)=[sigma a2jbj(o4)]beta4(j)=(0.30*0.50*0.05+0.50*0.40*0.06+0.20*0.70*0.05+0)=0.026\n",
"beta3(3)=[sigma a3jbj(o4)]beta4(j)=(0.20*0.50*0.05+0.30*0.40*0.06+0.50*0.70*0.05+0)=0.030\n",
"beta2(1)=[sigma a1jbj(o3)]beta3(j)=(0.50*0.50*0.03+0.20*0.40*0.03+0.30*0.70*0.03+0)=0.015\n",
"beta2(2)=[sigma a2jbj(o3)]beta3(j)=(0.30*0.50*0.03+0.50*0.40*0.03+0.20*0.70*0.03+0)=0.014\n",
"beta2(3)=[sigma a3jbj(o3)]beta3(j)=(0.20*0.50*0.03+0.30*0.40*0.03+0.50*0.70*0.03+0)=0.016\n",
"beta1(1)=[sigma a1jbj(o2)]beta2(j)=(0.50*0.50*0.02+0.20*0.60*0.01+0.30*0.30*0.02+0)=0.007\n",
"beta1(2)=[sigma a2jbj(o2)]beta2(j)=(0.30*0.50*0.02+0.50*0.60*0.01+0.20*0.30*0.02+0)=0.007\n",
"beta1(3)=[sigma a3jbj(o2)]beta2(j)=(0.20*0.50*0.02+0.30*0.60*0.01+0.50*0.30*0.02+0)=0.006\n",
"P(O|lambda)=0.2*0.5*0.00698+0.3*0.4*0.00741+0.5*0.7*0.00647+0=0.003852\n"
]
}
],
"source": [
"HMM.forward(Q, V, A, B, O, PI)\n",
"HMM.backward(Q, V, A, B, O, PI)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -0,0 +1,133 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"原文代码作者:https://blog.csdn.net/GrinAndBearIt/article/details/79229803\n",
"\n",
"中文注释制作:机器学习初学者(微信公众号:ID:ai-start-com)\n",
"\n",
"配置环境:python 3.6\n",
"\n",
"代码全部测试通过。\n",
"![gongzhong](../gongzhong.jpg)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 第11章 条件随机场\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 例11.1"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from numpy import *"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"24.532530197109345\n",
"24.532530197109352\n"
]
}
],
"source": [
"#这里定义T为转移矩阵列代表前一个y(ij)代表由状态i转到状态j的概率,Tx矩阵x对应于时间序列\n",
"#这里将书上的转移特征转换为如下以时间轴为区别的三个多维列表,维度为输出的维度\n",
"T1=[[0.6,1],[1,0]];T2=[[0,1],[1,0.2]]\n",
"#将书上的状态特征同样转换成列表,第一个是为y1的未规划概率,第二个为y2的未规划概率\n",
"S0=[1,0.5];S1=[0.8,0.5];S2=[0.8,0.5]\n",
"Y=[1,2,2] #即书上例一需要计算的非规划条件概率的标记序列\n",
"Y=array(Y)-1 #这里为了将数与索引相对应即从零开始\n",
"P=exp(S0[Y[0]])\n",
"for i in range(1,len(Y)):\n",
" P *= exp((eval('S%d' % i)[Y[i]])+eval('T%d' % i)[Y[i-1]][Y[i]])\n",
"print(P)\n",
"print(exp(3.2))\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 例11.2"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"非规范化概率 24.532530197109345\n"
]
}
],
"source": [
"#这里根据例11.2的启发整合为一个矩阵\n",
"F0=S0;F1=T1+array(S1*len(T1)).reshape(shape(T1));F2=T2+array(S2*len(T2)).reshape(shape(T2))\n",
"Y=[1,2,2] #即书上例一需要计算的非规划条件概率的标记序列\n",
"Y=array(Y)-1\n",
"\n",
"P=exp(F0[Y[0]])\n",
"Sum=P\n",
"for i in range(1,len(Y)):\n",
" PIter=exp((eval('F%d' % i)[Y[i-1]][Y[i]]))\n",
" P *= PIter\n",
" Sum += PIter\n",
"print('非规范化概率',P)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -0,0 +1,133 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"原文代码作者:https://blog.csdn.net/GrinAndBearIt/article/details/79229803\n",
"\n",
"中文注释制作:机器学习初学者(微信公众号:ID:ai-start-com)\n",
"\n",
"配置环境:python 3.6\n",
"\n",
"代码全部测试通过。\n",
"![gongzhong](../gongzhong.jpg)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 第11章 条件随机场\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 例11.1"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from numpy import *"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"24.532530197109345\n",
"24.532530197109352\n"
]
}
],
"source": [
"#这里定义T为转移矩阵列代表前一个y(ij)代表由状态i转到状态j的概率,Tx矩阵x对应于时间序列\n",
"#这里将书上的转移特征转换为如下以时间轴为区别的三个多维列表,维度为输出的维度\n",
"T1=[[0.6,1],[1,0]];T2=[[0,1],[1,0.2]]\n",
"#将书上的状态特征同样转换成列表,第一个是为y1的未规划概率,第二个为y2的未规划概率\n",
"S0=[1,0.5];S1=[0.8,0.5];S2=[0.8,0.5]\n",
"Y=[1,2,2] #即书上例一需要计算的非规划条件概率的标记序列\n",
"Y=array(Y)-1 #这里为了将数与索引相对应即从零开始\n",
"P=exp(S0[Y[0]])\n",
"for i in range(1,len(Y)):\n",
" P *= exp((eval('S%d' % i)[Y[i]])+eval('T%d' % i)[Y[i-1]][Y[i]])\n",
"print(P)\n",
"print(exp(3.2))\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 例11.2"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"非规范化概率 24.532530197109345\n"
]
}
],
"source": [
"#这里根据例11.2的启发整合为一个矩阵\n",
"F0=S0;F1=T1+array(S1*len(T1)).reshape(shape(T1));F2=T2+array(S2*len(T2)).reshape(shape(T2))\n",
"Y=[1,2,2] #即书上例一需要计算的非规划条件概率的标记序列\n",
"Y=array(Y)-1\n",
"\n",
"P=exp(F0[Y[0]])\n",
"Sum=P\n",
"for i in range(1,len(Y)):\n",
" PIter=exp((eval('F%d' % i)[Y[i-1]][Y[i]]))\n",
" P *= PIter\n",
" Sum += PIter\n",
"print('非规范化概率',P)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,123 @@
import numpy as np
from math import sqrt
import pandas as pd
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['label'] = iris.target
df.columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'label']
data = np.array(df.iloc[:100, [0, 1, -1]])
train, test = train_test_split(data, test_size=0.1)
x0 = np.array([x0 for i, x0 in enumerate(train) if train[i][-1] == 0])
x1 = np.array([x1 for i, x1 in enumerate(train) if train[i][-1] == 1])
def show_train():
plt.scatter(x0[:, 0], x0[:, 1], c='pink', label='[0]')
plt.scatter(x1[:, 0], x1[:, 1], c='orange', label='[1]')
plt.xlabel('sepal length')
plt.ylabel('sepal width')
class Node:
def __init__(self, data, depth=0, lchild=None, rchild=None):
self.data = data
self.depth = depth
self.lchild = lchild
self.rchild = rchild
class KdTree:
def __init__(self):
self.KdTree = None
self.n = 0
self.nearest = None
def create(self, dataSet, depth=0):
if len(dataSet) > 0:
m, n = np.shape(dataSet)
self.n = n - 1
axis = depth % self.n
mid = int(m / 2)
dataSetcopy = sorted(dataSet, key=lambda x: x[axis])
node = Node(dataSetcopy[mid], depth)
if depth == 0:
self.KdTree = node
node.lchild = self.create(dataSetcopy[:mid], depth+1)
node.rchild = self.create(dataSetcopy[mid+1:], depth+1)
return node
return None
def preOrder(self, node):
if node is not None:
print(node.depth, node.data)
self.preOrder(node.lchild)
self.preOrder(node.rchild)
def search(self, x, count=1):
nearest = []
for i in range(count):
nearest.append([-1, None])
self.nearest = np.array(nearest)
def recurve(node):
if node is not None:
axis = node.depth % self.n
daxis = x[axis] - node.data[axis]
if daxis < 0:
recurve(node.lchild)
else:
recurve(node.rchild)
dist = sqrt(sum((p1 - p2) ** 2 for p1, p2 in zip(x, node.data)))
for i, d in enumerate(self.nearest):
if d[0] < 0 or dist < d[0]:
self.nearest = np.insert(self.nearest, i, [dist, node], axis=0)
self.nearest = self.nearest[:-1]
break
n = list(self.nearest[:, 0]).count(-1)
if self.nearest[-n-1, 0] > abs(daxis):
if daxis < 0:
recurve(node.rchild)
else:
recurve(node.lchild)
recurve(self.KdTree)
knn = self.nearest[:, 1]
belong = []
for i in knn:
belong.append(i.data[-1])
b = max(set(belong), key=belong.count)
return self.nearest, b
kdt = KdTree()
kdt.create(train)
kdt.preOrder(kdt.KdTree)
score = 0
for x in test:
input('press Enter to show next:')
show_train()
plt.scatter(x[0], x[1], c='red', marker='x') # 测试点
near, belong = kdt.search(x[:-1], 5) # 设置临近点的个数
if belong == x[-1]:
score += 1
print("test:")
print(x, "predict:", belong)
print("nearest:")
for n in near:
print(n[1].data, "dist:", n[0])
plt.scatter(n[1].data[0], n[1].data[1], c='green', marker='+') # k个最近邻点
plt.legend()
plt.show()
score /= len(test)
print("score:", score)
File diff suppressed because one or more lines are too long
@@ -0,0 +1,372 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"原文代码作者:https://github.com/wzyonggege/statistical-learning-method\n",
"\n",
"中文注释制作:机器学习初学者(微信公众号:ID:ai-start-com)\n",
"\n",
"配置环境:python 3.6\n",
"\n",
"代码全部测试通过。\n",
"![gongzhong](../gongzhong.jpg)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 第4章 朴素贝叶斯"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"基于贝叶斯定理与特征条件独立假设的分类方法。\n",
"\n",
"模型:\n",
"\n",
"- 高斯模型\n",
"- 多项式模型\n",
"- 伯努利模型"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"%matplotlib inline\n",
"\n",
"from sklearn.datasets import load_iris\n",
"from sklearn.model_selection import train_test_split\n",
"\n",
"from collections import Counter\n",
"import math"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"# data\n",
"def create_data():\n",
" iris = load_iris()\n",
" df = pd.DataFrame(iris.data, columns=iris.feature_names)\n",
" df['label'] = iris.target\n",
" df.columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'label']\n",
" data = np.array(df.iloc[:100, :])\n",
" # print(data)\n",
" return data[:,:-1], data[:,-1]"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"X, y = create_data()\n",
"X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(array([4.6, 3.4, 1.4, 0.3]), 0.0)"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"X_test[0], y_test[0]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"参考:https://machinelearningmastery.com/naive-bayes-classifier-scratch-python/\n",
"\n",
"## GaussianNB 高斯朴素贝叶斯\n",
"\n",
"特征的可能性被假设为高斯\n",
"\n",
"概率密度函数:\n",
"$$P(x_i | y_k)=\\frac{1}{\\sqrt{2\\pi\\sigma^2_{yk}}}exp(-\\frac{(x_i-\\mu_{yk})^2}{2\\sigma^2_{yk}})$$\n",
"\n",
"数学期望(mean)$\\mu$,方差:$\\sigma^2=\\frac{\\sum(X-\\mu)^2}{N}$"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"class NaiveBayes:\n",
" def __init__(self):\n",
" self.model = None\n",
"\n",
" # 数学期望\n",
" @staticmethod\n",
" def mean(X):\n",
" return sum(X) / float(len(X))\n",
"\n",
" # 标准差(方差)\n",
" def stdev(self, X):\n",
" avg = self.mean(X)\n",
" return math.sqrt(sum([pow(x-avg, 2) for x in X]) / float(len(X)))\n",
"\n",
" # 概率密度函数\n",
" def gaussian_probability(self, x, mean, stdev):\n",
" exponent = math.exp(-(math.pow(x-mean,2)/(2*math.pow(stdev,2))))\n",
" return (1 / (math.sqrt(2*math.pi) * stdev)) * exponent\n",
"\n",
" # 处理X_train\n",
" def summarize(self, train_data):\n",
" summaries = [(self.mean(i), self.stdev(i)) for i in zip(*train_data)]\n",
" return summaries\n",
"\n",
" # 分类别求出数学期望和标准差\n",
" def fit(self, X, y):\n",
" labels = list(set(y))\n",
" data = {label:[] for label in labels}\n",
" for f, label in zip(X, y):\n",
" data[label].append(f)\n",
" self.model = {label: self.summarize(value) for label, value in data.items()}\n",
" return 'gaussianNB train done!'\n",
"\n",
" # 计算概率\n",
" def calculate_probabilities(self, input_data):\n",
" # summaries:{0.0: [(5.0, 0.37),(3.42, 0.40)], 1.0: [(5.8, 0.449),(2.7, 0.27)]}\n",
" # input_data:[1.1, 2.2]\n",
" probabilities = {}\n",
" for label, value in self.model.items():\n",
" probabilities[label] = 1\n",
" for i in range(len(value)):\n",
" mean, stdev = value[i]\n",
" probabilities[label] *= self.gaussian_probability(input_data[i], mean, stdev)\n",
" return probabilities\n",
"\n",
" # 类别\n",
" def predict(self, X_test):\n",
" # {0.0: 2.9680340789325763e-27, 1.0: 3.5749783019849535e-26}\n",
" label = sorted(self.calculate_probabilities(X_test).items(), key=lambda x: x[-1])[-1][0]\n",
" return label\n",
"\n",
" def score(self, X_test, y_test):\n",
" right = 0\n",
" for X, y in zip(X_test, y_test):\n",
" label = self.predict(X)\n",
" if label == y:\n",
" right += 1\n",
"\n",
" return right / float(len(X_test))"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"model = NaiveBayes()"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'gaussianNB train done!'"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model.fit(X_train, y_train)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0.0\n"
]
}
],
"source": [
"print(model.predict([4.4, 3.2, 1.3, 0.2]))"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1.0"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model.score(X_test, y_test)"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"scikit-learn实例\n",
"\n",
"# sklearn.naive_bayes"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"from sklearn.naive_bayes import GaussianNB"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"GaussianNB(priors=None)"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"clf = GaussianNB()\n",
"clf.fit(X_train, y_train)"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1.0"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"clf.score(X_test, y_test)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([0.])"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"clf.predict([[4.4, 3.2, 1.3, 0.2]])"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"from sklearn.naive_bayes import BernoulliNB, MultinomialNB # 伯努利模型和多项式模型"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -0,0 +1,886 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"原文代码作者:https://github.com/wzyonggege/statistical-learning-method\n",
"\n",
"中文注释制作:机器学习初学者(微信公众号:ID:ai-start-com)\n",
"\n",
"配置环境:python 3.6\n",
"\n",
"代码全部测试通过。\n",
"![gongzhong](../gongzhong.jpg)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 第5章 决策树"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- ID3(基于信息增益)\n",
"- C4.5(基于信息增益比)\n",
"- CARTgini指数)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### entropy$H(x) = -\\sum_{i=1}^{n}p_i\\log{p_i}$\n",
"\n",
"#### conditional entropy: $H(X|Y)=\\sum{P(X|Y)}\\log{P(X|Y)}$\n",
"\n",
"#### information gain : $g(D, A)=H(D)-H(D|A)$\n",
"\n",
"#### information gain ratio: $g_R(D, A) = \\frac{g(D,A)}{H(A)}$\n",
"\n",
"#### gini index:$Gini(D)=\\sum_{k=1}^{K}p_k\\log{p_k}=1-\\sum_{k=1}^{K}p_k^2$"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"%matplotlib inline\n",
"\n",
"from sklearn.datasets import load_iris\n",
"from sklearn.model_selection import train_test_split\n",
"\n",
"from collections import Counter\n",
"import math\n",
"from math import log\n",
"\n",
"import pprint"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 书上题目5.1"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"# 书上题目5.1\n",
"def create_data():\n",
" datasets = [['青年', '否', '否', '一般', '否'],\n",
" ['青年', '否', '否', '好', '否'],\n",
" ['青年', '是', '否', '好', '是'],\n",
" ['青年', '是', '是', '一般', '是'],\n",
" ['青年', '否', '否', '一般', '否'],\n",
" ['中年', '否', '否', '一般', '否'],\n",
" ['中年', '否', '否', '好', '否'],\n",
" ['中年', '是', '是', '好', '是'],\n",
" ['中年', '否', '是', '非常好', '是'],\n",
" ['中年', '否', '是', '非常好', '是'],\n",
" ['老年', '否', '是', '非常好', '是'],\n",
" ['老年', '否', '是', '好', '是'],\n",
" ['老年', '是', '否', '好', '是'],\n",
" ['老年', '是', '否', '非常好', '是'],\n",
" ['老年', '否', '否', '一般', '否'],\n",
" ]\n",
" labels = [u'年龄', u'有工作', u'有自己的房子', u'信贷情况', u'类别']\n",
" # 返回数据集和每个维度的名称\n",
" return datasets, labels"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"datasets, labels = create_data()"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"train_data = pd.DataFrame(datasets, columns=labels)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>年龄</th>\n",
" <th>有工作</th>\n",
" <th>有自己的房子</th>\n",
" <th>信贷情况</th>\n",
" <th>类别</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>青年</td>\n",
" <td>否</td>\n",
" <td>否</td>\n",
" <td>一般</td>\n",
" <td>否</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>青年</td>\n",
" <td>否</td>\n",
" <td>否</td>\n",
" <td>好</td>\n",
" <td>否</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>青年</td>\n",
" <td>是</td>\n",
" <td>否</td>\n",
" <td>好</td>\n",
" <td>是</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>青年</td>\n",
" <td>是</td>\n",
" <td>是</td>\n",
" <td>一般</td>\n",
" <td>是</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>青年</td>\n",
" <td>否</td>\n",
" <td>否</td>\n",
" <td>一般</td>\n",
" <td>否</td>\n",
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
" <td>中年</td>\n",
" <td>否</td>\n",
" <td>否</td>\n",
" <td>一般</td>\n",
" <td>否</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6</th>\n",
" <td>中年</td>\n",
" <td>否</td>\n",
" <td>否</td>\n",
" <td>好</td>\n",
" <td>否</td>\n",
" </tr>\n",
" <tr>\n",
" <th>7</th>\n",
" <td>中年</td>\n",
" <td>是</td>\n",
" <td>是</td>\n",
" <td>好</td>\n",
" <td>是</td>\n",
" </tr>\n",
" <tr>\n",
" <th>8</th>\n",
" <td>中年</td>\n",
" <td>否</td>\n",
" <td>是</td>\n",
" <td>非常好</td>\n",
" <td>是</td>\n",
" </tr>\n",
" <tr>\n",
" <th>9</th>\n",
" <td>中年</td>\n",
" <td>否</td>\n",
" <td>是</td>\n",
" <td>非常好</td>\n",
" <td>是</td>\n",
" </tr>\n",
" <tr>\n",
" <th>10</th>\n",
" <td>老年</td>\n",
" <td>否</td>\n",
" <td>是</td>\n",
" <td>非常好</td>\n",
" <td>是</td>\n",
" </tr>\n",
" <tr>\n",
" <th>11</th>\n",
" <td>老年</td>\n",
" <td>否</td>\n",
" <td>是</td>\n",
" <td>好</td>\n",
" <td>是</td>\n",
" </tr>\n",
" <tr>\n",
" <th>12</th>\n",
" <td>老年</td>\n",
" <td>是</td>\n",
" <td>否</td>\n",
" <td>好</td>\n",
" <td>是</td>\n",
" </tr>\n",
" <tr>\n",
" <th>13</th>\n",
" <td>老年</td>\n",
" <td>是</td>\n",
" <td>否</td>\n",
" <td>非常好</td>\n",
" <td>是</td>\n",
" </tr>\n",
" <tr>\n",
" <th>14</th>\n",
" <td>老年</td>\n",
" <td>否</td>\n",
" <td>否</td>\n",
" <td>一般</td>\n",
" <td>否</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" 年龄 有工作 有自己的房子 信贷情况 类别\n",
"0 青年 否 否 一般 否\n",
"1 青年 否 否 好 否\n",
"2 青年 是 否 好 是\n",
"3 青年 是 是 一般 是\n",
"4 青年 否 否 一般 否\n",
"5 中年 否 否 一般 否\n",
"6 中年 否 否 好 否\n",
"7 中年 是 是 好 是\n",
"8 中年 否 是 非常好 是\n",
"9 中年 否 是 非常好 是\n",
"10 老年 否 是 非常好 是\n",
"11 老年 否 是 好 是\n",
"12 老年 是 否 好 是\n",
"13 老年 是 否 非常好 是\n",
"14 老年 否 否 一般 否"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"train_data"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"# 熵\n",
"def calc_ent(datasets):\n",
" data_length = len(datasets)\n",
" label_count = {}\n",
" for i in range(data_length):\n",
" label = datasets[i][-1]\n",
" if label not in label_count:\n",
" label_count[label] = 0\n",
" label_count[label] += 1\n",
" ent = -sum([(p/data_length)*log(p/data_length, 2) for p in label_count.values()])\n",
" return ent\n",
"\n",
"# 经验条件熵\n",
"def cond_ent(datasets, axis=0):\n",
" data_length = len(datasets)\n",
" feature_sets = {}\n",
" for i in range(data_length):\n",
" feature = datasets[i][axis]\n",
" if feature not in feature_sets:\n",
" feature_sets[feature] = []\n",
" feature_sets[feature].append(datasets[i])\n",
" cond_ent = sum([(len(p)/data_length)*calc_ent(p) for p in feature_sets.values()])\n",
" return cond_ent\n",
"\n",
"# 信息增益\n",
"def info_gain(ent, cond_ent):\n",
" return ent - cond_ent\n",
"\n",
"def info_gain_train(datasets):\n",
" count = len(datasets[0]) - 1\n",
" ent = calc_ent(datasets)\n",
" best_feature = []\n",
" for c in range(count):\n",
" c_info_gain = info_gain(ent, cond_ent(datasets, axis=c))\n",
" best_feature.append((c, c_info_gain))\n",
" print('特征({}) - info_gain - {:.3f}'.format(labels[c], c_info_gain))\n",
" # 比较大小\n",
" best_ = max(best_feature, key=lambda x: x[-1])\n",
" return '特征({})的信息增益最大,选择为根节点特征'.format(labels[best_[0]])"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"特征(年龄) - info_gain - 0.083\n",
"特征(有工作) - info_gain - 0.324\n",
"特征(有自己的房子) - info_gain - 0.420\n",
"特征(信贷情况) - info_gain - 0.363\n"
]
},
{
"data": {
"text/plain": [
"'特征(有自己的房子)的信息增益最大,选择为根节点特征'"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"info_gain_train(np.array(datasets))"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"---\n",
"\n",
"利用ID3算法生成决策树,例5.3"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"# 定义节点类 二叉树\n",
"class Node:\n",
" def __init__(self, root=True, label=None, feature_name=None, feature=None):\n",
" self.root = root\n",
" self.label = label\n",
" self.feature_name = feature_name\n",
" self.feature = feature\n",
" self.tree = {}\n",
" self.result = {'label:': self.label, 'feature': self.feature, 'tree': self.tree}\n",
"\n",
" def __repr__(self):\n",
" return '{}'.format(self.result)\n",
"\n",
" def add_node(self, val, node):\n",
" self.tree[val] = node\n",
"\n",
" def predict(self, features):\n",
" if self.root is True:\n",
" return self.label\n",
" return self.tree[features[self.feature]].predict(features)\n",
" \n",
"class DTree:\n",
" def __init__(self, epsilon=0.1):\n",
" self.epsilon = epsilon\n",
" self._tree = {}\n",
"\n",
" # 熵\n",
" @staticmethod\n",
" def calc_ent(datasets):\n",
" data_length = len(datasets)\n",
" label_count = {}\n",
" for i in range(data_length):\n",
" label = datasets[i][-1]\n",
" if label not in label_count:\n",
" label_count[label] = 0\n",
" label_count[label] += 1\n",
" ent = -sum([(p/data_length)*log(p/data_length, 2) for p in label_count.values()])\n",
" return ent\n",
"\n",
" # 经验条件熵\n",
" def cond_ent(self, datasets, axis=0):\n",
" data_length = len(datasets)\n",
" feature_sets = {}\n",
" for i in range(data_length):\n",
" feature = datasets[i][axis]\n",
" if feature not in feature_sets:\n",
" feature_sets[feature] = []\n",
" feature_sets[feature].append(datasets[i])\n",
" cond_ent = sum([(len(p)/data_length)*self.calc_ent(p) for p in feature_sets.values()])\n",
" return cond_ent\n",
"\n",
" # 信息增益\n",
" @staticmethod\n",
" def info_gain(ent, cond_ent):\n",
" return ent - cond_ent\n",
"\n",
" def info_gain_train(self, datasets):\n",
" count = len(datasets[0]) - 1\n",
" ent = self.calc_ent(datasets)\n",
" best_feature = []\n",
" for c in range(count):\n",
" c_info_gain = self.info_gain(ent, self.cond_ent(datasets, axis=c))\n",
" best_feature.append((c, c_info_gain))\n",
" # 比较大小\n",
" best_ = max(best_feature, key=lambda x: x[-1])\n",
" return best_\n",
"\n",
" def train(self, train_data):\n",
" \"\"\"\n",
" input:数据集D(DataFrame格式),特征集A,阈值eta\n",
" output:决策树T\n",
" \"\"\"\n",
" _, y_train, features = train_data.iloc[:, :-1], train_data.iloc[:, -1], train_data.columns[:-1]\n",
" # 1,若D中实例属于同一类Ck,则T为单节点树,并将类Ck作为结点的类标记,返回T\n",
" if len(y_train.value_counts()) == 1:\n",
" return Node(root=True,\n",
" label=y_train.iloc[0])\n",
"\n",
" # 2, 若A为空,则T为单节点树,将D中实例树最大的类Ck作为该节点的类标记,返回T\n",
" if len(features) == 0:\n",
" return Node(root=True, label=y_train.value_counts().sort_values(ascending=False).index[0])\n",
"\n",
" # 3,计算最大信息增益 同5.1,Ag为信息增益最大的特征\n",
" max_feature, max_info_gain = self.info_gain_train(np.array(train_data))\n",
" max_feature_name = features[max_feature]\n",
"\n",
" # 4,Ag的信息增益小于阈值eta,则置T为单节点树,并将D中是实例数最大的类Ck作为该节点的类标记,返回T\n",
" if max_info_gain < self.epsilon:\n",
" return Node(root=True, label=y_train.value_counts().sort_values(ascending=False).index[0])\n",
"\n",
" # 5,构建Ag子集\n",
" node_tree = Node(root=False, feature_name=max_feature_name, feature=max_feature)\n",
"\n",
" feature_list = train_data[max_feature_name].value_counts().index\n",
" for f in feature_list:\n",
" sub_train_df = train_data.loc[train_data[max_feature_name] == f].drop([max_feature_name], axis=1)\n",
"\n",
" # 6, 递归生成树\n",
" sub_tree = self.train(sub_train_df)\n",
" node_tree.add_node(f, sub_tree)\n",
"\n",
" # pprint.pprint(node_tree.tree)\n",
" return node_tree\n",
"\n",
" def fit(self, train_data):\n",
" self._tree = self.train(train_data)\n",
" return self._tree\n",
"\n",
" def predict(self, X_test):\n",
" return self._tree.predict(X_test)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"datasets, labels = create_data()\n",
"data_df = pd.DataFrame(datasets, columns=labels)\n",
"dt = DTree()\n",
"tree = dt.fit(data_df)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"text/plain": [
"{'label:': None, 'feature': 2, 'tree': {'否': {'label:': None, 'feature': 1, 'tree': {'否': {'label:': '否', 'feature': None, 'tree': {}}, '是': {'label:': '是', 'feature': None, 'tree': {}}}}, '是': {'label:': '是', 'feature': None, 'tree': {}}}}"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tree"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'否'"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"dt.predict(['老年', '否', '否', '一般'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## sklearn.tree.DecisionTreeClassifier\n",
"\n",
"### criterion : string, optional (default=”gini”)\n",
"The function to measure the quality of a split. Supported criteria are “gini” for the Gini impurity and “entropy” for the information gain."
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"# data\n",
"def create_data():\n",
" iris = load_iris()\n",
" df = pd.DataFrame(iris.data, columns=iris.feature_names)\n",
" df['label'] = iris.target\n",
" df.columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'label']\n",
" data = np.array(df.iloc[:100, [0, 1, -1]])\n",
" # print(data)\n",
" return data[:,:2], data[:,-1]\n",
"\n",
"X, y = create_data()\n",
"X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"from sklearn.tree import DecisionTreeClassifier\n",
"\n",
"from sklearn.tree import export_graphviz\n",
"import graphviz"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None,\n",
" max_features=None, max_leaf_nodes=None,\n",
" min_impurity_decrease=0.0, min_impurity_split=None,\n",
" min_samples_leaf=1, min_samples_split=2,\n",
" min_weight_fraction_leaf=0.0, presort=False, random_state=None,\n",
" splitter='best')"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"clf = DecisionTreeClassifier()\n",
"clf.fit(X_train, y_train,)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1.0"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"clf.score(X_test, y_test)"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"tree_pic = export_graphviz(clf, out_file=\"mytree.pdf\")\n",
"with open('mytree.pdf') as f:\n",
" dot_graph = f.read()"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\r\n",
"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\r\n",
" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\r\n",
"<!-- Generated by graphviz version 2.38.0 (20140413.2041)\r\n",
" -->\r\n",
"<!-- Title: Tree Pages: 1 -->\r\n",
"<svg width=\"550pt\" height=\"477pt\"\r\n",
" viewBox=\"0.00 0.00 550.00 477.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\r\n",
"<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 473)\">\r\n",
"<title>Tree</title>\r\n",
"<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-473 546,-473 546,4 -4,4\"/>\r\n",
"<!-- 0 -->\r\n",
"<g id=\"node1\" class=\"node\"><title>0</title>\r\n",
"<polygon fill=\"none\" stroke=\"black\" points=\"377.5,-469 273.5,-469 273.5,-401 377.5,-401 377.5,-469\"/>\r\n",
"<text text-anchor=\"middle\" x=\"325.5\" y=\"-453.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">X[0] &lt;= 5.45</text>\r\n",
"<text text-anchor=\"middle\" x=\"325.5\" y=\"-438.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">gini = 0.5</text>\r\n",
"<text text-anchor=\"middle\" x=\"325.5\" y=\"-423.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">samples = 70</text>\r\n",
"<text text-anchor=\"middle\" x=\"325.5\" y=\"-408.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">value = [35, 35]</text>\r\n",
"</g>\r\n",
"<!-- 1 -->\r\n",
"<g id=\"node2\" class=\"node\"><title>1</title>\r\n",
"<polygon fill=\"none\" stroke=\"black\" points=\"316.5,-365 218.5,-365 218.5,-297 316.5,-297 316.5,-365\"/>\r\n",
"<text text-anchor=\"middle\" x=\"267.5\" y=\"-349.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">X[1] &lt;= 2.85</text>\r\n",
"<text text-anchor=\"middle\" x=\"267.5\" y=\"-334.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">gini = 0.234</text>\r\n",
"<text text-anchor=\"middle\" x=\"267.5\" y=\"-319.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">samples = 37</text>\r\n",
"<text text-anchor=\"middle\" x=\"267.5\" y=\"-304.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">value = [32, 5]</text>\r\n",
"</g>\r\n",
"<!-- 0&#45;&gt;1 -->\r\n",
"<g id=\"edge1\" class=\"edge\"><title>0&#45;&gt;1</title>\r\n",
"<path fill=\"none\" stroke=\"black\" d=\"M306.669,-400.884C301.807,-392.332 296.508,-383.013 291.423,-374.072\"/>\r\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"294.421,-372.262 286.435,-365.299 288.335,-375.722 294.421,-372.262\"/>\r\n",
"<text text-anchor=\"middle\" x=\"279.806\" y=\"-385.704\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">True</text>\r\n",
"</g>\r\n",
"<!-- 10 -->\r\n",
"<g id=\"node11\" class=\"node\"><title>10</title>\r\n",
"<polygon fill=\"none\" stroke=\"black\" points=\"432.5,-365 334.5,-365 334.5,-297 432.5,-297 432.5,-365\"/>\r\n",
"<text text-anchor=\"middle\" x=\"383.5\" y=\"-349.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">X[1] &lt;= 3.45</text>\r\n",
"<text text-anchor=\"middle\" x=\"383.5\" y=\"-334.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">gini = 0.165</text>\r\n",
"<text text-anchor=\"middle\" x=\"383.5\" y=\"-319.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">samples = 33</text>\r\n",
"<text text-anchor=\"middle\" x=\"383.5\" y=\"-304.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">value = [3, 30]</text>\r\n",
"</g>\r\n",
"<!-- 0&#45;&gt;10 -->\r\n",
"<g id=\"edge10\" class=\"edge\"><title>0&#45;&gt;10</title>\r\n",
"<path fill=\"none\" stroke=\"black\" d=\"M344.331,-400.884C349.193,-392.332 354.492,-383.013 359.577,-374.072\"/>\r\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"362.665,-375.722 364.565,-365.299 356.579,-372.262 362.665,-375.722\"/>\r\n",
"<text text-anchor=\"middle\" x=\"371.194\" y=\"-385.704\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">False</text>\r\n",
"</g>\r\n",
"<!-- 2 -->\r\n",
"<g id=\"node3\" class=\"node\"><title>2</title>\r\n",
"<polygon fill=\"none\" stroke=\"black\" points=\"200,-261 109,-261 109,-193 200,-193 200,-261\"/>\r\n",
"<text text-anchor=\"middle\" x=\"154.5\" y=\"-245.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">X[0] &lt;= 4.7</text>\r\n",
"<text text-anchor=\"middle\" x=\"154.5\" y=\"-230.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">gini = 0.32</text>\r\n",
"<text text-anchor=\"middle\" x=\"154.5\" y=\"-215.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">samples = 5</text>\r\n",
"<text text-anchor=\"middle\" x=\"154.5\" y=\"-200.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">value = [1, 4]</text>\r\n",
"</g>\r\n",
"<!-- 1&#45;&gt;2 -->\r\n",
"<g id=\"edge2\" class=\"edge\"><title>1&#45;&gt;2</title>\r\n",
"<path fill=\"none\" stroke=\"black\" d=\"M230.812,-296.884C220.648,-287.709 209.505,-277.65 198.95,-268.123\"/>\r\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"201.159,-265.402 191.39,-261.299 196.468,-270.598 201.159,-265.402\"/>\r\n",
"</g>\r\n",
"<!-- 5 -->\r\n",
"<g id=\"node6\" class=\"node\"><title>5</title>\r\n",
"<polygon fill=\"none\" stroke=\"black\" points=\"316.5,-261 218.5,-261 218.5,-193 316.5,-193 316.5,-261\"/>\r\n",
"<text text-anchor=\"middle\" x=\"267.5\" y=\"-245.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">X[0] &lt;= 5.35</text>\r\n",
"<text text-anchor=\"middle\" x=\"267.5\" y=\"-230.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">gini = 0.061</text>\r\n",
"<text text-anchor=\"middle\" x=\"267.5\" y=\"-215.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">samples = 32</text>\r\n",
"<text text-anchor=\"middle\" x=\"267.5\" y=\"-200.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">value = [31, 1]</text>\r\n",
"</g>\r\n",
"<!-- 1&#45;&gt;5 -->\r\n",
"<g id=\"edge5\" class=\"edge\"><title>1&#45;&gt;5</title>\r\n",
"<path fill=\"none\" stroke=\"black\" d=\"M267.5,-296.884C267.5,-288.778 267.5,-279.982 267.5,-271.472\"/>\r\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"271,-271.299 267.5,-261.299 264,-271.299 271,-271.299\"/>\r\n",
"</g>\r\n",
"<!-- 3 -->\r\n",
"<g id=\"node4\" class=\"node\"><title>3</title>\r\n",
"<polygon fill=\"none\" stroke=\"black\" points=\"91,-149.5 0,-149.5 0,-96.5 91,-96.5 91,-149.5\"/>\r\n",
"<text text-anchor=\"middle\" x=\"45.5\" y=\"-134.3\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">gini = 0.0</text>\r\n",
"<text text-anchor=\"middle\" x=\"45.5\" y=\"-119.3\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">samples = 1</text>\r\n",
"<text text-anchor=\"middle\" x=\"45.5\" y=\"-104.3\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">value = [1, 0]</text>\r\n",
"</g>\r\n",
"<!-- 2&#45;&gt;3 -->\r\n",
"<g id=\"edge3\" class=\"edge\"><title>2&#45;&gt;3</title>\r\n",
"<path fill=\"none\" stroke=\"black\" d=\"M119.111,-192.884C106.653,-181.226 92.6699,-168.141 80.2641,-156.532\"/>\r\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"82.4644,-153.797 72.7713,-149.52 77.6815,-158.908 82.4644,-153.797\"/>\r\n",
"</g>\r\n",
"<!-- 4 -->\r\n",
"<g id=\"node5\" class=\"node\"><title>4</title>\r\n",
"<polygon fill=\"none\" stroke=\"black\" points=\"200,-149.5 109,-149.5 109,-96.5 200,-96.5 200,-149.5\"/>\r\n",
"<text text-anchor=\"middle\" x=\"154.5\" y=\"-134.3\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">gini = 0.0</text>\r\n",
"<text text-anchor=\"middle\" x=\"154.5\" y=\"-119.3\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">samples = 4</text>\r\n",
"<text text-anchor=\"middle\" x=\"154.5\" y=\"-104.3\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">value = [0, 4]</text>\r\n",
"</g>\r\n",
"<!-- 2&#45;&gt;4 -->\r\n",
"<g id=\"edge4\" class=\"edge\"><title>2&#45;&gt;4</title>\r\n",
"<path fill=\"none\" stroke=\"black\" d=\"M154.5,-192.884C154.5,-182.326 154.5,-170.597 154.5,-159.854\"/>\r\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"158,-159.52 154.5,-149.52 151,-159.52 158,-159.52\"/>\r\n",
"</g>\r\n",
"<!-- 6 -->\r\n",
"<g id=\"node7\" class=\"node\"><title>6</title>\r\n",
"<polygon fill=\"none\" stroke=\"black\" points=\"316.5,-149.5 218.5,-149.5 218.5,-96.5 316.5,-96.5 316.5,-149.5\"/>\r\n",
"<text text-anchor=\"middle\" x=\"267.5\" y=\"-134.3\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">gini = 0.0</text>\r\n",
"<text text-anchor=\"middle\" x=\"267.5\" y=\"-119.3\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">samples = 28</text>\r\n",
"<text text-anchor=\"middle\" x=\"267.5\" y=\"-104.3\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">value = [28, 0]</text>\r\n",
"</g>\r\n",
"<!-- 5&#45;&gt;6 -->\r\n",
"<g id=\"edge6\" class=\"edge\"><title>5&#45;&gt;6</title>\r\n",
"<path fill=\"none\" stroke=\"black\" d=\"M267.5,-192.884C267.5,-182.326 267.5,-170.597 267.5,-159.854\"/>\r\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"271,-159.52 267.5,-149.52 264,-159.52 271,-159.52\"/>\r\n",
"</g>\r\n",
"<!-- 7 -->\r\n",
"<g id=\"node8\" class=\"node\"><title>7</title>\r\n",
"<polygon fill=\"none\" stroke=\"black\" points=\"426,-157 335,-157 335,-89 426,-89 426,-157\"/>\r\n",
"<text text-anchor=\"middle\" x=\"380.5\" y=\"-141.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">X[1] &lt;= 3.2</text>\r\n",
"<text text-anchor=\"middle\" x=\"380.5\" y=\"-126.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">gini = 0.375</text>\r\n",
"<text text-anchor=\"middle\" x=\"380.5\" y=\"-111.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">samples = 4</text>\r\n",
"<text text-anchor=\"middle\" x=\"380.5\" y=\"-96.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">value = [3, 1]</text>\r\n",
"</g>\r\n",
"<!-- 5&#45;&gt;7 -->\r\n",
"<g id=\"edge7\" class=\"edge\"><title>5&#45;&gt;7</title>\r\n",
"<path fill=\"none\" stroke=\"black\" d=\"M304.188,-192.884C314.352,-183.709 325.495,-173.65 336.05,-164.123\"/>\r\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"338.532,-166.598 343.61,-157.299 333.841,-161.402 338.532,-166.598\"/>\r\n",
"</g>\r\n",
"<!-- 8 -->\r\n",
"<g id=\"node9\" class=\"node\"><title>8</title>\r\n",
"<polygon fill=\"none\" stroke=\"black\" points=\"371,-53 280,-53 280,-0 371,-0 371,-53\"/>\r\n",
"<text text-anchor=\"middle\" x=\"325.5\" y=\"-37.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">gini = 0.0</text>\r\n",
"<text text-anchor=\"middle\" x=\"325.5\" y=\"-22.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">samples = 1</text>\r\n",
"<text text-anchor=\"middle\" x=\"325.5\" y=\"-7.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">value = [0, 1]</text>\r\n",
"</g>\r\n",
"<!-- 7&#45;&gt;8 -->\r\n",
"<g id=\"edge8\" class=\"edge\"><title>7&#45;&gt;8</title>\r\n",
"<path fill=\"none\" stroke=\"black\" d=\"M361.264,-88.9485C356.206,-80.2579 350.736,-70.8608 345.633,-62.0917\"/>\r\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"348.534,-60.1189 340.479,-53.2367 342.484,-63.6401 348.534,-60.1189\"/>\r\n",
"</g>\r\n",
"<!-- 9 -->\r\n",
"<g id=\"node10\" class=\"node\"><title>9</title>\r\n",
"<polygon fill=\"none\" stroke=\"black\" points=\"480,-53 389,-53 389,-0 480,-0 480,-53\"/>\r\n",
"<text text-anchor=\"middle\" x=\"434.5\" y=\"-37.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">gini = 0.0</text>\r\n",
"<text text-anchor=\"middle\" x=\"434.5\" y=\"-22.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">samples = 3</text>\r\n",
"<text text-anchor=\"middle\" x=\"434.5\" y=\"-7.8\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">value = [3, 0]</text>\r\n",
"</g>\r\n",
"<!-- 7&#45;&gt;9 -->\r\n",
"<g id=\"edge9\" class=\"edge\"><title>7&#45;&gt;9</title>\r\n",
"<path fill=\"none\" stroke=\"black\" d=\"M399.387,-88.9485C404.353,-80.2579 409.722,-70.8608 414.733,-62.0917\"/>\r\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"417.871,-63.6557 419.793,-53.2367 411.793,-60.1826 417.871,-63.6557\"/>\r\n",
"</g>\r\n",
"<!-- 11 -->\r\n",
"<g id=\"node12\" class=\"node\"><title>11</title>\r\n",
"<polygon fill=\"none\" stroke=\"black\" points=\"432.5,-253.5 334.5,-253.5 334.5,-200.5 432.5,-200.5 432.5,-253.5\"/>\r\n",
"<text text-anchor=\"middle\" x=\"383.5\" y=\"-238.3\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">gini = 0.0</text>\r\n",
"<text text-anchor=\"middle\" x=\"383.5\" y=\"-223.3\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">samples = 30</text>\r\n",
"<text text-anchor=\"middle\" x=\"383.5\" y=\"-208.3\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">value = [0, 30]</text>\r\n",
"</g>\r\n",
"<!-- 10&#45;&gt;11 -->\r\n",
"<g id=\"edge11\" class=\"edge\"><title>10&#45;&gt;11</title>\r\n",
"<path fill=\"none\" stroke=\"black\" d=\"M383.5,-296.884C383.5,-286.326 383.5,-274.597 383.5,-263.854\"/>\r\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"387,-263.52 383.5,-253.52 380,-263.52 387,-263.52\"/>\r\n",
"</g>\r\n",
"<!-- 12 -->\r\n",
"<g id=\"node13\" class=\"node\"><title>12</title>\r\n",
"<polygon fill=\"none\" stroke=\"black\" points=\"542,-253.5 451,-253.5 451,-200.5 542,-200.5 542,-253.5\"/>\r\n",
"<text text-anchor=\"middle\" x=\"496.5\" y=\"-238.3\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">gini = 0.0</text>\r\n",
"<text text-anchor=\"middle\" x=\"496.5\" y=\"-223.3\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">samples = 3</text>\r\n",
"<text text-anchor=\"middle\" x=\"496.5\" y=\"-208.3\" font-family=\"Times New Roman,serif\" font-size=\"14.00\">value = [3, 0]</text>\r\n",
"</g>\r\n",
"<!-- 10&#45;&gt;12 -->\r\n",
"<g id=\"edge12\" class=\"edge\"><title>10&#45;&gt;12</title>\r\n",
"<path fill=\"none\" stroke=\"black\" d=\"M420.188,-296.884C433.103,-285.226 447.599,-272.141 460.46,-260.532\"/>\r\n",
"<polygon fill=\"black\" stroke=\"black\" points=\"463.15,-262.819 468.228,-253.52 458.46,-257.622 463.15,-262.819\"/>\r\n",
"</g>\r\n",
"</g>\r\n",
"</svg>\r\n"
],
"text/plain": [
"<graphviz.files.Source at 0x22d67bed7b8>"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"graphviz.Source(dot_graph)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -0,0 +1,206 @@
import numpy as np
import pandas as pd
from collections import Counter
import math
class Node:
def __init__(self, x=None, label=None, y=None, data=None):
self.label = label # label:子节点分类依据的特征
self.x = x # x:特征
self.child = [] # child:子节点
self.y = y # y:类标记(叶节点才有)
self.data = data # data:包含数据(叶节点才有)
def append(self, node): # 添加子节点
self.child.append(node)
def predict(self, features): # 预测数据所述类
if self.y is not None:
return self.y
for c in self.child:
if c.x == features[self.label]:
return c.predict(features)
def printnode(node, depth=0): # 打印树所有节点
if node.label is None:
print(depth, (node.label, node.x, node.y, len(node.data)))
else:
print(depth, (node.label, node.x))
for c in node.child:
printnode(c, depth+1)
class DTree:
def __init__(self, epsilon=0, alpha=0): # 预剪枝、后剪枝参数
self.epsilon = epsilon
self.alpha = alpha
self.tree = Node()
def prob(self, datasets): # 求概率
datalen = len(datasets)
labelx = set(datasets)
p = {l: 0 for l in labelx}
for d in datasets:
p[d] += 1
for i in p.items():
p[i[0]] /= datalen
return p
def calc_ent(self, datasets): # 求熵
p = self.prob(datasets)
ent = sum([-v * math.log(v, 2) for v in p.values()])
return ent
def cond_ent(self, datasets, col): # 求条件熵
labelx = set(datasets.iloc[col])
p = {x: [] for x in labelx}
for i, d in enumerate(datasets.iloc[-1]):
p[datasets.iloc[col][i]].append(d)
return sum([self.prob(datasets.iloc[col])[k] * self.calc_ent(p[k]) for k in p.keys()])
def info_gain_train(self, datasets, datalabels): # 求信息增益(互信息)
#print('----信息增益----')
datasets = datasets.T
ent = self.calc_ent(datasets.iloc[-1])
gainmax = {}
for i in range(len(datasets) - 1):
cond = self.cond_ent(datasets, i)
#print(datalabels[i], ent - cond)
gainmax[ent - cond] = i
m = max(gainmax.keys())
return gainmax[m], m
def train(self, datasets, node):
labely = datasets.columns[-1]
if len(datasets[labely].value_counts()) == 1:
node.data = datasets[labely]
node.y = datasets[labely][0]
return
if len(datasets.columns[:-1]) == 0:
node.data = datasets[labely]
node.y = datasets[labely].value_counts().index[0]
return
gainmaxi, gainmax = self.info_gain_train(datasets, datasets.columns)
#print('选择特征:', gainmaxi)
if gainmax <= self.epsilon: # 若信息增益(互信息)为0意为输入特征x完全相同而标签y相反
node.data = datasets[labely]
node.y = datasets[labely].value_counts().index[0]
return
vc = datasets[datasets.columns[gainmaxi]].value_counts()
for Di in vc.index:
node.label = gainmaxi
child = Node(Di)
node.append(child)
new_datasets = pd.DataFrame([list(i) for i in datasets.values if i[gainmaxi]==Di], columns=datasets.columns)
self.train(new_datasets, child)
def fit(self, datasets):
self.train(datasets, self.tree)
def findleaf(self, node, leaf): # 找到所有叶节点
for t in node.child:
if t.y is not None:
leaf.append(t.data)
else:
for c in node.child:
self.findleaf(c, leaf)
def findfather(self, node, errormin):
if node.label is not None:
cy = [c.y for c in node.child]
if None not in cy: # 全是叶节点
childdata = []
for c in node.child:
for d in list(c.data):
childdata.append(d)
childcounter = Counter(childdata)
old_child = node.child # 剪枝前先拷贝一下
old_label = node.label
old_y = node.y
old_data = node.data
node.label = None # 剪枝
node.y = childcounter.most_common(1)[0][0]
node.data = childdata
error = self.c_error()
if error <= errormin: # 剪枝前后损失比较
errormin = error
return 1
else:
node.child = old_child # 剪枝效果不好,则复原
node.label = old_label
node.y = old_y
node.data = old_data
else:
re = 0
i = 0
while i < len(node.child):
if_re = self.findfather(node.child[i], errormin) # 若剪过枝,则其父节点要重新检测
if if_re == 1:
re = 1
elif if_re == 2:
i -= 1
i += 1
if re:
return 2
return 0
def c_error(self): # 求C(T)
leaf = []
self.findleaf(self.tree, leaf)
leafnum = [len(l) for l in leaf]
ent = [self.calc_ent(l) for l in leaf]
print("Ent:", ent)
error = self.alpha*len(leafnum)
for l, e in zip(leafnum, ent):
error += l*e
print("C(T):", error)
return error
def cut(self, alpha=0): # 剪枝
if alpha:
self.alpha = alpha
errormin = self.c_error()
self.findfather(self.tree, errormin)
datasets = np.array([['青年', '否', '否', '一般', '否'],
['青年', '否', '否', '好', '否'],
['青年', '是', '否', '好', '是'],
['青年', '是', '是', '一般', '是'],
['青年', '否', '否', '一般', '否'],
['中年', '否', '否', '一般', '否'],
['中年', '否', '否', '好', '否'],
['中年', '是', '是', '好', '是'],
['中年', '否', '是', '非常好', '是'],
['中年', '否', '是', '非常好', '是'],
['老年', '否', '是', '非常好', '是'],
['老年', '否', '是', '好', '是'],
['老年', '是', '否', '好', '是'],
['老年', '是', '否', '非常好', '是'],
['老年', '否', '否', '一般', '否'],
['青年', '否', '否', '一般', '是']]) # 在李航原始数据上多加了最后这行数据,以便体现剪枝效果
datalabels = np.array(['年龄', '有工作', '有自己的房子', '信贷情况', '类别'])
train_data = pd.DataFrame(datasets, columns=datalabels)
test_data = ['老年', '否', '否', '一般']
dt = DTree(epsilon=0) # 可修改epsilon查看预剪枝效果
dt.fit(train_data)
print('DTree:')
printnode(dt.tree)
y = dt.tree.predict(test_data)
print('result:', y)
dt.cut(alpha=0.5) # 可修改正则化参数alpha查看后剪枝效果
print('DTree:')
printnode(dt.tree)
y = dt.tree.predict(test_data)
print('result:', y)
@@ -0,0 +1,28 @@
digraph Tree {
node [shape=box] ;
0 [label="X[0] <= 5.45\ngini = 0.5\nsamples = 70\nvalue = [35, 35]"] ;
1 [label="X[1] <= 2.85\ngini = 0.234\nsamples = 37\nvalue = [32, 5]"] ;
0 -> 1 [labeldistance=2.5, labelangle=45, headlabel="True"] ;
2 [label="X[0] <= 4.7\ngini = 0.32\nsamples = 5\nvalue = [1, 4]"] ;
1 -> 2 ;
3 [label="gini = 0.0\nsamples = 1\nvalue = [1, 0]"] ;
2 -> 3 ;
4 [label="gini = 0.0\nsamples = 4\nvalue = [0, 4]"] ;
2 -> 4 ;
5 [label="X[0] <= 5.35\ngini = 0.061\nsamples = 32\nvalue = [31, 1]"] ;
1 -> 5 ;
6 [label="gini = 0.0\nsamples = 28\nvalue = [28, 0]"] ;
5 -> 6 ;
7 [label="X[1] <= 3.2\ngini = 0.375\nsamples = 4\nvalue = [3, 1]"] ;
5 -> 7 ;
8 [label="gini = 0.0\nsamples = 1\nvalue = [0, 1]"] ;
7 -> 8 ;
9 [label="gini = 0.0\nsamples = 3\nvalue = [3, 0]"] ;
7 -> 9 ;
10 [label="X[1] <= 3.45\ngini = 0.165\nsamples = 33\nvalue = [3, 30]"] ;
0 -> 10 [labeldistance=2.5, labelangle=-45, headlabel="False"] ;
11 [label="gini = 0.0\nsamples = 30\nvalue = [0, 30]"] ;
10 -> 11 ;
12 [label="gini = 0.0\nsamples = 3\nvalue = [3, 0]"] ;
10 -> 12 ;
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,122 @@
import math
from copy import deepcopy
class MaxEntropy:
def __init__(self, EPS=0.005):
self._samples = []
self._Y = set() # 标签集合,相当去去重后的y
self._numXY = {} # key为(x,y)value为出现次数
self._N = 0 # 样本数
self._Ep_ = [] # 样本分布的特征期望值
self._xyID = {} # key记录(x,y),value记录id号
self._n = 0 # 特征键值(x,y)的个数
self._C = 0 # 最大特征数
self._IDxy = {} # key为(x,y)value为对应的id号
self._w = []
self._EPS = EPS # 收敛条件
self._lastw = [] # 上一次w参数值
def loadData(self, dataset):
self._samples = deepcopy(dataset)
for items in self._samples:
y = items[0]
X = items[1:]
self._Y.add(y) # 集合中y若已存在则会自动忽略
for x in X:
if (x, y) in self._numXY:
self._numXY[(x, y)] += 1
else:
self._numXY[(x, y)] = 1
self._N = len(self._samples)
self._n = len(self._numXY)
self._C = max([len(sample)-1 for sample in self._samples])
self._w = [0]*self._n
self._lastw = self._w[:]
self._Ep_ = [0] * self._n
for i, xy in enumerate(self._numXY): # 计算特征函数fi关于经验分布的期望
self._Ep_[i] = self._numXY[xy]/self._N
self._xyID[xy] = i
self._IDxy[i] = xy
def _Zx(self, X): # 计算每个Z(x)值
zx = 0
for y in self._Y:
ss = 0
for x in X:
if (x, y) in self._numXY:
ss += self._w[self._xyID[(x, y)]]
zx += math.exp(ss)
return zx
def _model_pyx(self, y, X): # 计算每个P(y|x)
zx = self._Zx(X)
ss = 0
for x in X:
if (x, y) in self._numXY:
ss += self._w[self._xyID[(x, y)]]
pyx = math.exp(ss)/zx
return pyx
def _model_ep(self, index): # 计算特征函数fi关于模型的期望
x, y = self._IDxy[index]
ep = 0
for sample in self._samples:
if x not in sample:
continue
pyx = self._model_pyx(y, sample)
ep += pyx/self._N
return ep
def _convergence(self): # 判断是否全部收敛
for last, now in zip(self._lastw, self._w):
if abs(last - now) >= self._EPS:
return False
return True
def predict(self, X): # 计算预测概率
Z = self._Zx(X)
result = {}
for y in self._Y:
ss = 0
for x in X:
if (x, y) in self._numXY:
ss += self._w[self._xyID[(x, y)]]
pyx = math.exp(ss)/Z
result[y] = pyx
return result
def train(self, maxiter=1000): # 训练数据
for loop in range(maxiter): # 最大训练次数
print("iter:%d" % loop)
self._lastw = self._w[:]
for i in range(self._n):
ep = self._model_ep(i) # 计算第i个特征的模型期望
self._w[i] += math.log(self._Ep_[i]/ep)/self._C # 更新参数
print("w:", self._w)
if self._convergence(): # 判断是否收敛
break
dataset = [['no', 'sunny', 'hot', 'high', 'FALSE'],
['no', 'sunny', 'hot', 'high', 'TRUE'],
['yes', 'overcast', 'hot', 'high', 'FALSE'],
['yes', 'rainy', 'mild', 'high', 'FALSE'],
['yes', 'rainy', 'cool', 'normal', 'FALSE'],
['no', 'rainy', 'cool', 'normal', 'TRUE'],
['yes', 'overcast', 'cool', 'normal', 'TRUE'],
['no', 'sunny', 'mild', 'high', 'FALSE'],
['yes', 'sunny', 'cool', 'normal', 'FALSE'],
['yes', 'rainy', 'mild', 'normal', 'FALSE'],
['yes', 'sunny', 'mild', 'normal', 'TRUE'],
['yes', 'overcast', 'mild', 'high', 'TRUE'],
['yes', 'overcast', 'hot', 'normal', 'FALSE'],
['no', 'rainy', 'mild', 'high', 'TRUE']]
maxent = MaxEntropy()
x = ['overcast', 'mild', 'high', 'FALSE']
maxent.loadData(dataset)
maxent.train()
print('predict:', maxent.predict(x))
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,257 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"原文代码作者:https://github.com/wzyonggege/statistical-learning-method\n",
"\n",
"中文注释制作:机器学习初学者(微信公众号:ID:ai-start-com)\n",
"\n",
"配置环境:python 3.6\n",
"\n",
"代码全部测试通过。\n",
"![gongzhong](../gongzhong.jpg)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 第9章 EM算法及其推广"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Expectation Maximization algorithm\n",
"\n",
"### Maximum likehood function\n",
"\n",
"[likehood & maximum likehood](http://fangs.in/post/thinkstats/likelihood/)\n",
"\n",
"> 在统计学中,似然函数(likelihood function,通常简写为likelihood,似然)是一个非常重要的内容,在非正式场合似然和概率(Probability)几乎是一对同义词,但是在统计学中似然和概率却是两个不同的概念。概率是在特定环境下某件事情发生的可能性,也就是结果没有产生之前依据环境所对应的参数来预测某件事情发生的可能性,比如抛硬币,抛之前我们不知道最后是哪一面朝上,但是根据硬币的性质我们可以推测任何一面朝上的可能性均为50%,这个概率只有在抛硬币之前才是有意义的,抛完硬币后的结果便是确定的;而似然刚好相反,是在确定的结果下去推测产生这个结果的可能环境(参数),还是抛硬币的例子,假设我们随机抛掷一枚硬币1,000次,结果500次人头朝上,500次数字朝上(实际情况一般不会这么理想,这里只是举个例子),我们很容易判断这是一枚标准的硬币,两面朝上的概率均为50%,这个过程就是我们运用出现的结果来判断这个事情本身的性质(参数),也就是似然。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"$$P(Y|\\theta) = \\prod[\\pi p^{y_i}(1-p)^{1-y_i}+(1-\\pi) q^{y_i}(1-q)^{1-y_i}]$$\n",
"\n",
"### E step:\n",
"\n",
"$$\\mu^{i+1}=\\frac{\\pi (p^i)^{y_i}(1-(p^i))^{1-y_i}}{\\pi (p^i)^{y_i}(1-(p^i))^{1-y_i}+(1-\\pi) (q^i)^{y_i}(1-(q^i))^{1-y_i}}$$"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import math"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"pro_A, pro_B, por_C = 0.5, 0.5, 0.5\n",
"\n",
"def pmf(i, pro_A, pro_B, por_C):\n",
" pro_1 = pro_A * math.pow(pro_B, data[i]) * math.pow((1-pro_B), 1-data[i])\n",
" pro_2 = pro_A * math.pow(pro_C, data[i]) * math.pow((1-pro_C), 1-data[i])\n",
" return pro_1 / (pro_1 + pro_2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### M step:\n",
"\n",
"$$\\pi^{i+1}=\\frac{1}{n}\\sum_{j=1}^n\\mu^{i+1}_j$$\n",
"\n",
"$$p^{i+1}=\\frac{\\sum_{j=1}^n\\mu^{i+1}_jy_i}{\\sum_{j=1}^n\\mu^{i+1}_j}$$\n",
"\n",
"$$q^{i+1}=\\frac{\\sum_{j=1}^n(1-\\mu^{i+1}_jy_i)}{\\sum_{j=1}^n(1-\\mu^{i+1}_j)}$$"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"class EM:\n",
" def __init__(self, prob):\n",
" self.pro_A, self.pro_B, self.pro_C = prob\n",
" \n",
" # e_step\n",
" def pmf(self, i):\n",
" pro_1 = self.pro_A * math.pow(self.pro_B, data[i]) * math.pow((1-self.pro_B), 1-data[i])\n",
" pro_2 = (1 - self.pro_A) * math.pow(self.pro_C, data[i]) * math.pow((1-self.pro_C), 1-data[i])\n",
" return pro_1 / (pro_1 + pro_2)\n",
" \n",
" # m_step\n",
" def fit(self, data):\n",
" count = len(data)\n",
" print('init prob:{}, {}, {}'.format(self.pro_A, self.pro_B, self.pro_C))\n",
" for d in range(count):\n",
" _ = yield\n",
" _pmf = [self.pmf(k) for k in range(count)]\n",
" pro_A = 1/ count * sum(_pmf)\n",
" pro_B = sum([_pmf[k]*data[k] for k in range(count)]) / sum([_pmf[k] for k in range(count)])\n",
" pro_C = sum([(1-_pmf[k])*data[k] for k in range(count)]) / sum([(1-_pmf[k]) for k in range(count)])\n",
" print('{}/{} pro_a:{:.3f}, pro_b:{:.3f}, pro_c:{:.3f}'.format(d+1, count, pro_A, pro_B, pro_C))\n",
" self.pro_A = pro_A\n",
" self.pro_B = pro_B\n",
" self.pro_C = pro_C\n",
" "
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"data=[1,1,0,1,0,0,1,0,1,1]"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"init prob:0.5, 0.5, 0.5\n"
]
}
],
"source": [
"em = EM(prob=[0.5, 0.5, 0.5])\n",
"f = em.fit(data)\n",
"next(f)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1/10 pro_a:0.500, pro_b:0.600, pro_c:0.600\n"
]
}
],
"source": [
"# 第一次迭代\n",
"f.send(1)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2/10 pro_a:0.500, pro_b:0.600, pro_c:0.600\n"
]
}
],
"source": [
"# 第二次\n",
"f.send(2)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"init prob:0.4, 0.6, 0.7\n"
]
}
],
"source": [
"em = EM(prob=[0.4, 0.6, 0.7])\n",
"f2 = em.fit(data)\n",
"next(f2)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1/10 pro_a:0.406, pro_b:0.537, pro_c:0.643\n"
]
}
],
"source": [
"f2.send(1)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2/10 pro_a:0.406, pro_b:0.537, pro_c:0.643\n"
]
}
],
"source": [
"f2.send(2)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
}