<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="/feeds/rss-style.xsl" type="text/xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>惟愿此心无怨尤</title>
        <link>https://bingqiangzhou.github.io/</link>
        <description>TO BE, TO UP.</description>
        <lastBuildDate>Thu, 16 Apr 2026 16:23:05 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>Astro-Theme-Retypeset with Feed for Node.js</generator>
        <language>zh</language>
        <copyright>Copyright © 2026 Bingqiang Zhou</copyright>
        <atom:link href="https://bingqiangzhou.github.io/rss.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[【学习笔记】归一化(Normalization)、标准化(Standardization)与正则化(Regularization)]]></title>
            <link>https://bingqiangzhou.github.io/posts/dailysummary-normalizationstandardizationregularization/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/dailysummary-normalizationstandardizationregularization/</guid>
            <pubDate>Wed, 26 Aug 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[回到学校一个星期了，算是差不多找回状态了。]]></description>
            <content:encoded><![CDATA[<p>回到学校一个星期了，算是差不多找回状态了。</p>
<p>今天记录一下归一化(Normalization)、标准化(Standardization)与正则化(Regularization)。</p>
<h2>归一化(Normalization)</h2>
<p>归一化是特征缩放的一种形式，归一化是把数据压缩到一个区间内，比如$[0,1], [-1, 1]$。常用的方法有：</p>
<p>Rescaling:</p>
<p>$$
x' = \frac{x - min(x)}{max(x) - min(x)}
$$</p>
<p>Mean normalization:</p>
<p>$$
x' = \frac{x - mean(x)}{max(x)-min(x)}
$$</p>
<h2>标准化(Standardization)</h2>
<p>标准化，与归一化一样，也是特征缩放的一种形式。标准化是减去均值后再除以方差，公式如下：</p>
<p>$$
x'= \frac{x - \overline{x}}{\sigma}
$$</p>
<p>需要注意的是，标准化之后的数据分布并不一定是正态分布，标准化并不会改变原始数据的分布。可以浏览一下<a href="https://blog.csdn.net/weixin_36604953/article/details/102652160">这篇博客</a>，这篇博客中有使用例子来说明，标准化，并不会改变它的分布。</p>
<p>拓展知识：z分数（z-score），也叫标准分数（standard score）是一个数与平均数的差再除以标准差的过程。
因此上面的标准化公式也被称为z-score标准化。</p>
<h2>正则化(Regularization)</h2>
<p>正则化是指为解决适定性问题或过拟合而加入额外信息的过程。常用的有L1正则化，L2正则化。这篇文章<a href="http://blog.sina.com.cn/s/blog_a89e19440102x1el.html">理解泛化能力、weight decay</a>讲的比较好，它推理了了L2正则化就是权重衰减是怎么来的，而在<a href="https://www.zhihu.com/question/268068952">知乎的另一个回答</a>中，讲到了它们是不完全一样，一个改变了目标函数（损失函数），一个是体现在更新参数的过程。</p>
<h2>其他的一些数据变换方式</h2>
<p>数据变换方式很多，不同的数据变换有不同的用途。
Softmax、RELU、Sigmoid等等都是数据变换，不过用途不一样。</p>
<p>再宽泛一点讲，其实每一个函数（映射）都是一种变换方式，只是有一些函数（映射）常用于机器学习。</p>
<h2>Q&amp;A</h2>
<p>这里综合了一下，下面两篇论文中我觉得不错的问题以及解释。</p>
<p><a href="https://blog.csdn.net/weixin_36604953/article/details/102652160">标准化和归一化，请勿混为一谈，透彻理解数据变换</a></p>
<p><a href="https://blog.csdn.net/huanyingzhizai/article/details/92772793?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.edu_weight&amp;depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.edu_weight">【python数据预处理笔记】——特征缩放（标准化 &amp; 归一化）</a></p>
<ol>
<li>
<p>为什么需要做标准化或者归一化？</p>
<p>a. 目的是使其不同尺度之间的特征具有可比性，同时不改变原始数据的分布。例如$y=x_1+x_2+7$是一个简单的线性函数，如果$x_1$的尺度为$[0,1]$，$x_2$的尺度为$[0,10000]$，那么$x_2$的变动将会产生更大的影响。这个时候就要对特征进行缩放，也就是标准化或者规范化。简单来说就是让模型不会偏向于某一个特征。</p>
<p>b. 参数估计时使用梯度下降，在使用梯度下降的方法求解最优化问题时，归一化/标准化后可以加快梯度下降的求解速度，即提升模型的收敛速度。</p>
</li>
<li>
<p>哪些数据不能做特征缩放？</p>
<p>稀疏数据不能进行特征缩放，因为特征缩放相当于对原数据进平移和缩放。而对于稀疏数据来说，数据中含有大量的0，一旦进行平移，那么稀疏特征将会变为密集特征，这会产生很大的影响，比如特征向量中包含没有在文章中出现的所有单词，那么当它变为密集向量的时候，特征的意义会发生巨大的改变。</p>
</li>
<li>
<p>什么时候Standardization，什么时候Normalization？</p>
<p>a. 如果对处理后的数据范围有严格要求，那肯定是归一化；</p>
<p>b. 如果无从下手，可以直接使用标准化；</p>
<p>c. 如果数据不稳定，存在极端的最大最小值，不要用归一化。</p>
<p>d. 在分类、聚类算法中，需要使用距离来度量相似性的时候、或者使用PCA技术进行降维的时候，标准化表现更好；在不涉及距离度量、协方差计算的时候，可以使用归一化方法。</p>
<p>PS：PCA中标准化表现更好的原因可以参考(<a href="https://blog.csdn.net/young951023/article/details/78389445">PCA标准化</a>)</p>
</li>
<li>
<p>所有情况都应当Standardization或Normalization么?</p>
<p>a. 当原始数据不同维度特征的尺度(量纲)不一致时，需要标准化步骤对数据进行标准化或归一化处理，反之则不需要进行数据标准化。</p>
<p>b. 也不是所有的模型都需要做归一的，比如模型算法里面有没关于对距离的衡量，没有关于对变量间标准差的衡量。比如决策树，他采用算法里面没有涉及到任何和距离等有关的，所以在做决策树模型时，通常是不需要将变量做标准化的；</p>
<p>c. 另外，概率模型不需要归一化，因为它们不关心变量的值，而是关心变量的分布和变量之间的条件概率。</p>
</li>
</ol>
<h2>参考链接</h2>
<p><a href="https://www.zhihu.com/question/20467170">标准化和归一化什么区别？</a></p>
<p><a href="https://blog.csdn.net/weixin_36604953/article/details/102652160">标准化和归一化，请勿混为一谈，透彻理解数据变换</a></p>
<p><a href="https://blog.csdn.net/huanyingzhizai/article/details/92772793?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.edu_weight&amp;depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.edu_weight">【python数据预处理笔记】——特征缩放（标准化 &amp; 归一化）</a></p>
<p><a href="http://blog.sina.com.cn/s/blog_a89e19440102x1el.html">Deep Learning-1.5 理解泛化能力、weight decay</a></p>
<p><a href="https://www.zhihu.com/question/268068952">权重衰减和L2正则化是一个意思吗？</a></p>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【折腾记录】监控实验室GPU使用情况，有空闲GPU发送邮件提醒]]></title>
            <link>https://bingqiangzhou.github.io/posts/dailyjungle-monitorgpuusageandemailremainer/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/dailyjungle-monitorgpuusageandemailremainer/</guid>
            <pubDate>Sun, 26 Jul 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[又有一段时间没有记录总结了，马上要开学了，这次记录一下监控实验室GPU使用情况，有空闲GPU发送邮件提醒的代码。]]></description>
            <content:encoded><![CDATA[<p>又有一段时间没有记录总结了，马上要开学了，这次记录一下监控实验室GPU使用情况，有空闲GPU发送邮件提醒的代码。</p>
<p>前一段时间跑代码，而跑代码的人比较多，然后资源有限，需要抢占先机才行，然后就写了这段代码。</p>
<h2>主要思路</h2>
<p>执行命令行<code>nvidia-smi -q -x</code>，获取到命令行输出，解析xml，获取到GPU使用情况，判断是否有空闲GPU，发送邮件。</p>
<h2>主要代码</h2>
<p>下面主要给出执行命令行，以及解析返回结果代码，发送邮件仅给出之前的的一篇博客作为参考，<a href="/posts/dailyjungle-smtp-seleniumwithheadlessbroswer/#2%E5%8F%91%E9%80%81%E9%82%AE%E4%BB%B6">参考:【折腾记录】定时获取成绩，新出一门成绩使用邮件提醒</a>。</p>
<p>NVIDIA-SMI相关命令，参考<a href="https://www.cnblogs.com/omgasw/p/10218180.html">NVIDIA-SMI系列命令总结</a>。</p>
<p>解析xml，参考<a href="https://docs.python.org/3/library/xml.etree.elementtree.html">python 官方文档 xml.etree.ElementTree -- The ElementTree XML API</a></p>
<p>获取进程相关信息的包<a href="https://psutil.readthedocs.io/en/latest/">psutil (python system and process utilities)</a>。</p>
<pre><code># references:
#     https://www.cnblogs.com/omgasw/p/10218180.html
#     https://docs.python.org/3/library/xml.etree.elementtree.html
#     https://psutil.readthedocs.io/en/latest/

import os
import psutil
import xml.etree.ElementTree as ET
import time
from pprint import pprint
from datetime import timedelta, datetime

def execute_command(command_str):
    p = os.popen(command_str)
    text = p.read()
    p.close()
    return text

# command_str = 'nvidia-smi dmon -c 1'
# pwr：功耗，temp：温度，sm：流处理器，mem：显存，enc：编码资源，dec：解码资源，mclk：显存频率，pclk：处理器频率
# # gpu   pwr  temp    sm   mem   enc   dec  mclk  pclk
# Idx     W     C     %     %     %     %   MHz   MHz
#     0   112    87    19     7     0     0  5005  1480
#     1   177    83    78     8     0     0  5005  1809
#     2    93    81    31    10     0     0  5005  1809
#     3   150    83    93    54     0     0  5005  1784

def get_process_info(pid):
    p = psutil.Process(pid)
#     print(p.as_dict())
    return p.as_dict()

def get_gpu_info():
    command_str = 'nvidia-smi -q -x'
    # 查询所有GPU的当前详细信息并将查询的信息以xml的形式输出
    gpus_info_dict = {}
    text = execute_command(command_str)
    root = ET.fromstring(text)
    for i, gpu_info in enumerate(root.findall('gpu')):
        gpu_info_dict = {}
        gpu_name = gpu_info.find('product_name').text
        fb_memory_usage = gpu_info.find('fb_memory_usage')
        total = fb_memory_usage.find('total').text
        used = fb_memory_usage.find('used').text
        free = fb_memory_usage.find('free').text
        rate = int(used.split(' ')[0]) / int(total.split(' ')[0])
        gpu_info_dict.update({'gpu name': gpu_name})
        gpu_info_dict.update({'memory usage':{'total': total, 'used': used, 'free': free}})
        gpu_info_dict.update({'gpu utilization':rate})

        processes = gpu_info.find('processes')
        processes_dict = {}
        for process_info in processes.findall('process_info'):
            pid = process_info.find('pid').text
            infos = get_process_info(int(pid))
            create_time = infos['create_time']
            current_time = time.time()
            time_difference = time.gmtime(current_time - create_time)
            run_time = f'{time_difference.tm_hour}h,{time_difference.tm_min}m,{time_difference.tm_sec}s'
            cmd_line = ' '.join(infos['cmdline'])
            processes_dict.update({'pid': pid, 'user name':infos['username'], 'cwd':infos['cwd'], 'cmd line':cmd_line, 'run time': run_time})
        gpus_info_dict.update({f'gpu:{i}':{'gpu info': gpu_info_dict, 'processes': processes_dict}})
    return gpus_info_dict

pprint(get_gpu_info())
</code></pre>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【读书笔记】《神经网络与深度学习》-- 网络优化与正则化]]></title>
            <link>https://bingqiangzhou.github.io/posts/readingnotes-networksoptimizationandregularization/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/readingnotes-networksoptimizationandregularization/</guid>
            <pubDate>Mon, 20 Jul 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[神经网络与深度学习，第七章-网络优化与正则化，笔记。]]></description>
            <content:encoded><![CDATA[<p><a href="https://nndl.github.io">神经网络与深度学习</a>，第七章-网络优化与正则化，笔记。</p>
<p>网络优化主要介绍一些常用的优化算法、参数初始化方法、数据预处理方法、逐层归一化方法和超参数优化方法。</p>
<p>网络正则化主要介绍一些提高泛化能力的方法，包括$l_1$和$l_2$正则化、权重衰减、提前停止、丢弃法、数据增强和标签平滑。</p>
<h2>1、网络优化</h2>
<p><em>网络优化</em></p>
<p>网络优化是指寻找一个神经网络模型来使得经验（或结构）风险（损失）最小化的过程，包括模型选择以及参数学习等。深度神经网络是一个高度非线性的模型，它的损失函数是一个非凸函数，因此最小化损失是一个非凸优化问题．</p>
<p><em>鞍点（Saddle Point）与局部最小值（Local Minima）</em></p>
<p>在高维空间中，非凸优化的难点并不在于如何逃离局部最优点，而是如何逃离鞍点（鞍点示意图如下），鞍点的梯度是0，但是在一些维度上是最高点，在另一些维度上是最低点。相比鞍点，局部最小值要求在每一维度上都是最低点。</p>
<p><img src="/assets/images/2020/20200720/saddle-point.png" alt="鞍点" /></p>
<p><em>局部最小解的等价性</em></p>
<p>在非常大的神经网络中，大部分的局部最小解是等价的，它们在测试集上性能都比较相似。<strong>在训练神经网络时，我们通常没有必要找全局最小值，这反而可能导致过拟合。</strong></p>
<p><em>神经网络优化的改善方法</em></p>
<p>目前比较有效的经验性改善方法通常分为以下几个方面：</p>
<ol>
<li><strong>使用更有效的优化算法</strong>来提高梯度下降优化方法的效率和稳定性，比如动态学习率调整、梯度估计修正等。</li>
<li><strong>使用更好的参数初始化方法、数据预处理方法</strong>，来提高优化效率．</li>
<li><strong>修改网络结构来得到更好的优化地形（Optimization Landscape，好的优化地形即比较平滑的损失函数的曲面形状）</strong>，比如使用ReLU 激活函数、残差连接、逐层归一化等。</li>
<li><strong>使用更好的超参数优化方法</strong>。</li>
</ol>
<h3>1.1、优化算法 - 梯度下降</h3>
<p>目前，深度神经网络的参数学习主要是通过<strong>梯度下降法</strong>来寻找一组可以最小化结构风险的参数。在具体实现中，梯度下降法可以分为：</p>
<ol>
<li><strong>批量梯度下降</strong>（Batch Gradient Descent，在更新参数时，使用<em>所有的样本</em>来进行更新。）</li>
<li><strong>随机梯度下降</strong>（Stochastic Gradient 是在更新参数时都使用<em>一个样本</em>来进行更新。）</li>
<li><strong>小批量梯度下降</strong>（Mini-batch Gradient Descent，是在更新参数时都使用<em>一部分样本</em>来进行更新。）</li>
</ol>
<p><em>梯度下降公式</em></p>
<p>令$f(x;\theta)$表示一个深度神经网络，$\theta$为网络参数，在使用小批量梯度下降进行优化时，每次选取$K$个训练样本$S_t={(x(k), y(k))}^K_{k=1}$。第$t$次迭代（Iteration）时损失函数关于参数$\theta$的偏导数为</p>
<p>$$
G_t(\theta) = \frac{1}{K} \sum_{(x,y) \in S_t}\frac{\partial L(y, f(x;\theta))}{\partial \theta}
$$</p>
<p>其中$L(\cdot)$为可微分的损失函数，$K$为批量大小（Batch Size）。</p>
<p>第$t$次更新的梯度${\rm g_t}$定义为</p>
<p>$$
{\rm g}<em>t \triangleq G_t(\theta</em>{t-1})
$$</p>
<p>更新参数，$\alpha(\alpha &gt; 0)$为学习率。</p>
<p>$$
\theta_t \leftarrow \theta_{t-1}- \alpha {\rm g}_t
$$</p>
<p>每次迭代时参数更新的差值$\Delta \theta_t$定义为</p>
<p>$$
\Delta \theta_t \triangleq \theta_t-\theta_{t-1}
$$</p>
<p>$\Delta \theta_t$和梯度${\rm g_t}$并不需要完全一致（有时候会实际情况对梯度${\rm g_t}$进行调整）。$\Delta \theta_t$为每次迭代时参数的实际更新方向，即
$\theta_t = \theta_{t-1} + \Delta \theta_t$。在标准的小批量梯度下降中，$\Delta \theta_t = - \alpha {\rm g}_t$。</p>
<p>从上面公式可以看出，影响梯度下降法的主要因素有：</p>
<ol>
<li>批量大小$K$</li>
<li>学习率$\alpha$</li>
<li>梯度估计</li>
</ol>
<h4>1.1.1、*批量大小$K$*的选择</h4>
<p>一般而言，批量大小不影响随机梯度的期望，但是会影响随机梯度的方差。批量大小越大，随机梯度的方差越小，引入的噪声也越小，训练也越稳定，因此可以设置较大的学习率；而批量大小较小时，需要设置较小的学习率，否则模型会不收敛。学习率通常要随着批量大小的增大而相应地增大。（可以使用线性缩放规则（Linear Scaling Rule），当批量大小增加$m$倍时，学习率也增加$m$倍。）<strong>根据GPU资源来设置批量$K$</strong>。</p>
<h4>1.1.2、学习率$\alpha$调整</h4>
<p>在梯度下降法中，学习率$\alpha$的取值非常关键，如果过大就不会收敛，如果过小则收敛速度太慢。</p>
<p>常用的学习率调整方法包括学习率衰减、学习率预热、周期性学习率调整以及一些自适应调整学习率的方法。</p>
<ul>
<li>学习率衰减
常见的衰减方法有以下几种：
<ol>
<li>分段常数衰减（Piecewise Constant Decay），也称为阶梯衰减（Step Decay）</li>
<li>逆时衰减（Inverse Time Decay）</li>
<li>指数衰减（Exponential Decay）</li>
<li>自然指数衰减（Natural Exponential Decay）</li>
<li>余弦衰减（Cosine Decay）</li>
</ol>
</li>
</ul>
<p><img src="/assets/images/2020/20200720/learning-rate-decay.png" alt="学习率衰减" /></p>
<ul>
<li>
<p>学习率预热
在小批量梯度下降法中，当批量大小的设置比较大时，通常需要比较大的学习率．但在刚开始训练时，由于参数是随机初始化的，梯度往往也比较大，再加上比较大的初始学习率，会使得训练不稳定。为了提高训练稳定性，我们可以在最初几轮迭代时，采用比较小的学习率，等梯度下降到一定程度后再恢复到初始的学习率，这种方法称为学习率预热（Learning Rate Warmup）。</p>
<p>常用的学习率预热方法是逐渐预热（Gradual Warmup），设定预热的迭代次数（作为分母，当前迭代次数作为分子），逐渐让学习率接近初始学习率，直到等于初始学习率。</p>
</li>
<li>
<p>周期性学习率调整</p>
<p>两种常用的周期性调整学习率的方法：</p>
<p><em>循环学习率</em>，学习率在一个区间内周期性地增大和缩小。</p>
<p><em>带热重启的随机梯度下降</em>，学习率每间隔一定周期后重新初始化为某个预先设定值，然后逐渐衰减．每次重启后模型参数不是从头开始优化，而是从重启前的参数基础上继续优化。</p>
</li>
</ul>
<p><img src="/assets/images/2020/20200720/periodic-learning-rate-adjustment.png" alt="周期性学习率调整" /></p>
<ul>
<li>
<p>AdaGrad 算法</p>
<p>AdaGrad 算法（Adaptive Gradient Algorithm）是借鉴
$L_2$正则化的思想，每次迭代时自适应地调整每个参数的学习率。每次迭代时，先计算每个参数梯度平方的累计值，将这个累计值开根作为分母，初始学习作为分子，从而实现学习率随着迭代次数的增加逐渐缩小。</p>
<p>在AdaGrad 算法中，如果某个参数的偏导数累积比较大，其学习率相对较小；相反，如果其偏导数累积较小，其学习率相对较大．但整体是随着迭代次数的增加，学习率逐渐缩小。</p>
<p>AdaGrad 算法的缺点是在经过一定次数的迭代依然没有找到最优点时，由于这时的学习率已经非常小，很难再继续找到最优点。</p>
</li>
<li>
<p>RMSprop 算法</p>
<p>RMSProp 算法和AdaGrad 算法的区别在于梯度平方的累计值的计算由加上了指数衰减，前面的梯度对当前的梯度影响逐渐变小，将其加起来来确定当前参数学习率。在迭代过程中，每个参数的学习率并不是呈衰减趋势，既可以变小也可以变大，这样可以在有些情况下避免AdaGrad算法中学习率不断单调下降以至于过早衰减的缺点。</p>
</li>
<li>
<p>AdaDelta 算法
也是AdaGrad 算法的一个改进。使用RMSprop 算法同样的方式来调整学习率。此外，AdaDelta 算法将RMSprop 算法中的初始学习率$\alpha$改为动态计算的$\sqrt{\Delta X_{t-1}^2}$在一定程度上平抑了学习率的波动。</p>
</li>
</ul>
<h4>1.1.3、梯度估计修正</h4>
<ul>
<li>
<p>动量法</p>
<p>动量（Momentum）是模拟物理中的概念。一个物体的动量指的是该物体在它运动方向上保持运动的趋势，是该物体的质量和速度的乘积．动量法（Momentum Method）是用之前积累动量来替代真正的梯度．每次迭代的梯度可以看作加速度。</p>
<p>每个参数的实际更新差值取决于最近一段时间内梯度的加权平均值，当某个参数在最近一段时间内的梯度方向不一致时，其真实的参数更新幅度变小；相反，当在最近一段时间内的梯度方向都一致时，其真实的参数更新幅度变大，起到加速作用．一般而言，在迭代初期，梯度方向都比较一致，动量法会起到加速作用，可以更快地到达最优点．在迭代后期，梯度方向会不一致，在收敛值附近振荡，动量法会起到减速作用，增加稳定性。</p>
<p>类似于RMSprop 算法，前面的梯度对当前的梯度影响逐渐变小，并将其加起来作为更新参数的梯度，而且参数更新对前一次迭代的参数有一个惯性（相当于初速度），而梯度相当于加速度。</p>
</li>
<li>
<p>Nesterov 加速梯度</p>
<p>Nesterov 加速梯度（Nesterov Accelerated Gradient，NAG）是一种对动量法的改进，也称为Nesterov 动量法（Nesterov Momentum）。</p>
<p>Nesterov 加速梯度与动量法 思想是类似的，但是认为使用前一次迭代参数的梯度作为加速度是不太合理的，应该使用动量（惯性）移动到了的地方的加速度（偏导）来计算下一次迭代的参数（速度）。</p>
</li>
</ul>
<p><img src="/assets/images/2020/20200720/momentum-and-nesterov.png" alt="动量法和Nesterov 加速梯度的比较" /></p>
<ul>
<li>
<p>Adam 算法和Nadam 算法</p>
<p>Adam算法（Adaptive Moment Estimation Algorithm）可以看作动量法和RMSprop 算法的结合。不但使用动量作为参数更新方向，而且可以自适应调整学习率。</p>
<p>而Nadam 算法是Nesterov 加速梯度与RMSprop 算法的结合。</p>
</li>
<li>
<p>梯度截断</p>
<p>为了避免梯度突然增大使得参数远离最优点，当梯度的模大于一定阈值时，就对梯度进行截断，称为梯度截断（Gradient Clipping）。</p>
<p>梯度截断是一种比较简单的启发式方法，把梯度的模限定在一个区间，当梯度的模小于或大于这个区间时就进行截断．一般截断的方式有以下几种：</p>
<pre><code>*按值截断* ，给定一个区间$[a, b]$，如果一个参数的梯度小于$a$时，就将其设为$a$；如果大于$b$时，就将其设为$b$.

*按模截断* ，给定的截断阈值$b$，当梯度的模小于等于$b$时，不改变梯度，当梯度的模大于$b$时，梯度等于$b$除以梯度的模再乘以梯度（将梯度的模缩放到$b$以内）。
</code></pre>
<p>在训练循环神经网络时，按模截断是避免梯度爆炸问题的有效方法。</p>
</li>
</ul>
<h4>1.1.4、神经网络常用优化方法的汇总</h4>
<p><img src="/assets/images/2020/20200720/networks-optimization-methods.png" alt="神经网络常用优化方法的汇总" /></p>
<p>不同优化方法的比较</p>
<p><img src="/assets/images/2020/20200720/networks-optimization-methods-compare.png" alt="不同优化方法的比较" /></p>
<h3>1.2、参数初始化</h3>
<p>当使用梯度下降法来进行优化网络参数时，参数初始值的选取十分关键，关系到网络的优化效率和泛化能力。参数初始化的方式通常有以下三种：</p>
<ol>
<li>预训练初始化：使用一个已经在大规模数据上训练过的模型提供参数初始值。</li>
<li>随机初始化</li>
<li>固定值初始化：对于一些特殊的参数，我们可以根据经验用一个特殊的固定值来进行初始化。</li>
</ol>
<p>预训练初始化通常具有更好的收敛性和泛化性，但是灵活性不够，不能在目标任务上任意地调整网络结构，所以有时还是只能使用随机初始化方法来初始化参数。</p>
<h4>1.2.1、三类常用的随机初始化方法</h4>
<p>基于固定方差的参数初始化、基于方差缩放的参数初始化和正交初始化方法。</p>
<p><em>基于固定方差的参数初始化</em></p>
<p>随机初始化方法是从一个固定均值（通常为0）和方差$\sigma^2$的分布中采样来生成参数的初始值。基于固定方差的参数初始化方法主要有以下两种：</p>
<p>高斯分布初始化：使用一个高斯分布$N(0, \ \sigma^2)$对每个参数进行随机初始化。</p>
<p>均匀分布初始化：在一个给定的区间$[r, -r]$内采用均匀分布来初始化参数。</p>
<p><em>基于方差缩放的参数初始化</em></p>
<p>初始化一个深度网络时，为了缓解梯度消失或爆炸问题，我们尽可能保持每个神经元的输入和输出的方差一致，<strong>根据神经元的连接数量来自适应地调整初始化分布的方差</strong>，这类方法称为方差缩放（Variance Scaling）。</p>
<p>Xavier 初始化和He 初始化的具体设置情况，$M_{l-1}$和$M_l$分别为输入输出的神经元个数。</p>
<p><img src="/assets/images/2020/20200720/Xavier-and-He.png" alt="Xavier 初始化和He 初始化的具体设置情况" /></p>
<p><em>正交初始化</em></p>
<p>将权重矩阵初始化为正交矩阵的方法称为正交初始化（Orthogonal Initialization），正交初始化的具体实现过程可以分为两步：</p>
<ol>
<li>用均值为0、方差为1 的高斯分布初始化一个矩阵。</li>
<li>将这个矩阵用奇异值分解得到两个正交矩阵，并使用其中之一作为权重矩阵。</li>
</ol>
<h3>1.3、数据预处理</h3>
<p>如果一个机器学习算法在缩放全部或部分特征后不影响它的学习和预测，我们就称该算法具有尺度不变性（Scale Invariance）。尺度就是取值范围。</p>
<h4>1.3.1、归一化</h4>
<p>归一化（Normalization）方法泛指把数据特征转换为相同尺度的方法，比如</p>
<ol>
<li>
<p>最小最大值归一化（Min-Max Normalization）把数据特征映射到[0, 1] 或[−1, 1] 区间内。</p>
</li>
<li>
<p>标准化（Standardization）也叫Z 值归一化（Z-Score Normalization）把数据特征映射为服从均值为0、方差为1的标准正态分布。</p>
</li>
</ol>
<h4>1.3.2、白化</h4>
<p>白化（Whitening）是一种重要的预处理方法，用来降低输入数据特征之间的冗余性．输入数据经过白化处理后，特征之间相关性较低，并且所有特征具有相同的方差．白化的一个主要实现方式是使用**主成分分析（Principal Component Analysis，PCA）**方法去除掉各个成分之间的相关性。</p>
<p>标准归一化和PCA 白化的比较</p>
<p><img src="/assets/images/2020/20200720/standardization-and-whitening.png" alt="标准归一化和PCA 白化的比较" /></p>
<h3>1.4、逐层归一化</h3>
<p>逐层归一化（Layer-wise Normalization）是对神经网络中隐藏层的输入进行归一化，从而使得网络更容易训练。</p>
<p>逐层归一化可以有效提高训练效率的原因：</p>
<ol>
<li>更好的尺度不变性，把每个神经层的输入分布都归一化为标准正态分布，可以使得每个神经层对其输入具有更好的<strong>尺度不变性</strong>。不论低层的参数如何变化，高层的输入保持相对稳定。另外，尺度不变性可以使得我们更加高效地进行参数初始化以及超参数的选择。</li>
<li>更平滑的优化地形，逐层归一化一方面可以使得大部分神经层的输入处于不饱和区域，从而让梯度变大，避免梯度消失问题；另一方面还可以使得神经网络的优化地形（Optimization Landscape）更加平滑，以及使梯度变得更加稳定，从而允许我们使用更大的学习率，并提高收敛速度。</li>
</ol>
<p>比较常用的逐层归一化方法：批量归一化、层归一化、权重归
一化和局部响应归一化。</p>
<h4>1.4.1、批量归一化</h4>
<p>批量归一化（Batch Normalization），对输出的张量（N, C, H, W），基于N x H x W个数值求均值和方差进行归一化。</p>
<h4>1.4.2、层归一化</h4>
<p>层归一化（Layer Normalization），对输出的张量（N, C, H, W），基于C x H x W个数值求均值和方差进行归一化。</p>
<h4>1.4.3、实例归一化</h4>
<p>实例归一化（Instance Normalization），对输出的张量（N, C, H, W），基于H x W个数值求均值和方差进行归一化。</p>
<h4>1.4.4、分组归一化</h4>
<p>分组归一化（Group Normalization），对输出的张量（N, C, H, W），基于G x H x W个数值求均值和方差进行归一化。</p>
<p>G是分组数，需要满足C可以整除G。</p>
<p><img src="/assets/images/2020/20200720/normalization-methods.png" alt="归一化方法" /></p>
<p><a href="https://mlexplained.com/2018/11/30/an-overview-of-normalization-methods-in-deep-learning/">参考博客：An Overview of Normalization Methods in Deep Learning</a></p>
<h4>1.4.5、权重归一化</h4>
<p>权重归一化（Weight Normalization），是对权重或者说参数进行归一化。</p>
<h4>1.4.5、局部响应归一化</h4>
<p>局部响应归一化（Local Response Normalization，LRN）和层归一化都是对同层的神经元进行归一化．不同的是，<strong>局部响应归一化应用在激活函数之后</strong>，只是对邻近的神经元进行局部归一化，并且不减去均值。邻近的神经元指对应同样位置的邻近特征映射。</p>
<p>局部响应归一化和生物神经元中的<strong>侧抑制（lateral inhibition）现象</strong>比较类似，即活跃神经元对相邻神经元具有抑制作用。当使用ReLU 作为激活函数时，神经元的活性值是没有限制的，局部响应归一化可以起到平衡和约束作用。如果一个神经元的活性值非常大，那么和它邻近的神经元就近似地归一化为0，从而起到抑制作用，增强模型的泛化能力。<strong>最大汇聚</strong>也具有侧抑制作用．但最大汇聚是对同一个特征映射中的邻近位置中的神经元进行抑制，而局部响应归一化是对同一个位置的邻近特征映射中的神经元进行抑制．</p>
<h3>1.5、超参数优化</h3>
<p>常见的超参数有以下三类：</p>
<ol>
<li>网络结构，包括神经元之间的连接关系、层数、每层的神经元数量、激活函数的类型等。</li>
<li>优化参数，包括优化方法、学习率、小批量的样本数量等。</li>
<li>正则化系数。</li>
</ol>
<p>超参数优化（Hyperparameter Optimization）主要存在两方面的困难：</p>
<ol>
<li>超参数优化是一个组合优化问题，无法像一般参数那样通过梯度下降方法来优化，也没有一种通用有效的优化方法；</li>
<li>评估一组超参数配置（Configuration）的时间代价非常高，从而导致一些优化方法（比如演化算法（Evolution Algorithm））在超参数优化中难以应用。</li>
</ol>
<p>对于超参数的配置，比较简单的方法有网格搜索、随机搜索、贝叶斯优化、动态资源分配和神经架构搜索。（都是搜索超参数的值的策略）</p>
<h4>1.5.1、网格搜索</h4>
<p>网格搜索（Grid Search）是一种通过尝试所有超参数的组合来寻址合适一组超参数配置的方法。假设有K个超参数，第K个超参数有可能合适的值$m_k$个。那么总共的配置组合数量为$m_1<em>m_2</em> \dotsc  * m_K$，每次尝试一组，最后选择最好的训练结果下对应的超参数值。</p>
<h4>1.5.2、随机搜索</h4>
<p>采用网格搜索会在不重要的超参数上进行不必要的尝试。一种在实践中比较有效的改进方法是对超参数进行随机组合，然后选取一个性能最好的配置，这就是随机搜索（Random Search）。</p>
<h4>1.5.3、贝叶斯优化</h4>
<p>贝叶斯优化（Bayesian optimization）是一种自适应的超参数优化方法。根据当前已经试验的超参数组合，来预测下一个可能带来最大收益的组合。</p>
<h4>1.5.4、动态资源分配</h4>
<p>在超参数优化中，每组超参数配置的评估代价比较高。如果我们可以在较早的阶段就估计出一组配置的效果会比较差，那么我们就可以中止这组配置的评估，将更多的资源留给其他配置。（相当于做一个提前停止（Early-Stopping））。</p>
<p>这个问题可以归结为多臂赌博机问题的一个泛化问题：最优臂问题（Best-Arm Problem），即在给定有限的机会次数下，如何玩这些赌博机并找到收益最大的臂。和多臂赌博机问题类似，最优臂问题也是在利用和探索之间找到最佳的平衡。</p>
<p>有一种策略是逐次减半（Successive Halving），我们可以通过$T=  \lceil log_2(N) - 1 \rceil$轮逐次减半的方法来选取最优的配置，每次都选取一半最优资源，并固定住。</p>
<h4>1.5.5、神经架构搜索</h4>
<p>神经架构搜索，通过神经网络来自动实现网络架构的设计。利用元学习的思想，神经架构搜索利用一个控制器来生成另一个子网络的架构描述。控制器可以由一个循环神经网络来实现。控制器的训练可以通过强化学习来完成，其奖励信号为生成的子网络在开发集上的准确率。（用神经网络来自动实现网络架构）</p>
<h2>2、网络正则化</h2>
<p>正则化（Regularization）是一类通过限制模型复杂度，从而避免过拟合，提高泛化能力的方法。</p>
<p>在传统的机器学习中，提高泛化能力的方法主要是限制模型复杂度，比如采用$l1$和$l2$正则化等方式。而在训练深度神经网络时，特别是在过度参数化（Over-Parameterization）时，$l1$和$l2$正则化的效果往往不如浅层机器学习模型中显著。过度参数化是指模型参数的数量远远大于训练数据的数量。因此训练深度学习模型时，往往还会使用其他的正则化方法，比如数据增强、提前停止、丢弃法、集成法等。</p>
<h3>2.1、$l1$和$l2$正则化</h3>
<p>$l1$和$l2$正则化是机器学习中最常用的正则化方法，通过约束参数的$l1$和$l2$范数来减小模型在训练数据集上的过拟合现象。</p>
<p>同时加$l1$和$l2$正则化，称为弹性网络正则化（Elastic Net Regularization</p>
<h3>2.2、权重衰减</h3>
<p>权重衰减（Weight Decay），在每次参数更新时，引入一个衰减系数</p>
<p>$$
\theta_t \leftarrow (1 - \beta)\theta_{t-1} - \alpha {\rm g}_t
$$</p>
<p>其中${\rm g}_t$为第$t$步更新时的梯度，$\alpha$为学习率，$\beta$为权重衰减系数，一般取值比较小，比如0.0005。</p>
<h3>2.3、提前停止</h3>
<p>提前停止（Early Stop），使用梯度下降法进行优化时，我们可以使用一个和训练集独立的样本集合，称为验证集（Validation Set），并用验证集上的错误来代替期望错误。当验证集上的错误率不再下降，就停止迭代。</p>
<h3>2.4、丢弃法</h3>
<p>当训练一个深度神经网络时， 我们可以随机丢弃一部分神经元（同时丢弃其对应的连接边）来避免过拟合，这种方法称为丢弃法（Dropout Method）</p>
<p>丢弃法一般是针对神经元进行随机丢弃，但是也可以扩展到对神经元之间的连接进行随机丢弃，或每一层进行随机丢弃。</p>
<p>在循环神经网络上的应用丢弃法，有两种方法：</p>
<ol>
<li>对非时间维度的连接（即非循环连接）进行随机丢失。</li>
<li>对参数矩阵的每个元素进行随机丢弃，并在所有时刻都使用相同的丢弃掩码（丢弃掩码定义如下）。</li>
</ol>
<p>掩蔽函数$mask(\cdot)$，</p>
<p>$$
mask(\cdot) =
\begin{cases}
m \odot x &amp; 当训练阶段时 \
p x &amp; 当测试阶段时
\end{cases}
$$</p>
<p>$m \in {0, 1}^D$是丢弃掩码（Dropout Mask）,$p$为保留的概率。</p>
<h3>2.5、数据增强</h3>
<p>深度神经网络一般都需要大量的训练数据才能获得比较理想的效果．在数据量有限的情况下，可以通过数据增强（Data Augmentation）来增加数据量，提高模型鲁棒性，避免过拟合。</p>
<p>增强的方法主要有以下几种：</p>
<ol>
<li>旋转（Rotation）：将图像按顺时针或逆时针方向随机旋转一定角度。</li>
<li>翻转（Flip）：将图像沿水平或垂直方法随机翻转一定角度。</li>
<li>缩放（Zoom In/Out）：将图像放大或缩小一定比例。</li>
<li>平移（Shift）：将图像沿水平或垂直方法平移一定步长。</li>
<li>加噪声（Noise）：加入随机噪声。</li>
</ol>
<h3>2.6、标签平滑</h3>
<p>在数据增强中，我们可以给样本特征加入随机噪声来避免过拟合．同样，我们也可以<strong>给样本的标签引入一定的噪声</strong>。假设训练数据集中有一些样本的标签是被错误标注的，那么最小化这些样本上的损失函数会导致过拟合。一种改善的正则化方法是标签平滑（Label Smoothing），即在输出标签中添加噪声来避免模型过拟合。</p>
<h2>3、总结</h2>
<p><em>提高深度神经网络训练效率的方法</em>，通常分为以下3个方面：</p>
<ol>
<li>修改网络模型来得到更好的优化地形，比如使用逐层归一化、残差连接以及ReLU 激活函数等。</li>
<li>使用更有效的优化算法，比如动态学习率以及梯度估计修
正等。</li>
<li>使用更好的参数初始化方法。</li>
</ol>
<p><em>提升深度神经网络的泛化能力</em>，在传统机器学习模型上$l1$和$l2$正则化比较有效，而在深度神经网络中作用比较有限，而一些经验的做法（比如小的批量大小、大的学习率、提前停止、丢弃法、数据增强）会更有效。</p>
<p>先总结到这，开始实践了。</p>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【折腾记录】解析SSR订阅信息，将SSR信息保存到二维码中并保存到PDF中]]></title>
            <link>https://bingqiangzhou.github.io/posts/dailyjungle-parsessrsubscriptionsavessrqrcodeintopdf/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/dailyjungle-parsessrsubscriptionsavessrqrcodeintopdf/</guid>
            <pubDate>Sun, 19 Jul 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[今天放弃了收费的shadowsocks小火箭，转向了免费的potatso lite，这个过程是挺有意思的。]]></description>
            <content:encoded><![CDATA[<p>今天放弃了收费的<code>shadowsocks</code>小火箭，转向了免费的<code>potatso lite</code>，这个过程是挺有意思的。</p>
<p>放弃<code>shadowsocks</code>的原因是：它经常更新，有时候一天一更，而我又有强迫症，它一更新我就想更新，不仅如此，共享的<code>Apple ID</code>也经常要换，每次更换ID之后，<code>shadowsocks</code>都需要重新下载，SSR订阅也需要更新添加，这个过程操作太多了，放弃了。</p>
<p>放弃了<code>shadowsocks</code>，需要一个新的替代方案，刚开始，我想的是自己编译<a href="https://github.com/WuChuming/shadowsocks-iOS"><code>shadowsocks</code></a>，还没开始尝试，又在GitHub找到了一个提供了<code>ipa</code>文件的项目，<a href="https://github.com/Jigsaw-Code/outline-client">Outline clients</a>，这时问题又转移了，变成了如何安装<code>ipa</code>文件的问题，第一想法是使用爱思助手安装，可惜的是爱思助手还没有支持<code>macOS Big Sur</code>，我有需要转向其他方法，看到知乎的文章<a href="https://zhuanlan.zhihu.com/p/95760925">如何在手机上安装IPA? 讲四种方法！再也不怕应用下架了！</a>，准备使用捷径来操作，而还没等操作，看到了去年玩捷径时的留下的<code>TestFlight</code>软件大全就执行了一下，这一执行，又发现了新大陆，其中有支持<code>shadowsocks</code>的app，不过人数都满了，我开始搜支持<code>shadowsocks</code>的app内测链接，然后搜到了这个<a href="https://www.igfw.net/archives/13944">TestFlight 邀请分享：iOS 应用 测试版收集清单</a>，其中有十几个符合要求的应用，而且有一个<code>potatso lite</code>的测试人数没满，我开始了测试，很快，我就遇到了两个问题，订阅无法解析，而且还有广告，这时又有点忍不了了，开始又开始搜了起来，然后发现<code>potatso lite</code>是免费了，我登上了我美区的<code>Apple ID</code>，下载了并发现没有广告，虽然用其他方式也可以SSR连接信息，但是我还是开始研究订阅的问题。</p>
<h2>订阅连接</h2>
<p>通过<a href="https://github.com/xiaoliang8006/SSR">ss和ssr链接解析</a>，知道了<code>shadowsocks</code>链接是通过<code>Base64</code>进行编码的，尝试用<code>Base64</code>订阅连接返回结果，得到了许多SSR链接，中间以<code>\n</code>隔开，而其中SSR链接的编码方式是<a href="https://github.com/xiaoliang8006/SSR">ss和ssr链接解析</a>中说的方式，得到了SSR链接之后，使用<code>potatso lite</code>添加服务器，并不太方便，所以想着把它弄成二维码，而二维码图片太多也不好传文件，所以将它们保存到PDF中。</p>
<h2>将SSR链接保存到二维码中</h2>
<p>将信息存到二维码中，使用的是<a href="https://pypi.org/project/qrcode/">qrcode</a>模块，简单易用。</p>
<pre><code>import qrcode
img = qrcode.make('Some data here')
</code></pre>
<h2>PDF将图片存到pdf文件中</h2>
<p>通过poillow模块<a href="https://datatofish.com/images-to-pdf-python/">Convert Images to PDF using Python</a>，简单示例如下。</p>
<pre><code>from PIL import Image

image1 = Image.open(r'\Users\Ron\Desktop\Test\image1.png')
image2 = Image.open(r'\Users\Ron\Desktop\Test\image2.png')
image3 = Image.open(r'\Users\Ron\Desktop\Test\image3.png')

im1 = image1.convert('RGB')
im2 = image2.convert('RGB')
im3 = image3.convert('RGB')

imagelist = [im2, im3]

im1.save(r'\Users\Ron\Desktop\Test\myImages.pdf',save_all=True, append_images=imagelist)
</code></pre>
<p><a href="https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html?highlight=pdf#pdf">其中<code>save_all</code>是必须等于True的</a></p>
<p>通过<a href="https://github.com/alexanderankin/pyfpdf">FPDF模块</a>，简单示例如下。</p>
<pre><code>from fpdf import FPDF

document = FPDF()
document.add_page()
document.set_font('Arial', size=12)
document.cell(w=0, txt="hello world")
document.output("hello_world.pdf")
</code></pre>
<h2>pip安装来自Github的包</h2>
<p>示例如下，参考链接为<a href="https://medium.com/i-want-to-be-the-very-best/installing-packages-from-github-with-conda-commands-ebf10de396f4">Using the Command Line to Install Packages from GitHub</a></p>
<p>GitHub链接：<code>https://github.com/yourname/projectname</code></p>
<pre><code>pip install git+git://github.com/yourname/projectname.git
</code></pre>
<h2>完整代码</h2>
<p>最简单的方式是解析好，放在腾讯服务器上。</p>
<pre><code>import base64
import requests
from pprint import pprint
import pandas as pd
import qrcode
import os
from PIL import Image
from fpdf import FPDF

def fill_padding(base64_encode_str):

    need_padding = len(base64_encode_str) % 4 != 0
    if need_padding:
        missing_padding = 4 - need_padding
        base64_encode_str += '=' * missing_padding
    return base64_encode_str

def base64_decode(base64_encode_str):
    base64_encode_str = fill_padding(base64_encode_str)
    return base64.urlsafe_b64decode(base64_encode_str).decode('utf-8')

subscription_url = 'XXXXXXXXXXXX'

response = requests.get(subscription_url)
if response.status_code == 200:
    ssr_base64_strs = response.text
    ssr_strs = base64_decode(ssr_base64_strs)
    ssr_list = ssr_strs.split('\n')

    qrcodes_dir = 'ssr qrcodes'
    if os.path.exists(qrcodes_dir) == False:
            os.mkdir(qrcodes_dir)
    ssr_names = []
    pdf = FPDF('P', 'mm', (240, 240))
    for i, ssr in enumerate(ssr_list):
        temp_ssr = ssr.replace('ssr://', '').replace('_', '/').replace('-', '+')
        ssr_name = base64_decode(temp_ssr).split('.')[0].split('-')[0]
        ssr_names.append(ssr_name)
        pdf.add_page()
        pdf.set_font('Arial', 'B', 16)
        pdf.cell(20, 10, ssr_name)
        img = qrcode.make(ssr)
        img_name = os.path.join(qrcodes_dir, ssr_name + '.png')
        img.save(img_name)
        pdf.image(img_name, 20, 20, 200)
    pdf.output(qrcodes_dir+'.pdf', 'F')
    ssrs_dict = {'server name': ssr_names, 'ssr link': ssr_list}
    df = pd.DataFrame.from_dict(ssrs_dict)
    df.to_csv('ssr_links.txt', sep='\n', header=False, index=False)
</code></pre>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【学习笔记】记录一下torch.backends.cudnn.benchmark=True以及copy、multiprocessing与threading模块]]></title>
            <link>https://bingqiangzhou.github.io/posts/dailysummary-codereadinginstancesegmentationbyjointlyoptimizingspatialembeddingsandclusteringbandwidth/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/dailysummary-codereadinginstancesegmentationbyjointlyoptimizingspatialembeddingsandclusteringbandwidth/</guid>
            <pubDate>Sat, 04 Jul 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[今天晚上跑了一下《Instance Segmentation by Jointly Optimizing Spatial Embeddings and Clustering Bandwidth》论文中的验证的代码，这里记录一下一些之前不知道或者不熟练的点。]]></description>
            <content:encoded><![CDATA[<p>今天晚上跑了一下<a href="/posts/paperreading-instancesegmentationbyjointlyoptimizingspatialembeddingsandclusteringbandwidth/">《Instance Segmentation by Jointly Optimizing Spatial Embeddings and Clustering Bandwidth》</a>论文中的验证的代码，这里记录一下一些之前不知道或者不熟练的点。</p>
<h2>1、<code>torch.backends.cudnn.benchmark = True</code></h2>
<p>这一项设置呢，是打开<code>cudnn</code>的<code>benchmark mode</code>，开启这个基准模式之后，cudnn将会自动的寻找适应于硬件的最佳算法来计算模型，而这个寻找的过程会花费一些时间，当输入固定时，可以选择开启，可以加速，而当输入不固定，经常变的时候，就无需开启了，开启之后反而可能会花费更多的时间。</p>
<p><a href="https://discuss.pytorch.org/t/what-does-torch-backends-cudnn-benchmark-do/5936">参考：What does torch.backends.cudnn.benchmark do?</a></p>
<h2>2、python内置的库<code>copy</code></h2>
<p><code>copy</code>库主要有两个方法：</p>
<ul>
<li>copy.copy(x)，浅拷贝</li>
<li>copy.deepcopy(x[, memo])，深拷贝</li>
</ul>
<p>深拷贝与浅拷贝的区别在于对复合对象（对象中包含着对象）处理的不同：</p>
<p>浅拷贝，新建一个复合对象，把原来复合对象中的<strong>对象</strong>直接插入进来。浅拷贝后的复合对象，改变复合对象中的对象时，会改变之前的这个对象，如下图。
<img src="/assets/images/2020/20200704/shallow-copy.png" alt="浅拷贝" /></p>
<p>深拷贝，新建一个复合对象，把原来复合对象中的<strong>对象的副本</strong>插入进来。深拷贝后的复合对象，改变复合对象中的对象时，不会改变之前的这个对象，如下图。
<img src="/assets/images/2020/20200704/deep-copy.png" alt="深拷贝" /></p>
<p><a href="https://docs.python.org/3/library/copy.html">参考：copy — Shallow and deep copy operations</a></p>
<h2>3、 python内置的库<code>multiprocessing</code>与<code>threading</code></h2>
<p>这两个库分别是多进程、多线程操作的库。下面给出<code>multiprocessing</code>最基础的例子，<code>threading</code>模块这次先不展开讲。</p>
<p><a href="https://docs.python.org/3/library/multiprocessing.html#module-multiprocessing.pool">Pool对象</a>方便的提供了并行执行有多个输入的方法，它会将输入分发给不同的进程。</p>
<pre><code>from multiprocessing import Pool

def f(x):
    return x*x

if __name__ == '__main__':
    with Pool(5) as p:
        print(p.map(f, [1, 2, 3]))
</code></pre>
<p><a href="https://docs.python.org/3/library/multiprocessing.html#the-process-class">Process类</a>开启一个新进程。</p>
<pre><code>from multiprocessing import Process

def f(name):
    print('hello', name)

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()
</code></pre>
<p><a href="https://docs.python.org/3/library/multiprocessing.html">参考：multiprocessing — Process-based parallelism</a></p>
<p><a href="https://docs.python.org/3/library/threading.html">参考：threading — Thread-based parallelism</a></p>
<h2>小结</h2>
<p>还有太多的东西要学，继续加油，多学习，多总结，哈哈！</p>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【学习笔记】分割任务常用数据集：VOC、COCO、Cityscapes]]></title>
            <link>https://bingqiangzhou.github.io/posts/dailysummary-datasetsvoc2012coco2017cityscapes/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/dailysummary-datasetsvoc2012coco2017cityscapes/</guid>
            <pubDate>Wed, 01 Jul 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[今天加了一个“支线任务”页面，之前的图标没有支持在MacOS 11中Safari浏览器的标签页的显示，然后换成了现在这个图标，主要参考：添加新的页面、更换图标，图标来源于flaticon。]]></description>
            <content:encoded><![CDATA[<p>今天加了一个“支线任务”页面，之前的图标没有支持在MacOS 11中Safari浏览器的标签页的显示，然后换成了现在这个图标，主要参考：<a href="https://tianqi.name/jekyll-TeXt-theme/docs/en/navigation">添加新的页面</a>、<a href="https://tianqi.name/jekyll-TeXt-theme/docs/en/logo-and-favicon">更换图标</a>，图标来源于<a href="https://www.flaticon.com/free-icons/china">flaticon</a>。</p>
<p>这两天呢，一直在弄数据集，为什么弄数据集呢，这个故事的开始是悲伤的，我在<code>VS Code</code>中连接服务器，想要删除数据集中多余的文件，先是选择了数据集文件夹，然后又直接选择了想删除的文件，接着点击了永久删除，这时就悲剧了，我尝试了去恢复数据，奈何没有管理员权限，最后选择了重新下载（下载过程真是...），顺带把几个数据集都大致了解一下，总结一下。</p>
<h2><a href="http://host.robots.ox.ac.uk/pascal/VOC/voc2012/">VOC 2012</a></h2>
<p>Visual Object Classes Challenge 2012 (VOC2012)</p>
<h3>1.1、简单介绍</h3>
<p>数据集包含了20个分类（如下），后面的括号是按A-Z排序后的编号，也是语义分割标注的像素值（0表示背景，255表示对象的轮廓，如下图）</p>
<ul>
<li>
<p>Person: person(15)</p>
</li>
<li>
<p>Animal: bird(3), cat(8), cow(10), dog(12), horse(13), sheep(17)</p>
</li>
<li>
<p>Vehicle: aeroplane(1), bicycle(2), boat(4), bus(6), car(7), motorbike(14), train(19)</p>
</li>
<li>
<p>Indoor: bottle(5), chair(9), dining table(11), potted plant(16), sofa(19), tv/monitor(20)</p>
</li>
</ul>
<p><img src="/assets/images/2020/20200701/voc-segmentation-label.jpeg" alt="语义分割标注" /></p>
<p>这个数据可以完成如下任务：</p>
<ul>
<li>分类（可以区分上面的20类，还可以区分动作<code>Action Classification</code>，包括如下动作）
<ul>
<li>Jumping</li>
<li>Phoning</li>
<li>PlayingInstrument</li>
<li>Reading</li>
<li>RidingBike</li>
<li>RidingHorse</li>
<li>Running</li>
<li>TakingPhoto</li>
<li>UsingComputer</li>
<li>Walking</li>
</ul>
</li>
<li>检测（不仅可以检测整个对象，还能检测人的部分<code>Person Layout</code>，不过只限于检测头、手、脚）
<ul>
<li>head</li>
<li>hands</li>
<li>feet</li>
</ul>
</li>
<li>分割（包括语义分割与实例分割）
<ul>
<li>总共有2913张图片，训练集1464张，验证集1449张，当然可以根据自己的想法调整训练集验证集图片的数量。</li>
</ul>
</li>
</ul>
<h3>1.2、下载数据集</h3>
<ul>
<li>
<p>通过命令下载</p>
<pre><code>wget -c http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar
</code></pre>
</li>
<li>
<p>通过torchvision.datasets下载</p>
<p>设置一下下载到哪个文件夹下（<code>root</code>参数），然后将<code>download</code>参数设置为<code>True</code>。</p>
<p><strong>我个人比较推荐这种方法，比较简单，主要是它会自动解压好。</strong></p>
<pre><code>import torchvision

root = './'
torchvision.datasets.VOCSegmentation(root, year='2012', image_set='trainval', download=True, transform=None, target_transform=None, transforms=None)
</code></pre>
</li>
</ul>
<h3>1.3、加载数据集</h3>
<p>使用torchvision.datasets加载数据集较为简单，“站在巨人的肩膀上”。</p>
<ul>
<li>
<p>做分类的话，使用如下代码，<a href="https://pytorch.org/docs/stable/torchvision/datasets.html#torchvision.datasets.VOCDetection">pytorch文档</a></p>
<pre><code>torchvision.datasets.VOCDetection(root, year='2012', image_set='train', download=False, transform=None, target_transform=None, transforms=None)
</code></pre>
</li>
<li>
<p>做分割的话，可以使用一下代码，从pytorch，<a href="https://pytorch.org/docs/stable/torchvision/datasets.html#torchvision.datasets.VOCSegmentation"><code>torchvision.datasets.VOCSegmentation</code></a>源码中改过来的。</p>
<p><strong>官方代码只适应语义分割任务，下面的代码通过<code>target_type</code>参数控制返回不同的目标。（语义分割<code>target_type='Class'</code>，实例分割<code>target_type='Object'</code>）</strong></p>
<pre><code>import os
from torchvision.datasets.vision import VisionDataset
from PIL import Image

class VOCSegmentation(VisionDataset):
    def __init__(self,
                root,
                target_type='Object',
                image_set='train',
                transform=None,
                target_transform=None,
                transforms=None):
        super(VOCSegmentation, self).__init__(root, transforms, transform, target_transform)

        assert target_type in ['Object', 'Class'], "target_type must in ['Object', 'Class']"

        base_dir = 'VOCdevkit/VOC2012'
        voc_root = os.path.join(self.root, base_dir)
        image_dir = os.path.join(voc_root, 'JPEGImages')
        mask_dir = os.path.join(voc_root, 'Segmentation'+target_type)

        if not os.path.isdir(voc_root):
            raise RuntimeError('Dataset not found or corrupted.' +
                            ' You can use download=True to download it')

        splits_dir = os.path.join(voc_root, 'ImageSets/Segmentation')

        split_f = os.path.join(splits_dir, image_set.rstrip('\n') + '.txt')

        with open(os.path.join(split_f), "r") as f:
            file_names = [x.strip() for x in f.readlines()]

        self.images = [os.path.join(image_dir, x + ".jpg") for x in file_names]
        self.masks = [os.path.join(mask_dir, x + ".png") for x in file_names]
        assert (len(self.images) == len(self.masks))

    def __getitem__(self, index):
        img = Image.open(self.images[index]).convert('RGB')
        target = Image.open(self.masks[index])

        if self.transforms is not None:
            img, target = self.transforms(img, target)

        return img, target

    def __len__(self):
        return len(self.images)
</code></pre>
</li>
</ul>
<h3>1.4、提取对象的标记</h3>
<p>获取对象对应的像素值，列表第一个值为背景值，这种方式比较简单，还有一种是通过颜色<code>color map</code>来判断的，这部分内容放到最后<a href="%E6%8B%93%E5%B1%95%EF%BC%9A%E9%A2%9C%E8%89%B2%E8%BD%AC%E6%8D%A2">拓展</a>来讲。</p>
<pre><code>def get_target_ids(target):
    # 提取对象对应的像素值，将背景（0），轮廓（255）结合到一起
    ids = [ iid if iid != 0 else [0, 255] for iid in np.unique(target) ]
    ids = ids[0:-1]
#     print(ids)
    return ids
</code></pre>
<h4>1.4.1、二值masks（0为背景，1为对象）</h4>
<pre><code># 二值mask（0为背景，1为对象）
def get_object_masks(target, ids) -&gt; 'np.ndarray (n, h, w)':
    target = np.array(target)

    # 提取mask列表，并堆叠到一起
    mask_list = []
    for j, iid in enumerate(ids):
        if j == 0:
            mask = np.isin(target, iid)
        else:
            mask = np.where(target == iid, 1, 0)
        mask_list.append(mask)
    masks = np.stack(mask_list, axis=0)

    return masks
</code></pre>
<h4>1.4.2、数字标号的mask（0为背景，1...n为对象的值）</h4>
<pre><code># 数字标号的mask（0为背景，1...n为对象的值）
def get_object_nums_mask(target, ids) -&gt; 'np.ndarray (h, w)':
    target = np.array(target)

    # 将对象对应的标号加到一起（一个像素不会对应两个值）
    nums_mask = np.zeros_like(target)
    for j, iid in enumerate(ids):
        if j == 0:
            continue
        else:
            mask = np.where(target == iid, j, 0)
        nums_mask = nums_mask + mask
    return nums_mask
</code></pre>
<h2><a href="https://cocodataset.org/#download">COCO 2017</a></h2>
<p>Common Objects in Context (COCO)</p>
<h3>2.1、简单介绍</h3>
<p>COCO数据集也是支持多种任务：分类、检测（物体检测、关键点检测、姿态检测）、图片字幕、分割（包括全景分割、实例分割、语义分割）</p>
<p>COCO2017 训练集有118287张图片，验证集有5000张图片。</p>
<p>不多说了，直接进行下一步吧。</p>
<h3>2.2、下载数据集</h3>
<p>下面是用命令下载，可能下载会比较慢，可以使用迅雷下载，然后上传到服务器，也可以使用axel工具。（<a href="https://zhuanlan.zhihu.com/p/89232542">参考:知乎-命令行的“迅雷”，提升百倍以上下载速率</a>）</p>
<ul>
<li>
<p>下载并解压训练集</p>
<pre><code>wget -c http://images.cocodataset.org/zips/train2017.zip

mkdir COCO2017

mv train2017.zip ./COCO2017/train2017.zip

unzip -d ./COCO2017/  ./COCO2017/train2017.zip
</code></pre>
</li>
<li>
<p>下载并解压验证集</p>
<pre><code>wget -c http://images.cocodataset.org/zips/val2017.zip

mv val2017.zip ./COCO2017/val2017.zip

unzip -d ./COCO2017/ ./COCO2017/val2017.zip
</code></pre>
</li>
<li>
<p>下载并解压训练集与验证的集的标注</p>
<pre><code>wget -c http://images.cocodataset.org/annotations/annotations_trainval2017.zip

mv annotations_trainval2017.zip ./COCO2017/annotations_trainval2017.zip

unzip -d ./COCO2017/ ./COCO2017/annotations_trainval2017.zip
</code></pre>
</li>
</ul>
<h3>2.3、加载数据集</h3>
<h4>2.3.1、下载COCO API</h4>
<ul>
<li>
<p>pip</p>
<pre><code>pip3 install pycocotools
</code></pre>
</li>
<li>
<p>conda</p>
<pre><code>conda install -c conda-forge pycocotools -y
</code></pre>
</li>
</ul>
<h4>2.3.2、加载数据集</h4>
<p>加载数据集的target并不是图像，而是json字符串，包含了图片的信息，包括图片高宽、边框信息、图片ID、多边形点标注或者Run-length encoding (RLE)格式，具体格式根据任务不同，可以通过<a href="https://cocodataset.org/#format-data">COCO官方给出的Data format介绍</a></p>
<ul>
<li>
<p>通过torchvision.datasets</p>
<pre><code>import torchvision
import matplotlib.pyplot as plt

root = r'COCO2017/train2017'
annFile_train = r'./COCO2017/annotations/instances_train2017.json'
annFile_val = r'./COCO2017/annotations/instances_val2017.json'

coco_dataset_train = torchvision.datasets.CocoDetection(root, annFile_train, transform=None, target_transform=None, transforms=None)

coco_dataset_val = torchvision.datasets.CocoDetection(root, annFile_val, transform=None, target_transform=None, transforms=None)
</code></pre>
<p><code>图像字幕</code>可以通过<a href="https://pytorch.org/docs/stable/torchvision/datasets.html#torchvision.datasets.CocoCaptions"><code>torchvision.datasets.CocoCaptions(root, annFile, transform=None, target_transform=None, transforms=None)</code></a>来加载。</p>
</li>
<li>
<p>通过pycocotools</p>
<pre><code>import os
from pycocotools.coco import COCO
from PIL import Image

root = r'COCO2017/train2017'
annFile_train = r'./COCO2017/annotations/instances_train2017.json'
annFile_val = r'./COCO2017/annotations/instances_val2017.json'

coco = COCO(annFile_train)
ids = list(sorted(coco.imgs.keys()))
for i, img_id in enumerate(ids):
    img_path = coco.loadImgs(img_id)[0]['file_name']
    img = Image.open(os.path.join(root, img_path)).convert('RGB') # 加载原图

    ann_ids = coco.getAnnIds(imgIds=img_id)
    target = coco.loadAnns(ann_ids) #加载target
</code></pre>
<p>pycocotools <a href="https://github.com/cocodataset/cocoapi">github地址</a></p>
<p>最主要的python源文件有两个：<a href="https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocotools/coco.py">coco.py</a>、<a href="https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocotools/mask.py">mask.py</a>、<a href="https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocotools/cocoeval.py">cocoeval.py</a>，下面是这几个文件里API的说明。</p>
<ul>
<li>
<p>coco.py</p>
<pre><code># The following API functions are defined:
#  COCO       - COCO api class that loads COCO annotation file and prepare data structures.
#  decodeMask - Decode binary mask M encoded via run-length encoding.
#  encodeMask - Encode binary mask M using run-length encoding.
#  getAnnIds  - Get ann ids that satisfy given filter conditions.
#  getCatIds  - Get cat ids that satisfy given filter conditions.
#  getImgIds  - Get img ids that satisfy given filter conditions.
#  loadAnns   - Load anns with the specified ids.
#  loadCats   - Load cats with the specified ids.
#  loadImgs   - Load imgs with the specified ids.
#  annToMask  - Convert segmentation in an annotation to binary mask.
#  showAnns   - Display the specified annotations.
#  loadRes    - Load algorithm results and create API for accessing them.
#  download   - Download COCO images from mscoco.org server.
# Throughout the API "ann"=annotation, "cat"=category, and "img"=image.
# Help on each functions can be accessed by: "help COCO&gt;function".
</code></pre>
</li>
<li>
<p>mask.py</p>
<pre><code># The following API functions are defined:
#  encode         - Encode binary masks using RLE.
#  decode         - Decode binary masks encoded via RLE.
#  merge          - Compute union or intersection of encoded masks.
#  iou            - Compute intersection over union between masks.
#  area           - Compute area of encoded masks.
#  toBbox         - Get bounding boxes surrounding encoded masks.
#  frPyObjects    - Convert polygon, bbox, and uncompressed RLE to encoded RLE mask.
</code></pre>
</li>
<li>
<p>cocoeval.py</p>
<pre><code># The usage for CocoEval is as follows:
#  cocoGt=..., cocoDt=...       # load dataset and results
#  E = CocoEval(cocoGt,cocoDt); # initialize CocoEval object
#  E.params.recThrs = ...;      # set parameters as desired
#  E.evaluate();                # run per image evaluation
#  E.accumulate();              # accumulate per image results
#  E.summarize();               # display summary metrics of results
# For example usage see evalDemo.m and http://mscoco.org/.
</code></pre>
</li>
</ul>
</li>
</ul>
<h3>2.4、提取对象的标签标记</h3>
<p>这里主要以通过torchvision.datasets加载数据集方式下的<code>target</code>，而通过pycocotools加载数据集下的<code>target</code>有些不同，他包括了更多信息，可以通过下面这句代码转为通过torchvision.datasets加载数据集方式下的<code>target</code>。</p>
<pre><code>target = target['Objects']
</code></pre>
<p>以下两个方法中的coco_object参数是COCO类实例化的对象，可以使用torchvision.datasets数据集对象中的coco属性调用，如：<code>coco_dataset_train.coco</code>，或者是pycocotools中COCO类实例化的对象。</p>
<h4>2.4.1、二值masks（0为背景，1为对象）</h4>
<pre><code>def get_object_masks(coco_object, target):

    bg = 0
    mask_list = []
    for j, ann in enumerate(target):
        mask = coco_object.annToMask(ann)
        mask_list.append(mask)
        bg = bg + mask

    bg = np.where(bg == 0, 1, 0)

    mask_list.insert(0, bg)
    masks = np.stack(mask_list, axis=0)

    return masks
</code></pre>
<h4>2.4.2、数字标号的mask（0为背景，1...n为对象的值）</h4>
<p>COCO数据集中有对象有重叠，下面的方法有去掉重叠像素（后面的对象像素点位置在赋值的时候，如果已经有对象赋值了则不再赋值，如下图第三张），去掉重叠对象（后面的对象如果与已有对象有重叠则跳过，如下图第五张），以上方法是不够好的，可能应该直接不使用数字标号的mask（这种mask主要为softmax服务），而使用二值mask做二分类。（如下图所示，第一张图为原图，第二站图为有标注遮罩的图，第三张图为没有去重叠的图, 第四张图为去掉重叠元素的像素点的图，下面的第一段代码，第五张图为去掉重叠元素的像素点的图，下面的第二段代码）</p>
<p><img src="/assets/images/2020/20200701/coco-dataset-nums-mask.png" alt="coco" /></p>
<pre><code>def get_object_nums_mask_remove_overlap_pixel(coco_object, target):

    nums_mask = []
    for j, ann in enumerate(target):
        mask = coco_object.annToMask(ann)
        if j == 0:
            nums_mask = np.zeros_like(mask)
        # 去掉了后面有重叠的像素点
        nums_mask = nums_mask + np.where((mask - (nums_mask &gt; 0))==1, j+1, 0)

    return nums_mask
</code></pre>
<pre><code>def get_object_nums_mask_remove_overlap_object(coco_object, target):

    nums_mask = []
    nums_object = 1
    for j, ann in enumerate(target):
        mask = coco_object.annToMask(ann)

        if j == 0:
            nums_mask = np.zeros_like(mask)

        # 去掉了后面有重叠的像素点的对象
        if ((mask - (nums_mask == 0)) == 1).any():
            break

        nums_mask = nums_mask + np.where(mask == 1, nums_object, 0)
        nums_object = nums_object + 1

    return nums_mask
</code></pre>
<h2><a href="https://www.cityscapes-dataset.com/">Cityscapes</a></h2>
<h3>3.1、简单介绍</h3>
<p>Cityscapes数据集呢，主要是车行驶在各个城市的图像，图像比较大（1024*2048），主要用于分割，检测等任务，这里就不多说了，看下面的数据集信息吧。</p>
<p><img src="/assets/images/2020/20200701/cityscape-dataset.png" alt="cityscapes dataset" /></p>
<table>
<thead>
<tr>
<th>文件名</th>
<th>packageID</th>
<th>md5值</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://www.cityscapes-dataset.com/file-handling/?packageID=1"><code>gtFine_trainvaltest.zip</code></a></td>
<td>1</td>
<td>4237c19de34c8a376e9ba46b495d6f66</td>
</tr>
<tr>
<td><a href="https://www.cityscapes-dataset.com/file-handling/?packageID=2"><code>gtCoarse.zip</code></a></td>
<td>2</td>
<td>1c7b95c84b1d36cc59a9194d8e5b989f</td>
</tr>
<tr>
<td><a href="https://www.cityscapes-dataset.com/file-handling/?packageID=3"><code>leftImg8bit_trainvaltest.zip</code></a></td>
<td>3</td>
<td>0a6e97e94b616a514066c9e2adb0c97f</td>
</tr>
<tr>
<td><a href="https://www.cityscapes-dataset.com/file-handling/?packageID=4"><code>leftImg8bit_trainextra.zip</code></a></td>
<td>4</td>
<td>9167a331a158ce3e8989e166c95d56d4</td>
</tr>
</tbody>
</table>
<p>md5值可以使用md5sum工具来验证<a href="https://blog.csdn.net/gatieme/article/details/52833540">参考：CSDN-Linux下使用md5sum计算和检验MD5码</a></p>
<p>gtFine_trainvaltest.zip（241MB）：主要为标的好一些的标注，包括标注训练与验证集共3475张图片，测试集1525张图片忽略了区域，这个数据集和leftImg8bit_trainvaltest.zip（11GB）相呼应</p>
<p>gtCoarse.zip（1.3GB）：主要为标的粗糙一些的标注，包括标注训练与验证集标注共3475张图片，标注了额外的训练数据集19998张图片，与leftImg8bit_trainextra.zip (44GB) 和leftImg8bit_trainvaltest.zip（11GB）相呼应。</p>
<h3>3.2、下载数据集</h3>
<p>可以通过<a href="https://www.cityscapes-dataset.com/downloads/">Cityscapes官网</a>注册登录然后下载，也可以通过命令下载<a href="https://blog.csdn.net/fjssharpsword/article/details/102531156?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-7.edu_weight&amp;depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-7.edu_weight">参考：CSDN-Linux下载交通图片数据集CityScapes Dataset</a></p>
<p>命令如下</p>
<h4>3.2.1、登录</h4>
<p>注意需要先<a href="https://www.cityscapes-dataset.com/downloads/">注册</a>。</p>
<pre><code>wget --keep-session-cookies --save-cookies=cookies.txt --post-data 'username=yourname&amp;password=yourpassword&amp;submit=Login' https://www.cityscapes-dataset.com/login/
</code></pre>
<h4>3.2.2、下对应数据集</h4>
<p>修改下面对应的ID值就可以下载对应的包，相关包的ID可以看上面的表格。</p>
<pre><code>wget --load-cookies cookies.txt --content-disposition https://www.cityscapes-dataset.com/file-handling/?packageID=3
</code></pre>
<h4>3.2.3 解压图片和标注</h4>
<pre><code>mkdir Cityscapes
mv leftImg8bit_trainvaltest.zip ./Cityscapes/leftImg8bit_trainvaltest.zip
unzip -d ./Cityscapes ./Cityscapes/leftImg8bit_trainvaltest.zip
mv ./Cityscapes/README ./Cityscapes/README_leftImg8bit
mv ./Cityscapes/license.txt ./Cityscapes/license_leftImg8bit.txt

mv gtFine_trainvaltest.zip ./Cityscapes/gtFine_trainvaltest.zip
unzip -d ./Cityscapes ./Cityscapes/gtFine_trainvaltest.zip
mv ./Cityscapes/README ./Cityscapes/README_gtFine
mv ./Cityscapes/license.txt ./Cityscapes/license_gtFine.txt
</code></pre>
<h3>3.3、加载数据集</h3>
<p>加载数据集之前需要下载安装<a href="https://github.com/mcordts/cityscapesScripts">cityscapesScripts</a></p>
<pre><code>python -m pip install cityscapesscripts
</code></pre>
<p>依旧使用<code>torchvision.datasets</code>来加载数据集，简单和方便，代码如下。加载数据集的类型可分为'instance', 'semantic', 'color', 'polygon'这几种，也可以是这集中的任意组合，其中color是输出全景分割label，polygon是输出分割对象的多边形轮廓的点信息。</p>
<pre><code>import torchvision
import numpy as np

root = r'./Cityscapes/'

# target_type = ['instance', 'semantic', 'color', 'polygon']
target_type = 'instance'

cityscapes_dataset = torchvision.datasets.Cityscapes(root, split='train', mode='fine', target_type=target_type, transform=None, target_transform=None)
</code></pre>
<p><strong>还可以通过将Cityscapes数据集转换为COCO数据，然后通过COCO来加载，<a href="https://github.com/jinfagang/cityscapestococo">参考：https://github.com/jinfagang/cityscapestococo</a></strong></p>
<h3>3.4、提取对象的mask标记</h3>
<pre><code>def get_target_ids(target):
    ids = [iid for iid in np.unique(target) if iid &gt;= 1000]
#     print(ids)
    return ids
</code></pre>
<p>提取对象的mask方式与<a href="#14%E6%8F%90%E5%8F%96%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%A0%87%E8%AE%B0">VOC2012的方式</a>基本一样（代码如上），只是获取ids的方式不同。VOC2012的实例分割标注，没有带有类别信息，只是按第一个、第二个、到第n个对象的顺序标号（从零开始）来标记的（如下图一），而Cityscapes的实例分割包括了分类标签信息（如下图三，来自<a href="https://github.com/mcordts/cityscapesScripts/blob/master/cityscapesscripts/helpers/labels.py">labels.py</a>），它是通过对象id*1000+第几个对象的顺序标号来标记的（如下图二）。</p>
<p>VOC2012中，0表示背景，255表示对象轮廓
<img src="/assets/images/2020/20200701/voc-target.png" alt="voc-targe" /></p>
<p>Cityscapes中，小于1000的数字是语义分割的标号，而大于1000的是对应对象的实例分割标注（对象id*1000+第几个对象的顺序标号）。如26003是对象<code>car</code>的第四个对象（下标从零开始）。</p>
<p><img src="/assets/images/2020/20200701/cityscapes-target.png" alt="cityscapes-target" /></p>
<p><img src="/assets/images/2020/20200701/cityscape-labels.png" alt="cityscapes labels" /></p>
<h2>拓展：颜色转换</h2>
<p>将<code>(0-255)</code>的<code>PIL.PngImagePlugin.PngImageFile</code>转成RGB模式下的<code>PIL.Image.Image</code>的颜色转换，如下代码<code>color_map_rgb</code>可以获取获取到<code>0-N</code>的对应的颜色。</p>
<pre><code>def bitget(number, pos):
    return (number &gt;&gt; pos - 1) &amp; 1

def bitor(number1, number2):
    return number1 | number2

def bitshift(number, shift_bit_count):
    if shift_bit_count &lt; 0:
        number = number &gt;&gt; abs(shift_bit_count)
    else:
        number = number &lt;&lt; shift_bit_count
    return number

def color_map_rgb(N):
    cmap = np.zeros((N+1,3), np.uint8)
    for i in range(N+1):
        if i == 0:
            continue
        id = i
        r = g = b = 0
        for j in range(8):
            r = bitor(r, bitshift(bitget(id, 1), 7 - j))
            g = bitor(g, bitshift(bitget(id, 2), 7 - j))
            b = bitor(b, bitshift(bitget(id, 3), 7 - j))
            id = bitshift(id, -3)
        cmap[i, :]=[r, g, b]
#         print([r,g,b])
    return cmap
</code></pre>
<p>简单来看<code>0-255</code>对应的颜色值的规律：(以下面<code>0-10</code>所对应的RGB颜色值来看)</p>
<table>
<thead>
<tr>
<th>数字值</th>
<th>二进制</th>
<th>RGB颜色值</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>b000000</td>
<td>[  0,   0,   0]</td>
</tr>
<tr>
<td>1</td>
<td>b000001</td>
<td>[128,   0,   0]</td>
</tr>
<tr>
<td>2</td>
<td>b000010</td>
<td>[  0, 128,   0]</td>
</tr>
<tr>
<td>3</td>
<td>b000011</td>
<td>[128, 128,   0]</td>
</tr>
<tr>
<td>4</td>
<td>b000100</td>
<td>[  0,   0, 128]</td>
</tr>
<tr>
<td>5</td>
<td>b000101</td>
<td>[128,   0, 128]</td>
</tr>
<tr>
<td>6</td>
<td>b000110</td>
<td>[  0, 128, 128]</td>
</tr>
<tr>
<td>7</td>
<td>b000111</td>
<td>[128, 128, 128]</td>
</tr>
<tr>
<td>8</td>
<td>b001000</td>
<td>[ 64,   0,   0]</td>
</tr>
<tr>
<td>9</td>
<td>b001001</td>
<td>[192,   0,   0]</td>
</tr>
<tr>
<td>10</td>
<td>b001010</td>
<td>[ 64, 128,   0]</td>
</tr>
</tbody>
</table>
<p><strong>规律</strong>
将二进制逆序，以三位为一捆，然后每一位的数字对应乘以255*(1/2)^n(第几捆)，然后把所有捆对应位的对应的值加起来。</p>
<p>以10为例，10的二进制是b001010，逆序010100，分为两捆，第一捆010，第二捆100，第一捆对应乘以128（255*(1/2)^1）得到(0<em>128, 1</em>128, 0<em>128)，第二捆对应乘以64（255</em>(1/2)^2）得到(1<em>64, 0</em>64, 0*64)，最后对应相加得到RGB值(64, 128, 0)</p>
<p>再来个大点的数66，66的二进制b1000010，逆序0100001，第一捆010，第二捆000，第三捆100，分别对应，(0<em>128, 1</em>128, 0<em>128)，(0</em>64, 0<em>64, 0</em>64)，(32*1, 0, 0)，对应相加得到(32, 128, 0)，验证结果正确（如下图所示）。</p>
<p><img src="/assets/images/2020/20200701/cal-color-map-value-val.png" alt="验证计算结果" /></p>
<h2>总结</h2>
<p>小小总结一下，这次主要总结了三个数据集VOC2012、COCO2017、Cityscapes的使用方法，主要以实例分割方向上来讲解，不知道怎么总结好了，不说了，休息。</p>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【论文阅读笔记】Instance Segmentation by Jointly Optimizing Spatial Embeddings and Clustering Bandwidth]]></title>
            <link>https://bingqiangzhou.github.io/posts/paperreading-instancesegmentationbyjointlyoptimizingspatialembeddingsandclusteringbandwidth/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/paperreading-instancesegmentationbyjointlyoptimizingspatialembeddingsandclusteringbandwidth/</guid>
            <pubDate>Sun, 28 Jun 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[几天没写博客了，这几天在看一些课外书，不过今天看了一篇论文，Instance Segmentation by Jointly Optimizing Spatial Embeddings and Clustering Bandwidth，它是之前EmbedMask那篇论文中的可学习间隙的来源，这篇论文说主要提出了一...]]></description>
            <content:encoded><![CDATA[<p>几天没写博客了，这几天在看一些课外书，不过今天看了一篇论文，<code>Instance Segmentation by Jointly Optimizing Spatial Embeddings and Clustering Bandwidth</code>，它是之前<code>EmbedMask</code>那篇论文中的可学习间隙的来源，这篇论文说主要提出了一种新的损失，而我觉得论文的主要贡献是提出了可学习的间隙<code>margin</code>以及在提高实时性的同时，保持了高的准确度。</p>
<p><a href="https://arxiv.org/abs/1906.11109">论文地址</a></p>
<p><a href="https://github.com/davyneven/SpatialEmbeddings">源码地址</a></p>
<h2>网络结构</h2>
<p>我们直接来看网络结构图。</p>
<p><img src="/assets/images/2020/20200628/network-architecture.png" alt="网络结构图" /></p>
<p>网络主要分为两个分支，上面的分支用来预测每个语义类别的种子图<code>seed map</code>，这个种子图呢，是一个分数图，靠近实例对象的中心点对应会有一个高的分数，而离中心点比较远的分数则比较低，下面的分支呢，输出<code>offset map</code>和间隙$\sigma$，<code>offset map</code>与坐标向量（xmap，ymap）相加构造成像素点的<code>embedding</code>。随后由<code>seed map</code>确定实例对象的中心点位置，取对应位置的$\sigma$作为间隙，通过计算<code>embedding</code>的距离（或者说是相似度）来确定实例分割的结果。</p>
<h2>网络细节</h2>
<h3><code>seed map</code></h3>
<p>查看源码发现呢，这个种子图，并不是像网络结构图中那样分了成了多张，而只是一张包含了所有语义对象的图并且用损失函数（如下）在语义对象上进行约束。</p>
<p>$$
L_{seed} = \frac{1}{N}\sum_{i}^{N}\mathbf{1}<em>{{s_i \in S_k}}||s_i-\phi_k(e_i)||^2+\mathbf{1}</em>{{s_i \in bg }}||s_i-0||^2
$$</p>
<p>这里的$S_k$是由<code>ground truth</code>确定的语义对象，$s_i$是<code>seed map</code>中的点，函数$1_{{s_i \in S_k}}$表示属于前景对象的值为1，属于背景的值为0，反过来，函数$1_{{s_i \in bg }}$表示属于背景为1，属于前景对象为0。
$\phi_k(e_i)$为如下公式，计算属于$i$点属于对象$k$的分数。</p>
<p>$$
\phi_k(e_i)=exp\left(-\frac{||e_i-C_k||^2}{2\sigma_k^2} \right)
$$</p>
<p>那为什么要用$s_i$减去$\phi_k(e_i)$，作者认为这个分数应该等于计算由$\phi_k(e_i)$计算出来的值，即这个分数应该等于推理中属于正确语义类别中的分数，而这个属于正确语义类别中的分数由$\phi_k(e_i)$计算出来。</p>
<h3>间隙$\sigma$</h3>
<p>间隙$\sigma$在实际计算中，是使用的实例在<code>seed map</code>最大分数位置对应到$\sigma \ map$中的$\sigma$值，这里和推理中的$\sigma$是不一致，所以用损失函数（如下）来约束它。</p>
<p>$$
L_{smooth}=\frac{1}{|S_k|}\sum_{\sigma_i \in S_k}||\sigma_i - \sigma_k||^2
$$</p>
<p>其中，$\sigma_k$是推理中的间隙。</p>
<p>$$
\sigma_k = \frac{1}{|S_k|}\sum_{\sigma_i \in S_k} \sigma_i
$$</p>
<h3>基础网络</h3>
<p>使用<code>ERFNet</code>，一种编码解码器结构来作为基础网络，将如网络结构图所示，将解码器分开用两次，分别训练不同的参数，实现不同的效果，效果中将网络输出结果叠加到一起，形成了一个4的通道的张量，第一二通道是<code>offset</code>，第三通道是间隙$\sigma$，第四通道是<code>seed map</code>。</p>
<h3><code>xmap</code>与<code>ymap</code></h3>
<p><code>xmap</code>与<code>ymap</code>由如下代码构造而成。其中2048与1024分别为图像（<code>Cityscas数据集</code>）的宽与高。</p>
<pre><code>xm = torch.linspace(0, 2, 2048).view(1, 1, -1).expand(1, 1024, 2048)
ym = torch.linspace(0, 1, 1024).view(1, -1, 1).expand(1, 1024, 2048)
xym = torch.cat((xm, ym), 0)
</code></pre>
<h3>损失函数</h3>
<p>除了上面对间隙$\sigma$和<code>seed map</code>的分数进行约束的损失之外，还需要优化分割效果的损失，论文中使用二分类损失<code>Lovasz-hinge loss</code>，公式如下，其中又一个雅卡尔指数<code>Jaccard index</code>的概念，其实就是交叠率。</p>
<p>$$
J_c(y^<em>, \bar{y})= \frac{|y^</em>=c \cap \bar{y}=c|}{|y^*=c \cup \bar{y}=c|}
$$</p>
<p>转换为求最小loss</p>
<p>$$
\Delta_{J_c(y^<em>, \bar{y})} = 1 - J_c(y^</em>, \bar{y})
$$</p>
<p>求预测像素$i$的<code>hinge loss</code>，其中$F_i(x)$是像素$i$的评分函数的值，而$\bar{y}=sign(F_i(x))$，即$F_i(x)$是没有转为标签之前的分数，$sign(\cdot)$是符号函数，大于$0$，则等于$1$，等于$0$，不变，小于$0$，则等于$-1$，$y_i^*$是像素$i$的标签等于$-1$或者$1$，</p>
<p>$$
loss_{hinge} = max(1 - F_i(x)<em>y_i^</em>,\ 0)
$$</p>
<p>再进一步拓展到<code>Lovasz-hinge loss</code>，$\Delta_j^-$是$\Delta_j$的<code>Lovasz</code>拓展。</p>
<p>$$
Loss_{Lovasz\ hinge} = \Delta_j^-(loss_{hinge}(F))
$$</p>
<p><code>Lovasz</code>拓展又是什么呢，<code>Lovasz</code>拓展可用于求解次模最小化问题。次模函数的<code>Lovasz</code>拓展是一个凸函数，可高效实现最小化，这里的次模函数看了定义，还是懵的，不纠结这个了，还是继续看<code>Lovasz</code>拓展吧。给定一个次模函数$f$，<code>Lovasz</code>拓展$\hat{f}$如下:</p>
<p>$$
\hat{f}(x) = \sum_{i=0}^{n}\lambda_if(X_{S_i})
$$</p>
<p>其中，</p>
<p>$$
\lambda_i = x_{\pi_{(i)}} - x_{\pi_{(i + 1)}}
$$</p>
<p>$x_{\pi_{(i)}}$是排好序之后的$x$，</p>
<p>$$1\ge x_{\pi_{(1)}} \ge x_{\pi_{(2)}} \ge ... \ge x_{\pi_{(n)}} \ge 0 $$</p>
<p>经过<a href="https://sudeepraja.github.io/Submodular/">相关的推到和证明</a>，得到最后得到<code>Lovasz</code>拓展的次梯度<code>sub-gradients</code>将其作为最小化的对象。</p>
<p>$$
g(x) = \sum_{i=1}^{n}(\Delta_{J_c(loss_{hinge}{<em>{\pi(i)}})}  - \Delta</em>{J_c(loss_{hinge}{<em>{\pi(i-1)}})} ) * loss</em>{hinge}{_{\pi(i)}}
$$</p>
<p>这里只要将$loss_{hinge}{_{\pi(i-1)}}$改成如下公式就可以将二分类<code>Lovasz-hinge loss</code>变成多分类<code>lovasz-softmax loss</code></p>
<p>$$
loss =
\begin{cases}
1-F_i(c)  &amp; if \ c = y^*, \
F_i(c) &amp; otherwise.
\end{cases}
$$</p>
<p><strong>参考</strong></p>
<p><a href="https://zhuanlan.zhihu.com/p/41615416">知乎的文章：Lovasz-Softmax loss</a></p>
<p><a href="https://blog.csdn.net/baidu_27643275/article/details/95487631">CSDN文章：lovasz-softmax loss</a></p>
<p><a href="https://github.com/bermanmaxim/LovaszSoftmax">LovaszSoftmax源码地址</a></p>
<h3>损失拓展</h3>
<p>论文中提到了对间隔和像素点中心进行拓展。</p>
<ul>
<li>
<p>将间隔由圆变成椭圆的间隔，学习二维（x，y）的间隔</p>
<p>$$
\phi_k(e_i)=exp\left(-\frac{||e_{ix}-C_{kx}||^2}{2\sigma_{kx}^2}-\frac{||e_{iy}-C_{ky}||^2}{2\sigma_{ky}^2} \right)
$$</p>
</li>
<li>
<p>将直接取<code>seed map</code>最大值对应的位置作为中心改为取属于对象的<code>embedding</code>的均值。</p>
<p>$$
\phi_k(e_i)=exp\left(-\frac{||e_i-\frac{1}{|S_k|}\sum_{e_j\in S_k}e_j||^2}{2\sigma_k^2} \right)
$$</p>
</li>
</ul>
<p>很显然的是，第二个拓展，只能用于训练的时候，因为我们需要确定点属于哪个实例，才能通过求均值的方式求中心，论文中为了比较以上的两种拓展，将<code>seed map</code>采样改成了在<code>ground truth</code>上采样，这样直接可以确定点属于哪个实例，从而可以顺利的通过求均值的方式求中心。</p>
<h2>训练策略</h2>
<p>由于使用<code>Cityscapes</code>数据集，图片比较大<code>h1024xw2048</code>，作者先将图片剪切成<code>500x500</code>，做了100轮预训练，之后在剪切图片<code>1024x1024</code>大小下，进行了50轮的微调，这种方式可以借鉴一下，想起之前还有一种方式，将网络最后的分割层改成改成分类层，在<code>ImageNet</code>数据集上做预训练，只能改回来再在分割的数据集上做分割任务。</p>
<h2>论文小结</h2>
<p>从这篇论文中学到了什么呢，我觉得主要还是可学习的间隙，然后的话，算是了解一下了做分割的两个损失函数二分类的<code>Lovasz-hinge loss</code>和多分类的<code>lovasz-softmax loss</code>，之后在我们的工作中，我想我会去比较一下交叉熵和<code>lovasz-softmax loss</code>。</p>
<p>看这篇论文，之前卡了我一下的是，之前一直觉得求中心点是个死环，求不出来中心点，在论文中的推理过程中，中心点需要知道哪些点属于实例，而哪些点属于实例需要由有中心点的高斯函数求出来，最后发现作者用逐步取<code>seed map</code>中最大值来确定中心位置和相应位置的间隙，从而可以算哪些点属于实例（$\phi_k(e_i) &gt; 0.5$），逐步求实例中心的这个过程其实有点类似于非最大值抑制求极大值的过程，每个极大值代表一个实例的中心。</p>
<p>这篇论文就大概总结到这里了。</p>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【折腾记录】定时获取成绩，新出一门成绩使用邮件提醒]]></title>
            <link>https://bingqiangzhou.github.io/posts/dailyjungle-smtp-seleniumwithheadlessbroswer/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/dailyjungle-smtp-seleniumwithheadlessbroswer/</guid>
            <pubDate>Mon, 22 Jun 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[今天上午考完了这个学期的所有考试，下午就准备写个爬虫，定时获取成绩，新出一门成绩使用邮件提醒，使用Chrome无头浏览器配合Selenium模块爬取成绩，然后用IIS中的虚拟SMTP服务器来发送邮件到自己邮箱中，来提醒自己有新的成绩出来了。]]></description>
            <content:encoded><![CDATA[<p>今天上午考完了这个学期的所有考试，下午就准备写个爬虫，定时获取成绩，新出一门成绩使用邮件提醒，使用<code>Chrome</code>无头浏览器配合<code>Selenium</code>模块爬取成绩，然后用<code>IIS</code>中的虚拟<code>SMTP</code>服务器来发送邮件到自己邮箱中，来提醒自己有新的成绩出来了。</p>
<h2>爬取成绩</h2>
<h3>1、准备工作</h3>
<p>在开始爬取数据前先到做一些准备工作。下载安装<code>Chrome</code>浏览器、<code>chromedriver</code>以及<code>selenium</code>模块。</p>
<p>本来是准备使用<a href="https://phantomjs.org"><code>PhantomJS</code></a>，奈何开发团队主要人员吵架，然后暂停了项目的开发，<code>Selenium</code>的新版本中，也不再支持<code>PhantomJS</code>，后来知道了<code>Firefox</code>和<code>Chrome</code>都是支持无头模式的，所以我决定使用<code>Chrome</code>试一试。</p>
<h4>安装<code>Chrome</code>浏览器</h4>
<pre><code>$ sudo apt-get update
$ wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
$ sudo dpkg -i google-chrome-stable_current_amd64.deb
</code></pre>
<p>如果出现了缺少依赖的情况，可以使用如下命令解决</p>
<pre><code>$ sudo apt-get -f -y install
</code></pre>
<p>再重新安装<code>Chrome</code>即可，可以通过以下命令查看是否安装正确。</p>
<pre><code>$ google-chrome --version
$ which google-chrome
</code></pre>
<h4>安装<code>chromedriver</code></h4>
<p>在<a href="http://chromedriver.storage.googleapis.com/index.html">这里</a>找到与<code>Chrome</code>大版本一致的<code>chromedriver</code>，下载下来，并解压到<code>/usr/bin</code>或者<code>/usr/local/bin</code>目录中，也可以解压到当前吗目录，不过这样后续需要指定<code>chromedriver</code>的路径。</p>
<pre><code>$ wget http://chromedriver.storage.googleapis.com/83.0.4103.39/chromedriver_linux64.zip
$ unzip chromedriver_linux64.zip -d /usr/local/bin
</code></pre>
<p>如果将<code>chromedriver</code>解压到了<code>/usr/bin</code>或者<code>/usr/local/bin</code>目录中，可以使用如下命令验证一下。</p>
<pre><code>$ chromedriver --version
</code></pre>
<p><code>Seleium</code>官方文档中给出了<a href="https://www.selenium.dev/documentation/en/webdriver/driver_requirements/">各个浏览器<code>WebDriver</code>的下载地址和加载方式</a></p>
<h4>安装<code>selenium</code>模块</h4>
<p>这里就不多说了，<code>pip</code>，<code>conda</code>安装都很容易。</p>
<h3>2、开始爬取成绩</h3>
<p><code>Seleium</code>官方给的<a>快速入门</a>，特别不粗，看一遍，直接就能上手，这里我不具体来说爬成绩的代码，而是简单讲一讲<code>Seleium</code>使用<code>WebDriver</code>的简单上手。</p>
<h4>设置无头加载</h4>
<pre><code>chrome_options = webdriver.ChromeOptions()
chrome_options.headless = True
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--no-sandbox")
</code></pre>
<h4>加载<code>WebDriver</code></h4>
<p>以<code>Chrome</code>为例。其他浏览器加载方式类似，具体可见<a href="https://www.selenium.dev/documentation/en/webdriver/driver_requirements/">文档</a></p>
<pre><code>#Simple assignment
from selenium.webdriver import Chrome

driver = Chrome(executable_path='/usr/bin/chromedriver', options=chrome_options)

#Or use the context manager
from selenium.webdriver import Chrome

with Chrome(executable_path='/usr/bin/chromedriver', options=chrome_options) as driver:
    #your code inside this indent
</code></pre>
<h4>访问网站链接</h4>
<pre><code>driver.get("https://selenium.dev")
</code></pre>
<h4>等待网站加载完成</h4>
<ul>
<li>
<p>强制等待</p>
<pre><code>time.sleep(5) # Wait for 5 seconds.
</code></pre>
</li>
<li>
<p>隐性等待</p>
<p>设置最长等待时间，在没有达到最长时间时加载完成，则进行下一步，到最长等待时间，还没有加载完成，则也惊喜下一步。</p>
<pre><code>driver.implicitly_wait(30)  # 最长等30秒
</code></pre>
</li>
<li>
<p>显性等待</p>
<p>程序每隔xx秒验证一下条件，如果条件成立了，则执行下一步，否则继续等待，如果超过设置的最长时间，则抛出异常<code>TimeoutException</code>。</p>
<pre><code>WebDriverWait(driver, timeout=3).until(some_condition)
</code></pre>
<pre><code>from selenium.webdriver.support.ui import WebDriverWait
def document_initialised(driver):
    return driver.execute_script("return initialised")

driver.navigate("file:///race_condition.html")
WebDriverWait(driver).until(document_initialised)
el = driver.find_element(By.TAG_NAME, "p")
assert el.text == "Hello from JavaScript!"
</code></pre>
<pre><code>from selenium.webdriver.support.ui import WebDriverWait

driver.navigate("file:///race_condition.html")
el = WebDriverWait(driver).until(lambda d: d.find_element_by_tag_name("p"))
assert el.text == "Hello from JavaScript!"
</code></pre>
<pre><code>driver = Firefox()
driver.get("http://somedomain/url_that_delays_loading")
wait = WebDriverWait(driver, 10, poll_frequency=1, ignored_exceptions=[ElementNotVisibleException, ElementNotSelectableException])
element = wait.until(EC.element_to_be_clickable((By.XPATH, "//div")))
</code></pre>
</li>
</ul>
<h4>浏览器相关操作</h4>
<pre><code>```python
driver.get("https://selenium.dev")

driver.current_url

driver.back()

driver.forward()

driver.refresh()

driver.title

driver.current_window_handle

driver.switch_to.window(window_handle)

# Opens a new tab and switches to new tab
driver.switch_to.new_window('tab')
# Opens a new window and switches to new window
driver.switch_to.new_window('window')

#Close the tab or window
driver.close()
#Switch back to the old tab or window
driver.switch_to.window(original_window)

driver.quit()
```

其他窗口相关的操作请看[文档](https://www.selenium.dev/documentation/en/webdriver/browser_manipulation/)。
</code></pre>
<h4>常用接口获取值</h4>
<pre><code>size #获取元素的尺寸
text #获取元素的文本
get_attribute(name) #获取属性值
location #获取元素坐标，先找到要获取的元素，再调用该方法
page_source #返回页面源码
driver.title #返回页面标题
current_url #获取当前页面的URL
is_displayed() #设置该元素是否可见
is_enabled() #判断元素是否被使用
is_selected() #判断元素是否被选中
tag_name #返回元素的tagName
</code></pre>
<h4>定位元素</h4>
<ul>
<li>
<p>定位单个元素</p>
<pre><code>from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Firefox()
driver.get("http://www.google.com")
# Get search box element from webElement 'q' using Find Element
search_box = driver.find_element(By.NAME, "q")
search_box.send_keys("webdriver")
</code></pre>
<pre><code>find_element_by_id()
find_element_by_name()
find_element_by_xpath()
find_element_by_link_text()
find_element_by_partial_link_text()
find_element_by_tag_name()
find_element_by_class_name()
find_element_by_css_selector()
</code></pre>
</li>
<li>
<p>定位多个元素</p>
<pre><code># Get all the elements available with tag name 'p'
elements = driver.find_elements(By.TAG_NAME, 'p')
</code></pre>
</li>
<li>
<p>从元素中定位元素</p>
<pre><code>from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Firefox()
driver.get("http://www.google.com")
search_form = driver.find_element(By.TAG_NAME, "form")
search_box = search_form.find_element(By.NAME, "q")
search_box.send_keys("webdriver")
</code></pre>
<pre><code>from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.example.com")

# Get element with tag name 'div'
element = driver.find_element(By.TAG_NAME, 'div')

# Get all the elements available with tag name 'p'
elements = element.find_elements(By.TAG_NAME, 'p')
for e in elements:
    print(e.text)
</code></pre>
</li>
<li>
<p>定位到焦点的元素</p>
<pre><code>from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.google.com")
driver.find_element(By.CSS_SELECTOR, '[name="q"]').send_keys("webElement")

# Get attribute of current active element
attr = driver.switch_to.active_element.get_attribute("title")
print(attr)
</code></pre>
</li>
<li>
<p>获取元素属性</p>
<pre><code>element.get_attribute("title")
</code></pre>
</li>
<li>
<p>调用截图（需要在有浏览器显示的情况下）</p>
<pre><code>driver.save_screenshot("screenshot.png")
</code></pre>
</li>
</ul>
<h4>操作元素</h4>
<pre><code>clear()     #清除元素的内容
send_keys() #模拟按键输入
click()     #点击元素
submit()    #提交表单
</code></pre>
<h4>键盘与鼠标操作</h4>
<ul>
<li>
<p>键盘操作</p>
<pre><code>from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
driver = webdriver.Chrome()

# Navigate to url
driver.get("http://www.google.com")

# Enter "webdriver" text and perform "ENTER" keyboard action
driver.find_element(By.NAME, "q").send_keys("webdriver" + Keys.ENTER)

# Perform action ctrl + A (modifier CONTROL + Alphabet A) to select the page
webdriver.ActionChains(driver).key_down(Keys.CONTROL).send_keys("a").perform()
</code></pre>
<pre><code>from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
driver = webdriver.Chrome()

# Navigate to url
driver.get("http://www.google.com")

# Store google search box WebElement
search = driver.find_element(By.NAME, "q")

action = webdriver.ActionChains(driver)

# Enters text "qwerty" with keyDown SHIFT key and after keyUp SHIFT key (QWERTYqwerty)
action.key_down(Keys.SHIFT).send_keys_to_element(search, "qwerty").key_up(Keys.SHIFT).send_keys("qwerty").perform()
</code></pre>
<pre><code>from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()

# Navigate to url
driver.get("http://www.google.com")

# Store 'SearchInput' element
SearchInput = driver.find_element(By.NAME, "q")
SearchInput.send_keys("selenium")

# Clears the entered text
SearchInput.clear()
</code></pre>
<pre><code>send_keys(Keys.ENTER) #按下回车键
send_keys(Keys.TAB) #按下Tab制表键
send_keys(Keys.SPACE) #按下空格键space
send_keys(Kyes.ESCAPE) #按下回退键Esc
send_keys(Keys.BACK_SPACE) #按下删除键BackSpace
send_keys(Keys.SHIFT) #按下shift键
send_keys(Keys.CONTROL) #按下Ctrl键
send_keys(Keys.ARROW_DOWN) #按下鼠标光标向下按键
send_keys(Keys.CONTROL,'a') #组合键全选Ctrl+A
send_keys(Keys.CONTROL,'c') #组合键复制Ctrl+C
send_keys(Keys.CONTROL,'x') #组合键剪切Ctrl+X
send_keys(Keys.CONTROL,'v') #组合键粘贴Ctrl+V
</code></pre>
</li>
<li>
<p>鼠标操作</p>
<pre><code>click()
context_click(elem) #右击鼠标点击元素elem，另存为等行为
double_click(elem) #双击鼠标点击元素elem，地图web可实现放大功能
drag_and_drop(source,target) #拖动鼠标，源元素按下左键移动至目标元素释放
move_to_element(elem) #鼠标移动到一个元素上
click_and_hold(elem) #按下鼠标左键在一个元素上
perform() #在通过调用该函数执行ActionChains中存储行为
</code></pre>
</li>
</ul>
<h4>Selenium小结</h4>
<p>在实现需求的过程中，遇到了一直提醒浏览器版本过低的问题，然后使用如下语句，点击提醒框的确定按钮，进入下一步。</p>
<pre><code>alert = browser.switch_to.alert
alert.accept()
</code></pre>
<p>Selenium处理的过程：设置无头加载，加载驱动，加载网页，等待加载完成，爬取数据。</p>
<h2>邮件发送</h2>
<h3>1、在IIS上搭建SMTP服务器</h3>
<p>这里主要参考CSDN中的一篇博客<a href="https://blog.csdn.net/leelyliu/article/details/80840443">Windows Server 2012 r2搭建SMTP</a>，这里放上博客中给出的图，以后好找到。</p>
<ul>
<li>
<p>1、打开控制面板选择打开或关闭<code>windows features</code>，按照提示在features中找到<code>SMTP</code>，选择安装；在<code>Windows Server</code>中也可以在服务器管理<code>Server Manage</code>中的<code>Manage</code>中添加。</p>
<p><img src="/assets/images/2020/20200622/smtp-setting-1.png" alt="添加SMTP" /></p>
</li>
<li>
<p>2、打开IIS6.0，这里需要注意的是IIS6.0，点击windows窗口图标，在search中输入“iis”，选择打开iis 6.0 Manager；在<code>Windows Server</code>中也可以在服务器管理<code>Server Manage</code>中<code>Tools</code>中打开。</p>
<p><img src="/assets/images/2020/20200622/smtp-setting-2.png" alt="打开IIS 6" /></p>
</li>
<li>
<p>3、在打开窗口，<code>SMTP Vritual Server</code>右键选择<code>domain</code>，并新建一个新的<code>domain</code>，按照提示输入名称和类型即可.</p>
<p><img src="/assets/images/2020/20200622/smtp-setting-3.png" alt="创建doamin" /></p>
<p><img src="/assets/images/2020/20200622/smtp-setting-4.png" alt="选择domain类型" /></p>
<p><img src="/assets/images/2020/20200622/smtp-setting-5.png" alt="输入domain名称" /></p>
</li>
<li>
<p>4、设置ip（可以不改默认的<code>All Unassgined</code>）以及端口（也可以不改默认的<code>25</code>）。</p>
<p><img src="/assets/images/2020/20200622/smtp-setting-6.png" alt="打开属性" /></p>
<p><img src="/assets/images/2020/20200622/smtp-setting-7.png" alt="设置ip" /></p>
<p><img src="/assets/images/2020/20200622/smtp-setting-8.png" alt="修改端口" /></p>
</li>
<li>
<p>5、访问控制，选择基本的明文验证。</p>
<p><img src="/assets/images/2020/20200622/smtp-setting-9.png" alt="设置ip" /></p>
<p><img src="/assets/images/2020/20200622/smtp-setting-10.png" alt="修改端口" /></p>
</li>
<li>
<p>6、在windows中添加一个用户，用来做明文认证，从“Security”的配置中，将用户分配给SMTP。</p>
<p>创建用户过程：在【控制面板<code>Contorl Panel</code>】中选择【用户账号<code>User Accounts</code>】，接着再次选择【用户账号<code>User Accounts</code>】，选择【管理另一个账号<code>Manage another account</code>】，选择添加一个【添加一个用户账号<code>add a user account</code>】，输入账户名密码创建用户。</p>
<p><img src="/assets/images/2020/20200622/smtp-setting-11.png" alt="添加用户权限" /></p>
</li>
</ul>
<h3>2、发送邮件</h3>
<p>大致代码如下，发出的邮箱<code>from_addr</code>，任意设置，只要符合正确的邮件格式就行，<code>smtp_server.login('XXX', 'XXXX')</code>使用上面添加的用户账号密码登录。</p>
<pre><code>import smtplib
from email.mime.text import MIMEText
from email.header import Header

host = 'XXXX'
smtp_server = smtplib.SMTP()
smtp_server.connect(host, 25)
# smtp_server.set_debuglevel(1)
from_addr = 'XXX'
to_addr = 'XXX'
smtp_server.login('XXX', 'XXXX') # 添加到SMTP服务器的用户账号密码

message = MIMEText(content, 'html', 'utf-8')
message['From'] = from_addr
message['To'] = to_addr
message['Subject'] = Header('成绩单', 'utf-8').encode()

smtp_server.sendmail(from_addr, to_addr, message.as_string())
smtp_server.quit()
</code></pre>
<h2>总结</h2>
<p><code>selenium</code>大概入了下门，<code>SMTP</code>基本是复制粘贴的<a href="https://www.runoob.com/python3/python3-smtp.html">runoob.com的代码</a>，以后有遇到要使用SMTP再具体深入了解了。</p>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【折腾记录】内网穿透工具natapp]]></title>
            <link>https://bingqiangzhou.github.io/posts/dailyjungle-natapp/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/dailyjungle-natapp/</guid>
            <pubDate>Sat, 20 Jun 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[之前本来没想弄这个natapp的，奈何天意需要弄一下，学校服务器在昨晚11左右不知道是重启了一下，还是怎么了，然后客服端离线了，连不上，然后同学在使用的时候来不上，找到了我，然后我又了解了一下natapp，然后还开了一个免费的通道给jupyter notebook。]]></description>
            <content:encoded><![CDATA[<p>之前本来没想弄这个<a href="https://natapp.cn">natapp</a>的，奈何天意需要弄一下，学校服务器在昨晚11左右不知道是重启了一下，还是怎么了，然后客服端离线了，连不上，然后同学在使用的时候来不上，找到了我，然后我又了解了一下<code>natapp</code>，然后还开了一个免费的通道给<code>jupyter notebook</code>。</p>
<h2>故事的开始</h2>
<p>服务器不知道是咋滴啦，frp客户端掉了，natapp客户端也掉了。</p>
<p><img src="/assets/images/2020/20200620/server-close.png" alt="客服端掉了" /></p>
<p>开启了了解这个natapp之路。</p>
<h2>配置natapp SSH通道</h2>
<p>以创建免费的SSH通道为例。</p>
<h3>1、创建SSH隧道</h3>
<p>登录到natapp网页，在【我的隧道】中，点击【购买隧道】，就可以看到一个【免费隧道】，点击它，选择隧道协议，【TCP】（这里有三种，TCP、UDP、WEB），然后设置【本地端口】（设置默认的SSH端口22吧），点击【免费购买】，创建隧道成功。</p>
<h3>2、下载natapp</h3>
<p><a href="https://natapp.cn/#download">下载natapp地址</a></p>
<p>下载对应的系统的版本的natapp。</p>
<h3>3、配置config.ini文件</h3>
<p>创建一个<code>config.ini</code>文件，将下面的内容复制进去，再将authtoken复制进去，然后配置一下log，也可以不配置，在启动客户端的时候加入命令参数。如<code>$ ./natapp -log=stdout -loglevel=ERROR</code></p>
<pre><code>#将本文件放置于natapp同级目录 程序将读取 [default] 段
#在命令行参数模式如 natapp -authtoken=xxx 等相同参数将会覆盖掉此配置
#命令行参数 -config= 可以指定任意config.ini文件
[default]
authtoken=                      #对应一条隧道的authtoken
clienttoken=                    #对应客户端的clienttoken,将会忽略authtoken,若无请留空,
log=none                        #log 日志文件,可指定本地文件, none=不做记录,stdout=直接屏幕输出 ,默认为none
loglevel=ERROR                  #日志等级 DEBUG, INFO, WARNING, ERROR 默认为 DEBUG
http_proxy=                     #代理设置 如 http://10.123.10.10:3128 非代理上网用户请务必留空
</code></pre>
<h3>4、启动natapp</h3>
<p>执行如下命令，也可以参考<a href="https://natapp.cn/article/nohup">natapp给出的教程</a></p>
<pre><code>nohup ./natapp -config=config.ini -log=stdout -loglevel=INFO  &gt;log.out 2&gt;&amp;1  &amp;
</code></pre>
<p>这里指定了配置文件，是因为我后续还增加了一个免费的web通道。</p>
<h3>5、测试SSH连接</h3>
<p>由于免费的没有固定的域名和端口，它会在启动的时候给定，我们可以在log.out中找到对于的url，这里的图，我是用的web隧道的，因为我们使用了付费的SSH通道，又固定的域名与端口。
<img src="/assets/images/2020/20200620/url.png" alt="免费的url" /></p>
<p>使用对应的的域名与端口，执行ssh命令，测试是否可以连接。</p>
<h2>新增免费的隧道</h2>
<p>对于每个一个注册用户，natapp提供两个免费的不同协议的隧道，下面创建免费的web通道。</p>
<p><img src="/assets/images/2020/20200620/tunnel.png" alt="natapp免费web通道" /></p>
<h3>1、创建Web隧道</h3>
<p>与<a href="#1%E5%88%9B%E5%BB%BAssh%E9%9A%A7%E9%81%93">创建SSH隧道</a>类似，将【隧道协议】改成【web】,其他步骤一致。</p>
<h3>2、配置隧道</h3>
<p>之前已经下载了natapp，这里只需要创建一个新的配置文件，例如<code>webconfig.ini</code>（这可以直接复制成一个新的文件<code>cp config.ini webconfig.ini</code>），再进行之前与SSH通道类似的的<a href="#3%E9%85%8D%E7%BD%AEconfigini%E6%96%87%E4%BB%B6">配置config.ini文件</a>就行，将对应的authtoken换上去。</p>
<h3>3、启动并测试</h3>
<ul>
<li>
<p>与之前SSH<a href="#4%E5%90%AF%E5%8A%A8natapp">启动natapp</a>类似，指定一下配置文件就好了，如下，</p>
<pre><code>nohup ./natapp -config=webconfig.ini -log=stdout -loglevel=INFO &gt;weblog.out 2&gt;&amp;1  &amp;
</code></pre>
</li>
<li>
<p>启动一个web服务，比如jupyter notebook，指定端口为之前创建隧道时设置的端口，命令如下。</p>
<pre><code>nohup jupyter notebook --port 8888 &gt;jupyter.out 2&gt;&amp;1  &amp;
</code></pre>
<p>需要注意的是，一定要指定端口为之前创建隧道时设置的端口，<code>jupyter notebook</code>远程设置可以参考我上一篇博客。</p>
</li>
<li>
<p>在log文件中找到链接，看链接是否可以正常访问。</p>
</li>
</ul>
<h2>总结</h2>
<p>总结一下，这个natapp配置起来，其实也和frp配置起来差不多，没有多复杂，natapp基于ngrok还是做了一些简化吧，不错，收费也不高，怎么说呢，说不高也不高，其实说高也高，它按单个隧道来付费。有好的带宽的外网服务器的话，我还是比较推荐开源的frp吧，没有必要花额外的钱了。</p>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【折腾记录】frp实现内网穿透以及配置Jupyter Notebook远程连接]]></title>
            <link>https://bingqiangzhou.github.io/posts/dailyjungle-frpandjupyternotebookremoteconfig/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/dailyjungle-frpandjupyternotebookremoteconfig/</guid>
            <pubDate>Thu, 18 Jun 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[最近我们用到了另一款基于ngrok内网穿透应用natapp，将校内实验室主机映射到外网来使用，我出于好奇搜了一下natapp，也是第一次知道内网穿透这个词，再去搜ngrok，又发现了几款内网穿透的工具。比如frp，pagekite，localtunnel。]]></description>
            <content:encoded><![CDATA[<p>最近我们用到了另一款基于<code>ngrok</code>内网穿透应用<code>natapp</code>，将校内实验室主机映射到外网来使用，我出于好奇搜了一下<code>natapp</code>，也是第一次知道内网穿透这个词，再去搜<code>ngrok</code>，又发现了几款内网穿透的工具。比如<code>frp</code>，<code>pagekite</code>，<code>localtunnel</code>。</p>
<h2>内网穿透配置</h2>
<p>相关开源的内网穿透工具的地址如下：</p>
<ul>
<li><a href="https://github.com/inconshreveable/ngrok">ngrok</a></li>
<li><a href="https://github.com/fatedier/frp">frp</a></li>
<li><a href="https://github.com/pagekite/PyPagekite">pagekite的python开源实现pypagekite</a></li>
<li><a href="https://github.com/localtunnel/localtunnel">localtunnel</a></li>
</ul>
<h3>为什么选择frp</h3>
<p>接着上面的几个开源工具来说，其中<code>ngrok</code>是1.x的版本，2.x之后内，不开源了，其实也是可以理解的，他们也要吃饭（听说1.x又许多bug没解决，不过我没使用过也不知道），看文档配置不怎么简单。<code>frp</code>的话，简单，我基本上几分钟就看懂是怎么一回事了，<code>pypagekite</code>的话执行python语句来配置服务，后面跟一大串命令参数，最后一个<code>localtunnel</code>，是基于js开发的，还需要先搭建node.js包管理平台npm（node package manager）来弄，直接就放弃这个选项了。
<strong>由于配置简单，我选择了用frp（fast reverse proxy）玩一下</strong></p>
<h3>frp内网穿透过程</h3>
<p><img src="/assets/images/2020/20200618/architecture.png" alt="架构图" /></p>
<p>看上面的架构图，大概是在外网环境下运行frp服务端，而在内网环境下运行frp客户端，服务端与客服端形成通道，可以进行连接，外网下用户访问服务端，服务端在与客服端通信得到请求访问的结果，总结为一个词就是反向代理。</p>
<h4>什么是正向代理、反向代理</h4>
<p>先说一下正向代理，正向代理是代理了客服端，代理与服务端通信，代替客户端与服务端的直接通信。通俗理解：可以说是正向代理是代表客户（客户端）与厂商（服务端）通信，像是代购的角色。</p>
<ul>
<li>[客户端 &lt;一&gt;  代理] 一&gt; 服务端</li>
</ul>
<p>相反，反向代理就是代理了服务端，代理与服务端通信，代替服务端与客户端的直接通信。通俗理解：反向代理则是是代表厂商（服务端）与客户（客户端）通信，像是经销商的角色。</p>
<ul>
<li>客户端 一&gt; [代理  &lt;一&gt; 服务端]</li>
</ul>
<p><strong>正向代理隐藏真实客户端，反向代理隐藏真实服务端</strong></p>
<h3>配置frp</h3>
<h4>1、下载frp</h4>
<p>在<a href="https://github.com/fatedier/frp/releases">frp github库的发布的页面</a>下载对应的对应系统的frp工具。</p>
<h4>2、配置ssh访问</h4>
<ul>
<li>
<p>2.1、配置服务端</p>
<p>在外网环境下的服务器（我这里是腾讯云的学生服务器），配置<code>frps.ini</code>文件中的<code>bind_port</code>，给服务器设置开通一个访问的端口。</p>
<pre><code>[common]
bind_port = 7000
</code></pre>
</li>
<li>
<p>2.2、启动服务端</p>
<pre><code>./frps -c ./frps.ini
</code></pre>
</li>
<li>
<p>2.3、配置客户端</p>
<p>在内网环境下的服务器（我这里是学校的服务器），配置<code>frpc.ini</code>如下，其中<code>server_addr</code>对应外网下服务器的地址（域名），<code>server_port</code>对应上面frps配置的端口。</p>
<pre><code>[common]
server_addr = x.x.x.x
server_port = 7000

[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000
</code></pre>
</li>
<li>
<p>2.4、启动客服端</p>
<pre><code>./frpc -c ./frpc.ini
</code></pre>
</li>
<li>
<p>2.5、测试ssh链接</p>
<pre><code>ssh test@x.x.x.x -p 6000
</code></pre>
<p>这里<code>test</code>需要改成对应的要登录的账户名，<code>@</code>后面为服务器地址（域名），<code>-p</code>后面是内网服务器ssh对应开通的端口。</p>
</li>
</ul>
<h4>3、配置web服务访问</h4>
<ul>
<li>
<p>3.1、配置服务端</p>
<p>在外网环境下的服务器，配置<code>frps.ini</code>文件中的<code>vhost_http_port</code>，给服务器设置开通一个web服务访问的端口。</p>
<pre><code>[common]
bind_port = 7000
vhost_http_port = 8080
</code></pre>
</li>
<li>
<p>3.2、启动服务端</p>
<pre><code>./frps -c ./frps.ini
</code></pre>
</li>
<li>
<p>3.3、配置客户端</p>
<p>在内网环境下的服务器，配置<code>frpc.ini</code>如下，其中<code>server_addr</code>对应外网下服务器的地址（域名），<code>server_port</code>对应之前的frps配置的端口，配置本地的端口，<code>custom_domains</code>是外网下服务器的域名，一般使用二级域名。</p>
<pre><code>[common]
server_addr = x.x.x.x
server_port = 7000

[web]
type = http
local_port = 80
custom_domains = www.example.com
</code></pre>
<p><strong>也可以配置多个web服务</strong>，这里我就配置了两个web服务，一个为jupyter notebook准备的，一个为tensorboard可视化准备的。设置多个web服务，只需要在后面加数字就行。</p>
<pre><code>[common]
server_addr = x.x.x.x
server_port = 7000

[web1]
type = http
local_port = 80
custom_domains = site1.example.com

[web2]
type = http
local_port = 81
custom_domains = site2.example.com
</code></pre>
</li>
<li>
<p>3.4、启动客服端</p>
<pre><code>./frpc -c ./frpc.ini
</code></pre>
</li>
<li>
<p>3.5、测试web访问</p>
<pre><code>http://site1.example.com:7000
http://site2.example.com:7000
</code></pre>
</li>
</ul>
<h4>4、仪表盘</h4>
<ul>
<li>
<p>4.1、配置仪表盘</p>
<p>设置仪表盘，可以查看，链接的状态以及使用的流量，在服务端<code>frps.ini</code>中设置仪表盘的端口<code>dashboard_port</code>，可以设置用户名和密码，不设置的话，默认都是admin。</p>
<pre><code>[common]
dashboard_port = 7500
dashboard_user = admin
dashboard_pwd = admin
</code></pre>
</li>
<li>
<p>4.2、测试仪表盘访问</p>
<p>访问<code>http://x.x.x.x:7500</code>。</p>
<p><img src="/assets/images/2020/20200618/dashboard.png" alt="仪表盘" /></p>
</li>
</ul>
<h4>5、其他内容</h4>
<p>4.1、web服务可以设置https，要求是有域名证书，具体操作看<a href="https://github.com/fatedier/frp/blob/master/README.md#enable-https-for-local-http-service">文档</a></p>
<p>4.2、还有其他一些配置，比如<a href="https://github.com/fatedier/frp/blob/master/README.md#p2p-mode">P2P模式</a> 等等其他的请看<a href="https://github.com/fatedier/frp/blob/master/README.md">文档</a></p>
<h2>jupyter notebook配置远程连接</h2>
<p>配置jupyter远程访问，大概需要一下三个步骤，密码可以不设置，不过设置密码也会相对方便一点，在不设置密码的情况下，启动jupyter notebook会自动生成一个token，这个token作为链接的一部分，会比较难记。</p>
<h3>1、生成notebook配置文件</h3>
<p>执行以下命令，会在用户目录下生成的配置文件<code>.jupyter/jupyter_notebook_config.py</code>，之后在这个文件中再进行配置。</p>
<pre><code>jupyter notebook --generate-config
</code></pre>
<h3>2、生成密码</h3>
<p>输入下面的命令，会在用户目录下生成一个保存有密码的哈希值的<code>json</code>文件，<code>.jupyter/jupyter_notebook_config.json</code>。</p>
<pre><code>jupyter notebook password
</code></pre>
<h3>3、修改配置文件</h3>
<p>在用户目录下的<code>.jupyter/jupyter_notebook_config.py</code>中找到下面几行取消注释并修改，这几行分别对应修改可以访问的ip、启动jupyter notebook时是否开启浏览器，以及jupyter notebook服务对应的接口，<strong>其实直接将下面的几行复制到文件也可以</strong>。</p>
<pre><code>c.NotebookApp.ip = '*'
c.NotebookApp.open_browser = False
c.NotebookApp.port = 8888
</code></pre>
<p><strong>注意：如果用frp做内网穿透的话，这里的端口需要对应上面frp客户端web服务配置的端口号，tensorboard可视化等等其他web服务也是一样。</strong></p>
<h3>4、启动jupyter notebook并在后台运行</h3>
<p>使用<code>nohup</code>命令后台运行jupyter notebook，并将输出的内容追加的内容输出到<code>jupyter.out</code>文件中，同时将错误信息也输出到文件中。</p>
<pre><code>nohup jupyter notebook &gt;&gt;jupyter.out 2&gt;&amp;1 &amp;
</code></pre>
<h3>拓展知识</h3>
<ul>
<li>
<p><code>nohup</code>命令语法</p>
<ul>
<li>nohup Command [ Arg … ] [ &amp; ]</li>
</ul>
<p><code>nohup</code>加上需要后台执行的命令以及命令的参数<code>Command [ Arg … ]</code>，以<code>&amp;</code>结束</p>
</li>
<li>
<p><code>&gt;&gt;</code>表示追加，<code>&gt;</code>表示覆盖。</p>
</li>
<li>
<p><code>2&gt;&amp;1</code>：0表示键盘输入，1表示标准输出（屏幕输出），2表示错误输出。</p>
</li>
</ul>
<h2>总结</h2>
<p>算是知道了几个内网穿透的工具，在使用的过程中，由于腾讯云服务器的带宽的限制，访问tensorboard的时候，加载图片有些慢，所以其实有这方面的真实需求，可以使用那些付费的内网穿透工具，例如natapp，有很多套餐，但是都不贵。</p>
<p>ssh、http、仪表盘的配置合并到一起的文件如下：</p>
<p>外网服务器<code>frps.ini</code>:</p>
<pre><code>[common]
;ssh开通的端口，可以改
bind_port = 7000
;http开通的端口，可以改
vhost_http_port = 8080  
;仪表盘开通的端口，可以改
dashboard_port = 7500
;仪表盘账户名（可选），可以改
dashboard_user = admin
;仪表盘密码（可选），可以改  
dashboard_pwd = admin
</code></pre>
<p>内网服务器<code>frpc.ini</code>:</p>
<pre><code>[common]
;外网服务器地址，需要改
server_addr = x.x.x.x
;外网服务器端口，可以改
server_port = 7000

[ssh]
;协议类型，不用改
type = tcp
;本地地址，不用改
local_ip = 127.0.0.1
;本地ssh的端口，不用改
local_port = 22
;远程ssh的端口，可以改
remote_port = 6000

;ssh访问方式：ssh 用户名@外网服务器地址 -p 远程ssh端口remote_port

[web1]
;协议类型，不用改
type = http
;本地地址，可以改，需要对应web服务的端口，如jupyter以及tensorboard远程访问端口需要设置为这里的端口
local_port = 80
;对应外网服务器二级域名的，需要改，而且二级域名需要添加到域名记录列表
custom_domains = site1.example.com

;web访问方式：外网服务器二级域名:外网服务器端口号，如http://site1.example.com:7000


[web2]
;协议类型，不用改
type = http
;本地地址，可以改，需要对应web服务的端口，如jupyter以及tensorboard远程访问端口需要设置为这里的端口
local_port = 81
;对应外网服务器二级域名的，需要改，而且二级域名需要添加到域名记录列表
custom_domains = site2.example.com

;web访问方式：外网服务器二级域名:外网服务器端口号，如http://site2.example.com:7000
</code></pre>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【学习笔记】python模块--tqdm]]></title>
            <link>https://bingqiangzhou.github.io/posts/dailysummary-pythonmoduletqdm/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/dailysummary-pythonmoduletqdm/</guid>
            <pubDate>Wed, 17 Jun 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[今天总结一下tqdm模块~~以及pytorch框架选择GPU运行程序遇到的一个小问题。~~]]></description>
            <content:encoded><![CDATA[<p>今天总结一下tqdm模块<s>以及pytorch框架选择GPU运行程序遇到的一个小问题。</s>
tqdm模块，是一个python进度条模块，简单已用。
<a href="https://tqdm.github.io">tqdm文档</a> <a href="https://github.com/tqdm/tqdm">github</a> 推荐看github中的README了解如何使用，这个文档好像有点老旧。</p>
<h2>pytorch框架选择GPU运行程序</h2>
<p><s>pytorch框架选择GPU运行程序，发现<code>os.environ["CUDA_VISIBLE_DEVICES"]="0"</code>放置位置是有讲究的，需要放在倒入torch模块之前，才行。</s></p>
<p>经过测试，发现没有这个问题，之前我们遇到的问题是，程序没有用我自己设定的gpu跑代码，我调整了<code>config</code>文件（注：<code>config</code>文件中有语句<code>os.environ["CUDA_VISIBLE_DEVICES"]="0"</code>）的导入顺序之后，发现解决了问题，因此我错误的认为<code>os.environ["CUDA_VISIBLE_DEVICES"]="0"</code>需要在torch模块之前导入才行。</p>
<h2>tqdm模块</h2>
<h3>tqdm.tqdm类</h3>
<pre><code>tqdm.__init__(iterable=None,
              desc=None,
              total=None,
              leave=True,
              file=None,
              ncols=None,
              mininterval=0.1,
              maxinterval=10.0,
              miniters=None,
              ascii=None,
              disable=False,
              unit='it',
              unit_scale=False,
              dynamic_ncols=False,
              smoothing=0.3,
              bar_format=None,
              initial=0,
              position=None,
              postfix=None,
              unit_divisor=1000,
              write_bytes=None,
              lock_args=None,
              nrows=None,
              gui=False)
</code></pre>
<p>所有的参数都是可选的，初始化方法返回一个已经装饰了的可迭代器。</p>
<h4><code>iterable</code>参数</h4>
<p>需要用进度条装饰的可迭代的对象，list等等都可以。</p>
<h4><code>desc</code>参数</h4>
<p><code>desc</code>是进度条的前缀，字符串。如下图红框所示。</p>
<p><img src="/assets/images/2020/20200617/desc.png" alt="进度条的前缀" /></p>
<h4><code>total</code>参数</h4>
<p>迭代的总次数，不设置的话，就等于可迭代对象的长度<code>len(iterable)</code>，具体用法如下，常用于手动设置更新迭代次数。</p>
<pre><code>with tqdm(total=100) as pbar:
    for i in range(10):
        sleep(0.1)
        pbar.update(10)
</code></pre>
<pre><code>pbar = tqdm(total=100)
for i in range(10):
    sleep(0.1)
    pbar.update(10)
pbar.close()
</code></pre>
<h4><code>leave</code>参数</h4>
<p>布尔类型，默认为<code>True</code>，<code>leave</code>参数表示在迭代结束之后，是否保留进度条状态，如果设置为<code>False</code>或者<code>None</code>，则在迭代之后，不会保留最后的状态，而变成0，如下图所示。</p>
<p><img src="/assets/images/2020/20200617/leave.png" alt="参数" /></p>
<h4><code>file</code>参数</h4>
<p><code>io.TextIOWrapper</code>或者<code>io.StringIO</code>，默认为<code>sys.stderr</code>，表示将进度条信息输出到那里。如下图所示。</p>
<p><img src="/assets/images/2020/20200617/file.png" alt="参数" /></p>
<h4><code>ncols</code>参数</h4>
<p>输出信息所占的宽度，不指定，则为进度条的整个宽度，指定则为指定的宽度，如下图。</p>
<p><img src="/assets/images/2020/20200617/ncols.png" alt="参数" /></p>
<h4><code>mininterval</code>参数</h4>
<p>进度条更新的最小时间间隔，默认为0.1秒。如下图。</p>
<p><img src="/assets/images/2020/20200617/mininterval.png" alt="参数" /></p>
<p>更新进度条，其实就是说重新出来一个进度条。</p>
<h4><code>maxinterval</code>参数</h4>
<p>进度条更新的最大时间间隔，默认为10秒，与<code>mininterval</code>类似。</p>
<h4><code>miniters</code>参数</h4>
<p>进度条更新的最小迭代次数，当设置为0时，根据CPU性能自适应，指定了最小迭代次数，则经过了多少次迭代之后就会更新进度条。</p>
<h4><code>ascii</code>参数</h4>
<p><code>bool</code>或者<code>str</code>，如果不指定或者指定为<code>False</code>，会有滑块填充，如上面的图，有黑色的滑块，如果指定<code>ascii</code>参数，则不会有滑块出现，而是以<code>#</code>填充，并且没有进行完的地方会以数字滚动显示。</p>
<h4><code>disable</code>参数</h4>
<p><code>bool</code>，默认为<code>False</code>，如果指定为<code>True</code>则禁用了整个进度条，如果设置为<code>None</code>，在不是TTY（Teletypes，可以理解为在终端或者控制台上，不会禁用进度条）的情况下，禁用进度条。</p>
<p>经过尝试，在终端中设置为<code>None</code>，依然还是会显示进度条，而在jupyter notebook不会显示进度条。</p>
<h4><code>unit</code>参数</h4>
<p><code>str</code>，表示迭代次数的单位，默认是<code>it</code>，如下红色框框所示。</p>
<p><img src="/assets/images/2020/20200617/unit.png" alt="参数" /></p>
<h4><code>unit_scale</code>参数</h4>
<p>如果设定为<code>1</code>或者<code>True</code>，则会自适应数字单位，如下面的三万多变成了37.5k。
<img src="/assets/images/2020/20200617/unit_scale.png" alt="参数" /></p>
<h4><code>dynamic_ncols</code>参数</h4>
<p>默认为<code>False</code>，如果设置了，则会自适应输出的宽<code>ncols</code>和高<code>nrows</code>。</p>
<h4><code>smoothing</code>参数</h4>
<p>默认为<code>0.3</code>，设置范围在0到1之间，表示速度估算的指数移动平均平滑因子，<code>0</code>为平均速度，<code>1</code>是当前速度/瞬时速度，表示的是在进度条变化的时候，显示的多少次迭代每秒是平均速度还是瞬时速度。（原话：Exponential moving average smoothing factor for speed estimates）</p>
<h4><code>bar_format</code>参数</h4>
<p>进度条的格式，<code>str</code>，不同的进度条格式设置会影响性能，默认的格式是<code>{l_bar}{bar}{r_bar}</code>，其中<code>l_bar</code>是<code>{desc}: {percentage:3.0f}%|</code>，<code>r_bar</code>是<code>|{n_fmt}/{total_fmt} [{elapsed}&lt;{remaining},''{rate_fmt}{postfix}</code>，这里可以设置很多参数，如下<code>vars: l_bar, bar, r_bar, n, n_fmt, total, total_fmt, percentage, elapsed, elapsed_s, ncols, nrows, desc, unit, rate, rate_fmt, rate_noinv, rate_noinv_fmt, rate_inv, rate_inv_fmt, postfix, unit_divisor, remaining, remaining_s</code>。
当没有设置<code>desc</code>参数的时候，后面的冒号<code>:</code>会被自动去掉。</p>
<h4><code>initial</code>参数</h4>
<p>默认值为<code>0</code>，表示初始的迭代次数，常用于新开始一个进度条。如下图，从3开始，最后变成50003次迭代。</p>
<p><img src="/assets/images/2020/20200617/initial.png" alt="参数" /></p>
<h4><code>position</code>参数</h4>
<p>默认为<code>0</code>，如果设置为其他数（记为N），它会在进度条前，先输出N个空行。</p>
<h4><code>postfix</code>参数</h4>
<p>与<code>desc</code>参数相似，<code>desc</code>是前缀，<code>postfix</code>是后缀</p>
<p><img src="/assets/images/2020/20200617/postfix.png" alt="参数" /></p>
<h4><code>unit_divisor</code>参数</h4>
<p>迭代次数的除数，也可以理解为单位，默认为<code>1000</code>，当<code>unit_scale</code>参数为<code>True</code>的时候，忽略这个参数。</p>
<h4><code>write_bytes</code>参数</h4>
<p>表示写入二进制字节编码还是<code>unicode</code>编码，默认为写入<code>unicode</code>编码，当<code>write_bytes</code>参数为<code>True</code>时，写入二进制字节编码。</p>
<h4><code>lock_args</code>参数</h4>
<p><code>tuple</code>类型，刷新中间输出，通过<code>tqdm.refresh()</code>方法，获得中间输出.</p>
<h4><code>nrows</code>参数</h4>
<p>与<code>ncols</code>参数类似，表示显示进度条的高度.</p>
<h4><code>gui</code>参数</h4>
<p>将进度条使用<code>matplotlib</code>去画，进度条从字符串变成了图片。
不过不建议使用改参数，已经被<code>tqdm.gui.tqdm(...)</code>取代。</p>
<h4><code>tqdm.update(n=1)</code>方法</h4>
<p>默认<code>n=1</code>，常用于手动更新迭代次数。</p>
<h4><code>tqdm.close()</code>方法</h4>
<p>清除进度条，如果指定了<code>leave=False</code>，则进度条会被清理掉，反之还会留着。</p>
<h4><code>tqdm.refresh(nolock=False, lock_args=None)</code>方法</h4>
<p>刷新进度条的显示。</p>
<h4><code>tqdm.unpause()</code>方法</h4>
<p>重新开始打印时间，从上次停止的时间开始。</p>
<h4><code>tqdm.reset(total=None)</code>方法</h4>
<p>重设<code>total</code>参数</p>
<h4><code>tqdm.set_description(desc=None, refresh=True)</code>方法</h4>
<h4><code>tqdm.set_description_str(desc=None, refresh=True)</code>方法</h4>
<p>重新设置<code>desc</code>参数，<code>set_description_str</code>方法，没有冒号<code>:</code>跟在<code>desc</code>参数后。</p>
<p><strong>后面的一系列方法不再说明，大多数是重新设置初始化方法中的参数。</strong></p>
<h3><code>tqdm.trange()</code></h3>
<p>与<code>tqdm.tqdm()</code>类似，<code>tqdm.trange()</code>代替了<code>tqdm(range(*args), **kwargs)</code></p>
<h3><code>tqdm.notebook.tqdm()</code>与<code>tqdm.notebook.trange()</code></h3>
<p><img src="/assets/images/2020/20200617/notebook.png" alt="notebook" /></p>
<p>在<code>IPython</code>和<code>Jupyter Notebook</code>中显示好看一些的进度条，使用方法和<code>tqdm.tqdm()</code>、<code>tqdm.trange()</code>类似。</p>
<h3><code>tqdm.gui.tqdm()</code>与<code>tqdm.gui.trange()</code></h3>
<p>用于GUI的进度条，使用方法和<code>tqdm.tqdm()</code>、<code>tqdm.trange()</code>类似。</p>
<h3>实验性模块</h3>
<p>好像暂时还不能用。</p>
<h4><code>tqdm.contrib.tenumerate()</code></h4>
<p>类似于python内建的<code>enumerate</code>方法</p>
<h4><code>tqdm.contrib.tzip()</code></h4>
<p>类似于python内建的<code>zip</code>方法</p>
<h4><code>tqdm.contrib.tmap()</code></h4>
<p>类似于python内建的<code>tmap</code>方法</p>
<h3>自适应打印</h3>
<pre><code>from tqdm.auto import tqdm, trange
</code></pre>
<p>会根据环境的不同，输出不同样式的进度条。如下图所示。</p>
<p><img src="/assets/images/2020/20200617/auto-notebook.png" alt="notebook" /></p>
<p><img src="/assets/images/2020/20200617/auto-ipython.png" alt="ipython" /></p>
<h2>小结</h2>
<p>依旧比较啰嗦，其实主要知道<code>tqdm</code>, <code>trange</code>的用法就行。</p>
<pre><code>from tqdm.auto import tqdm, trange
</code></pre>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【学习笔记】python模块--argparse]]></title>
            <link>https://bingqiangzhou.github.io/posts/dailysummary-pythonmoduleargparse/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/dailysummary-pythonmoduleargparse/</guid>
            <pubDate>Tue, 16 Jun 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[今天总结一下python的argparse模块，这是python自带的命令行解析模块。]]></description>
            <content:encoded><![CDATA[<p>今天总结一下python的argparse模块，这是python自带的命令行解析模块。
<a href="https://docs.python.org/3/library/argparse.html">官方文档</a></p>
<h2>argparse模块</h2>
<p>argparse模块与logging一样都是python自带的模块，这个模块让命令行解析(如下图)变得简单，也可以说是很简单的将程序构建成为一个命令行工具。</p>
<p><img src="/assets/images/2020/20200616/command-line.png" alt="命令行工具" /></p>
<h3>创建解析器</h3>
<pre><code>class argparse.ArgumentParser(prog=None, usage=None, description=None, epilog=None, parents=[], formatter_class=argparse.HelpFormatter, prefix_chars='-', fromfile_prefix_chars=None, argument_default=None, conflict_handler='error', add_help=True, allow_abbrev=True)
</code></pre>
<p>创建一个<code>ArgumentParser</code>对象的可设置的参数，有上面这么多，不过我都只自定义设置过一个参数<code>description</code>，其他参数都默认，这个<code>description</code>参数呢，是<code>usage</code>与<code>agrument</code>之间的一个对程序的描述，可见上面的图。<a href="https://docs.python.org/3/library/argparse.html#argumentparser-objects"><code>ArgumentParser</code>文档</a></p>
<h3>加入参数</h3>
<pre><code>ArgumentParser.add_argument(name or flags...[, action][, nargs][, const][, default][, type][, choices][, required][, help][, metavar][, dest])
</code></pre>
<p>加入参数的过程是用上面创建解析器步骤的解析器对象调用<code>add_argument</code>方法来完成。<a href="https://docs.python.org/3/library/argparse.html#the-add-argument-method"><code>add_argument</code>方法文档</a>，这块用的比较多的，我们一个一个参数看一下。</p>
<h4><code>name or flags</code>参数</h4>
<p><code>name or flags</code>表示参数的名字，可以使用多个名字，一般有下面两种用法，分别表示为可选参数和必选参数。</p>
<pre><code>parser.add_argument('-f', '--foo') # 表示可选参数
parser.add_argument('bar') # 表示必须的参数
</code></pre>
<p>其中表示可选参数的形式，也可以通过<code>required</code>参数来将可选参数作为必选参数。</p>
<h4><code>action</code>参数</h4>
<p><code>action</code>参数是当解析参数时，当前参数有使用到的时候，调用action类来处理它。python提供了许多<code>action</code>，同时我们也可以自己实现。</p>
<ul>
<li>
<p><code>store</code>，<code>action</code>参数的默认值，只存储参数值。</p>
</li>
<li>
<p><code>store_const</code>存储固定值，常用于可选参数。</p>
</li>
<li>
<p><code>store_true</code> 与 <code>store_false</code>，存储布尔类型的值，默认为<code>True</code>或者<code>False</code>，相当于<code>default</code>参数设置为了<code>True</code>或<code>False</code>。</p>
</li>
<li>
<p><code>append</code>存储一个列表，将值追加到列表，常用于可选参数多次指定，实例如下：</p>
<pre><code>&gt;&gt;&gt; parser = argparse.ArgumentParser()
&gt;&gt;&gt; parser.add_argument('--foo', action='append')
&gt;&gt;&gt; parser.parse_args('--foo 1 --foo 2'.split())
Namespace(foo=['1', '2'])
</code></pre>
</li>
<li>
<p><code>append_const</code>也是存储一个列表，与<code>append</code>类似，不过追加的是常量。</p>
<pre><code>&gt;&gt;&gt; parser = argparse.ArgumentParser()
&gt;&gt;&gt; parser.add_argument('--str', dest='types', action='append_const', const=str)
&gt;&gt;&gt; parser.add_argument('--int', dest='types', action='append_const', const=int)
&gt;&gt;&gt; parser.parse_args('--str --int'.split())
Namespace(types=[&lt;class 'str'&gt;, &lt;class 'int'&gt;])
</code></pre>
</li>
<li>
<p><code>count</code>存储参数出现的次数，未出现则为0。</p>
<pre><code>&gt;&gt;&gt; parser = argparse.ArgumentParser()
&gt;&gt;&gt; parser.add_argument('--verbose', '-v', action='count', default=0)
&gt;&gt;&gt; parser.parse_args(['-vvv'])
Namespace(verbose=3)
</code></pre>
</li>
<li>
<p><code>help</code>，输出帮助信息，如输出最上面那张图那样的内容，默认添加到解析器中，使用<code>-h</code>或者<code>-help</code>调用。</p>
</li>
<li>
<p><code>version</code>，输出版本信息。</p>
<pre><code>&gt;&gt;&gt; import argparse
&gt;&gt;&gt; parser = argparse.ArgumentParser(prog='PROG')
&gt;&gt;&gt; parser.add_argument('--version', action='version', version='%(prog)s 2.0')
&gt;&gt;&gt; parser.parse_args(['--version'])
PROG 2.0
</code></pre>
</li>
<li>
<p><code>extend</code>，存储一个列表将参数值加入到这个列表。</p>
<pre><code>&gt;&gt;&gt; parser = argparse.ArgumentParser()
&gt;&gt;&gt; parser.add_argument("--foo", action="extend", nargs="+", type=str)
&gt;&gt;&gt; parser.parse_args(["--foo", "f1", "--foo", "f2", "f3", "f4"])
Namespace(foo=['f1', 'f2', 'f3', 'f4'])
</code></pre>
</li>
<li>
<p>自定义<code>action</code>，没用过，这里贴上示例代码。</p>
<pre><code>&gt;&gt;&gt; class FooAction(argparse.Action):
...     def __init__(self, option_strings, dest, nargs=None, **kwargs):
...         if nargs is not None:
...             raise ValueError("nargs not allowed")
...         super(FooAction, self).__init__(option_strings, dest, **kwargs)
...     def __call__(self, parser, namespace, values, option_string=None):
...         print('%r %r %r' % (namespace, values, option_string))
...         setattr(namespace, self.dest, values)
...
&gt;&gt;&gt; parser = argparse.ArgumentParser()
&gt;&gt;&gt; parser.add_argument('--foo', action=FooAction)
&gt;&gt;&gt; parser.add_argument('bar', action=FooAction)
&gt;&gt;&gt; args = parser.parse_args('1 --foo 2'.split())
Namespace(bar=None, foo=None) '1' None
Namespace(bar='1', foo=None) '2' '--foo'
&gt;&gt;&gt; args
Namespace(bar='1', foo='2')
</code></pre>
</li>
</ul>
<h4><code>nargs</code>参数</h4>
<p><code>nargs</code>参数关联不同数目的命令行参数到单一动作，也就是说<code>nargs</code>参数来规定命令行某个参数的个数。支持的值有<code>N</code>，<code>?</code>，<code>*</code>，<code>+</code>，<code>argparse.REMAINDER</code>，当<code>nargs</code>参数没有指定值，则参数的个数又<code>action</code>来决定。其中：</p>
<ul>
<li>
<p><code>N</code>表示参数后的N个值组成为一个列表。</p>
</li>
<li>
<p><code>?</code>表示的是多种情况，当参数没有出现的时候，参数的值是<code>defalut</code>参数的值，当参数后面没有跟着值的时候，参数的值是<code>const</code>参数的值，当参数出现并且后面有值的时候才参数的值才会是这个值。常用于输入输出文件，如下实例代码，没有输入输出文档，则从控制台读取以及写入。</p>
<pre><code>&gt;&gt;&gt; parser = argparse.ArgumentParser()
&gt;&gt;&gt; parser.add_argument('infile', nargs='?', type=argparse.FileType('r'),
...                     default=sys.stdin)
&gt;&gt;&gt; parser.add_argument('outfile', nargs='?', type=argparse.FileType('w'),
...                     default=sys.stdout)
&gt;&gt;&gt; parser.parse_args(['input.txt', 'output.txt'])
Namespace(infile=&lt;_io.TextIOWrapper name='input.txt' encoding='UTF-8'&gt;,
    outfile=&lt;_io.TextIOWrapper name='output.txt' encoding='UTF-8'&gt;)
&gt;&gt;&gt; parser.parse_args([])
Namespace(infile=&lt;_io.TextIOWrapper name='&lt;stdin&gt;' encoding='UTF-8'&gt;,
    outfile=&lt;_io.TextIOWrapper name='&lt;stdout&gt;' encoding='UTF-8'&gt;)
</code></pre>
</li>
<li>
<p><code>*</code>表示参数后面跟的所有值，组合成为一个列表，作为这个参数的值；需要注意的是：必须参数中<code>nargs</code>参数中为<code>*</code>的情况不要出现多次，这样会引起冲突，而可选残生是没关系的。</p>
</li>
<li>
<p><code>+</code>与<code>*</code>相似，也是将参数后面跟的值，组合成一个列表，不同的是<code>+</code>要求参数后面跟的值至少又一个值。</p>
</li>
<li>
<p><code>argparse.REMAINDER</code>表示所有对不上号的参数组合成一个列表。</p>
</li>
</ul>
<h4><code>const</code>参数</h4>
<p><code>const</code>参数是指定参数的常量值，当使用了<code>store_const</code>和 <code>append_const</code>动作的时候，必须指定const参数。</p>
<h4><code>default</code>参数</h4>
<p><code>default</code>参数指定参数的默认值，当<code>default</code>参数等于<code>argparse.SUPPRESS</code>，且参数没有出现时，则参数对应属性不存在。</p>
<h4><code>type</code>参数</h4>
<p><code>type</code>参数是参数对应的类型，<code>type</code>可以是任意可以调用的对象，而且传入字符串，返回操作后的值。</p>
<h4><code>choices</code>参数</h4>
<p><code>choices</code>参数传入一个容器，参数值只能从容器中选取一个，任何容器都可作为<code>choices</code>值传入，比如list 对象，set 对象以及自定义容器等。</p>
<h4><code>required</code>参数</h4>
<p><code>required</code>参数用于将可变参数变为必须参数。</p>
<h4><code>help</code>参数</h4>
<p><code>help</code>参数表示描述参数的字符串，由于帮助字符串支持<code>%-formatting</code>（示例如下），因此在这个帮助字符串中显示<code>%</code>，必须将其转义为<code>%%</code>。</p>
<pre><code>parser.add_argument('bar', nargs='?', type=int,default=42, help='the bar to %(prog)s (default: %(default)s)')
</code></pre>
<h4><code>metavar</code>参数</h4>
<p>使用<code>metavar</code>来指定一个参数的替代名称，需要注意的是，它只是替代了显示的名字，而正在调用的参数名称是由<code>dest</code>参数决定的。不使用<code>metavar</code>参数的默认情况下，对于位置参数动作，<code>dest</code>值将被直接使用，而对于可选参数动作，<code>dest</code> 值将被转为大写形式。 因此，一个位置参数<code>dest='bar'</code>的引用形式将为<code>bar</code>，一个带有单独命令行参数的可选参数<code>--foo</code>的引用形式将为<code>FOO</code>。</p>
<h4><code>dest</code>参数</h4>
<p>设置解析参数所返回对象的属性名的，默认情况下，会接受第一个长选项字符串并去掉开头的<code>--</code>字符串来生成<code>dest</code>的值，如果没有提供长选项字符串，则<code>dest</code>将通过接受第一个短选项字符串并去掉开头的<code>-</code>字符来获得；如果名称内部有<code>-</code>字符都将被转换为<code>_</code>字符，来确保字符串是有效的属性名称。</p>
<h3>解析参数</h3>
<pre><code>ArgumentParser.parse_args(args=None, namespace=None)
</code></pre>
<p><code>args</code>是要解析的字符串列表，默认值是从<code>sys.argv</code>获取。
<code>namespace</code>用于获取属性的对象，默认值是一个新的空<code>Namespace</code>对象，这里的<code>Namespace</code>是一个具有可读字符串表示形式的对象。</p>
<p>解析参数，在这里不多说了，给一个示例代码。</p>
<h3>示例代码</h3>
<pre><code>parser = argparse.ArgumentParser(description='train or val for our model which no embeding')

parser.add_argument("--exp", type=str, default="train_without_embeding", help="experiment")
parser.add_argument("--VOC2012_root_path", type=str, default="/home/mist/datasets/VOCdevkit/VOC2012",
                        help="the root of VOC2012 dataset")
parser.add_argument("--num_epochs", type=int, default=100, help="number of epoch")
parser.add_argument('--use_gpu', action='store_true',default=True, help='whether to use GPU')
parser.add_argument('--gpu', type=str, default="0", help='choose GPU')
parser.add_argument('--optimizer', type=str, default="Adam", choices=['SGD', 'Adam'], help='choose optimizer')

args = parser.parse_args()
</code></pre>
<h3>小结</h3>
<p>感觉这次还是有点在翻译官方文档的感觉，不过也算是将官方文档大概的看了一下，明天继续总结一下tqdm模块，加油，奥利给！</p>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【学习笔记】python模块--logging]]></title>
            <link>https://bingqiangzhou.github.io/posts/dailysummary-pythonmoduleloggingargparsetqdm/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/dailysummary-pythonmoduleloggingargparsetqdm/</guid>
            <pubDate>Mon, 15 Jun 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[上星期挺忙的，参加了一下学校的数学建模比赛，预测湖南湖北的疫情走势以及疫情对长沙经济的影响。]]></description>
            <content:encoded><![CDATA[<p>上星期挺忙的，参加了一下学校的数学建模比赛，预测湖南湖北的疫情走势以及疫情对长沙经济的影响。
看了别人数学系的论文之后，发现我们太业余了，哈哈，我们基本就是在网上找了几片论文看了一下，然后选择了一个模型，对疫情数据做了一下拟合。他们我就不说了。。</p>
<p>今天总结一下python模块，logging、argparse、tqdm，好早就想小结一下，一直没有做.（写的有点多，argparse、tqdm独立出来，下两篇）
还有<a href="https://nndl.github.io">《神经网络与深度学习》</a>中的第七章的网络优化与正则化、第八章的注意力机制，看了好久了，也想总结一下，这几天都总结一下吧。</p>
<h2>logging模块</h2>
<p>logging是python自带的日志模块<a href="https://docs.python.org/3/library/logging.html#">官方文档</a>，这里主要总结一下Logger、Handler、Formatter对象以及常用的方法。</p>
<h3>Logger对象-<code>class logging.Logger</code></h3>
<h4>获取日志对象Logger</h4>
<p>Logger类不能被直接实例化，需要用<code>logging.getLogger(name)</code>来获取日志对象。</p>
<h4>常用方法</h4>
<ol>
<li>设置日志对象输出信息的最低级别<code>setLevel(level)</code>，相应的<a href="#%E6%97%A5%E5%BF%97%E7%BA%A7%E5%88%AB">日志级别</a></li>
<li>添加处理日志的处理器对象<code>addHandler(hdlr)</code></li>
<li>输出对应级别的信息：<code>debug(msg, *args, **kwargs)</code>、<code>info(msg, *args, **kwargs)</code>、<code>warning(msg, *args, **kwargs)</code>、<code>error(msg, *args, **kwargs)</code>、<code>critical(msg, *args, **kwargs)</code>、<code>log(level, msg, *args, **kwargs)</code>、<code>exception(msg, *args, **kwargs)</code>，相应的<a href="#%E6%97%A5%E5%BF%97%E7%BA%A7%E5%88%AB">日志级别</a>。</li>
</ol>
<h4>日志级别</h4>
<p>日志级别如下表，根日志对象默认的级别是<code>NOTSET</code>，而后代日志对象默认是<code>WARNING</code>，后代日志对象可以由<code>getChild(suffix)</code>方法来获取。</p>
<table>
<thead>
<tr>
<th>级别</th>
<th>对应的数值</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>CRITICAL</code></td>
<td>50</td>
</tr>
<tr>
<td><code>ERROR</code></td>
<td>40</td>
</tr>
<tr>
<td><code>WARNING</code></td>
<td>30</td>
</tr>
<tr>
<td><code>INFO</code></td>
<td>20</td>
</tr>
<tr>
<td><code>DEBUG</code></td>
<td>10</td>
</tr>
<tr>
<td><code>NOTSET</code></td>
<td>0</td>
</tr>
</tbody>
</table>
<h3>Handler对象-<code>logging.Handler</code></h3>
<p><a href="https://docs.python.org/3/library/logging.html#handler-objects">Handler对象</a>是可以直接实例化的，而且它还有很多子类，如下:</p>
<ul>
<li><a href="https://docs.python.org/3/library/logging.handlers.html#streamhandler">StreamHandler</a>：
<ul>
<li><code>StreamHandler(stream=None)</code></li>
<li>输出日志到<code>sys.stdout</code>，<code>sys.stderr</code>或者是其他<code>file-like</code>文件对象，常用于输出到控制台</li>
</ul>
</li>
<li><a href="https://docs.python.org/3/library/logging.handlers.html#filehandler">FileHandler</a>：
<ul>
<li><code>FileHandler(filename, mode='a', encoding=None, delay=False)</code></li>
<li>输出日志到文件，继承了StreamHandler</li>
</ul>
</li>
<li>NullHandler：不做任何处理</li>
<li>WatchedFileHandler：监测文件打开与关闭操作，输出日志</li>
<li>BaseRotatingHandler：滚动处理<code>RotatingFileHandler</code>，<code>TimedRotatingFileHandler</code>的基类</li>
<li><a href="https://docs.python.org/3/library/logging.handlers.html#logging.handlers.RotatingFileHandler">RotatingFileHandler</a>：
<ul>
<li><code>RotatingFileHandler(filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False)</code></li>
<li>这个Handler类似于上面的FileHandler，但是它可以管理文件大小。当文件达到一定大小<code>maxBytes</code>之后，它会自动将当前日志文件改名，然后创建一个新的同名日志文件继续输出。比如日志文件是app.log。当app.log达到指定的大小之后，RotatingFileHandler自动把文件改名为app.log.1，如果app.log.1已经存在，会先把app.log.1重命名为app.log.2，一直到最后重新创建chat.log，继续输出日志信息，如果<code>backupCount=5</code>那么，最多创建备份到app.log.5。</li>
</ul>
</li>
<li><a href="https://docs.python.org/3/library/logging.handlers.html#timedrotatingfilehandler">TimedRotatingFileHandler</a>
<ul>
<li><code>TimedRotatingFileHandler(filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None)</code></li>
<li>这个Handler和RotatingFileHandler类似，不过，它没有通过判断文件大小来决定何时重新创建日志文件，而是间隔一定时间就自动创建新的日志文件。重命名的过程与RotatingFileHandler类似，不过新的文件不是附加数字，而是当前时间。其中filename参数和backupCount参数和RotatingFileHandler具有相同的意义。interval是时间间隔.when参数是一个字符串。表示时间间隔的单位，不区分大小写。它有以下取值：
<table>
<thead>
<tr>
<th>值</th>
<th>类型</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>S</code></td>
<td>秒</td>
</tr>
<tr>
<td><code>M</code></td>
<td>分</td>
</tr>
<tr>
<td><code>H</code></td>
<td>小时</td>
</tr>
<tr>
<td><code>D</code></td>
<td>天</td>
</tr>
<tr>
<td><code>W0</code>-<code>W6</code></td>
<td>每星期（0表示星期一）</td>
</tr>
<tr>
<td><code>midnight</code></td>
<td>每天凌晨</td>
</tr>
</tbody>
</table>
</li>
</ul>
</li>
<li>SocketHandler：远程输出日志到TCP/IP sockets</li>
<li>DatagramHandler：远程输出日志到UDP sockets</li>
<li>SysLogHandler：日志输出到系统日志syslog</li>
<li>NTEventLogHandler：远程输出日志到Windows NT/2000/XP的事件日志</li>
<li>SMTPHandler：远程输出日志到邮件地址</li>
<li>MemoryHandler：日志输出到内存中的制定buffer</li>
<li>HTTPHandler：通过"GET"或"POST"远程输出到HTTP服务器</li>
<li>QueueHandler：输出日志到队列中</li>
<li>QueueListener：从队列中接受日志信息</li>
</ul>
<p>以上这些<a href="https://docs.python.org/3/library/logging.handlers.html">logging.Handler子类</a>，比较常用的是<code>StreamHandler</code>、<code>FileHandler</code>、<code>RotatingFileHandler</code>、<code>TimedRotatingFileHandler</code>。</p>
<h4>常用方法</h4>
<ol>
<li>设置处理器处理信息最低等级<code>setLevel(level)</code></li>
<li>设置输出日志信息格式<code>setFormatter(fmt)</code>，fmt为<a href="#Formatter%E5%AF%B9%E8%B1%A1-%60logging.Formatter%60">Formatter对象</a></li>
</ol>
<h3>Formatter对象-<code>logging.Formatter</code></h3>
<p>Formatter对象用于规定输出日志的格式，这些格式其实是来源于LogRecord对象的属性，不过可以通过字符串的方式直接创建，如<a href="#%E5%AE%9E%E4%BE%8B%E4%BB%A3%E7%A0%81">实例代码</a>中logging.Formatter。</p>
<table>
<thead>
<tr>
<th>格式</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>%(asctime)s</code></td>
<td>调用日志输出函数的的时间，默认格式像这样<code>2003-07-08 16:49:45,896</code></td>
</tr>
<tr>
<td><code>%(created)f</code></td>
<td>与<code>%(asctime)s</code>类似，也是调用日志输出函数的时间，但是输出格式不同，输出time.time()，时间戳格式，像这样<code>1592202653.744816</code>的数字</td>
</tr>
<tr>
<td><code>%(filename)s</code></td>
<td>调用日志输出函数是在哪个文件创建的文件名</td>
</tr>
<tr>
<td><code>%(funcName)s</code></td>
<td>调用日志输出函数是在哪个方法中的方法名</td>
</tr>
<tr>
<td><code>%(levelname)s</code></td>
<td>调用日志输出函数对应的<a href="#%E6%97%A5%E5%BF%97%E7%BA%A7%E5%88%AB">级别</a>名称</td>
</tr>
<tr>
<td><code>%(levelno)s</code></td>
<td>调用日志输出函数对应的<a href="#%E6%97%A5%E5%BF%97%E7%BA%A7%E5%88%AB">级别</a>数字</td>
</tr>
<tr>
<td><code>%(lineno)d</code></td>
<td>调用日志输出函数对应是在代码文件中的哪一行  （有可能不存在）</td>
</tr>
<tr>
<td><code>%(message)s</code></td>
<td>日志消息，由我们调用日志输出函数中指定</td>
</tr>
<tr>
<td><code>%(module)s</code></td>
<td>调用日志输出函数是在哪个模块中创建的模块名</td>
</tr>
<tr>
<td><code>%(msecs)d</code></td>
<td>与<code>%(asctime)s</code>类似，也是调用日志输出函数的时间，但是输出格式不同，输出的是毫秒</td>
</tr>
<tr>
<td><code>%(name)s</code></td>
<td>Logger对象的名字</td>
</tr>
<tr>
<td><code>%(pathname)s</code></td>
<td>调用日志输出函数的模块的完整路径名（可能不存在）</td>
</tr>
<tr>
<td><code>%(process)d</code></td>
<td>调用日志输出函数属于哪个进程的id（可能不存在）</td>
</tr>
<tr>
<td><code>%(processName)s</code></td>
<td>调用日志输出函数属于哪个进程的名称（可能不存在）</td>
</tr>
<tr>
<td><code>%(relativeCreated)d</code></td>
<td>调用日志输出函数的相对时间，自Logger对象创建以来的毫秒数</td>
</tr>
<tr>
<td><code>%(thread)d</code></td>
<td>调用日志输出函数属于哪个线程的id（可能不存在）</td>
</tr>
<tr>
<td><code>%(threadName)s</code></td>
<td>调用日志输出函数属于哪个线程的名称（可能不存在）</td>
</tr>
</tbody>
</table>
<p>格式就大概讲到这里，具体可以看<a href="#%E5%AE%9E%E4%BE%8B%E4%BB%A3%E7%A0%81">实例代码</a>。</p>
<h3>实例代码</h3>
<pre><code>import logging
import os

class Logger():

    def __init__(self, logger_path):
        self.logger = logging.getLogger()
        self.logger.setLevel(logging.DEBUG)
        # 输出到文件
        self.logfile = logging.FileHandler(logger_path)
        self.logfile.setLevel(logging.DEBUG)
        # formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        formatter = logging.Formatter('%(asctime)s -%(filename)s:%(lineno)s - %(levelname)s - %(message)s')
        self.logfile.setFormatter(formatter)
        # 输出到控制台
        self.logdisplay = logging.StreamHandler()
        # self.logdisplay.setLevel(logging.DEBUG)
        self.logdisplay.setLevel(logging.INFO)
        self.logdisplay.setFormatter(formatter)
        self.logger.addHandler(self.logfile)
        self.logger.addHandler(self.logdisplay)

    def get_logger(self):
        return self.logger
</code></pre>
<pre><code>from logger import Logger

logger = Logger('./test.log')
my_logger = logger.get_logger()

my_logger.info('hello, world')
my_logger.debug('hello, world')
## `debug(msg, *args, **kwargs)`
## `info(msg, *args, **kwargs)`
## `warning(msg, *args, **kwargs)`
## `error(msg, *args, **kwargs)`
## `critical(msg, *args, **kwargs)`
## `log(level, msg, *args, **kwargs)`
## `exception(msg, *args, **kwargs)`
</code></pre>
<h2>小结</h2>
<p>上面写了这么多，总结一起，主要就是上面的实例代码这部分代码，全文内容应该差不多可以满足我们大多数日常开放的使用，就总结到这了，用过滤器对象过滤输出等等其他内容就等待探索了，<a href="https://docs.python.org/3/library/logging.html#logrecord-attributes">文档在此</a>。</p>
<p>本来想logging、argparse、tqdm三个模块放一起的，发现写的有点多（啰里八嗦的），argparse、tqdm模块还是独立写出来吧。</p>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【论文阅读笔记】DFANet: Deep Feature Aggregation for Real-Time Semantic Segmentation]]></title>
            <link>https://bingqiangzhou.github.io/posts/paperreading-dfanet_deepfeatureaggregationforrealtimesemanticsegmentation/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/paperreading-dfanet_deepfeatureaggregationforrealtimesemanticsegmentation/</guid>
            <pubDate>Fri, 05 Jun 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[数字图像处理课要结课了，结课任务是讲解一篇，然后我们拿到的论文是这一篇：DFANet: Deep Feature Aggregation for Real-Time Semantic Segmentation，今天看了一下，论文主要提出一种特征聚合（Feature Aggregation）的结构，这里总结一下。]]></description>
            <content:encoded><![CDATA[<p>数字图像处理课要结课了，结课任务是讲解一篇，然后我们拿到的论文是这一篇：DFANet: Deep Feature Aggregation for Real-Time Semantic Segmentation，今天看了一下，论文主要提出一种特征聚合（Feature Aggregation）的结构，这里总结一下。</p>
<p><a href="https://arxiv.org/abs/1904.02216">论文简介</a>
<a href="https://arxiv.org/pdf/1904.02216">论文下载</a>
<a href="https://github.com/huaifeng1993/DFANet">源码地址</a></p>
<h2>提出并解决问题</h2>
<p>实时语义分割：希望同时做到分割速度快、分割质量高。有许多方法已经在一些benchmarks（例如：pascal voc、cityscapes、coco等等数据集）上取得好的效果。（<a href="https://zhuanlan.zhihu.com/p/129872257">拓展知识: 浅谈 baseline &amp; benchmark &amp; backbone中的理解</a>）</p>
<h3>提出问题</h3>
<ul>
<li>
<p>有些方法使用U-shape结构<a href="%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3U%E5%9E%8B%E7%BB%93%E6%9E%84%E5%91%A2%EF%BC%8C%E6%88%91%E4%B8%AA%E4%BA%BA%E7%9A%84%E7%90%86%E8%A7%A3%E6%98%AF%EF%BC%8C%E5%9B%BE%E5%83%8F%E5%A4%A7%E5%B0%8F%E5%9C%A8%E7%BC%96%E7%A0%81%E7%9A%84%E6%97%B6%E5%80%99%E4%B8%8D%E6%96%AD%E7%9A%84%E5%8F%98%E5%B0%8F%EF%BC%8C%E9%9A%8F%E5%90%8E%E5%86%8D%E8%A7%A3%E7%A0%81%E7%9A%84%E8%BF%87%E7%A8%8B%E5%9B%BE%E5%83%8F%E5%A4%A7%E5%B0%8F%E5%8F%88%E4%B8%8D%E6%96%AD%E7%9A%84%E5%8F%98%E5%A4%A7%EF%BC%8C%E6%9C%89%E8%BF%99%E7%A7%8D%E6%AF%94%E8%BE%83%E5%AF%B9%E7%A7%B0%E7%9A%84%E5%8F%98%E5%B0%8F%E5%8F%98%E5%A4%A7%E7%9A%84%E8%BF%87%E7%A8%8B%E7%9A%84%E7%BD%91%E7%BB%9C%EF%BC%8C%E6%88%91%E6%8A%8A%E4%BB%96%E7%90%86%E8%A7%A3%E4%B8%BAU%E5%9E%8B%E7%BB%93%E6%9E%84%E3%80%82">^u_shape</a>（U型结构，例如下图结构），这种结构在处理高分辨率特征图时候，会消耗大量时间。（论文中这么说，我个人觉得还是跟网络深度有关系，这里没有明白为什么作者要特意的挑出U型结构。）
<img src="/assets/images/2020/20200605/u-shape-architecture.png" alt="U型结构" /></p>
<ul>
<li>有些工作通过限制固定的图像大小。</li>
<li>有些工作会去掉特征图的一些“冗余”信息（比如使用Spatial Dropout，在DeepLabV3+论文中提到说不是所有的的特征都对解码模块有重要的作用）。</li>
</ul>
<p>一些方法通过以上两个方法来加快计算速度，但是相应的也存在一些问题。<strong>会丢失掉一些边界和小对象的空间细节</strong></p>
</li>
<li>
<p>浅层网络（shallow network）的特征区分能力会比较弱，然而使用深层的网络（deep network）则会影响速度。</p>
</li>
</ul>
<h3>解决问题</h3>
<p>为了解决以上问题，最大程度的利用特征，并尽可能的减少计算量，很多相应的结构产生了，如下图所示。
<img src="/assets/images/2020/20200605/structure-comparison.png" alt="结构比较" /></p>
<ul>
<li>多分支（Multi-branch）结构，图中（a），使用这种多分支结构结合了空间细节和上下文信息，然而加入了额外的分支，会使得速度受到限制，并且相互独立的两个分支会限制模型的学习能力。</li>
<li>空间金字塔池化（spatial pyramid pooling）结构，图中（b），使用空间金字塔池化结构呢，加强了带有高层次上下文的特征（features with high-level context），但是这样也会显著的加大计算量，而且加强的特征是来自于单路径输出的特征图，缺乏低层次特征，而低层次特征同样保留空间细节和分割信息。</li>
<li>网络级的特征重用（feature reuse in network level）结构，图中（c），使用网络级的特征重用结构，则包括了低层次的特征。从图（c）中看，其实就是使用了单个网络结构的低层次与高层次的特征，那它为什么叫网络级的特征重用呢，其实是因为左边与右边的网络结构是一样的，只是输入的大小变小了。</li>
<li>阶段级的特征重用（feature reuse in stage level）结构，图中（d），这种结构在结合了网络级的特征重用结构的基础上，还在不同的阶段聚合特征，加强了特征表示能力，重用（reuse）高层次的特征打破了语义信息与空间结构细节的隔阂（gap）（即同时使用了低层次与高层次的特征）。</li>
</ul>
<p>论文中提出的网络级的特征重用结构，图中（c）、阶段级的特征重用结构，图中（d），显然会消耗更多的空间以及算力，但是由于可以更好的利用特征，所以降低网络的深度，从而达到整体计算量减少的效果。</p>
<h2>DFANet与其他语义分割模型比较</h2>
<p>论文中方法DFANet（Deep Feature Aggregation Network）与其他语义分割模型性能与计算量比较，如下图。
<img src="/assets/images/2020/20200605/result-compare.png" alt="与其他模型比较" /></p>
<p>横坐标单位为GFLOPs（Giga FLoating-point Operations），十亿浮点运算次数，衡量模型的复杂度（计算量），这个概念需要和全大写的FLOPS（floating point operations per second），意指每秒浮点运算次数，是一个衡量硬件性能的指标（每秒的计算速度）。
纵坐标是准确率（交叠率，mIoU），可以看出效果在性能与计算量是比较均衡的。
其中DFANet A与DFANet B是在使用不同的backbone的情况下进行比较，DFANet A使用的backbone的计算量是DFANet B的两倍（1.6GFLOPs：0.83GFLOPs），DFANet A与DFANet A‘是在不同的输入图像大小的情况下进行比较的，DFANet A的输入图片的大小为1024x1024，DFANet A‘的输入大小为512x1024。</p>
<h2>相关的工作与概念</h2>
<h3>相关工作</h3>
<p>实时的语义分割相关工作：</p>
<ul>
<li>SegNet使用比较小的架构（small architecture）和池化索引策略（pooling indices strategy）来减少网络参数。</li>
<li>ENet减少下采样的次数，追求的紧致的框架，然后去掉了模型的最后一个阶段，使得感受野变小，以至于导致对大的对象分割效果不够好。</li>
<li>ESPNet使用了新的空间金字塔结构。</li>
<li>ICNet使用多尺度的图像作为输入，并且使用一个级联网络来提升效率。</li>
<li>BiSeNet使用空间路径（spatial path）和语义路径（semantic
path）来减少计算量。</li>
</ul>
<h3>相关概念</h3>
<ul>
<li>深度分离卷积（Depthwise Separable Convolution）：深度卷积操作由逐深度卷积（depthwise convolution）加逐点卷积（pointwise convolution）构成，可以在保持很小的性能（效果）损失的情况下，减少参数和计算量。Xception模块中的卷积就是深度分离卷积，因而兼顾了语义分割速度和准确度。相关概念可以查看这个帖子<a href="https://zhuanlan.zhihu.com/p/80041030">Depthwise卷积与Pointwise卷积</a>。</li>
<li>高层次特征（High-level Features）：高层次的特征刻画了输入图片的语义信息，分割任务关键在于感受野与分类能力，PSPNet、与DeepLab系列网络使用额外的操作将山下问信息和多尺度特征表示信息合并到一起。空间金字塔池化已经广泛的应用，作为对整体场景理解（overall scene interpretation）一个好的描述。</li>
<li>上下文编码（Context Encoding）：SENet使用逐深度注意力（channel-wise attention）提升了模型特征的表示。EncNet使用上下文编码来加强对每个像素点分割结果的预测，这本篇论文中使用全连接模块作为attention，来提升backbone性能（会对性能有一点点影响）。</li>
<li>特征聚合（Feature Aggregation）：实现特征聚合有跳跃连接（skip connection），另外还有密集连接（dense connection），另外RefineNet提出在上采样解读提取多尺度特征的模块。</li>
</ul>
<h2>论文中提出的方法</h2>
<h3>模型</h3>
<p>论文提出的模型主要分三部分轻量级权重的backbone（the lightweight backbones）, 子网络级的聚合（sub-network aggregation ）模块和子阶段级的聚合（sub-stage aggregation ）模块，而其中子网络级的聚合模块和子阶段级的聚合模块是相融在一起的，如下图。
<img src="/assets/images/2020/20200605/model.png" alt="模型图" /></p>
<p>模型图中，“C”表示串联到一起（concatenation），“xN”代表上采样N倍。“+”表示对应元素相加，‘x’表示对应元素相乘，fc attention模块是Xception中的全连接层，加上一个1x1卷积<a href="%E8%BF%99%E9%87%8C%E8%AE%BA%E6%96%87%E4%B8%AD%E8%AF%B4%E6%98%AF1x1%E5%8D%B7%E7%A7%AF%EF%BC%8C%E5%AE%9E%E7%8E%B0%E7%9A%84%E6%97%B6%E5%80%99%E6%98%AF%E4%BD%BF%E7%94%A8%E7%9A%84%E5%85%A8%E8%BF%9E%E6%8E%A5%EF%BC%8C%E4%BB%94%E7%BB%86%E4%B8%80%E6%83%B3%EF%BC%8C%E5%AF%B9%E4%BA%8E%E7%BB%B4%E5%BA%A6%E4%B8%BA%EF%BC%88b%EF%BC%8C1000%EF%BC%8C1%EF%BC%8C1%EF%BC%89%E7%9A%84%E5%90%91%E9%87%8F%E7%9A%84%E5%85%A8%E9%93%BE%E6%8E%A5%E4%B8%8E1x1%E7%9A%84%E5%8D%B7%E7%A7%AF%E6%98%AF%E4%B8%80%E6%A0%B7%E7%9A%84%E3%80%82">^1x1_conv</a>在于之前的特征逐元素相乘（[B, 1000, 1, 1] 通过1x1卷积得到[B, 192, 1, 1]），其他各个模块的细节如下图，
，conv1是普通的卷积操作，enc2、enc3、enc4中的卷积都是深度分离卷积操作，Xception A与Xception B是两种不同的Bakcbone。<a href="%E7%9C%8B%E4%BB%A3%E7%A0%81%EF%BC%8C%E6%88%91%E6%89%8D%E5%8F%91%E7%8E%B0enc2%E3%80%81enc3%E3%80%81enc4%E4%B8%AD%E7%9A%84%E5%8D%B7%E7%A7%AF%E8%BE%93%E5%87%BA%E7%9A%84%E9%80%9A%E9%81%93%E6%95%B0%E9%83%BD%E6%98%AF%E6%9C%80%E5%90%8E%E4%B8%80%E5%B1%82%E6%98%AF%E5%89%8D%E9%9D%A2%E7%9A%84%E4%B8%A4%E5%80%8D%EF%BC%8C%E8%80%8C%E4%B8%94Xception%E4%B8%AD%E7%9A%84enc2%E3%80%81enc3%E3%80%81enc4%E7%9A%84%E5%AE%9E%E7%8E%B0%E6%98%AF%EF%BC%8C%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%B7%B1%E5%BA%A6%E5%88%86%E7%A6%BB%E5%8D%B7%E7%A7%AF%E6%9C%89stride=2%EF%BC%8C%E7%AC%AC%E4%BA%8C%E3%80%81%E4%B8%89%E5%B1%82%E6%B2%A1%E6%9C%89%EF%BC%8C%E6%9C%80%E5%90%8E%E5%8F%88%E4%B8%80%E4%B8%AA%E5%AF%B9%E8%BE%93%E5%85%A5%E8%BF%87%E4%B8%80%E6%AC%A11x1%E5%8D%B7%E7%A7%AF%EF%BC%8Cstride=2%EF%BC%8C%E5%86%8D%E4%BA%8E%E7%AC%AC%E4%B8%89%E5%B1%82%E7%9A%84%E7%BB%93%E6%9E%84%E5%AF%B9%E4%BA%8E%E5%85%83%E7%B4%A0%E7%9B%B8%E5%8A%A0%E3%80%82%E7%BB%93%E6%9E%84%E5%8F%AF%E4%BB%A5%E7%B1%BB%E4%BC%BC%E4%BA%8E%E4%B8%8B%E5%9B%BE%E3%80%82!%5BXception%5D(/assets/images/2020/20200605/xception.png)">^Xception</a>
<img src="/assets/images/2020/20200605/module-details.png" alt="模块细节" /></p>
<p>在模型图中decoder那部分紫色的conv都没有体现卷积的通道，我看了代码它是输出64个通道的1x1卷积，所以输出大小维持原大小。在解码阶段的卷积只是做了减少通道数量的操作。</p>
<h3>训练细节</h3>
<ul>
<li>损失是用的交叉熵损失</li>
<li>backbone是用的作者自己修改后的Xception模型，并在ImageNet-1k数据集<a href="ImageNet%E6%95%B0%E6%8D%AE%E9%9B%86%E6%9C%8914M%EF%BC%881400%E5%A4%9A%E4%B8%87%EF%BC%89%E5%BC%A0%E5%9B%BE%E7%89%87%EF%BC%8C%E6%9C%8922k%E7%A7%8D%E7%B1%BB%E5%88%AB%EF%BC%8C%E8%80%8CImageNet%E2%80%941k%E6%95%B0%E6%8D%AE%E9%9B%86%E6%98%AF%E5%8F%AA%E6%9C%891k%E7%A7%8D%E7%B1%BB%E5%88%AB%EF%BC%8C%E5%AF%B9%E5%BA%94%E5%9B%BE%E7%89%87%E4%B8%80%E7%99%BE%E5%A4%9A%E4%B8%87%E5%BC%A0%E3%80%82">^ImageNet_1k</a>上进行训练，然后用于DFANet模型的预训练。</li>
<li>fc attention<a href="Attention%EF%BC%8C%E7%AE%80%E8%80%8C%E8%A8%80%E4%B9%8B%EF%BC%8C%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E6%B3%A8%E6%84%8F%E5%8A%9B%E5%8F%AF%E4%BB%A5%E8%A2%AB%E5%B9%BF%E4%B9%89%E5%9C%B0%E7%90%86%E8%A7%A3%E4%B8%BA%E8%A1%A8%E7%A4%BA%E9%87%8D%E8%A6%81%E6%80%A7%E7%9A%84%E6%9D%83%E9%87%8D%E5%90%91%E9%87%8F%EF%BC%88%E4%B8%80%E8%88%AC%E4%BC%9A%E5%81%9A%E4%B8%80%E4%B8%AA%E5%BD%92%E4%B8%80%E5%8C%96%EF%BC%8C%E6%AF%94%E5%A6%82%E9%80%9A%E8%BF%87%E4%B8%80%E4%B8%AAsigmoid%E5%87%BD%E6%95%B0%EF%BC%89%E3%80%82%E4%B8%BA%E4%BA%86%E9%A2%84%E6%B5%8B%E6%88%96%E6%8E%A8%E6%96%AD%E4%B8%80%E4%B8%AA%E5%85%83%E7%B4%A0%EF%BC%8C%E4%BE%8B%E5%A6%82%E5%9B%BE%E5%83%8F%E4%B8%AD%E7%9A%84%E5%83%8F%E7%B4%A0%E6%88%96%E5%8F%A5%E5%AD%90%E4%B8%AD%E7%9A%84%E5%8D%95%E8%AF%8D%EF%BC%8C%E6%88%91%E4%BB%AC%E4%BD%BF%E7%94%A8%E6%B3%A8%E6%84%8F%E5%8A%9B%E6%9D%83%E9%87%8D%E6%9D%A5%E4%BC%B0%E8%AE%A1%E5%85%B6%E5%AE%83%E5%85%83%E7%B4%A0%E4%B8%8E%E5%85%B6%E7%9B%B8%E5%85%B3%E7%9A%84%E5%BC%BA%E5%BA%A6%EF%BC%8C%E5%B9%B6%E5%B0%86%E7%94%B1%E6%B3%A8%E6%84%8F%E5%8A%9B%E6%9D%83%E9%87%8D%E5%8A%A0%E6%9D%83%E7%9A%84%E5%80%BC%E7%9A%84%E6%80%BB%E5%92%8C%E4%BD%9C%E4%B8%BA%E8%AE%A1%E7%AE%97%E6%9C%80%E7%BB%88%E7%9B%AE%E6%A0%87%E7%9A%84%E7%89%B9%E5%BE%81%E3%80%82Step1%EF%BC%9A%E8%AE%A1%E7%AE%97%E5%85%B6%E5%AE%83%E5%85%83%E7%B4%A0%E4%B8%8E%E5%BE%85%E9%A2%84%E6%B5%8B%E5%85%83%E7%B4%A0%E7%9A%84%E7%9B%B8%E5%85%B3%E6%80%A7%E6%9D%83%E9%87%8D%E3%80%82Step2%EF%BC%9A%E6%A0%B9%E6%8D%AE%E7%9B%B8%E5%85%B3%E6%80%A7%E6%9D%83%E9%87%8D%E5%AF%B9%E5%85%B6%E5%AE%83%E5%85%83%E7%B4%A0%E8%BF%9B%E8%A1%8C%E5%8A%A0%E6%9D%83%E6%B1%82%E5%92%8C%E3%80%82%E6%88%91%E4%B8%AA%E4%BA%BA%E7%AE%80%E5%8D%95%E7%9A%84%E7%90%86%E8%A7%A3%E6%98%AF%EF%BC%9A%E7%AC%AC%E4%B8%80%EF%BC%8C%E5%AE%83%E6%98%AF%E8%A1%A8%E7%A4%BA%E9%87%8D%E8%A6%81%E6%80%A7%E7%9A%84%E6%9D%83%E9%87%8D%EF%BC%8C%E7%AC%AC%E4%BA%8C%EF%BC%8C%E5%AE%83%E4%BC%9A%E9%9A%8F%E8%BE%93%E5%85%A5%E4%B8%8D%E5%90%8C%E8%80%8C%E4%B8%8D%E5%90%8C%EF%BC%8C%E6%89%80%E4%BB%A5%E5%AE%83%E5%BA%94%E8%AF%A5%E6%98%AF%E7%AE%97%E5%87%BA%E6%9D%A5%E7%9A%84%EF%BC%8C%E4%B8%8D%E6%98%AF%E6%88%91%E4%BB%AC%E8%87%AA%E5%B7%B1%E7%BB%99%E7%9A%84%EF%BC%8C%E4%B9%9F%E4%B8%8D%E6%98%AF%E5%AD%A6%E4%B9%A0%E5%88%B0%E7%9A%84%E3%80%82">^attention</a>模块中的全连接层参数也是来自backbone中的全连接层参数。</li>
</ul>
<h2>总结与感受</h2>
<p>DFANet: Deep Feature Aggregation for Real-Time Semantic Segmentation 这篇论文主要提出了一种特征聚合网络结构，结合了网络级的特征重用模块、阶段级的特征重用模块，达到了比较好的分割性能的情况下，同时有比较好的实时性。</p>
<p>这次总结大多已翻译为主了，大多是翻译了一些我认为可以拿出来讲一下的东西总结在了这里，也算是了解了一种新的特征聚合模块吧，不过还是对当前工作是有启发的，我们当前的特征是用的backbone最后的输出，没有结合低层次的特征，之前一直想使用结合多个层次的特征，而这篇论文正提供了一种方法。</p>
<p>暂且总结到这里，做ppt，准备下周的汇报了。</p>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【论文阅读笔记】EmbedMask: Embedding Coupling for One-stage Instance Segmentation]]></title>
            <link>https://bingqiangzhou.github.io/posts/paperreading-embedmask_embeddingcouplingforonestageinstancesegmentation/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/paperreading-embedmask_embeddingcouplingforonestageinstancesegmentation/</guid>
            <pubDate>Wed, 03 Jun 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[这两天在上课之余，看了这篇论文，同学极力推荐，说embedding的效果的非常好（看图一中c图的效果确实很好），而且他跑过了代码，效果确实是不错的，嘿嘿，我来了，总结一下吧，看学到了些什么吧。]]></description>
            <content:encoded><![CDATA[<p>这两天在上课之余，看了这篇论文，同学极力推荐，说embedding的效果的非常好（看图一中c图的效果确实很好），而且他跑过了代码，效果确实是不错的，嘿嘿，我来了，总结一下吧，看学到了些什么吧。</p>
<p><a href="https://arxiv.org/abs/1912.01954">论文简介</a>
<a href="https://arxiv.org/pdf/1912.01954">论文下载</a>
<a href="https://github.com/yinghdb/EmbedMask">源码地址</a></p>
<h2>效果图</h2>
<p>先看效果图吧（图一），图c的embedding效果图确实挺好的。
<img src="/assets/images/2020/20200603/result.png" alt="图一" /></p>
<p>下面来看论文主体内容吧。</p>
<h2>摘要与引言</h2>
<p>论文一开始就说结合了现在流行的两种实例分割方法：基于分割方法（先分割然后聚类）、基于建议方法（先检测然后预测mask），打到了Mask R-CNN的性能以及速度的同时，文章中的方法是one-stage方法，而Mask R-CNN是具有代表性的two-stage方法。</p>
<p>这里说一下我所理解的one-stage、two-stage以及end-to-end吧。<a href="one-stage%E3%80%81two-stage%E3%80%81multi-stage%E5%8F%AF%E4%BB%A5%E7%AE%80%E5%8D%95%E7%9A%84%E7%90%86%E8%A7%A3%E4%B8%BA%E6%98%AF%E5%90%A6%E5%88%86%E4%B8%BA%E4%BA%86%E5%A4%9A%E4%B8%AA%E5%88%86%E5%BC%80%E7%9A%84%E4%BB%BB%E5%8A%A1%E5%B9%B6%E9%9C%80%E8%A6%81%E5%A4%9A%E4%B8%AA%E6%AD%A5%E9%AA%A4%E6%9D%A5%E5%AE%9E%E7%8E%B0%E6%88%91%E4%BB%AC%E7%9A%84%E7%9B%AE%E6%A0%87%EF%BC%8C%E5%88%86%E4%B8%BA%E5%87%A0%E4%B8%AA%E6%AD%A5%E9%AA%A4%E5%B0%B1%E6%98%AF%E5%87%A0%E4%B8%AA-stage%EF%BC%8C%E8%80%8C%E6%AF%8F%E4%B8%AA%E6%AD%A5%E9%AA%A4%E9%83%BD%E4%BC%9A%E5%BD%B1%E5%93%8D%E6%80%A7%E8%83%BD%E4%BB%A5%E5%8F%8A%E6%95%88%E6%9E%9C%EF%BC%8C%E8%80%8C%E4%B8%94%E5%8D%95%E4%B8%AA%E6%AD%A5%E9%AA%A4%E7%9A%84%E6%95%88%E6%9E%9C%E5%A5%BD%E5%B9%B6%E4%B8%8D%E4%B8%80%E5%AE%9A%E6%95%B4%E5%90%88%E5%88%B0%E4%B8%80%E8%B5%B7%EF%BC%8C%E6%95%88%E6%9E%9C%E6%88%96%E8%80%85%E6%80%A7%E8%83%BD%E5%B0%B1%E5%A5%BD%EF%BC%8C%E6%89%80%E4%BB%A5%E4%B8%80%E8%88%ACone-stage%E6%96%B9%E6%B3%95%E4%BC%9A%E6%AF%94two-stage%E3%80%81multi-stage%E8%A6%81%E5%A5%BD%EF%BC%8C%E5%9B%A0%E4%B8%BA%E5%BD%B1%E5%93%8D%E6%80%A7%E8%83%BD%E5%92%8C%E6%95%88%E6%9E%9C%E7%9A%84%E5%9B%A0%E7%B4%A0%E6%9B%B4%E5%B0%91%E3%80%82%E8%80%8Cone-stage%E5%BC%BA%E8%B0%83%E6%B2%A1%E6%9C%89%E5%A4%9A%E4%B8%AA%E6%AD%A5%E9%AA%A4%E5%BD%B1%E5%93%8D%E6%80%A7%E8%83%BD%E4%BB%A5%E5%8F%8A%E6%95%88%E6%9E%9C%EF%BC%8Cend-to-end%E5%BC%BA%E8%B0%83%E4%B8%80%E7%AB%AF%EF%BC%88%E8%BE%93%E5%85%A5%EF%BC%89%E5%88%B0%E5%8F%A6%E4%B8%80%E7%AB%AF%EF%BC%88%E8%BE%93%E5%87%BA%EF%BC%89%EF%BC%8C%E6%A8%A1%E5%9E%8B%E7%9A%84%E8%BE%93%E5%87%BA%E5%B0%B1%E6%98%AF%E6%88%91%E4%BB%AC%E9%9C%80%E8%A6%81%E7%9A%84%E7%BB%93%E6%9E%9C%EF%BC%8C%E4%B8%8Eone-stage%E6%96%B9%E6%B3%95%E7%B1%BB%E4%BC%BC%E7%9A%84%E5%9C%B0%E6%96%B9%E5%B0%B1%E6%98%AF%E4%B8%80%E6%AD%A5%E5%88%B0%E4%BD%8D%E3%80%82">^one_stage__two_stage__end_to_end</a></p>
<p>论文还提到了Mask R-CNN存在的两个问题：</p>
<ul>
<li>“RoIPooling/RoIAlign”（相关概念可以看一下<a href="https://www.cnblogs.com/wangyong/p/8523814.html">这篇博客</a>）步骤的结果会导致特征的丢失和长宽比变形（the
distortion to the aspect ratios），“RoIPooling/RoIAlign”主要的作用是将不同大小的建议框对应的feature map映射到统一大小，方便后续的处理，由于建议框大小不是固定的，所以结果对应到建议框的区域的长宽比可能是变形的（比如建议框大小为大小为665x665，而经过“RoIPooling/RoIAlign”要固定成7*7大小的特征图，这个映射的比例是2.86:1，这个比例不成整数，固而扭曲了长宽比），从而会丢失一些细节。</li>
<li>原话：it still sustains
weakness in being complex to adjust too many parameters. 我不确定这里的参数是说的模型参数还是超参数，不过我更偏向于是超参数太多。</li>
</ul>
<p>然后论文中还提到了现在基于分割的one-stage方法，在聚类上有瓶颈，例如有很难确定簇的数量以及簇的中心，从而在性能上不如基于建议方法。</p>
<p>论文中方法结合基于分割方法与基于建议方法，使用两个embedding概念，像素上的嵌入（embedding for pixels），实例建议上的嵌入（embedding for instance
proposals，来表示框以及分类），在簇与簇之间使用灵活的间隙（margin）来训练。</p>
<h2>EmbedMask</h2>
<h3>模型</h3>
<p><img src="/assets/images/2020/20200603/model.png" alt="模型图" /></p>
<p>图中模型主体是FCOS模型<a href="%5B%E8%AE%BA%E6%96%87%E5%9C%B0%E5%9D%80%5D(https://arxiv.org/abs/1904.01355)%E3%80%81%5B%E6%BA%90%E7%A0%81%E5%9C%B0%E5%9D%80%5D(https://github.com/tianzhi0549/FCOS)">^fcos</a>（一个物体检测模型），蓝色的部分是论文中新加入的内容，加入了像素级别和建议框上的嵌入以及建议间隙（用来灵活的建议框中像素嵌入向量之间的间隙）。</p>
<h3>生成Mask</h3>
<p>生成Mask的方法是求像素级别的嵌入向量与建议框的嵌入向量（作为建议框中实例簇的中心）的欧式距离，距离小于margin时，则属于建议框分类的实例，反之不属于，公式如下：</p>
<p>$$
Mask_k(x_i) =
\begin{cases}
1,\ \ ||p_i-Q_k|| \leq \sigma &amp; \
0,\ \ ||p_i-Q_k|| &gt; \sigma
\end{cases}
$$</p>
<p>其中$Mask_k(x_i)$是像素点$x_i$属于实例$k$的对应的Mask值，$p_i$是像素点对应的像素级别的嵌入向量，$Q_k$是像素点所属建议框对应分类k的嵌入向量，$\sigma$则是margin。</p>
<p>论文中还提到这个margin是可以学习得到的（由Box Regression经过1x1的卷积后得到，可见<strong>模型图</strong>），而上面那种常用的方法到这里还并不能学习，而论文中提到参考了一篇论文中的方法，使用高斯函数，将这个margin融入到生成属于分类对象$k$的Mark的概率图中。公式如下：</p>
<p>$$\phi(x_i, S_k) = \phi(p_i, Q_k, \Sigma_k) = exp(-\frac{||p_i-Q_k||^2}{2{\Sigma_k}^2})$$</p>
<p>其中$\phi(x_i, S_k)$是指像素点$x_i$属于实例$S_k$的概率，而$\Sigma_k$是实例$k$对应的Margin。（由Box Regression经过1x1的卷积后得到。）</p>
<p>就这样，将Margin融入到了训练参数中，而在训练的时候$Q_k$与$\Sigma_k$与推理的时候的$Q_k$与$\Sigma_k$是有所区别的。训练的时候$Q_k$与$\Sigma_k$的计算公式如下：</p>
<p>$$Q_k = \frac{1}{N_k} \sum_{j\in M_k} q_j$$</p>
<p>$$\Sigma_k = \frac{1}{N_k} \sum_{j\in M_k} \sigma_j$$</p>
<p>其中$N_k$是在实例$S_k$中正采样的像素点的个数，$M_k$是实例$k$的对应的Mask。</p>
<p>正因为推理的时候与训练的时候的不同，在损失函数上对其进行了约束，<a href="#2-smooth%E6%8D%9F%E5%A4%B1">见smooth损失</a>。</p>
<h3>可学习的的间隙（Margin）</h3>
<p>论文中提到了两点使用手动设置固定的margin的问题：</p>
<ul>
<li>找到一个比较好的值相对较难</li>
<li>对于多尺度对象的训练不太友好，因为像素级别的嵌入向量在大的对象往往比较发散，而在小的对象却比较集中（即距离的跨度或者说范围与对象大小成正比，当对象越大时，像素嵌入向量的距离的范围跨度会大一些，而对象笔比较小的时候，范围跨度会小一些）。</li>
</ul>
<h3>损失函数</h3>
<h4>1. 用于优化Mask的损失函数</h4>
<p>使用二分类损失函数，公式如下，但在实践中，作者发现使用lovasz-hinge loss<a href="%5B%E8%AE%BA%E6%96%87%E5%9C%B0%E5%9D%80%5D(http://proceedings.mlr.press/v37/yub15.pdf)">^lovasz_hinge_loss</a>损失更好，这个损失暂时还么了解。</p>
<p>$$L_{mask}=\frac{1}{K}\sum_{k=1}^K\frac{1}{N_k}\sum_{p_i\in B_k}L(\phi(x_i,S_k), G(x_i,S_k))$$</p>
<p>其中$L(·)$是二分类（交叉熵）损失、$B_k$
是实例$k$的对应的推荐框内的像素点集，$\phi(x_i, S_k)$是指像素点$x_i$属于实例$S_k$的概率，$G(x_i,S_k)$是像素点$x_i$属于实例$S_k$的真实值，属于为1，不属于为0。</p>
<h4>2. Smooth损失</h4>
<p>由于在训练的时候的$Q_k$与$\Sigma_k$与推理的时候的$Q_k$与$\Sigma_k$是有所区别的，所以使用Smooth损失来约束它，让$q_j$与$Q_k$以及$\sigma$与$\Sigma_k$尽量接近。公式如下：</p>
<p>$$
\begin{aligned}
L_{smooth} &amp; = \frac{1}{K}\sum_{k=1}^K\frac{1}{N_k}\sum_{j\in M_k}||q_j - Q_k||^2 \
&amp; + \frac{1}{K}\sum_{k=1}^K\frac{1}{N_k}\sum_{j\in M_k}||\sigma_j - \Sigma_k||^2
\end{aligned}
$$</p>
<p>其中$M_k$是实例$k$的对应的Mask。</p>
<h4>3. 整体的损失</h4>
<p>整体的损失公式如下：</p>
<p>$$L=L_{cls}+L_{center}+L_{box}+\lambda_1L_{mask}+\lambda_2L_{smooth}$$</p>
<p>其中原始分类的损失$L_{cls}$、中心损失（center-ness loss）$L_{center}$、边框回归损失（box regression loss）$L_{box}$还是沿用FCOS那篇论文中的，而加了上面提到的两个损失$L_{mask}$、$L_{smooth}$。</p>
<h3>模型整个处理过程</h3>
<p>EmbedMask整个过程大概是：给定一张图片，通过修改后的物体检测网络FCOS，经过NMS（non-maximum suppression，非极大值抑制），得到最终建议的实例集$S$，对应这个实例集中的实例$S_k$有它的边框值、类别分数、推荐边框的嵌入向量$q_j$、推荐边框的间隙（Margin）$\sigma_j$，计算代表实例k的嵌入向量$Q_k$和可学习的阈值$\Sigma_k$，再通过计算$\phi(x_i, S_k)$（$\phi(p_i, Q_k, \Sigma_k)$）得到$x_i$属于$S_k$的概率，设置阈值为0.5，生成最终Mask。</p>
<h2>总结与感受</h2>
<p>首先这个网络主体是使用的物体检测网络FCOS，加入了像素上的嵌入，实例建议上的嵌入，对这些我的感触都不大，最让我感觉学到了的是可学习的间隙（margin），我们当前的课题，是使用的固定的间隙，也确实遇到多尺度对象的训练效果不好的问题，我想之后我们得使用类似论文中的方法改进这点，算是意外收获了，还有就是结合使用中间层的特征，我们暂时还没有用起来，一直有提说要用上，看这篇论文也是这样用的。</p>
<p>好了，先记录到这了。</p>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【碎碎念】我又回来了]]></title>
            <link>https://bingqiangzhou.github.io/posts/narration-comebacktoblog/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/narration-comebacktoblog/</guid>
            <pubDate>Tue, 02 Jun 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[好久没有更新博客了，这两天和朋友聊到写博客，然后良心发现，博客还是要写起来，总结一下，之后回顾起来，还是好一点，而且下学期好像，好像要检查看的论文了，哈哈。]]></description>
            <content:encoded><![CDATA[<p>好久没有更新博客了，这两天和朋友聊到写博客，然后良心发现，博客还是要写起来，总结一下，之后回顾起来，还是好一点，而且下学期好像，好像要检查看的论文了，哈哈。</p>
<p>今天整理了一下博客，大概看了一下<a href="https://github.com/kitian616/jekyll-TeXt-theme">jekyll-TeXt-theme</a>的源码，</p>
<ol>
<li>把TOC(目录)从右边移到了，加大了一下字体。</li>
<li>在查看效果的时候，惊奇的发现，TOC没有内容的时候，依旧有会占据这块地方，然后又改了一下代码，在没有内容的时候隐藏一下TOC。</li>
<li>看<a href="https://tianqi.name/jekyll-TeXt-theme/archive.html">jekyll-TeXt-theme文档</a>的时候，又发现了一些好玩的东西，jekyll 脚注(Footnote)<a href="%E8%BF%99%E5%B0%B1%E6%98%AF%E8%84%9A%E6%B3%A8%EF%BC%8C%5B%E6%96%87%E6%A1%A3%E9%93%BE%E6%8E%A5%5D(https://tianqi.name/jekyll-TeXt-theme/post/2016/05/04/footnote.html)">^Footnote</a>、还有定义(Definition)<a href="%5B%E5%AE%9A%E4%B9%89%E7%9A%84%E6%96%87%E6%A1%A3%E9%93%BE%E6%8E%A5%5D(https://tianqi.name/jekyll-TeXt-theme/post/2016/05/05/definition.html)%EF%BC%8C%E5%AE%9A%E4%B9%89%E6%98%AF%E4%BB%80%E4%B9%88%E5%91%A2%EF%BC%9F%E4%B9%9F%E5%B0%B1%E6%98%AF%E4%B8%80%E7%A7%8Dmarkdown%E7%9A%84%E5%B1%95%E7%A4%BA%E5%BD%A2%E5%BC%8F%E8%80%8C%E5%B7%B2%E3%80%82">^Definition</a>，画图[^ChartAndMermaid]等等。</li>
<li>设置了一下图片居中，将如下css代码加入到<code>_article-content.scss</code>文件的<code>img:not(.emoji)</code>标签中，同时注释掉<code>vertical-align</code>项，<a href="https://www.smslit.top/2015/10/15/PostImgCenter-Jekyll/">参考链接</a>。<pre><code> clear: both; 
 display: block; 
 margin:auto; 
 // vertical-align: middle; // 注释掉
</code></pre>
</li>
<li>最后还把博客的标题命名，文件命名统一了一下。</li>
</ol>
<p>好了，最后点下题，我又回来了。</p>
<p>明天算法与数据结构课、文献检索与论文写作结课结课，要考试、要汇报，溜了溜了。</p>
<p>[^ChartAndMermaid]: <a href="https://tianqi.name/jekyll-TeXt-theme/post/2017/05/05/chart.html">图表Chart的文档链接</a> 、<a href="https://tianqi.name/jekyll-TeXt-theme/post/2017/06/06/mermaid.html">流程图、时序图等等Mermaid图的文档链接</a>，这里的<a href="https://mermaid-js.github.io/mermaid/">Mermaid</a>图，其实是一个JavaScript库的名字，用来画图的库，翻译过来为美人鱼。</p>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【学习笔记】pipenv虚拟环境]]></title>
            <link>https://bingqiangzhou.github.io/posts/dailysummary-aboutpipenv/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/dailysummary-aboutpipenv/</guid>
            <pubDate>Mon, 28 Oct 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[粗略的总结一下使用pipenv创建虚拟环境，主要可以看上面那博客。]]></description>
            <content:encoded><![CDATA[<p><a href="https://github.com/pypa/pipenv">pipenv github</a></p>
<p><a href="https://www.cnblogs.com/zingp/p/8525138.html">Pipenv——最好用的python虚拟环境和包管理工具</a></p>
<p>粗略的总结一下使用pipenv创建虚拟环境，主要可以看上面那博客。</p>
<h2>Installation</h2>
<p>If you're on MacOS, you can install Pipenv easily with Homebrew:</p>
<pre><code>$ brew install pipenv
</code></pre>
<p>Or, if you're using Debian Buster+:</p>
<pre><code>$ sudo apt install pipenv
</code></pre>
<p>Or, if you're using Fedora 28:</p>
<pre><code>$ sudo dnf install pipenv
</code></pre>
<p>Or, if you're using FreeBSD:</p>
<pre><code># pkg install py36-pipenv
</code></pre>
<p>Otherwise, refer to the <a href="https://pipenv.kennethreitz.org/en/latest/#install-pipenv-today">documentation</a> for instructions.</p>
<h2>主要使用到的命令</h2>
<p>创建虚拟环境过程</p>
<pre><code>$ pipenv install
</code></pre>
<p>使用特定的python环境
$ pipenv —python 3.7</p>
<p>进入pipenv环境</p>
<pre><code>$ pipenv shell
</code></pre>
<p>pipenv下安装包</p>
<pre><code>$ pipenv install module-name
</code></pre>
<p>example：pipenv install tensorflow-gpu</p>
<p>pipenv环境下执行python</p>
<pre><code>$ pipenv run python source.py
</code></pre>
<p>pipenv查看已安装的包和包依赖</p>
<pre><code>$ pipenv graph
</code></pre>
<p>退出pipenv环境</p>
<pre><code>$ exit 或者 ctrl+d
</code></pre>
<h2>pipenv相关命令</h2>
<h2>☤ Usage</h2>
<pre><code>$ pipenv
Usage: pipenv [OPTIONS] COMMAND [ARGS]...

Options:
  --where          Output project home information.
  --venv           Output virtualenv information.
  --py             Output Python interpreter information.
  --envs           Output Environment Variable options.
  --rm             Remove the virtualenv.
  --bare           Minimal output.
  --completion     Output completion (to be eval'd).
  --man            Display manpage.
  --three / --two  Use Python 3/2 when creating virtualenv.
  --python TEXT    Specify which version of Python virtualenv should use.
  --site-packages  Enable site-packages for the virtualenv.
  --version        Show the version and exit.
  -h, --help       Show this message and exit.


Usage Examples:
   Create a new project using Python 3.7, specifically:
   $ pipenv --python 3.7

   Remove project virtualenv (inferred from current directory):
   $ pipenv --rm

   Install all dependencies for a project (including dev):
   $ pipenv install --dev

   Create a lockfile containing pre-releases:
   $ pipenv lock --pre

   Show a graph of your installed dependencies:
   $ pipenv graph

   Check your installed dependencies for security vulnerabilities:
   $ pipenv check

   Install a local setup.py into your virtual environment/Pipfile:
   $ pipenv install -e .

   Use a lower-level pip command:
   $ pipenv run pip freeze

Commands:
  check      Checks for security vulnerabilities and against PEP 508 markers
             provided in Pipfile.
  clean      Uninstalls all packages not specified in Pipfile.lock.
  graph      Displays currently–installed dependency graph information.
  install    Installs provided packages and adds them to Pipfile, or (if no
             packages are given), installs all packages from Pipfile.
  lock       Generates Pipfile.lock.
  open       View a given module in your editor.
  run        Spawns a command installed into the virtualenv.
  shell      Spawns a shell within the virtualenv.
  sync       Installs all packages specified in Pipfile.lock.
  uninstall  Un-installs a provided package and removes it from Pipfile.
</code></pre>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【折腾记录】Update BIOS]]></title>
            <link>https://bingqiangzhou.github.io/posts/toolsandresources-updatebios/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/toolsandresources-updatebios/</guid>
            <pubDate>Fri, 25 Oct 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[今天更新了bios系统，最底层的bios管理的功能太少了，甚至都没有官网上说的固件管理，最后各种尝试了很久，这里记录一下。]]></description>
            <content:encoded><![CDATA[<p>今天更新了bios系统，最底层的bios管理的功能太少了，甚至都没有官网上说的固件管理，最后各种尝试了很久，这里记录一下。</p>
<p>根据这篇文章来， <a href="https://support.hp.com/cn-zh/document/c04483563">HP 筆記型電腦 -- 更新 BIOS</a></p>
<p>其中：第三步：打開硬件診斷 UEFI 功能表
选用【通過 Windows 8 訪問 UEFI】</p>
<p>最后更新bios这个步骤时，按【f9 设备启动方式】选择【从文件中启动】，在hpbiosupdate文件夹中找到HpBiosUpdate.efi，点击启动，再从按文章中的步骤4的第4步开始进行下一步。</p>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【工具分享】jupyter lab]]></title>
            <link>https://bingqiangzhou.github.io/posts/toolsandresources-aboutjupyterlab/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/toolsandresources-aboutjupyterlab/</guid>
            <pubDate>Mon, 23 Sep 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[jupyter lab用了一段时间了，感觉特别棒，Markdown渲染可以直接渲染Mathjax公式，还可以下载各种插件等等，总之，我觉得很好用，这里mark一下。]]></description>
            <content:encoded><![CDATA[<p>jupyter lab用了一段时间了，感觉特别棒，Markdown渲染可以直接渲染Mathjax公式，还可以下载各种插件等等，总之，我觉得很好用，这里mark一下。</p>
<p><a href="https://zhuanlan.zhihu.com/p/67959768">利器 JupyterLab 数据分析必备IDE完全指南 - 游薪渝的文章 - 知乎</a> 包括如何安装、有哪些特性、插件的推荐等等，其中<a href="https://github.com/altair-viz/jupyterlab_voyager/issues/77">Jupyterlab_voyager还不支持jupyter lab 1.13以上的版本</a></p>
<p><a href="http://blog.rexking6.top/2018/12/20/JupyterLab%E6%8F%92%E4%BB%B6/">JupyterLab插件</a> 推荐了一些插件。</p>
<p><a href="https://www.zhihu.com/question/59392251">如何优雅地使用 Jupyter？ - 知乎</a></p>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【折腾记录】PDF相关]]></title>
            <link>https://bingqiangzhou.github.io/posts/dailyjungle-aboutpdf/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/dailyjungle-aboutpdf/</guid>
            <pubDate>Sun, 22 Sep 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[故事开始于昨天早上，接收到一条短信，告诉我有一个小项目可以接，接着我联系了一天，都没有确定是要做什么，没有明确的需求，大概知道了会和PDF相关信息的检索有关，随后我就开始自己先琢磨一下了，今天又快过完了，还是没得需求，十月底要弄出来，真的想说，“赚钱不容易呀”。]]></description>
            <content:encoded><![CDATA[<p>故事开始于昨天早上，接收到一条短信，告诉我有一个小项目可以接，接着我联系了一天，都没有确定是要做什么，没有明确的需求，大概知道了会和PDF相关信息的检索有关，随后我就开始自己先琢磨一下了，今天又快过完了，还是没得需求，十月底要弄出来，真的想说，“赚钱不容易呀”。</p>
<h2>pdfboxNet库</h2>
<p>PDFBox是Java实现的PDF文档协作类库，提供PDF文档的创建、处理以及文档内容提取功能，也包含了一些命令行实用工具，pdfboxNet也就是pdfbox的.Net实现了。</p>
<p>主要特性包括：从PDF提取文本、合并PDF文档、PDF文档加密与解密、与Lucene搜索引擎的集成、填充PDF/XFDF表单数据、从文本文件创建PDF文档、从PDF页面创建图片、打印PDF文档等等。</p>
<p>大概参考了一下这篇文章，<a href="https://blog.csdn.net/LCL_data/article/details/6043898">C#读取PDF ——PDFBox使用</a>，其中不需要和文章中说的一样取下载库文件，在VS集成开发环境中，使用NnGet直接搜索安装就好了。</p>
<p>文章有点古老了，技术我也不知道是不是被淘汰了，其实我只是想验证我的想法，打开PDF提取相关信息，这里就以及验证了可以提取信息，虽然后来通过进一步的交流发现，那边又整理好的EXCEL表格，可能是检索EXCEL吧，还是步完全清楚需求，罢了罢了，继续玩一玩PDF相关的东西吧。</p>
<h2>PDF winform控件</h2>
<p>一想，玩一下PDF相关的Winform控件吧，随后就开始找了。</p>
<h3>Adobe提供的ActiveX控件</h3>
<p><a href="https://blog.csdn.net/sl1990129/article/details/78094602">Adobe提供的ActiveX控件</a>，这里没有具体尝试，需要在安装adobe acrobat。</p>
<h4>相关链接</h4>
<p><a href="https://blog.csdn.net/zt15732625878/article/details/79248523">【C# 基础】— 解决 "winForm 引用 Adobe PDF Reader控件不显示pdf 文件" 问题</a>
<a href="https://bbs.csdn.net/topics/392179589">CSDN问题：C# winform Acrobat Reader 显示pdf如何获取当前页数,提到了Free Spire.PDFViewer、devexpress pdf的控件</a></p>
<h3>DevExpress的PDF控件</h3>
<p>听说比较好用，有点跃跃欲试的感觉，但是一个license需要18k+，果断放弃了，不过网上有破解版，之后有时间再尝试一下。</p>
<h4>相关链接</h4>
<p><a href="https://docs.devexpress.com/WindowsForms/15216/Controls-and-Libraries/PDF-Viewer">PDF Viewer文档</a></p>
<p><a href="https://www.cnblogs.com/wuhuacong/p/4175266.html">基于DevExpress实现对PDF、Word、Excel文档的预览及操作处理</a></p>
<p><a href="https://wenku.baidu.com/view/c22bb9f127d3240c8447eff6.html">DevExpress控件使用详细说明</a></p>
<p><a href="https://blog.csdn.net/weixin_33835103/article/details/85730069">DevExpress 编译成功的 dll</a></p>
<p><a href="https://www.dxper.net/thread-42367-1-1.html">DevExpress 18 源代码编译方法</a></p>
<p><a href="https://my.oschina.net/u/1163318/blog/362638">2014年DevExpress使用教程合集</a></p>
<p><a href="https://github.com/ProximaMonkey/DevExpressSources151">DevExpressSources151</a></p>
<p><a href="https://blog.csdn.net/qq_36628003/article/details/82684679">devexpress 使用安装、破解注册和汉化包进行汉化的步骤</a></p>
<p><a href="https://www.dxper.net/thread-40506-1-1.html">DevExpress安装文件、源码、注册破解下载</a></p>
<h3>没有尝试的收费的PDF控件</h3>
<p><a href="https://www.grapecity.com.cn/developer/componentone-winform/controls/pdf">ComponentOne-PDF for WinForm</a></p>
<p><a href="https://www.e-iceblue.com/Introduce/free-pdf-viewer-net.html#.XYefwCrithE">Free Spire.PDFViewer for .NET</a> 收费，但是也有免费版，免费版有功能上的限制，比如只能显示10页。</p>
<h3>开源的PDF控件</h3>
<p><a href="https://www.codeproject.com/Articles/37458/PDF-Viewer-Control-Without-Acrobat-Reader-Installe">PDF Viewer Control Without Acrobat Reader Installed</a>
很老的开源项目了，09年的。</p>
<p><a href="http://www.o2sol.com/pdfview4net/download.htm">The PDFView4NET toolkit</a> 免费的，并且持续更新，有时间再尝试一下。<a href="https://www.cnblogs.com/onestow/p/5977807.html">相关链接</a></p>
<h3>PdfiumViewer</h3>
<p>一个免费的.NET的PDF控件库。<a href="https://github.com/pvginkel/PdfiumViewer">PdfiumViewer GitHub库</a>
<a href="https://github.com/pvginkel/PdfiumBuild">PdfiumBuild GitHub库</a></p>
<p>PdfiumBuild库的下载，对应在NuGet下载PdfiumViewer.Native.x86_64.v8-xfa库和PdfiumViewer.Native.x86.v8-xfa库就好了，而PdfiumViewer库在NuGet对应的搜索下载就好了。</p>
<p>其中我下载了PdfiumViewer Github上的源码，跑了PdfiumViewer的Demo项目，除了页面老了点，其他是极好的。</p>
<h2>写在最后</h2>
<p>PDF相关的库就大概接触了一下上面这些了，不多说了，PDF相关的东西，以后有用得到再翻一翻看一看吧。</p>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【论文阅读笔记】FEELVOS: Fast End-to-End Embedding Learning for Video Object Segmentation]]></title>
            <link>https://bingqiangzhou.github.io/posts/paperreading-fastendtoendembeddinglearningforvideoobjectsegmentation/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/paperreading-fastendtoendembeddinglearningforvideoobjectsegmentation/</guid>
            <pubDate>Fri, 20 Sep 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[视频对象分割快速端对端嵌入学习方法（FEELVOS: Fast End-to-End Embedding Learning for Video Object Segmentation）]]></description>
            <content:encoded><![CDATA[<p>视频对象分割快速端对端嵌入学习方法（FEELVOS: Fast End-to-End Embedding Learning for Video Object Segmentation）</p>
<p><a href="https://arxiv.org/abs/1902.09513">论文简介</a>
<a href="https://arxiv.org/pdf/1902.09513v2.pdf">论文下载</a></p>
<h2>概述</h2>
<p><img src="/assets/images/2019/20190920/figure3.png" alt="模型图" /></p>
<p>根据上图，大概描述一下整个模型，首先给定视频的一帧，通过在DeepLabV3+架构，去掉最后一层，得到骨干特征（backbone features）并再头部（top of that）加上一个嵌入层（embedding layer）的网络，得到每一个像素点的嵌入向量（对应的得到整个嵌入向量空间），随后对应于每一个对象，通过当前嵌入空间和前一帧图片的嵌入空间计算得到全局匹配距离图（global matching distance map），通过当前嵌入空间和前一帧图片的嵌入空间计算得到本地匹配距离图（local matching distance map），随后将骨干特征（backbone features）（共享，复制N份，N为对象的个数）和对应于一个对象的全局匹配距离图、本地匹配距离图、前一帧的预测结果组合到一起，形成一个维度为[w/4, h/4, 259(256+3)]的张量，再将这个张量作为分割头网络（segmentation head）的输入，得到每一个点对于每个对象的logits值（校正值），将其堆叠在一起，通过softmax函数得到一个张量[w/4, h/4, N]，即为处理的结果，对应于每个点属于每个对象的概率。</p>
<p>下面分别较为详细一点的总结一下各个部分内容。</p>
<h2>语义嵌入（semantic embedding）</h2>
<p>对于每一个像素点，都提取一个实体嵌入向量在学习到的嵌入空间中，这里定义一下两个像素点的距离。</p>
<p>$$d(p,q)=1-\frac {2}{1+exp({\vert \vert e_p -e_q \vert \vert }^2)}$$</p>
<p>当两点距离比较远的时候，距离趋近于1，当两点距离比较近的时候距离趋近于0，关于点的距离的计算都是用像素点对应的嵌入向量。</p>
<h2>全局匹配（global matching）</h2>
<p>定义全局匹配公式为</p>
<p>$$ G_{t, o}(p) = min_{q \in P_{1, o}} d(p, q)$$</p>
<p>这里的t代表第几帧，o为全体对象的一个，$G_{t, o}(p)$为第t帧中像素点p与第一帧对象o的最小距离，而$P_{1, o}$对应于第一帧所属对象o的像素点集合。</p>
<p>假设第一帧对象o中有n个像素点，则这里的$G_{t, o}(p)$是第t帧中的一个像素点p到这n个像素点中的最短距离。</p>
<h2>本地前一帧匹配（local previous frame matching）</h2>
<p>$$ \hat G_{t, o}(p) = \begin{cases} min_{q \in P_{t-1, o}} d(p, q), &amp;{ if\ P_{t-1, o}\neq \emptyset}\ 1,&amp;{otherwise}\end{cases} $$</p>
<p>这里与全局匹配类似，计算上一帧与当前帧的距离，但是，当这一帧不存在对象o的时候设置距离为1。</p>
<p>显而易见的是，这里可以优化，在我们计算第一帧与当前帧的距离的时候，两帧可能变化比较大，不得不计算所有的点的距离，但是在计算当前帧的时候，其实两帧之间的变化是比较小的，我们可以只计算部分点，论文中提出了一种方法优化，减少计算量。</p>
<p><img src="/assets/images/2019/20190920/myfigure.png" alt="窗口大小示意图" /></p>
<p>给定一个窗口大小k（作者设置k=15），以p点为中心，向上下左右延伸k个像素点，记这个像素点集合为$N(p)$，集合中存在有$(2\cdot k +1)^2$个元素，让集合$N(p)$与$P_{1, o}$去交集，再计算距离，没有交集的情况，距离直接设置1，这里的原理是只有p点周围的像素点中存在属于对象的像素点的时候，才进行计算，周围没有属于对象的像素点的时候，看作没有变化不进行计算。</p>
<p>$$ L_{t, o}(p) = \begin{cases} min_{q \in P_{t-1, o}^p} d(p, q), &amp;{ if\ P_{t-1, o}^p\neq \emptyset}\ 1,&amp;{otherwise}\end{cases} $$</p>
<p>其中$P_{t-1, o}^p = P_{t-1, o} \cap N(p)$</p>
<h2>前一帧的预测结果（previous frame predications）</h2>
<p>对应对象的预测结果图为二值图，属于该对象的像素点对应值为1，不属于该对象的像素点对应值为0，但是用在下一帧的预测时的图是只经过softmax的可能性图（soft probability map）。</p>
<p>除了前一帧预测的可能性图，还有全局匹配距离图和本地前一帧距离图，它们与当前帧的嵌入空间堆积到一起作为动态分割头网络的输入，下面大概说一下动态分割头网络。</p>
<h2>动态分割头（dynamic segmentation head）</h2>
<p>可以看到上面的模型图，动态分割头网络只前向运行一次，各个对象之间共享权重，最后对应每个对象生成一个一维的校正值（logits）特征图，将它们堆叠到一起，再对象层面的维度运用softmax函数，得到像素点属于各个对象的可能性，最后损失函数使用交叉熵损失，最后再大概说一下大概的训练过程。</p>
<h2>训练过程（training procedure）</h2>
<p>对于训练的每一步，首先随机选取训练集中的一个mini-batch的视频，再每一个视频中随机选取3帧，一帧做“第一帧”，另外两个临近的帧，分别作为“前一帧”和当前帧，再经过上面所说的一系列的运算并进行训练（loss为softmax情况下的交叉熵损失）。</p>
<h2>推论或结论（inference）</h2>
<p>FEELVOS是简单的、直接的（straightforward），对于每一帧只需要一次前向传播，这里作者又将整个测试过程大概说了一遍：给定一个测试视频和第一帧的真实值（ground truth），首先提取第一帧的嵌入向量，随后，一帧一帧的计算当前帧的嵌入向量，并于第一帧计算全局匹配（global matching）和前一帧计算本地匹配（local matching），结合前一帧的预测结果，对每一个对象运行动态分割头（dynamic segmentation head）网络，再对每个像素执行softmax（原文是argmax）生成最终的分割结果。</p>
<h2>实现细节（implementation details）</h2>
<p>提取特征的网络是DeepLabv3+架构（基于Xception-65架构），运用逐深度可分离卷积（depthwise separable convolutions），批标准化（batch normalization），深黑的空间金字塔池化（atrous spatial pyramid pooling），和一个步长为4的生成特征的解码模块。</p>
<p>这里作者的方法加了一个嵌入层（embedding layer）组合在逐深度可分离卷积层（depthwise separable convolutions），设置提取的嵌入向量维度为100。</p>
<p>在动态分割头，作者发现一个大的接收域（large receptive field）是很重要的（这里的接收域还没有完全理解），作者使用了4层逐深度可分离卷积层（256维）和一层核大小为7x7的逐深度的卷积层，最后加一层1x1的卷积来提取一维的校正值（logits）。</p>
<p>文中还提到了在全局匹配中计算所有成对的像素点的距离是很昂贵的，而且并不是所有的像素都又必要考虑的，所以提出了一种优化方法，在训练中，参考点集取随机子样本（randomly subsample），从第一帧变成每个对象最多包含1024个像素点，即本身要计算整张图片，变成每个对象最多计算1024点，极大的减小了计算量，并且作者指出对结果几乎没有影响。</p>
<p>对于本地匹配，窗口大小k=15，也就是对于取以点p为中心的$(2*15+1)^2$，窗口大小在这一小节提到<a href="#%E6%9C%AC%E5%9C%B0%E5%89%8D%E4%B8%80%E5%B8%A7%E5%8C%B9%E9%85%8Dlocal-previous-frame-matching">本地前一帧匹配</a>。</p>
<p>在开始训练时，使用DeepLabv3+在ImageNet和COCO训练好的参数。</p>
<p>训练数据，作者使用的是DAVIS 2017数据集中的60个训练视频。</p>
<p>作者还运用了一个白举交叉熵损失，对于计算损失，只考虑最难的15%的像素点。</p>
<p>在优化的时候，使用带有动量的梯度下降（$momentum = 0.9$），学习率$\alpha=0.0007$，每一捆的大小（$batch\  size = 3\ videos$）,训练200000（20万）步。</p>
<p>最后还运用了标准数据增强（standard data augmentation），例如翻转和缩放、随机剪切输入图像为465x465像素。</p>
<p>其他实验细节这里不再细说，需要实现的时候再回顾论文。</p>
<h2>总结与感受</h2>
<p>看了这篇论文，除了对模型的了解，还学到了两种降低计算成本的思路（对全局匹配取随机子样本进行计算，还有对于本地匹配取窗口），挺好的，虽然没有取跑一遍他的实验，但是还是学到了很多思想了，还是希望自己有一个好的改进的方法的时候，然后再好好做实验吧，加油！</p>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【论文阅读笔记】Semantic Instance Segmentation via Deep Metric Learning（二）]]></title>
            <link>https://bingqiangzhou.github.io/posts/2019-09-18-paperreading-semanticinstancesegmentationviadeepmetriclearning/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/2019-09-18-paperreading-semanticinstancesegmentationviadeepmetriclearning/</guid>
            <pubDate>Wed, 18 Sep 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[接过上一次的话题，这一次，结合自己的理解，记录一下论文中提出的方法，包括嵌入模型（embedding model）、创建遮罩（creating masks）、分类和种子度模型（classification and seediness model）。]]></description>
            <content:encoded><![CDATA[<p>接过上一次的话题，这一次，结合自己的理解，记录一下论文中提出的方法，包括嵌入模型（embedding model）、创建遮罩（creating masks）、分类和种子度模型（classification and seediness model）。</p>
<h2>嵌入模型（embedding model）</h2>
<p>学习一个嵌入模型（维度为[h, w, d]的张量），使用logistics损失来训练它。</p>
<h3>相似度</h3>
<p>$$\sigma(p,q)=\frac{2}{1+exp(||e_p-e_q||)}$$</p>
<p>其中，$\sigma(p,q)$ 表示点p与点q的相似度，当两者在嵌入空间（$e_p$ 与 $e_q$）比较近时，$\sigma(p,q)=\frac{2}{1+e^0}=1$，两者比较远时，$\sigma(p,q)=\frac{2}{1+e^\infty}=0$。</p>
<h3>损失函数</h3>
<p>$$L_e=-\frac{1}{|S|}\sum_{ {p,q}\in S}w_{pq}[1_{{y_p=y_q}}log(\sigma(p,q))+1_{{y_p\neq y_q}}log(1-\sigma(p,q))]$$</p>
<p>其中$S$ 是"种子点"集合，这里的$\vert S \vert$表示集合中种子点的个数，$w_{pq}$是点p与点q相似度损失的权重，$w_{pq}$与点p和点q所属的实例大小成反比，添加这个权重，从而使得损失函数不会偏向于更大的样本，$$1_{{y_p=y_q}}$$表示当$$y_p=y_q$$成立的时候式子取1，不成立则取0，$$1_{{y_p\neq y_q}}$$同理。</p>
<p>论文中还提到了正则化权重，$\sum_{p,q}w_pq=1$ 。</p>
<p>在训练开始时，在每一个实例中随机取样，选取K个点来作为种子点，假设有N个实例，则种子点集合元素个数$\vert S \vert=N \cdot K$，在这里只计算在$\vert S \vert$中的点与点之间的相似度损失，选取种子点机制在<a href="#%E5%88%86%E7%B1%BB%E5%92%8C%E7%A7%8D%E5%AD%90%E5%BA%A6%E6%A8%A1%E5%9E%8Bclassification-and-seediness-model">分类和选种子点模型</a>说明，以上说的点都是用嵌入空间对于点的嵌入向量来计算的。</p>
<h2>创建遮罩（creating masks）</h2>
<h3>创建遮罩机制</h3>
<p>对于种子点p，比较所有其他点q，当p与q的相似度大于一个阈值的时候，则记q点与p点属于同一个遮罩，反之，则不属于同一个遮罩，一般记为背景，公式如下：</p>
<p>$$m(p,\tau)={ q:\sigma(p,q) \geq \tau }$$</p>
<p>对于阈值$\tau$，论文中提到，作者实验中使用阈值的数值$$\tau \in {{ 0.25,0.5,0.75}}$$。</p>
<h3>向量化距离公式</h3>
<p>在计算相似度的公式中，计算两点的距离$\vert\vert e_p - e_q \vert\vert_2^2$，将其向量化，$A^2+B^2-2A \bigodot B$，其中$A$是嵌入空间张量（维度为[h, w, d]，h为高度，w为宽度，d为嵌入维度），$B$是K个种子点的嵌入向量（维度为[k, d]），这里再计算相似度，再阈值化（thresholding）处理矩阵就好了。</p>
<h3>选取种子点机制</h3>
<p>当p点与种子点q的相似度大于一个阈值时，则将这个点p设置为与点q同类，反之属于背景，由此可知每一个种子点都会产生一个二值遮罩，那么我们如何选种子点呢？首先选择每个类中一个“种子度”最高的点，这个点是在各个阈值$\tau$（各个分类模型下）下取属于某个类别的最大可能性中的最大值对应的点，结合一个“正则项”选点。</p>
<p>选择第t个种子点</p>
<p>$$p_t=arg\ max_{p \notin p_{1:t-1}}[log(S_p)+\alpha log(D(p,p_{1:t-1}))]$$</p>
<p>其中，</p>
<p>$$ S_p = max_{\tau \in \mathit{T}}\ max_{c=1}^C \mathit{C}_{pc}^{\tau} $$</p>
<p>$$ D(p,p_{1:t-1}) = min_{q \in p_{1:t-1}} ||e_p - e_q ||_2^2 $$</p>
<p>$S_p$表示p点的种子度（seediness），$D(p,p_{1:t-1})$表示p点与已选择的点中的最小距离，在论文中提到处理不同大小的对象分别对每一个阈值$\tau$训练一个分类模型（原文：To handle objects of different sizes, we train a different classification model for each value of $\tau$;），在实验中作者使用阈值的范围为$$\mathit{T}={{ 0.25, 0.5, 0.75, 0.9 }}$$，即$\tau \in {{ 0.25, 0.5, 0.75, 0.9}}$对应每个阈值生成一个模型，$S_p$为在每一个阈值$\tau $的分类模型下，取属于c类的可能性的最大值，随后,再取对应各个阈值下的最大可能性的最大值，$C_{pc}^{\tau}$是在阈值为$\tau$产生的模型下,点p属于c类的可能性。$arg \ max$表示后面的式子取最大值时的变量值，这里是取最大值时对应的点。</p>
<p>$\alpha \ log(D(p,p_{1:t-1}))$ 可以简单理解为正则项，作用是让新选的p点与已经选择了的t-1个种子点的距离大一些，这里体现为惩罚新选p点与已经选择的t-1个点的最小距离。</p>
<p>简单总结一下，选种子点机制，平衡p点属于c类的可能性的最大可能性和p点远离已选点，选择取最大值时的p点。</p>
<h2>分类和种子度模型（classification and seediness model）</h2>
<h3>处理的机制</h3>
<p>创建遮罩时，通过$m(p,\tau)$，当q点与p点的相似度大于阈值时，将p点的真实值（ground truth）复制给q点，反之将背景的标签设置给q点，整个图完成这个工作之后，将分配的标签转为one-hot形式，再对每个实例的K个种子点，使用softmax交叉熵损失来训练模型。</p>
<p>$$ L_{cls} = -\frac{1}{|S|}\sum_{p \in S}\sum_{c=0}^{C}y_{pc}\ log \  \mathit{C}_{pc}$$</p>
<p>当我们已经选择了最好的种子点集之后，我们就可以算出对应最好的阈值和在选择最好的阈值的情况下相关的置信度。</p>
<p>最好的阈值以及对应的标签c</p>
<p>$$(\tau, c_p) = arg \ max_{\tau \in \mathit{T},c \in {1:\mathit{C}}} C_{pc}^{\mathit{T}}$$</p>
<p>置信度</p>
<p>$$ S_p = \mathit{C}_{p,c_p}^{\mathit{T}_p} $$</p>
<h3>共享全图片卷积特征</h3>
<p>这里主要告诉我们特征提取器基于DeepLab v2模型（基于resnet-101），使用COCO进行预训练，损失函数如下：</p>
<p>$$L = L_c + \lambda L_{cls}$$</p>
<p>这里超参数$\lambda$的设置在这里不详细讲了，跑代码的时候自己再调一下，论文中说到作者最大使用0.2。</p>
<p>这部分请看论文原文吧。<a href="https://arxiv.org/pdf/1703.10277.pdf">基于深度度量学习的语义实例分割（Semantic Instance Segmentation via Deep Metric Learning）</a></p>
<h2>总结与感受</h2>
<p>我个人认为论文给我们最重要的方法是自动选取种子点的方法，这样就可以实现自动化分割实体，在应用上，比如重上色，其实选点的过程下可以让用户交互，用户都只要交互一次，在训练上的话，难度也比较小，当然如何我们还是让用户去交互的话，应用面就被限制了，再回到用户交互的方式的话那就是一种退步。
回到方法，感觉还是有点难度的，可能还没有完全搞懂，可能是没有跑代码的原因吧，不过现在大概知道其中的思想了，以后有需要再好好回顾论文吧。</p>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【工具分享】ai studio、colab、kaggle免费的算力]]></title>
            <link>https://bingqiangzhou.github.io/posts/toolsandresources-usefreegputraining/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/toolsandresources-usefreegputraining/</guid>
            <pubDate>Tue, 17 Sep 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[薅羊毛，在Baidu AI Studio训练模型，框架不限于PaddlePaddle，TensorFlow，Pytorch都可以。]]></description>
            <content:encoded><![CDATA[<p>薅羊毛，在Baidu AI Studio训练模型，框架不限于PaddlePaddle，TensorFlow，Pytorch都可以。</p>
<p>之前在暑假的时候，可以直接在Notebook中下载包，然后运行TensorFlow，Pytorch，百度升级了两次平台，现在已经不可以了，但是我们可以通过终端下载包，上传代码，然后运行，训练就好了。</p>
<h2>安装包</h2>
<p>上传package.txt,包括python包信息
打开终端，下载包，命令如下</p>
<pre><code>pip3 install -r package.txt
</code></pre>
<p>pip不可使用，更新一下就可以使用了，命令如下</p>
<pre><code>pip3 install --upgrade pip 
</code></pre>
<p>将数据以及代码上传到AI Studio工作空间，执行代码训练模型。</p>
<h2>相关链接</h2>
<p><a href="https://blog.csdn.net/The_Time_Runner/article/details/96993733">python如何从txt文件中批量pip安装包</a></p>
<p><a href="https://blog.csdn.net/dlh_sycamore/article/details/82378544">Ubuntu下安装Python 3.7</a></p>
<p><a href="https://www.cnblogs.com/z-joshua/p/5710698.html">Centos/Linux 下升级python2.7至3.5.0</a></p>
<p><a href="https://blog.csdn.net/cocoaqin/article/details/79184540">Google Colab 免费GPU服务器使用教程</a></p>
<p><a href="https://karbo.online/dl/kaggle-gpu/">在Kaggle免费使用GPU训练自己的神经网络</a></p>
<h2>更新博客</h2>
<p>今天再一次尝试了百度AI studio，kaggle，谷歌colab这三个平台。</p>
<p>百度的AI studio在基础版cpu的情况下，是可以上传代码进行训练，再带gpu的主机上没有权限。</p>
<p>kaggle上传文件这块受到限制了，自己写代码还是可以的，每个星期有30个小时的免费GPU使用时间，然后就是有丰富的数据集，可以不用下载下来，可以参加竞赛拿奖金，预装了tensorflow和pytorch等等。</p>
<p>谷歌的colab和百度的差不多，但是开放程度更高，稳定性更好，预装了tensorflow和pytorch，有免费的GPU，15g网盘。</p>
<h2>翻墙工具</h2>
<p>附上两个翻墙工具链接，不是完全免费的。</p>
<p><a href="https://socketproapp.com/zh/home">SocketPro</a></p>
<p><a href="https://d.wjsq.xyz/">W加速器</a></p>
<h2>命令</h2>
<p>查看linux系统版本</p>
<pre><code>cat /proc/version
</code></pre>
<p>查看cpu信息</p>
<pre><code>cat /proc/cpuinfo
</code></pre>
<p>查看内存信息</p>
<pre><code>cat /proc/meminfo
</code></pre>
<p>英伟达系统管理接口,查看cuda信息gpu信息等等。</p>
<pre><code>nvidia-smi
</code></pre>
<p>python更新pip</p>
<pre><code>python -m pip install –upgrade pip
</code></pre>
<h2>相关说明</h2>
<h3>可以直接再jupyter lab的notebook模式直接执行shell命令</h3>
<p>执行shell命令Shell是一种与计算机进行文本交互的方式。一般来讲，当你正在使用Python编译器，需要用到命令行工具的时候，要在shell和IDLE之间进行切换。但是，如果你用的是Jupyter，就完全不用这么麻烦了，你可以直接在命令之前放一个“!”，就能执行shell命令，完全不用切换来切换去，就能在IPython里执行任何命令行。In [1]: !ls</p>
<pre><code>In [2]: !pwd
/home/Parul/Desktop/Hello World Folder
In [3]: !echo "Hello World"
Hello World
</code></pre>
<h3>再次安装依赖包</h3>
<p>注意将所依赖的包以及版本信息保存到txt文件中，方便之后再安装使用，colab好像不需要重新安装包（可再次考证一下），ai studio每次都需要重新安装。</p>
<p><a href="https://www.jianshu.com/p/7f55544f54ff">pip常用命令</a>
pip freeze,pip show等等。</p>
<h3>下载数据集，进行训练</h3>
<p>这一块，我们把各个平台，当作一个云主机，直接执行命令下载数据集，上传代码，然后就可以进行训练了。</p>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【折腾记录】反编译APK]]></title>
            <link>https://bingqiangzhou.github.io/posts/dailyjungle-decomplieapk/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/dailyjungle-decomplieapk/</guid>
            <pubDate>Mon, 16 Sep 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[简单记录一次突发奇想的实践经历，以及反编译APK的步骤。]]></description>
            <content:encoded><![CDATA[<p>简单记录一次突发奇想的实践经历，以及反编译APK的步骤。</p>
<h2>突发奇想</h2>
<p>学校的APP特别不好用，经常吐槽它，就在昨天晚上，我们突发奇想，看可不可以将一些内容迁移到小程序上，于是我说干就干了，首先我反编译了APK，随后看了一些相关代码，找了到一些接口的URL以及传递的参数名，然而不知道参数是什么样的形式，随后使用<code>fiddler</code>开始抓包，其中碰到了一个不能抓取https协议请求的问题，这里附上配置链接，<a href="https://www.cnblogs.com/hushaojun/p/6385947.html">安卓配置</a>，<a href="https://blog.csdn.net/weixin_39198406/article/details/81123716">iOS配置</a>，解决了这个问题，抓到包，但是发现了参数中的<code>token</code>需要解决，解决不了无法提供给多人使用，所以我又看了一下代码，发现他是使用了一个单例模型，将一个类的实例转为<code>String</code>作为<code>token</code>，但是这里我不太确定只有这一种方法，因为我看到了登录的接口中，返回的参数返回了一个<code>token</code>，可能分不同的情况吧，到这里我开始打退堂鼓了，还得好好锻炼，一个人还是很难耐得住寂寞。</p>
<h2>反编译APK</h2>
<p>再这里记录一下反编译APK的步骤吧。</p>
<p><a href="https://blog.csdn.net/vipzjyno1/article/details/21039349">参考链接</a></p>
<h3>第一步、解压APK</h3>
<p>使用WINRAR解压软件解压APK包，得到<code>.dex</code>文件。</p>
<h3>第二步、使用apktool反编译APK</h3>
<p>注意需要安装java 8以及以上版本的JDK。<a href="https://ibotpeaches.github.io/Apktool/install/">参考链接</a></p>
<h4>下载工具</h4>
<p>下载脚本<a href="https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/windows/apktool.bat"><code>apktool.bat</code></a>，下载jar包<a href="https://bitbucket.org/iBotPeaches/apktool/downloads/"><code>apktool.jar</code></a>，注意，这里下载的包需要更名为<code>apktool.jar</code></p>
<h4>配置环境变量</h4>
<p>将<code>apktool.bat</code>和<code>apktool.jar</code>所在文件夹添加到环境变量中，或者这两个文件放到<code>C://Windows</code>目录下。</p>
<h4>执行反编译命令</h4>
<p>命令格式为 apktool d [apk文件] [输出文件夹]</p>
<pre><code>apktool d base.apk base
</code></pre>
<h3>第三步、使用dxe2jar反编译APK</h3>
<p>这里将解压出来的<code>.dex</code>转为<code>jar</code>包。</p>
<h4>下载工具</h4>
<p>下载<a href="https://bitbucket.org/pxb1988/dex2jar/downloads">dex2jar</a></p>
<h4>执行命令</h4>
<p>再<code>dex2jar</code>文件夹中执行命令，命令格式<code>d2j-dex2jar.bat [dex文件路径]</code>，注意将所有的<code>.dex</code>文件转换为<code>jar</code>包。</p>
<pre><code>d2j-dex2jar.bat E:\classes.dex
</code></pre>
<h3>第四步、使用jd-gui查看jar源代码</h3>
<p>下载<a href="https://github.com/java-decompiler/jd-gui/releases">jd-gui</a>，使用<code>jd-gui</code>打开<code>jar</code>包查看源码。</p>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
        <item>
            <title><![CDATA[【论文阅读笔记】Semantic Instance Segmentation with a Discriminative Loss Function]]></title>
            <link>https://bingqiangzhou.github.io/posts/paperreading-semanticinstancesegmentationwithadiscriminativelossfunction/</link>
            <guid isPermaLink="false">https://bingqiangzhou.github.io/posts/paperreading-semanticinstancesegmentationwithadiscriminativelossfunction/</guid>
            <pubDate>Mon, 16 Sep 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[论文提出一种将特征空间的点分簇的损失函数，损失函数主要分为三项构成，分别为方差项（variance term），距离项（distance term），正则项（regulariztion trem），其中方差项计算的是簇内的距离，距离项计算的是簇与簇的距离，分别控制同簇点与簇中心之间的距离在δv之内，不同簇与簇的中...]]></description>
            <content:encoded><![CDATA[<p><a href="%E8%AE%BA%E6%96%87%E5%9C%B0%E5%9D%80">带有歧视的损失函数的语义实体分割（Semantic Instance Segmentation with a Discriminative Loss Function）</a></p>
<p>论文提出一种将特征空间的点分簇的损失函数，损失函数主要分为三项构成，分别为方差项（variance term），距离项（distance term），正则项（regulariztion trem），其中方差项计算的是簇内的距离，距离项计算的是簇与簇的距离，分别控制同簇点与簇中心之间的距离在<code>δv</code>之内，不同簇与簇的中心距离大于<code>δd</code>，<a href="https://arxiv.org/pdf/1703.10277.pdf">论文下载地址</a></p>
<p><img src="/assets/images/2019/20190916/figure2.png" alt="图片：损失函数原理图" /></p>
<p>论文主要关注于损失函数，主要关注于训练出好的特征空间（如<code>Embedding Space</code>）。</p>
<h2>带有歧视的损失函数</h2>
<h3>定义</h3>
<p>$C$是真实值中的簇的总数，$N_c$簇$c$中的元素数量，$x_i$是一个元素对应的嵌入向量，$\mu_c$是$c$簇中嵌入向量的平均值（簇的中心），$\vert\vert\cdot\vert\vert$是L1距离或者L2距离，$[x]_+$是max(0,x)定义的铰链函数，$\delta_v$和$\delta_d$代表方差和距离损失的间隙。</p>
<h3>方差项</h3>
<p>$$L_{var} = \frac{1}{C}\sum_{c=1}^C\frac{1}{N_c}\sum_{i=1}^{N_c}[||{\mu_i-x_i}||-\delta_v]_+^2$$</p>
<h3>距离项</h3>
<p>$$L_{dist}=\frac{1}{C(C-1)}{\sum_{c_A=1}^{C}\sum_{c_B=1}^{C}}<em>{c_A \neq c_B}[2\delta_d-||{\mu</em>{c_A}-\mu_{c_B}}||]_+^2$$</p>
<h3>正则项</h3>
<p>$$L_{reg}=\frac{1}{C}\sum_{c=1}^{C}||\mu_c||$$</p>
<h3>完整损失函数</h3>
<p>$$L=\alpha\cdot{L_{var}}+\beta\cdot{L_{dist}}+\gamma\cdot{L_{reg}}$$</p>
<p>在实验时，作者设置$\alpha=\beta=1$，$\gamma=0.001$。</p>
<h2>其他内容</h2>
<p>论文还说到了后处理（post-processing），包括增强鲁棒性（increasing robustness）等等， 还有其他实验的设置和数据集，还有优缺点等等，这里最后说一下这个方法的优缺点（pros and cons）。</p>
<h3>优缺点</h3>
<p>论文中方法，在有重叠部分的情况下依然有好的效果，比如在合成的零散的线条中，在数据集图片中有比较相似的实例时，效果也比较好，但是，在随机多种多样的数据集中，效果不太好，在只有一个的实例对象的图片中训练效果不是太好。</p>
<h3>MathJax数学公式</h3>
<p><a href="https://www.cnblogs.com/Bone-ACE/p/4558870.html">MathJax语法</a></p>
<p><a href="https://blog.csdn.net/luyaxige/article/details/80193409">MathJax语法</a></p>
<p><a href="http://cxcgzx.cn:88/test/mathtest.php">在线MathJax公式书写</a></p>
]]></content:encoded>
            <author>Bingqiang Zhou</author>
        </item>
    </channel>
</rss>