PAC学习框架

几个重要不等式
1. 马尔科夫不等式:设X为非负随机变量,满足\(E[X]< \infty\), 则\[P(X\ge t\cdot E[X])\le\frac 1 t,\forall t > 0.\]
2. 切比雪夫不等式:设 \(Var[X]<\infty\),标准差 \(\sigma_X=\sqrt{Var[X]}\),则\[P(|X-E[X]|\ge t\sigma_X)\le\frac{1}{t^2},\forall t > 0.\]
3. Chernoff界:对任意 \( t>0, \epsilon>0,\)\[P(X\ge\epsilon) \le e^{-t\epsilon}E[e^{tX}]\]
证明:因为\(P(X\ge \epsilon) = P(e^{tX}\ge e^{t\epsilon})\),由马尔可夫不等式知\(P(e^{tX}\ge e^{t\epsilon})\le \frac{1}{e^{t\epsilon}}E[e^{tX}]\),代入即得。

4. Hoeffding引理:设\(E[X]=0, a\le X\le b\). 则\[E[e^{tX}]\le \exp \{\frac{t^2(b-a)^2}{8}\}\]

5. Hoeffding不等式:设\(X_1,\cdots, X_m\)为一组独立随机变量,满足\(a_i\le X_i\le b_i\).
令\(S=\sum\limits_{1}^m X_i\),则对任意\(\epsilon > 0\),有
(1) \[P(S-E[S]\ge\epsilon)\le \exp\{-\frac{2\epsilon^2}{\sum\limits_{i=1}^m (b_i-a_i)^2}\}.\](2)\[P(S-E[S]\le - \epsilon)\le \exp\{-\frac{2\epsilon^2}{\sum\limits_{i=1}^m (b_i-a_i)^2}\}.\]推论(1) \[P(|S-E[S]|\ge\epsilon)\le 2\exp\{-\frac{2\epsilon^2}{\sum\limits_{i=1}^m (b_i-a_i)^2}\}.\](2) 如果\(b_i-a_i=1\),则\[P(\frac{|S-E[S]|}{m}\ge\epsilon)\le 2\exp\{-{2m\epsilon^2}\}.\]
证明:由Chernoff界,\(P(S-E[S]\ge\epsilon)\le e^{-t\epsilon} E[e^{t(S-E[S])}]\)。
利用独立性展开\(E[e^{t(S-E[S])}]=\prod\limits_{i=1}^m E[e^{t(X_i-E[X_i])}]\)。
由Hoeffding引理得\(E[e^{t(X_i-E[X_i])}]\le \exp\{\frac{t^2(b_i-a_i)^2}{8}\}\)。
最后得到一关于\(t\)的二次函数。选择\(t=\frac{4\epsilon}{\sum(b_i-a_i)}\)即得。

PAC学习模型
设有输入空间\(\mathcal X\)有隐含分布\(\mathcal D\),标签集合\(\mathcal Y = \{0,1\}\)。设概念\(c:\mathcal X\to \mathcal Y\)是我们希望学习的目标,所有可能学习的概念集合为一假设空间\(H\),对\(H\)中每个假设\(h:\mathcal X\to\mathcal Y\),其泛化误差定义为\[R(h) = P_{x\sim\mathcal D}(h(x)\ne c(x))=E_{x\sim\mathcal D}\mathbb I (h(x)\ne c(x)).\]
对于样本集\(S=\{x_i\}_{i=1}^m\),\(h\)的经验误差(经验风险)定义为\[\hat R(h) = \frac{1}{m}\sum\limits_{i=1}^m \mathbb I (h(x)\ne c(x)).\]
PAC(Probably Approximately Correct)可学习:称目标概念类\(C\)是PAC可学习的,如果存在一个学习算法\(\mathcal A\)和一个多项式函数\(p(\cdot,\cdot,\cdot,\cdot)\),满足:对任意的\(\epsilon<1, \delta >0,\mathcal X\)上任一分布\(\mathcal D\),任一目标概念\(c\in C\),任一样本容量\(m\ge p(\frac 1 \epsilon,\frac 1 \delta, size(x) ,size(c))\)成立:\[P_{S\sim\mathcal D^m}(R(h_S)\le \epsilon )\ge 1-\delta\]

    发表于2022-06-07

基于近邻的分类方法

k-近邻法
对于新输入数据x,从训练样本集中找出k个与x最相近的样本,然后通过这k个样本的标记进行投票对x的标记给出预测。
可以采用Minkowski距离度量相似性:\[dist_p(x,y) = \lVert x-y\rVert_p = (\sum\limits_{i=1}^n |x^i-y^i|^p)^{\frac{1}{p}}\]
当\(p=1,2,\infty\)时,分别为曼哈顿距离、欧氏距离和切比雪夫距离。
如果各个维度特征的重要性不同,可以在\(|x^i-y^i|^p\)前加权重\(w_i\). 一般要求\(\sum w_i=1.\)

