learn.lianglianglee.com/专栏/程序员的数学课/21 神经网络与深度学习:计算机是如何理解图像、文本和语音的?.md.html
2022-08-14 03:40:33 +08:00

357 lines
26 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>21 神经网络与深度学习:计算机是如何理解图像、文本和语音的?.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/程序员的数学课/00 开篇词 数学,编程能力的营养根基.md.html">00 开篇词 数学,编程能力的营养根基</a>
</li>
<li>
<a href="/专栏/程序员的数学课/01 从计数开始,程序员必知必会的数制转换法.md.html">01 从计数开始,程序员必知必会的数制转换法</a>
</li>
<li>
<a href="/专栏/程序员的数学课/02 逻辑与沟通,怎样才能讲出有逻辑的话?.md.html">02 逻辑与沟通,怎样才能讲出有逻辑的话?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/03 用数学决策,如何规划好投入、转化和产出?.md.html">03 用数学决策,如何规划好投入、转化和产出?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/04 万物可数学,经典公式是如何在生活中应用的?.md.html">04 万物可数学,经典公式是如何在生活中应用的?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/05 求极值:如何找到复杂业务的最优解?.md.html">05 求极值:如何找到复杂业务的最优解?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/06 向量及其导数:计算机如何完成对海量高维度数据计算?.md.html">06 向量及其导数:计算机如何完成对海量高维度数据计算?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/07 线性回归:如何在离散点中寻找数据规律?.md.html">07 线性回归:如何在离散点中寻找数据规律?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/08 加乘法则:如何计算复杂事件发生的概率?.md.html">08 加乘法则:如何计算复杂事件发生的概率?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/09 似然估计:如何利用 MLE 对参数进行估计?.md.html">09 似然估计:如何利用 MLE 对参数进行估计?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/10 信息熵:事件的不确定性如何计算?.md.html">10 信息熵:事件的不确定性如何计算?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/11 灰度实验:如何设计灰度实验并计算实验的收益?.md.html">11 灰度实验:如何设计灰度实验并计算实验的收益?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/12 统计学方法:如何证明灰度实验效果不是偶然得到的?.md.html">12 统计学方法:如何证明灰度实验效果不是偶然得到的?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/13 复杂度:如何利用数学推导对程序进行优化?.md.html">13 复杂度:如何利用数学推导对程序进行优化?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/14 程序的循环:如何利用数学归纳法进行程序开发?.md.html">14 程序的循环:如何利用数学归纳法进行程序开发?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/15 递归:如何计算汉诺塔问题的移动步数?.md.html">15 递归:如何计算汉诺塔问题的移动步数?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/16 二分法:如何利用指数爆炸优化程序?.md.html">16 二分法:如何利用指数爆炸优化程序?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/17 动态规划:如何利用最优子结构解决问题?.md.html">17 动态规划:如何利用最优子结构解决问题?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/18 AI 入门:利用 3 个公式搭建最简 AI 框架.md.html">18 AI 入门:利用 3 个公式搭建最简 AI 框架</a>
</li>
<li>
<a href="/专栏/程序员的数学课/19 逻辑回归:如何让计算机做出二值化决策?.md.html">19 逻辑回归:如何让计算机做出二值化决策?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/20 决策树:如何对 NP 难复杂问题进行启发式求解?.md.html">20 决策树:如何对 NP 难复杂问题进行启发式求解?</a>
</li>
<li>
<a class="current-tab" href="/专栏/程序员的数学课/21 神经网络与深度学习:计算机是如何理解图像、文本和语音的?.md.html">21 神经网络与深度学习:计算机是如何理解图像、文本和语音的?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/22 面试中那些坑了无数人的算法题.md.html">22 面试中那些坑了无数人的算法题</a>
</li>
<li>
<a href="/专栏/程序员的数学课/23 站在生活的十字路口,如何用数学抉择?.md.html">23 站在生活的十字路口,如何用数学抉择?</a>
</li>
<li>
<a href="/专栏/程序员的数学课/24 结束语 数学底子好,学啥都快.md.html">24 结束语 数学底子好,学啥都快</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>21 神经网络与深度学习:计算机是如何理解图像、文本和语音的?</h1>
<p>在上一讲的最后,我们提到过“浅层模型”和“深层模型”。其实,人工智能的早期并没有“浅层模型”的概念,浅层模型是深度学习出现之后,与之对应而形成的概念。在浅层模型向深层模型转变的过程中,<strong>神经网络算法无疑是个催化剂</strong>,并在此基础上诞生了深度学习。</p>
<p>这一讲,我们就来学习一下神经网络和深度学习。</p>
<h3>神经网络的基本结构及其表达式</h3>
<p>回想一下上一讲我们学的决策树,理论上来看,只要一直递归,一层又一层地寻找分裂变量,决策树做出预测的准确率是可以达到 100% 的。可见,这种层次化建立模型的思想,是不断提高模型效果的重要手段。</p>
<p>然而对于决策树而言AI 框架的第一个公式 y = f(<em><strong>w;x</strong></em>)只能被“画出”却很难用被写出。而这背后的原因其实是决策树是一种类似于“if-else-”的条件分支结构,这本身就不是一种基于函数的数学表达形式。</p>
<p>那么我们不禁会想,有没有哪个模型既能保留层次化建模提高效果的优势,又能拥有基于函数的数学表达形式呢?</p>
<p>答案,就是神经网络。</p>
<p>神经网络是一种具有层次化结构的模型,它的设计来自生物学中对于人类大脑的研究。我们知道,神经元是人脑的基本结构,众多神经元组织在一起,就构成了人的大脑。</p>
<h4>1.神经元,神经网络的基本单位</h4>
<p>神经网络的结构与人类大脑结构非常相似,它的基本单位是函数化的神经元,再通过层次化地把这些神经元组织在一起,就构成了神经网络的表达式。</p>
<p>如下图,就是神经网络的神经元。</p>
<p><img src="assets/Ciqc1F_wgVSAJnWRAACyABgNtZA007.png" alt="图片1.png" /></p>
<p>我们假设输入变量有两个。</p>
<blockquote>
<p>实际中如果输入变量较多,只需要增加输入变量 xi 和权重系数 wi 的链接就可以了。</p>
</blockquote>
<p>图中x1 和 x2 是两个输入变量,它们分别与两个系数变量 w1 和 w2 相乘之后,指向了“+”号的模块。</p>
<p>得到了加权求和的结果之后,需要输入到一个 Sigmoid 函数中,最右的 y 就是这个神经元的输出,即</p>
<p><img src="assets/Cip5yF_wgaqAMJCPAAAl_m9Hvxs824.png" alt="图片2.png" /></p>
<p>有了神经元的表达式之后,我们把图中虚线框的神经元用一个圆形的结点来进行封装,再把输出 y 写入这个结点中,这样就有了下面的表示形式。</p>
<p><img src="assets/Cip5yF_wgWSAFcJnAACIbvQJjQc885.png" alt="图片3.png" /></p>
<h4>2.层次化将“神经元”构成神经网络</h4>
<p>我们说过,层次化地把多个神经元组织在一起,才构成了神经网络。在这里,<strong>层次化</strong>的含义是,每一层有若干个神经元结点,层与层之间通过带权重的边互相连接。如下图,就是一个简单的神经网络。</p>
<p><img src="assets/CgpVE1_wgbSARHK6AAElMQEDF1Q333.png" alt="图片4.png" /></p>
<p>在这个神经网络中,输入变量有 3 个,分别是 x1、x2 和 x3。结点与结点之间由带箭头的边连接每条边都是一个权重系数 wijk。作用是将前面一个结点的输出乘以权重系数后输入给后面一个结点中。</p>
<blockquote>
<p>这里 wijk 的含义,是第 i 层的第 j 个结点到第 i+1 层的第 k 个结点的权重。</p>
</blockquote>
<p>网络中,除了最后一个结点以外,其余结点的输出都是临时结果;且每个临时结果,都将成为下一层神经元结点的输入。而最后一个结点的输出,也就是最终模型的输出 y。</p>
<p>对于神经网络而言,它既可以用图画的方式“画出”模型的结构,也可以通过函数化的形式写出输入和输出的关系,上图中的表达式如下。</p>
<p>y = y3 = sigmoid(y1w211+y2w221)</p>
<p>y1 = sigmoid(x1w111+x2w121+x3w131)</p>
<p>y2 = sigmoid(x1w112+x2w122+x3w132)</p>
<p>我们将 y1 和 y2 代入 y3则有</p>
<p>y = sigmoid[sigmoid(x1w111+x2w121+x3w131) ·w211 + sigmoid(x1w112+x2w122+x3w132)·w221]</p>
<p>虽然,神经网络模型可以用函数来写出输入输出关系的表达式,但由于网络结构本身的复杂性导致这个表达式并不好看。而且随着网络层数变多、每一层结点数变多,这个表达式会变得越来越复杂。</p>
<p>在实际应用中,根据需要神经网络可以有任意多个层次,每层里可以有任意多个神经元,这通常是由开发者自己根据问题的复杂程度而预先设置的。</p>
<h3>神经网络的损失函数</h3>
<p>有了神经网络的表达式之后,我们就继续用 AI 框架的第二个公式,去写出它的损失函数。神经网络的损失函数并没有什么特殊性,在绝大多数场景下,都会选择最小二乘的平方误差作为损失函数。</p>
<blockquote>
<p>这一点,与线性回归是一致的。</p>
</blockquote>
<p>最小二乘损失函数计算方式,是所有样本真实值 ŷ 与预测值 y 之间差值的平方和,则有:</p>
<p><img src="assets/Ciqc1F_wgeqAI7WGAAAdxe9diJE517.png" alt="图片6.png" /></p>
<p>其中 n 代表的是所有的样本数。在这个损失函数中还有一个 1/2 的系数,增加一个系数会影响损失函数 L(<em><strong>w</strong></em>) 的值,但并不会影响最优系数的取值。</p>
<blockquote>
<p>例如y = 2x2+4和 y=x2+2 取得极值都是在 x=0 的点,之所以增加这个系数,是为了抵消后面平方项求导而产生的 2 倍的系数。</p>
</blockquote>
<h3>随机梯度下降法求解神经网络参数</h3>
<p>最后,我们利用 AI 框架的第三个公式<em><strong>w</strong></em>*= argmin L(<em><strong>w</strong></em>),来求解神经网络。在神经网络中,<em><strong>w</strong></em>系数就是所有的 wijk。</p>
<p>我们把到现在为止的所有已知条件进行整理</p>
<p><img src="assets/Cip5yF_wgq2AKSy7AABA7VeZyF8001.png" alt="71.png" /></p>
<p>y = sigmoid[sigmoid(x1w111+x2w121+x3w131)·w211+sigmoid(x1w112+x2w122+x3w132)·</p>
<p>w221]</p>
<p>其中对于某个给定的数据集而言xi 和 ŷi 都是已知的。也就是说,我们要求解出让上面损失函数 L(<em><strong>w</strong></em>) 取得极小值的 wijk 的值,我们可以考虑用先前学的随机梯度下降法来进行求解。</p>
<p>在使用随机梯度下降法的时候,只会随机选择一个样本(假设标记为 m进行梯度下降的优化。因此损失函数的大型求和符号就可以消灭掉了</p>
<p><img src="assets/Cip5yF_wgreAHb13AAAotbO_Dwc622.png" alt="81.png" /></p>
<p>ym=sigmoid[sigmoid(xm1w111+xm2w121+xm3w131)·w211+sigmoid(xm1w112+xm2w122+xm3w132)·w221]</p>
<p>在这个例子中,我们有 8 个 wijk 变量,分别是 w111、w121、w131、w211、w112、w122、w132、w221因此需要求分别计算损失函数关于这 8 个变量的导数。</p>
<p>既然表达式都有了,我们就利用大学数学求导的<strong>链式法则</strong>,耐着性子来求解一下吧。</p>
<blockquote>
<p>别忘了y=sigmoid(x) 的一阶导数是 y·(1-y)。</p>
</blockquote>
<p><img src="assets/Cip5yF_wgh-AHZJJAABVXWdACA8110.png" alt="图片9.png" /></p>
<p>有了梯度之后,就可以设置学习率,再利用随机梯度下降法求解最优参数了。</p>
<h3><strong>神经网络建模案例</strong></h3>
<p>利用下面的数据集,建立一个神经网络。这个数据集中,每一行是一个样本,每一列一个变量,最后一列是真实值标签。</p>
<p><img src="assets/Ciqc1F_wgiqAI97lAABd8lOjn78149.png" alt="图片10.png" /></p>
<p>在利用神经网络建模时,需要预先设计网络结构。也就是说,你计划采用几层的网络,每一层准备设置多少个神经元结点。</p>
<p>我们看到,每个样本包含了 3 个输入变量。那么,我们可以直接采用上面推倒过的网络结构,即神经网络的结构如下所示。</p>
<p><img src="assets/CgqCHl_wgjKAFyLOAADrEi6vHSo470.png" alt="图片11.png" /></p>
<p>同时,我们也已经推导出了损失函数关于每个链接权重边的梯度,即</p>
<p><img src="assets/Ciqc1F_wgjuASOELAABXlbT4eT8833.png" alt="图片12.png" /></p>
<p>由于神经网络的代码量比较多,而且有非常多的开源工具可以使用。因此,我们这里给出伪代码,来展示其核心思想。</p>
<pre><code>#获取数据集x和y
x,y = getData()
#随机初始化参数w
w = init()
#设置学习率
a = 1.0
#随机梯度下降法
for _ in range(1000):
index = random.randint()
y1,y2,y3 = getResult(x,w)
g = getGrad(x,y,w)
w = w - a*g
</code></pre>
<p>我们对代码进行解读:</p>
<ul>
<li>第 2 行,读取数据集,并保存在变量 x 和 y 中,可以考虑用 Numpy 的 array 进行保存;</li>
<li>第 5 行,随机初始化参数向量 w因为神经网络是多层、多结点的结构所以可以考虑用个三维数组进行保存</li>
<li>第 8 行,设置学习率,与以前的结论一样,如果迭代轮数够多,学习率可以考虑设置小一些;</li>
<li>第 11 行开始,进行随机梯度下降法的迭代。</li>
<li>第 12 行,调用随机函数,随机获取一个数据样本。</li>
<li>第 13 行,根据网络结构,计算 y1、y2、y3 每个结点的输出,其中还需要多次调用 Sigmoid 函数,可以考虑把 Sigmoid 的计算单独函数化;</li>
<li>第 14 行,根据梯度公式计算梯度值,并保存在 g 变量中g 和 w 应该设置一样的数据类型;</li>
<li>第 15 行,利用梯度下降法进行参数更新。</li>
</ul>
<p>在实际工作中,如果你需要建立神经网络的模型,除了上面自己开发代码的方式外,还可以考虑使用 Tensorflow 或者 Keras 等开源的人工神经网络库。</p>
<blockquote>
<p>因为这只是实现的工具,原理上并没有什么差异,故而我们不再深入展开讨论。</p>
</blockquote>
<h3><strong>神经网络和深度学习</strong></h3>
<p>深度学习通常指训练大型深度的神经网络的过程。</p>
<ul>
<li>与传统的神经网络模型相比,深度学习模型在结构上与之非常相似;</li>
<li>不同的是,深度学习模型的“深度”更大,“深度”的体现就是神经网络层数多,神经网络每一层的结点数多。</li>
</ul>
<p>下面,我们简单介绍两种深度神经网络——卷积神经网络和循环神经网络,以及它们分别在图像处理、文本处理和语音处理上的效果。</p>
<h4>1.卷积神经网络CNN</h4>
<p>与普通神经网络相比,卷积神经网络引入了“卷积”和“池化”两个操作,下面通过详细的例子,讲解卷积神经网络在图像处理的主要思路。</p>
<p>彩色图像由红、绿、蓝三原色组成,每种原色按照深浅可以表示为 0 到 255 间的一个数字。因此,对于图像中的每个像素(图像中不可分割的最小单位),都可以写出其相应的红、绿、蓝数值。</p>
<p>所以在计算机中,一幅彩色图像可由红、绿、蓝三个颜色的像素矩阵表示出来,下图给出了一幅 128×128 像素图像的矩阵表示:</p>
<p><img src="assets/CgqCHl_wgk2AM9wAAAonpFyfDOo523.png" alt="图片13.png" /></p>
<ul>
<li><strong>“卷积”操作的思想</strong>
采用一个较小的卷积核,例如 3×3 的矩阵,来对图像特征进行局部的提取。这样做可以增加参数的共享,减少随着神经网络变深、结点数变多而带来的巨大计算量。</li>
<li><strong>“池化”操作的思想</strong>
采用一种过滤的方法,去除冗余信息并且加快计算。池化可以将一个 4×4 的图像切割成 4 个 2×2 的小矩阵,在每个小矩阵中取最大值,所得结果形成一个新矩阵。这种操作,可以减少神经网络结点的个数,加快计算速度。</li>
</ul>
<p>在卷积神经网络中,通常某一个层都是在做卷积处理,某一层都是在做池化处理。一般,它们都是在层次之间交替进行的。经过多层卷积、池化操作后,所得特征图的分辨率远小于输入图像的分辨率,减少了计算量,加快了计算速度。</p>
<p>通过卷积和池化两项操作,卷积神经网络能在准确提取图像特征的同时,提升运算效率,因此在图像处理中取得了良好效果。</p>
<h4>2.循环神经网络RNN</h4>
<p>循环神经网络是一种善于处理序列信息的神经网络,在语音、文本处理方面有着非常大的优势。因为人类的自然语言属于一种时序信息,它有着明显的顺序关系,这就让以循环神经网络结构为基础的深度神经网络有其发挥空间。</p>
<p>除此之外,循环神经网络在引入 LSTMLong Short-TermMemory结构后在对有用时序信息的“记忆”和没用时序信息的“忘记”上有着强大的处理能力。</p>
<p>下图给出了一个 LSTM 的神经元结构。</p>
<p><img src="assets/Ciqc1F_wglaAH-ZjAAD1d_BWqcY925.png" alt="图片14.png" /></p>
<p>可以发现LSTM 的网络结构和神经元结构已经非常复杂了,但它仍然保持着神经网络的那些特性。尤其是结构可被“画出”,输入、输出之间可以用函数表达。有了这些基本条件后,就仍然可以用损失函数和随机梯度下降法,来求解网络结构的参数。</p>
<h3>小结</h3>
<p>这一讲,我们学习了神经网络和深度学习。在当前的 AI 时代下,深度学习模型在效果方面打败了传统的浅层模型。而深度学习的基本原理都主要来自神经网络,神经网络结构可被“画出”,输入、输出之间可以用函数表达,这些特点都是支持它深度化的前提。</p>
<p>神经网络之所以能取得很好的效果,主要是因为网络结构的多样性。计算机在面对语音、图像、文本的不同问题时,主要是通过对网络结构进行优化迭代,设计出 CNN、RNN 的新型神经网络结构的。</p>
<p>此外,神经网络的损失函数和参数求解,仍然和其他浅层模型相似,并没有什么特别。</p>
<p>最后,我们给大家留一个练习题。假设有一个 4 层的神经网络,第一层是输入 xi最后一层是输出 y。4 层的结点数分别是 3、2、2、1。试着去求解一下损失函数关于每个链接权重的梯度吧。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/程序员的数学课/20 决策树:如何对 NP 难复杂问题进行启发式求解?.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/程序员的数学课/22 面试中那些坑了无数人的算法题.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997bed09b03cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>