普通视图

发现新文章,点击刷新页面。
昨天以前icy's blog

从 CallBack 到 Promise,React 框架异步开发学习心得

作者 icy
2023年2月4日 22:00
<h2 id="引入"><a href="#引入" class="headerlink" title="引入"></a>引入</h2><p>说起 React,我印象最深刻的是,在 React 中,数据是<a href="https://zh-hans.reactjs.org/docs/state-and-lifecycle.html#the-data-flows-down">向下流动</a>的(<a href="https://www.php.cn/website-design-ask-493282.html">react 为什么是单向数据流</a>)——越高层级的组件,获得着越多的数据,而低层级组件数据的获取和更新,大多都通过<a href="https://zh-hans.reactjs.org/docs/components-and-props.html">组件属性传递</a>以及<a href="https://juejin.cn/post/7065555069889937415">回调函数</a>方式得到。这就意味着,高层组件刷新会同时刷新低层组件,而低层组件刷新往往不会带动高层组件刷新,于是更多的状态和逻辑会出现在比较高层级的组件里,在 React 中叫做<a href="https://zh-hans.reactjs.org/docs/lifting-state-up.html">状态提升</a> 。例如对话框的打开与关闭更应该是对话框组件的属性,而不是对话框组件的状态——对话框的操作往往与高层数据相关,如果把状态放在低层级,则很难把当前的状态和数据与高层级组件交互。</p><p>在这种数据流的模式下,为了使得基本组件“动起来”,高层级组件里总会有大大小小的许多状态,以便控制基本组件的开/关、显示/隐藏等等。此外,除了控制基本组件的状态以外,高层级组件本身可能还承担着数据通信的功能,例如我们本次提到的异步请求和发送数据。在 React 中,状态<code>state</code>的更新会使得组件重新进行渲染(见<a href="https://react.docschina.org/docs/state-and-lifecycle.html">State &amp; 生命周期</a>),有的时候我们只希望重新渲染这个组件的一部分组件(例如刚才所说的对话框),而有的时候我们希望重新请求数据(数据同步、表格翻页)全部刷新,于是我们通常会使用 <a href="https://zh-hans.reactjs.org/docs/hooks-effect.html">useEffect 钩子</a>对一些刷新操作添加限定,仅仅在某些变量修改的时候,才会重新执行该部分代码逻辑(在 React 官方文档中叫做<a href="https://zh-hans.reactjs.org/docs/hooks-effect.html#tip-use-multiple-effects-to-separate-concerns">关注点分离</a>)。</p><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>所以对于一个又需要刷新数据,又需要控制对话框,而且获取数据要请求两次 api 的组件,就会变成这个样子(CallBack 版本):</p><figure class="highlight jsx"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Component</span>(<span class="params">props</span>) {</span><br><span class="line"> <span class="keyword">let</span> [dialogState, setDialogState] = <span class="title function_">useState</span>(<span class="literal">false</span>); <span class="comment">// some states for dialogs</span></span><br><span class="line"> <span class="keyword">let</span> [renderData, setRenderData] = <span class="title function_">useState</span>(<span class="literal">null</span>); <span class="comment">// some states for rendering</span></span><br><span class="line"> <span class="keyword">let</span> [page, setPage] = <span class="title function_">useState</span>(<span class="number">1</span>); <span class="comment">// some states which force data refresh</span></span><br><span class="line"> <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> {</span><br><span class="line"> <span class="title function_">fetchSomeData</span>(<span class="string">"url"</span>, {<span class="attr">params</span>: <span class="string">"Some Params"</span>}, {<span class="attr">config</span>: <span class="string">"Some Configurations"</span>}, <span class="function">(<span class="params">data</span>) =&gt;</span> {</span><br><span class="line"> <span class="comment">// callback for success</span></span><br><span class="line"> <span class="keyword">let</span> someProps = <span class="title function_">getSomeProps</span>(data);</span><br><span class="line"> <span class="title function_">fetchSomeData</span>(<span class="string">"url2"</span>, {<span class="attr">params</span>: someProps}, {<span class="attr">config</span>: <span class="string">"Some Configurations"</span>}, <span class="function">(<span class="params">data</span>) =&gt;</span> {</span><br><span class="line"> <span class="comment">// funciton for process</span></span><br><span class="line"> <span class="title function_">setRenderData</span>(<span class="title function_">processing</span>(data));</span><br><span class="line"> }, <span class="function">() =&gt;</span> {});</span><br><span class="line"> }, <span class="function">(<span class="params">error</span>) =&gt;</span> {</span><br><span class="line"> <span class="comment">// callback for failure</span></span><br><span class="line"> });</span><br><span class="line"> }, [page]); <span class="comment">// fetch data only when page changes</span></span><br><span class="line"> <span class="keyword">if</span> (renderData === <span class="literal">null</span>) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="language-xml"><span class="tag">&lt;&gt;</span> {/* equals to <span class="tag">&lt;<span class="name">React.Fragment</span>&gt;</span> */}</span></span><br><span class="line"><span class="language-xml"> <span class="tag">&lt;<span class="name">Dialog</span> <span class="attr">someStates</span>=<span class="string">{dialogState}/</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"> <span class="tag">&lt;<span class="name">Others</span> <span class="attr">data</span>=<span class="string">{RenderData}/</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"> <span class="tag">&lt;/&gt;</span> {/* equals to <span class="tag">&lt;/<span class="name">React.Fragment</span>&gt;</span> */}</span></span><br><span class="line"><span class="language-xml"> );</span></span><br><span class="line"><span class="language-xml">}</span></span><br></pre></td></tr></tbody></table></figure><p>于是这个组件的执行流程是这样子的:</p><ul><li>组件第一次渲染:执行<code>useEffect</code>,开启异步数据请求,此时并没有任何有效数据用于渲染,于是返回<code>null</code>不加载模型;</li><li>组件收取到信息:执行回调函数,对数据进行处理,并更新组件状态,此时仍未执行组件刷新,数据不变;</li><li>组件状态得到更新:组件状态变化,组件刷新,但不再执行<code>useEffect</code>;</li><li>(中间可能的)对话框状态变化:组件状态变化,组件刷新,但不再执行<code>useEffect</code>。</li></ul><p>这样存在的问题在于,</p><ul><li>回调函数过于复杂——函数体量太大在合作时难以理解,包装起来可能涉及数据传递的问题</li><li>回调函数嵌套——有可能在收到某些信息,还要基于这些信息继续发请求,那么回调函数可能嵌套多层</li></ul><h2 id="Promise-的意义"><a href="#Promise-的意义" class="headerlink" title="Promise 的意义"></a>Promise 的意义</h2><p>我感觉<a href="https://zhuanlan.zhihu.com/p/26523836">理解 JavaScript Promise</a>这篇文章写的还是不错的,使用 <code>Promise</code> 构造一个函数,这个 <code>Promise</code> 就可以管理这个函数的状态,以便后续任务在这个函数执行完毕后使用。所以现代的 fetch 函数都尽可能返回一个 <code>Promise</code>,以便我们使用 <code>Promise.then()</code> 这个方法以便对数据进行处理。</p><p>所以上面的代码或许可以改成这个样子:</p><figure class="highlight jsx"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Component</span>(<span class="params">props</span>) {</span><br><span class="line"> ...</span><br><span class="line"> <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> {</span><br><span class="line"> <span class="title function_">fetchSomeData</span>(<span class="string">"url"</span>, {<span class="attr">params</span>: <span class="string">"Some Params"</span>}, {<span class="attr">config</span>: <span class="string">"Some Configurations"</span>}).<span class="title function_">then</span>(<span class="function">(<span class="params">data</span>) =&gt;</span> {</span><br><span class="line"> <span class="comment">// funciton for process</span></span><br><span class="line"> <span class="keyword">let</span> someProps = <span class="title function_">getSomeProps</span>(data);</span><br><span class="line"> <span class="keyword">return</span> <span class="title function_">fetchSomeData</span>(<span class="string">"url2"</span>, {<span class="attr">params</span>: someProps}, {<span class="attr">config</span>: <span class="string">"Some Configurations"</span>});</span><br><span class="line"> }).<span class="title function_">then</span>(<span class="function">(<span class="params">data</span>) =&gt;</span> {</span><br><span class="line"> <span class="comment">// funciton for process</span></span><br><span class="line"> <span class="title function_">setRenderData</span>(<span class="title function_">processing</span>(data));</span><br><span class="line"> }).<span class="title function_">catch</span>(<span class="function">(<span class="params">error</span>) =&gt;</span> {</span><br><span class="line"> <span class="comment">// function for failure</span></span><br><span class="line"> });</span><br><span class="line"> }, [page]); <span class="comment">// fetch data only when page changes</span></span><br><span class="line">...</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>那么这样有没有实质性的减少代码层数?减少了,原先存在的嵌套调用现在变成了连续使用 <code>.then()</code>函数,使得硕大的处理层变得轻松的多。</p><h2 id="Async-和-await"><a href="#Async-和-await" class="headerlink" title="Async 和 await"></a>Async 和 await</h2><p>与上一节一样,先挂出一个链接用于学习:<a href="https://www.cnblogs.com/youma/p/10475214.html">【学习笔记】深入理解 async/await</a>。</p><p><code>await</code> 的出现带来了什么呢?<code>await</code> 使得获取的结果直接提取了出来,不再需要额外套一层函数用于执行。这样函数嵌套会更加少,而且也可以像同步的函数一样处理数据了。于是我们的代码会变得更加清楚,不会再像原来一样晦涩难懂。</p><p>于是我们的代码可能变成这样子,如果想分开处理异常可以套两个 <code>try-catch</code>块:</p><figure class="highlight jsx"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Component</span>(<span class="params">props</span>) {</span><br><span class="line"> ...</span><br><span class="line"> <span class="title function_">useEffect</span>(<span class="keyword">async</span> () =&gt; {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">let</span> data = <span class="keyword">await</span> <span class="title function_">fetchSomeData</span>(<span class="string">"url"</span>, {<span class="attr">params</span>: <span class="string">"Some Params"</span>}, {<span class="attr">config</span>: <span class="string">"Some Configurations"</span>});</span><br><span class="line"> <span class="comment">// funciton for process</span></span><br><span class="line"> <span class="keyword">let</span> someProps = <span class="title function_">getSomeProps</span>(data);</span><br><span class="line"> <span class="keyword">let</span> anoData = <span class="keyword">await</span> <span class="title function_">fetchSomeData</span>(<span class="string">"url2"</span>, {<span class="attr">params</span>: someProps}, {<span class="attr">config</span>: <span class="string">"Some Configurations"</span>});</span><br><span class="line"> <span class="comment">// funciton for process</span></span><br><span class="line"> <span class="title function_">setRenderData</span>(<span class="title function_">processing</span>(anoData));</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> <span class="comment">// function for failure</span></span><br><span class="line"> } <span class="comment">// try-catch block is unnecessary if no error exist</span></span><br><span class="line"> }, [page]); <span class="comment">// fetch data only when page changes</span></span><br><span class="line">...</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>但是这样 <code>Eslint</code> 组件是会报警告的——</p><blockquote><p>ESLint: Effect callbacks are synchronous to prevent race conditions. Put the async function inside:</p></blockquote><p>如果版本早于 React 16,可能会直接报错误——</p><blockquote><p>An effect function must not return anything besides a function, which is used for clean-up. It looks like you wrote useEffect(async () =&gt; …) or returned a Promise. Instead, write the async function inside your effect and call it immediately</p></blockquote><p>这是因为 <code>useEffect</code>是需要返回值来解决组件销毁/重建时的副作用清除的,而我们加上 <code>async</code> 关键字则会让这个函数返回一个 <code>Promise</code>,所以应该建一个普通的函数,然后在函数里面创建带有<code>async</code>关键字的函数,并立即调用。详见 <a href="https://juejin.cn/post/7029117054233870349">hooks 学习之 useEffect</a>。</p><figure class="highlight jsx"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Component</span>(<span class="params">props</span>) {</span><br><span class="line"> ...</span><br><span class="line"> <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> {(<span class="keyword">async</span> () =&gt; {</span><br><span class="line"> ...</span><br><span class="line"> })()}, [page]); <span class="comment">// fetch data only when page changes</span></span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h2 id="并行的数据请求"><a href="#并行的数据请求" class="headerlink" title="并行的数据请求"></a>并行的数据请求</h2><p>我们的请求可能没有前置要求,那么异步的数据获取我们怎么进行处理呢?一般来说,我们对于数据请求,难免存在请求失败的情况,所以常见的策略是哪部分到了先加载哪部分,报错的部分再进行重试或请求备用数据源,以免用户等待太着急。于是我们就可以建造多个<code>useEffect</code>函数,分别进行数据请求和处理,加上 <code>React</code> 的关注点分离策略,我们就可以实现部分数据的渲染。</p><figure class="highlight jsx"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Component</span>(<span class="params">props</span>) {</span><br><span class="line"> <span class="keyword">let</span> [dataA, setDataA] = <span class="title function_">useState</span>(<span class="literal">null</span>); <span class="comment">// data for Component A</span></span><br><span class="line"> <span class="keyword">let</span> [dataB, setDataB] = <span class="title function_">useState</span>(<span class="literal">null</span>); <span class="comment">// data for Component B</span></span><br><span class="line"> <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> {</span><br><span class="line"> <span class="title function_">fetchSomeData</span>(<span class="string">"urlA"</span>, {<span class="attr">params</span>: <span class="string">"Some Params"</span>}, {<span class="attr">config</span>: <span class="string">"Some Configurations"</span>}).<span class="title function_">then</span>(<span class="function">(<span class="params">data</span>) =&gt;</span> <span class="title function_">setDataA</span>(data));</span><br><span class="line"> }, [page]); </span><br><span class="line"> <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> {</span><br><span class="line"> <span class="title function_">fetchSomeData</span>(<span class="string">"urlB"</span>, {<span class="attr">params</span>: <span class="string">"Some Params"</span>}, {<span class="attr">config</span>: <span class="string">"Some Configurations"</span>}).<span class="title function_">then</span>(<span class="function">(<span class="params">data</span>) =&gt;</span> <span class="title function_">setDataB</span>(data));</span><br><span class="line"> }, [page]); </span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="language-xml"><span class="tag">&lt;&gt;</span> {/* equals to <span class="tag">&lt;<span class="name">React.Fragment</span>&gt;</span> */}</span></span><br><span class="line"><span class="language-xml"> { dataA &amp;&amp; <span class="tag">&lt;<span class="name">A</span> <span class="attr">data</span>=<span class="string">{dataA}/</span>&gt;</span> }</span></span><br><span class="line"><span class="language-xml"> { dataB &amp;&amp; <span class="tag">&lt;<span class="name">B</span> <span class="attr">data</span>=<span class="string">{dataB}/</span>&gt;</span> }</span></span><br><span class="line"><span class="language-xml"> <span class="tag">&lt;/&gt;</span> {/* equals to <span class="tag">&lt;/<span class="name">React.Fragment</span>&gt;</span> */}</span></span><br><span class="line"><span class="language-xml"> );</span></span><br><span class="line"><span class="language-xml">}</span></span><br></pre></td></tr></tbody></table></figure><p>如果我们对数据正确性有非常高的要求,要求必须所有数据到齐才能渲染的话,可以使用<code>Promise.all()</code>函数。</p><figure class="highlight jsx"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Component</span>(<span class="params">props</span>) {</span><br><span class="line"> <span class="keyword">let</span> [dataA, setDataA] = <span class="title function_">useState</span>(<span class="literal">null</span>); <span class="comment">// data for Component A</span></span><br><span class="line"> <span class="keyword">let</span> [dataB, setDataB] = <span class="title function_">useState</span>(<span class="literal">null</span>); <span class="comment">// data for Component B</span></span><br><span class="line"> <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> {</span><br><span class="line"> <span class="keyword">let</span> remoteA = <span class="title function_">fetchSomeData</span>(<span class="string">"urlA"</span>, {<span class="attr">params</span>: <span class="string">"Some Params"</span>}, {<span class="attr">config</span>: <span class="string">"Some Configurations"</span>});</span><br><span class="line"> <span class="keyword">let</span> remoteB = <span class="title function_">fetchSomeData</span>(<span class="string">"urlB"</span>, {<span class="attr">params</span>: <span class="string">"Some Params"</span>}, {<span class="attr">config</span>: <span class="string">"Some Configurations"</span>});</span><br><span class="line"> <span class="title class_">Promise</span>.<span class="title function_">all</span>([remoteA, remoteB]).<span class="title function_">then</span>(<span class="function">(<span class="params">dataArray</span>) =&gt;</span> {</span><br><span class="line"> <span class="keyword">let</span> [first, second] = dataArray;</span><br><span class="line"> <span class="title function_">setDataA</span>(first);</span><br><span class="line"> <span class="title function_">setDataB</span>(second);</span><br><span class="line"> }).<span class="title function_">catch</span>(<span class="function">(<span class="params">error</span>) =&gt;</span> <span class="title function_">someFunction</span>(error));</span><br><span class="line"> }, [page]); </span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="language-xml"><span class="tag">&lt;&gt;</span> {/* equals to <span class="tag">&lt;<span class="name">React.Fragment</span>&gt;</span> */}</span></span><br><span class="line"><span class="language-xml"> { dataA &amp;&amp; <span class="tag">&lt;<span class="name">A</span> <span class="attr">data</span>=<span class="string">{dataA}/</span>&gt;</span> }</span></span><br><span class="line"><span class="language-xml"> { dataB &amp;&amp; <span class="tag">&lt;<span class="name">B</span> <span class="attr">data</span>=<span class="string">{dataB}/</span>&gt;</span> }</span></span><br><span class="line"><span class="language-xml"> <span class="tag">&lt;/&gt;</span> {/* equals to <span class="tag">&lt;/<span class="name">React.Fragment</span>&gt;</span> */}</span></span><br><span class="line"><span class="language-xml"> );</span></span><br><span class="line"><span class="language-xml">}</span></span><br></pre></td></tr></tbody></table></figure>

境内外分离并分离图片源的 Hexo 博客配置

作者 icy
2022年7月30日 00:00
<p>每年寒暑假,我都会去倒腾倒腾博客的搭建问题,上一次的全站加速存在着可能的几个问题:</p><ul><li>对于更新的同步来说太慢了,导致修改后还得手动到后台“刷新预热”处清除各部署单元的缓存</li><li>全站加速的境内境外分别计费,资源包不互通</li><li>在博客访问少的时候,CDN 节点也得回源到 GitHub,还是挺慢的</li></ul><p>所以有没有一种方法,对内对外都可以用起来很方便呢?在研究域名解析的负载均衡后,我选择了国内国外分开解析的方法——国内直接解析到自己的服务器,国外解析到 GitHub Pages,就可以了。</p><p><img src="/../images/2022073001.png" alt="DNSPods中设置境内境外分别的解析"></p><p>一开始觉得,这有什么难的呀,改改解析就完事了,后来发现博客的图包太大了,部署起来费事(因为从 GitHub 克隆仓库,带宽时高时低),再加上服务器是 1M 小水管儿,所以这次我用上了云服务中吹来吹去的<strong>对象存储</strong>来托管图片的存储<del>(对哦,没有对象存就 new 一个诶)</del>。</p><p><img src="/../images/2022073002.png" alt="腾讯云的对象存储"></p><p>顺便在这里提一句,腾讯云的对象存储对于新用户赠送 180 天 50GB 的存储(但是流量费、请求费少不了),但是我看了一下我的免费额度,好像对于我当时那批用户,腾讯云给的额度好像不局限于存储诶,还是永久免费的。庆幸高一的时候,我就已经整起腾讯云这东西了 23333。</p><p><img src="/../images/2022073003.png" alt="我的资源包免费额度"></p><p>对象存储首先需要在云服务商控制台创建桶,设置好权限(记得设置防盗链,以及关闭空 referer 支持),设置好自定义域名(其实是换 cdn 方便,因为到时候只要改解析就好了,不过坏处是要设置 SSL 证书,各有利弊吧)。</p><p>由于本站的图片都在<code>/images/</code>目录下,于是一个非常简单的想法就是把这个目录 302 出去。</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">location /images/ {</span><br><span class="line"> rewrite ^/(.*) https://imgcdn.icys.top$request_uri redirect;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>因为我也不喜欢存储桶里图片堆在根目录下(不好清理),所以我在存储桶里还是建立了一个<code>images</code>目录,这样在跳转的时候就省事了。</p><p>但是国内外如果我都使用国内节点可能带来国外图片速度下降问题,再加上 Pages 也没有 302 支持,于是还得考虑一下国外的图片处理问题。然后我发现,好像我可以选择建一个叫做<code>images</code>的仓库,开启 Pages 后就自然而然的就是<code>icy-blue.github.io/images/</code>(配置 CNAME 之后就是<code>icys.top/images/</code>),自然而然地就可以不用 302 了,真的是方便。</p><img src="../images/2022073004.png" alt="images仓库的Pages" style="zoom:50%;"><p>于是,对于图片的处理,可以在本地<code>images</code>目录新建一个仓库与 GitHub 同步,这样本地编辑 markdown 的时候写<code>../images/xxx</code>也能找得到,计划再在<code>images</code>仓库设置<code>Action</code>对新图片自动同步到腾讯云,这样就实现了一个比较优秀的图片加载问题。</p><hr><p>对了,刚打算收工,就发现 GitHub 对于 Pages 的更新<a href="https://github.blog/changelog/2022-07-27-github-pages-custom-github-actions-workflows-beta/">GitHub Pages: Custom GitHub Actions Workflows (beta)</a>。大概说的是,Pages 的输入可以不是一个分支了,可以直接用 Action 打好的 tar 包去部署,省的像我,先建立一个<code>icy-blog</code>仓库,再通过 Action 生成到<code>icy-blue.github.io</code>仓库,就可以一步到位啦。看看暑假有没有空做,不行等寒假再搞搞~</p><p>另外,之前好像挖了几个坑来着,填填坑——</p><ul><li>使用 GitHub Actions 实现仓库从 GitHub 到 Gitee 的同步:这个 Gitee 好像自己就可以实现,也省的自己写了</li><li>使用 Gitee Pages:现在 Gitee 的开源环境还是算了吧,另外 Gitee Pages 是要身份证核验的</li><li>做一个合适的负载均衡:境内外的负载均衡可能也算负载均衡吧</li><li>使用 Lint-md 让博客内容排版更加规范:这个好像可以简单提一下,用的是<a href="https://github.com/lint-md/lint-md">llint-md</a>的仓库,用起来也非常轻松,在 Action 部署的时候 npm 安装好,然后把 markdown 文件送到参数里(我用了 shell 里<code>xargs</code>的一个方法使得一行就弄完了,不过注意一下当时我设置的时候项目路径有大小写 bug 所以我用的 Windows 下的 Bash Shell,也许现在 bug 修了罢),最后别忘了创建提交改回去(要在设置里给 Action 写权限)</li></ul><figure class="highlight yaml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Import</span> <span class="string">lint-md</span></span><br><span class="line"> <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string"> npm update npm</span></span><br><span class="line"><span class="string"> npm i -g @lint-md/cli</span></span><br><span class="line"><span class="string"></span> </span><br><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Fix</span> <span class="string">format</span> <span class="string">of</span> <span class="string">markdown</span> <span class="string">sources</span></span><br><span class="line"> <span class="attr">shell:</span> <span class="string">bash</span></span><br><span class="line"> <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"> <span class="string">find</span> <span class="string">./source/</span> <span class="string">-name</span> <span class="string">"*.md"</span> <span class="string">|</span> <span class="string">xargs</span> <span class="string">lint-md</span> <span class="string">--fix</span> </span><br></pre></td></tr></tbody></table></figure><img src="../images/2022073005.png" alt="在Setting-Action-General中开启Action的写许可" style="zoom:50%;">

【论文阅读】Computational Design of High-level Interlocking Puzzles

作者 icy
2022年7月18日 23:00
<p>这是新加坡科技大学<a href="https://songpenghit.github.io/">宋鹏老师</a>组的项目,获得了 SIGGRAPH 2022 提名奖,在创新上还是非常不错的,也非常的有趣。</p><p>宋鹏老师在一月前还参加了 Games 的<a href="https://www.bilibili.com/video/BV153411G78S">Webminar 报告</a>,也是讲述的类似的工作,也欢迎大家去了解一下。</p><h2 id="谜题与解组装"><a href="#谜题与解组装" class="headerlink" title="谜题与解组装"></a>谜题与解组装</h2><iframe src="//player.bilibili.com/player.html?aid=796597593&amp;bvid=BV1VC4y187iZ&amp;cid=220933451&amp;page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe><p>这是 B 站 Up 主“GM 的秘密基地”关于“圣剑”谜题的解题动画还原,论文中提到的谜题 Puzzle,其实就是指的这些可以在一步步移动后拆解为一块块的个体的过程。</p><p>为了方便读者了解相关背景,在这里先科普一些背景知识(来自于宋老师组在<a href="https://eg2022.univ-reims.fr/">Eurographics 2022</a>上的<a href="https://sutd-cgl.github.io/supp/Publication/projects/2022-EG-AssemblyTutorial/index.html">tutorial</a>)。</p><p>关于这种谜题,学术界通常将其与组装 Assembly 归为一类问题,并把解开谜题的问题列为解组装问题 disassembly。组装的话每个物块之间有不同的连接方式,按永久性来讲,分为永久和临时的连接;按接触形式来讲,分为用摩擦力固定的连接和支持力固定的连接;按连接方式来讲,临时的连接还分为内在 internal(结构形状决定)和外在 external(使用非连接部件的第三方部件进行连接)……</p><p>组装问题中,学术界还会去研究两个物块之间接触面的几何特征,来约束物块之间的相对位移(该工作出自 ZIQI WANG 于 2021 年 SIGGRAPH 的<a href="https://dl.acm.org/doi/10.1145/3450626.3459680">MOCCA 论文</a>,通讯也是<a href="https://songpenghit.github.io/">宋鹏老师</a>)。</p><img src="../images/2022072401.png" alt="接触面与可能的相对位移方向的对应" style="zoom: 33%;"><p>当然上面那篇论文更重要的贡献是在结构稳定与平衡方面。论文对输入的物体给出了设计每个块的方式,使得在组装的时候组装比较容易,这个工作非常有意思。</p><h2 id="解组装-Disassembly-问题的常见分类"><a href="#解组装-Disassembly-问题的常见分类" class="headerlink" title="解组装 Disassembly 问题的常见分类"></a>解组装 Disassembly 问题的常见分类</h2><p>前四个分类是描述组装问题难度的重要指标,后面的论文中我们还会遇到。而自锁 interlocking 是描述一个组装的重要特征,也是一个重要的分类。</p><h3 id="顺序性-Sequentiality"><a href="#顺序性-Sequentiality" class="headerlink" title="顺序性 Sequentiality"></a>顺序性 Sequentiality</h3><blockquote><p>Maximum number of moving sub-assemblies w.r.t one another in any (dis)assembly operation</p></blockquote><p>个人理解的顺序性指的是每个物块(组)可以从整体中按一个方向取下,不存在需要一次性需要两个物块按照不同方向同时用力才能取出的情况。从图片中我们可以看出来,上方的组合可以用两只手解决,而下方的需要三只手(两只手移动下面两块,一只手用来固定)。</p><img src="../images/2022072402.png" alt="顺序性 Sequentiality" style="zoom: 33%;"><h3 id="单调性-Monotonicity"><a href="#单调性-Monotonicity" class="headerlink" title="单调性 Monotonicity"></a>单调性 Monotonicity</h3><blockquote><p>Need for intermediate placement operations for at least one part of the assembly</p></blockquote><p>个人理解的单调性指的是某块物体的取出需要其他物块的移动,从而在操作顺序上存在了一个偏序关系。从图片中我们可以看到,上面的图片移动蓝色还是绿色都是无阻挡的,而下方的组合需要先把绿色块移走才能把蓝色的块取出来。</p><img src="../images/2022072403.png" alt="顺序性 Sequentiality" style="zoom: 33%;"><h3 id="线性性-Linearity"><a href="#线性性-Linearity" class="headerlink" title="线性性 Linearity"></a>线性性 Linearity</h3><blockquote><p>All assembly operations involve moving a single part with respect to the rest of the assembly</p></blockquote><p>个人理解的线性性指的是组合可以一块块取出。从图片中我们可以看到,上面的图片中蓝色块和绿色块可以独立取出,而下面的需要把蓝绿两块作为整体取出。</p><img src="../images/2022072404.png" alt="顺序性 Sequentiality" style="zoom: 33%;"><h3 id="相干性-Coherence"><a href="#相干性-Coherence" class="headerlink" title="相干性 Coherence"></a>相干性 Coherence</h3><blockquote><p>Whether or not each part that is moved will touch the rest of the assembly</p></blockquote><p>个人理解的相干性指的是每个物块的移除对当前状态是有影响的。从图片中我们可以看到,上面的图片由于是一个整体,某块的移除对当前状态自然是影响的,而下面的图中,移除了绿色块后,自然地形成了左右两部分,左边块的移除自然不影响右边的状态,所以这一块是不相干的。</p><img src="../images/2022072405.png" alt="顺序性 Sequentiality" style="zoom: 33%;"><h3 id="自锁-Interlocking"><a href="#自锁-Interlocking" class="headerlink" title="自锁 Interlocking"></a>自锁 Interlocking</h3><blockquote><p>An assembly is interlocking if only one movable part (key), while all other parts, as well as any subset of the parts, are immobilized</p></blockquote><p>个人理解这里说的自锁描述的是组合可解,而且活动块只有一块,这一块取出后其他块才可以移动取出。从图中我们可以看到,左图中绿色和蓝色都可以直接取出,中间的只有绿色的可以取出,绿色取出钱其他块无法取出,右图中每块都不能正常取出。这也意味着这种结构存在一种稳定性,只要锁住活动块 key,就能保证整个结构是稳定的。</p><img src="../images/2022072406.png" alt="顺序性 Sequentiality" style="zoom: 33%;"><h2 id="High-Level-Puzzle"><a href="#High-Level-Puzzle" class="headerlink" title="High Level Puzzle"></a>High Level Puzzle</h2><h3 id="贡献与创新"><a href="#贡献与创新" class="headerlink" title="贡献与创新"></a>贡献与创新</h3><p>论文主要有以下几个贡献</p><ul><li>给出了难度等级的准确定义,并给出了一个求解每个谜题难度计算的算法(虽然是 NP 完全的 BFS 算法)</li><li>给出了用于构造每一个块的迭代计算算法,并让难度维持在输入的难度上,此外还给出了基于已有迭代结果构造更大难度的 puzzle 算法</li><li>给出了将非立方体体素的物体进行形状优化,转化为规则体素结构的方法</li></ul><p>理论上,pipeline 应该是<strong>先把 Mesh 网格进行优化,优化为可以打印的体素表示,再对体素进行分块处理</strong>。不过为了与论文同步,我们接下来我们将依次介绍每个贡献。</p><h3 id="难度等级与难度计算"><a href="#难度等级与难度计算" class="headerlink" title="难度等级与难度计算"></a>难度等级与难度计算</h3><p>那既然叫做 High Level,那怎么去定义一个谜题的 level 呢?论文给出了业界常用的两个计算方法——按第一块取出的最少移动步骤数以及所有块相互分离所需的最小移动步骤数,并选取了前者作为论文所关心的难度指标(毕竟拿掉第一块之后可能后面的块拿出就会简单一些,当然这也确定了论文讨论是满足单调性 Monotonicity 的 puzzle,否则难度就是 1 了)。论文对此进行了详细的描述:</p><ul><li>无论一次移动几个块,只要是按照一个方向同时移动,记一次移动</li><li>移除某个块记一次移动</li><li>一块无论移动多长的距离,记一次移动</li></ul><p>而难度计算其实就是一个平凡的 BFS 方法,每一次设置一个状态,以状态和移动的关系构成一张无向图。在图中节点分为根节点(初始状态)、普通节点和目标节点(成功移除一块),而初始状态到目标节点的最短路径的边数正是难度等级数。这里我把论文中的伪代码贴在下方,就不再展开介绍了。</p><img src="../images/2022072407.png" alt="计算puzzle P取出第一块的难度算法" style="zoom: 50%;"><img src="../images/2022072408.png" alt="通过BFS可以获得一个无向图" style="zoom:50%;"><p>值得注意的是,对于 K 个物块来说,运动超过一半的物块相当于剩下的物块进行移动(运动的相对性),所以在枚举物块数量的时候不需要让单步移动的物块数超过一半$\lceil K / 2 \rceil$。</p><img src="../images/2022072409.png" alt="枚举每个状态的可能转移" style="zoom:50%;"><p>除了盲目的搜索以外,我们还要考虑到无解的情况,因此论文也给出了一个伪代码来解决问题无解(主要指的是上方 deadlock 的情况)。</p><img src="../images/2022072410.png" style="zoom:50%;"><h3 id="谜题构造"><a href="#谜题构造" class="headerlink" title="谜题构造"></a>谜题构造</h3><img src="../images/2022072411.png" alt="迭代构造每一个物块" style="zoom:50%;"><p>如图所示,本节介绍的是迭代生成每个物块的方法。论文对生成的物块有以下的约束:</p><ul><li>几何连通性:需要保证每一个物块的体素都是六连通的</li><li>大小均匀:需要保证每一块既不是太大又不是太小,设允许误差为$\delta$,总体素数为$M$,物块数为$K$,则约束每个块大小在$[(1-\delta)\lfloor M/K\rfloor],\ (1+\delta)\lfloor M/K\rfloor]$范围内。</li><li>块的可移动性:正在设计的这一块是可以移动的</li><li>子问题不可解:选择的前一状态是不可解的,否则就变成了并行的问题,难度并没有增加</li><li>最终结果可解:不然就设计了个寂寞</li></ul><p>其实设计每一块,总的来说就分为<strong>设计第一块</strong>和<strong>从一个状态设计下一块</strong>的问题,我们将分别进行展开。</p><h4 id="设计第一块"><a href="#设计第一块" class="headerlink" title="设计第一块"></a>设计第一块</h4><p>第一块来说是相对麻烦的,我们要设计一块可以移动但是不能直接移除的一块,设计方法如下:</p><ul><li>确定一个移动方向,来确定有那些块可以被移动。如图 a 和图 b 所示,当选择向左还是向右时,既要满足块可以移动而且不能直接移除的体素被标记为 S,而阻挡他们按此方向移除的体素被标记为 T。</li><li>从适合的体素中随机选一个(通过设置随机种子进行随机),作为块的一个体素。</li><li>通过设置阻碍对,保证选出来的块只能沿着选择的移动方向移动<del>(草怎么引用的这篇论文还是宋老师团队的,还是 2012 的 SIG ASIA)</del><ul><li>如图,图 a 的红色体素有向上和向左两个移动方向,而我们希望限制其向左移动</li><li>我们会选择限制方向的一对体素(图 b 中的任意蓝色圈都可以作为这一对体素)作为阻碍对</li><li>让阻碍对目标方向侧的体素作为阻碍体素,让红色体素与另一个体素最短路相连(但不能包含阻碍体素),就像图 c 一样,使得红色块无法向左移动</li><li>但是图 c 现在也不能向目标方向移动了,我们则扩展块使其可以向目标方向移动,得到图 e</li></ul></li><li>在对满足上面条件的块进行增补,使得块的大小满足上节的约束条件</li></ul><h4 id="设计其他块"><a href="#设计其他块" class="headerlink" title="设计其他块"></a>设计其他块</h4><p>我们设计其他块,其实是有意让难度增加(也是子问题不可解),所以我们尽可能地在已有 puzzle 状态中离根节点最远的节点进行扩展,以免难度不变或降低。</p><p>其他的与设计第一块是类似的,在设计完所有块后判断一下是否满足输入要求的难度等级,不满足则继续递归求解(所以是一个复杂度特别大的算法)。</p><h4 id="提高谜题难度"><a href="#提高谜题难度" class="headerlink" title="提高谜题难度"></a>提高谜题难度</h4><p>论文发现直接通过迭代去构造满足比较大难度的谜题,需要的时间成本太高,所以他们分了两步走——先构造低难度再通过调整提高难度。具体操作如下:</p><ol><li>选择邻居块相对少的体素</li><li>把它加到随机选择的邻居里</li><li>计算新难度</li><li>如果难度增大则保留修改,否则还原修改</li></ol><img src="../images/2022072412.png" alt="提高谜题难度算法伪代码" style="zoom:50%;"><h3 id="形状优化和体素化"><a href="#形状优化和体素化" class="headerlink" title="形状优化和体素化"></a>形状优化和体素化</h3><p>对于一个 Mesh 表达的物体,不是总能无瑕疵地分为体素,如果是对于没有完全填充结构的体素,则可能会导致一些断裂之类的的打印问题。因此如何更好地对形状进行优化,以适合的方法进行体素化,是一个比较重要的问题。所以论文提出希望牺牲一部分的形状精度,通过一些小变形来解决打印问题。</p><p>对于一个 Mesh $M$,其中的点集为$V$,面集为$F$。如果用户给定的体素边长为$\mu$,则可以把体素分为三类——填充满的体素、部分填充体素和空体素(如图 a)。而论文定义有问题的体素是部分填充率小于 10%的体素。因此,论文提出优化目标$$\mathop{\min}<em>{\boldsymbol{t}, w, V} E</em>{\mathrm{voxel}(\boldsymbol{t}, w, V) + \lambda E_{\mathrm{shape}(V)}}$$,其中$\boldsymbol{t}$是物体相对于中心点的位移,$w$是等比缩放率,$E_{\mathrm{voxel}}$是问题体素数量,$E_{\mathrm{shape}}$是维持形状的能量,$\lambda$是权重系数,而$E_{\mathrm{shape}}(V)$的计算使用的是<strong>尽可能刚性</strong>的形状维持能量计算公式。</p><img src="../images/2022072413.png" alt="形状的优化" style="zoom:50%;"><p>这个优化问题存在两个问题:$E_{\mathrm{voxel}}$梯度不好求,以及$E_{\mathrm{voxel}}$的参数$t, w, V$范围太大,不可能完全采样。为了解决梯度的问题,论文提出了一个外壳 S,是从原填充满的体素(不含部分填充体素)向外拓展$a \cdot \mu$和$\mu$中间所夹的区域(见图 b 中黄色和绿色线所夹区域),而$E_{\mathrm{voxel}}$则定义为面片到夹层距离$E_{\mathrm{voxel}}(\boldsymbol{t}, w, V)=\sum_{F_i\in F}(\mathrm{dist}(F_i)(\boldsymbol{t}, w, V))$,通过这样的方式来细化梯度的求解,使得梯度方向为目标优化方向。而范围问题,论文选择了两阶段法进行考虑——先固定$V$,对于$\boldsymbol{t}, w$的四个自由度进行随机采样,然后固定$\boldsymbol{t}, w$,使用 L-BFGS 方法优化 V。</p><h3 id="工作的效果"><a href="#工作的效果" class="headerlink" title="工作的效果"></a>工作的效果</h3><img src="../images/2022072414.png" style="zoom:50%;"><p>这是论文工作的效果,上方是体素表示,下方是实物展示。此外论文给出一个表格来表现工作的情况(见下表),第三列表示是否为光滑表面(需要使用形状优化算法),第四列为体素化的分辨率,后边的列分别为**孔洞体素(为了游戏块可以移动设计的,后面还会提)**数$E$,谜题块数$K$,难度等级$L$,在第一个贡献中的无向图里计算出来的状态数$G_N$、边数$G_E$、目标状态数$G_T$(目标状态数越多表示解开谜题的方法越多),是否二次修改难度$\mathrm{Modify}$,以及程序计算(不含形状优化)的时间$\mathrm{Time}$。</p><img src="../images/2022072415.png" style="zoom:50%;"><p>此外,论文与 2020 年的工作相比,同样都是对于立方体进行谜题设计,他们就可以生成更高难度的谜题。</p><img src="../images/2022072416.png" style="zoom:50%;"><p>下面的是他们的一些结果,其中左图表示在$K$块、难度为$L$的谜题中,程序计算的时间,右图是实际结果的展示。</p><img src="../images/2022072417.png" style="zoom: 33%;"><p>此外,论文还展示了其中一个边长为 6 的立方体,解开第一块需要 27 步,其中每一步的移动如图所示。</p><img src="../images/2022072418.png" style="zoom: 33%;"><h2 id="尾记"><a href="#尾记" class="headerlink" title="尾记"></a>尾记</h2><p>年初看到学长在讨论班里讲宋老师去年 SIGGRAPH 那篇平衡性的论文,于是这次就用了些时间来跟了一下宋老师的工作。当我运行代码,发现 run 不起来的时候,还给作者陈学长发了一封邮件。其实也是第一次发邮件来请教论文的问题,英文的邮件打磨了好久,最后收到了“加个微信吧,这样交流起来方便”的中文回答,还是感觉学长蛮实在的,非常的意外 2333。</p><p>其实这个工作还是很不错的,美中不足的地方是,论文其实并没有提到如何选择谜题中孔洞体素的选择方法,也没有对孔洞数量对设计难度的影响进行定量的分析。此外论文给出的设计问题的复杂度是$O(K^N\cdot N^{2K})$,也能看出来是个 NP 问题,其中$K$是块数,$N$是实心体素数。这意味着论文给的方法基本上只能解决$K \le 8$且$N \le 1000$的输入,这个输入规模还是非常有限的。</p><p>没想到,从 2012 的那篇 SIG ASIA 看出,宋老师的课题组已经在这个项目耕耘十余年了,对于喜欢新问题的 SIGGRAPH 来说,每篇论文对他们来说都是一个挑战。祝宋老师的课题组做得越来越好,也期待之后能与他们有合作 qwq!</p>

论文翻译——MeshCNN

作者 icy
2022年7月3日 23:00
<h1 id="论文翻译——MeshCNN-A-Network-with-an-Edge"><a href="#论文翻译——MeshCNN-A-Network-with-an-Edge" class="headerlink" title="论文翻译——MeshCNN: A Network with an Edge"></a>论文翻译——MeshCNN: A Network with an Edge</h1><p>这是 CAD 五人组(好吧实际上四个人)完成的论文翻译,原论文 <a href="https://arxiv.org/abs/1809.05910">MeshCNN: A Network with an Edge</a>,感谢 juraws, Gwyn, tk 三位朋友的一同协作。</p><h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>多边形网格为三维形状提供了一种有效的表示方法。它们显式地捕获形状的表面和拓扑情况,并利用网格的非均匀性来表示大形平坦区域以及尖锐、复杂的特征共存的情况。然而,这种不均匀性和不规则性会阻碍使用结合卷积和池化运算的神经网络进行网格分析。在本文中,我们使用 MeshCNN,一个专门为三角形网格设计的卷积神经网络,直接通过网格的独特性质来分析 3D 形状。类似于经典的卷积神经网络,MeshCNN 使用了专门处理网格边的卷积层和池化层,具体将通过利用网格固有的测地线连接来完成。MeshCNN 对边及其 4 条邻边应用卷积,并通过保留表面拓扑的边折叠操作应用池化,从而为后续卷积生成新的网格连接 MeshCNN 学习要折叠哪些边,从而形成一个任务驱动的过程,在这个过程中,网络逐渐暴露并扩展重要的特征,同时丢弃冗余的特征。我们展示了我们的任务驱动池化操作在各种 3D 网格学习任务上应用的有效性。</p><h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>三维形状是计算机图形学领域的前沿和中心,也是计算机视觉和计算几何等相关领域的主要研究点。我们周围的形状,尤其是描述自然实体的形状,通常由连续曲面组成。</p><img src="../images/MeshCNN/fig1.png" style="zoom:67%;"><p>出于计算的原因,同时为了便于数据处理,已经提出了各种三维形状的离散近似方法,并用于在一系列应用中表示形状。多边形网格表示(简称网格)是许多人的最爱,它通过三维空间中的一组二维多边形来近似曲面。网格提供了高效、非均匀的形状表示。一方面,只需要少量的多边形就可以描述形状大而简单的表面,另一方面,网格灵活的表示支持可能需要的高分辨率,以便支持真实感重建及表现在几何上复杂的显著形状特征。网格的另一个显著特征是它可以自然地提供面片之间的接信息。这形成了下垫面的综合表示。</p><p>与另一个流行选项(点云表示)相比,这些优势显而易见。尽管点云表示法简单且与常用的数据采集技术(扫描)直接相关,但当需要更高的质量和保留锐利的形状特征时,点云表示法仍存在不足。</p><p>近年来,在图像上使用卷积神经网络(CNN)在分类和语义分割等多种任务上表现出了优异的性能。他们成功的秘诀在于卷积层、非线性激活函数和池化层的结合,从而形成一个对输入的无关变化保持不变(或鲁棒性)的框架。然而,由于图像是在离散值的规则晶格上表示的,将 CNN 扩展到不规则结构上是不容易的。</p><p>最初的方法通过使用规则的表示,来绕过适配对于不规则数据的 CNN:将 3D 形状映射到多个 2D 投影或 3D 体素晶格。虽然这些方法受益于直接使用理解良好的图像 CNN 操作符,但它们的间接表示需要大量的内存和浪费或冗余的 CNN 计算。</p><p>更有效的方法是直接将 CNN 应用于不规则和稀疏的点云表示。虽然这些方法得益于紧凑的输入表示,但它们本质上对局部曲面不敏感。此外,邻域和连通性的概念定义不明确,使得应用卷积和池化运算变得困难。这种模糊性带来了一系列的工作来克服这一挑战。</p><p>为了激发原生的网格表示的自然潜力,我们提出了 MeshCNN:一种类似于著名 CNN,但专门为网格设计的神经网络。MeshCNN 直接对三角形网格进行操作,执行卷积和池化运算,这些操作与独特的网格属性相协调。在 MeshCNN 中,网格的边类似于图像中的像素,因为它们是应用所有操作的基本对象。我们选择使用边,因为每条边正好与两个面(三角形)关联,这定义了四条边的自然的固定大小的卷积邻域(见图 2)。我们利用一致的表面法线顺序应用非对称卷积运算,学习与位置、旋转、缩放无关的边的特征。</p><img src="../images/MeshCNN/fig2.png" style="zoom: 67%;"><p>MeshCNN 的一个关键特征是独特的池化操作,即网格池化操作,该操作在不规则结构上运行,并在空间上适应 CNN 中的任务,池化减少了网络中特征的数量,从而学会消除信息量较小的特征。由于特征位于边,下采样的直观方法是使用著名的网格简化技术边折叠。但是,与传统的边折叠不同,传统的边折叠会删除引入最小几何变形的边,网格池化将选择要折叠的边以特定于任务的方式交给网络。删除的边是特征对所用目标贡献最小的边(参见图 1 和图 8 中的示例)。</p><p>为了增加灵活性并支持各种可用数据,每个池化层将网格简化为预定的恒定边数。此外,尽管 MeshCNN 生成某个特定数量边的输出,但它对输入网格大小没有限制,也能处理不同的三角剖分。如图 1 所示,中间计算池化步骤以及最终输出在语义上是可解释的。为了说明我们的方法的能力,我们在形状分类和分割任务上进行了各种实验,并在常见数据集和高度非均匀网格上证明了优于最新方法的结果。</p><h2 id="相关工作"><a href="#相关工作" class="headerlink" title="相关工作"></a>相关工作</h2><p>我们在工作中提出或使用的许多算子都基于经典网格处理技术,或者更具体地说,基于网格简化技术。特别是,我们使用用于任务驱动池化算子的边折叠技术。不同于经典的网格简化技术旨在以最小的几何失真减少网格元素的数量,在这项工作中,我们使用网格简化技术来降低神经网络背景下特征图的分辨率。</p><p>在下文中,我们将回顾使用神经网络进行 3D 数据分析的相关工作,这些神经网络按照输入表示类型进行组织。</p><p>我们在工作中展示或使用的许多操作都是基于经典的网格处理技术[Hoppe 于 1999 年提出;Rusinkiewicz 和 Levoy 等人于 2000 年提出;Botsch 等人于 2010 提出;Kalogerakis 等人于 2010 提出],更具体地说,网格简化技术[Hoppe 等人于 1993 提出;land 和 Heckbert 等人于 1997 年提出;Hoppe 于 1997 年提出]。特别地,我们在我们的任务驱动池化操作符中使用了边折叠技术[Hoppe 于 1997 年提出]。而经典的网格简化技术旨在以最小的几何变形减少网格元素的数量[Tarini 等人于 2010 年提出;Gao 等人于 2017 年提出],在这项工作中,我们使用网格简化技术来降低神经网络背景下特征映射的分辨率。在接下来的文章中,我们将回顾使用根据输入表示类型组织的神经网络进行 3D 数据分析的相关工作。</p><p><strong>多视角 2D 投影</strong>。通过不同视角的 2D 投影来表示 3D 形状,使得利用 2D 领域的现有技术和架构成为可能。这些渲染后的图像作为标准 CNN 模型后续处理的输入。Su 等人[2015]是第一个应用多视图卷积神经网络进行形状分类的,但是这种方法不能很好地进行分割任务。后来,Kalogerakis 等人在 2017 年提出了一种更全面的多视图框架用于形状分割:生成每个视图的图像级分割地图,然后使用 CRF(端到端训练)方式解决标签一致性问题。Qi 等人[2016]探索了基于视图的方法和基于体积的方法,并观察到与当时可用的方法相比,第一种方法存在优越性。</p><p><strong>体素</strong>。将 3D 形状转换为二进制体素形式提供了类似于图像的 2D 晶格的基于晶格的表示。因此,应用于 2D 晶格的操作可以以一种直接的方式扩展到 3D 晶格,从而允许常见的基于图像的方法自然转移到 3D。Wu 等人[2015]率先提出了这一概念,并提出了一种处理体素化形状进行分类和补全的卷积神经网络。随后,Brock 等人[2016]使用基于体素的变分自编码器进行形状重建,[Tchapmi 等人于 2017 提出]将三线性插值和条件随机场(CRF)与体积网络相结合,以促进语义形状分割。Hanocka 等人[2018]使用体积形状表示训练网络回归基于晶格的翘曲场以进行形状对齐,并将估计的变形应用于原始晶格。</p><p>尽管它们的表示非常简单,但体积表示需要高要求的计算,以及大量内存使用。为了缓解这一问题,人们提出了几种加速策略,其中包含利用实心体素的稀疏性来减少特征的策略。</p><p><strong>图</strong>。允许基于晶格的不规则表示的一个常见表达即为图结构。为了支持基于图的数据分析,神经网络在涉及以图表示的数据的热门任务中的应用受到了相当大的关注,尤其是在社交网络、通信中的传感器网络或遗传数据。一种方法提倡处理拉普拉斯图表示 [Bruna 等人于 2014 年提出; Henaff 等人于 2015 年提出; Defferrard 等人于 2016 年提出; Kostrikov 等人于 2018 年提出],因此在频域中起作用。另一种方法选择直接处理图,通过提取局部连接区域,并将其转换为标准形式,Niepert 等人在 2016 提出由神经网络处理。Atwood 等人[2016]提出扩散-卷积,在每个节点上应用扩散来确定其局部邻域。Monti 等人[2017]利用图空间域将曲面参数化为局部斑块。Xu 等人[2017]使用曲面斑块的方向卷积进行语义分割。Yi 等人[2017]在三维分割任务中使用频域的图卷积。Kostrikov 等人[2018]使用拉普拉斯曲面网络开发三维形状的生成模型。Such 等人[2017]引入了图上顶点过滤的概念,但没有纳入用于特征聚合的池化操作,这些方法通常是对图的顶点进行操作。</p><p><strong>流形簇</strong>。Masci 等人[2015]的开创性工作引入了网格上局部特征的深度学习(与[Kokkinos 等人 2012]中的内固有网格描述符类似),并展示了如何进行深度学习,使用这些学习到的特征进行通信和检索。具体来说,它们演示了如何使卷积操作成为网格固有的操作。</p><p>通常,流形簇上的局部块近似满足欧几里得性质。通过将三维流形参数化至二维,这一特性可以用于使用标准神经网络卷积进行流形分析 [Henaff 等人于 2015 年提出; Boscaini 等人于 2016 年提出; Sinha 等人于 2016 年提出;Maron 等人于 2017 年提出; Ezuz 等人于 2017 年提出]。如 Boscaini 等人在[2015]提出的那样使用顶点频率分析来学习局部固有三维形状描述符。另一种方法使用环面拓扑来定义形状图上的卷积 [Haim 等人于 2018 年提出; Maron 等人于 2017 年提出]。Poulenard 等人在 2018 年定义了一种新的卷积层,允许在网络的各个层传播测地线信息。</p><p>Verma 等人在 2018 年提出了一种图神经网络,其中卷积操作的每个顶点的邻域不是预定义,而是基于其特征动态计算的。Tatarchenko 等人[2018]引入了切线卷积,在每个点周围使用一个小的邻域来重建应用卷积的局部函数。与之前的作品不同,他们通过在 3D 晶格上进行子采样来合并池化操作。一些生成模型也被提出。Litany 等人[2018]介绍了一种执行形状补全的自动编码器。Ranjan 等人在 2018 演示了如何通过网格自动编码器生成 3D 人脸。</p><p>查阅[Bronstein 等人于 2017 年提出]关于几何深度学习的综述。与之前的方法相比,我们方法的独特性在于,我们的网络操作经过了特殊的设计,能适应网格结构。特别地,我们学习了一个唯一的池化操作符,它根据目标任务选择要简化的区域。</p><p>据我们所知,这是第一个同时满足(i)在网格的边上进行卷积和(ii)一个经过学习以适应手头的任务的网格池化操作。在[Ranjan 等人 2018]中,为网格自动编码器提出了一种固定的池化操作。学习池化操作已经在图神经网络的背景下被提出[Ying 等人 2018;Cangea 等人 2018]。然而,这些操作没有考虑到三角形网格的独特性质。</p><p>有人提出了一种利用对偶图卷积模型来提取边特征的卷积[Monti 等人在 2018 年提出],该模型扩展了图关注网络[Velickovic 等人在 2018 年提出]。然而,他们在工作中所使用的注意力和优化机制与我们截然不同;在这项工作中,我们定义了运算符的网格,利用其独特的结构和性质。这使得我们可以定义一个对称卷积,从而得到不变的网络特征。</p><p>**点云:**点云表示法可以说是所有 3D 数据表示法中最简单的一种,它为底层的 3D 形状提供了一个简单的近似值。它能与数据获取密切关系并能与其他表示法进行轻松转换,这使得点云表示法成为数据分析的经典候选。因此,近期研究方向集中于开发使用神经网络分析点云形状的技术。如 PointNet[Qi 等人在 2017 年提出]建议使用 1x1 卷积,然后通过全局最大池化以实现顺序不变。在其基础上又研究了 PointNet++ [Qi 等人在 2017 年提出],以划分点集的方式更好地捕获局部结构特征。[Wang 等人在 2018 年提出]考虑局部点邻域信息,基于特征空间中点的距离进行相似性计算驱动的动态更新。虽然大多数基于点的方法关注全局或中间属性,[Guerrero 等人于 2018 年]提出了一个估计局部形状属性的网络,其关注点在于原始点云的法线和曲率;Williams 等人[2018]从点学习从表面重建的几何先验;而 Atzmon 等人[2018]通过将点云函数映射为体积函数,定义了一种有效的点云卷积算子。这使得该方法对于点的顺序来说具有不变性,对数据中的一些变形具有鲁棒性。最近,Li 等人[2018]提出了 PointCNN,它将卷积的概念从局部晶格扩展到欧几里得领域中驻留点的χ-卷积。</p><p>与以往的研究不同,在这项工作中,我们依靠多边形网格边来提供非均匀的测地线邻域信息和拥有统一数量的卷积邻域。在多边形网格边上执行特征不变计算,并应用网格精简技术,如符合形状几何及拓扑结构进行抽取的边折叠技术。</p><h2 id="概述:将卷积操作应用到网格上"><a href="#概述:将卷积操作应用到网格上" class="headerlink" title="概述:将卷积操作应用到网格上"></a>概述:将卷积操作应用到网格上</h2><p>计算机图形学中最基本、最常用的 3D 数据表达方式为非均匀多边形网格,这种表达方式在大的较为平坦的区域采用少量大型多边形,而在需要体现细节的区域采用大量多边形。一个非均匀多边形网格能很好地体现物体曲面的拓扑结构,如图 3 所示,它在完美体现结构细节的同时又能够消除邻域曲面的歧义。</p><img src="../images/MeshCNN/fig3.png" style="zoom:50%;"><p>为了实现将卷积操作应用到三角形网格的目标,需要设计一种类似于传统卷积定义与实现的标准化构建模块:卷积层和池化层。对比于在晶格上用离散值表示的图像,非均匀多边形网格分析上的关键挑战为其固有的不规则性和不均匀性。在这项工作中,我们的目标是运用这些具有挑战性的独特性质,而不是绕过它们。出于上述原因,我们在设计网络时有意直接在非均匀多边形网格上应用卷积和池化操作,从而避免将非均匀多边形网络转化为规则统一的表示。</p><p><strong>卷积不变性:</strong> 我们假设,所有的图形都被表示为了可能有边界的非均匀多边形网格。这样的假设保证了每条边与 1 到 2 个三角形面相连,因此每条边会与 2 或 4 条边相邻,每个三角形面的顶点以逆时针的顺序排列,则对于每条边的 4 条邻边存在 2 种可能的排序,例如在图 4 中,边 e 的 4 条邻边就可以被排成(a, b, c, d) 或者 (c, d, a, b) , 这种存在歧义性的表示方法对卷积不变性造成了阻碍。</p><img src="../images/MeshCNN/fig4.png" style="zoom: 67%;"><p>为了保证网格内相似性变换(平移、旋转、缩放)对于网络的卷积不变性,我们采取了两种行动来解决这个问题。首先,我们仔细设计了基于边的输入描述,使得它只包含对相似变换具有不变性的相对的几何特征。其次,我们将 4 条邻边分成 2 对具有模糊性特征的边对(如图 5 中的 a 与 c、b 与 d),并且在新的边对基础上应用简单的具有对称性的变换函数(如求和操作)生成新的特征。卷积操作将应用在新生成的特征上以消除输入顺序带来的歧义。</p><img src="../images/MeshCNN/fig5.png" style="zoom: 67%;"><p><strong>输入特征</strong>:每条边的输入特征在形式上是一个 5 维向量:二面角、2 个内角和 2 个三角形面各自的边比值。其中每个三角形面的边比值为中间边边长与每个三角形面垂线(图中虚线)的比值。我们将这些特征按一定顺序(每个面的内角值紧接着边比值)排列来消除歧义从而保证卷积不变性。由于这些特征都是相对的,对网格作相似变换如旋转、等比缩放等操作并不会影响到它们的不变性。</p><p><strong>全局顺序</strong>:边的全局顺序是特定形状的边的数据进入网络的顺序。由于卷积操作是在局部邻域内完成的,上述顺序对卷积不会造成影响。更一般地,需要完整卷积的任务例如分割任务也不会受此影响。对于需要全局特征聚合的任务,例如分类任务,我们按照 Qi 等人在 PointNet 中建议的那样,在网络中的卷积层和全连接层中间添加一层全局平均池化层,这一层使得初始输入顺序与特征聚合无关从而保证了变换的不变性。</p><p><strong>池化</strong>:网格的池化由边折叠完成,如图 2 中的(b)和(c)的说明,在(b)中虚线的边被折叠成了一个点,随后,4 条蓝色的边被折叠成了(c)中 2 条蓝色的边,注意在边折叠操作中,原来的 5 条边变换成了 2 条边。操作按边的特征优先级(最小范数)进行排序,从而允许网络选择哪部分网格简化、哪部分保留,这创建了一个任务感知过程,在此过程中,网络可以通过学习确定每部分物体对于任务的重要性(如图 1)。</p><p>这种简化的一个天然而显著的优点在于它能够在到达最终的全连接层之前,为池化层提供输出维度上的灵活性。池化操作也有助于对初始三角形网格的稳定性作贡献。虽然它并不提供三角形的不变性,但是在不同初始条件下,通过不断地对边进行折叠和简化,我们最终总能观察到网络收敛到相似的表达。</p><h2 id="具体方法"><a href="#具体方法" class="headerlink" title="具体方法"></a>具体方法</h2><p>基于晶格的表达(如传统图片)可以通过一个单一矩阵提供邻域的信息以及本身特征的信息。然而,网格不符合这样的格式,我们必须将其本身的特征和它的连通性分开定义。我们将通过使用标准的网格结构来完成上述工作。</p><p>一个网格由一组($V$, $F$)定义,其中$V= { \mathbf{v}_1, \mathbf{v}_2 \cdots }$为三维立体空间中的点集,$F$通过给出三角形网格面的三元组定义了连通性。在给出($V$, $F$)后,网格的连通性同时采用一组两两连接的点对形成的边集合$F$来定义。</p><p>所有的网格元素$V$, $F$, $E$可以被关联到多种多样的特征(例如法线或颜色)。在上述工作中,$E$同样包含了大量的特征信息。边的特征刚开始为一组相似但独立的几何特征(与图片的 RGB 值类似),它们通过网络层后将变得更加抽象。</p><p>在我们的设定中,不规则多边形网格为网络提供了两个特征:相邻卷积元素的连通性以及初始的几何输入特征。当这些输入特征被提取后,网格顶点的具体位置就失去了意义。随着边折叠操作而产生的新顶点的位置对分类和分割任务没有任何影响,计算它们只是为了满足可视化的需求。</p><p>在接下来的内容中,我们将进一步提供关于网格卷积、网格池化和网格反池化操作的细节。</p><h3 id="网格卷积"><a href="#网格卷积" class="headerlink" title="网格卷积"></a>网格卷积</h3><p>我们为边定义卷积操作,其中每条边的空间信息是通过它的 4 条邻边定义的(如图 3 所示)。回想一下,传统卷积是卷积核 k 与一个邻域的点积,类似地,网格卷积基于一条边 e 以及它的 4 条邻边的操作为:</p><p>$$\begin{equation}<br> e \cdot k_0 + \sum\limits_{j=1}^{4} k_j \cdot e^j,<br>\end{equation}$$</p><p>其中$e^j$即为$e$的第$j$条卷积邻边。注意如图 4 中展示的那样,$e$的 4 条邻边的顺序,即!,$(e^1,e^2,e^3,e^4)$不是就$(a,b,c,d)$是$(c,d,a,b)$, 这样会使得每个滤值的操作对象可能对应到最多 2 条不同的边上(如$k_1$的操作对象可能为$a$或$c$)。为了保证卷积对于输入数据不同顺序的不变性,我们将在存在歧义的边对上应用简单对称函数。上述操作产生了一组能够保证卷积不变性的新的卷积邻域,在我们的设定中,边$e$的邻域为:</p><p>$$\begin{equation}<br> (e^1,e^2,e^3,e^4) = (|a-c|, a+c, |b-d|, b+d).<br>\end{equation}$$</p><p>显然,不论初始的网格元素的输入顺序是什么,卷积操作都会给出相同的输出。回想一下,在传统卷积中多通道张量与一个卷积核的操作可以通过将图片数据展成列矩阵然后执行一般的矩阵乘法来实现。类似地,我们可以为网格卷积构造一个展开矩阵来提高卷积操作的效率。</p><p>在实际应用中,我们可以通过将所有边特征聚合成$n_c \times n_e \times 5$格式的特征张量以使用高度优化的批处理操作(如二维卷积),其中是$n_e$边的数量,$n_c$是特征通道的数量,$5$是包含边$e$本身以及它的卷积邻域特征值信息(见式 2)的数量。这个矩阵以标准一般矩阵乘法的方式乘以权重矩阵。</p><p>在卷积操作之后,一个新的批处理特征张量将被生成,其中新的特征数量等于卷积核的数量(类似在传统图像上的卷积)。注意在每个池化阶段之后,下一次卷积操作的卷积邻域是在新的连接基础上定义的。</p><h3 id="网格池化"><a href="#网格池化" class="headerlink" title="网格池化"></a>网格池化</h3><p>为了将池化操作扩展到不规则的数据上,我们需要明确池化操作的三个基本步骤:</p><ol><li>定义池化邻域;</li><li>合并每个池化邻域的特征信息;</li><li>重新定义合并特征后的池化邻域。</li></ol><p>对于传统图片的池化来说,邻域是暗含的,因此每个池化邻域在确定了卷积核的大小后就被直接确定了。因为不同区域内的特征被合并(如通过平均或取最大值的方式合并)会产生另一个新的晶格,这种方式同时暗含确定了新的邻域。结合上文定义的池化操作的三个基本步骤,传统图片池化操作显然是广义池化操作的一种特殊情况。</p><p>网格池化是广义池化的另一种特殊情况,其连通性由拓扑序决定。不像图片池化具有天然的简化换算方式,例如需要简化系数为 4 自然对应$2 \times 2$池化,我们将网格的池化定义为一系列的边折叠操作,在每个边折叠操作中我们将 5 条边转换为 2 条边。因此,我们可以添加一个超参数,表示池化后网格边的数量,从而在每个网格池化操作之后控制所需的网格分辨率。在实际运行时,提取网格邻接信息需要不断查询连续更新的特殊数据结构。</p><p>我们将根据边特征信息的重要性为边折叠顺序定义优先级以帮助网络确定哪部分网格与解决任务的关联性更强(采用优先队列)。这使得网络可以不均匀地折叠对损失影响最小的特定区域。回想一下前面定义的折叠两个相邻面中间的边的操作,由于该操作将每个面都变成了一条边,从结果上来看是删除 3 条边(见图 2)。每一个三角形网格面含有 3 条边:两个面相交的中间边以及 2 条中间边的邻边(如图 2,中间边被标成了红色,它的邻边被标成了蓝色)。三条边中的所有特征都以均值的方式被合并到了新边的特征通道中。</p><p>根据边特征的强度对边折叠顺序进行优先级排序,并将其作为边折叠的 L2 范式。特征的聚合过程在图 5 中有详细解释,这里有两个合并操作,对于每个输入的三角形网格表面的边特征,操作的结果为两个新的特征向量(用 p 和 q 表示)。更形式化地,第 i 个通道中两个三角形网格面的边特征可以表示为:</p><p>$$\begin{equation}<br> p_i = avg (a_i, b_i, e_i),<br> \textrm{ and, }<br> q_i = avg (c_i, d_i, e_i),<br>\end{equation}$$</p><p>在一系列边折叠操作的同时,数据结构也随之更新。</p><p>最后需要注意的是,不是每条边都可以被折叠。在我们的设定中,一条处于非流形网格面的边是不允许被折叠的,因为这会违反每条边有 4 条邻边的前提假设。因此,一条边如果含有 3 条邻边或者它的 2 个顶点都是边界顶点,则不允许被折叠。</p><h3 id="网格上池化"><a href="#网格上池化" class="headerlink" title="网格上池化"></a>网格上池化</h3><p>上池化是池化的(部分)逆操作。池化层降低了特征激活的分辨率(对信息进行编码或压缩),而上池化层增加了特征激活的分辨率(对信息进行解码或解压)。池化操作记录了每次历史合并操作(例如最大位置),并使用它们来扩展特征激活。因此,单独的上池化操作不具有可学习参数,它通常与卷积操作结合起来以解析恢复在池化操作中丢失的原始特征信息。与卷积操作的有效结合使上池化成为一种可学习的操作。</p><p>每个网格上池化层都匹配有一个网格池化层以上采样网格拓扑和边的特征。上池化层通过存储池化操作之前的连接来恢复上采样拓扑(在网格池化之前)。注意,对连接进行上采样是一个可逆的操作(就像在传统图像上那样)。对于上池化中的边特征计算,我们保留一个存储了从原始边(池化前)到新边(池化后)的邻接表。每个上池化边的特征都是池化边特征的加权组合。图 5 即为一个平均上池化操作的例子。</p><h2 id="实验"><a href="#实验" class="headerlink" title="实验"></a>实验</h2><p>MeshCNN 是一种在三角网格上直接应用卷积的方法,它具有许多应用。结合在第 4 节中提到的 MeshCNN 标准化构建模块,我们可以构建不同的网络以解决不同的任务。与传统的卷积神经网络一样,这些构建模块提供了即插即用的框架。为了提高计算效率,在池化操作中,每个池化操作只对特征进行一次聚合。而边的排序和折叠是按顺序进行的,这种松弛使得可以在 GPU 上进行特征聚合操作,从而提高计算效率。</p><p>接下来,我们将展示 MeshCNN 在分类和分割任务上的表现。所使用的网络架构细节将在附录 A 中给出。</p><h3 id="数据处理"><a href="#数据处理" class="headerlink" title="数据处理"></a>数据处理</h3><p>在所有集合中,我们将每个网格简化为数量大致相同的边。注意,如前所述,MeshCNN 并不要求输入的边数相同。然而,与卷积神经网络中调整图像初始大小的原因类似,几何网格抽取有助于降低输入分辨率,从而降低训练网络所需的内存容量。由于分类任务是对全局形状描述进行学习,相比于分割任务(2250 条边左右),分类任务通常使用较低的分辨率(750 条边左右)。</p><p><strong>数据扩充</strong>。为了为网络生成更多的数据样本,存在几种数据扩充方式。注意,由于我们的输入特征具有相似不变性,应用旋转、平移和各向同性缩放($x$, $y$,$z$轴相同)不会产生新的输入特征。然而,我们可以通过对$x$, $y$和$z$中的顶点位置$&lt;S_x,S_y,S_z&gt;$应用各向异性缩放(使得每一个值都来自于服从$\mu=1$和 $\sigma=0.1$正态分布的随机抽样)来生成新的特征;我们可以将顶点转移到同一个网格曲面的不同位置;我们还可以通过执行随机的边翻转来增加每个对象的细分。另外,由于输入分辨率非常灵活,我们可以在训练前随机折叠数量不多的一组边。</p><h3 id="网格的分类"><a href="#网格的分类" class="headerlink" title="网格的分类"></a>网格的分类</h3><p><strong>SHREC</strong>:我们对 SHREC 数据集中的$30$个类进行了分类[Lian 等人在 2011 年提出],每个类有$20$个数据。我们参照[Ezuz 等人在 2017 年提出]中的设置方式,其中分为两组,训练测试数据比为$1:1$(Split $10$)和$4:1$(Split $16$)。我们在$200$个 epoch 后停止训练。因为我们没有像[2017]中使用精确分割,我们在$3$个随机生成的$4:1$和 1:1 组取平均结果,结果见表 1。为了进行比较,我们直接从[2017]获得其他结构的评估结果,与 SG [Bronstein 等人在 2011 年提出] (bof 表示)、SN [Wu 等人在 2015 年提出] (体积 CNN)、GI [Sinha 等人在 2016 提出] (固定几何图像上的 CNN)和 GWCNN [2017](学习几何图像的 CNN)进行比较。我们的方法的优点是显而易见的。我们在图 6 中可视化了这个数据集的网格池化简化示例。我们观察到网格池化的表现具有一致的语义方式(参见图 11)。</p><img src="../images/MeshCNN/table1.png" style="zoom: 67%;"><img src="../images/MeshCNN/fig6.png" style="zoom: 67%;"><img src="../images/MeshCNN/fig11.png" style="zoom: 50%;"><p><strong>Cube 数据集</strong>。为了说明 MeshCNN 的独特功能,我们建了一组图标浅雕刻的立方体模型(见图 7)。我们使用 MPEG-7 二进制形状[Latecki 和 Lakamper 2000]数据集中的 23 个类,每个类大约有$20$个图标。我们每类为测试集各留出三个图标,其余的用于训练。对于每个图标,我们随机抽取$10$个不同的位置(坐标,旋转及立方体的某个面)来雕刻图标。每个立方体大约有$500$个面片,这意味着精细模型在平面区域中有更少的三角形,而不精细的模型在平面区域中有更多的三角形。这个集合包含了总共$4600$个模型,其中训练集$3910$个,测试集$690$个。我们计划在论文发表之后公开这个数据集以及数据生成代码。</p><img src="../images/MeshCNN/fig7.png" style="zoom: 67%;"><p>我们训练 MeshCNN 对立方体进行分类。我们在表 2 中展示了定量结果。为了可视化网格池化对分类任务的影响,我们提取了每次网格池化操作后的中间结果(如图 8 所示)。观察 MeshCNN 如何学会减少与分类任务无关的边(平面立方体表面),同时保留图标雕刻内部和周围的边。</p><p>我们还在这个数据集上训练了基于点的方法,结果如表 2 所示。虽然该例会被认为是不自然的,这是为了强调 MeshCNN 擅长在几何分辨率方差较大的 3D 模型集合。</p><img src="../images/MeshCNN/table2.png" style="zoom: 67%;"><img src="../images/MeshCNN/fig8.png" style="zoom: 50%;"><h3 id="网格分割"><a href="#网格分割" class="headerlink" title="网格分割"></a>网格分割</h3><p>MeshCNN 的另一个应用是一致的形状分割,这是形状分析和合成中许多应用的一个重要构件。我们使用监督学习来训练 MeshCNN,以预测每条边属于 COSEG 和人体分割数据集上的特定段的概率。由于这两个数据集都提供了每个表面的真实分割,我们根据原始分辨率的标签在简化的网格上生成边级的语义标签。</p><p>最直接的 MeshCNN 语义分割配置是使用一连串的网格卷积层(以及归一化和非线性激活函数)。然而,加入网格池化使 MeshCNN 能够学习分割驱动的边折叠。考虑到网格池降低了输入网格的分辨率,这不再符合边级别的真实标签。为此,我们使用网格上池化层上采样将分辨率提高到原来的输入尺寸。</p><p><strong>COSEG</strong>。我们评估了 MeshCNN 在 COSEG 数据集的分割任务上的表现。该数据集包含三个大型集合: 外星人,花瓶和椅子,各包含$200$、$300$和$400$个模型。我们将每个形状类别分成$85 % / 15 %$的训练集/测试集。我们与 PointNet、PointNet++和 PointCNN 进行了比较。在表 3 中报告了所有方法的最佳准确性。我们的技术 在这个数据集上取得了比所有其他方法更好的结果。</p><img src="../images/MeshCNN/table3.png" style="zoom: 67%;"><p>我们认为,这是因为我们的网络是根据网格结构定制的,这使它比其他策略更有优势。为了进一步证明这一点,我们还报告了随机集合(随机抽取塌陷边)并且表明这一变化降低了网络的性能。此外,在一个测试集中使用池化和上池化层的 MeshCNN 语义分割网络最终的分割预测如图 9 所示。这也体现了所进行的池化是如何适应目标问题的。</p><img src="../images/MeshCNN/fig9.png" style="zoom: 67%;"><p>**人体分割。**我们在[Maron 等人]提出的人体分割数据集上评估了我们的方法。该数据集包括来自 SCAPE[Anguelov 等人]、FAUST[Bogo 等人]、MIT[Vlasic 等人]和 Adobe Fuse[Adobe ]的$370$个训练模型,测试集是来自 SHREC07[Giorgi 等人](人类)数据集的$18$个模型。根据[Kalogerakis 等人,2010]中的标签,这些模型被手动分割成八个标签。最近,[Poulenard 和 Ovsjanikov ]报告了他们的方法在这个数据集上的结果,并与之进行了比较 (GCNN [Masci 等人于 2015 年提出]、PointNet++ [Qi 等人,2017b]、Dynamic 图 CNN[Wang 等人,2018b]和 Toric Cover[Meron 等人,2017]。 我们直接从[Poulenard 和 Ovsjanikov 2018]获得结果,并将其列于表 4。我们在表中加入了最近由 Haim 等人[2018]报告的最先进的结果。在这种情况下,MeshCNN 也比其他的方法(部分是基于图/多方面的,部分是基于点的)有优势,我们认为这是由于 MeshCNN 对网格结构及任务的适应性。图 10 展示了 一些 MeshCNN 的定性结果。</p><img src="../images/MeshCNN/fig10.png" style="zoom: 67%;"><img src="../images/MeshCNN/table4.png" style="zoom: 67%;"><h3 id="附加评估"><a href="#附加评估" class="headerlink" title="附加评估"></a>附加评估</h3><p>**计算时间:**当使用 GTX $1080$ Ti 显卡对$2250 / 750$个边进行分割/分类训练时,我们的非优化 PyTorch[Paszke 等人,2017]实现每个示例平均需要$0.21 / 0.13$秒。</p><p>**曲面细分的鲁棒性:**我们使用 COSEG 分割数据集,通过几个定性和定量实验,检验了我们的方法对三角剖分差异的鲁棒性。为此,我们生成了数据集的两个修改版本。第一种是通过应用重新网格化程序(使用 Blender 和 MeshLab)获得的,第二种是通过随机扰动$30%$的顶点位置获得的,使得这些点及所有与这些点直接相邻的顶点随机增加向量。 性能上的微小差异(见表 5)意味着对细分变化的弹性。定性结果见图 12。</p><img src="../images/MeshCNN/fig12.png" style="zoom: 67%;"><img src="../images/MeshCNN/table5.png" style="zoom: 67%;"><p>**不变特征:**使用相对特征的一个值得注意的优点是,MeshCNN 保证对旋转、平移和均匀缩放保持不变。从本质上讲,常用的笛卡尔坐标对刚性变换很敏感。为了说明这一点,我们对 MeshCNN 进行了语义分割训练:(i)使用不变几何特征,(ii)使用边的中点$(x,y,z)$作为输入特征。为了评估学习泛化,我们沿纵轴应用非等比缩放(无需对这些类型的增强进行训练)。我们的相对几何特征达到$98.44%$,而标准测试集为$99.63%$,而绝对坐标下降到$78.27%$,而标准测试集为$99.11%$。请注意,虽然我们的几何特征对非均匀缩放不是不变性的,但由于它们对定位不敏感,因此可以更好地概括。</p><img src="../images/MeshCNN/fig13.png" style="zoom: 67%;"><h2 id="讨论和未来的工作"><a href="#讨论和未来的工作" class="headerlink" title="讨论和未来的工作"></a>讨论和未来的工作</h2><p>我们提出了 MeshCNN,这是一种在不规则三角形网格上直接使用神经网络的通用方法。我们工作的主要贡献是针对不规则和非均匀结构定义和应用卷积和池化操作。这些操作有助于直接分析那些原始表现形式为网格的形状,也因此受益于具有非均匀结构的曲面流形表示的独特属性。</p><p>**不变卷积:**选择网格边作为网络运行的基本构建块是非常重要的,因为边集有一种简单的方法,来定义局部固定大小的邻域,以便在不规则结构上进行卷积。通过利用三角形网格特有的唯一对称性,我们消除了邻域排序对偶性的歧义,以实现对变换的不变性。我们通过选择输入边特征来完成这一点。这些特征经过精心设计,仅包含相对几何特性,而不是绝对位置。因此,与常见表示(例如基于点的表示)不同,顶点的笛卡尔坐标被忽略,局部和非局部特征不受位置影响,从而更好地概括形状特征,并促进对相似性变换的不变性。我们强调,我们仅使用顶点位置来显示演化网格,但它们的位置对任务没有影响。</p><p>**空间适应池化:**我们开发了一种通过边折叠执行的池化操作,该操作基于学习过的边特征,导致任务驱动池化由网络损失函数引导。在未来,我们希望添加一组专用的单独功能,用于优先处理边折叠,类似于注意力模型。将网络确定的一系列特征可视化是很重要的,这导向了对网络实际学习内容的深刻见解。我们观察到,与使用绝对笛卡尔坐标相比,我们的微分特征不仅提供了对相似变换的不变性,而且还抑制了过度拟合。通过在不同对象之间执行语义相似的池化的能力,网络的泛化功能被进一步证明,这自然会产生更好的结果。研究这种强大的机制可以更好地理解神经网络的行为。</p><p>我们认为这种空间自适应的不规则任务驱动池化是一种重要的贡献,可能还会影响许多基于图像的 CNN 任务。例如,高分辨率图像分割通常会生成低分辨率的分割图并对其进行上采样,这一过程可能会跳过连接。MeshCNN 中的池化在语义上简化了具有统一特征的区域,同时保留了复杂的区域;因此,在未来,我们有兴趣将类似的不规则池化应用于图像分割任务,以获得高分辨率的分割图,其中图像中的大的均匀区域将由少量三角形表示。</p><p>目前,我们的实现执行顺序边折叠。通过对边特征使用并行排序技术(每个池化操作仅计算一次),并确保仅同时折叠非相邻边,可以在 GPU 上并行化此操作。显然,以这种非顺序方式进行池化的特征可能与顺序方式不同。</p><p>尽管我们的方法对不同的三角剖分具有鲁棒性(如我们的实验所示),但与任何其他网络一样,MeshCNN 依靠良好的训练数据进行成功的泛化。从这个意义上讲,与图像中的对抗性噪声非常相似,MeshCNN 容易受到可能影响性能的对抗性的网格重划分攻击。因此,对此类对抗性攻击的鲁棒性是未来工作的一个有趣方向。</p><p>未来研究的另一个途径是生成建模、网格上采样和属性合成,以修改现有网格。我们的想法是通过记录边折叠列表,以与边折叠操作相反的顺序应用顶点分割。这类似于用于反池化层的记录。因此,在合成新网格时,网络决定分割哪个顶点,例如,分割与具有高特征值的边相邻的顶点。</p><p>最后,我们发现了一个很有希望的尝试,即将我们针对三角形网格设计的策略扩展到一般图。基于边折叠的池化和上池化可以以与我们提出的网格连接神经网络类似的方式应用于一般图。至于卷积,我们必须考虑一种适用于一般图的不规则性的适当替代方法。一种有趣的方法可能是使用注意力机制来处理边。</p><h2 id="训练配置"><a href="#训练配置" class="headerlink" title="训练配置"></a>训练配置</h2><p>对于分类,我们对 SHREC 和 Cube engraving 数据集使用相同的网络架构。我们在表 6 中详细介绍了网络配置和学习参数。对于分割任务,对于 COSEG 和人体数据集,我们使用 U-Net 类型的网络。表 7 提供了该网络的详细信息。</p><img src="../images/MeshCNN/table6.png" style="zoom: 67%;"><img src="../images/MeshCNN/table7.png" style="zoom: 67%;">

英文学术论文写作指南

作者 icy
2022年7月3日 23:00
<h1 id="英文学术论文写作指南"><a href="#英文学术论文写作指南" class="headerlink" title="英文学术论文写作指南"></a>英文学术论文写作指南</h1><p>本文章出自 CCF 学生领航计划 (CCF SPP),讲义和回放都挂在 CCF 的电子图书馆,这里放一个传送门,方便大家前去了解(第一周限免,第二周起需要 CCF 学生会员)。</p><blockquote><p>第一期:学术研究与论文写作 <a href="https://dl.ccf.org.cn/ppt/pptDetail.html?_ack=1&amp;id=6015114958456832">讲义地址</a> <a href="https://dl.ccf.org.cn/video/videoDetail.html?_ack=1&amp;id=6015101387786240">视频地址</a></p><p>第二期:英文学术论文写作规范与日常积累 <a href="https://dl.ccf.org.cn/ppt/pptDetail.html?_ack=1&amp;id=6025045838252032">讲义地址</a> <a href="https://dl.ccf.org.cn/video/videoDetail.html?_ack=1&amp;id=6024786148001792">视频地址</a></p><p>讲者于静老师,b 站帐号<a href="https://space.bilibili.com/301285406">于老师的日常</a></p></blockquote><p>学术研究过程与论文写作过程是一致的,从确定研究领域,找到研究动机,提出问题,设计方法,实验验证最后总结展望,也是我们论文 introduction related work 到 methodology 和 experiments 最后 conclusion 的一个流程。</p><p>目前新手上路完成论文存在以下几个问题</p><ul><li>学生什么都不懂,希望老师一步一步教;老师希望学生自己完成论文,自己只做点拨(甚至带带老师)</li><li>选择了很好的主题,得到了或许很好的结果,但是由于论文写作水平的原因,自己所写并不能写出整个论文的水平</li><li>论文无法准确表达研究的内容,把文章逻辑和自己的贡献讲清楚</li><li>论文在 abstract 把贡献写的很大,但根据后面实验的结果来看难以自圆其说,应该在写论文之前把所有的内容有个把握</li><li>持续拖延:写论文与科研应该是协同的,可能重要性占 30-40%,不应该拖来拖去</li><li>不知道论文如何逐步完善</li></ul><p>科研论文写作四步走</p><ol><li>价值观: 为何做科研?为何选这个问题?(第一期 Why?)</li><li>思路: 写哪些?什么逻辑?什么内容?(第一期 What? )</li><li>写法: 怎么写?怎么改?怎么高效?(第二期 How?)</li><li>规范: 英文怎么写规范、简洁、清晰?(第二期 How?)</li></ol><h2 id="研究动机——是否探究本质"><a href="#研究动机——是否探究本质" class="headerlink" title="研究动机——是否探究本质"></a>研究动机——是否探究本质</h2><table><thead><tr><th>CCF-A</th><th>CCF-C</th></tr></thead><tbody><tr><td>问题-方法-实验,相互呼应</td><td>问题-方法-实验,各为其说</td></tr><tr><td>问题:有理有据,足够具体</td><td>问题:大家都在研究,所以我研究</td></tr><tr><td>方法:针对问题设计,每一步设计目标明确</td><td>方法:step1-&gt;step2-&gt;step3</td></tr><tr><td>实验:针对方法逐一证明,针对动机逐一分析</td><td>实验:达到了 SOTA,缺乏分析</td></tr></tbody></table><blockquote><p>SOTA model: state-of-the-art model,并不是特指某个具体的模型,而是指在该项研究任务中,目前最好/最先进的模型。</p><p>SOTA result: state-of-the-art result,指的是在该项研究任务中,目前最好的模型的结果/性能/表现。</p></blockquote><h2 id="各模块方法技巧"><a href="#各模块方法技巧" class="headerlink" title="各模块方法技巧"></a>各模块方法技巧</h2><h3 id="标题:核心问题与创新点的高度凝练"><a href="#标题:核心问题与创新点的高度凝练" class="headerlink" title="标题:核心问题与创新点的高度凝练"></a>标题:核心问题与创新点的高度凝练</h3><p>标题的基本要求:不超过 15 个单词、英文形式规范、语言精炼简洁、范围大小适当</p><p>一个好的标题:反应核心问题、突出技术创新、保护知识产权、易于记忆传播</p><p>一个不太好的标题:无法从标题中看不出工作或者贡献(创新点)、为了拼凑大写字母把单词中间的某个字母大写、或者直接凑了几个大写字母使得自己的论文难以记忆</p><blockquote><p>MuKEA: Multimodal Knowledge Extraction and Accumulation for Knowledge-based Visual Question Answering(CVPR 2022)</p><p>其中 Multimodal Knowledge Extraction and Accumulation 是贡献,Knowledge-based Visual Question Answering 是领域,一目了然;为了方便记忆,把字母最好拼成方便读的单词,效果会更好</p></blockquote><h3 id="摘要——题目的扩展"><a href="#摘要——题目的扩展" class="headerlink" title="摘要——题目的扩展"></a>摘要——题目的扩展</h3><p>现在这个问题存在的挑战-&gt;现有方法存在的问题-&gt;本文方法思路(1 句)-&gt;本文的亮点-&gt;本文方法优势</p><p><img src="/../images/2022062301.png" alt="摘要的几个重要部分"></p><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>引言的内容与 abstract 格式大致相同,但是需要展开阐述。</p><p>引言要有理有据,足够具体:</p><ul><li>背景阐述聚焦重点</li><li>问题提出明确具体<ul><li>背景时提的问题是这个 task 最核心的问题,要解决的任务的本质问题</li><li>而 Related work 是具体某个方法提出来的问题,是某个方法的具体缺陷</li></ul></li><li>聚焦研究动机,总结现状问题<ul><li>认真阅读,客观评价</li></ul></li><li>基于研究动机,概述研究方法<ul><li>为什么方法可以解决问题,每一步具体是解决了问题的哪个部分,不需要详细介绍每个步骤</li></ul></li><li>面向领域需求,拔高论文贡献<ul><li>准确评估自己的贡献,每个贡献要分开分点表述</li></ul></li></ul><p>介绍大背景-&gt;对现有方法问题进行归纳,进行客观的评价-&gt;从具体方法归纳技术问题-&gt;介绍自己方法,如何解决这些问题,每一步解决了问题的哪个环节-&gt;评估自己的贡献(提出新问题、解决的新视角、实现的新框架、新方法、达到了新 SOTA、具备了新的能力)</p><p><img src="/../images/2022062302.png" alt="引言的结构与主要内容"></p><h3 id="相关工作"><a href="#相关工作" class="headerlink" title="相关工作"></a>相关工作</h3><p>基本要求:</p><ul><li>包括<strong>理解本文</strong>的所有主题,不要罗列看过的所有论文</li><li>包括<strong>问题相关</strong>的所有工作,Task 或者我的方法的相关工作<ul><li>自己研究领域的相关工作+自己的方法在领域内外的应用+与自己方法起到相同的功能的方法的介绍</li></ul></li><li>从不同维度划分主题</li><li>同一主题方法归类</li><li>总结问题</li><li>引出本研究的区别和贡献</li></ul><h3 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h3><p>方法是最容易写的部分,可以先写。不过真正的科研论文,方法内是含有问题的研究思路的,而不是简简单单的第一步第二步第三步。</p><ul><li>总原则:<strong>换位思考,从读者角度出发</strong><ul><li>最费力的部分不一定是最重要的部分</li></ul></li><li>问题建模:数学语言描述、确定研究目标</li><li>模型介绍:<ul><li>模型框架图,清晰定义模块,突出创新之处</li><li>小标题确定,突出方法特色、用于、创新性,图文一致</li><li>模型总体介绍,突出模块间关联</li><li>分模块介绍,突出模块设计动机</li><li>精简表达,善用公式,理论分析</li></ul></li><li>根据重点,重新组织方法介绍思路</li><li>标题和图突出创新性和重点,相互呼应</li><li>每一步方法设计都有理可依,介绍这一步是解决了什么样的问题</li></ul><p>绘制 Framework 图</p><ul><li>把输入输出、模块(大块里的小块)表现好,一个例子贯穿始终</li><li>图上的模块名字与论文子标题要一致</li></ul><p>每个过程首先介绍背后的动机以及目标,具体的过程可以分小标题或加粗来表现逻辑。</p><p>在讲方法之前,整体介绍模型设计思路,描写动机以及为什么不选择其他的方式的原因,以便给审稿人进行解释。</p><h3 id="实验"><a href="#实验" class="headerlink" title="实验"></a>实验</h3><ul><li>一致:支撑理论/方法、动机(实验要印证工作的特性)</li><li>核心:提供重要实验结果</li><li>诚实:不只展示最佳个例(开源之后得到反差可能会影响自己学术生涯)</li><li>分析:给出结果的合理解释(为什么会这样,结果与设计的关联)</li><li>局限:给出方法的能力边界(写出自己方法的边界,不要凭空地去解释,可以学习 ML 的论文的设计)</li></ul><p><img src="/../images/2022062303.jpg" alt="实验部分的内容"></p><p>提出一个具体的问题-&gt;针对问题设计方法,每一步目标明确-&gt;针对方法逐一证明,针对动机逐一分析,以不同的维度进行分析。</p><h3 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h3><p>尽量简洁、避免过度夸大地总结论文的<strong>主要发现</strong>以及<strong>经过验证后的结论</strong>,<strong>介绍未来工作</strong>等。</p><p>总结工作体现效果+说明局限指明方向</p><h3 id="致谢"><a href="#致谢" class="headerlink" title="致谢"></a>致谢</h3><ul><li>帮助这篇论文的人员、机构、项目资助</li><li>审稿人</li><li>提供建议的其他科研人员</li><li>非 co-author</li></ul><h3 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h3><ul><li>不遗漏,查全</li><li>按照会议/期刊既定格式</li><li>常见错误:大小写、全称/缩写、漏写、名字错拼</li></ul><h2 id="做图规范"><a href="#做图规范" class="headerlink" title="做图规范"></a>做图规范</h2><h3 id="研究动机图绘制"><a href="#研究动机图绘制" class="headerlink" title="研究动机图绘制"></a>研究动机图绘制</h3><ul><li>记录方法需要的输入输出,<strong>解决了什么问题</strong></li><li>使用专业语言和符号表达,<strong>使用对比</strong>表示和之前工作的不同</li><li>使用例子来帮助理解问题,避免歧义和偏见</li><li>减少大面积的文字,使得图片直观</li><li>合理表现本文的研究内容和挑战</li></ul><h3 id="模型图绘制"><a href="#模型图绘制" class="headerlink" title="模型图绘制"></a>模型图绘制</h3><ul><li>目标:确定思路是否清晰,逻辑能否形成闭环</li><li>有哪些步骤,分别解决什么问题,每个模块概念和边界在哪里</li><li>训练过程和测试过程是什么样的(<strong>对边界进行确认</strong>),<strong>输入是什么,输出是什么</strong></li><li>排版问题、字体字号、图片大小等</li><li>精准语言描述每个<strong>过程、变量、符号</strong></li><li><strong>相同的概念颜色保持一致</strong>(不然不知道是一个概念)</li><li><strong>前后无歧义</strong>(否则审稿人会觉得是不是有什么操作没展示出来)</li><li><strong>展示自己的创新点,而不是展示自己费劲的部分,例如数据处理</strong></li></ul><h2 id="英语写作规范"><a href="#英语写作规范" class="headerlink" title="英语写作规范"></a>英语写作规范</h2><h3 id="精简的表达方式"><a href="#精简的表达方式" class="headerlink" title="精简的表达方式"></a>精简的表达方式</h3><ul><li>用最简单的话表达最明白的意思,不需要非得凑成一句</li><li>一句话最好只表达一个意思</li><li>减少从中文翻译的英文</li><li>避免重复的表达</li></ul><h3 id="严谨的叙述逻辑"><a href="#严谨的叙述逻辑" class="headerlink" title="严谨的叙述逻辑"></a>严谨的叙述逻辑</h3><ul><li>在术语使用之前进行定义,缩写为什么这么缩写</li><li>所有的符号在定义前后定义清楚,在全文表示相同的信息要用相同的符号</li><li>有清晰的段落结构,段落和章节之间有过度</li><li>图表文字清晰表达内容,与图注、表注、正文一致</li></ul><h3 id="专业的学术用语"><a href="#专业的学术用语" class="headerlink" title="专业的学术用语"></a>专业的学术用语</h3><ul><li>表述自己的贡献的时候,不要过于绝对(the best)</li><li>不要过于口语化(As we know)</li><li>对于自己和他人的评价,不要太主观(good enough to)</li><li>学习《导师防秃指南》<a href="http://www.cse.cuhk.edu.hk/~cslui/english_writing1.pdf">The Most Common Habits from more than 200 English Papers written by Graduate Chinese Engineering Students</a></li><li>学术论文需要精准把握学术术语边界(共识),可以参考计算机 CCF 计算机术语工委的<a href="http://pedia.ccf.org.cn/">CCFpedia</a></li></ul><h3 id="规范的符号运用"><a href="#规范的符号运用" class="headerlink" title="规范的符号运用"></a>规范的符号运用</h3><ul><li>阅读英文论文符号公式定义规范 <a href="https://mathvault.ca/wp-content/uploads/Comprehensive-List-of-Mathematical-Symbols.pdf">Comprehensive List of Mathematical Symbols</a></li><li>学习本领域的经典论文</li></ul><h3 id="客观的图表绘制"><a href="#客观的图表绘制" class="headerlink" title="客观的图表绘制"></a>客观的图表绘制</h3><ul><li>展示探究本质的全面结果:不要只展示自己方法的 best case,客观展示自己的结果以便其他人复现参照</li><li>多视角量化分析结果提升原因</li><li>模型细节明确,提供代码</li></ul><h3 id="正确的文献引用"><a href="#正确的文献引用" class="headerlink" title="正确的文献引用"></a>正确的文献引用</h3><ul><li>使用会议期刊标准模板</li><li>使用 DBLP 寻找文献信息</li><li>一键完善参考文献工具 <a href="https://github.com/yuchenlin/rebiber">Rebiber: A tool for normalizing bibtex with official info</a></li></ul><h3 id="坚守的学术道德"><a href="#坚守的学术道德" class="headerlink" title="坚守的学术道德"></a>坚守的学术道德</h3><ul><li>严守学术道德,切忌抄袭</li></ul><h2 id="日常积累"><a href="#日常积累" class="headerlink" title="日常积累"></a>日常积累</h2><p>日常 5L 积累: Paper List, Idea List, Math List, English List, Code List</p><h3 id="Paper-List"><a href="#Paper-List" class="headerlink" title="Paper List"></a>Paper List</h3><h4 id="找论文"><a href="#找论文" class="headerlink" title="找论文"></a>找论文</h4><ul><li>从自己的方向找相关方向,收集相关的讲座、报告、workshop 和综述<ul><li>会议里经常出现相关的综述</li></ul></li><li>找一个经典的工作(引用量高的论文)</li><li>从一个工作向前向后找到整个发展脉络<ul><li>可以 Follow 研究团队,文章 related work 等</li></ul></li></ul><h4 id="读论文"><a href="#读论文" class="headerlink" title="读论文"></a>读论文</h4><p>读之前进行积极的提问,带着问题读论文——</p><ul><li>文章是关于<strong>什么领域</strong>的?</li><li>解决<strong>什么问题</strong>?为什么要解决此问题?</li><li><strong>创新点在哪里</strong>?为何巧妙?</li><li>如何<strong>验证</strong>并得出结论的?</li><li>对“我”的研究<strong>有何启发</strong>?</li></ul><p>用批判性思维读论文——</p><ul><li>论文是否<strong>正确、真正地</strong>解决了问题?</li><li>论文所用的方法是否具有<strong>局限性</strong>?</li><li>论文的实验<strong>能否证明观点</strong> or 方法的有效性?</li><li>所读论文<strong>没有解决的问题</strong>,“我”能解决吗?</li><li>能采用比论文中<strong>更简单的方法</strong>解决问题吗?</li></ul><p>循序渐进阅读——</p><ul><li>第一遍:阅读标题、摘要和文中图表</li><li>第二遍:阅读引言、结论、关键信息,结合图表快速扫视其他内容</li><li>第三遍:整体阅读论文,可<strong>跳过陌生复杂的技术、数学公式</strong></li><li>第四遍:增强<strong>对数学、技术和未知术语</strong>了解</li><li>为对领域深入研究,还可再多读几遍</li></ul><p>系统记录——</p><ul><li>记录文章的<strong>主体内容</strong></li><li>记录<strong>有启发</strong>的地方</li><li>记录<strong>有问题</strong>的地方</li></ul><p>对接下来工作的帮助——</p><ul><li>“我”<strong>错过了</strong>哪些相关论文?</li><li>这篇论文值得关注吗?对我的工作有何帮助?</li><li>这个领域的<strong>领头人</strong>有哪些?哪些团队值得关注?</li><li>如果我遇到作者,“我”会问什么问题?</li><li>下一步可以做什么?</li></ul><p>文献整理——</p><ul><li>按内容整理:将论文按照不同的主题进行分类</li><li>按时间整理:年份+ 来源+ 题目+ 内容简介</li><li>可以将领域内的经典模型方法记录在 Excel 表格中,便于查找。</li></ul><h3 id="Idea-List"><a href="#Idea-List" class="headerlink" title="Idea List"></a>Idea List</h3><ul><li>论文读好了,自然而然就有 idea 的 list</li><li>根据所需目标、时间要求、资源实际情况统筹考虑 idea 顺序</li><li>及时复盘自己的计划</li></ul><h3 id="Math-List"><a href="#Math-List" class="headerlink" title="Math List"></a>Math List</h3><ul><li>积累基础的数学知识</li><li>对模型中重要的数学公式进行推导</li><li>积累领域常用的符号、公式,形成自己与专家的共同语言</li></ul><h3 id="English-List"><a href="#English-List" class="headerlink" title="English List"></a>English List</h3><ul><li>积累专业知识相关的英语词汇</li><li>积累论文中的好句、好词(但是注意不要抄袭)</li></ul><h3 id="Code-List"><a href="#Code-List" class="headerlink" title="Code List"></a>Code List</h3><ul><li>注意代码的命名,注释规范</li><li>统一函数接口,形成自己的代码库,便于复用代码</li><li>整理深度学习框架的常用函数</li></ul><h2 id="QA-及小问题整理"><a href="#QA-及小问题整理" class="headerlink" title="QA 及小问题整理"></a>QA 及小问题整理</h2><ul><li>论文写作及修改过程:</li></ul><p><img src="/../images/2022062304.png" alt="论文写作及修改过程"></p><ul><li>从论文到期刊:改进方法(更加通用、更加优化、可迁移性高、泛化性强)、扩展实验(增加数据集、对比方法、对比任务,更全面、更深入进行分析)、完善描述(背景、问题、模型、实验部分描述更加详细,related work 随着方法设计内容更多增多)、增多 contribution</li><li>本科生做科研的发展路径:找科研导师,给定点指导,复现论文,从调研小问题相关的工作,找到关联启发和改进。</li><li>在引言中挑战和问题哪个更重要:都是非常重要的</li><li>Contribution 在摘要和引言的区别:引言里详细写对领域的推进作用</li><li>写综述论文与这个有什么区别:刚入门时,更像调研报告,分类组织,找到相关问题,完成偏总结性文章;到博士中后期,对领域有非常多的理解,在小领域的范围内写现在有哪些方法以及他们的缺陷,存在哪些挑战,方法在哪些情况下是适用的。</li><li>在进入工业界如何提升学术敏感度:从技术与本质问题做关联,从细节中理解问题,把实际需求与本质问题关联在一起</li><li>找导师:为什么要来实验室,未来有什么计划?看看老师的方向,与老师沟通。</li><li>导师指导比较少,怎么发出第一篇:调研问题(选论文读论文讲论文)、把论文写好、给别人介绍工作以获得其他人的 feedback</li><li>Step by step 不好的地方在哪里:让外行人无法看出自己的贡献</li><li>在开始研究问题前,如何积累对一个研究领域的认识:在网上找其他老师的 tutorial,从他们所讲比较泛泛的内容进行了解,从打动自己的方向进行研究</li><li>如果研究内容比较工程,比较 open,无法抽出研究问题:把实际问题用 research 的方法去解决</li><li>老师对于科研热爱的点在哪里:如果是充满好奇心去做新的东西,做好了可以有成就感</li><li>写论文先写中文框架还是全英文:先抛开语言,把逻辑表示清楚,再先按中文组织,但是写完之后一定要保证逻辑正确,再看看英文怎么组织</li></ul>

【添翼工程】雅思课

作者 icy
2022年3月27日 16:00
<h1 id="写作"><a href="#写作" class="headerlink" title="写作"></a>写作</h1><p>中国大陆均分 5.37:语法词汇、评分标准、逻辑</p><p>题目设置:图表信息描述 20min+议论文 250 词 40min</p><h2 id="大作文"><a href="#大作文" class="headerlink" title="大作文"></a>大作文</h2><h3 id="分类"><a href="#分类" class="headerlink" title="分类"></a>分类</h3><ul><li>讨论类:Discuss both views and give your opinion?<ul><li>两个段落一样长</li></ul></li><li>观点类:To what extent 程度 do you agree or disagree? Do you agree or disagree?<ul><li>不一样长 两种不同的问法回答不一样</li></ul></li><li>问题解决类:What caused the problem and what solutions can solve this problem?</li></ul><h3 id="常见误区"><a href="#常见误区" class="headerlink" title="常见误区"></a>常见误区</h3><ul><li>以类分区:避免跑题</li><li>用万能模板套题</li><li>享受舒适圈(使用简单词句,目标凑字数)</li></ul><h3 id="评分标准"><a href="#评分标准" class="headerlink" title="评分标准"></a>评分标准</h3><ul><li>任务回应<ul><li>完成各部分任务</li><li>清晰的观点,不能最后更换观点</li></ul></li><li>逻辑与连接<ul><li>语义上的逻辑连贯</li><li>每个段落都有一个清晰的中心主题</li></ul></li><li>词汇丰富度<ul><li>试图使用不常用词汇,但有时不准确</li></ul></li><li>语法丰富度和正确性<ul><li>用复杂句式</li></ul></li></ul><h3 id="结构"><a href="#结构" class="headerlink" title="结构"></a>结构</h3><ul><li>介绍段</li><li>核心段——面向逻辑连贯<ul><li>问题:没有细节(举不出例子)、不连贯、想说的太多(每个段有一个中心主题)</li><li>第一局应当是一个主旨句</li></ul></li><li>总结段</li></ul><h3 id="核心段"><a href="#核心段" class="headerlink" title="核心段"></a>核心段</h3><p>核心段的顺序和个人观点有关,观点有偏向一方(任何题目)和缺一不可(如果观点对立 就不能缺一不可),先写反对的,再写偏向的</p><p>如果是 Discuss,无论是否偏向,字数差不多,不要带感情。</p><p>Discuss:3 种 偏向一方(√)、缺一不可</p><p>extent:3 种 完全赞同 部分赞同 完全不赞同</p><p>do you agree: 2 种</p><p>outweigh:2 种 先写缺点 跟 2 优点</p><h4 id="主旨句的书写"><a href="#主旨句的书写" class="headerlink" title="主旨句的书写"></a>主旨句的书写</h4><ol><li>审题,找准限定词(对 xxx 来说、在 xxx 方面)</li><li>主旨句内容要合理(举办奥运会可以提升国际影响力 因为逻辑不通顺)</li><li>简洁明了(不需要写从句),预告写作内容(含有观点)<ul><li>近二十年科学技术有了发展:描述了客观事实没有观点</li></ul></li></ol><h4 id="主题句的展开"><a href="#主题句的展开" class="headerlink" title="主题句的展开"></a>主题句的展开</h4><ul><li>原因+举例/对比(明确的结果)</li><li>举例+举例(说不出原因的时候 明确的结果)<ul><li>年轻人应该受到重视——一线人员以年轻人为多、创业公司多是年轻人</li></ul></li><li>反向假设/让步+结果/举例(意见与建议)</li><li>细化+举例(抽象的概念)</li></ul><h3 id="开头段"><a href="#开头段" class="headerlink" title="开头段"></a>开头段</h3><ul><li>People have different views about / People have different opinions about </li><li>while/although</li><li>I belive that / in my opinion / I would argue that</li></ul><h3 id="结尾段——个人观点-倡议-建议(凑字数)"><a href="#结尾段——个人观点-倡议-建议(凑字数)" class="headerlink" title="结尾段——个人观点+倡议/建议(凑字数)"></a>结尾段——个人观点+倡议/建议(凑字数)</h3><h3 id="做题方法"><a href="#做题方法" class="headerlink" title="做题方法"></a>做题方法</h3><ul><li>审题三遍,画出双方关键词</li><li>确定个人观点(只影响顺序 不影响内容)</li><li>快速写提纲,可以不用英语,确定段落顺序(偏向 A 则先 B 后 A, 总结)和大致的例子,5min 内</li><li>写作(首段有模板,核心段首句,尾段总结)</li></ul><p>问程度:100%同意、100%不赞同、部分赞同</p><p>部分赞同确定哪一部分,完全的观点不需要写让步段</p><p><img src="/../images/image-20220410164626925.png" alt="image-20220410164626925"></p><p>分类:把人/物进行分类</p><p><img src="/../images/image-20220410170805689.png" alt="image-20220410170805689"></p><p><img src="/../images/image-20220410171649451.png" alt="image-20220410171649451"></p><h1 id="阅读"><a href="#阅读" class="headerlink" title="阅读"></a>阅读</h1><h2 id="判断题"><a href="#判断题" class="headerlink" title="判断题"></a>判断题</h2><p>判断题有 T/F/NG(not given)或者 Y/N/NG,分别都有其判断依据</p><h3 id="True"><a href="#True" class="headerlink" title="True"></a>True</h3><ol><li><p>题目是对原文的同义改写(题目和原文的信息出现了同义词替换、主动换被动等情况)</p><blockquote><p>原文:Food production has kept pace with <strong>soaring</strong> populations <strong>mainly because of</strong> the <strong>expansion</strong> of artificial irrigation(灌溉) system.</p><p>题目:Feeding <strong>increasing</strong> populations is possible <strong>due primarily to improved</strong> irrigation systems.</p></blockquote></li><li><p>题目是对原文的合理总结或推断</p><blockquote><p>原文:The metal had to melt at a temperature less than the hardening point of glass(about 600℃), but could not boil at a temperature below to the temperature of the molten glass(about 1500℃).</p><p>题目:The metal used in the floated process had to have <strong>specific properties</strong>.</p></blockquote></li></ol><h3 id="False"><a href="#False" class="headerlink" title="False"></a>False</h3><ol><li><p>反义改写(题目和原文的信息出现了反义词驳斥)</p><ul><li>Before—after</li><li>Almost—totally</li><li>Always—never</li><li>肯定—否定</li></ul><blockquote><p>原文:It is <strong>by no means</strong> the most obvious way to resolve the problem.</p><p>题目:It is the most apparent way to resolve the problem.</p></blockquote></li><li><p>关系驳斥(对比关系、因果关系、目的关系等相矛盾)</p><ul><li>More/less/as……as/…….</li><li>Because /result from/ as a result of/so/result in/…….</li><li>In order to/ lead to/aim/purpose/…….</li></ul><blockquote><p>原文:The systems supplied the Roman Empire with <strong>as much as</strong> water per person as is provided in many parts of the Industrial world </p><p>题目:Water use per person is <strong>higher</strong> in the industrial world than it was in Ancient Rome</p><p>原文:The FAA was created as a result of <strong>the introduction of the jet engine</strong>.</p><p>题目:<strong>The Grand Canyon crash</strong> in 1956 resulted in the establishment of FAA.</p></blockquote></li><li><p>隐含否定或者通过时态的不同进行表述(Used to do sth/ as was once the case)</p><blockquote><p>原文:She <strong>used to</strong> ask advice from me. 过去常常,现在不再</p><p>题目:She <strong>often</strong> get some suggestions from me.</p><p>原文:He undertook a task to record the <strong>almost-completed</strong> railway</p><p>题目:the railway <strong>had been finished</strong> when he undertook the task.</p></blockquote></li></ol><h3 id="Not-Given"><a href="#Not-Given" class="headerlink" title="Not Given"></a>Not Given</h3><ol><li><p>题干的信息在原文找不到定位词,或者没有提及无中生出一个国家名、时间、细节信息等</p></li><li><p>无中生出比较关系,因果关系等</p><blockquote><p>原文:Schools are usually <strong>modern</strong> in design, set well back from the road and spacious inside.</p><p>题目:Private schools in Japan are <strong>more modern</strong> and spacious than state-run lower secondary schools.</p></blockquote></li><li><p>题目可以定位到原文,但是从原文无法得知题目中的事实</p><ul><li>原文是某人的目标(aim)、目的(purpose)、愿望(wish)、保证(promise)、发誓(vow)等,题目是事实</li><li>原文是并列关系,题目是占比关系</li><li>原文是大范围,题目是范围中具体的一个</li></ul><blockquote><p>原文:The new police chief <strong>vows</strong> to crack down on crimes with tough control and iron hands.</p><p>题目:The crime rate will be down in the future.</p><p>原文:China is the world’s biggest exporter of raw silk <strong>and</strong> silk yarn(纱,纱线)</p><p>题目:Silk yarn makes up the majority of silk exported from China.</p></blockquote></li></ol><h3 id="判断题考点词"><a href="#判断题考点词" class="headerlink" title="判断题考点词"></a>判断题考点词</h3><ul><li>绝对词(Only, all, most, first, always, none, must, 最高级等)</li><li><strong>比较关系</strong>,因果关系,目的关系等</li><li>数字(日期, 数量, 百分比等)<ul><li>Increase= soar =burst = ascend =grow= climb</li><li>Reduce =diminish =decline = decrease= mitigate=dampen=decend</li></ul></li><li>细节信息(宾语的替换,主宾位置,范围大小,并列和占比等)</li></ul><h3 id="易混淆练习题"><a href="#易混淆练习题" class="headerlink" title="易混淆练习题"></a>易混淆练习题</h3><ul><li>原文:Beijing is <strong>one of the largest</strong> cities in the world.<br>题目:Beijing is the <strong>largest</strong> city in the world. <strong>NG</strong></li><li>原文:Beijing is the <strong>largest</strong> city in the world.<br>题目: Beijing is <strong>one of the largest</strong> cities in the world. <strong>Y</strong></li><li>原文:I <strong>used to</strong> play piano very often.<br>题目:I <strong>often</strong> play piano. <strong>N</strong></li><li>原文:Visitors can travel the park <strong>not only by bus but also by bike</strong>.<br>题目:It was found that <strong>most</strong> visitors travel the park <strong>by bike</strong>. <strong>NG</strong></li><li>原文:The trees <strong>are native to</strong> Yantai.<br>题目: The trees <strong>grow only in</strong> Yantai. <strong>NG</strong></li></ul><h2 id="填空题"><a href="#填空题" class="headerlink" title="填空题"></a>填空题</h2><h3 id="句子填空、笔记题"><a href="#句子填空、笔记题" class="headerlink" title="句子填空、笔记题"></a>句子填空、笔记题</h3><p><strong>做题方法</strong></p><ul><li>精读答题指引,看清题目要求(字数限制) </li><li>通读题目,判断最容易定位的一道题</li><li>划出空格所在句中的定位词,迅速找到题目的出处</li><li>找出文中出题句,根据题干其他信息确定空格答案</li></ul><p><strong>定位</strong></p><ul><li>绝对特殊词<ul><li>数字(百分数、钱数),年份,时间,专有名词/全大写缩合词,斜体,带引号的词</li></ul></li><li>相对特殊词<ul><li>名词>形容词>动词</li><li>备注:注意误区 标题词汇</li></ul></li><li>在通读题目, 划出定位词的过程中,除了题干的定位词,同时要<ul><li>重视<strong>逻辑关系词</strong>,如 and / both / but / neither 等。这些关系词所表达的逻辑在文章也一般不会发生变化,较为稳定</li><li>注意空格前后的语法现象,如词性、单复数等。利用空格前后的词去<strong>推测</strong>空格中词的<strong>词性</strong>,甚至词义<br> n.(80%) v.(10%) adj./adv./num. (10%)</li></ul></li></ul><h3 id="表格、图表标记、流程题"><a href="#表格、图表标记、流程题" class="headerlink" title="表格、图表标记、流程题"></a>表格、图表标记、流程题</h3><p><strong>题目特点</strong>:难度低,出镜率低,送分题</p><p><strong>顺序原则</strong>:展示文章思路和叙述逻辑</p><p><strong>做题方法</strong></p><ul><li>精读答题指引,看清题目要求(<strong>字数限制</strong>) </li><li>定位:有标题首先用标题定位大致位置<br>对于单道题目,使用空格前后的定位词(如名词,数字等)定位。</li><li>找出文中出题句,根据题干其他信息确定空格答案</li><li>注意表格,流程,图表标记<strong>是否有标题</strong></li></ul><p><strong>注意事项</strong></p><ul><li>表格题需要注意<strong>表格的表头</strong>,识别单元格信息特征</li><li>流程图是<strong>顺序题</strong>,注意箭头走向</li><li>图表标记题需要理解图表<strong>含义和特点以及各种方位关系</strong>(前后上下左右)</li><li>注意空格字数要求,以及<strong>是否需要填写数字</strong></li></ul><h3 id="摘要题"><a href="#摘要题" class="headerlink" title="摘要题"></a>摘要题</h3><p><strong>做题方法</strong></p><ul><li>精读答题指引,看清题目要求(字数限制) <ul><li>如果是有选项摘要,看一下是否有提及<br>NB you may use any letter more than once;</li></ul></li><li>忽略空格,通读 Summary</li><li>划出空格所在句中的定位词,并判断空格词性</li><li>找出文中出题句,根据题干其他信息确定空格答案<br> 如果是有选项摘要题,注意正确选项是原文词或词组近义词。</li></ul><p> <strong>注意事项</strong></p><ul><li>摘要题分为<strong>无选项摘要</strong>和<strong>有选项摘要</strong></li><li>无选项摘要选用原文中的<strong>原词</strong>,注意选词的<strong>字数</strong>限定</li><li>有选项摘要需要选择和文中词或者词组<strong>意思相近</strong>的选项</li><li>有选项摘要<strong>不一定是顺序题</strong>,正确选项是原文的同义替换</li></ul><h2 id="标题题"><a href="#标题题" class="headerlink" title="标题题"></a>标题题</h2><h2 id="匹配题"><a href="#匹配题" class="headerlink" title="匹配题"></a>匹配题</h2><p>填空和判断一般按顺序出题,但是匹配完全乱序</p><h3 id="特殊词匹配"><a href="#特殊词匹配" class="headerlink" title="特殊词匹配"></a>特殊词匹配</h3><p>题目是人名,匹配是观点-&gt;去原文找人名,匹配对应观点</p><p>地名/国家与解决措施匹配</p><h3 id="段落信息匹配"><a href="#段落信息匹配" class="headerlink" title="段落信息匹配"></a>段落信息匹配</h3><p>信息匹配段落(类似于四六级)</p><p>NB(特别注意) You may use any letter more than once 可能重复选择 一般是 1 个字母选 2 次,很少 1 字母 3 次和 2 字母 2 次</p><ul><li>审题 NB</li><li>划定位词</li><li>略读 不要精读 读完一段之后看题</li><li>比对题目</li></ul><p><img src="/../images/image-20220410143908163.png" alt="image-20220410143908163"></p><h2 id="做法"><a href="#做法" class="headerlink" title="做法"></a>做法</h2><ol><li>审题<ul><li>注意乱序</li><li>特殊词等对应</li></ul></li><li>分析题目观点,划出关键词<ul><li>实义名词</li></ul></li><li>用特殊词定位,分析出题句</li><li>比对(注意同义替换)</li></ol><h3 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h3><p><img src="/../images/image-20220410141405644.png" alt="image-20220410141405644"></p><h2 id="句子结尾配对题"><a href="#句子结尾配对题" class="headerlink" title="句子结尾配对题"></a>句子结尾配对题</h2><p>有顺序</p><p><img src="/images/image-20220410150142746.png" alt="image-20220410150142746"></p><h3 id="分类题"><a href="#分类题" class="headerlink" title="分类题"></a>分类题</h3><p>类型配特征</p><p><img src="/../images/image-20220410151923428.png" alt="image-20220410151923428"></p><p><img src="/images/image-20220410152017326.png" alt="image-20220410152017326"></p><h1 id="听力"><a href="#听力" class="headerlink" title="听力"></a>听力</h1><h2 id="月份的说法"><a href="#月份的说法" class="headerlink" title="月份的说法"></a>月份的说法</h2><ul><li>April the twenty-ninth </li><li>the twenty-ninth of April</li><li>29 the fourth</li></ul><h2 id="拼写"><a href="#拼写" class="headerlink" title="拼写"></a>拼写</h2><ul><li>答案修改</li><li>双写 double l double r</li><li>辨音 uu&amp;w 重音位置, j&amp;g, m&amp;n</li><li>T for Tango</li></ul><p>路的说法:Avenue, lane, path, drive, boulevard<br>时间说法:a quarter past/to am/pm 时间单位不缩写</p><p>钱 $ £ € cash check credit card </p><p>钱的数额和题号区分</p><p>566222 five double six triple two</p><p>0 o zero naught 三种 0 的说法</p><p>z /zed/ /zi-/</p><p>字母+数字 postcode passport drivers’ licence </p><p>50A vs 58</p><p>P4 生物 商业</p><p>限定词、定位词</p>

【雅思哥】雅思 7 分备考

作者 icy
2022年3月8日 20:00
<p>这是雅思哥的 6.6r 的公开课,报了正课之后送了这个课,一些笔记分享一下~</p><h1 id="口语"><a href="#口语" class="headerlink" title="口语"></a>口语</h1><h2 id="Part1"><a href="#Part1" class="headerlink" title="Part1"></a>Part1</h2><ul><li>【高频话题】手机如何改变生活<ul><li>需要细节</li><li>口语不需要 conclusion</li></ul></li><li>养成考试习惯,Part1 通用结构——时间双观点结构<ul><li>先给主旨(可选)</li><li>过去 xxxxx 样,现在 xxxxx 样</li></ul></li><li>常见词汇<ul><li>daily errands 日常事务 俚语表达 加分项</li><li>game changer 造成了巨大的变化 game 其实没有用其原因</li><li>case in point 例如</li></ul></li></ul><h2 id="Part2"><a href="#Part2" class="headerlink" title="Part2"></a>Part2</h2><ul><li>Describe an interesting song<ul><li>As a typical millennial</li><li>down to a t</li><li>fit this bill</li><li>millennial 80-90s gen-z 00s</li><li>push comes to shove</li><li>成名曲 breakout song ( and shot them to fame)</li><li>binge watching 疯狂地做</li><li>super duper catchy 非常吸引人</li></ul></li><li>注意重音</li></ul><h1 id="听力"><a href="#听力" class="headerlink" title="听力"></a>听力</h1><ul><li><p>听力填空:</p><ul><li>不需要背特别广(四级、六级)的词汇</li></ul></li><li><p>选择配对:</p><ul><li>新题的多,强调口音(英、美、奥新西兰)+语速(平均比剑桥稍快一点,参考剑 8 剑 12)</li></ul></li><li><p>考试经验:</p><ul><li>考前回顾考点(陷阱)、根据语境区分近音词</li></ul></li><li><p>地图(5%)流程问答:</p><ul><li>总体题量不大,占比不大</li><li>机考地图注意巧妙处理(左边是地图右边是表格,两个距离太远,可以抄一下题目)</li><li>注意多义词(pass 通道、路过 past the bridge 不过桥)</li></ul></li><li><p>5 分及以下短期备考策略</p><ul><li>趋势内考试高频词优先背记</li><li>填空题+选择题优先学习</li><li>考前剑雅模考(月份/星期/数字必练)</li></ul></li><li><p>5.5-7 分备考策略</p><ul><li>趋势内考试答案词尽全力背记</li><li>填空题、选择题、地图、配对技巧梳理</li><li>要高分必须从有效沟通的方向去理解题目<ul><li>听的答案要符合逻辑,写一些让交流有信息量的词汇</li></ul></li></ul></li><li><p>8+备考策略</p><ul><li>预测+熟悉难题套路</li><li>抽象到具体的替换</li></ul></li></ul><h1 id="写作"><a href="#写作" class="headerlink" title="写作"></a>写作</h1><h2 id="写作预测题精讲"><a href="#写作预测题精讲" class="headerlink" title="写作预测题精讲"></a>写作预测题精讲</h2><ul><li>雅思大作文的类型<ul><li>利弊类 Is is positive or negative……</li><li>观点类 Do you agree / disagree</li><li>讨论类 Discuss both views and give your own opinion</li><li>报告类 What are reasons, effects and solutions</li><li>开放式 What? How?</li></ul></li><li>学习建议<ul><li>最好学习作文结构,学习范文中的好词好句,不要闭门造车</li><li>不要抄范文,不能化用成自己的,没有用</li><li>多练习,从范文里举一反三</li><li>考的是熟练度和准确度不是难度</li><li>solution 类 把目的和措施分开</li><li>不要只看范文,可以看着范文思路结构,仿写然后对答案更好</li></ul></li></ul><h2 id="句子扩展"><a href="#句子扩展" class="headerlink" title="句子扩展"></a>句子扩展</h2><ul><li>使用介词,把句子进行延伸<ul><li>both within …… and ……</li><li>such as …… etc.</li></ul></li></ul><h2 id="写作素材的一题多用"><a href="#写作素材的一题多用" class="headerlink" title="写作素材的一题多用"></a>写作素材的一题多用</h2><ul><li>提建议、解决措施<ul><li>To do…, we need to ……</li><li>…… would be an effective strategy for ……</li><li>… should ……</li><li>If …, …… would ……</li><li>…… should …… so that ……</li></ul></li></ul><h2 id="加上一点衔接词"><a href="#加上一点衔接词" class="headerlink" title="加上一点衔接词"></a>加上一点衔接词</h2><ul><li>Considering</li><li>In this process</li><li>代词</li></ul><h1 id="阅读"><a href="#阅读" class="headerlink" title="阅读"></a>阅读</h1><p>中国平均分阅读最高,口语最低,阅读对于中国人是一个提分的科目,对于一个 7 分,一般来说就是 8766。</p><hr><ul><li><p>同学提分误区:天天做题;对照解析书总结错题;总结文章中不认识的单词;精读</p><ul><li>其实这样只是在做题,忽略了单词的学习,忽略了方法的学习</li><li>不要自己总结单词,因为高频词单词书比自己总结的好</li><li>自己这个总结的单词,大概率是假努力,为了满足感而已</li></ul></li><li><p>学习方法</p><ul><li>单词,一定要提升词汇,不要放在句子背<ul><li>循环、时间、专注</li></ul></li><li>做题方法技巧(短时间提分用)</li><li>刷题(有了 12 再去做题不然没用)</li><li>总结</li><li>看考前预测</li></ul></li><li><p>题目有许多种,每个题型都有对应的方法,要分细</p></li><li><p><em>You may use any letter more than once</em> 有 90%概率会有选项重复选</p></li><li><p>None of the above 有且仅会被选一次 即便有 NB(Nota Bene)</p></li><li><p>大定位:对于名字配对题,先数每个名字在文章出现的次数,从出现次数最少的开始选,最后没时间了都选出现次数最多的</p></li><li><p>小定位:分析题干,把所有重复出现的词拿掉(对做题没有意义),再找定位词</p><ul><li>定位词不一定是一个词</li><li>有数字划数字,有比较划比较,剩下的划剩下的,在剩下的重点划有实际意义的词</li></ul></li><li><p>人名是不会发生同义替换的,所以好找</p></li></ul>

【雅思哥】雅思 FAQ

作者 icy
2022年3月8日 20:00
<p>这是雅思哥的 6.6r 的公开课,报了正课之后送了这个课,一些笔记分享一下~</p><h1 id="口语"><a href="#口语" class="headerlink" title="口语"></a>口语</h1><h2 id="两周内准备口语"><a href="#两周内准备口语" class="headerlink" title="两周内准备口语"></a>两周内准备口语</h2><ul><li>六分:把 Part1 和 Part2 先练好</li><li>七分:在六分没问题的基础上稍微练一下 Part3</li></ul><h2 id="一紧张语法混乱"><a href="#一紧张语法混乱" class="headerlink" title="一紧张语法混乱"></a>一紧张语法混乱</h2><ul><li>先保证流畅性,允许一定的语法错误,以免考官打断</li></ul><h2 id="思维打不开"><a href="#思维打不开" class="headerlink" title="思维打不开"></a>思维打不开</h2><ul><li>积累</li></ul><h2 id="P2P3-怎么备考"><a href="#P2P3-怎么备考" class="headerlink" title="P2P3 怎么备考"></a>P2P3 怎么备考</h2><ul><li>Part2 准备故事</li><li>Part3 准备通用的观点去答题 不能背题</li></ul><h2 id="考官问的东西听不懂"><a href="#考官问的东西听不懂" class="headerlink" title="考官问的东西听不懂"></a>考官问的东西听不懂</h2><ul><li>P1 会有归类 </li><li>P2 基本都是原题</li><li>P3 可以问一下</li></ul><h2 id="P3-考官越聊越深入很慌张"><a href="#P3-考官越聊越深入很慌张" class="headerlink" title="P3 考官越聊越深入很慌张"></a>P3 考官越聊越深入很慌张</h2><ul><li>不需要每个都头头是道</li><li>不知道这个话题可以去拓展,学方法</li></ul><h2 id="语料不丰富-如何在短期内快速提升"><a href="#语料不丰富-如何在短期内快速提升" class="headerlink" title="语料不丰富 如何在短期内快速提升"></a>语料不丰富 如何在短期内快速提升</h2><ul><li>会提供范文 学习俚语表达</li></ul><h2 id="一个月报考,如何合理安排备考"><a href="#一个月报考,如何合理安排备考" class="headerlink" title="一个月报考,如何合理安排备考"></a>一个月报考,如何合理安排备考</h2><ul><li>四个评分标准</li><li>fluency 词汇 语法 发音</li></ul><h2 id="内容大众化好还是个性化好"><a href="#内容大众化好还是个性化好" class="headerlink" title="内容大众化好还是个性化好"></a>内容大众化好还是个性化好</h2><ul><li>个性化好</li></ul><h2 id="P2-准备多少故事比较合适"><a href="#P2-准备多少故事比较合适" class="headerlink" title="P2 准备多少故事比较合适"></a>P2 准备多少故事比较合适</h2><ul><li>一共 50 个话题</li><li>准备 30 个话题比较合适</li></ul><h2 id="口语题库换题对备考有什么影响"><a href="#口语题库换题对备考有什么影响" class="headerlink" title="口语题库换题对备考有什么影响"></a>口语题库换题对备考有什么影响</h2><ul><li>Part1 换 1/3(出现新题 1/3)</li><li>Part2(40%-50%)</li><li>不建议 159 月初考试,会换题</li></ul><h2 id="从来没学过怎么学"><a href="#从来没学过怎么学" class="headerlink" title="从来没学过怎么学"></a>从来没学过怎么学</h2><ul><li>口语不推荐自己学</li><li>很多内容中文拓展丰富,但是缺乏俚语表达,或者俚语表达不合适,发音不太好练习</li><li>有些人过于关注语法、词汇,应该使用更好的方法</li></ul><h2 id="说的时候简单的单词想不起来"><a href="#说的时候简单的单词想不起来" class="headerlink" title="说的时候简单的单词想不起来"></a>说的时候简单的单词想不起来</h2><ul><li>把一些句型要养成习惯,从课上吸收表达</li></ul><h2 id="Part3-需要高级词汇吗"><a href="#Part3-需要高级词汇吗" class="headerlink" title="Part3 需要高级词汇吗"></a>Part3 需要高级词汇吗</h2><ul><li>需要</li></ul><h2 id="口语每天练习多长时间"><a href="#口语每天练习多长时间" class="headerlink" title="口语每天练习多长时间"></a>口语每天练习多长时间</h2><ul><li>3-4 小时,纯说,不包括写题时间</li><li>实在没时间了,再去挑重点</li></ul><h2 id="10-天复习口语复习什么"><a href="#10-天复习口语复习什么" class="headerlink" title="10 天复习口语复习什么"></a>10 天复习口语复习什么</h2><ul><li>看小范围预测</li></ul><h2 id="口语考试时间有什么讲究吗"><a href="#口语考试时间有什么讲究吗" class="headerlink" title="口语考试时间有什么讲究吗"></a>口语考试时间有什么讲究吗</h2><ul><li>我们没办法选择考官,不要追寻虚无缥缈的东西(早上考、下午考、穿着之类的)</li></ul><h2 id="突然卡顿怎么办"><a href="#突然卡顿怎么办" class="headerlink" title="突然卡顿怎么办"></a>突然卡顿怎么办</h2><ul><li>课上会讲怎么圆一下</li></ul><h2 id="想说定语从句但总是卡顿,脑子在想语序、动词就会卡顿"><a href="#想说定语从句但总是卡顿,脑子在想语序、动词就会卡顿" class="headerlink" title="想说定语从句但总是卡顿,脑子在想语序、动词就会卡顿"></a>想说定语从句但总是卡顿,脑子在想语序、动词就会卡顿</h2><ul><li>我们学习方法太书面化了</li><li>先保证流畅性,再去想语法和俚语表达</li><li>给老师交作业,发现问题解决问题</li></ul><h1 id="听力"><a href="#听力" class="headerlink" title="听力"></a>听力</h1><h2 id="听力几倍速练习比较合适"><a href="#听力几倍速练习比较合适" class="headerlink" title="听力几倍速练习比较合适"></a>听力几倍速练习比较合适</h2><ul><li>本身不高(6 以下)的就不要倍速了</li><li>其他的适度加速,一点一点加速,最多不要超过两倍</li></ul><h2 id="听力经常走神怎么办"><a href="#听力经常走神怎么办" class="headerlink" title="听力经常走神怎么办"></a>听力经常走神怎么办</h2><ul><li>练习注意时长,精听不要超过半小时,不要无用功</li><li>不要一个 section 一练,一次做一套</li><li>是不是基础不行 听不懂 学习</li></ul><h2 id="答案词错过了怎么办"><a href="#答案词错过了怎么办" class="headerlink" title="答案词错过了怎么办"></a>答案词错过了怎么办</h2><ul><li>模考的时候反复背关键词</li></ul><h2 id="听-TED-有用吗"><a href="#听-TED-有用吗" class="headerlink" title="听 TED 有用吗"></a>听 TED 有用吗</h2><ul><li>意义不算太大</li><li>因为语速、内容与考试不一定匹配</li></ul><h2 id="填空题日期时间等的写法"><a href="#填空题日期时间等的写法" class="headerlink" title="填空题日期时间等的写法"></a>填空题日期时间等的写法</h2><ul><li><code>North-West</code>可以写<code>northwest</code></li><li>年份写在最后,日在前月在前都可以</li><li>月份不可以缩写,不可以用数字,但是大小写无所谓</li><li>时间写<code>17:30</code>和<code>17.30</code>都可以</li></ul><h2 id="Section-2、3-的选择题比-1、4-填词题更容易出错,应该怎么提升呢"><a href="#Section-2、3-的选择题比-1、4-填词题更容易出错,应该怎么提升呢" class="headerlink" title="Section 2、3 的选择题比 1、4 填词题更容易出错,应该怎么提升呢"></a>Section 2、3 的选择题比 1、4 填词题更容易出错,应该怎么提升呢</h2><ul><li>因为 2、3 的干扰项太多</li><li>特别注意思维严谨</li></ul><h2 id="要在草稿纸上记笔记吗"><a href="#要在草稿纸上记笔记吗" class="headerlink" title="要在草稿纸上记笔记吗"></a>要在草稿纸上记笔记吗</h2><ul><li>如果已经读完题目了,就不要记笔记了,而是观察题目的替换,提前预判</li><li>看不完题目,还是记一下比较好</li></ul><h2 id="双选题选不对"><a href="#双选题选不对" class="headerlink" title="双选题选不对"></a>双选题选不对</h2><ul><li>音频中碰(匹配)词的概率挺多,而不是匹配一个句子,从而选错</li></ul><h2 id="题干和选项过长的选择题,眼耳兼顾不到,反应不过来"><a href="#题干和选项过长的选择题,眼耳兼顾不到,反应不过来" class="headerlink" title="题干和选项过长的选择题,眼耳兼顾不到,反应不过来"></a>题干和选项过长的选择题,眼耳兼顾不到,反应不过来</h2><ul><li>读题优先看长选项</li></ul><h2 id="配对题不知道匹配到哪了"><a href="#配对题不知道匹配到哪了" class="headerlink" title="配对题不知道匹配到哪了"></a>配对题不知道匹配到哪了</h2><ul><li>题干如果是大写的定位</li><li>如果是小写的大概率是替换</li></ul><h2 id="听力一天练多少合适"><a href="#听力一天练多少合适" class="headerlink" title="听力一天练多少合适"></a>听力一天练多少合适</h2><ul><li>马上就考试了一天三套</li><li>如果刷题都刷完了,有瓶颈了,好好找出一套找问题</li></ul><h2 id="每个单词都能听懂,但是连起来听不懂"><a href="#每个单词都能听懂,但是连起来听不懂" class="headerlink" title="每个单词都能听懂,但是连起来听不懂"></a>每个单词都能听懂,但是连起来听不懂</h2><ul><li>在练习的时候听他们讲了什么,而不只是听单词</li><li>注意关联和逻辑</li></ul><h2 id="容易被陷阱题影响"><a href="#容易被陷阱题影响" class="headerlink" title="容易被陷阱题影响"></a>容易被陷阱题影响</h2><ul><li>没有注意细节,练习不要泛泛听</li><li>What When How Where 之后能复述出来</li></ul><h2 id="地图题怎么判断"><a href="#地图题怎么判断" class="headerlink" title="地图题怎么判断"></a>地图题怎么判断</h2><ul><li>示意图、填图、配对</li><li>方向的说法(总结)、尽头拐角之类的标识怎么说之类的要总结</li></ul><h2 id="抓不到答案"><a href="#抓不到答案" class="headerlink" title="抓不到答案"></a>抓不到答案</h2><ul><li>加强看题,是不是听完之前看完题</li><li>漏听:磨耳朵</li></ul><h2 id="Matching-题跟不上"><a href="#Matching-题跟不上" class="headerlink" title="Matching 题跟不上"></a>Matching 题跟不上</h2><ul><li>注重替换</li></ul><h1 id="阅读"><a href="#阅读" class="headerlink" title="阅读"></a>阅读</h1><h2 id="阅读达到-7-需要多少词汇量"><a href="#阅读达到-7-需要多少词汇量" class="headerlink" title="阅读达到 7 需要多少词汇量"></a>阅读达到 7 需要多少词汇量</h2><ul><li>如果不用方法的话,需要 7000~8000 词汇量</li><li>如果有方法的话,可以减半,4000 词汇量就可以</li></ul><h2 id="阅读机考"><a href="#阅读机考" class="headerlink" title="阅读机考"></a>阅读机考</h2><ul><li>机考优势:左边题目右边题,省的翻来翻去,已经很好了</li></ul><h2 id="定位词不明显的怎么快速定位"><a href="#定位词不明显的怎么快速定位" class="headerlink" title="定位词不明显的怎么快速定位"></a>定位词不明显的怎么快速定位</h2><ul><li>40 个题有 35 个题有定位词,不过可能不是一个词而已,需要掌握技巧</li></ul><h2 id="怎么更好的做题"><a href="#怎么更好的做题" class="headerlink" title="怎么更好的做题"></a>怎么更好的做题</h2><ul><li>如果单词掌握不好,没找到技巧,直接做题没用,因为很多题干没听懂</li><li>对照老师讲的方法进行总结,但不需要总结词汇,买词汇书更好</li></ul><h2 id="NG-和-F-怎么区分"><a href="#NG-和-F-怎么区分" class="headerlink" title="NG 和 F 怎么区分"></a>NG 和 F 怎么区分</h2><ul><li>如果是阅读整体不行,有了方法同义替换看不懂,也没用</li><li>如果只是判断题有问题,其实是没找到套路,在有方法之后很快能掌握</li></ul><h2 id="文章很长,细节题定位不明显"><a href="#文章很长,细节题定位不明显" class="headerlink" title="文章很长,细节题定位不明显"></a>文章很长,细节题定位不明显</h2><ul><li>定位词技巧</li><li>有 30%的题会有同意替换</li></ul><h2 id="阅读能力如何提升"><a href="#阅读能力如何提升" class="headerlink" title="阅读能力如何提升"></a>阅读能力如何提升</h2><ul><li>上课可以短期之内提高雅思阅读分数,但短期之内不能提高阅读能力</li></ul><h2 id="每篇时间分配上有侧重吗"><a href="#每篇时间分配上有侧重吗" class="headerlink" title="每篇时间分配上有侧重吗"></a>每篇时间分配上有侧重吗</h2><ul><li>有三篇文章,不一定第一篇最简单最后一篇最难</li><li>先判断哪个是最难的,剩下俩 18min,难的 24min</li></ul><h2 id="判断题-TF-写成了-YN-有分吗"><a href="#判断题-TF-写成了-YN-有分吗" class="headerlink" title="判断题 TF 写成了 YN 有分吗"></a>判断题 TF 写成了 YN 有分吗</h2><ul><li>看考官,很有可能没分了</li></ul><h2 id="问答题首字母大写扣分吗"><a href="#问答题首字母大写扣分吗" class="headerlink" title="问答题首字母大写扣分吗"></a>问答题首字母大写扣分吗</h2><ul><li>阅读大小写无所谓不扣分</li><li>听力只考察拼写,不考差大小写</li></ul><h2 id="需要把每段意思都弄懂再做题吗"><a href="#需要把每段意思都弄懂再做题吗" class="headerlink" title="需要把每段意思都弄懂再做题吗"></a>需要把每段意思都弄懂再做题吗</h2><ul><li>不需要,因为三篇文章都读完,题目一定做不完</li><li>95%的人做不到</li><li>官方考察的是效率阅读</li></ul><h2 id="如何保证速度的基础上提高做题的准确度"><a href="#如何保证速度的基础上提高做题的准确度" class="headerlink" title="如何保证速度的基础上提高做题的准确度"></a>如何保证速度的基础上提高做题的准确度</h2><ul><li>方法、技巧、练习</li></ul><h2 id="技巧-Example"><a href="#技巧-Example" class="headerlink" title="技巧 Example"></a>技巧 Example</h2><p>原文:香蕉是世界上最古老的水果,而题目:香蕉是世界上最古老的水果之一-&gt;<strong>T</strong></p><p>原文:香蕉是世界上最古老的水果之一,而题目:香蕉是世界上最古老的水果-&gt;<strong>NG</strong></p><h2 id="能力和技巧哪个重要"><a href="#能力和技巧哪个重要" class="headerlink" title="能力和技巧哪个重要"></a>能力和技巧哪个重要</h2><ul><li>能力更重要,但是培养需要两年</li><li>但是对于备考雅思,技巧重要</li></ul><h2 id="阅读对-34-36-道,但是需要一个半小时,如何提高阅读速度"><a href="#阅读对-34-36-道,但是需要一个半小时,如何提高阅读速度" class="headerlink" title="阅读对 34~36 道,但是需要一个半小时,如何提高阅读速度"></a>阅读对 34~36 道,但是需要一个半小时,如何提高阅读速度</h2><ul><li>这个其实是能力技巧都有问题</li><li>而且不要这么做题,这样只会给自己盲目的自信</li></ul><h2 id="阅读词汇量怎么快速提升"><a href="#阅读词汇量怎么快速提升" class="headerlink" title="阅读词汇量怎么快速提升"></a>阅读词汇量怎么快速提升</h2><ul><li>背单词:高度注意力、循环、专注</li><li>不建议用手机背单词,因为手机诱惑比较多</li><li>最好一天一个小时单词</li></ul><h2 id="阅读没耐心"><a href="#阅读没耐心" class="headerlink" title="阅读没耐心"></a>阅读没耐心</h2><ul><li>多做做题,做得少了</li></ul><h2 id="填空题或问答题找到定位段但是找不到答案"><a href="#填空题或问答题找到定位段但是找不到答案" class="headerlink" title="填空题或问答题找到定位段但是找不到答案"></a>填空题或问答题找到定位段但是找不到答案</h2><ul><li>单词的问题,同义替换问题</li></ul><h2 id="长难句怎么处理"><a href="#长难句怎么处理" class="headerlink" title="长难句怎么处理"></a>长难句怎么处理</h2><ul><li>考试时候没时间分析长难句</li><li>答案的查找与长难句分析没关系</li><li>备考阅读的时候不考语法,只有口语写作考语法</li></ul><h1 id="作文"><a href="#作文" class="headerlink" title="作文"></a>作文</h1><h2 id="大作文是不是任何话题都可以用五段式书写"><a href="#大作文是不是任何话题都可以用五段式书写" class="headerlink" title="大作文是不是任何话题都可以用五段式书写"></a>大作文是不是任何话题都可以用五段式书写</h2><ul><li>四段式:好处坏处各占一半</li><li>五段式:有侧重,好处两段坏处一段</li></ul><h2 id="大作文如果跑题扣分很严重吗"><a href="#大作文如果跑题扣分很严重吗" class="headerlink" title="大作文如果跑题扣分很严重吗"></a>大作文如果跑题扣分很严重吗</h2><ul><li>看跑题程度</li><li>看错单词 planet-&gt;plant,很有可能寄了</li><li>题目之间看着很像,但是侧重点不一样,直接背答案可能会有点偏,成绩稍微有影响</li><li>文章思路要连贯</li></ul><h2 id="作为格式标准应该是像中文那样首行缩进还是顶格写,段落于段落之间是否需要空行"><a href="#作为格式标准应该是像中文那样首行缩进还是顶格写,段落于段落之间是否需要空行" class="headerlink" title="作为格式标准应该是像中文那样首行缩进还是顶格写,段落于段落之间是否需要空行"></a>作为格式标准应该是像中文那样首行缩进还是顶格写,段落于段落之间是否需要空行</h2><ul><li>顶格写、空行</li></ul><h2 id="备考书籍"><a href="#备考书籍" class="headerlink" title="备考书籍"></a>备考书籍</h2><ul><li>长期备考:杂志、新闻、小说</li><li>短期备考:看看真题</li></ul><h2 id="机考是随时存盘吗?拼写错了会识别吗"><a href="#机考是随时存盘吗?拼写错了会识别吗" class="headerlink" title="机考是随时存盘吗?拼写错了会识别吗"></a>机考是随时存盘吗?拼写错了会识别吗</h2><ul><li>拼写错了不会识别</li></ul><h2 id="7-分的作文是什么样的"><a href="#7-分的作文是什么样的" class="headerlink" title="7 分的作文是什么样的"></a>7 分的作文是什么样的</h2><ul><li>有一些比较好的词汇和句型</li><li>其实考的不是难度,是熟练度和准确度,七分不需要用特别难的词汇</li></ul><h2 id="短期准备雅思作文最简易从哪方面入手呢"><a href="#短期准备雅思作文最简易从哪方面入手呢" class="headerlink" title="短期准备雅思作文最简易从哪方面入手呢"></a>短期准备雅思作文最简易从哪方面入手呢</h2><ul><li>造句 3 天</li><li>段落 4-5 天</li><li>全篇剩下时间</li></ul><h2 id="Task-Response-单方面-8-分的标准是什么"><a href="#Task-Response-单方面-8-分的标准是什么" class="headerlink" title="Task Response 单方面 8 分的标准是什么"></a>Task Response 单方面 8 分的标准是什么</h2><ul><li>词汇不好,通过扣题提分不现实,提高的话从语言提高</li></ul><h2 id="大作文看语言能力还是思维能力"><a href="#大作文看语言能力还是思维能力" class="headerlink" title="大作文看语言能力还是思维能力"></a>大作文看语言能力还是思维能力</h2><ul><li>雅思是语言考试,把 70%的时间放在语言上</li><li>思维也是很重要的,作文是看整体等级</li></ul><h2 id="小作文万能模板和大作文万能理由"><a href="#小作文万能模板和大作文万能理由" class="headerlink" title="小作文万能模板和大作文万能理由"></a>小作文万能模板和大作文万能理由</h2><ul><li>写开头和段之间关联的时候,有固定的套句,考试的时候可以使用</li><li>不能用四六级模板,那个容易不扣题</li><li>固定的句型可以背一些,可以快速搭建框架</li></ul><h2 id="写作-5-5-一个月提到-6"><a href="#写作-5-5-一个月提到-6" class="headerlink" title="写作 5.5 一个月提到 6"></a>写作 5.5 一个月提到 6</h2><ul><li>6 分是一个门槛,造句是一个难点,少犯语法错误,基本就可以 6 分</li><li>7 分需要扩充词汇,注意连贯性,句法多用一些</li></ul><h2 id="字数不够怎么办"><a href="#字数不够怎么办" class="headerlink" title="字数不够怎么办"></a>字数不够怎么办</h2><ul><li>讲完道理后,来个例子,再不够,反着再来一次</li></ul><h2 id="流程图字数不够"><a href="#流程图字数不够" class="headerlink" title="流程图字数不够"></a>流程图字数不够</h2><ul><li>根据常识补充一些内容,加上一两句话</li></ul><h2 id="大作文写多少字"><a href="#大作文写多少字" class="headerlink" title="大作文写多少字"></a>大作文写多少字</h2><ul><li>250-300 字,不够 250 扣分,太多了可能不太想看</li></ul><h2 id="备考计划"><a href="#备考计划" class="headerlink" title="备考计划"></a>备考计划</h2><ul><li>第一阶段:基础练习(写作词汇+造句练习+语法练习)</li><li>第二阶段:写作练习(仿写)段落练习</li><li>第三阶段:写作练习(不限时写作)</li><li>第四阶段:5-8 次模考+复习</li><li>过犹不及,建议隔一天写一次,这样在休息的时候好好复习一下</li></ul>

C++ 中字符串与数字的拼接

作者 icy
2022年2月26日 14:00
<p>在 Java 中,字符串和其他类型的对象用 <code>+</code> 相连时,默认将非字符串对象转为字符串(调用<code>.toString()</code> 函数)。今天被问到才发现,在 C++ 中,string 与 string 的连接与 Java 一样,而字符串与数字的拼接,可能就与 Java 不一样了。</p><p>通过查询 cpp reference,发现其实对于这种加法的重载只定义了以下这些:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line">string <span class="keyword">operator</span>+ (<span class="type">const</span> string&amp; lhs, <span class="type">const</span> string&amp; rhs);</span><br><span class="line">string <span class="keyword">operator</span>+ (string&amp;&amp; lhs, string&amp;&amp; rhs);</span><br><span class="line">string <span class="keyword">operator</span>+ (string&amp;&amp; lhs, <span class="type">const</span> string&amp; rhs);</span><br><span class="line">string <span class="keyword">operator</span>+ (<span class="type">const</span> string&amp; lhs, string&amp;&amp; rhs);</span><br><span class="line"></span><br><span class="line">string <span class="keyword">operator</span>+ (<span class="type">const</span> string&amp; lhs, <span class="type">const</span> <span class="type">char</span>* rhs);</span><br><span class="line">string <span class="keyword">operator</span>+ (string&amp;&amp; lhs, <span class="type">const</span> <span class="type">char</span>* rhs);</span><br><span class="line">string <span class="keyword">operator</span>+ (<span class="type">const</span> <span class="type">char</span>* lhs, <span class="type">const</span> string&amp; rhs);</span><br><span class="line">string <span class="keyword">operator</span>+ (<span class="type">const</span> <span class="type">char</span>* lhs, string&amp;&amp; rhs);</span><br><span class="line"></span><br><span class="line">string <span class="keyword">operator</span>+ (<span class="type">const</span> string&amp; lhs, <span class="type">char</span> rhs);</span><br><span class="line">string <span class="keyword">operator</span>+ (string&amp;&amp; lhs, <span class="type">char</span> rhs);</span><br><span class="line">string <span class="keyword">operator</span>+ (<span class="type">char</span> lhs, <span class="type">const</span> string&amp; rhs);</span><br><span class="line">string <span class="keyword">operator</span>+ (<span class="type">char</span> lhs, string&amp;&amp; rhs);</span><br></pre></td></tr></tbody></table></figure><p>那字符串直接与数字用 <code>+</code> 相连时,会怎么样呢?</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line">string str = <span class="string">"abc"</span>;</span><br><span class="line">string b = str + <span class="number">1</span>; <span class="comment">// compile failure</span></span><br><span class="line"></span><br><span class="line">string str = <span class="string">"abc"</span>;</span><br><span class="line">string b = str + (<span class="type">char</span>) <span class="number">1</span>; <span class="comment">// compile successfully</span></span><br></pre></td></tr></tbody></table></figure><p>由于自动的类型转换只能向上转(char -&gt; int)而不能反向,所以编译是失败的,如果是引号的字符串呢?</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line">string str = <span class="string">"str"</span> + <span class="number">1</span>;</span><br><span class="line">cout &lt;&lt; str &lt;&lt; endl;</span><br><span class="line"><span class="comment">// output: tr</span></span><br><span class="line"></span><br><span class="line">string str = <span class="number">1</span> + <span class="string">"str"</span>;</span><br><span class="line">cout &lt;&lt; str &lt;&lt; endl;</span><br><span class="line"><span class="comment">// output: tr</span></span><br></pre></td></tr></tbody></table></figure><p>可见,这个加数字其实意味着字符数组的偏移,与<code>string str = &amp;"str" [1];</code>等价。在这种情况下,部分编译器会提 Warning,提醒用户这个加号并不是表示字符串的连接,防止用户受到 Java 影响误用。</p><hr><p><strong>那如何实现字符串和数字的拼接呢?</strong></p><p>对于 C 语言有经典的做法<code>sprintf</code>,与<code>printf</code>类似,这个函数的作用是格式化输出存到字符数组中。以下是该函数的一个例子,关于该函数的其他信息可以查阅<a href="https://www.cplusplus.com/reference/cstdio/sprintf/">cplusplus reference</a>。</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span> <span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="type">char</span> buffer [<span class="number">50</span>];</span><br><span class="line"> <span class="type">int</span> n, a=<span class="number">5</span>, b=<span class="number">3</span>;</span><br><span class="line"> n=<span class="built_in">sprintf</span> (buffer, <span class="string">"%d plus %d is %d"</span>, a, b, a+b);</span><br><span class="line"> <span class="built_in">printf</span> (<span class="string">"[%s] is a string %d chars long\n"</span>,buffer,n);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>一般对于 C++ 来说,如果使用的是 C99,一般来说是使用字符串流(有人好像叫它字符串操作模板类,在这里也一并列上这个名字)<code>stringstream</code>。</p><p>在 C++ 里这样的流有很多,就像 C++ 中进行文件输入输出的<code>ifstream</code> <code>ofstream</code>,如果不熟悉的话,我们可以把它类似于 C++ 的<code>cin</code>和<code>cout</code>。</p><p>这个字符串流使用的方式比较简单,样例如下:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> stringstream s;</span><br><span class="line"> s &lt;&lt; <span class="string">"str"</span> &lt;&lt; <span class="number">1</span>;</span><br><span class="line"> cout &lt;&lt; s.<span class="built_in">str</span>() &lt;&lt; endl;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// output: str1</span></span><br></pre></td></tr></tbody></table></figure><p>这样就可以用这种流的方法处理字符串拼接问题了~</p><p>Emmm,如果能把支持的 C++ 特性提升到 C11,可能就不需要那么麻烦了,我们有了<code>to_string()</code>函数,把一些数据转成字符串,样例如下:</p><figure class="highlight cpp"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;bits/stdc++.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> string str = <span class="string">"str"</span> + <span class="built_in">to_string</span>(<span class="number">1</span>);</span><br><span class="line"> cout &lt;&lt; str &lt;&lt; endl;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// output: str1</span></span><br></pre></td></tr></tbody></table></figure><p>C++11 给的新特性真的很方便,比如可能大家常用的<code>auto</code>,比如大家可能用得到的智能指针<code>shared_ptr</code>,以及更好用的 random <code>uniform_int_distribution</code> <code>uniform_real_distribution</code>,所以很多在线的考试、平台对 C++11 支持的很好。</p><p>不过正是因为 C++11 满足了很多人的需求,这些平台对以后的 C++14、C++17 甚至 C++20 支持有限,因此很多人就没法享受到例如字符串分割、函数模板的推导、<code>optional</code>之类新特性带来的好处。新特性,是真的香啊~</p>

退群杯——从闲聊到现实

作者 icy
2022年2月2日 19:00
<blockquote><p>各位 OI 退役或还没退役的选手、各位对 XCPC 感兴趣或已经参与过的选手、各位仍在关注退群杯的同学<del>以及各位乐子人</del>:</p><p>首先祝大家新年快乐!我们非常荣幸地在此宣布,经过两个多月的前期筹备,第三届「退群杯」竞赛(XCPC 分场)即将与大家见面!</p><p>本届「退群杯」与前两届不同,其内容为<strong>算法竞赛</strong>,赛制则采取大家所熟悉<del>如果不熟悉就现在熟悉</del>的 XCPC 赛制,但改为<strong>单人参赛</strong>。正赛共⑨题,时长 5 小时,计划于 2 月 12 日下午进行。为方便各位选手熟悉赛制和办赛平台,正赛前一天即 2 月 11 日将有一场时长 2 小时的热身赛。赛后,我们将公开题解(PDF 格式)。如条件允许,我们也将准备一场直播讲解。</p><p>与第二届「退群杯」一样,本届「退群杯」也将有伴随题目的剧情。我们力图为大家带来更多参赛乐趣,得益于算法竞赛题目相对较高的自由度,解谜剧情将与题目充分结合。不过请各位选手放心,即使完全不看剧情也不会影响 AK 解题。</p><p>本次比赛我们为各位参赛选手准备了丰富的奖励,包括但不限于马克杯退群杯、桌垫、亚克力挂件,解谜成功更有机会领取神秘奖品!</p><p>退役 OIer 文化课交流群 第三届「退群杯」全体 STAFF</p><p>2022 年 2 月 1 日</p></blockquote><p>「退群杯」中的「退群」二字,可能与大家所熟悉的退群含义不同,指的是退役 OIer 文化课交流群(群号 1107305016,其实由于 OIer 的不断迭代,其实也已经成为了一个社交文化圈,并衍生出了若干子群,退群杯群就是其中一个)。</p><p>由于自招取消,强基计划开始,高中生们也开始用脚投票,高中生 OIer 也越来越少。第三届「退群杯」即将开始,作为第一届第二届「退群杯」英语科出题人(前两届是文化课模拟考试,第三届开始为 XCPC 类考试),很高兴学弟们接过了「退群杯」的接力棒,让这种 Oier 学长学姐带学弟学妹的风气继续保持下去。</p><h2 id="从无到有——第一届退群杯"><a href="#从无到有——第一届退群杯" class="headerlink" title="从无到有——第一届退群杯"></a>从无到有——第一届退群杯</h2><p>由于主群退群「退役 OIer 文化课交流群」迭代了数次,导致一些历史不可考,此处仅能通过一部分聊天记录和个人回忆来还原历史。</p><p>其他组的成员看到都有在<a href="https://www.zhihu.com/question/371927453">知乎上</a>发表自己角度的经历,我也分享一下我的经历。</p><p>当时看到了主群的群公告,征集一些群友一起给 20 级高中毕业生出套模拟题。当时正值疫情最严重的时候,各地也没法如期开学,大学生们也省去了各种各样非必要的活动,剩下的时间正好可以用来出题,于是我报名了英语组,也成为了第一届退群杯的英语组负责人。</p><p>当时一起去完成的有六七人,每人从选素材到高考题型的适配化,最后拼在一起,就形成了一套英语科试题。对于听力,当时没有条件去录音(我们的口音也不合适),然后我们最后的决定是找一些听力题拼在一起,我使用 Au 把各段听力进行了拼接,把 ksyx 用谷歌娘读的考试名称组装在一起,导出了听力音频。</p><p>当初一个组的 SDUer 还有 Kamigen 和 Raffica,在后期高考结束志愿选择的时候,我们三位与其他群友劝退自己学校的行为相反,非常欢迎大家来 SDU,于是我们三位的行为就非常亮眼,成为了退群杯群里的 SDU 三人组。</p><p>在这次英语出题里,我记得我们还借此成立了一个英语口语交流组,以讨论班的形式举行。每位同学准备一次讨论的材料,然后其他人就会一起用英语讨论这些话题。当时因为有泰山学堂周小兰老师的英语口语课,我就拉了学堂的 Arno 同学,一起参与这个口语交流。当然在英语口语课结束之后那个群就长期咕咕咕了<span class="github-emoji"><span>😎</span><img src="https://github.githubassets.com/images/icons/emoji/unicode/1f60e.png?v8" aria-hidden="true" onerror="this.parent.classList.add('github-emoji-fallback')"></span>。</p><p>记得在公告之前,大家讨论宣传文案,记得当时借鉴的文案来自 UOJ——</p><blockquote><p>10 月 26 号,星期日晚上 7:00~10:00 开始公测!</p><p>UOJ 就要迈出第一步了!欢迎大家来捧场!</p><p>由于是公测,目的主要在于测 BUG,所以是人民群众喜闻乐见的原题大战。</p><p>不过不用担心!我觉得我选的题还是蛮好玩的!</p><p>为了应景,题目难度、部分分设置都和联赛差不多,对于想要为 NOIP 准备的同学而言是个不可错过的机会!</p><p>赛后我们会有详细的题解。</p><p>出题人有 ydc, vfleaking。</p><p>题目三小时三道题的 OI 赛制,</p><p>难度高仿 noip,</p><p>大家也不用担心掉 rating,</p><p>因为这一场是 unrated 哟,还怕什么!</p><p>这是神犇强者展现实力,虐爆全场的时候!</p><p>这是大众选手增强信心,迈向未来的时候!</p><p>还等什么?来战吧!</p><p>有什么问题请在下面留言。</p></blockquote><p>最后一版文案如下(还好当时截了个图,不然真就没记录了 2333)</p><img src="../images/2022020301.jpg" alt="第一届退群杯公告文案" style="zoom: 33%;"><p>以及当时做的宣传图,宣传图好像是 Kamigen 同学使用 SCP 同人图 P 出来的</p><img src="../images/2022020302.jpg" alt="第一届退群杯宣传海报" style="zoom: 50%;"><p>Kamigen 小姐姐真的很认真负责,最后英语科试题的讲评是她讲的,虽然由于设备原因,笔记本噪音比较大,但是小姐姐甜美的嗓音还是非常动人的(</p><p>我记得当时答题卡,一直不知道该怎么还原一个好看的答题卡,于是我盯上了当时高中用的“好分数”平台。果然高中班主任老师的密码还是 6 位数生日,然后我就用他的权限做了各科的答题卡,导出后删除(抹除痕迹)。</p><h2 id="从小试牛刀到一次突破——第二届退群杯"><a href="#从小试牛刀到一次突破——第二届退群杯" class="headerlink" title="从小试牛刀到一次突破——第二届退群杯"></a>从小试牛刀到一次突破——第二届退群杯</h2><p>第二年,出题组们又开始“搞事情”了。今年大家想做一个“剧情向”的比赛,大家按照不同的顺序完成题目,可以解锁不同的剧情。</p><p>在这一年,出题组里又来了许多新血液——例如第一届退群杯受众 MikuNotFound 同学,搭建了第二届退群杯的前端。</p><p>这一次,退群杯甚至有了<a href="https://tqb.kskun.com/">官网</a>,用的是 KS 同学的域名和后端。大家可以通过在界面上注册登录进行做题,解锁剧情。</p><p>在<a href="https://tqb.kskun.com/doc/staff">staff 列表</a>的同学们越来越多啦,大家不光可以展示自己的 id 和头像,还可以放上一句话简介。数学组比较会玩,而英语组就没能提前商量好<span class="github-emoji"><span>😰</span><img src="https://github.githubassets.com/images/icons/emoji/unicode/1f630.png?v8" aria-hidden="true" onerror="this.parent.classList.add('github-emoji-fallback')"></span>。</p><img src="../images/2022020303.png" alt="命题组名单节选" style="zoom:67%;"><p>附第二届退群杯相关知乎文档</p><p><a href="https://zhuanlan.zhihu.com/p/350271701">2021 年退役 OIer 文化课交流群联合竞赛暨第二届“退群杯”预告</a></p><p><a href="https://zhuanlan.zhihu.com/p/343210737">第二届退群杯的一些近况</a></p><p><a href="https://zhuanlan.zhihu.com/p/353375513">第二届“退群杯”正式上线公告</a></p><p><a href="https://zhuanlan.zhihu.com/p/351085436">第二届“退群杯”剧情与数值系统前瞻</a></p>

基于 GitHub Actions + 宝塔 + DCDN 的 Hexo 博客配置

作者 icy
2022年1月21日 12:00
<p>不知不觉博客已经开启接近两年了,一直以来本站使用的技术是阿里云全站加速+GitHub Pages 的部署方式。不过由于国内 CDN 回源 Pages 服务器还是存在着连接质量差的问题,在两周年之际,本站使用了基于 GitHub Actions + 宝塔 + 阿里云全站加速的搭建模式。</p><h2 id="好用的-GitHub-Actions"><a href="#好用的-GitHub-Actions" class="headerlink" title="好用的 GitHub Actions"></a>好用的 GitHub Actions</h2><p>Actions 是个好东西,一个月有 3000 分钟的私有仓库时长配额(GitHub Pro 或 GitHub Student Pack),普通用户也有 2000 分钟每月的私有仓库时长配额。值得一提的是,<strong>对于公开的仓库,GitHub Actions 是全免费、随便用的</strong>(单帐户同类镜像仅允许同时开启一个,否则会排队等待上一个 Job 执行完毕,单 Job 运行最长时间 6 小时)。</p><p>相比于<code>Travis CI</code>之类的持续集成工具来说,GitHub Actions 对同平台的仓库相比更加具有便利性(至少 clone 个代码是真心快<span class="github-emoji"><span>😂</span><img src="https://github.githubassets.com/images/icons/emoji/unicode/1f602.png?v8" aria-hidden="true" onerror="this.parent.classList.add('github-emoji-fallback')"></span>)。</p><p>Actions 不只是可以做 Release 导出、项目构建之类的操作,由于其定时运行的特性,常被大家用来渲染账户成就(如 star 超过 3k 的<a href="https://github.com/lowlighter/metrics">Metrics</a>,作者 icy 也部署了其自动生成<a href="https://github.com/icy-blue">个人 Profile</a>),<a href="https://github.com/zhangt2333/actions-SduElectricityReminder">自动体温填报</a>等操作。</p><h2 id="Hexo-类博客与-GitHub-Pages"><a href="#Hexo-类博客与-GitHub-Pages" class="headerlink" title="Hexo 类博客与 GitHub Pages"></a>Hexo 类博客与 GitHub Pages</h2><p>Hexo 是目前来说非常普遍使用的博客了,在 GitHub 上有许多主题,如本站使用的是<a href="http://blinkfox.com/">闪烁之狐</a>的<a href="matery">matery</a>主题,在此也再次向作者表示感谢。用户可以轻易通过几行代码就可以生成一个简单的博客,编写 markdown 的博客内容,并通过一句简单的<code>hexo deploy</code>或者<code>hexo d</code>就可以将自己博客部署到仓库中。</p><p>虽然 Hexo 最近一直在更新,不过 Hexo 很多功能插件的依赖项,爆出了安全漏洞,如<code>hexo-renderer-marked</code>使用的<code>marked@^2.1.3</code>,爆出了<a href="https://github.com/advisories/GHSA-5v2h-r2cx-5xgj">GHSA-5v2h-r2cx-5xgj</a>和<a href="https://github.com/advisories/GHSA-rrrm-qjm4-v8hf">GHSA-rrrm-qjm4-v8hf</a>安全漏洞,而截至目前(2022 年 1 月 21 日)插件维护方尚未对该插件的依赖版本升级至<code>4.0.10</code>以上。</p><p>为了更加的傻瓜式,GitHub 还提供了 Pages 服务,帮助直接将一个静态网站部署到<code>https://&lt;username&gt;.github.io/&lt;repository&gt;/</code>上。经过一段时间的自动部署,我们就可以访问自己刚刚部署好的博客界面了。</p><p>GitHub Pages 可以满足绝大多数海外用户的使用需求,因为 Pages 对于一个简单的非商业项目来说配额已经十分充足——</p><blockquote><p>GitHub Pages sites are subject to the following usage limits:</p><ul><li>GitHub Pages source repositories have a recommended limit of 1GB. </li><li>Published GitHub Pages sites may be no larger than 1 GB.</li><li>GitHub Pages sites have a <em>soft</em> bandwidth limit of 100GB per month.</li><li>GitHub Pages sites have a <em>soft</em> limit of 10 builds per hour.</li></ul></blockquote><p>配额说明来自于 2022 年 1 月 21 日的<a href="https://docs.github.com/cn/pages/getting-started-with-github-pages/about-github-pages#usage-limits">GitHub Docs</a>,不过对于国内用户来说更大的问题是,由于海外带宽的限制以及一些原因,国内用户访问 GitHub 及 GitHub Pages 经常出现连接问题。自己好不容易搭建的博客,发现国内的朋友们,尤其是不太会科学使用网络的朋友们,打不开自己的博客,可能也会非常沮丧吧<span class="github-emoji"><span>😢</span><img src="https://github.githubassets.com/images/icons/emoji/unicode/1f622.png?v8" aria-hidden="true" onerror="this.parent.classList.add('github-emoji-fallback')"></span><span class="github-emoji"><span>😱</span><img src="https://github.githubassets.com/images/icons/emoji/unicode/1f631.png?v8" aria-hidden="true" onerror="this.parent.classList.add('github-emoji-fallback')"></span>。</p><img src="../images/2022012101.jpg" alt="真·二次元" style="zoom: 33%;"><h2 id="使用-CDN-分发与加速"><a href="#使用-CDN-分发与加速" class="headerlink" title="使用 CDN 分发与加速"></a>使用 CDN 分发与加速</h2><p>既然 GitHub Pages 打不开,我们可能就想,有没有其他更合适的办法,让大陆用户正常地访问自己的网页呢?当然有——icy 和他的朋友<a href="https://www.kskun.com/">KS</a>就不约而同地使用了 CDN 分发和加速。内容分发网络 CDN(Content Delivery Network)可以将用户的请求负载均衡到不同的缓存节点,当用户的请求到达时,CDN 将判断访问者 IP,将请求按优先级分发给源站或最近缓存节点,以加快用户的请求速度。</p><p>以当前的场景来说,在网站设置了 CDN(设置解析、源站、设置缓存目录及后缀、设置缓存过期时限、配置 HTTPS 等)后,用户请求到 CDN 时,CDN 会先判断是否存在缓存,有缓存将请求转给缓存,没有缓存会由 CDN 请求源站,按照配置进行缓存,并将结果返回给用户。由于 CDN 和 Pages 的连接是畅通的,用户和 CDN 的连接是畅通的,于是通过 CDN 作为跳板,实现了 GitHub Pages 的高速访问。自然,也付出了 CDN 的使用费用(见<a href="./CDN%E6%98%AF%E4%BB%80%E4%B9%88%E5%8F%AF%E4%BB%A5%E5%90%83%E5%90%97.md">CDN 是是什么可以吃吗</a>)。</p><p>值得注意的是,如果使用国内 CDN,且对国内用户提供服务时,要求域名进行 ICP 备案。</p><h2 id="使用服务器部署博客"><a href="#使用服务器部署博客" class="headerlink" title="使用服务器部署博客"></a>使用服务器部署博客</h2><p>既然使用国内 CDN 了,何不直接使用国内的服务器呢~</p><p>在 2021 年双十一期间<del>(活动已结束,价格供参考)</del>,腾讯云打出了 2 核 4GB 8Mbps 月流量 1200GB 的轻量应用服务器一年 70 元、三年 198 元的活动,吸引了许多建站开发者。按购买 3 年计算,月均消费 5.5 元,是非常适合学生党“折腾”的。</p><img src="../images/2022012103.png" alt="腾讯云 2021 年双十一活动界面" style="zoom: 67%;"><p>一般来说,我们的代码编辑仍然还是在自己的电脑上,而服务器仅仅是对于 Hexo 生成的静态文件的展示(一般来说不会在服务器上使用 Node.js 展示 Hexo 服务,因为对于静态网站,在线解析生成其实是一种浪费)。此时我们其实就可以使用上面所说的<code>hexo d</code>命令,把静态网站部署到<code>Gitee</code>上(因为服务器去访问 GitHub 还是非常困难的),然后在服务器上同步拉取即可。</p><p>同步拉取的方式,最简单的办法就是在服务器上设置循环脚本,每隔一段时间拉取仓库`。</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">git fetch --all </span><br><span class="line">git reset --hard origin/&lt;branch&gt; </span><br><span class="line">git pull</span><br></pre></td></tr></tbody></table></figure><p>由于 Hexo 的部署是强制推送,不存储过往的界面,以减少整个仓库的大小,我们在拉取仓库的时候使用强制覆盖本地仓库的方式进行更新。考虑到 SSH 会话运行的程序在 SSH 连接断开后不太稳定,我们可以使用终端复用器 Tmux(terminal multiplexer),使得脚本在后台使用。</p><p>关于 Tmux,在<a href="https://www.ruanyifeng.com/blog/2019/10/tmux.html">阮一峰博客</a>那里有一个很好的介绍,我们可以通过<code>tmux new -s &lt;session-name&gt;</code>创建一个新的会话,然后在会话里运行 Python 脚本,一个简单的 Python 脚本可能像这样:</p><figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> threading</span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> time.sleep(<span class="number">60</span>) <span class="comment"># 60 seconds</span></span><br><span class="line"> os.system(<span class="string">"git fetch --all"</span>)</span><br><span class="line"> os.system(<span class="string">"git reset --hard origin/master"</span>)</span><br><span class="line"> os.system(<span class="string">"git pull"</span>)</span><br></pre></td></tr></tbody></table></figure><p>当然读者也可以通过写 bash 脚本解决问题。我们在刚刚建立的 session 中运行脚本后关闭 SSH 会话或按下<code>Ctrl + B d</code>将会话切回到后台,即可实现脚本的后台运行。</p><h2 id="WebHook"><a href="#WebHook" class="headerlink" title="WebHook"></a>WebHook</h2><p>循环拉取仓库好像挺傻的,不美观,那能不能让仓库主动去推送更新消息呢?WebHook 可以帮助我们了解这样的信息。</p><p>WebHook 是一种 API 概念,当仓库有变动时(新 Push、新 PR、新 Issue 等等),代码托管平台会给仓库预留的链接发送 POST 请求。我们一般在服务器的某个端口,监听这类的 POST 请求(用 Node.js、Python 等程序可以很快地编写一个监听 POST 请求的 Server,Python + Flask 的实现可以参考<a href="https://www.cnblogs.com/Alin-2016/p/7422987.html">python+flask:实现 POST 接口功能</a>,Go 语言可以参考<a href="https://blog.csdn.net/qq_27312939/article/details/110632297">GO 接收 GET/POST 参数以及发送 GET/POST 请求</a>,Node.js 可以参考<a href="https://blog.csdn.net/w390058785/article/details/79770540">【node.js】处理前端提交的 POST 请求</a>,其他语言不再列举)。根据<a href="https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads">GitHub</a>和<a href="https://gitee.com/help/articles/4271">Gitee</a>的文档,我们可以在服务器上解析平台上发生的事件,并进行一定的处理。</p><p>以监测平台有新提交自动拉取代码这个需求来说,我们可以设置相关的触发器,设置相关的钩子地址,并设置鉴权(以免其他用户滥发),当代码托管平台的仓库有了新提交,我们的服务器则会收到 POST 消息,以便实现。</p><p>作为 POST 的回应,建议在服务器上回复 JSON,并设置<code>HTTP status 200</code>,以免部分平台认定推送失败。</p><h2 id="宝塔的-WebHook"><a href="#宝塔的-WebHook" class="headerlink" title="宝塔的 WebHook"></a>宝塔的 WebHook</h2><p>不愿意自己写代码监听?宝塔软件商店里的<code>宝塔WebHook</code>可以帮你实现 WebHook 的接收。</p><p>在宝塔的软件商店中,搜索<code>WebHook</code>,找到<code>宝塔WebHook</code>,便可以在里面添加钩子。</p><img src="../images/2022012104.png" alt="宝塔 WebHook 界面" style="zoom: 80%;"><p>添加完后,我们可以看到自己添加钩子的情况,包括钩子名称、添加时间、近期调用、调用次数等信息。我们还可以查看密钥查看钩子的密钥,以便鉴权。在查看密钥的界面,宝塔提供了一个示例的链接,其中参数包括密钥和脚本参数,我们可以按需进行调整,并把最后的链接放在代码仓库-设置-WebHook 的相应位置(由于我们的鉴权密钥在 URL 的参数上,所以对于 GitHub 和 Gitee 来说,密码处可以随便写)。</p><p><img src="/../images/2022012105.png" alt="宝塔WebHook界面"></p><p><img src="/../images/2022012106.png" alt="宝塔WebHook查看密钥界面"></p><p>下面是 GitHub 和 Gitee 的 WebHook 设置示例:</p><img src="../images/2022012107.png" alt="GItHub的WebHook设置示例" style="zoom: 50%;"><img src="../images/2022012108.png" alt="Gitee的WebHook设置示例" style="zoom:50%;"><p>做到这里,我们就可以让服务器自动跟进代码仓库的更新了。</p><h2 id="使用-GitHub-Actions-实现静态界面的生成"><a href="#使用-GitHub-Actions-实现静态界面的生成" class="headerlink" title="使用 GitHub Actions 实现静态界面的生成"></a>使用 GitHub Actions 实现静态界面的生成</h2><p>每次我们发现,自己的静态界面确实已经保存在代码仓库中了,而原始文件却没能保存。有的朋友会选择再建一个仓库专门提交原始文件,先提交静态界面,再提交原始文件,提交两次可能总是会遗漏,那有没有只提交一次的办法呢?</p><p>答案自然是肯定的,我们可以通过使用 GitHub Actions 实现静态界面的生成。</p><p>下面是一个简单的配置文件,修改用户名邮箱等信息后,将其放在原始博客仓库的<code>.github/workflows/</code>内,以 yml 格式为文件名后缀,配置就已经部署好了——吗?</p><figure class="highlight yaml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">Hexo</span> <span class="string">Deploy</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line"> <span class="comment"># 如果你的默认 branch 是 master,将下面的 main 改为 master</span></span><br><span class="line"> <span class="attr">push:</span></span><br><span class="line"> <span class="attr">branches:</span> [ <span class="string">main</span> ]</span><br><span class="line"> <span class="attr">pull_request:</span></span><br><span class="line"> <span class="attr">branches:</span> [ <span class="string">main</span> ]</span><br><span class="line"> <span class="attr">workflow_dispatch:</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line"> <span class="attr">deploy-github:</span></span><br><span class="line"> <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line"></span><br><span class="line"> <span class="attr">steps:</span></span><br><span class="line"> <span class="comment"># 设置服务器时区为东八区 </span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Set</span> <span class="string">time</span> <span class="string">zone</span></span><br><span class="line"> <span class="attr">run:</span> <span class="string">sudo</span> <span class="string">timedatectl</span> <span class="string">set-timezone</span> <span class="string">'Asia/Shanghai'</span></span><br><span class="line"></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Checkout</span> <span class="string">source</span></span><br><span class="line"> <span class="attr">uses:</span> <span class="string">actions/checkout@v2.4.0</span></span><br><span class="line"></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Setup</span> <span class="string">Node.js</span></span><br><span class="line"> <span class="attr">uses:</span> <span class="string">actions/setup-node@v1</span></span><br><span class="line"> <span class="attr">with:</span></span><br><span class="line"> <span class="attr">node-version:</span> <span class="string">'12'</span></span><br><span class="line"></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Setup</span> <span class="string">Hexo</span></span><br><span class="line"> <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string"> npm install hexo-cli -g</span></span><br><span class="line"><span class="string"> npm install hexo-deployer-git --save</span></span><br><span class="line"><span class="string"> npm install</span></span><br><span class="line"><span class="string"></span> </span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Setup</span> <span class="string">Git</span></span><br><span class="line"> <span class="attr">env:</span></span><br><span class="line"> <span class="attr">ACTION_DEPLOY_KEY:</span> <span class="string">${{</span> <span class="string">secrets.HEXO_DEPLOY_KEY</span> <span class="string">}}</span></span><br><span class="line"> <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string"> mkdir -p ~/.ssh/</span></span><br><span class="line"><span class="string"> echo "$ACTION_DEPLOY_KEY" &gt; ~/.ssh/id_rsa</span></span><br><span class="line"><span class="string"> chmod 700 ~/.ssh</span></span><br><span class="line"><span class="string"> chmod 600 ~/.ssh/id_rsa</span></span><br><span class="line"><span class="string"> ssh-keyscan github.com &gt;&gt; ~/.ssh/known_hosts</span></span><br><span class="line"><span class="string"> git pull</span></span><br><span class="line"><span class="string"> git config --global user.email "&lt;email&gt;"</span></span><br><span class="line"><span class="string"> git config --global user.name "&lt;username&gt;"</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="comment"># 如果在自己的_config.yml已配置,则直接运行最后两条命令即可</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Deploy</span></span><br><span class="line"> <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string"> cat &gt;&gt; _config.yml &lt;&lt;EOF</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"> <span class="comment"># 自动部署配置</span></span><br><span class="line"> <span class="attr">deploy:</span></span><br><span class="line"> <span class="attr">type:</span> <span class="string">git</span></span><br><span class="line"> <span class="attr">repo:</span> <span class="string">git@github.com:&lt;username&gt;/&lt;username&gt;.github.io.git</span></span><br><span class="line"> <span class="attr">branch:</span> <span class="string">hexo</span></span><br><span class="line"></span><br><span class="line"> <span class="string">EOF</span></span><br><span class="line"> <span class="string">hexo</span> <span class="string">clean</span></span><br><span class="line"> <span class="string">hexo</span> <span class="string">deploy</span></span><br></pre></td></tr></tbody></table></figure><p>去部署一个仓库,我们当然是要提供一些权限让它有权更改目的仓库,我们到目的仓库<code>&lt;username&gt;.github.io</code>的设置界面,找到<code>Deploy keys</code>后在右上角添加一对公钥(为了避免后期权限混乱的麻烦,建议新建一对新的公钥私钥对,公钥私钥的生成方式可以参考<a href="https://gitee.com/help/articles/4181">Gitee 的帮助文档</a>),将公钥放在<code>Key</code>区域,标题任取即可,<strong>注意勾选<code>Allow write access</code>,否则仅有只读权限,仍然无法部署。</strong></p><p><img src="/../images/2022012109.png" alt="添加仓库部署可信公钥"></p><p>现在目的仓库就认可了这个公钥,接下来我们要把私钥交给源仓库。直接放在仓库内是不保险的,因为我们 clone 了这个仓库就可以看到私钥明文,即便是私有仓库但是如果存储该仓库的主机被攻破,仍然可以得到私钥的明文。因此我们应该放在一个更加合适的位置,就比如仓库设置中的<code>Secret</code>区域。</p><p><img src="/../images/2022012110.png" alt="仓库 Secret 区"></p><p>我们在右上角添加新密钥,命名为<code>HEXO_DEPLOY_KEY</code>(如修改需要同步修改上面 yml 文件的名称)。密钥粘贴时,需将<code>-----BEGIN OPENSSH PRIVATE KEY-----</code>之类的标识行一同复制,在<code>-----END OPENSSH PRIVATE KEY-----</code>后最好换一行(即光标在密钥下一行)。</p><p><strong>请注意,密钥写入后无法查看,只能覆盖,如丢失需要重新生成公钥私钥对。</strong></p><p><img src="/../images/2022012111.png"></p><p>此时,用于部署的仓库就有了部署权限,在每次用户的 Push 操作和 PR 操作后,可以把生成的静态目录推送的目标仓库了。</p><h2 id="放在最后"><a href="#放在最后" class="headerlink" title="放在最后"></a>放在最后</h2><p>篇幅有点长了,剩下的当作下期预告吧——</p><ul><li><p>使用 GitHub Actions 实现仓库从 GitHub 到 Gitee 的同步</p></li><li><p>使用 Gitee Pages</p></li><li><p>做一个合适的负载均衡</p></li><li><p>使用 Lint-md 让博客内容排版更加规范</p></li></ul>
❌
❌