如果k值太小,则对噪声敏感;若k值太大,则与x无关的样本也会影响分类。一般从较小的k开始尝试,选择在验证集上错误率最小的k值。
特别地,\(k=1\)时为最近邻法。
在高维特征空间中,总能找到距离x任意小的样本比较困难。故最近邻法适合低维特征空间的分类任务。

基于K-means的分类方法
给定训练集\(D=\{(x_i,y_i)\}\). 用\(D_i\)表示属于类\(c_i\)的样本,\(i=1,2,\cdots,m.\)
K-means方法将每个\(D_i\)划分成K个单元,以属于每个单元的训练样本的特征向量均值作为该单元代表。
K-means方法的目标是寻找一个D的划分使得数据分布的方差最小,即\[(D_{i,1},\cdots,D_{i,K})=\arg\min\sum_{j=1}^K\sum_{(x_t,y_t)\in D_{i,j}}\lVert x_t - c_{i,j}\rVert _2^2.\]
一般采取近似划分方法:
1. 选择K个点\(c_{i,1},\cdots,c_{i,K}\)作为初始点;
2. 对每个\((x_t, y_t)\in D_i\), 选择与\(x_t\)最近的\(c_{i,j}\),将\(x_t\)划分进对应的\(D_{i,j}\);
3. 此时根据已有的一组\(D_{i,j}\)更心其对应的均值\(c_{i,j}\);
4. 重复2-3,直到收敛。
对于新输入的数据点\(x\),使用最近邻法,利用得到的\(M\times K\)个代表点进行分类。

学习向量量化方法
对每个类别\(c_m\)构建K个代表点,遵循代表点靠近同类样本且远离异类样本的原则来调整。
1. 对每个类随机选择K个点\(I_{m,1},\cdots, I_{m,k}\)作为其代表点向量的初始值;
2. 随机选择一个训练样本\((x,y)\in D\), 找到与\(x\)最近的代表点\(I_{m^*k^*}\).
3. 如果\(y=m^*\), 对\(I_{m^*k^*}\)如下更新:\[I_{m^*k^*}\leftarrow I_{m^*k^*} + \eta(x - I_{m^*k^*})\]否则:\[I_{m^*k^*}\leftarrow I_{m^*k^*} - \eta(x - I_{m^*k^*})\]4. 重复2-3直到收敛。
与K-means类似,对于新输入数据,采用最近邻法预测其类别。

    发表于2022-05-21

贝叶斯分类器

后验概率最大化分类准则
以条件概率分布\(P(Y|X)\)而非决策函数\(f(x)\)(如支持向量机)为模型的分类方法。一般地,先从训练集\(T=\{(x_i,y_i)\}\)学得条件概率分布\(P(Y|X)\),再对新数据x按照后验概率最大化原则确定预测\(\hat y\),即\[\hat y = \arg\max_{y\in \mathbb Y} P(y|x).\]约定\(|D|=N, x_i\in \mathbb R^n, \mathbb Y =\{c_1,\cdots,c_K\}\).

将x的类别预测为\(c_i\)的期望损失为\[R(Y=c_i|x)=\sum_{j=1}^K \lambda_{ij}\cdot P(Y=c_j|x),\] 其中\(\lambda_{ij}\)是把属于\(c_j\)的样本判定为\(c_i\)的损失。
根据贝叶斯决策论,最优预测应满足\[\hat y =\arg\min_{c_i} R(Y=c_i|x).\]
对于给定x可以直接建模\(P(c|x)\)来预测c,这样的模型称为判别式模型;对于先对联合概率分布\(P(x,c)\)建模,再由此获得\(P(c|x)\), 这样得到的是生成式模型。根据贝叶斯公式:\[P(Y=c_i|x) = \frac{P(x|Y=c_i)\cdot P(Y=c_i)}{\sum\limits_{k=1}^K P(x|Y=c_k)\cdot P(Y=c_k)}\]先由训练样本集学得先验概率分布\(P(Y)\)和条件概率分布\(P(X|Y)\). 然后即可计算\(P(c_i|x)\).

逻辑斯蒂回归模型
逻辑斯蒂回归模型直接以参数形式给出条件概率分布\(P(Y|X)\).

对于二项逻辑斯蒂回归模型,即\(\mathbb Y = \{c_1,c_2\}\), 二项逻辑斯蒂回归模型是如下的后验概率分布:\[P(c_1|x) = \frac{\exp(w\cdot x + b)}{1+\exp(w\cdot x + b)}\]\[P(c_2|x) = \frac{1}{1+\exp(w\cdot x + b)}\]对于输入实例x, 二项逻辑斯蒂回归按照后验概率最大化原则对x进行分类,即\[y = \arg\max_{c_i} P(c_i|x)\]这等价于\[y=c_1\Leftrightarrow w\cdot x + b > 0.\]

对于多分类任务\(\mathbb Y = \{c_1,\cdots,c_K\}\), 多项逻辑斯蒂回归模型即\[P(c_k|x) = \frac{\exp(w_k\cdot x + b_k)}{1+\sum\limits_{k=1}^{K-1}\exp(w_k\cdot x + b_k)}\quad (1\le k \le K-1)\]而对于\(c_K\)分子为1.

参数的极大似然估计:假定\(\mathbb Y = \{0,1\}\), 用\(\theta=(w,b)\)表示参数,令\[p(x;\theta) = P(Y=1|x)\]则似然函数为\[L(\theta) = \prod_{i=1}^N p(x_i;\theta)^{y_i}\cdot (1-p(x_i;\theta))^{1-y_i}\]转化为对数似然函数后可采用梯度下降等方法求解。

朴素贝叶斯分类器
基于贝叶斯公式估计后验概率的主要困难在于类条件概率\(P(x|c)\)是所有属性上的联合概率,难以直接估计。为此,朴素贝叶斯分类器采用了属性条件独立性假设。设第j维特征\(X^j\)的可能取值为\(a_1^j,\cdots,a_{m_j}^j\), 则先验概率分布的极大似然估计为\[\hat P(Y=c_k) = \frac{\sum\limits_{i=1}^N \mathbb I(y_i=c_k)}{N}\]对于每个类\(c_k\)及第j维特征,条件概率分布的极大似然估计为\[\hat P(X^j=a_l^j|Y=c_k) = \frac{\sum\limits_{i=1}^N \mathbb I(x_i^j=a_l^j, y_i=c_k)}{\sum\limits_{i=1}^N \mathbb I (Y_i=c_k)}.\]
对于新的输入\(x=(x^1,\cdots, x^n)\), 后验概率\[\hat P(Y=c_k|x)=\frac{(\prod\limits_i^n \hat P(X^i=x^i|Y=c_k))\hat P(Y=c_k)}{\sum\limits_l^K((\prod\limits_i^n \hat P(X^i=x^i|Y=c_l))\hat P(Y=c_l))}\]由于所有类别的分母项相同,故实际预测时取分子最大的一类即可。

注意到某维特征的取值\(X^i=x^i\)和类别\(c_k\)可能没有同时在训练样本中出现,导致其极大似然估计为0. 采用贝叶斯估计可以避免这种情况。贝叶斯估计通过对频数加以常数\(\lambda\ge 0\)来进行平滑,其中\(\lambda=1\)时称为Laplace平滑:\[\hat P_\lambda(Y=c_k) = \frac{\sum\limits_{j=1}^N \mathbb I(y_i=c_k) + \lambda}{N+K\lambda}\]条件概率分布的贝叶斯估计为\[\hat P_\lambda(X^j=a_l^j|Y=c_k) = \frac{\sum\limits_{i=1}^N \mathbb I(x_i^j=a_l^j, y_i=c_k)+\lambda}{\sum\limits_{i=1}^N \mathbb I (Y_i=c_k)+m_j\lambda}.\]

    发表于2022-05-19

决策树模型

特征选择
约定记号:训练集\(D=\{(x_i,y_i)\}_{i=1}^N\), 样本 \(x_i\in\mathbb R^n\), 标签 \(y\in Y=\{c_1,c_2,\cdots,c_K\}\)。

经验熵:将D划分成\(D_1,\cdots, D_K\), 每一子集对应一类标签。设子集\(D_i\)在D中占比为\(p_i=\frac{|D_i|}{|D|}\), 定义经验熵\(H(D)\)为\[H(D)=-\sum\limits_{i=1}^K p_i\log_2 p_i.\] 经验熵刻画了数据集D的纯度。

条件经验熵:给定某维特征A及其取值范围\(\{a_1,\cdots,a_m\}\), 按照特征A也可将D划分为m个子集\(D_1^A,\cdots, D_m^A\).
进一步考察\(D_i^A\)中的分类情况,用\(D_{ik}^A\)表示其中标签为k的子集,则\(D_i^A\)的经验熵为\[H(D_i^A)=-\sum_{k=1}^K \frac{|D_{ik}^A|}{|D_i^A|} \log_2 \frac{|D_{ik}^A|}{|D_i^A|}. \]
用基于特征A划分的子集的平均经验熵,即给定条件A下,D的条件经验熵为\[H(D|A)=\sum_{i=1}^m \frac{|D_i^A|}{|D|}H(D_i^A).\]可以视为给定特征A的条件下,数据集D的纯度度量。

信息增益:定义\[G(D,A)=H(D) - H(D|A).\]刻画使用特征A来划分使得数据集D在纯度上的提升。信息增益越高,说明特征A为分类提供了更多的有效信息,刻画了特征A的分类能力。

使用信息增益准则对训练集中数量较多的属性有所偏好,可以用信息增益率进行修正。
信息增益率:特征A的分裂信息(又称固有值)\(IV(D,A)\)定义为\[IV(D,A)=-\sum_{i=1}^m \frac{|D_i^A|}{|D|}\log_2\frac{|D_i^A|}{|D|},\]并定义信息增益率\(G_R(D,A)\)为\[G_R(D,A)=\frac{G(D,A)}{IV(D,A)}.\]
基尼指数:数据集的基尼指数Gini(D)定义为\[Gini(D) = 1-\sum_{k=1}^K(\frac{|D_k|}{|D|})^2.\]其中平方项反映了从D中随机抽取两个样本,都属于\(c_k\)的概率。基尼指数可以看作从数据集中随机抽取两个样本,其标签不一致的概率。

根据基尼指数划分:特征A的基尼指数定义为\[Gini(D,A)=\sum_{i=1}^m \frac{|D_i^A|}{|D|} Gini(D_i^A).\]Gini(D, A)越小,说明同一\(D_i^A\)内的标签一致性更高,A的分类能力更强。

决策树学习算法
输入训练集D,特征集合\(\mathbb A\)和特征选择函数\(F(\cdot,\cdot)\),输出决策树T。
1. 首先解决平凡情况:(1)D中样本属于同一类别;(2)\(\mathbb A=\emptyset\)。 返回单结点树,并以D中样本最多的类别作为标记。
2. 计算\(A^*=F(D,\mathbb A)\)得到选择的特征, 对于\(A^*\)的每个可能取值\(a_i\),构造一个对应于\(D_i\)的子结点。
3. 若\(D_i=\emptyset\), 以D中最多的类别作为标记,并标记为叶子结点。
4. 对每个\(D_i\)对应的非叶结点,以\(D_i\)为训练集,\(\mathbb A -\{A^*\}\)为特征集,递归调用决策树算法,得到子树\(T_i\)。

决策树剪枝
剪枝能够降低决策树模型的过拟合风险,分为预剪枝和后剪枝。
预剪枝是在生成决策树过程中,如果新展开的结点在验证机上不能带来性能提升,则放弃展开此结点。后剪枝也类似,在构造好的决策树上由下至上分析每个结点。

后剪枝也可采用正则化方法。
定义决策树T的损失函数:\[C_\alpha(T)=C(T)+\alpha|T|.\]其中\(C(T)\)是评估T对D的拟合程度的函数,\(|T|\)是T的叶结点个数作为T的模型复杂度。\(C(T)\)可以定义为:\[C(T) = \sum_{t=1}^{|T|} N_tH_T(T),\]\(N_t, H_t(T)\)分别表示叶结点t对应的数据集大小和经验熵。

最小二乘回归树
使用平方误差最小准则来选择属性和最优切分点。
给定数据集\(D=\{(x_i,y_i)\}\),其中\(y_i\in\mathbb R\).
设回归树模型将输入空间划分成M个单元\(R_1,\cdots,R_M\),使得单元\(R_m\)上的输出值为\(c_m\)。则回归树模型的输出可以表示成\[f(x) =\sum_{m=1}^M c_m\cdot\mathbb I(x\in R_m)\]使用均方误差可得\[\hat c_m = \text{ave} \{y_i|x_i\in R_m\}.\]对于选择切分的维度j和切分点s,定义\(R_1(j,s)=\{x|x^j\le s\}, R_2= (j,s)=\{x|x^j > s\}\). 则通过求解\[\min\limits_{j,s}[\min_{c_1}\sum_{x\in R_1} (y_i-c_i)^2 + \min_{c_2}\sum_{x\in R_2} (y_i-c_i)^2]\]即得最优切分维度和切分点\((j,s)\).

    发表于2022-05-19

支持向量机

线性可分支持向量机
约定训练样本集\(D=\{(x_i,y_i)|i=1...N\}\), \(x\in\mathcal X=R^n, y_i\in\mathcal Y =\{\pm 1\}\).

如果存在特征空间\(\mathcal X\)中的超平面\(H:w\cdot x + b = 0\)满足:\[(w\cdot x_i + b ) \cdot y_i >0\]即所有样本能被H正确分类,称D是线性可分的。原点到H的距离为\(\frac{|b|}{\lVert w\rVert}\).
我们用\((w,b)\)表示H。

下面设D线性可分,讨论最优的分离超平面。
令 \(\hat \gamma = \min\limits _{i} y_i \cdot (w\cdot x_i + b)\). 可以对 \((w,b)\) 乘以同一常数,使得\(\hat\gamma=1\).

此时,取得等号成立的点\(y_i\cdot(w\cdot x_i + b)=1\)落在两个超平面之一中:\(w\cdot x_i + b = \pm 1\). 它们到原超平面H的距离恰好是\(\frac{1}{\lVert w\rVert}\). 两个超平面的距离\(\frac{2}{\lVert w\rVert}\)称为间隔。

样本点到超平面的距离表征此点分类预测的置信度,最小者为\(\frac{1}{\lVert w\rVert}\). 最优的分离超平面将优化如下问题:\[\max\limits_{w,b} \frac 1 {\lVert w\rVert}\]\[s.t.\quad y_i\cdot (w\cdot x_i + b)\ge 1\]等价的凸二次规划问题为\[\max\limits_{w,b} \frac 1 2 \lVert w \rVert^2.\]若D线性可分,则此凸二次规划问题的解存在且唯一。

对偶问题
采用拉格朗日乘子法来求此凸二次规划问题:令\[L(w,b,a) = \frac 1 2 \lVert w\rVert^2+\sum\limits _{i=1}^N a_i - \sum\limits_{i=1}^N a_iy_i(w\cdot x_i + b)\]
分别对w和b求偏导等于0可得\[w = \sum\limits_{i=1}^N a_iy_ix_i,\]\[\sum\limits_{i=1}^N a_iy_i=0.\]
代入L得对偶问题\[\max\limits_a \sum_i a_i -\frac 1 2 \sum_i\sum_j a_ia_jy_iy_j\ (x_i\cdot x_j)\] 其约束条件为\[\sum_i a_i y_i =0, a_i\ge 0\]
由KKT条件可得\[1-y_i(w\cdot x_i + b ) \le 0, \quad a_i\ge 0,\]\[a_i\cdot [1-y_i(w\cdot x_i + b)]=0.\]

SMO算法
SMO算法的基本思路是固定某\(a_i\)以外的全部参数,并优化\(a_i\)。但固定n-1个参数时最后一个参数可以直接解出。因此每次选取两个参数进行优化。进一步,SMO算法采取启发式选取,每次选择一个违背KKT条件最严重的变量和一个使目标函数增长最快的变量。
对原约束\(\sum a_iy_i=0\)乘以\(y_1\)得\(a_1+a_2y_1y_2 - \gamma=0\),\(\gamma\)是暂时不考虑的常数,\(s=y_1y_2\). 得\(a_1+sa_2=\gamma\).
转化为优化问题:\[\max_{a_1,a_2} W(a_1,a_2)\]\[s.t.\quad 0\le a_1,a_2\le C, a_1+sa_2=\gamma.\]
线性支持向量机
考虑D不是线性可分的情况。策略:容忍一部分点违反约束\(y_i(w\cdot x_i + b)\ge 1\),但尽可能少。
引入松弛变量\(\xi_i\)使得约束弱化为\(y_i(w\cdot x_i + b)+\xi_i\ge 1.\) 对于\(\xi\ne0\)的点称为特异点。优化问题为:\[\min_{w,b}\ \frac 1 2 \lVert w \rVert ^2 + C\sum_{i=1}^N \xi_i^p\quad (p\ge 1),\]\[s.t.\quad y_i(w\cdot x_i + b)\ge 1-\xi_i, \quad \xi_i\ge0.\]
对于特异点,不妨取\(\xi_i = 1-y_i(w\cdot x_i + b)\). 由此启发,引入合页损失函数\(h(z) =\max(0,\ 1-z)\). 则可以简写\[\xi_i = h(y_i(w\cdot x_i + b))\]得到等价的优化问题\[\min_{w,b} \sum_{i=1}^N h(y_i(w\cdot x_i + b)) + \frac 1 {2C} \lVert w \rVert ^2.\]
合页损失函数h也可以选取0-1损失函数,二次合页损失函数。以上优化问题也可用拉格朗日乘子法求解对偶问题。

核方法与非线性支持向量机
对于非线性边界的问题,可以将原始特征空间\(\mathcal X\)映射到高维特征空间\(\phi:\mathcal X\to \mathcal Z\),使在这个空间内线性可分。超平面变为\(w\cdot\phi(x) + b =0\). 为了减轻计算高维内积的复杂度,引入核技巧:不直接定义\(\phi\),而定义核函数\(K(x_i,x_j) = \phi(x_i)\cdot \phi(x_j).\)

正定对称核函数:\((K(x_i,x_j))_{m\times m}\)是对称半正定矩阵。

    发表于2022-05-10

模型评估与选择

留出法
将数据集D划分为\(D=S\cup T, S\cup T=\emptyset\).

交叉验证法
\(D = D_1\cup D_2\cup\cdots\cup D_k\)
留一法:\(k=|D|\)

自助法
从D中有放回的采样得到\(D'\), \(|D|=|D'|\), 样本在\(m=|D|\)中始终不被采到的概率为\[(1-\frac 1 m)^m\to \frac{1}{e} \approx 0.368\]
用\(D-D'\)作为测试集,约占D中元素的三分之一。

准确率和召回率
对于二分类任务,分为正类P和负类F。
对样本预测结果有4类:真正例TP,假正例FP,真负例TN,假负例FN
查准率\(P=\frac{TP}{TP+FP}\)
查全率\(R=\frac{TP}{TP+FN}\)
\(F_1\)度量:\[\frac 2 {F_1} = \frac 1 P + \frac 1 R\]

偏差-方差分解
对测试集D中的测试样本\(x\), 用\(y,y_D\)分别表示其真实标记和数据集中标记,\(f(x;D)\)为训练集D上学得模型\(f\)在x上的预测输出。
则学习算法的期望预测为\[\bar f(x) = E_D[f(x;D)].\]
使用样本数相同的不同训练集产生的方差为\[var(x) = E_D[(f(x;D)-\bar f(x))^2].\]
Gauss噪声为\[\epsilon^2 = E_D[( y_D - y)^2].\]
期望输出与真实标记的差别称为偏差,即\[bias(x)=\bar f(x) - y.\]
算法的期望泛化误差\[E(f;D) = E_D[(f(x;D)- y_D)^2].\]
Theorem. \[E(f;D) = bias^2(x) +var(x) +\epsilon^2.\]
Proof.
\(E(f;D)\)
\(=E_D[(f(x;D)-y_D)^2]\)
\(=E_D[(f(x;D)-\bar f(x) + \bar f(x) -y_D)^2]\)
\(=E_D[(f(x;D)-\bar f(x) )^2] + E_D[(\bar f(x) - y_D)^2] + E_D [2(f(x;D)-\bar f(x) ) (\bar f(x) -y_D)]\) \\末项为0
\(=var(x) +E_D[(\bar f(x) -y+y- y_D)^2]\)
\(=var(x) + E_D[(\bar f(x) -y)^2] + E_D[(y_D-y)^2]\)\\同上,展开后交叉项的期望为0
\(=var(x) + bias^2(x) +\epsilon^2.\)

    发表于2022-05-03

PyTorch实现岭回归正则化

只需要将优化器进行调整即可:
trainer = torch.optim.SGD([
{"params": net[0].weight, 'weight_decay':wd},
{"params": net[0].bias}
], lr=lr)

其中wd是惩罚超参数,net是训练模型。

完整代码示例:
import torch
from torch import nn
from d2l import torch as d2l
from torch.utils import data
import matplotlib.pyplot as plt
#d2l.synthetic_data()
# 生成含有噪声的数据集的函数
def generate_data(w, b, n):
X = torch.normal(0, 1, size=(n, len(w)))
Y = X @ w+ b
Y += torch.normal(0, 0.01, size=Y.shape)
return X, Y.reshape(-1, 1)

# 读取数据集的函数
def load_array(data_arr, batch_size, isTrain=True):
dataset = data.TensorDataset(*data_arr)
return data.DataLoader(dataset, batch_size, isTrain)

# 定义输入输出规模
n_train, n_test, num_inputs, b_size = 20, 100, 200, 5
true_w, true_b = torch.ones((num_inputs, 1)) * 0.01, 0.05
train_data = generate_data(true_w, true_b, n_train)
train_iter = load_array(train_data, b_size)

test_data = generate_data(true_w, true_b, n_test)
test_iter = load_array(test_data, b_size, False)

train_log = []
test_log = []
#定义训练函数
def train_concise(wd): #wd是惩罚超参数
net = nn.Sequential(nn.Linear(num_inputs, 1))
for param in net.parameters():
param.data.normal_()
loss = nn.MSELoss()
num_epochs, lr = 100, 0.003
trainer = torch.optim.SGD([
{"params": net[0].weight, 'weight_decay':wd},
{"params": net[0].bias}
], lr=lr)
for epoch in range(num_epochs):
for X, y in train_iter:
trainer.zero_grad()
l = loss(net(X),y)
l.mean().backward()
trainer.step()
with torch.no_grad():
train_loss, train_num = evaluate_loss(net, train_iter, loss)
test_loss, test_num = evaluate_loss(net, test_iter, loss)
train_log.append(train_loss/train_num)
test_log.append(test_loss/test_num)
if epoch % 10 == 1:
plt.close()
plt.plot(range(epoch + 1), train_log, color='red')
plt.plot(range(epoch + 1), test_log, color='blue')
plt.show()

def evaluate_loss(net, data_iter, loss):
cnt = 0
total_loss = 0
for X, y in data_iter:
y_hat = net(X)
y.reshape(y_hat.shape)
lost = loss(y_hat, y)
cnt += y.numel()
total_loss += lost
return total_loss, cnt

train_concise(1)

    发表于2022-02-10

PyTorch实现Dropout

在模型中加入
nn.Dropout(d)
其中d是dropout参数,当d=0时所有单元均保留,d=1时所有单元均丢弃。

完整示例代码如下:
import torch
import torchvision
import torch.nn as nn
from torchvision import transforms
import matplotlib.pyplot as plt
from torch.utils import data

# 定义模型参数
n_i, n_h1, n_h2, n_o = 784, 256, 256, 10
dropout1, dropout2 = 0.2, 0.5
# 定义模型
net = nn.Sequential(nn.Flatten(),
nn.Linear(n_i, n_h1),
nn.ReLU(),
nn.Dropout(dropout1),
nn.Linear(n_h1, n_h2),
nn.ReLU(),
nn.Dropout(dropout2),
nn.Linear(n_h2, n_o))
# 初始化模型参数
def init_weights(m):
if m.type == nn.Linear:
nn.init.normal(m.weight, std=0.01)
net.apply(init_weights)

#定义训练参数
num_epochs, lr, batch_size = 100, 0.5, 256

#损失函数和优化器
loss = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(), lr=lr)

#定义单次训练
def epoch(net, train_iter, test_iter, loss, optimizer):
net.train()
train_correct, test_correct = 0, 0
for X, y in train_iter:
y_hat = net(X)
l = loss(y_hat, y)
optimizer.zero_grad()
l.mean().backward()
optimizer.step()
with torch.no_grad():
train_correct += evaluate_accuracy(net, X, y)
with torch.no_grad():
for X, y in test_iter:
test_correct += evaluate_accuracy(net, X, y)
return train_correct, test_correct

# 定义精度检验函数
def evaluate_accuracy(net, X, y):
y_hat = net(X).argmax(axis=1)
grade = y_hat.type(y.dtype) == y
return float(grade.type(y.dtype).sum())

# 读取数据集
def load_data(batch_size):
trans = transforms.ToTensor()
train_set = torchvision.datasets.FashionMNIST(root='../data', train=True, transform=trans, download=True)
test_set = torchvision.datasets.FashionMNIST(root='../data', train=False, transform=trans, download=True)
return data.DataLoader(train_set, batch_size, shuffle=True), data.DataLoader(test_set, batch_size, shuffle=False)

train_iter, test_iter = load_data(batch_size)

# 训练
t1s, t2s = [], []
def train(net, train_iter, test_iter, loss, num_epochs, optimizer):
for epochs in range(num_epochs):
t1, t2 = epoch(net, train_iter, test_iter, loss, optimizer)
print(f'epoch{epochs + 1}: train={t1}, test={t2}')
t1s.append(t1/600)
t2s.append(t2/100)
plt.ylim((0, 100))
plt.plot(range(epochs+1), t1s)
plt.plot(range(epochs+1), t2s)
if epochs % 10 == 1:
plt.show()

train(net, train_iter, test_iter, loss, num_epochs, optimizer)

    发表于2022-02-10

PyTorch实现多层感知机

import torch
import torchvision
from torchvision import transforms
import torch.nn as nn
from torch.utils import data
import matplotlib.pyplot as plt

# 定义模型
i_size, h_size, o_size = 784, 1024, 10
net = nn.Sequential(nn.Flatten(), nn.Linear(i_size, h_size), nn.ReLU(), nn.Linear(h_size, o_size))

# 初始化模型参数
def init_weights(m):
if m.type == nn.Linear: # 只需要填充线性模型
nn.init.normal_(m.weight, std=0.01) # 正态分布注入Tensor

net.apply(init_weights)

# 定义训练参数
b_size, lr, n_epochs = 256, 0.1, 1000

# 定义损失函数
loss = nn.CrossEntropyLoss()

# 定义优化函数
optimizer = torch.optim.SGD(net.parameters(), lr=lr)

# 读取数据集
def load_data(batch_size):
trans = transforms.ToTensor()
train_set = torchvision.datasets.FashionMNIST(root='../data', train=True, transform=trans, download=True)
test_set = torchvision.datasets.FashionMNIST(root='../data', train=False, transform=trans, download=True)
return data.DataLoader(train_set, batch_size, shuffle=True), data.DataLoader(test_set, batch_size, shuffle=False)

train_iter, test_iter = load_data(b_size)

# 定义单次训练迭代
def train_epoch(net, train_iter, test_iter, loss, optimizer):
net.train()
train_correct = 0
test_correct = 0
for X, y in train_iter:
y_hat = net(X)
print(y.shape, y_hat.shape)
l = loss(y_hat, y)
optimizer.zero_grad()
l.mean().backward()
optimizer.step()
with torch.no_grad():
train_correct += evaluate_accuracy(net, X, y)
with torch.no_grad():
for X, y in test_iter:
test_correct += evaluate_accuracy(net, X, y)
return train_correct, test_correct

# 定义测试精度函数
def evaluate_accuracy(net, X, y):
y_hat = net(X).argmax(axis=1)
grade = y_hat.type(y.dtype) == y
return float(grade.type(y.dtype).sum())


t1s, t2s = [], []

def train(net, train_iter, test_iter, loss, num_epochs, optimizer):
for epoch in range(num_epochs):
t1, t2 = train_epoch(net, train_iter, test_iter, loss, optimizer)
print(f'epoch{epoch + 1}: train={t1}, test={t2}')
t1s.append(t1/600)
t2s.append(t2/100)
plt.ylim((0, 100))
plt.plot(range(epoch+1), t1s)
plt.plot(range(epoch+1), t2s)
if epoch == 0:
plt.show()

train(net, train_iter, test_iter, loss, n_epochs, optimizer)

    发表于2022-02-10

PyTorch实现Softmax回归

#softmax回归的简洁实现
import torch
from torch import nn
from torchvision import transforms
import torchvision
from torch.utils import data

# 读取数据集的函数
def load_data_fashion_mnist(b_size, resize=None):
trans = [transforms.ToTensor()]
if resize:
trans.insert(0, transforms.Resize(resize)) # 在第0个位置插入resize变换
trans = transforms.Compose(trans)
mnist_train = torchvision.datasets.FashionMNIST(root='../data', train=True, transform=trans, download=True)
mnist_test = torchvision.datasets.FashionMNIST(root="../data", train=False, transform=trans, download=True)
return (data.DataLoader(mnist_train, b_size, shuffle=True, num_workers=0),
data.DataLoader(mnist_test, b_size, shuffle=False, num_workers=0),)

batch_size = 256
train_iter, test_iter = load_data_fashion_mnist(batch_size)

# 定义模型
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))

#初始化模型参数
def init_weights(m):
if type(m) == nn.Linear: #由于下面的apply会递归调用所有子类,所以先判断类型决定是否初始化
nn.init.normal_(m.weight, std=0.01)
net.apply(init_weights)

#定义损失函数
loss = nn.CrossEntropyLoss(reduction='none')

#定义优化函数
trainer = torch.optim.SGD(net.parameters(), lr=0.1)

# Accumulator是一个类,用于累加多个变量
class Accumulator:
def __init__(self, n):
self.data = [0.] * n
def add(self, *args):
self.data = [a + float(b) for a, b in zip(self.data, args)]
def reset(self):
self.data = [0.] * len(self.data)
def __getitem__(self, idx):
return self.data[idx]

# 计算分类精度的函数
def accuracy(y_hat, y):
y_hat_res = y_hat.argmax(axis=1) # 假定第二个维度存储每个类的预测分数
cmp = y_hat_res.type(y.dtype) == y #cmp是由True或False组成的一维数组
return float(cmp.type(y.dtype).sum())

# 计算模型精度的函数
def evaluate_accuracy(net, data_iter):
if isinstance(net, torch.nn.Module):
net.eval() # 模型设置成评估模式
metric = Accumulator(2) # 用两个变量分别存储正确预测数和总预测数
with torch.no_grad():
for X, y in data_iter:
metric.add(accuracy(net(X), y), y.numel())
return metric[0] / metric[1]

# 单次迭代的函数
def train_epoch(net, train_iter, loss, updater):
net.train()
metric = Accumulator(3) #训练损失总和、训练准确度总和、样本数
for X, y in train_iter:
y_hat = net(X)
l = loss(y_hat, y)
updater.zero_grad()
l.mean().backward()
updater.step()
metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
return metric[0]/metric[2], metric[1]/metric[2] #返回训练损失和训练精度

# 整合训练函数
def train(net, train_iter, test_iter, loss, num_epochs, updater):
for epoch in range(num_epochs):
train_metrics = train_epoch(net, train_iter, loss, updater)
test_acc = evaluate_accuracy(net, test_iter)
print(f'epoch {epoch+1}: ')
print(f'train_loss: {train_metrics[0]}; train_acc: {train_metrics[1]}')
print(f'test_acc: {test_acc}')

num_epochs = 10
train(net, train_iter, test_iter, loss, num_epochs, trainer)

    发表于2022-02-06