<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Blog of TheChef</title>
  
  
  <link href="http://example.com/atom.xml" rel="self"/>
  
  <link href="http://example.com/"/>
  <updated>2025-04-19T07:55:09.112Z</updated>
  <id>http://example.com/</id>
  
  <author>
    <name>TheChef</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>LLM之分词器(tokenizer)</title>
    <link href="http://example.com/2025/04/19/LLM%E4%B9%8B%E5%88%86%E8%AF%8D%E5%99%A8(tokenizer)/"/>
    <id>http://example.com/2025/04/19/LLM%E4%B9%8B%E5%88%86%E8%AF%8D%E5%99%A8(tokenizer)/</id>
    <published>2025-04-19T07:28:31.000Z</published>
    <updated>2025-04-19T07:55:09.112Z</updated>
    
    <content type="html"><![CDATA[<h1 id="LLM之分词器-tokenizer"><a href="#LLM之分词器-tokenizer" class="headerlink" title="LLM之分词器(tokenizer)"></a>LLM之分词器(tokenizer)</h1><h2 id="一、引言"><a href="#一、引言" class="headerlink" title="一、引言"></a>一、引言</h2><p>大语言模型在自然语言处理领域取得了巨大成功，而分词器作为其关键组成部分，对模型的性能和效果有着显著影响。今天，我们将探讨四种常见的分词方法：BPE、WordPiece、SentencePiece 和 unigram，剖析它们的技术原理、实现细节及应用场景。</p><p>在此之前，我们需要了解一下什么是tokenizition。</p><p>任何一段文本，输入给模型，都是要转换成一串embedding。这个过程简单概括为：</p><ol><li>分词，并把词转换为token（即词的ID）</li><li>token转换成embedding</li></ol><p>而tokenization就是在做这第一步。而对于第二步就是常见的Embedding查表操作，即根据token_id的值，去Embedding矩阵中查找第token_id行的数据作为embedding。</p><span id="more"></span><h2 id="二、方法介绍"><a href="#二、方法介绍" class="headerlink" title="二、方法介绍"></a>二、方法介绍</h2><h3 id="1-BPE"><a href="#1-BPE" class="headerlink" title="1. BPE"></a>1. BPE</h3><p>BPE 是一种基于频率的分词算法，最初用于机器翻译任务中的词汇扩展。其核心思想是通过不断合并文本中出现频率最高的字节对来构建词汇表。具体来说，首先将文本中的每个字符视为一个独立的 token，然后统计所有相邻字符对的频率，选择最频繁的字符对进行合并，并更新词汇表和文本表示，重复这一过程直到达到预设的词汇表大小。其实现方法如下</p><ol><li>初始化词汇表：以字符为粒度，将文本中的每个字符作为初始词汇表的元素。</li><li>统计字节对频率：遍历文本，统计所有相邻字符对的出现次数。</li><li>合并高频字节对：选择频率最高的字节对进行合并，生成新的 token，并更新词汇表和文本表示。</li><li>迭代更新：重复统计频率和合并操作，直到词汇表大小达到设定值。</li></ol><p>Byte-Pair Encoding(BPE)是最广泛采用的subword分词器。</p><ul><li>训练方法：从字符级的小词表出发，训练产生合并规则以及一个词表</li><li>编码方法：将文本切分成字符，再应用训练阶段获得的合并规则</li><li>经典模型：GPT, GPT-2, RoBERTa, BART, LLaMA, ChatGLM等</li></ul><h4 id="1-1-训练阶段"><a href="#1-1-训练阶段" class="headerlink" title="1.1. 训练阶段"></a>1.1. 训练阶段</h4><p>在训练环节，目标是给定语料，通过训练算法，生成合并规则和词表。 BPE算法是从一个字符级别的词表为基础，合并pair并添加到词表中，逐步形成大词表。合并规则为选择相邻pair词频最大的进行合并。</p><p>假定训练的语料(已归一化处理)为4个句子。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">corpus = [</span><br><span class="line">    &quot;This is the Hugging Face Course.&quot;,</span><br><span class="line">    &quot;This chapter is about tokenization.&quot;,</span><br><span class="line">    &quot;This section shows several tokenizer algorithms.&quot;,</span><br><span class="line">    &quot;Hopefully, you will be able to understand how they are trained and generate tokens.&quot;,</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>首先进行预切分处理。这里采用gpt2的预切分逻辑。 具体会按照空格和标点进行切分，并且空格会保留成特殊的字符“Ġ”。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">from transformers import AutoTokenizer</span><br><span class="line"></span><br><span class="line"># init pre tokenize function</span><br><span class="line">gpt2_tokenizer = AutoTokenizer.from_pretrained(&quot;gpt2&quot;)</span><br><span class="line">pre_tokenize_function = gpt2_tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str</span><br><span class="line"></span><br><span class="line"># pre tokenize</span><br><span class="line">pre_tokenized_corpus = [pre_tokenize_str(text) for text in corpus]</span><br></pre></td></tr></table></figure><p>获得的pre_tokenized_corpus如下，每个单元分别为[word, (start_index, end_index)]</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">[</span><br><span class="line">    [(&#x27;This&#x27;, (0, 4)), (&#x27;Ġis&#x27;, (4, 7)), (&#x27;Ġthe&#x27;, (7, 11)), (&#x27;ĠHugging&#x27;, (11, 19)), (&#x27;ĠFace&#x27;, (19, 24)), (&#x27;ĠCourse&#x27;, (24, 31)), (&#x27;.&#x27;, (31, 32))], </span><br><span class="line">    [(&#x27;This&#x27;, (0, 4)), (&#x27;Ġchapter&#x27;, (4, 12)), (&#x27;Ġis&#x27;, (12, 15)), (&#x27;Ġabout&#x27;, (15, 21)), (&#x27;Ġtokenization&#x27;, (21, 34)), (&#x27;.&#x27;, (34, 35))], </span><br><span class="line">    [(&#x27;This&#x27;, (0, 4)), (&#x27;Ġsection&#x27;, (4, 12)), (&#x27;Ġshows&#x27;, (12, 18)), (&#x27;Ġseveral&#x27;, (18, 26)), (&#x27;Ġtokenizer&#x27;, (26, 36)), (&#x27;Ġalgorithms&#x27;, (36, 47)), (&#x27;.&#x27;, (47, 48))], </span><br><span class="line">    [(&#x27;Hopefully&#x27;, (0, 9)), (&#x27;,&#x27;, (9, 10)), (&#x27;Ġyou&#x27;, (10, 14)), (&#x27;Ġwill&#x27;, (14, 19)), (&#x27;Ġbe&#x27;, (19, 22)), (&#x27;Ġable&#x27;, (22, 27)), (&#x27;Ġto&#x27;, (27, 30)), (&#x27;Ġunderstand&#x27;, (30, 41)), (&#x27;Ġhow&#x27;, (41, 45)), (&#x27;Ġthey&#x27;, (45, 50)), (&#x27;Ġare&#x27;, (50, 54)), (&#x27;Ġtrained&#x27;, (54, 62)), (&#x27;Ġand&#x27;, (62, 66)), (&#x27;Ġgenerate&#x27;, (66, 75)), (&#x27;Ġtokens&#x27;, (75, 82)), (&#x27;.&#x27;, (82, 83))]</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>进一步统计每个整词的词频</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">word2count = defaultdict(int)</span><br><span class="line">for split_text in pre_tokenized_corpus:</span><br><span class="line">    for word, _ in split_text:</span><br><span class="line">        word2count[word] += 1</span><br></pre></td></tr></table></figure><p>获得word2count如下</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">defaultdict(&lt;class &#x27;int&#x27;&gt;, &#123;&#x27;This&#x27;: 3, &#x27;Ġis&#x27;: 2, &#x27;Ġthe&#x27;: 1, &#x27;ĠHugging&#x27;: 1, &#x27;ĠFace&#x27;: 1, &#x27;ĠCourse&#x27;: 1, &#x27;.&#x27;: 4, &#x27;Ġchapter&#x27;: 1, &#x27;Ġabout&#x27;: 1, &#x27;Ġtokenization&#x27;: 1, &#x27;Ġsection&#x27;: 1, &#x27;Ġshows&#x27;: 1, &#x27;Ġseveral&#x27;: 1, &#x27;Ġtokenizer&#x27;: 1, &#x27;Ġalgorithms&#x27;: 1, &#x27;Hopefully&#x27;: 1, &#x27;,&#x27;: 1, &#x27;Ġyou&#x27;: 1, &#x27;Ġwill&#x27;: 1, &#x27;Ġbe&#x27;: 1, &#x27;Ġable&#x27;: 1, &#x27;Ġto&#x27;: 1, &#x27;Ġunderstand&#x27;: 1, &#x27;Ġhow&#x27;: 1, &#x27;Ġthey&#x27;: 1, &#x27;Ġare&#x27;: 1, &#x27;Ġtrained&#x27;: 1, &#x27;Ġand&#x27;: 1, &#x27;Ġgenerate&#x27;: 1, &#x27;Ġtokens&#x27;: 1&#125;)</span><br></pre></td></tr></table></figure><p>因为BPE是从字符级别的小词表，逐步合并成大词表，所以需要先获得字符级别的小词表。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">vocab_set = set()</span><br><span class="line">for word in word2count:</span><br><span class="line">    vocab_set.update(list(word))</span><br><span class="line">vocabs = list(vocab_set)</span><br></pre></td></tr></table></figure><p>获得的初始小词表vocabs如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[&#x27;i&#x27;, &#x27;t&#x27;, &#x27;p&#x27;, &#x27;o&#x27;, &#x27;r&#x27;, &#x27;m&#x27;, &#x27;e&#x27;, &#x27;,&#x27;, &#x27;y&#x27;, &#x27;v&#x27;, &#x27;Ġ&#x27;, &#x27;F&#x27;, &#x27;a&#x27;, &#x27;C&#x27;, &#x27;H&#x27;, &#x27;.&#x27;, &#x27;f&#x27;, &#x27;l&#x27;, &#x27;u&#x27;, &#x27;c&#x27;, &#x27;T&#x27;, &#x27;k&#x27;, &#x27;h&#x27;, &#x27;z&#x27;, &#x27;d&#x27;, &#x27;g&#x27;, &#x27;w&#x27;, &#x27;n&#x27;, &#x27;s&#x27;, &#x27;b&#x27;]</span><br></pre></td></tr></table></figure><p>基于小词表就可以对每个整词进行切分</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">word2splits = &#123;word: [c for c in word] for word in word2count&#125;</span><br><span class="line"></span><br><span class="line">&#x27;This&#x27;: [&#x27;T&#x27;, &#x27;h&#x27;, &#x27;i&#x27;, &#x27;s&#x27;], </span><br><span class="line">&#x27;Ġis&#x27;: [&#x27;Ġ&#x27;, &#x27;i&#x27;, &#x27;s&#x27;], </span><br><span class="line">&#x27;Ġthe&#x27;: [&#x27;Ġ&#x27;, &#x27;t&#x27;, &#x27;h&#x27;, &#x27;e&#x27;], </span><br><span class="line">...</span><br><span class="line">&#x27;Ġand&#x27;: [&#x27;Ġ&#x27;, &#x27;a&#x27;, &#x27;n&#x27;, &#x27;d&#x27;], </span><br><span class="line">&#x27;Ġgenerate&#x27;: [&#x27;Ġ&#x27;, &#x27;g&#x27;, &#x27;e&#x27;, &#x27;n&#x27;, &#x27;e&#x27;, &#x27;r&#x27;, &#x27;a&#x27;, &#x27;t&#x27;, &#x27;e&#x27;], </span><br><span class="line">&#x27;Ġtokens&#x27;: [&#x27;Ġ&#x27;, &#x27;t&#x27;, &#x27;o&#x27;, &#x27;k&#x27;, &#x27;e&#x27;, &#x27;n&#x27;, &#x27;s&#x27;]</span><br></pre></td></tr></table></figure><p>基于word2splits统计vocabs中相邻两个pair的词频pair2count</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">def _compute_pair2score(word2splits, word2count):</span><br><span class="line">    pair2count = defaultdict(int)</span><br><span class="line">    for word, word_count in word2count.items():</span><br><span class="line">        split = word2splits[word]</span><br><span class="line">        if len(split) == 1:</span><br><span class="line">            continue</span><br><span class="line">        for i in range(len(split) - 1):</span><br><span class="line">            pair = (split[i], split[i + 1])</span><br><span class="line">            pair2count[pair] += word_count</span><br><span class="line">    return pair2count</span><br></pre></td></tr></table></figure><p>获得pair2count如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">defaultdict(&lt;class &#x27;int&#x27;&gt;, &#123;(&#x27;T&#x27;, &#x27;h&#x27;): 3, (&#x27;h&#x27;, &#x27;i&#x27;): 3, (&#x27;i&#x27;, &#x27;s&#x27;): 5, (&#x27;Ġ&#x27;, &#x27;i&#x27;): 2, (&#x27;Ġ&#x27;, &#x27;t&#x27;): 7, (&#x27;t&#x27;, &#x27;h&#x27;): 3, ..., (&#x27;n&#x27;, &#x27;s&#x27;): 1&#125;)</span><br></pre></td></tr></table></figure><p>统计当前频率最高的相邻pair</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">def _compute_most_score_pair(pair2count):</span><br><span class="line">    best_pair = None</span><br><span class="line">    max_freq = None</span><br><span class="line">    for pair, freq in pair2count.items():</span><br><span class="line">        if max_freq is None or max_freq &lt; freq:</span><br><span class="line">            best_pair = pair</span><br><span class="line">            max_freq = freq</span><br><span class="line">    return best_pair</span><br></pre></td></tr></table></figure><p>经过统计，当前频率最高的pair为: (‘Ġ’, ‘t’)， 频率为7次。 将(‘Ġ’, ‘t’)合并成一个词并添加到词表中。同时在合并规则中添加(‘Ġ’, ‘t’)这条合并规则。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">merge_rules = []</span><br><span class="line">best_pair = self._compute_most_score_pair(pair2score)</span><br><span class="line">vocabs.append(best_pair[0] + best_pair[1])</span><br><span class="line">merge_rules.append(best_pair)</span><br></pre></td></tr></table></figure><p>此时的vocab词表更新成:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[&#x27;i&#x27;, &#x27;t&#x27;, &#x27;p&#x27;, &#x27;o&#x27;, &#x27;r&#x27;, &#x27;m&#x27;, &#x27;e&#x27;, &#x27;,&#x27;, &#x27;y&#x27;, &#x27;v&#x27;, &#x27;Ġ&#x27;, &#x27;F&#x27;, &#x27;a&#x27;, &#x27;C&#x27;, &#x27;H&#x27;, &#x27;.&#x27;, &#x27;f&#x27;, &#x27;l&#x27;, &#x27;u&#x27;, &#x27;c&#x27;, &#x27;T&#x27;, &#x27;k&#x27;, &#x27;h&#x27;, &#x27;z&#x27;, &#x27;d&#x27;, &#x27;g&#x27;, &#x27;w&#x27;, &#x27;n&#x27;, &#x27;s&#x27;, &#x27;b&#x27;, </span><br><span class="line">&#x27;Ġt&#x27;]</span><br></pre></td></tr></table></figure><p>根据更新后的vocab重新对word2count进行切分。具体实现上，可以直接在旧的word2split上应用新的合并规则(‘Ġ’, ‘t’)</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">def _merge_pair(a, b, word2splits):</span><br><span class="line">    new_word2splits = dict()</span><br><span class="line">    for word, split in word2splits.items():</span><br><span class="line">        if len(split) == 1:</span><br><span class="line">            new_word2splits[word] = split</span><br><span class="line">            continue</span><br><span class="line">        i = 0</span><br><span class="line">        while i &lt; len(split) - 1:</span><br><span class="line">            if split[i] == a and split[i + 1] == b:</span><br><span class="line">                split = split[:i] + [a + b] + split[i + 2:]</span><br><span class="line">            else:</span><br><span class="line">                i += 1</span><br><span class="line">        new_word2splits[word] = split</span><br><span class="line">    return new_word2splits</span><br></pre></td></tr></table></figure><p>从而获得新的word2split</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#123;&#x27;This&#x27;: [&#x27;T&#x27;, &#x27;h&#x27;, &#x27;i&#x27;, &#x27;s&#x27;], </span><br><span class="line">&#x27;Ġis&#x27;: [&#x27;Ġ&#x27;, &#x27;i&#x27;, &#x27;s&#x27;], </span><br><span class="line">&#x27;Ġthe&#x27;: [&#x27;Ġt&#x27;, &#x27;h&#x27;, &#x27;e&#x27;], </span><br><span class="line">&#x27;ĠHugging&#x27;: [&#x27;Ġ&#x27;, &#x27;H&#x27;, &#x27;u&#x27;, &#x27;g&#x27;, &#x27;g&#x27;, &#x27;i&#x27;, &#x27;n&#x27;, &#x27;g&#x27;],</span><br><span class="line">...</span><br><span class="line">&#x27;Ġtokens&#x27;: [&#x27;Ġt&#x27;, &#x27;o&#x27;, &#x27;k&#x27;, &#x27;e&#x27;, &#x27;n&#x27;, &#x27;s&#x27;]&#125;</span><br></pre></td></tr></table></figure><p>可以看到新的word2split中已经包含了新的词”Ġt”。</p><p>重复上述循环直到整个词表的大小达到预先设定的词表大小。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">while len(vocabs) &lt; vocab_size:</span><br><span class="line">    pair2score = self._compute_pair2score(word2splits, word2count)</span><br><span class="line">    best_pair = self._compute_most_score_pair(pair2score)</span><br><span class="line">    vocabs.append(best_pair[0] + best_pair[1])</span><br><span class="line">    merge_rules.append(best_pair)</span><br><span class="line">    word2splits = self._merge_pair(best_pair[0], best_pair[1], word2splits)</span><br></pre></td></tr></table></figure><p>假定最终词表的大小为50，经过上述迭代后我们获得的词表和合并规则如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">vocabs = [&#x27;i&#x27;, &#x27;t&#x27;, &#x27;p&#x27;, &#x27;o&#x27;, &#x27;r&#x27;, &#x27;m&#x27;, &#x27;e&#x27;, &#x27;,&#x27;, &#x27;y&#x27;, &#x27;v&#x27;, &#x27;Ġ&#x27;, &#x27;F&#x27;, &#x27;a&#x27;, &#x27;C&#x27;, &#x27;H&#x27;, &#x27;.&#x27;, &#x27;f&#x27;, &#x27;l&#x27;, &#x27;u&#x27;, &#x27;c&#x27;, &#x27;T&#x27;, &#x27;k&#x27;, &#x27;h&#x27;, &#x27;z&#x27;, &#x27;d&#x27;, &#x27;g&#x27;, &#x27;w&#x27;, &#x27;n&#x27;, &#x27;s&#x27;, &#x27;b&#x27;, &#x27;Ġt&#x27;, &#x27;is&#x27;, &#x27;er&#x27;, &#x27;Ġa&#x27;, &#x27;Ġto&#x27;, &#x27;en&#x27;, &#x27;Th&#x27;, &#x27;This&#x27;, &#x27;ou&#x27;, &#x27;se&#x27;, &#x27;Ġtok&#x27;, &#x27;Ġtoken&#x27;, &#x27;nd&#x27;, &#x27;Ġis&#x27;, &#x27;Ġth&#x27;, &#x27;Ġthe&#x27;, &#x27;in&#x27;, &#x27;Ġab&#x27;, &#x27;Ġtokeni&#x27;, &#x27;Ġtokeniz&#x27;]</span><br><span class="line"></span><br><span class="line">merge_rules = [(&#x27;Ġ&#x27;, &#x27;t&#x27;), (&#x27;i&#x27;, &#x27;s&#x27;), (&#x27;e&#x27;, &#x27;r&#x27;), (&#x27;Ġ&#x27;, &#x27;a&#x27;), (&#x27;Ġt&#x27;, &#x27;o&#x27;), (&#x27;e&#x27;, &#x27;n&#x27;), (&#x27;T&#x27;, &#x27;h&#x27;), (&#x27;Th&#x27;, &#x27;is&#x27;), (&#x27;o&#x27;, &#x27;u&#x27;), (&#x27;s&#x27;, &#x27;e&#x27;), (&#x27;Ġto&#x27;, &#x27;k&#x27;), (&#x27;Ġtok&#x27;, &#x27;en&#x27;), (&#x27;n&#x27;, &#x27;d&#x27;), (&#x27;Ġ&#x27;, &#x27;is&#x27;), (&#x27;Ġt&#x27;, &#x27;h&#x27;), (&#x27;Ġth&#x27;, &#x27;e&#x27;), (&#x27;i&#x27;, &#x27;n&#x27;), (&#x27;Ġa&#x27;, &#x27;b&#x27;), (&#x27;Ġtoken&#x27;, &#x27;i&#x27;), (&#x27;Ġtokeni&#x27;, &#x27;z&#x27;)]</span><br></pre></td></tr></table></figure><p>至此我们就根据给定的语料完成了BPE分词器的训练。</p><h4 id="1-2-推理阶段"><a href="#1-2-推理阶段" class="headerlink" title="1.2. 推理阶段"></a>1.2. 推理阶段</h4><p>在推理阶段，给定一个句子，我们需要将其切分成一个token的序列。 具体实现上需要先对句子进行预分词并切分成字符级别的序列，然后根据合并规则进行合并。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">def tokenize(self, text: str) -&gt; List[str]:</span><br><span class="line">    # pre tokenize</span><br><span class="line">    words = [word for word, _ in self.pre_tokenize_str(text)]</span><br><span class="line">    # split into char level</span><br><span class="line">    splits = [[c for c in word] for word in words]</span><br><span class="line">    # apply merge rules</span><br><span class="line">    for merge_rule in self.merge_rules:</span><br><span class="line">        for index, split in enumerate(splits):</span><br><span class="line">            i = 0</span><br><span class="line">            while i &lt; len(split) - 1:</span><br><span class="line">                if split[i] == merge_rule[0] and split[i + 1] == merge_rule[1]:</span><br><span class="line">                    split = split[:i] + [&quot;&quot;.join(merge_rule)] + split[i + 2:]</span><br><span class="line">                else:</span><br><span class="line">                    i += 1</span><br><span class="line">            splits[index] = split</span><br><span class="line">    return sum(splits, [])</span><br></pre></td></tr></table></figure><p>例如</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&gt;&gt;&gt; tokenize(&quot;This is not a token.&quot;)</span><br><span class="line">&gt;&gt;&gt; [&#x27;This&#x27;, &#x27;Ġis&#x27;, &#x27;Ġ&#x27;, &#x27;n&#x27;, &#x27;o&#x27;, &#x27;t&#x27;, &#x27;Ġa&#x27;, &#x27;Ġtoken&#x27;, &#x27;.&#x27;]</span><br></pre></td></tr></table></figure><h3 id="2-WordPiece"><a href="#2-WordPiece" class="headerlink" title="2. WordPiece"></a>2. WordPiece</h3><p>WordPiece 也是一种基于频率的分词方法，与 BPE 不同的是，它在选择合并单元时，不仅考虑字节对的出现频率，还引入了语言模型的似然估计。其目标是最小化语言模型的困惑度，即选择使语言模型概率最大的子词划分方式。[只是在训练阶段合并pair的策略不是pair的频率而是互信息。]</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">socre=log(p(ab))−(log(p(a))+log(p(b)))=log(p(ab)/p(a)p(b))</span><br></pre></td></tr></table></figure><p>这里的动机是一个pair的频率很高，但是其中pair的一部分的频率更高，这时候不一定需要进行该pair的合并。 而如果一个pair的频率很高，并且这个pair的两个部分都是只出现在这个pair中，就说明这个pair很值得合并。实现步骤如下：</p><ol><li>初始化词汇表：通常以字符为初始 token。</li><li>训练语言模型：使用初始词汇表对文本进行编码，并训练一个语言模型。</li><li>寻找最优合并：在每次迭代中，尝试所有可能的子词对合并，计算合并后的语言模型困惑度，选择使困惑度最小的合并对。</li><li>更新词汇表和语言模型：将新合并的子词加入词汇表，并重新训练语言模型。</li><li>重复迭代：直到达到预设的词汇表大小或困惑度不再显著降低。</li></ol><ul><li>训练方法：从字符级的小词表出发，训练产生合并规则以及一个词表</li><li>编码方法：将文本切分成词，对每个词在词表中进行最大前向匹配</li><li>经典模型：BERT及其系列DistilBERT，MobileBERT等</li></ul><h4 id="2-1-训练阶段"><a href="#2-1-训练阶段" class="headerlink" title="2.1. 训练阶段"></a>2.1. 训练阶段</h4><p>在训练环节，给定语料，通过训练算法，生成最终的词表。 WordPiece算法也是从一个字符级别的词表为基础，逐步扩充成大词表。合并规则为选择相邻pair互信息最大的进行合并。</p><p>下面进行具体手工实现。</p><p>假定训练的语料(已归一化处理)为</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">corpus = [</span><br><span class="line">    &quot;This is the Hugging Face Course.&quot;,</span><br><span class="line">    &quot;This chapter is about tokenization.&quot;,</span><br><span class="line">    &quot;This section shows several tokenizer algorithms.&quot;,</span><br><span class="line">    &quot;Hopefully, you will be able to understand how they are trained and generate tokens.&quot;,</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>首先进行预切分处理。这里采用BERT的预切分逻辑。具体会按照空格和标点进行切分。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">from transformers import AutoTokenizer</span><br><span class="line"></span><br><span class="line"># init pre tokenize function</span><br><span class="line">bert_tokenizer = AutoTokenizer.from_pretrained(&quot;bert-base-cased&quot;)</span><br><span class="line">pre_tokenize_function = bert_tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str</span><br><span class="line"></span><br><span class="line"># pre tokenize</span><br><span class="line">pre_tokenized_corpus = [pre_tokenize_str(text) for text in corpus]</span><br></pre></td></tr></table></figure><p>获得的pre_tokenized_corpus如下，每个单元分别为[word, (start_index, end_index)]</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">[</span><br><span class="line">    [(&#x27;This&#x27;, (0, 4)), (&#x27;is&#x27;, (5, 7)), (&#x27;the&#x27;, (8, 11)), (&#x27;Hugging&#x27;, (12, 19)), (&#x27;Face&#x27;, (20, 24)), (&#x27;Course&#x27;, (25, 31)), (&#x27;.&#x27;, (31, 32))], </span><br><span class="line">    [(&#x27;This&#x27;, (0, 4)), (&#x27;chapter&#x27;, (5, 12)), (&#x27;is&#x27;, (13, 15)), (&#x27;about&#x27;, (16, 21)), (&#x27;tokenization&#x27;, (22, 34)), (&#x27;.&#x27;, (34, 35))], </span><br><span class="line">    [(&#x27;This&#x27;, (0, 4)), (&#x27;section&#x27;, (5, 12)), (&#x27;shows&#x27;, (13, 18)), (&#x27;several&#x27;, (19, 26)), (&#x27;tokenizer&#x27;, (27, 36)), (&#x27;algorithms&#x27;, (37, 47)), (&#x27;.&#x27;, (47, 48))], </span><br><span class="line">    [(&#x27;Hopefully&#x27;, (0, 9)), (&#x27;,&#x27;, (9, 10)), (&#x27;you&#x27;, (11, 14)), (&#x27;will&#x27;, (15, 19)), (&#x27;be&#x27;, (20, 22)), (&#x27;able&#x27;, (23, 27)), (&#x27;to&#x27;, (28, 30)), (&#x27;understand&#x27;, (31, 41)), (&#x27;how&#x27;, (42, 45)), (&#x27;they&#x27;, (46, 50)), (&#x27;are&#x27;, (51, 54)), (&#x27;trained&#x27;, (55, 62)), (&#x27;and&#x27;, (63, 66)), (&#x27;generate&#x27;, (67, 75)), (&#x27;tokens&#x27;, (76, 82)), (&#x27;.&#x27;, (82, 83))]</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>进一步统计词频</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">word2count = defaultdict(int)</span><br><span class="line">for split_text in pre_tokenized_corpus:</span><br><span class="line">    for word, _ in split_text:</span><br><span class="line">        word2count[word] += 1</span><br></pre></td></tr></table></figure><p>获得word2count如下</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">defaultdict(&lt;class &#x27;int&#x27;&gt;, &#123;&#x27;This&#x27;: 3, &#x27;is&#x27;: 2, &#x27;the&#x27;: 1, &#x27;Hugging&#x27;: 1, &#x27;Face&#x27;: 1, &#x27;Course&#x27;: 1, &#x27;.&#x27;: 4, &#x27;chapter&#x27;: 1, &#x27;about&#x27;: 1, &#x27;tokenization&#x27;: 1, &#x27;section&#x27;: 1, &#x27;shows&#x27;: 1, &#x27;several&#x27;: 1, &#x27;tokenizer&#x27;: 1, &#x27;algorithms&#x27;: 1, &#x27;Hopefully&#x27;: 1, &#x27;,&#x27;: 1, &#x27;you&#x27;: 1, &#x27;will&#x27;: 1, &#x27;be&#x27;: 1, &#x27;able&#x27;: 1, &#x27;to&#x27;: 1, &#x27;understand&#x27;: 1, &#x27;how&#x27;: 1, &#x27;they&#x27;: 1, &#x27;are&#x27;: 1, &#x27;trained&#x27;: 1, &#x27;and&#x27;: 1, &#x27;generate&#x27;: 1, &#x27;tokens&#x27;: 1&#125;)</span><br></pre></td></tr></table></figure><p>因为WordPiece同样是从字符级别的小词表，逐步合并成大词表，所以先获得字符级别的小词表。注意这里如果字符不是不一个词的开始，需要添加上特殊字符”##”。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">vocab_set = set()</span><br><span class="line">for word in word2count:</span><br><span class="line">    vocab_set.add(word[0])</span><br><span class="line">    vocab_set.update([&#x27;##&#x27; + c for c in word[1:]])</span><br><span class="line">vocabs = list(vocab_set)</span><br></pre></td></tr></table></figure><p>获得的初始小词表vocabs如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[&#x27;##a&#x27;, &#x27;##b&#x27;, &#x27;##c&#x27;, &#x27;##d&#x27;, &#x27;##e&#x27;, &#x27;##f&#x27;, &#x27;##g&#x27;, &#x27;##h&#x27;, &#x27;##i&#x27;, &#x27;##k&#x27;, &#x27;##l&#x27;, &#x27;##m&#x27;, &#x27;##n&#x27;, &#x27;##o&#x27;, &#x27;##p&#x27;, &#x27;##r&#x27;, &#x27;##s&#x27;, &#x27;##t&#x27;, &#x27;##u&#x27;, &#x27;##v&#x27;, &#x27;##w&#x27;, &#x27;##y&#x27;, &#x27;##z&#x27;, &#x27;,&#x27;, &#x27;.&#x27;, &#x27;C&#x27;, &#x27;F&#x27;, &#x27;H&#x27;, &#x27;T&#x27;, &#x27;a&#x27;, &#x27;b&#x27;, &#x27;c&#x27;, &#x27;g&#x27;, &#x27;h&#x27;, &#x27;i&#x27;, &#x27;s&#x27;, &#x27;t&#x27;, &#x27;u&#x27;, &#x27;w&#x27;, &#x27;y&#x27;]</span><br></pre></td></tr></table></figure><p>基于小词表对每个词进行切分</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">word2splits = &#123;word: [word[0]] + [&#x27;##&#x27; + c for c in word[1:]] for word in word2count&#125;</span><br><span class="line"></span><br><span class="line">&#123;&#x27;This&#x27;: [&#x27;T&#x27;, &#x27;##h&#x27;, &#x27;##i&#x27;, &#x27;##s&#x27;], </span><br><span class="line">&#x27;is&#x27;: [&#x27;i&#x27;, &#x27;##s&#x27;], </span><br><span class="line">&#x27;the&#x27;: [&#x27;t&#x27;, &#x27;##h&#x27;, &#x27;##e&#x27;], </span><br><span class="line">&#x27;Hugging&#x27;: [&#x27;H&#x27;, &#x27;##u&#x27;, &#x27;##g&#x27;, &#x27;##g&#x27;, &#x27;##i&#x27;, &#x27;##n&#x27;, &#x27;##g&#x27;], </span><br><span class="line">...</span><br><span class="line">&#x27;generate&#x27;: [&#x27;g&#x27;, &#x27;##e&#x27;, &#x27;##n&#x27;, &#x27;##e&#x27;, &#x27;##r&#x27;, &#x27;##a&#x27;, &#x27;##t&#x27;, &#x27;##e&#x27;], </span><br><span class="line">&#x27;tokens&#x27;: [&#x27;t&#x27;, &#x27;##o&#x27;, &#x27;##k&#x27;, &#x27;##e&#x27;, &#x27;##n&#x27;, &#x27;##s&#x27;]&#125;</span><br></pre></td></tr></table></figure><p>进一步统计vocabs中相邻两个pair的互信息</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">def _compute_pair2score(word2splits, word2count):</span><br><span class="line">    &quot;&quot;&quot;</span><br><span class="line">    计算每个pair的分数</span><br><span class="line">    score=(freq_of_pair)/(freq_of_first_element×freq_of_second_element)</span><br><span class="line">    :return:</span><br><span class="line">    &quot;&quot;&quot;</span><br><span class="line">    vocab2count = defaultdict(int)</span><br><span class="line">    pair2count = defaultdict(int)</span><br><span class="line">    for word, word_count in word2count.items():</span><br><span class="line">        splits = word2splits[word]</span><br><span class="line">        if len(splits) == 1:</span><br><span class="line">            vocab2count[splits[0]] += word_count</span><br><span class="line">            continue</span><br><span class="line">        for i in range(len(splits) - 1):</span><br><span class="line">            pair = (splits[i], splits[i + 1])</span><br><span class="line">            vocab2count[splits[i]] += word_count</span><br><span class="line">            pair2count[pair] += word_count</span><br><span class="line">        vocab2count[splits[-1]] += word_count</span><br><span class="line">    scores = &#123;</span><br><span class="line">        pair: freq / (vocab2count[pair[0]] * vocab2count[pair[1]])</span><br><span class="line">        for pair, freq in pair2count.items()</span><br><span class="line">    &#125;</span><br><span class="line">    return scores</span><br></pre></td></tr></table></figure><p>获得每个pair的互信息如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#123;(&#x27;T&#x27;, &#x27;##h&#x27;): 0.125, </span><br><span class="line">(&#x27;##h&#x27;, &#x27;##i&#x27;): 0.03409090909090909, </span><br><span class="line">(&#x27;##i&#x27;, &#x27;##s&#x27;): 0.02727272727272727, </span><br><span class="line">(&#x27;a&#x27;, &#x27;##b&#x27;): 0.2,</span><br><span class="line">...</span><br><span class="line">(&#x27;##n&#x27;, &#x27;##s&#x27;): 0.00909090909090909&#125;</span><br></pre></td></tr></table></figure><p>统计出互信息最高的相邻pair</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">def _compute_most_score_pair(pair2score):</span><br><span class="line">    best_pair = None</span><br><span class="line">    max_score = None</span><br><span class="line">    for pair, score in pair2score.items():</span><br><span class="line">        if max_score is None or max_score &lt; score:</span><br><span class="line">            best_pair = pair</span><br><span class="line">            max_score = score</span><br><span class="line">    return best_pair</span><br></pre></td></tr></table></figure><p>此时互信息最高的pair为: (‘a’, ‘##b’) 将(‘a’, ‘##b’)合并成一个词’ab’并添加到词表中</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">best_pair = self._compute_most_score_pair(pair2score)</span><br><span class="line">vocabs.append(best_pair[0] + best_pair[1])</span><br></pre></td></tr></table></figure><p>这样vocab词表更新成:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[&#x27;##a&#x27;, &#x27;##b&#x27;, &#x27;##c&#x27;, &#x27;##d&#x27;, &#x27;##e&#x27;, &#x27;##f&#x27;, &#x27;##g&#x27;, &#x27;##h&#x27;, &#x27;##i&#x27;, &#x27;##k&#x27;, &#x27;##l&#x27;, &#x27;##m&#x27;, &#x27;##n&#x27;, &#x27;##o&#x27;, &#x27;##p&#x27;, &#x27;##r&#x27;, &#x27;##s&#x27;, &#x27;##t&#x27;, &#x27;##u&#x27;, &#x27;##v&#x27;, &#x27;##w&#x27;, &#x27;##y&#x27;, &#x27;##z&#x27;, &#x27;,&#x27;, &#x27;.&#x27;, &#x27;C&#x27;, &#x27;F&#x27;, &#x27;H&#x27;, &#x27;T&#x27;, &#x27;a&#x27;, &#x27;b&#x27;, &#x27;c&#x27;, &#x27;g&#x27;, &#x27;h&#x27;, &#x27;i&#x27;, &#x27;s&#x27;, &#x27;t&#x27;, &#x27;u&#x27;, &#x27;w&#x27;, &#x27;y&#x27;, </span><br><span class="line">&#x27;ab&#x27;]</span><br></pre></td></tr></table></figure><p>根据更新的vocab重新对word2count进行切分。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">def _merge_pair(a, b, word2splits):</span><br><span class="line">    new_word2splits = dict()</span><br><span class="line">    for word, split in word2splits.items():</span><br><span class="line">        if len(split) == 1:</span><br><span class="line">            new_word2splits[word] = split</span><br><span class="line">            continue</span><br><span class="line">        i = 0</span><br><span class="line">        while i &lt; len(split) - 1:</span><br><span class="line">            if split[i] == a and split[i + 1] == b:</span><br><span class="line">                merge = a + b[2:] if b.startswith(&quot;##&quot;) else a + b</span><br><span class="line">                split = split[:i] + [merge] + split[i + 2:]</span><br><span class="line">            else:</span><br><span class="line">                i += 1</span><br><span class="line">        new_word2splits[word] = split</span><br><span class="line">    return new_word2splits</span><br></pre></td></tr></table></figure><p>获得新的word2split</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;&#x27;This&#x27;: [&#x27;T&#x27;, &#x27;##h&#x27;, &#x27;##i&#x27;, &#x27;##s&#x27;], </span><br><span class="line">&#x27;is&#x27;: [&#x27;i&#x27;, &#x27;##s&#x27;], &#x27;the&#x27;: [&#x27;t&#x27;, &#x27;##h&#x27;, &#x27;##e&#x27;], </span><br><span class="line">&#x27;Hugging&#x27;: [&#x27;H&#x27;, &#x27;##u&#x27;, &#x27;##g&#x27;, &#x27;##g&#x27;, &#x27;##i&#x27;, &#x27;##n&#x27;, &#x27;##g&#x27;], </span><br><span class="line">&#x27;about&#x27;: [&#x27;ab&#x27;, &#x27;##o&#x27;, &#x27;##u&#x27;, &#x27;##t&#x27;], </span><br><span class="line">&#x27;tokens&#x27;: [&#x27;t&#x27;, &#x27;##o&#x27;, &#x27;##k&#x27;, &#x27;##e&#x27;, &#x27;##n&#x27;, &#x27;##s&#x27;]&#125;</span><br></pre></td></tr></table></figure><p>可以看到新的word2split中已经包含了新的词”ab”。</p><p>重复上述步骤，直到整个词表的大小达到预先设定的词表大小。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">while len(vocabs) &lt; vocab_size:</span><br><span class="line">    pair2score = self._compute_pair2score(word2splits, word2count)</span><br><span class="line">    best_pair = self._compute_most_score_pair(pair2score)</span><br><span class="line">    word2splits = self._merge_pair(best_pair[0], best_pair[1], word2splits)</span><br><span class="line">    new_token = best_pair[0] + best_pair[1][2:] if best_pair[1].startswith(&#x27;##&#x27;) else best_pair[1]</span><br><span class="line">    vocabs.append(new_token)</span><br></pre></td></tr></table></figure><p>假定最终词表的大小为70，经过上述迭代后我们获得的词表如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vocabs = [&#x27;##a&#x27;, &#x27;##b&#x27;, &#x27;##c&#x27;, &#x27;##ct&#x27;, &#x27;##d&#x27;, &#x27;##e&#x27;, &#x27;##f&#x27;, &#x27;##fu&#x27;, &#x27;##ful&#x27;, &#x27;##full&#x27;, &#x27;##fully&#x27;, &#x27;##g&#x27;, &#x27;##h&#x27;, &#x27;##hm&#x27;, &#x27;##i&#x27;, &#x27;##k&#x27;, &#x27;##l&#x27;, &#x27;##m&#x27;, &#x27;##n&#x27;, &#x27;##o&#x27;, &#x27;##p&#x27;, &#x27;##r&#x27;, &#x27;##s&#x27;, &#x27;##t&#x27;, &#x27;##thm&#x27;, &#x27;##thms&#x27;, &#x27;##u&#x27;, &#x27;##ut&#x27;, &#x27;##v&#x27;, &#x27;##w&#x27;, &#x27;##y&#x27;, &#x27;##z&#x27;, &#x27;##za&#x27;, &#x27;##zat&#x27;, &#x27;,&#x27;, &#x27;.&#x27;, &#x27;C&#x27;, &#x27;F&#x27;, &#x27;Fa&#x27;, &#x27;Fac&#x27;, &#x27;H&#x27;, &#x27;Hu&#x27;, &#x27;Hug&#x27;, &#x27;Hugg&#x27;, &#x27;T&#x27;, &#x27;Th&#x27;, &#x27;a&#x27;, &#x27;ab&#x27;, &#x27;b&#x27;, &#x27;c&#x27;, &#x27;ch&#x27;, &#x27;cha&#x27;, &#x27;chap&#x27;, &#x27;chapt&#x27;, &#x27;g&#x27;, &#x27;h&#x27;, &#x27;i&#x27;, &#x27;is&#x27;, &#x27;s&#x27;, &#x27;sh&#x27;, &#x27;t&#x27;, &#x27;th&#x27;, &#x27;u&#x27;, &#x27;w&#x27;, &#x27;y&#x27;, &#x27;[CLS]&#x27;, &#x27;[MASK]&#x27;, &#x27;[PAD]&#x27;, &#x27;[SEP]&#x27;, &#x27;[UNK]&#x27;]</span><br></pre></td></tr></table></figure><p>注意词表中添加了特殊的token：[CLS], [MASK], [PAD], [SEP], [UNK] 至此我们就根据给定的语料完成了WordPiece分词器的训练。</p><h4 id="2-2-推理阶段"><a href="#2-2-推理阶段" class="headerlink" title="2.2. 推理阶段"></a>2.2. 推理阶段</h4><p>在推理阶段，给定一个句子，需要将其切分成一个token的序列。 具体实现上需要先对句子进行预分词，然后对每个词进行在词表中进行最大前向的匹配。如果词表中不存在则为UNK。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">def _encode_word(self, word):</span><br><span class="line">    tokens = []</span><br><span class="line">    while len(word) &gt; 0:</span><br><span class="line">        i = len(word)</span><br><span class="line">        while i &gt; 0 and word[:i] not in self.vocabs:</span><br><span class="line">            i -= 1</span><br><span class="line">        if i == 0:</span><br><span class="line">            return [&quot;[UNK]&quot;]</span><br><span class="line">        tokens.append(word[:i])</span><br><span class="line">        word = word[i:]</span><br><span class="line">        if len(word) &gt; 0:</span><br><span class="line">            word = f&quot;##&#123;word&#125;&quot;</span><br><span class="line">    return tokens</span><br><span class="line"></span><br><span class="line">def tokenize(self, text):</span><br><span class="line">    words = [word for word, _ in self.pre_tokenize_str(text)]</span><br><span class="line">    encoded_words = [self._encode_word(word) for word in words]</span><br><span class="line">    return sum(encoded_words, [])</span><br></pre></td></tr></table></figure><p>例如</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&gt;&gt;&gt; tokenize(&quot;This is the Hugging Face course!&quot;)</span><br><span class="line">&gt;&gt;&gt; [&#x27;Th&#x27;, &#x27;##i&#x27;, &#x27;##s&#x27;, &#x27;is&#x27;, &#x27;th&#x27;, &#x27;##e&#x27;, &#x27;Hugg&#x27;, &#x27;##i&#x27;, &#x27;##n&#x27;, &#x27;##g&#x27;, &#x27;Fac&#x27;, &#x27;##e&#x27;, &#x27;c&#x27;, &#x27;##o&#x27;, &#x27;##u&#x27;, &#x27;##r&#x27;, &#x27;##s&#x27;, &#x27;##e&#x27;, &#x27;[UNK]&#x27;]</span><br></pre></td></tr></table></figure><h3 id="3-Unigram"><a href="#3-Unigram" class="headerlink" title="3. Unigram"></a>3. Unigram</h3><p>Unigram分词与BPE和WordPiece不同，是基于一个大词表逐步裁剪成一个小词表。 通过Unigram语言模型计算删除不同subword造成的损失来衡量subword的重要性，保留重要性较高的子词。</p><ul><li>训练方法：从包含字符和全部子词的大词表出发，逐步裁剪出一个小词表，并且每个词都有自己的分数。</li><li>编码方法：将文本切分成词，对每个词基于Viterbi算法求解出最佳解码路径。</li><li>经典模型：AlBERT, T5, mBART, Big Bird, XLNet</li></ul><h4 id="3-1-训练阶段"><a href="#3-1-训练阶段" class="headerlink" title="3.1. 训练阶段"></a>3.1. 训练阶段</h4><p>在训练环节，目标是给定语料，通过训练算法，生成最终的词表，并且每个词有自己的概率值。 Unigram算法是从大词表为基础，逐步裁剪成小词表。裁剪规则是根据<strong>Unigram语言模型</strong>的打分依次裁剪重要度相对较低的词。</p><p>下面进行具体手工实现。</p><p>假定训练的语料(已归一化处理)为</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">corpus = [</span><br><span class="line">    &quot;This is the Hugging Face Course.&quot;,</span><br><span class="line">    &quot;This chapter is about tokenization.&quot;,</span><br><span class="line">    &quot;This section shows several tokenizer algorithms.&quot;,</span><br><span class="line">    &quot;Hopefully, you will be able to understand how they are trained and generate tokens.&quot;,</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>首先进行预切分处理。这里采用xlnet的预切分逻辑。具体会按照空格进行切分，标点不会切分。并且空格会保留成特殊字符”▁”，句子开头也会添加特殊字符”▁”。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">from transformers import AutoTokenizer</span><br><span class="line"></span><br><span class="line"># init pre tokenize function</span><br><span class="line">xlnet_tokenizer = AutoTokenizer.from_pretrained(&quot;xlnet-base-cased&quot;)</span><br><span class="line">pre_tokenize_function = xlnet_tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str</span><br><span class="line"></span><br><span class="line"># pre tokenize</span><br><span class="line">pre_tokenized_corpus = [pre_tokenize_str(text) for text in corpus]</span><br></pre></td></tr></table></figure><p>获得的pre_tokenized_corpus如下，每个单元分别为[word, (start_index, end_index)]</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">[</span><br><span class="line">    [(&#x27;▁This&#x27;, (0, 4)), (&#x27;▁is&#x27;, (5, 7)), (&#x27;▁the&#x27;, (8, 11)), (&#x27;▁Hugging&#x27;, (12, 19)), (&#x27;▁Face&#x27;, (20, 24)), (&#x27;▁Course.&#x27;, (25, 32))], </span><br><span class="line">    [(&#x27;▁This&#x27;, (0, 4)), (&#x27;▁chapter&#x27;, (5, 12)), (&#x27;▁is&#x27;, (13, 15)), (&#x27;▁about&#x27;, (16, 21)), (&#x27;▁tokenization.&#x27;, (22, 35))], </span><br><span class="line">    [(&#x27;▁This&#x27;, (0, 4)), (&#x27;▁section&#x27;, (5, 12)), (&#x27;▁shows&#x27;, (13, 18)), (&#x27;▁several&#x27;, (19, 26)), (&#x27;▁tokenizer&#x27;, (27, 36)), (&#x27;▁algorithms.&#x27;, (37, 48))], </span><br><span class="line">    [(&#x27;▁Hopefully,&#x27;, (0, 10)), (&#x27;▁you&#x27;, (11, 14)), (&#x27;▁will&#x27;, (15, 19)), (&#x27;▁be&#x27;, (20, 22)), (&#x27;▁able&#x27;, (23, 27)), (&#x27;▁to&#x27;, (28, 30)), (&#x27;▁understand&#x27;, (31, 41)), (&#x27;▁how&#x27;, (42, 45)), (&#x27;▁they&#x27;, (46, 50)), (&#x27;▁are&#x27;, (51, 54)), (&#x27;▁trained&#x27;, (55, 62)), (&#x27;▁and&#x27;, (63, 66)), (&#x27;▁generate&#x27;, (67, 75)), (&#x27;▁tokens.&#x27;, (76, 83))]</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>进一步统计词频</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">word2count = defaultdict(int)</span><br><span class="line">for split_text in pre_tokenized_corpus:</span><br><span class="line">    for word, _ in split_text:</span><br><span class="line">        word2count[word] += 1</span><br></pre></td></tr></table></figure><p>获得word2count如下</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">defaultdict(&lt;class &#x27;int&#x27;&gt;, &#123;&#x27;▁This&#x27;: 3, &#x27;▁is&#x27;: 2, &#x27;▁the&#x27;: 1, &#x27;▁Hugging&#x27;: 1, &#x27;▁Face&#x27;: 1, &#x27;▁Course.&#x27;: 1, &#x27;▁chapter&#x27;: 1, &#x27;▁about&#x27;: 1, &#x27;▁tokenization.&#x27;: 1, &#x27;▁section&#x27;: 1, &#x27;▁shows&#x27;: 1, &#x27;▁several&#x27;: 1, &#x27;▁tokenizer&#x27;: 1, &#x27;▁algorithms.&#x27;: 1, &#x27;▁Hopefully,&#x27;: 1, &#x27;▁you&#x27;: 1, &#x27;▁will&#x27;: 1, &#x27;▁be&#x27;: 1, &#x27;▁able&#x27;: 1, &#x27;▁to&#x27;: 1, &#x27;▁understand&#x27;: 1, &#x27;▁how&#x27;: 1, &#x27;▁they&#x27;: 1, &#x27;▁are&#x27;: 1, &#x27;▁trained&#x27;: 1, &#x27;▁and&#x27;: 1, &#x27;▁generate&#x27;: 1, &#x27;▁tokens.&#x27;: 1&#125;)</span><br></pre></td></tr></table></figure><p>统计词表的全部子词和词频，取前300个词，构成最初的大词表。为了避免OOV，char级别的词均需要保留。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">char2count = defaultdict(int)</span><br><span class="line">sub_word2count = defaultdict(int)</span><br><span class="line">for word, count in word2count.items():</span><br><span class="line">    for i in range(len(word)):</span><br><span class="line">        char2count[word[i]] += count</span><br><span class="line">        for j in range(i + 2, len(word) + 1):</span><br><span class="line">            sub_word2count[word[i:j]] += count</span><br><span class="line">sorted_sub_words = sorted(sub_word2count.items(), key=lambda x: x[1], reverse=True)</span><br><span class="line"># init a large vocab with 300</span><br><span class="line">tokens = list(char2count.items()) + sorted_sub_words[: 300 - len(char2count)]</span><br></pre></td></tr></table></figure><p>获得的初始小词表vocabs如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[(&#x27;▁&#x27;, 31), (&#x27;T&#x27;, 3), (&#x27;h&#x27;, 9), (&#x27;i&#x27;, 13), (&#x27;s&#x27;, 13), ...,  (&#x27;several&#x27;, 1)]</span><br></pre></td></tr></table></figure><p>进一步统计每个子词的概率，并转换成Unigram里的loss贡献</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">token2count = &#123;token: count for token, count in tokens&#125;</span><br><span class="line">total_count = sum([count for token, count in token2count.items()])</span><br><span class="line">model = &#123;token: -log(count / total_count) for token, count in token2count.items()&#125;</span><br><span class="line"></span><br><span class="line">model = &#123;</span><br><span class="line">    &#x27;▁&#x27;: 2.952892114877499, </span><br><span class="line">    &#x27;T&#x27;: 5.288267030694535, </span><br><span class="line">    &#x27;h&#x27;: 4.189654742026425, </span><br><span class="line">    ..., </span><br><span class="line">    &#x27;sever&#x27;: 6.386879319362645, </span><br><span class="line">    &#x27;severa&#x27;: 6.386879319362645, </span><br><span class="line">    &#x27;several&#x27;: 6.386879319362645</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>基于每个子词的loss以及Viterbi算法就可以求解出，输入的一个词的最佳分词路径。即整体语言模型的loss最小。词的长度为N，解码的时间复杂度为O(N^2)。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">def _encode_word(word, model):</span><br><span class="line">    best_segmentations = [&#123;&quot;start&quot;: 0, &quot;score&quot;: 1&#125;] + [&#123;&quot;start&quot;: None, &quot;score&quot;: None&#125; for _ in range(len(word))]</span><br><span class="line">    for start_idx in range(len(word)):</span><br><span class="line">        # This should be properly filled by the previous steps of the loop</span><br><span class="line">        best_score_at_start = best_segmentations[start_idx][&quot;score&quot;]</span><br><span class="line">        for end_idx in range(start_idx + 1, len(word) + 1):</span><br><span class="line">            token = word[start_idx:end_idx]</span><br><span class="line">            if token in model and best_score_at_start is not None:</span><br><span class="line">                score = model[token] + best_score_at_start</span><br><span class="line">                # If we have found a better segmentation (lower score) ending at end_idx</span><br><span class="line">                if (</span><br><span class="line">                        best_segmentations[end_idx][&quot;score&quot;] is None</span><br><span class="line">                        or best_segmentations[end_idx][&quot;score&quot;] &gt; score</span><br><span class="line">                ):</span><br><span class="line">                    best_segmentations[end_idx] = &#123;&quot;start&quot;: start_idx, &quot;score&quot;: score&#125;</span><br><span class="line">    segmentation = best_segmentations[-1]</span><br><span class="line">    if segmentation[&quot;score&quot;] is None:</span><br><span class="line">        # We did not find a tokenization of the word -&gt; unknown</span><br><span class="line">        return [&quot;&lt;unk&gt;&quot;], None</span><br><span class="line">    score = segmentation[&quot;score&quot;]</span><br><span class="line">    start = segmentation[&quot;start&quot;]</span><br><span class="line">    end = len(word)</span><br><span class="line">    tokens = []</span><br><span class="line">    while start != 0:</span><br><span class="line">        tokens.insert(0, word[start:end])</span><br><span class="line">        next_start = best_segmentations[start][&quot;start&quot;]</span><br><span class="line">        end = start</span><br><span class="line">        start = next_start</span><br><span class="line">    tokens.insert(0, word[start:end])</span><br><span class="line">    return tokens, score</span><br></pre></td></tr></table></figure><p>例如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&gt;&gt;&gt; tokenize(&quot;This&quot;)</span><br><span class="line">&gt;&gt;&gt; ([&#x27;This&#x27;], 6.288267030694535)</span><br><span class="line">&gt;&gt;&gt; tokenize(&quot;this&quot;)</span><br><span class="line">&gt;&gt;&gt;([&#x27;t&#x27;, &#x27;his&#x27;], 10.03608902044192)</span><br></pre></td></tr></table></figure><p>基于上述的函数，可以获得任一个词的分词路径，以及loss。这样就可以计算整个语料上的loss。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">def _compute_loss(self, model, word2count):</span><br><span class="line">    loss = 0</span><br><span class="line">    for word, freq in word2count.items():</span><br><span class="line">        _, word_loss = self._encode_word(word, model)</span><br><span class="line">        loss += freq * word_loss</span><br><span class="line">    return loss</span><br></pre></td></tr></table></figure><p>尝试移除model中的一个子词，并计算移除后新的model在全部语料上的loss，从而获得这个子词的score，即删除这个子词使得loss新增的量。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">def _compute_scores(self, model, word2count):</span><br><span class="line">    scores = &#123;&#125;</span><br><span class="line">    model_loss = self._compute_loss(model, word2count)</span><br><span class="line">    for token, score in model.items():</span><br><span class="line">        # We always keep tokens of length 1</span><br><span class="line">        if len(token) == 1:</span><br><span class="line">            continue</span><br><span class="line">        model_without_token = copy.deepcopy(model)</span><br><span class="line">        _ = model_without_token.pop(token)</span><br><span class="line">        scores[token] = self._compute_loss(model_without_token, word2count) - model_loss</span><br><span class="line">    return scores</span><br><span class="line"></span><br><span class="line">scores = self._compute_scores(model, word2count)</span><br></pre></td></tr></table></figure><p>为了提升迭代效率，批量删除前10%的结果，即让整体loss增量最小的前10%的词。(删除这些词对整体loss的影响不大。)</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">sorted_scores = sorted(scores.items(), key=lambda x: x[1])</span><br><span class="line"># Remove percent_to_remove tokens with the lowest scores.</span><br><span class="line">for i in range(int(len(model) * 0.1)):</span><br><span class="line">    _ = token2count.pop(sorted_scores[i][0])</span><br></pre></td></tr></table></figure><p>获得新的词表后，重新计算每个词的概率，获得新的模型。并重复以上步骤，直到裁剪到词表大小符合要求。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">while len(model) &gt; vocab_size:</span><br><span class="line">    scores = self._compute_scores(model, word2count)</span><br><span class="line">    sorted_scores = sorted(scores.items(), key=lambda x: x[1])</span><br><span class="line">    # Remove percent_to_remove tokens with the lowest scores.</span><br><span class="line">    for i in range(int(len(model) * percent_to_remove)):</span><br><span class="line">        _ = token2count.pop(sorted_scores[i][0])</span><br><span class="line">    total_count = sum([freq for token, freq in token2count.items()])</span><br><span class="line">    model = &#123;token: -log(count / total_count) for token, count in token2count.items()&#125;</span><br></pre></td></tr></table></figure><p>假定预设的词表的大小为100，经过上述迭代后我们获得词表如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">model = &#123;</span><br><span class="line">    &#x27;▁&#x27;: 2.318585434340487, </span><br><span class="line">    &#x27;T&#x27;: 4.653960350157523, </span><br><span class="line">    &#x27;h&#x27;: 3.5553480614894135, </span><br><span class="line">    &#x27;i&#x27;: 3.1876232813640963, </span><br><span class="line">    ...</span><br><span class="line">    &#x27;seve&#x27;: 5.752572638825633, </span><br><span class="line">    &#x27;sever&#x27;: 5.752572638825633, </span><br><span class="line">    &#x27;severa&#x27;: 5.752572638825633, </span><br><span class="line">    &#x27;several&#x27;: 5.752572638825633</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="3-2-推理阶段"><a href="#3-2-推理阶段" class="headerlink" title="3.2. 推理阶段"></a>3.2. 推理阶段</h4><p>在推理阶段，给定一个句子，需要将其切分成一个token的序列。 具体实现上先对句子进行预分词，然后对每个词基于Viterbi算法进行解码。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">def tokenize(self, text):</span><br><span class="line">    words = [word for word, _ in self.pre_tokenize_str(text)]</span><br><span class="line">    encoded_words = [self._encode_word(word, self.model)[0] for word in words]</span><br><span class="line">    return sum(encoded_words, [])</span><br></pre></td></tr></table></figure><p>例如</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&gt;&gt;&gt; tokenize(&quot;This is the Hugging Face course!&quot;)</span><br><span class="line">&gt;&gt;&gt; [&#x27;▁This&#x27;, &#x27;▁is&#x27;, &#x27;▁the&#x27;, &#x27;▁Hugging&#x27;, &#x27;▁Face&#x27;, &#x27;▁&#x27;, &#x27;c&#x27;, &#x27;ou&#x27;, &#x27;r&#x27;, &#x27;s&#x27;, &#x27;e&#x27;, &#x27;.&#x27;]</span><br></pre></td></tr></table></figure><p>基于Viterbi的切分获得的是最佳切分，基于unigram可以实现一个句子的多种切分方式，并且可以获得每种切分路径的打分。</p><h3 id="4-SentencePiece"><a href="#4-SentencePiece" class="headerlink" title="4. SentencePiece"></a>4. SentencePiece</h3><p>SentencePiece是Google出的一个分词工具，是一种基于 BPE 的分词工具，但它与 BPE 有所不同。它直接对原始文本进行处理，不需要预先进行空格分隔等预处理，并且可以生成子词单位。SentencePiece 将文本转换为unicode码点序列，然后对码点序列应用 BPE 算法，还可以对罕见码点进行 utf-8 编码转换:</p><ol><li>文本预处理：将文本转换为unicode码点序列。</li><li>BPE 训练：使用 BPE 算法对码点序列进行分词训练，生成子词单元。</li><li>罕见码点处理：对于低频码点，可以选择保留或进行 utf-8 编码转换。</li><li>词汇表生成：根据训练结果生成包含子词单元的词汇表。</li></ol><ul><li>内置BPE，Unigram，char和word的分词方法</li><li>无需预分词，以unicode方式直接编码整个句子，空格会被特殊编码为▁</li><li>相比传统实现进行优化，分词速度速度更快</li></ul><p>当前主流的大模型都是基于sentencepiece实现，例如ChatGLM的tokenizer。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">class TextTokenizer:</span><br><span class="line">    def __init__(self, model_path):</span><br><span class="line">        self.sp = spm.SentencePieceProcessor()</span><br><span class="line">        self.sp.Load(model_path)</span><br><span class="line">        self.num_tokens = self.sp.vocab_size()</span><br><span class="line"></span><br><span class="line">    def encode(self, text):</span><br><span class="line">        return self.sp.EncodeAsIds(text)</span><br><span class="line"></span><br><span class="line">    def decode(self, ids: List[int]):</span><br><span class="line">        return self.sp.DecodeIds(ids)</span><br><span class="line">...</span><br></pre></td></tr></table></figure><h2 id="三、对比分析"><a href="#三、对比分析" class="headerlink" title="三、对比分析"></a>三、对比分析</h2><table><thead><tr><th>分词方法</th><th>特点</th><th>优势</th><th>应用场景</th></tr></thead><tbody><tr><td>BPE</td><td>基于字节对频率合并，简单高效</td><td>平衡词汇表大小和文本粒度，处理罕见词效果好</td><td>GPT-2 等模型，多种自然语言处理任务</td></tr><tr><td>WordPiece</td><td>基于语言模型似然估计合并，考虑语义信息</td><td>更好地处理长尾词汇，提升模型泛化能力</td><td>BERT、多语言模型</td></tr><tr><td>SentencePiece</td><td>基于 BPE，直接处理原始文本，支持多种语言</td><td>处理无空格语言能力强，适用于多语言任务</td><td>中文、日语等语言处理，跨语言迁移任务</td></tr><tr><td>unigram</td><td>基于 unigram 语言模型概率，动态调整词汇表</td><td>提高语言模型准确性，适应文本统计特性</td><td>语言模型训练、语音识别等</td></tr></tbody></table><h2 id="引用"><a href="#引用" class="headerlink" title="引用"></a>引用</h2><p><a href="https://zhuanlan.zhihu.com/p/651430181">大模型基础组件 - Tokenizer</a></p>]]></content>
    
    
    <summary type="html">&lt;h1 id=&quot;LLM之分词器-tokenizer&quot;&gt;&lt;a href=&quot;#LLM之分词器-tokenizer&quot; class=&quot;headerlink&quot; title=&quot;LLM之分词器(tokenizer)&quot;&gt;&lt;/a&gt;LLM之分词器(tokenizer)&lt;/h1&gt;&lt;h2 id=&quot;一、引言&quot;&gt;&lt;a href=&quot;#一、引言&quot; class=&quot;headerlink&quot; title=&quot;一、引言&quot;&gt;&lt;/a&gt;一、引言&lt;/h2&gt;&lt;p&gt;大语言模型在自然语言处理领域取得了巨大成功，而分词器作为其关键组成部分，对模型的性能和效果有着显著影响。今天，我们将探讨四种常见的分词方法：BPE、WordPiece、SentencePiece 和 unigram，剖析它们的技术原理、实现细节及应用场景。&lt;/p&gt;
&lt;p&gt;在此之前，我们需要了解一下什么是tokenizition。&lt;/p&gt;
&lt;p&gt;任何一段文本，输入给模型，都是要转换成一串embedding。这个过程简单概括为：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;分词，并把词转换为token（即词的ID）&lt;/li&gt;
&lt;li&gt;token转换成embedding&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;而tokenization就是在做这第一步。而对于第二步就是常见的Embedding查表操作，即根据token_id的值，去Embedding矩阵中查找第token_id行的数据作为embedding。&lt;/p&gt;</summary>
    
    
    
    <category term="知识积累" scheme="http://example.com/categories/%E7%9F%A5%E8%AF%86%E7%A7%AF%E7%B4%AF/"/>
    
    
    <category term="llm" scheme="http://example.com/tags/llm/"/>
    
  </entry>
  
  <entry>
    <title>[2023-09-09] Linux #2:Awk命令及示例</title>
    <link href="http://example.com/2024/08/27/2023-09-09-Linux-2-Awk%E5%91%BD%E4%BB%A4%E5%8F%8A%E7%A4%BA%E4%BE%8B/"/>
    <id>http://example.com/2024/08/27/2023-09-09-Linux-2-Awk%E5%91%BD%E4%BB%A4%E5%8F%8A%E7%A4%BA%E4%BE%8B/</id>
    <published>2024-08-27T15:47:30.000Z</published>
    <updated>2024-08-27T15:53:38.330Z</updated>
    
    <content type="html"><![CDATA[<img src="/2024/08/27/2023-09-09-Linux-2-Awk%E5%91%BD%E4%BB%A4%E5%8F%8A%E7%A4%BA%E4%BE%8B/The-AWK-Programming-Language.svg" class="" title="Awk"><h1 id="Unix-Linux-中的-AWK-命令及示例"><a href="#Unix-Linux-中的-AWK-命令及示例" class="headerlink" title="Unix&#x2F;Linux 中的 AWK 命令及示例"></a>Unix&#x2F;Linux 中的 AWK 命令及示例</h1><h2 id="AWK简介"><a href="#AWK简介" class="headerlink" title="AWK简介"></a>AWK简介</h2><p>Awk 是一种用于处理数据和生成报告的脚本语言。awk 命令编程语言不需要编译，并允许用户使用变量、数字函数、字符串函数和逻辑运算符。</p><p>Awk 是一种实用程序，它使程序员能够以语句的形式编写小巧但有效的程序，这些语句定义要在文档的每一行中搜索的文本模式以及在行中找到匹配项时要采取的操作。Awk 主要用于模式扫描和处理。它搜索一个或多个文件以查看它们是否包含与指定模式匹配的行，然后执行相关操作。</p><h2 id="句法"><a href="#句法" class="headerlink" title="句法"></a>句法</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">awk options <span class="string">&#x27;selection _criteria &#123;action &#125;&#x27;</span> input-file &gt; output-file</span><br></pre></td></tr></table></figure><p><em><strong>Options:</strong></em> </p><figure class="highlight md"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">-f program-file : Reads the AWK program source from the file </span><br><span class="line"><span class="code">                  program-file, instead of from the </span></span><br><span class="line"><span class="code">                  first command line argument.</span></span><br><span class="line"><span class="code">-F fs            : Use fs for the input field separator</span></span><br></pre></td></tr></table></figure><span id="more"></span><h2 id="Awk命令示例"><a href="#Awk命令示例" class="headerlink" title="Awk命令示例"></a>Awk命令示例</h2><p>考虑以下文本文件作为以下所有情况的输入文件：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable">$cat</span> &gt; employee.txt </span><br><span class="line">ajay manager account 45000</span><br><span class="line">sunil clerk account 25000</span><br><span class="line">varun manager sales 50000</span><br><span class="line">amit manager account 47000</span><br><span class="line">tarun peon sales 15000</span><br><span class="line">deepak clerk sales 23000</span><br><span class="line">sunil peon sales 13000</span><br><span class="line">satvik director purchase 80000 </span><br></pre></td></tr></table></figure><ol><li>Awk 的默认行为：默认情况下，Awk 打印指定文件中的每一行数据。</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">$ awk <span class="string">&#x27;&#123;print&#125;&#x27;</span> employee.txt</span><br><span class="line"></span><br><span class="line">ajay manager account 45000</span><br><span class="line">sunil clerk account 25000</span><br><span class="line">varun manager sales 50000</span><br><span class="line">amit manager account 47000</span><br><span class="line">tarun peon sales 15000</span><br><span class="line">deepak clerk sales 23000</span><br><span class="line">sunil peon sales 13000</span><br><span class="line">satvik director purchase 80000 </span><br></pre></td></tr></table></figure><p>在上面的例子中，没有给出模式。因此这些操作适用于所有行。不带任何参数的打印操作默认打印整行，因此它会打印文件的所有行而不会失败。</p><ol start="2"><li>打印与给定模式匹配的行。</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ awk <span class="string">&#x27;/manager/ &#123;print&#125;&#x27;</span> employee.txt </span><br><span class="line">ajay manager account 45000</span><br><span class="line">varun manager sales 50000</span><br><span class="line">amit manager account 47000 </span><br></pre></td></tr></table></figure><p>在上面的示例中，awk 命令打印与“manager”匹配的所有行。</p><ol start="3"><li>将一行拆分为字段：对于每条记录（即行），awk 命令默认会以空格字符分隔记录并将其存储在 $n 变量中。如果该行有 4 个单词，则分别存储在 $1、$2、$3 和 $4 中。另外，$0 代表整行。</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ awk -F<span class="string">&#x27; &#x27;</span> <span class="string">&#x27;&#123;print $1,$4&#125;&#x27;</span> employee.txt </span><br><span class="line">ajay 45000</span><br><span class="line">sunil 25000</span><br><span class="line">varun 50000</span><br><span class="line">amit 47000</span><br><span class="line">tarun 15000</span><br><span class="line">deepak 23000</span><br><span class="line">sunil 13000</span><br><span class="line">satvik 80000 </span><br></pre></td></tr></table></figure><p>在上面的示例中，$1 和 $4 分别代表“Name”和“Salary”字段。</p><h2 id="awk-中的内置变量"><a href="#awk-中的内置变量" class="headerlink" title="awk 中的内置变量"></a>awk 中的内置变量</h2><p>awk 的内置变量包括字段变量 $1、$2、$3 等（$0 表示整行）——它们将一行文本分成单独的单词或片段，称为字段。</p><ul><li>NR：NR 命令保存当前输入记录的数量。请记住，记录通常是行。Awk 命令对文件中的每个记录执行一次模式&#x2F;操作语句。</li><li>NF：NF 命令保存当前输入记录中的字段数。</li><li>FS：FS 命令包含用于在输入行上划分字段的字段分隔符。默认值为“空白”，即空格和制表符。可以将 FS 重新分配给另一个字符（通常在 BEGIN 中）以更改字段分隔符。</li><li>RS：RS 命令存储当前记录分隔符。由于默认情况下，输入行是输入记录，因此默认记录分隔符是换行符。</li><li>OFS：OFS 命令存储输出字段分隔符，当 Awk 打印字段时，它会分隔字段。默认值为空格。每当 print 有多个用逗号分隔的参数时，它都会在每个参数之间打印 OFS 的值。</li><li>ORS：ORS 命令存储输出记录分隔符，Awk 打印输出行时，它会分隔输出行。默认为换行符。print 会自动将 ORS 的内容输出到打印内容的末尾。</li></ul><h3 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h3><ol><li>使用 NR 内置变量（显示行号）</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ awk <span class="string">&#x27;&#123;print NR,$0&#125;&#x27;</span> employee.txt </span><br><span class="line">1 ajay manager account 45000</span><br><span class="line">2 sunil clerk account 25000</span><br><span class="line">3 varun manager sales 50000</span><br><span class="line">4 amit manager account 47000</span><br><span class="line">5 tarun peon sales 15000</span><br><span class="line">6 deepak clerk sales 23000</span><br><span class="line">7 sunil peon sales 13000</span><br><span class="line">8 satvik director purchase 80000 </span><br></pre></td></tr></table></figure><p>在上面的示例中，带有 NR 的 awk 命令打印所有行以及行号。</p><ol start="2"><li>使用 NF 内置变量（显示最后一个字段）</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ awk <span class="string">&#x27;&#123;print $1,$NF&#125;&#x27;</span> employee.txt </span><br><span class="line">ajay 45000</span><br><span class="line">sunil 25000</span><br><span class="line">varun 50000</span><br><span class="line">amit 47000</span><br><span class="line">tarun 15000</span><br><span class="line">deepak 23000</span><br><span class="line">sunil 13000</span><br><span class="line">satvik 80000 </span><br></pre></td></tr></table></figure><p>在上面的示例中，$1 代表姓名，$NF 代表薪水。我们可以使用 $NF 获取薪资，其中 $NF 代表最后一个字段。</p><ol start="3"><li>NR内置变量的另一种用途（显示行从3到6</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ awk <span class="string">&#x27;NR==3, NR==6 &#123;print NR,$0&#125;&#x27;</span> employee.txt </span><br><span class="line">3 varun manager sales 50000</span><br><span class="line">4 amit manager account 47000</span><br><span class="line">5 tarun peon sales 15000</span><br><span class="line">6 deepak clerk sales 23000 </span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;img src=&quot;/2024/08/27/2023-09-09-Linux-2-Awk%E5%91%BD%E4%BB%A4%E5%8F%8A%E7%A4%BA%E4%BE%8B/The-AWK-Programming-Language.svg&quot; class=&quot;&quot; title=&quot;Awk&quot;&gt;

&lt;h1 id=&quot;Unix-Linux-中的-AWK-命令及示例&quot;&gt;&lt;a href=&quot;#Unix-Linux-中的-AWK-命令及示例&quot; class=&quot;headerlink&quot; title=&quot;Unix&amp;#x2F;Linux 中的 AWK 命令及示例&quot;&gt;&lt;/a&gt;Unix&amp;#x2F;Linux 中的 AWK 命令及示例&lt;/h1&gt;&lt;h2 id=&quot;AWK简介&quot;&gt;&lt;a href=&quot;#AWK简介&quot; class=&quot;headerlink&quot; title=&quot;AWK简介&quot;&gt;&lt;/a&gt;AWK简介&lt;/h2&gt;&lt;p&gt;Awk 是一种用于处理数据和生成报告的脚本语言。awk 命令编程语言不需要编译，并允许用户使用变量、数字函数、字符串函数和逻辑运算符。&lt;/p&gt;
&lt;p&gt;Awk 是一种实用程序，它使程序员能够以语句的形式编写小巧但有效的程序，这些语句定义要在文档的每一行中搜索的文本模式以及在行中找到匹配项时要采取的操作。Awk 主要用于模式扫描和处理。它搜索一个或多个文件以查看它们是否包含与指定模式匹配的行，然后执行相关操作。&lt;/p&gt;
&lt;h2 id=&quot;句法&quot;&gt;&lt;a href=&quot;#句法&quot; class=&quot;headerlink&quot; title=&quot;句法&quot;&gt;&lt;/a&gt;句法&lt;/h2&gt;&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;awk options &lt;span class=&quot;string&quot;&gt;&amp;#x27;selection _criteria &amp;#123;action &amp;#125;&amp;#x27;&lt;/span&gt; input-file &amp;gt; output-file&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Options:&lt;/strong&gt;&lt;/em&gt; &lt;/p&gt;
&lt;figure class=&quot;highlight md&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;-f program-file : Reads the AWK program source from the file &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;code&quot;&gt;                  program-file, instead of from the &lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;code&quot;&gt;                  first command line argument.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;code&quot;&gt;-F fs            : Use fs for the input field separator&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    <category term="技术积累" scheme="http://example.com/categories/%E6%8A%80%E6%9C%AF%E7%A7%AF%E7%B4%AF/"/>
    
    
    <category term="Linux" scheme="http://example.com/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>2024-05-19-LLM-RAG</title>
    <link href="http://example.com/2024/05/19/2024-05-19-LLM-RAG/"/>
    <id>http://example.com/2024/05/19/2024-05-19-LLM-RAG/</id>
    <published>2024-05-19T15:21:49.000Z</published>
    <updated>2024-05-19T17:15:53.846Z</updated>
    
    <content type="html"><![CDATA[<h1 id="检索增强生成（RAG）"><a href="#检索增强生成（RAG）" class="headerlink" title="检索增强生成（RAG）"></a>检索增强生成（RAG）</h1><h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>检索增强生成（Retrieval Augmented Generation），简称 RAG，已经成为当前最火热的LLM应用方案。</p><p><strong>检索增强生成 (RAG) 是一种使用来自私有或专有数据源的信息来辅助文本生成的技术。</strong>它将检索模型（设计用于搜索大型数据集或知识库）和生成模型（例如大型语言模型 (LLM)，此类模型会使用检索到的信息生成可供阅读的文本回复）结合在一起。</p><p>通过从更多数据源添加背景信息，以及通过训练来补充 LLM 的原始知识库，检索增强生成能够提高搜索体验的相关性。这能够改善大型语言模型的输出，但又无需重新训练模型。额外信息源的范围很广，从训练 LLM 时并未用到的互联网上的新信息，到专有商业背景信息，或者属于企业的机密内部文档，都会包含在内。</p><span id="more"></span><h2 id="为什么是RAG"><a href="#为什么是RAG" class="headerlink" title="为什么是RAG"></a>为什么是RAG</h2><p>大模型的能力很强，但是当我们将大模型应用于实际业务场景时会发现，通用的基础大模型基本无法满足我们的实际业务需求，主要有以下几方面原因：</p><ul><li><strong>知识的局限性</strong>：模型自身的知识完全源于它的训练数据，而现有的主流大模型（ChatGPT、文心一言、通义千问…）的训练集基本都是构建于网络公开的数据，对于一些实时性的、非公开的或离线的数据是无法获取到的，这部分知识也就无从具备。</li><li><strong>幻觉问题</strong>：所有的AI模型的底层原理都是基于数学概率，其模型输出实质上是一系列数值运算，大模型也不例外，所以它有时候会一本正经地胡说八道，尤其是在大模型自身不具备某一方面的知识或不擅长的场景。而这种幻觉问题的区分是比较困难的，因为它要求使用者自身具备相应领域的知识。</li><li><strong>数据安全性</strong>：对于企业来说，数据安全至关重要，没有企业愿意承担数据泄露的风险，将自身的私域数据上传第三方平台进行训练。这也导致完全依赖通用大模型自身能力的应用方案不得不在数据安全和效果方面进行取舍。</li></ul><p>而RAG是解决上述问题的一套有效方案。</p><p>一句话总结：<strong>RAG（中文为检索增强生成） &#x3D; 检索技术 + LLM 提示</strong>。例如，我们向 LLM 提问一个问题（answer），RAG 从各种数据源检索相关的信息，并将检索到的信息和问题（answer）注入到 LLM 提示中，LLM 最后给出答案。</p><h2 id="RAG历史简述"><a href="#RAG历史简述" class="headerlink" title="RAG历史简述"></a>RAG历史简述</h2><p>RAG 是2023年基于 LLM 的系统中最受欢迎的架构。许多产品基于 RAG 构建，从基于 web 搜索引擎和 LLM 的问答服务到使用私有数据的chat应用程序。</p><p>尽管在2019年，<a href="https://link.zhihu.com/?target=https://faiss.ai/">Faiss</a> 就实现了基于嵌入的向量搜索技术，但是 RAG 推动了<strong>向量搜索</strong>领域的发展。比如 <a href="https://link.zhihu.com/?target=https://github.com/chroma-core/chroma">chroma</a>、<a href="https://link.zhihu.com/?target=https://weaviate.io/">weaviate.io</a> 和 <a href="https://link.zhihu.com/?target=https://www.pinecone.io/">pinecone</a> 这些基于开源搜索索引引擎（主要是 faiss 和 <a href="https://link.zhihu.com/?target=https://github.com/nmslib/nmslib">nmslib</a>）向量数据库初创公司，最近增加了输入文本的额外存储和其他工具。</p><img src="/2024/05/19/2024-05-19-LLM-RAG/rag.png" class="" title="RAG workflow"><p><img src="/Users/thechef/repositories/thechef-blog/source/_posts/2024-05-19-LLM-RAG/rag.png" alt="img"></p><p>在这个过程中，有两个主要步骤：<strong>语义搜索</strong>和<strong>生成输出</strong>。在语义搜索步骤中，希望从知识库中找到与我们要回答的查询最相关的部分内容。然后，在生成步骤中，将使用这些内容来生成响应。</p><p>有两个最著名的基于 LLM 的管道和应用程序的开源库——<a href="https://link.zhihu.com/?target=https://python.langchain.com/docs/get_started/introduction">LangChain</a> 和 <a href="https://link.zhihu.com/?target=https://docs.llamaindex.ai/en/stable/">LlamaIndex</a>，受 ChatGPT 发布的启发，它们在 2022 年 10 月和 11 月创立，并在 2023 年获得大量采用。</p><h2 id="RAG架构"><a href="#RAG架构" class="headerlink" title="RAG架构"></a>RAG架构</h2><p>RAG的架构如图中所示，简单来讲，RAG就是通过检索获取相关的知识并将其融入Prompt，让大模型能够参考相应的知识从而给出合理回答。因此，可以将RAG的核心理解为“<strong>检索</strong>+<strong>生成</strong>”。</p><ol><li>前者主要是利用向量数据库的高效存储和检索能力，召回目标知识；</li><li>后者则是利用大模型和Prompt工程，将召回的知识合理利用，生成目标答案。</li></ol><img src="/2024/05/19/2024-05-19-LLM-RAG/rag_framwork.png" class="" title="RAG framwork"><p><img src="/Users/thechef/repositories/thechef-blog/source/_posts/2024-05-19-LLM-RAG/rag_framwork.png" alt="img"></p><p>完整的RAG应用流程主要包含两个阶段：</p><ul><li>数据准备阶段：数据提取—&gt;文本分割—&gt;向量化（embedding）—&gt;数据入库</li><li>应用阶段：用户提问—&gt;数据检索（召回）—&gt;注入Prompt—&gt;LLM生成答案</li></ul><p>下面详细介绍一下各环节的技术细节和注意事项。</p><h3 id="数据准备阶段："><a href="#数据准备阶段：" class="headerlink" title="数据准备阶段："></a>数据准备阶段：</h3><p>数据准备一般是一个离线的过程，主要是将私域数据向量化后构建索引并存入数据库的过程。主要包括：数据提取、文本分割、向量化、数据入库等环节。</p><ul><li><strong>数据提取</strong><ul><li>数据加载：包括多格式数据加载、不同数据源获取等，根据数据自身情况，将数据处理为同一个范式。</li><li>数据处理：包括数据过滤、压缩、格式化等。</li><li>元数据获取：提取数据中关键信息，例如文件名、Title、时间等 。</li></ul></li><li><strong>文本分割</strong>：<br>文本分割主要考虑两个因素：1）embedding模型的Tokens限制情况；2）语义完整性对整体的检索效果的影响。一些常见的文本分割方式如下：<ul><li>句分割：以”句”的粒度进行切分，保留一个句子的完整语义。常见切分符包括：句号、感叹号、问号、换行符等。</li><li>固定长度分割：根据embedding模型的token长度限制，将文本分割为固定长度（例如256&#x2F;512个tokens），这种切分方式会损失很多语义信息，一般通过在头尾增加一定冗余量来缓解。</li></ul></li><li><strong>向量化（embedding）</strong>：</li></ul><p>向量化是一个将文本数据转化为向量矩阵的过程，该过程会直接影响到后续检索的效果。目前常见的embedding模型如表中所示，这些embedding模型基本能满足大部分需求，但对于特殊场景（例如涉及一些罕见专有词或字等）或者想进一步优化效果，则可以选择开源Embedding模型微调或直接训练适合自己场景的Embedding模型。</p><table><thead><tr><th>模型名称</th><th>描述</th><th>获取地址</th></tr></thead><tbody><tr><td>ChatGPT-Embedding</td><td>ChatGPT-Embedding由OpenAI公司提供，以接口形式调用。</td><td><a href="https://link.zhihu.com/?target=https://platform.openai.com/docs/guides/embeddings/what-are-embeddings">https://platform.openai.com/docs/guides/embeddings/what-are-embeddings</a></td></tr><tr><td>ERNIE-Embedding V1</td><td>ERNIE-Embedding V1由百度公司提供，依赖于文心大模型能力，以接口形式调用。</td><td><a href="https://link.zhihu.com/?target=https://cloud.baidu.com/doc/WENXINWORKSHOP/s/alj562vvu">https://cloud.baidu.com/doc/WENXINWORKSHOP/s/alj562vvu</a></td></tr><tr><td>M3E</td><td>M3E是一款功能强大的开源Embedding模型，包含m3e-small、m3e-base、m3e-large等多个版本，支持微调和本地部署。</td><td><a href="https://link.zhihu.com/?target=https://huggingface.co/moka-ai/m3e-base">https://huggingface.co/moka-ai/m3e-base</a></td></tr><tr><td>BGE</td><td>BGE由北京智源人工智能研究院发布，同样是一款功能强大的开源Embedding模型，包含了支持中文和英文的多个版本，同样支持微调和本地部署。</td><td><a href="https://link.zhihu.com/?target=https://huggingface.co/BAAI/bge-base-en-v1.5">https://huggingface.co/BAAI/bge-base-en-v1.5</a></td></tr></tbody></table><ul><li>数据入库：</li></ul><p>数据向量化后构建索引，并写入数据库的过程可以概述为数据入库过程，适用于RAG场景的数据库包括：FAISS、Chromadb、ES、milvus等。一般可以根据业务场景、硬件、性能需求等多因素综合考虑，选择合适的数据库。</p><h3 id="应用阶段："><a href="#应用阶段：" class="headerlink" title="应用阶段："></a><strong>应用阶段：</strong></h3><p>在应用阶段，我们根据用户的提问，通过高效的检索方法，召回与提问最相关的知识，并融入Prompt；大模型参考当前提问和相关知识，生成相应的答案。关键环节包括：数据检索、注入Prompt等。</p><ul><li><strong>数据检索</strong></li></ul><p>常见的数据检索方法包括：相似性检索、全文检索等，根据检索效果，一般可以选择多种检索方式融合，提升召回率。</p><ul><li><p>相似性检索：即计算查询向量与所有存储向量的相似性得分，返回得分高的记录。常见的相似性计算方法包括：余弦相似性、欧氏距离、曼哈顿距离等。</p><p>全文检索：全文检索是一种比较经典的检索方式，在数据存入时，通过关键词构建倒排索引；在检索时，通过关键词进行全文检索，找到对应的记录。</p></li><li><p><strong>注入Prompt</strong></p><p>Prompt作为大模型的直接输入，是影响模型输出准确率的关键因素之一。在RAG场景中，Prompt一般包括任务描述、背景知识（检索得到）、任务指令（一般是用户提问）等，根据任务场景和大模型性能，也可以在Prompt中适当加入其他指令优化大模型的输出。一个简单知识问答场景的Prompt如下所示：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">【任务描述】</span><br><span class="line">假如你是一个专业的客服机器人，请参考【背景知识】，回</span><br><span class="line">【背景知识】</span><br><span class="line">&#123;content&#125; // 数据检索得到的相关文本</span><br><span class="line">【问题】</span><br><span class="line">石头扫地机器人P10的续航时间是多久？</span><br></pre></td></tr></table></figure><p>Prompt的设计只有方法、没有语法，比较依赖于个人经验，在实际应用过程中，往往需要根据大模型的实际输出进行针对性的Prompt调优。</p></li></ul><h2 id="RAG进阶"><a href="#RAG进阶" class="headerlink" title="RAG进阶"></a>RAG进阶</h2><img src="/2024/05/19/2024-05-19-LLM-RAG/rag_adv.png" class="" title="RAG Advanced"><p><img src="/Users/thechef/repositories/thechef-blog/source/_posts/2024-05-19-LLM-RAG/rag_adv.png" alt="img"></p><p>上图中绿色部分是我们接下来详细探讨的核心 RAG 技术。一张图并不能全部展示所有的高级 RAG 技术，比如我们这里省略了上文扩展技术。</p><h3 id="1：分块-Chunking-向量化-Vectorisation"><a href="#1：分块-Chunking-向量化-Vectorisation" class="headerlink" title="1：分块 (Chunking) &amp; 向量化 (Vectorisation)"></a>1：分块 (Chunking) &amp; 向量化 (Vectorisation)</h3><p>首先我们需要为文档内容创建向量索引，然后在运行时搜索与查询向量余弦距离最近的向量索引，这样就可以找到与查询内容最接近语义的文档。</p><p><strong>1.1 分块 (Chunking)</strong></p><p>Transformer 模型具有固定的输入序列长度，即使输入上下文窗口很大，一个句子或几个句子的向量也比几页文本的向量更能代表其语义含义，因此对数据进行分块—— 将初始文档拆分为一定大小的块，而不会失去其含义。有许多文本拆分器实现能够完成此任务。</p><p>块的大小是一个需要重点考虑的问题。块的大小取决于所使用的嵌入模型以及模型需要使用 token 的容量。如基于 BERT 的句子转换器，最多需要 512 个 token，OpenAI ada-002 能够处理更长的序列，如 8191 个 token，但这里的折衷是 LLM 有足够的上下文来推理，而不是足够具体的文本嵌入，以便有效地执行搜索。<a href="https://link.zhihu.com/?target=https://www.pinecone.io/learn/chunking-strategies/">有一项关于块大小选择的研究</a>。在 LlamaIndex 中，<a href="https://link.zhihu.com/?target=https://docs.llamaindex.ai/en/stable/api_reference/service_context/node_parser.html">NodeParser 类</a>很好支持解决这个问题，其中包含一些高级选项，例如定义自己的文本拆分器、元数据、节点&#x2F;块关系等。</p><p><strong>1.2 向量化 (Vectorisation)</strong></p><p>下一步是选择一个<strong>搜索优化的模型来嵌入我们的块</strong>。有很多选项，比如 <a href="https://link.zhihu.com/?target=https://huggingface.co/BAAI/bge-large-en-v1.5">bge-large</a> 或 <a href="https://link.zhihu.com/?target=https://huggingface.co/intfloat/multilingual-e5-large">E5 嵌入系列</a>。只需查看 <a href="https://link.zhihu.com/?target=https://huggingface.co/spaces/mteb/leaderboard">MTEB</a> 排行榜以获取最新更新即可。</p><p>有关分块和向量化步骤的 end2end 实现，请查看 LlamaIndex 中完整数据摄取管道的<a href="https://link.zhihu.com/?target=https://docs.llamaindex.ai/en/latest/module_guides/loading/ingestion_pipeline/root.html%23">示例</a>。</p><h3 id="2-搜索索引"><a href="#2-搜索索引" class="headerlink" title="2. 搜索索引"></a>2. 搜索索引</h3><p><strong>2.1 向量存储索引</strong></p><p><img src="/Users/thechef/repositories/thechef-blog/source/_posts/2024-05-19-LLM-RAG/index_retriecal.png" alt="img"></p><p><strong>RAG 管道的关键部分是搜索索引</strong>，它存储了我们在上一步中获得的向量化内容。最原始的实现是使用平面索引 — 查询向量和所有块向量之间的暴力计算距离。</p><p><strong>为了实现1w+元素规模的高效检索，搜索索引</strong>应该采用<strong>向量索引</strong>，比如 <a href="https://link.zhihu.com/?target=https://faiss.ai/">faiss</a>、<a href="https://link.zhihu.com/?target=https://github.com/nmslib/nmslib">nmslib</a> 以及 <a href="https://link.zhihu.com/?target=https://github.com/spotify/annoy">annoy</a>。这些工具基于近似最近邻居算法，如聚类、树结构或<a href="https://link.zhihu.com/?target=https://www.pinecone.io/learn/series/faiss/hnsw/">HNSW</a>算法。</p><p>此外，还有一些托管解决方案，如 OpenSearch、ElasticSearch 以及向量数据库，它们自动处理上面提到的数据摄取流程，例如<a href="https://link.zhihu.com/?target=https://www.pinecone.io/">Pinecone</a>、<a href="https://link.zhihu.com/?target=https://weaviate.io/">Weaviate</a>和<a href="https://link.zhihu.com/?target=https://www.trychroma.com/">Chroma</a>。</p><p>取决于你的索引选择、数据和搜索需求，还可以<strong>存储元数据</strong>，并使用<strong>元数据过滤器</strong>来按照日期或来源等条件进行信息检索。</p><p>LlamaIndex 支持多种<a href="https://link.zhihu.com/?target=https://docs.llamaindex.ai/en/latest/community/integrations/vector_stores.html">向量存储索引</a>，同时也兼容其他简单的索引类型，如列表索引、树索引和关键词表索引。关于这些索引，我们会在后续的融合检索部分详细介绍。</p><p><strong>2.2 分层索引</strong></p><img src="/2024/05/19/2024-05-19-LLM-RAG/hierarchical.png" class="" title="Hierarchical index retrieval"><p><img src="/Users/thechef/repositories/thechef-blog/source/_posts/2024-05-19-LLM-RAG/hierarchical.png" alt="img"></p><p>在大型数据库的情况下，一个有效的方法是创建两个索引——一个由摘要组成，另一个由文档块组成，然后分两步进行搜索，首先通过摘要过滤掉相关文档，然后只在这个相关组内搜索。</p><p><strong>2.3 假设性问题和 HyDE</strong></p><p>另一种方法是让 <strong>LLM 为每个块生成一个问题，并将这些问题嵌入到向量中</strong>，在运行时对这个问题向量的索引执行查询搜索（将块向量替换为索引中的问题向量），然后在检索后路由到原始文本块并将它们作为 LLM 获取答案的上下文发送。</p><p>这种方法提高了搜索质量，因为与实际块相比，<strong>查询和假设问题之间的语义相似性更高</strong>。</p><p>还有一种叫做 <a href="https://link.zhihu.com/?target=http://boston.lti.cs.cmu.edu/luyug/HyDE/HyDE.pdf">HyDE</a> 的反向逻辑方法——你要求 LLM 在给定查询的情况下生成一个假设的响应，然后将其向量与查询向量一起使用来提高搜索质量。</p><p><strong>2.4 内容增强</strong></p><p>这里的内容是将相关的上下文组合起来供 LLM 推理，以检索较小的块以获得更好的搜索质量。</p><p>有两种选择：一种是围绕较小的检索块的句子扩展上下文，另一种是递归地将文档拆分为多个较大的父块，其中包含较小的子块。</p><p><a href="https://link.zhihu.com/?target=https://docs.llamaindex.ai/en/stable/examples/node_postprocessor/MetadataReplacementDemo.html">2.4.1 语句窗口检索</a>器</p><p>在此方案中，文档中的每个句子都是单独嵌入的，这为上下文余弦距离搜索提供了极大的查询准确性。</p><p>为了在获取最相关的单个句子后更好地推理找到的上下文，我们将上下文窗口扩展为检索到的句子前后的 k 个句子，然后将这个扩展的上下文发送到 LLM。</p><img src="/2024/05/19/2024-05-19-LLM-RAG/sentence.png" class="" title="Sentence window retrieval"><p><img src="/Users/thechef/repositories/thechef-blog/source/_posts/2024-05-19-LLM-RAG/sentence.png" alt="img"></p><p>绿色部分是在索引中搜索时发现的句子嵌入，整个黑色 + 绿色段落被送到 LLM 以扩大其上下文，同时根据提供的查询进行推理。</p><p>2.4.2 <a href="https://link.zhihu.com/?target=https://docs.llamaindex.ai/en/latest/examples/retrievers/auto_merging_retriever.html">自动合并检索</a>器（或<a href="https://link.zhihu.com/?target=https://python.langchain.com/docs/modules/data_connection/retrievers/parent_document_retriever">父文档检索</a>器)</p><p>这里的思路与语句窗口检索器非常相似——搜索更精细的信息片段，然后在在LLM 进行推理之前扩展上下文窗口。文档被拆分为较小的子块，这些子块和较大的父块有引用关系。</p><img src="/2024/05/19/2024-05-19-LLM-RAG/parent_child.png" class="" title="Parent-child chunks retrieval"><p><img src="/Users/thechef/repositories/thechef-blog/source/_posts/2024-05-19-LLM-RAG/parent_child.png" alt="img"></p><p>首先在检索过程中获取较小的块，然后如果前 k 个检索到的块中有超过 n 个块链接到同一个父节点（较大的块），我们将这个父节点替换成给 LLM 的上下文——工作原理类似于自动将一些检索到的块合并到一个更大的父块中，因此得名。请注意，搜索仅在子节点索引中执行。查看 <a href="https://link.zhihu.com/?target=https://docs.llamaindex.ai/en/stable/examples/retrievers/recursive_retriever_nodes.html">LlamaIndex 教程 递归检索器 + 节点引用</a> 以更深入地了解。</p><p><strong>2.5 融合检索或混合搜索</strong></p><p>这是一个很早以前的思路：结合传统的基于关键字的搜索（稀疏检索算法，如 <a href="https://link.zhihu.com/?target=https://en.wikipedia.org/wiki/Tf%E2%80%93idf">tf-idf</a> 或搜索行业标准 <a href="https://link.zhihu.com/?target=https://en.wikipedia.org/wiki/Okapi_BM25">BM25</a>）和现代语义或向量搜索，并将其结果组合在一个检索结果中。</p><p>这里唯一的关键是如何组合不同相似度分数的检索结果。这个问题通常通过 <a href="https://link.zhihu.com/?target=https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf">Reciprocal Rank Fusion</a> 算法来解决，该算法能有效地对检索结果进行重新排序，以得到最终的输出结果。</p><img src="/2024/05/19/2024-05-19-LLM-RAG/fusion.png" class="" title="Fusion retrieval"><p><img src="/Users/thechef/repositories/thechef-blog/source/_posts/2024-05-19-LLM-RAG/fusion.png" alt="img"></p><p>在 LangChain 中，这种方法是通过 <a href="https://link.zhihu.com/?target=https://python.langchain.com/docs/modules/data_connection/retrievers/ensemble">Ensemble Retriever</a> 来实现的，该类将你定义的多个检索器结合起来，比如一个基于 faiss 的向量索引和一个基于 BM25 的检索器，并利用 RRF 算法进行结果的重排。</p><p>在 LlamaIndex 中，这一过程也是以类似的方式 <a href="https://link.zhihu.com/?target=https://docs.llamaindex.ai/en/stable/examples/retrievers/reciprocal_rerank_fusion.html">实现</a> 的。</p><p>混合或融合搜索通常能提供更优秀的检索结果，因为它结合了两种互补的搜索算法——既考虑了查询和存储文档之间的语义相似性，也考虑了关键词匹配。</p>]]></content>
    
    
    <summary type="html">&lt;h1 id=&quot;检索增强生成（RAG）&quot;&gt;&lt;a href=&quot;#检索增强生成（RAG）&quot; class=&quot;headerlink&quot; title=&quot;检索增强生成（RAG）&quot;&gt;&lt;/a&gt;检索增强生成（RAG）&lt;/h1&gt;&lt;h2 id=&quot;引言&quot;&gt;&lt;a href=&quot;#引言&quot; class=&quot;headerlink&quot; title=&quot;引言&quot;&gt;&lt;/a&gt;引言&lt;/h2&gt;&lt;p&gt;检索增强生成（Retrieval Augmented Generation），简称 RAG，已经成为当前最火热的LLM应用方案。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;检索增强生成 (RAG) 是一种使用来自私有或专有数据源的信息来辅助文本生成的技术。&lt;/strong&gt;它将检索模型（设计用于搜索大型数据集或知识库）和生成模型（例如大型语言模型 (LLM)，此类模型会使用检索到的信息生成可供阅读的文本回复）结合在一起。&lt;/p&gt;
&lt;p&gt;通过从更多数据源添加背景信息，以及通过训练来补充 LLM 的原始知识库，检索增强生成能够提高搜索体验的相关性。这能够改善大型语言模型的输出，但又无需重新训练模型。额外信息源的范围很广，从训练 LLM 时并未用到的互联网上的新信息，到专有商业背景信息，或者属于企业的机密内部文档，都会包含在内。&lt;/p&gt;</summary>
    
    
    
    <category term="知识积累" scheme="http://example.com/categories/%E7%9F%A5%E8%AF%86%E7%A7%AF%E7%B4%AF/"/>
    
    
    <category term="llm" scheme="http://example.com/tags/llm/"/>
    
    <category term="rag" scheme="http://example.com/tags/rag/"/>
    
  </entry>
  
  <entry>
    <title>[2024-04-11] LLM #1:Agent简介</title>
    <link href="http://example.com/2024/04/11/2024-04-11-1-LLM-Agent/"/>
    <id>http://example.com/2024/04/11/2024-04-11-1-LLM-Agent/</id>
    <published>2024-04-10T16:23:04.000Z</published>
    <updated>2024-05-19T15:23:26.376Z</updated>
    
    <content type="html"><![CDATA[<img src="/2024/04/11/2024-04-11-1-LLM-Agent/agent-overview.png" class="" title="Agent Overview"><h1 id="Agent介绍"><a href="#Agent介绍" class="headerlink" title="Agent介绍"></a>Agent介绍</h1><h2 id="什么是-AI-代理"><a href="#什么是-AI-代理" class="headerlink" title="什么是 AI 代理"></a>什么是 AI 代理</h2><p>AI代理（AI agent）是指使用 AI 技术设计和编程的一种计算机程序，其可以独立地进行某些任务并对环境做出反应。AI代理可以被视为一个智能体，它能够感知其环境，通过自己的决策和行动来改变环境，并通过学习和适应来提高其性能。这种智能体同时使用短期记忆（上下文学习）和长期记忆（从外部向量存储中检索信息），有能力通过逐步“思考”来计划、将目标分解为更小的任务 ，并反思自己的表现。AI代理通常包含多种技术，如机器学习、自然语言处理、计算机视觉、规划和推理等，这些技术使代理能够自主地处理信息并作出决策。</p><span id="more"></span><h2 id="什么是-LLM-支持的自主代理"><a href="#什么是-LLM-支持的自主代理" class="headerlink" title="什么是 LLM 支持的自主代理"></a>什么是 LLM 支持的自主代理</h2><p>OpenAI AI应用研究主管 Lilian Weng 最近发布了一篇关于 AI 代理的万字长文：《大语言模型（LLM）支持的自主代理》，深度解读了什么是由 LLM 训练构建的 AI 代理应用。LLM 支持的 AI 代理现在已经出现了很多优秀的应用，例如 AutoGPT、GPT-Engineer、BabyAGI 和 SuperAGI 等。在LLM 支持的自主代理系统中，LLM 充当代理的大脑，并由几个关键组件进行补充：规划（Planning）、内存（Memory）、工具使用（Tool Use)。</p><h2 id="任务规划"><a href="#任务规划" class="headerlink" title="任务规划"></a>任务规划</h2><ul><li><p>任务拆分：<br>复杂任务不是一次性就能解决的，需要拆分成多个并行或串行的子任务来进行求解，任务规划的目标是找到一条最优的、能够解决问题的路线</p></li><li><p>自我反省：<br>自我反思是一个重要的方面，它允许自主代理通过完善过去的行动决策和纠正以前的错误来迭代改进。它在不可避免地会出现试错的现实任务中发挥着至关重要的作用。ReAct (Yao et al. 2023) 发现让Agents执行下一步action的时候，加上LLM自己的思考过程，并将思考过程、执行的工具及参数、执行的结果放到prompt中，就能使得模型对当前和先前的任务完成度有更好的反思能力，从而提升模型的问题解决能力。</p></li></ul><figure class="highlight md"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Thought: ... </span><br><span class="line">Action: ... </span><br><span class="line">Observation: ... </span><br><span class="line">...(重复以上过程）</span><br></pre></td></tr></table></figure><ul><li><p>思想链：<br>已成为增强复杂任务模型性能的标准提示技术。该模型被指示“一步一步思考”，以利用更多的测试时间计算将困难任务分解为更小、更简单的步骤。 CoT 将大型任务转化为多个可管理的任务，并阐明模型思维过程的解释。</p></li><li><p>思维树：<br>通过在每一步探索多种推理可能性来扩展 CoT。它首先将问题分解为多个思考步骤，并在每个步骤中生成多个思考，从而创建树结构。搜索过程可以是 BFS（广度优先搜索）或 DFS（深度优先搜索），每个状态由分类器（通过提示）或多数投票进行评估。</p></li></ul><h2 id="记忆"><a href="#记忆" class="headerlink" title="记忆"></a>记忆</h2><ul><li><p>感知记忆：<br>这是记忆的最早阶段，提供在原始刺激结束后保留感觉信息（视觉、听觉等）印象的能力。感知记忆通常只能持续几秒钟。子类别包括图像记忆（视觉）、回声记忆（听觉）和触觉记忆（触摸）。感知记忆作为原始输入的学习嵌入表示，包括文本、图像或其他形式。</p></li><li><p>短期记忆：<br>它存储我们当前意识到的以及执行学习和推理等复杂认知任务所需的信息。短期记忆被认为具有大约 7 个项目的容量（Miller 1956）并且持续 20-30 秒。短期记忆作为情境学习。它是短且有限的，因为它受到 Transformer 有限上下文窗口长度的限制。</p></li><li><p>长期记忆（LTM）：<br>长期记忆可以存储相当长的时间信息，从几天到几十年不等，存储容量基本上是无限的。 LTM 有两种亚型：</p></li></ul><ol><li><p>外显&#x2F;陈述性记忆：这是对事实和事件的记忆，是指那些可以有意识地回忆起来的记忆，包括情景记忆（事件和经历）和语义记忆（事实和概念）。</p></li><li><p>内隐&#x2F;程序性记忆：这种类型的记忆是无意识的，涉及自动执行的技能和例程，例如骑自行车或在键盘上打字。</p></li></ol><p>长期记忆作为代理在查询时可以处理的外部向量存储，可通过快速检索进行访问。</p><h2 id="工具使用"><a href="#工具使用" class="headerlink" title="工具使用"></a>工具使用</h2><ul><li>代理学习调用外部 API 来获取模型权重中缺失的额外信息（通常在预训练后很难更改），包括当前信息、代码执行能力、对专有信息源的访问等。</li><li>API-Bank (Li et al. 2023) 是评估工具增强LLM性能的基准。它包含 53 个常用的 API 工具、一个完整的工具增强的 LLM 工作流程，以及涉及 568 个 API 调用的 264 个带注释的对话。API的选择非常多样化，包括搜索引擎，计算器，日历查询，智能家居控制，日程安排管理，健康数据管理，帐户身份验证工作流程等。因为有大量的API，LLM首先可以访问API搜索引擎找到合适的API调用，然后使用相应的文档进行调用。</li><li>清华发表的ToolLLM（Qin et al. 2023）中大模型能够使用的API高达16000多个。</li></ul>]]></content>
    
    
    <summary type="html">&lt;img src=&quot;/2024/04/11/2024-04-11-1-LLM-Agent/agent-overview.png&quot; class=&quot;&quot; title=&quot;Agent Overview&quot;&gt;

&lt;h1 id=&quot;Agent介绍&quot;&gt;&lt;a href=&quot;#Agent介绍&quot; class=&quot;headerlink&quot; title=&quot;Agent介绍&quot;&gt;&lt;/a&gt;Agent介绍&lt;/h1&gt;&lt;h2 id=&quot;什么是-AI-代理&quot;&gt;&lt;a href=&quot;#什么是-AI-代理&quot; class=&quot;headerlink&quot; title=&quot;什么是 AI 代理&quot;&gt;&lt;/a&gt;什么是 AI 代理&lt;/h2&gt;&lt;p&gt;AI代理（AI agent）是指使用 AI 技术设计和编程的一种计算机程序，其可以独立地进行某些任务并对环境做出反应。AI代理可以被视为一个智能体，它能够感知其环境，通过自己的决策和行动来改变环境，并通过学习和适应来提高其性能。这种智能体同时使用短期记忆（上下文学习）和长期记忆（从外部向量存储中检索信息），有能力通过逐步“思考”来计划、将目标分解为更小的任务 ，并反思自己的表现。AI代理通常包含多种技术，如机器学习、自然语言处理、计算机视觉、规划和推理等，这些技术使代理能够自主地处理信息并作出决策。&lt;/p&gt;</summary>
    
    
    
    <category term="知识积累" scheme="http://example.com/categories/%E7%9F%A5%E8%AF%86%E7%A7%AF%E7%B4%AF/"/>
    
    
    <category term="llm" scheme="http://example.com/tags/llm/"/>
    
    <category term="agent" scheme="http://example.com/tags/agent/"/>
    
  </entry>
  
  <entry>
    <title>[2023-12-27] Flask #1:Flask简单使用</title>
    <link href="http://example.com/2023/12/27/2023-12-27-2-Flask%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8/"/>
    <id>http://example.com/2023/12/27/2023-12-27-2-Flask%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8/</id>
    <published>2023-12-27T14:58:25.000Z</published>
    <updated>2024-05-05T12:30:21.413Z</updated>
    
    <content type="html"><![CDATA[<img src="/2023/12/27/2023-12-27-2-Flask%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8/flask.png" class="" title="Flask"><h1 id="Flask快速上手"><a href="#Flask快速上手" class="headerlink" title="Flask快速上手"></a>Flask快速上手</h1><h2 id="环境下载"><a href="#环境下载" class="headerlink" title="环境下载"></a>环境下载</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install flask</span><br></pre></td></tr></table></figure><h2 id="Flask服务-Flask-Server"><a href="#Flask服务-Flask-Server" class="headerlink" title="Flask服务(Flask Server)"></a>Flask服务(Flask Server)</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask, request, send_file, make_response</span><br><span class="line"><span class="keyword">from</span> model <span class="keyword">import</span> *</span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> torch</span><br><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"></span><br><span class="line"><span class="comment"># Use CUDA</span></span><br><span class="line">os.environ[<span class="string">&#x27;CUDA_VISIBLE_DEVICES&#x27;</span>] = <span class="string">&#x27;0&#x27;</span></span><br><span class="line">os.environ[<span class="string">&#x27;CUDA_LAUNCH_BLOCKING&#x27;</span>] = <span class="string">&#x27;1&#x27;</span></span><br><span class="line">use_cuda = torch.cuda.is_available()</span><br><span class="line"></span><br><span class="line">app = Flask(__name__)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 载入模型</span></span><br><span class="line">model=...</span><br><span class="line">root_dir = ...</span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> os.path.exists(root_dir):</span><br><span class="line">    os.makedirs(root_dir)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义服务接口</span></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/predict_from_file&#x27;</span>, methods=[<span class="string">&#x27;POST&#x27;</span>]</span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">predict</span>():</span><br><span class="line">    file = request.files.get(<span class="string">&#x27;file&#x27;</span>)  <span class="comment"># 获取上传的文件</span></span><br><span class="line">    <span class="keyword">if</span> file:</span><br><span class="line">        file.save(root_dir + <span class="string">&#x27;/&#x27;</span> + file.filename)  <span class="comment"># 将上传文件保存到本地</span></span><br><span class="line">        input_file = load(root_dir + <span class="string">&#x27;/&#x27;</span> + file.filename)  <span class="comment"># 读取本地文件</span></span><br><span class="line">        results = model.inference(input_file)  <span class="comment"># 对本地文件进行推理计算</span></span><br><span class="line">        <span class="comment"># 返回预测结果</span></span><br><span class="line">        <span class="keyword">return</span> make_response(results)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&#x27;No file uploaded&#x27;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义服务接口</span></span><br><span class="line"><span class="meta">@app.route(<span class="params"><span class="string">&#x27;/get_file&#x27;</span>, methods=[<span class="string">&#x27;GET&#x27;</span>]</span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">getresult</span>():</span><br><span class="line">    filename = request.args.get(<span class="string">&#x27;file&#x27;</span>)  <span class="comment"># 获取请求参数中的文件名</span></span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> filename:</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Missing parameter: file&quot;</span>  <span class="comment"># 没有提供文件名</span></span><br><span class="line">    filepath = root_dir + <span class="string">&#x27;/&#x27;</span> + filename  <span class="comment"># 生成完整的文件路径</span></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="keyword">return</span> send_file(filepath, as_attachment=<span class="literal">True</span>, attachment_filename=filename)</span><br><span class="line">    <span class="keyword">except</span> FileNotFoundError:</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;The file does not exist&quot;</span>  <span class="comment"># 文件不存在</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    app.run(host=<span class="string">&#x27;0.0.0.0&#x27;</span>, port=<span class="number">8000</span>)</span><br></pre></td></tr></table></figure><span id="more"></span><h2 id="Flask客户端-Flask-Client"><a href="#Flask客户端-Flask-Client" class="headerlink" title="Flask客户端(Flask Client)"></a>Flask客户端(Flask Client)</h2>]]></content>
    
    
    <summary type="html">&lt;img src=&quot;/2023/12/27/2023-12-27-2-Flask%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8/flask.png&quot; class=&quot;&quot; title=&quot;Flask&quot;&gt;

&lt;h1 id=&quot;Flask快速上手&quot;&gt;&lt;a href=&quot;#Flask快速上手&quot; class=&quot;headerlink&quot; title=&quot;Flask快速上手&quot;&gt;&lt;/a&gt;Flask快速上手&lt;/h1&gt;&lt;h2 id=&quot;环境下载&quot;&gt;&lt;a href=&quot;#环境下载&quot; class=&quot;headerlink&quot; title=&quot;环境下载&quot;&gt;&lt;/a&gt;环境下载&lt;/h2&gt;&lt;figure class=&quot;highlight python&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;pip install flask&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;Flask服务-Flask-Server&quot;&gt;&lt;a href=&quot;#Flask服务-Flask-Server&quot; class=&quot;headerlink&quot; title=&quot;Flask服务(Flask Server)&quot;&gt;&lt;/a&gt;Flask服务(Flask Server)&lt;/h2&gt;&lt;figure class=&quot;highlight python&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;39&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;40&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;41&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;42&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;43&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;44&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;45&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;46&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;47&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;from&lt;/span&gt; flask &lt;span class=&quot;keyword&quot;&gt;import&lt;/span&gt; Flask, request, send_file, make_response&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;from&lt;/span&gt; model &lt;span class=&quot;keyword&quot;&gt;import&lt;/span&gt; *&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;import&lt;/span&gt; os&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;import&lt;/span&gt; torch&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;import&lt;/span&gt; cv2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Use CUDA&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;os.environ[&lt;span class=&quot;string&quot;&gt;&amp;#x27;CUDA_VISIBLE_DEVICES&amp;#x27;&lt;/span&gt;] = &lt;span class=&quot;string&quot;&gt;&amp;#x27;0&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;os.environ[&lt;span class=&quot;string&quot;&gt;&amp;#x27;CUDA_LAUNCH_BLOCKING&amp;#x27;&lt;/span&gt;] = &lt;span class=&quot;string&quot;&gt;&amp;#x27;1&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;use_cuda = torch.cuda.is_available()&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;app = Flask(__name__)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 载入模型&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;model=...&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;root_dir = ...&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;not&lt;/span&gt; os.path.exists(root_dir):&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    os.makedirs(root_dir)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 定义服务接口&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@app.route(&lt;span class=&quot;params&quot;&gt;&lt;span class=&quot;string&quot;&gt;&amp;#x27;/predict_from_file&amp;#x27;&lt;/span&gt;, methods=[&lt;span class=&quot;string&quot;&gt;&amp;#x27;POST&amp;#x27;&lt;/span&gt;]&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;predict&lt;/span&gt;():&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    file = request.files.get(&lt;span class=&quot;string&quot;&gt;&amp;#x27;file&amp;#x27;&lt;/span&gt;)  &lt;span class=&quot;comment&quot;&gt;# 获取上传的文件&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; file:&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        file.save(root_dir + &lt;span class=&quot;string&quot;&gt;&amp;#x27;/&amp;#x27;&lt;/span&gt; + file.filename)  &lt;span class=&quot;comment&quot;&gt;# 将上传文件保存到本地&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        input_file = load(root_dir + &lt;span class=&quot;string&quot;&gt;&amp;#x27;/&amp;#x27;&lt;/span&gt; + file.filename)  &lt;span class=&quot;comment&quot;&gt;# 读取本地文件&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        results = model.inference(input_file)  &lt;span class=&quot;comment&quot;&gt;# 对本地文件进行推理计算&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;# 返回预测结果&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; make_response(results)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;else&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#x27;No file uploaded&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 定义服务接口&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@app.route(&lt;span class=&quot;params&quot;&gt;&lt;span class=&quot;string&quot;&gt;&amp;#x27;/get_file&amp;#x27;&lt;/span&gt;, methods=[&lt;span class=&quot;string&quot;&gt;&amp;#x27;GET&amp;#x27;&lt;/span&gt;]&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;getresult&lt;/span&gt;():&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    filename = request.args.get(&lt;span class=&quot;string&quot;&gt;&amp;#x27;file&amp;#x27;&lt;/span&gt;)  &lt;span class=&quot;comment&quot;&gt;# 获取请求参数中的文件名&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;not&lt;/span&gt; filename:&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;Missing parameter: file&amp;quot;&lt;/span&gt;  &lt;span class=&quot;comment&quot;&gt;# 没有提供文件名&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    filepath = root_dir + &lt;span class=&quot;string&quot;&gt;&amp;#x27;/&amp;#x27;&lt;/span&gt; + filename  &lt;span class=&quot;comment&quot;&gt;# 生成完整的文件路径&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;try&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; send_file(filepath, as_attachment=&lt;span class=&quot;literal&quot;&gt;True&lt;/span&gt;, attachment_filename=filename)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;except&lt;/span&gt; FileNotFoundError:&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;The file does not exist&amp;quot;&lt;/span&gt;  &lt;span class=&quot;comment&quot;&gt;# 文件不存在&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; __name__ == &lt;span class=&quot;string&quot;&gt;&amp;#x27;__main__&amp;#x27;&lt;/span&gt;:&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    app.run(host=&lt;span class=&quot;string&quot;&gt;&amp;#x27;0.0.0.0&amp;#x27;&lt;/span&gt;, port=&lt;span class=&quot;number&quot;&gt;8000&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    <category term="技术积累" scheme="http://example.com/categories/%E6%8A%80%E6%9C%AF%E7%A7%AF%E7%B4%AF/"/>
    
    
    <category term="Code" scheme="http://example.com/tags/Code/"/>
    
    <category term="Python" scheme="http://example.com/tags/Python/"/>
    
    <category term="Flask" scheme="http://example.com/tags/Flask/"/>
    
  </entry>
  
  <entry>
    <title>[2023-09-22] Docker #3:Docker容器操作</title>
    <link href="http://example.com/2023/09/22/2023-09-22-Docker-3-Docker%E5%AE%B9%E5%99%A8%E6%93%8D%E4%BD%9C/"/>
    <id>http://example.com/2023/09/22/2023-09-22-Docker-3-Docker%E5%AE%B9%E5%99%A8%E6%93%8D%E4%BD%9C/</id>
    <published>2023-09-22T15:19:10.000Z</published>
    <updated>2023-09-22T15:54:34.586Z</updated>
    
    <content type="html"><![CDATA[<img src="/2023/09/22/2023-09-22-Docker-3-Docker%E5%AE%B9%E5%99%A8%E6%93%8D%E4%BD%9C/contaner.png" class="" title="Vim"><h1 id="容器命令"><a href="#容器命令" class="headerlink" title="容器命令"></a>容器命令</h1><h2 id="1-启动容器"><a href="#1-启动容器" class="headerlink" title="1. 启动容器"></a>1. 启动容器</h2><p>以下命令使用 ubuntu 镜像启动一个容器，参数为以命令行模式进入该容器：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -it ubuntu /bin/bash</span><br></pre></td></tr></table></figure><p>参数说明：</p><ul><li><strong>-i</strong>: 交互式操作。</li><li><strong>-t</strong>: 终端。</li><li><strong>ubuntu</strong>: ubuntu 镜像。</li><li><strong>&#x2F;bin&#x2F;bash</strong>：放在镜像名后的是命令，这里我们希望有个交互式 Shell，因此用的是 &#x2F;bin&#x2F;bash。</li></ul><p>要退出终端，直接输入 <strong>exit</strong>:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">root@ed09e4490c57:/<span class="comment"># exit</span></span><br></pre></td></tr></table></figure>  <span id="more"></span><h3 id="启动已停止运行的容器"><a href="#启动已停止运行的容器" class="headerlink" title="启动已停止运行的容器"></a>启动已停止运行的容器</h3><p>查看所有的容器命令如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker ps -a</span><br></pre></td></tr></table></figure><p>使用 docker start 启动一个已停止的容器：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker start b750bbbcfd88 </span><br></pre></td></tr></table></figure><h3 id="后台运行"><a href="#后台运行" class="headerlink" title="后台运行"></a>后台运行</h3><p>在大部分的场景下，我们希望 docker 的服务是在后台运行的，我们可以过 <strong>-d</strong> 指定容器的运行模式。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -itd --name ubuntu-test ubuntu /bin/bash</span><br></pre></td></tr></table></figure><p><strong>注：</strong>加了 <strong>-d</strong> 参数默认不会进入容器，想要进入容器需要使用指令 <strong>docker exec</strong></p><h3 id="停止一个容器"><a href="#停止一个容器" class="headerlink" title="停止一个容器"></a>停止一个容器</h3><p>停止容器的命令如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker stop &lt;容器 ID&gt;</span><br></pre></td></tr></table></figure><p>停止的容器可以通过 docker restart 重启：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker restart &lt;容器 ID&gt;</span><br></pre></td></tr></table></figure><h2 id="2-进入容器"><a href="#2-进入容器" class="headerlink" title="2. 进入容器"></a>2. 进入容器</h2><p>在使用 <strong>-d</strong> 参数时，容器启动后会进入后台。此时想要进入容器，可以通过以下指令进入：</p><ul><li><strong>docker attach</strong></li><li><strong>docker exec</strong>：推荐大家使用 docker exec 命令，因为此命令会退出容器终端，但不会导致容器的停止。</li></ul><h3 id="attach-命令"><a href="#attach-命令" class="headerlink" title="attach 命令"></a>attach 命令</h3><p>下面演示了使用 docker attach 命令。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker attach 1e560fca3906 </span><br></pre></td></tr></table></figure><p><strong>注意：</strong> 如果从这个容器退出，会导致容器的停止。</p><h3 id="exec-命令"><a href="#exec-命令" class="headerlink" title="exec 命令"></a>exec 命令</h3><p>下面演示了使用 docker exec 命令。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable">$docker</span> <span class="built_in">exec</span> -it 243c32535da7 /bin/bash</span><br></pre></td></tr></table></figure><p><strong>注意：</strong> 如果从这个容器退出，容器不会停止，这就是为什么推荐大家使用 <strong>docker exec</strong> 的原因。</p><p>更多参数说明请使用 <strong>docker exec –help</strong> 命令查看。</p><h2 id="3-导出和导入容器"><a href="#3-导出和导入容器" class="headerlink" title="3. 导出和导入容器"></a>3. 导出和导入容器</h2><h3 id="导出容器"><a href="#导出容器" class="headerlink" title="导出容器"></a>导出容器</h3><p>如果要导出本地某个容器，可以使用 <strong>docker export</strong> 命令。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker <span class="built_in">export</span> 1e560fca3906 &gt; ubuntu.tar</span><br></pre></td></tr></table></figure><p>导出容器 1e560fca3906 快照到本地文件 ubuntu.tar。</p><h3 id="导入容器快照"><a href="#导入容器快照" class="headerlink" title="导入容器快照"></a>导入容器快照</h3><p>可以使用 docker import 从容器快照文件中再导入为镜像，以下实例将快照文件 ubuntu.tar 导入到镜像 test&#x2F;ubuntu:v1:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">cat</span> docker/ubuntu.tar | docker import - <span class="built_in">test</span>/ubuntu:v1</span><br></pre></td></tr></table></figure><h3 id="容器保存为镜像"><a href="#容器保存为镜像" class="headerlink" title="容器保存为镜像"></a>容器保存为镜像</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 将容器保存为镜像</span></span><br><span class="line"><span class="variable">$docker</span> commit [CONTAINER ID] [IMAGE NAME]   <span class="comment">#容器ID  创建的镜像名</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 docker save 命令根据 ID 将镜像保存成一个文件</span></span><br><span class="line">$ docker save 0fdf2b4c26d3 &gt; hangge_server.tar</span><br><span class="line"><span class="comment"># 还可以同时将多个 image 打包成一个文件，比如下面将镜像库中的 postgres 和 mongo 打包</span></span><br><span class="line">$ docker save -o images.tar postgres:9.6 mongo:3.4</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 docker load 命令则可将这个镜像文件载入进来</span></span><br><span class="line">$ docker load &lt; hangge_server.tar</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;img src=&quot;/2023/09/22/2023-09-22-Docker-3-Docker%E5%AE%B9%E5%99%A8%E6%93%8D%E4%BD%9C/contaner.png&quot; class=&quot;&quot; title=&quot;Vim&quot;&gt;


&lt;h1 id=&quot;容器命令&quot;&gt;&lt;a href=&quot;#容器命令&quot; class=&quot;headerlink&quot; title=&quot;容器命令&quot;&gt;&lt;/a&gt;容器命令&lt;/h1&gt;&lt;h2 id=&quot;1-启动容器&quot;&gt;&lt;a href=&quot;#1-启动容器&quot; class=&quot;headerlink&quot; title=&quot;1. 启动容器&quot;&gt;&lt;/a&gt;1. 启动容器&lt;/h2&gt;&lt;p&gt;以下命令使用 ubuntu 镜像启动一个容器，参数为以命令行模式进入该容器：&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;$ docker run -it ubuntu /bin/bash&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;参数说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;-i&lt;/strong&gt;: 交互式操作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-t&lt;/strong&gt;: 终端。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ubuntu&lt;/strong&gt;: ubuntu 镜像。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;#x2F;bin&amp;#x2F;bash&lt;/strong&gt;：放在镜像名后的是命令，这里我们希望有个交互式 Shell，因此用的是 &amp;#x2F;bin&amp;#x2F;bash。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;要退出终端，直接输入 &lt;strong&gt;exit&lt;/strong&gt;:&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;root@ed09e4490c57:/&lt;span class=&quot;comment&quot;&gt;# exit&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    <category term="技术积累" scheme="http://example.com/categories/%E6%8A%80%E6%9C%AF%E7%A7%AF%E7%B4%AF/"/>
    
    
    <category term="Code" scheme="http://example.com/tags/Code/"/>
    
    <category term="Docker" scheme="http://example.com/tags/Docker/"/>
    
  </entry>
  
  <entry>
    <title>[2023-09-14] Docker #2:Docker镜像操作</title>
    <link href="http://example.com/2023/09/15/2023-09-14-Docker-2-Docker%E9%95%9C%E5%83%8F%E6%93%8D%E4%BD%9C/"/>
    <id>http://example.com/2023/09/15/2023-09-14-Docker-2-Docker%E9%95%9C%E5%83%8F%E6%93%8D%E4%BD%9C/</id>
    <published>2023-09-14T16:15:23.000Z</published>
    <updated>2023-09-14T16:16:14.816Z</updated>
    
    <content type="html"><![CDATA[<img src="/2023/09/15/2023-09-14-Docker-2-Docker%E9%95%9C%E5%83%8F%E6%93%8D%E4%BD%9C/docker_image.jpg" class="" title="Docker"><h1 id="镜像命令"><a href="#镜像命令" class="headerlink" title="镜像命令"></a>镜像命令</h1><h2 id="1-获取镜像"><a href="#1-获取镜像" class="headerlink" title="1. 获取镜像"></a>1. 获取镜像</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]</span><br></pre></td></tr></table></figure><span id="more"></span><h2 id="2-列出镜像"><a href="#2-列出镜像" class="headerlink" title="2. 列出镜像"></a>2. 列出镜像</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1.显示顶层镜像</span></span><br><span class="line">$ docker images </span><br><span class="line"><span class="comment"># or</span></span><br><span class="line">$ docker image <span class="built_in">ls</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2.显示中间层镜像</span></span><br><span class="line">$ docker images -a</span><br><span class="line"><span class="comment"># or</span></span><br><span class="line">$ docker image <span class="built_in">ls</span> -a</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3.只显示镜像ID</span></span><br><span class="line">$ docker image <span class="built_in">ls</span> -q</span><br><span class="line">5f515359c7f8</span><br><span class="line">05a60462f8ba</span><br><span class="line">fe9198c04d62</span><br><span class="line">00285df0df87</span><br><span class="line">329ed837d508</span><br><span class="line">329ed837d508</span><br></pre></td></tr></table></figure><h2 id="3-运行镜像"><a href="#3-运行镜像" class="headerlink" title="3. 运行镜像"></a>3. 运行镜像</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker run [OPTIONS] IMAGE [COMMAND] [ARG...]</span><br></pre></td></tr></table></figure><p><strong>OPTIONS说明：</strong></p><p>-t: 为容器重新分配一个伪输入终端，通常与 -i 同时使用；</p><p>-d: 后台运行容器，并返回容器ID；</p><p>-i: 以交互模式运行容器，通常与 -t 同时使用；</p><p>-P: 随机端口映射，容器内部端口随机映射到主机的端口</p><p>-p: 指定端口映射，格式为：主机(宿主)端口:容器端口</p><p>–name&#x3D;”nginx-lb”: 为容器指定一个名称；</p><p>–volume , -v: 绑定一个卷</p><p><strong>实例</strong></p><ul><li>使用docker镜像nginx:latest以后台模式启动一个容器,并将容器命名为mynginx。</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run --name mynginx -d nginx:latest</span><br></pre></td></tr></table></figure><ul><li>使用镜像nginx:latest以后台模式启动一个容器,并将容器的80端口映射到主机随机端口。</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -P -d nginx:latest</span><br></pre></td></tr></table></figure><ul><li>使用镜像 nginx:latest，以后台模式启动一个容器,将容器的 80 端口映射到主机的 80 端口,主机的目录 &#x2F;data 映射到容器的 &#x2F;data。</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -p 80:80 -v /data:/data -d nginx:latest</span><br></pre></td></tr></table></figure><ul><li>绑定容器的 8080 端口，并将其映射到本地主机 127.0.0.1 的 80 端口上。</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -p 127.0.0.1:80:8080/tcp ubuntu bash</span><br></pre></td></tr></table></figure><ul><li>使用镜像nginx:latest以交互模式启动一个容器,在容器内执行&#x2F;bin&#x2F;bash命令。</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">runoob@runoob:~$ docker run -it nginx:latest /bin/bash</span><br><span class="line">root@b8573233d675:/<span class="comment"># </span></span><br></pre></td></tr></table></figure><h2 id="4-删除镜像"><a href="#4-删除镜像" class="headerlink" title="4. 删除镜像"></a>4. 删除镜像</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">$ docker image <span class="built_in">rm</span> [选项] &lt;镜像1&gt; [&lt;镜像2&gt; ...]</span><br><span class="line"><span class="comment"># or </span></span><br><span class="line">$ docker rmi &lt;image1&gt; [&lt;image2&gt;]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除仓库名为redis的镜像</span></span><br><span class="line">$ docker image <span class="built_in">rm</span> $(docker image <span class="built_in">ls</span> -q redis)</span><br><span class="line"><span class="comment"># 删除所有在 mongo:3.2 之前的镜像：</span></span><br><span class="line">$ docker image <span class="built_in">rm</span> $(docker image <span class="built_in">ls</span> -q -f before=mongo:3.2)</span><br></pre></td></tr></table></figure><h2 id="5-保存镜像"><a href="#5-保存镜像" class="headerlink" title="5. 保存镜像"></a>5. 保存镜像</h2><ul><li><code>docker commit [选项] &lt;容器ID或容器名&gt; [&lt;仓库名&gt;[:&lt;标签&gt;]]</code>: 从运行的容器中保存</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 运行一个容器</span></span><br><span class="line"><span class="comment"># 这条命令会用 nginx 镜像启动一个容器，命名为 webserver，并且映射了 80 端口，这样我们可以用浏览器去访问这个 nginx 服务器。</span></span><br><span class="line">$ docker run --name webserver -d -p 80:80 nginx</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 进入容器，进行修改</span></span><br><span class="line">$ docker <span class="built_in">exec</span> -it webserver bash</span><br><span class="line">root@3729b97e8226:/<span class="comment"># echo &#x27;&lt;h1&gt;Hello, Docker!&lt;/h1&gt;&#x27; &gt; /usr/share/nginx/html/index.html</span></span><br><span class="line">root@3729b97e8226:/<span class="comment"># exit</span></span><br><span class="line"><span class="built_in">exit</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 执行 docker commit 命令将修改后的容器保存为新的镜像</span></span><br><span class="line">$ docker commit \</span><br><span class="line">    --author <span class="string">&quot;Tao Wang &lt;twang2218@gmail.com&gt;&quot;</span> \</span><br><span class="line">    --message <span class="string">&quot;修改了默认网页&quot;</span> \</span><br><span class="line">    webserver \</span><br><span class="line">    nginx:v2</span><br><span class="line">sha256:07e33465974800ce65751acc279adc6ed2dc5ed4e0838f8b86f0c87aa1795214</span><br></pre></td></tr></table></figure><h2 id="6-镜像导出和导入-保存和载入"><a href="#6-镜像导出和导入-保存和载入" class="headerlink" title="6. 镜像导出和导入&#x2F;保存和载入"></a>6. 镜像导出和导入&#x2F;保存和载入</h2><ul><li><code>docker export; docker import</code> 或者<code>docker save; docker load</code></li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 方式1</span></span><br><span class="line"><span class="comment"># 使用 docker export 命令根据容器 ID 将镜像导出成一个文件</span></span><br><span class="line">$ docker <span class="built_in">export</span> f299f501774c &gt; hangger_server.tar</span><br><span class="line"><span class="comment"># 使用 docker import 命令则可将这个镜像文件导入进来</span></span><br><span class="line">$ docker import - new_hangger_server &lt; hangger_server.tar</span><br><span class="line"></span><br><span class="line"><span class="comment"># 方式2</span></span><br><span class="line"><span class="comment"># 使用 docker save 命令根据 ID 将镜像保存成一个文件</span></span><br><span class="line">$ docker save 0fdf2b4c26d3 &gt; hangge_server.tar</span><br><span class="line"><span class="comment"># 还可以同时将多个 image 打包成一个文件，比如下面将镜像库中的 postgres 和 mongo 打包</span></span><br><span class="line">$ docker save -o images.tar postgres:9.6 mongo:3.4</span><br><span class="line"><span class="comment"># 使用 docker load 命令则可将这个镜像文件载入进来</span></span><br><span class="line">$ docker load &lt; hangge_server.tar</span><br></pre></td></tr></table></figure><ul><li>注意：<ul><li><code>docker export; docker import</code>或<code>docker save; docker load</code>必须成对使用</li><li><strong>docker export 的应用场景</strong>：主要用来制作基础镜像，比如我们从一个 <strong>ubuntu</strong> 镜像启动一个容器，然后安装一些软件和进行一些设置后，使用 <strong>docker export</strong> 保存为一个基础镜像。然后，把这个镜像分发给其他人使用，比如作为基础的开发环境。</li><li><strong>docker save 的应用场景</strong>：如果我们的应用是使用 <strong>docker-compose.yml</strong> 编排的多个镜像组合，但我们要部署的客户服务器并不能连外网。这时就可以使用 <strong>docker save</strong> 将用到的镜像打个包，然后拷贝到客户服务器上使用 <strong>docker load</strong> 载入。</li></ul></li></ul>]]></content>
    
    
    <summary type="html">&lt;img src=&quot;/2023/09/15/2023-09-14-Docker-2-Docker%E9%95%9C%E5%83%8F%E6%93%8D%E4%BD%9C/docker_image.jpg&quot; class=&quot;&quot; title=&quot;Docker&quot;&gt;

&lt;h1 id=&quot;镜像命令&quot;&gt;&lt;a href=&quot;#镜像命令&quot; class=&quot;headerlink&quot; title=&quot;镜像命令&quot;&gt;&lt;/a&gt;镜像命令&lt;/h1&gt;&lt;h2 id=&quot;1-获取镜像&quot;&gt;&lt;a href=&quot;#1-获取镜像&quot; class=&quot;headerlink&quot; title=&quot;1. 获取镜像&quot;&gt;&lt;/a&gt;1. 获取镜像&lt;/h2&gt;&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    <category term="技术积累" scheme="http://example.com/categories/%E6%8A%80%E6%9C%AF%E7%A7%AF%E7%B4%AF/"/>
    
    
    <category term="Code" scheme="http://example.com/tags/Code/"/>
    
    <category term="Docker" scheme="http://example.com/tags/Docker/"/>
    
  </entry>
  
  <entry>
    <title>[2023-09-13] Docker #1:认识Docker</title>
    <link href="http://example.com/2023/09/13/2023-09-13-Docker-1-%E8%AE%A4%E8%AF%86Docker/"/>
    <id>http://example.com/2023/09/13/2023-09-13-Docker-1-%E8%AE%A4%E8%AF%86Docker/</id>
    <published>2023-09-13T12:19:15.000Z</published>
    <updated>2023-09-13T12:35:41.540Z</updated>
    
    <content type="html"><![CDATA[<img src="/2023/09/13/2023-09-13-Docker-1-%E8%AE%A4%E8%AF%86Docker/docker.jpg" class="" title="Docker"><h1 id="Docker介绍"><a href="#Docker介绍" class="headerlink" title="Docker介绍"></a>Docker介绍</h1><p>Docker 是一个开源的应用容器引擎，基于 [Go 语言] 并遵从 Apache2.0 协议开源。</p><p>Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中，然后发布到任何流行的 Linux 机器上，也可以实现虚拟化。</p><p>容器是完全使用沙箱机制，相互之间不会有任何接口（类似 iPhone 的 app）,更重要的是容器性能开销极低。</p><span id="more"></span><h2 id="Docker应用场景"><a href="#Docker应用场景" class="headerlink" title="Docker应用场景"></a>Docker应用场景</h2><ul><li>Web 应用的自动化打包和发布。</li><li>自动化测试和持续集成、发布。</li><li>在服务型环境中部署和调整数据库或其他的后台应用。</li><li>从头编译或者扩展现有的 OpenShift 或 Cloud Foundry 平台来搭建自己的 PaaS 环境。</li></ul><h2 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h2><h3 id="一致的运行环境"><a href="#一致的运行环境" class="headerlink" title="一致的运行环境"></a>一致的运行环境</h3><p>开发过程中一个常见的问题是环境一致性问题。由于开发环境、测试环境、生产环境不一致，导致有些 bug 并未在开发过程中被发现。而 <code>Docker</code> 的镜像提供了除内核外完整的运行时环境，确保了应用运行环境一致性，从而不会再出现 <em>「这段代码在我机器上没问题啊」</em> 这类问题。</p><h3 id="持续交付和部署"><a href="#持续交付和部署" class="headerlink" title="持续交付和部署"></a>持续交付和部署</h3><p>对开发和运维（<a href="https://zh.wikipedia.org/wiki/DevOps">DevOps</a>）人员来说，最希望的就是一次创建或配置，可以在任意地方正常运行。</p><p>使用 <code>Docker</code> 可以通过定制应用镜像来实现持续集成、持续交付、部署。开发人员可以通过 <a href="">Dockerfile</a> 来进行镜像构建，并结合 <a href="https://en.wikipedia.org/wiki/Continuous_integration">持续集成(Continuous Integration)</a> 系统进行集成测试，而运维人员则可以直接在生产环境中快速部署该镜像，甚至结合 <a href="https://en.wikipedia.org/wiki/Continuous_delivery">持续部署(Continuous Delivery&#x2F;Deployment)</a> 系统进行自动部署。</p><p>而且使用 <a href=""><code>Dockerfile</code></a> 使镜像构建透明化，不仅仅开发团队可以理解应用运行环境，也方便运维团队理解应用运行所需条件，帮助更好的生产环境中部署该镜像。</p><h3 id="更轻松的迁移"><a href="#更轻松的迁移" class="headerlink" title="更轻松的迁移"></a>更轻松的迁移</h3><p>由于 <code>Docker</code> 确保了执行环境的一致性，使得应用的迁移更加容易。<code>Docker</code> 可以在很多平台上运行，无论是物理机、虚拟机、公有云、私有云，甚至是笔记本，其运行结果是一致的。因此用户可以很轻易的将在一个平台上运行的应用，迁移到另一个平台上，而不用担心运行环境的变化导致应用无法正常运行的情况。</p><h3 id="更轻松的维护和扩展"><a href="#更轻松的维护和扩展" class="headerlink" title="更轻松的维护和扩展"></a>更轻松的维护和扩展</h3><p><code>Docker</code> 使用的分层存储以及镜像的技术，使得应用重复部分的复用更为容易，也使得应用的维护更新更加简单，基于基础镜像进一步扩展镜像也变得非常简单。此外，<code>Docker</code> 团队同各个开源项目团队一起维护了一大批高质量的 <a href="https://hub.docker.com/search/?type=image&image_filter=official">官方镜像</a>，既可以直接在生产环境使用，又可以作为基础进一步定制，大大的降低了应用服务的镜像制作成本。</p><h3 id="更高效的利用系统资源"><a href="#更高效的利用系统资源" class="headerlink" title="更高效的利用系统资源"></a>更高效的利用系统资源</h3><h3 id="更快速的启动时间"><a href="#更快速的启动时间" class="headerlink" title="更快速的启动时间"></a>更快速的启动时间</h3><h1 id="Docker基本概念"><a href="#Docker基本概念" class="headerlink" title="Docker基本概念"></a>Docker基本概念</h1><h2 id="Docker-镜像"><a href="#Docker-镜像" class="headerlink" title="Docker 镜像"></a>Docker 镜像</h2><p><strong>Docker 镜像</strong> 是一个特殊的文件系统，除了提供容器运行时所需的程序、库、资源、配置等文件外，还包含了一些为运行时准备的一些配置参数（如匿名卷、环境变量、用户等）。镜像 <strong>不包含</strong> 任何动态数据，其内容在构建之后也不会被改变。</p><h3 id="分层存储"><a href="#分层存储" class="headerlink" title="分层存储"></a>分层存储</h3><p>因为镜像包含操作系统完整的 <code>root</code> 文件系统，其体积往往是庞大的，因此在 Docker 设计时，就充分利用 <a href="https://en.wikipedia.org/wiki/Union_mount">Union FS</a> 的技术，将其设计为分层存储的架构。所以严格来说，镜像并非是像一个 <code>ISO</code> 那样的打包文件，镜像只是一个虚拟的概念，其实际体现并非由一个文件组成，而是由一组文件系统组成，或者说，由多层文件系统联合组成。</p><p>镜像构建时，会一层层构建，前一层是后一层的基础。每一层构建完就不会再发生改变，后一层上的任何改变只发生在自己这一层。比如，删除前一层文件的操作，实际不是真的删除前一层的文件，而是仅在当前层标记为该文件已删除。在最终容器运行的时候，虽然不会看到这个文件，但是实际上该文件会一直跟随镜像。因此，在构建镜像的时候，需要额外小心，每一层尽量只包含该层需要添加的东西，任何额外的东西应该在该层构建结束前清理掉。</p><p>分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层，然后进一步添加新的层，以定制自己所需的内容，构建新的镜像。</p><p>关于镜像构建，将会在后续相关章节中做进一步的讲解。</p><h2 id="Docker容器"><a href="#Docker容器" class="headerlink" title="Docker容器"></a>Docker容器</h2><p>镜像（<code>Image</code>）和容器（<code>Container</code>）的关系，就像是面向对象程序设计中的 <code>类</code> 和 <code>实例</code> 一样，镜像是静态的定义，容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。</p><p>容器的实质是进程，但与直接在宿主执行的进程不同，容器进程运行于属于自己的独立的 <a href="https://en.wikipedia.org/wiki/Linux_namespaces">命名空间</a>。因此容器可以拥有自己的 <code>root</code> 文件系统、自己的网络配置、自己的进程空间，甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里，使用起来，就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。也因为这种隔离的特性，很多人初学 Docker 时常常会混淆容器和虚拟机。</p><p>前面讲过镜像使用的是分层存储，容器也是如此。每一个容器运行时，是以镜像为基础层，在其上创建一个当前容器的存储层，我们可以称这个为容器运行时读写而准备的存储层为 <strong>容器存储层</strong>。</p><p>容器存储层的生存周期和容器一样，容器消亡时，容器存储层也随之消亡。因此，任何保存于容器存储层的信息都会随容器删除而丢失。</p><p>按照 Docker 最佳实践的要求，容器不应该向其存储层内写入任何数据，容器存储层要保持无状态化。所有的文件写入操作，都应该使用 <a href="">数据卷（Volume）</a>、或者 <a href="">绑定宿主目录</a>，在这些位置的读写会跳过容器存储层，直接对宿主（或网络存储）发生读写，其性能和稳定性更高。</p><p>数据卷的生存周期独立于容器，容器消亡，数据卷不会消亡。因此，使用数据卷后，容器删除或者重新运行之后，数据却不会丢失。</p>]]></content>
    
    
    <summary type="html">&lt;img src=&quot;/2023/09/13/2023-09-13-Docker-1-%E8%AE%A4%E8%AF%86Docker/docker.jpg&quot; class=&quot;&quot; title=&quot;Docker&quot;&gt;

&lt;h1 id=&quot;Docker介绍&quot;&gt;&lt;a href=&quot;#Docker介绍&quot; class=&quot;headerlink&quot; title=&quot;Docker介绍&quot;&gt;&lt;/a&gt;Docker介绍&lt;/h1&gt;&lt;p&gt;Docker 是一个开源的应用容器引擎，基于 [Go 语言] 并遵从 Apache2.0 协议开源。&lt;/p&gt;
&lt;p&gt;Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中，然后发布到任何流行的 Linux 机器上，也可以实现虚拟化。&lt;/p&gt;
&lt;p&gt;容器是完全使用沙箱机制，相互之间不会有任何接口（类似 iPhone 的 app）,更重要的是容器性能开销极低。&lt;/p&gt;</summary>
    
    
    
    <category term="技术积累" scheme="http://example.com/categories/%E6%8A%80%E6%9C%AF%E7%A7%AF%E7%B4%AF/"/>
    
    
    <category term="Code" scheme="http://example.com/tags/Code/"/>
    
    <category term="Docker" scheme="http://example.com/tags/Docker/"/>
    
  </entry>
  
  <entry>
    <title>[2023-09-09] Linux #1:Vim编辑器</title>
    <link href="http://example.com/2023/09/09/2023-09-09-Linux-1-Vim%E7%BC%96%E8%BE%91%E5%99%A8/"/>
    <id>http://example.com/2023/09/09/2023-09-09-Linux-1-Vim%E7%BC%96%E8%BE%91%E5%99%A8/</id>
    <published>2023-09-09T14:12:46.000Z</published>
    <updated>2024-04-10T16:10:00.846Z</updated>
    
    <content type="html"><![CDATA[<img src="/2023/09/09/2023-09-09-Linux-1-Vim%E7%BC%96%E8%BE%91%E5%99%A8/vim.png" class="" title="Vim"><h1 id="Vim"><a href="#Vim" class="headerlink" title="Vim"></a><code>Vim</code></h1><h2 id="Vim介绍"><a href="#Vim介绍" class="headerlink" title="Vim介绍"></a><code>Vim</code>介绍</h2><p>VIM 是 Linux 系统上一款文本编辑器，它是操作 Linux 的一款利器。</p><h3 id="Vim-常用的四个模式："><a href="#Vim-常用的四个模式：" class="headerlink" title="Vim 常用的四个模式："></a><code>Vim</code> 常用的四个模式：</h3><ul><li><p>正常模式 (Normal-mode)</p><p>一般用于浏览文件，也包括一些复制、粘贴、删除等操作。这时击键时，一些组合键就是<code>vim</code>的功能键，而不会在文本中键入对应的字符。</p></li><li><p>插入模式 (Insert-mode)</p><p>简单的来说，就是编辑文本的模式。</p></li><li><p>命令模式 (Command-mode)</p><p>在正常模式中，按下<code>:</code>（冒号）键或者<code>/</code> （斜杠），会进入命令模式。在命令模式中可以执行一些输入并执行一些 VIM 或插件提供的指令，就像在shell里一样。这些指令包括设置环境、文件操作、调用某个功能等等。</p></li><li><p>可视模式 (Visual-mode)</p><p>在正常模式按下<code>v, V, &lt;Ctrl&gt;+v</code>，可以进入可视模式。可视模式中的操作有点像拿鼠标进行操作，选择文本的时候有一种鼠标选择的即视感，有时候会很方便。</p><span id="more"></span></li></ul><h2 id="Vim使用"><a href="#Vim使用" class="headerlink" title="Vim使用"></a><code>Vim</code>使用</h2><h3 id="文件打开、切换、保存"><a href="#文件打开、切换、保存" class="headerlink" title="文件打开、切换、保存"></a>文件打开、切换、保存</h3><table><thead><tr><th>文件外命令</th><th>文件内命令</th><th>说明</th><th>备注</th></tr></thead><tbody><tr><td>vim file1</td><td></td><td>打开file1文件</td><td>打开单个文件</td></tr><tr><td>vim file1 file2 …</td><td></td><td>打开file1,file2文件</td><td>打开多个文件</td></tr><tr><td></td><td>:ls</td><td>列出Vim打开的所有文件</td><td>在上述打开多个文件的基础上使用</td></tr><tr><td></td><td>:b2</td><td>在显示屏上显示第2个文件</td><td>切换到第2个文件显示</td></tr><tr><td>vim -On file1 file2 …</td><td></td><td>左右分屏显示多个文件</td><td></td></tr><tr><td>vim -on file1 file2 …</td><td></td><td>上下分屏显示多个文件</td><td></td></tr><tr><td></td><td>Ctrl+w s</td><td>上下分割当前打开的所有文件</td><td>在打开多个文件的基础上使用</td></tr><tr><td></td><td>:sp file</td><td>上下分割当前文件和新打开的文件</td><td></td></tr><tr><td></td><td>Ctrl+w v</td><td>左右分割当前打开的所有文件</td><td>在打开多个文件的基础上使用</td></tr><tr><td></td><td>:vsp file</td><td>左右分割当前文件和新打开的文件</td><td></td></tr><tr><td></td><td>Ctrl+w h</td><td>将当前光标移动到左边的分屏</td><td></td></tr><tr><td></td><td>Ctrl+w l</td><td>将当前光标移动到右边的分屏</td><td></td></tr><tr><td></td><td>Ctrl+w H</td><td>将当前光标所在分屏移动到左边</td><td></td></tr><tr><td></td><td>Ctrl+w L</td><td>将当前光标所在分屏移动到右边</td><td></td></tr><tr><td></td><td>Ctrl+w j</td><td>将当前光标移动到下边的分屏</td><td></td></tr><tr><td></td><td>Ctrl+w k</td><td>将当前光标移动到上边的分屏</td><td></td></tr><tr><td></td><td>Ctrl+w J</td><td>将当前光标所在分屏移动到上边</td><td></td></tr><tr><td></td><td>Ctrl+w K</td><td>将当前光标所在分屏移动到下边</td><td></td></tr><tr><td></td><td>:w</td><td>保存当前对文件的修改，但是不退出文件</td><td></td></tr><tr><td></td><td>:w!</td><td>强制保存但是不退出文件</td><td></td></tr><tr><td></td><td>:w file</td><td>保存当前的文件修改到 file 文件当中</td><td></td></tr><tr><td></td><td>:q!</td><td>退出文件，对文件的修改不做保存</td><td></td></tr><tr><td></td><td>:qa!</td><td>退出所有的文件并不做保存</td><td></td></tr><tr><td></td><td>:wq</td><td>退出文件并保存对文件的修改</td><td></td></tr><tr><td></td><td>:x</td><td>退出文件并保存对文件的修改</td><td></td></tr><tr><td></td><td>:e file</td><td>打开另一个文件</td><td></td></tr><tr><td></td><td>:e!</td><td>放弃对文件的所有修改，恢复文件到上次保存的位置</td><td></td></tr><tr><td></td><td>:saveas file</td><td>另存为 file</td><td></td></tr><tr><td></td><td>:bn<code>和</code>:bp</td><td><code>:bn</code> 和 <code>:bp</code> 进行上一个文件或者下一个文件的切换</td><td></td></tr></tbody></table><h3 id="文件编辑"><a href="#文件编辑" class="headerlink" title="文件编辑"></a>文件编辑</h3><table><thead><tr><th>命令</th><th>说明</th></tr></thead><tbody><tr><td><code>i</code></td><td>在光标所在的字符之前插入需要录入的文本</td></tr><tr><td><code>I</code></td><td>在光标所在行的行首插入需要录入的文本</td></tr><tr><td><code>a</code></td><td>在光标所在的字符之后插入需要录入的文本</td></tr><tr><td><code>A</code></td><td>在光标所在行的行尾插入需要录入的文本</td></tr><tr><td><code>o</code></td><td>光标所在行的下一行行首插入需要录入的文本</td></tr><tr><td><code>O</code></td><td>光标所在行的上一行行首插入需要录入的文本</td></tr><tr><td><code>s</code></td><td>删除光标所在处的字符然后插入需要录入的文本</td></tr><tr><td><code>S</code></td><td>删除光标所在行，在当前行的行首开始插入需要录入的文本</td></tr></tbody></table><h3 id="光标移动"><a href="#光标移动" class="headerlink" title="光标移动"></a>光标移动</h3><table><thead><tr><th>命令</th><th>说明</th></tr></thead><tbody><tr><td>h</td><td>向左移动</td></tr><tr><td>j</td><td>向下移动</td></tr><tr><td>k</td><td>向上移动</td></tr><tr><td>l</td><td>向右移动</td></tr><tr><td><code>0 </code></td><td>移动到行头</td></tr><tr><td><code>^</code></td><td>移动到本行的第一个不是 blank 字符</td></tr><tr><td><code>$</code></td><td>移动到行尾</td></tr><tr><td><code>g_</code></td><td>移动到本行最后一个不是 blank 字符的位置</td></tr><tr><td><code>w</code></td><td>光标移动到下一个单词的开头</td></tr><tr><td><code>e</code></td><td>光标移动到下一个单词的结尾</td></tr><tr><td><code>b</code></td><td>光标移动到上一个单词的开头</td></tr><tr><td><code>fa</code></td><td>移动到本行下一个为 a 的字符处</td></tr><tr><td><code>nG </code></td><td>光标定位到第 n 行的行首</td></tr><tr><td><code>gg </code></td><td>光标定位到第一行的行首</td></tr><tr><td><code>G </code></td><td>光标定位到最后一行的行首</td></tr><tr><td><code>H </code></td><td>光标定位到当前屏幕的第一行行首</td></tr><tr><td><code>M</code></td><td>光标移动到当前屏幕的中间</td></tr><tr><td><code>L</code></td><td>光标移动到当前屏幕的尾部</td></tr><tr><td><code>zt</code></td><td>把当前行移动到当前屏幕的最上方，也就是第一行</td></tr><tr><td><code>zz</code></td><td>把当前行移动到当前屏幕的中间</td></tr><tr><td><code>zb</code></td><td>把当前行移动到当前屏幕的尾部</td></tr><tr><td><code>%</code></td><td>匹配括号移动，包括 ( , { , [ 需要把光标先移动到括号上</td></tr><tr><td><code>*</code></td><td>匹配光标当前所在的单词，移动光标到下一个匹配的单词</td></tr><tr><td><code>#</code></td><td>匹配光标当前所在的单词，移动光标到上一个匹配的单词</td></tr></tbody></table><h3 id="翻页操作"><a href="#翻页操作" class="headerlink" title="翻页操作"></a>翻页操作</h3><table><thead><tr><th>命令</th><th>说明</th></tr></thead><tbody><tr><td><code>ctrl+f</code></td><td>查看下一页内容</td></tr><tr><td><code>ctrl+b</code></td><td>查看上一页内容</td></tr></tbody></table><h3 id="撤销和恢复"><a href="#撤销和恢复" class="headerlink" title="撤销和恢复"></a>撤销和恢复</h3><table><thead><tr><th>命令</th><th>说明</th></tr></thead><tbody><tr><td><code>u</code></td><td>撤销刚才的操作</td></tr><tr><td><code>ctrl + r</code></td><td>恢复撤销操作</td></tr></tbody></table>]]></content>
    
    
    <summary type="html">&lt;img src=&quot;/2023/09/09/2023-09-09-Linux-1-Vim%E7%BC%96%E8%BE%91%E5%99%A8/vim.png&quot; class=&quot;&quot; title=&quot;Vim&quot;&gt;
&lt;h1 id=&quot;Vim&quot;&gt;&lt;a href=&quot;#Vim&quot; class=&quot;headerlink&quot; title=&quot;Vim&quot;&gt;&lt;/a&gt;&lt;code&gt;Vim&lt;/code&gt;&lt;/h1&gt;&lt;h2 id=&quot;Vim介绍&quot;&gt;&lt;a href=&quot;#Vim介绍&quot; class=&quot;headerlink&quot; title=&quot;Vim介绍&quot;&gt;&lt;/a&gt;&lt;code&gt;Vim&lt;/code&gt;介绍&lt;/h2&gt;&lt;p&gt;VIM 是 Linux 系统上一款文本编辑器，它是操作 Linux 的一款利器。&lt;/p&gt;
&lt;h3 id=&quot;Vim-常用的四个模式：&quot;&gt;&lt;a href=&quot;#Vim-常用的四个模式：&quot; class=&quot;headerlink&quot; title=&quot;Vim 常用的四个模式：&quot;&gt;&lt;/a&gt;&lt;code&gt;Vim&lt;/code&gt; 常用的四个模式：&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;正常模式 (Normal-mode)&lt;/p&gt;
&lt;p&gt;一般用于浏览文件，也包括一些复制、粘贴、删除等操作。这时击键时，一些组合键就是&lt;code&gt;vim&lt;/code&gt;的功能键，而不会在文本中键入对应的字符。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;插入模式 (Insert-mode)&lt;/p&gt;
&lt;p&gt;简单的来说，就是编辑文本的模式。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;命令模式 (Command-mode)&lt;/p&gt;
&lt;p&gt;在正常模式中，按下&lt;code&gt;:&lt;/code&gt;（冒号）键或者&lt;code&gt;/&lt;/code&gt; （斜杠），会进入命令模式。在命令模式中可以执行一些输入并执行一些 VIM 或插件提供的指令，就像在shell里一样。这些指令包括设置环境、文件操作、调用某个功能等等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;可视模式 (Visual-mode)&lt;/p&gt;
&lt;p&gt;在正常模式按下&lt;code&gt;v, V, &amp;lt;Ctrl&amp;gt;+v&lt;/code&gt;，可以进入可视模式。可视模式中的操作有点像拿鼠标进行操作，选择文本的时候有一种鼠标选择的即视感，有时候会很方便。&lt;/p&gt;</summary>
    
    
    
    <category term="技术积累" scheme="http://example.com/categories/%E6%8A%80%E6%9C%AF%E7%A7%AF%E7%B4%AF/"/>
    
    
    <category term="Linux" scheme="http://example.com/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>[2023-09-09] Python #1:Pdb调试器</title>
    <link href="http://example.com/2023/09/09/2023-09-09-Python-1-Pdb%E8%B0%83%E8%AF%95%E5%99%A8/"/>
    <id>http://example.com/2023/09/09/2023-09-09-Python-1-Pdb%E8%B0%83%E8%AF%95%E5%99%A8/</id>
    <published>2023-09-09T09:56:09.000Z</published>
    <updated>2023-09-13T12:24:16.976Z</updated>
    
    <content type="html"><![CDATA[<img src="/2023/09/09/2023-09-09-Python-1-Pdb%E8%B0%83%E8%AF%95%E5%99%A8/pdb.png" class="" title="Pdb"><h1 id="Pdb介绍"><a href="#Pdb介绍" class="headerlink" title="Pdb介绍"></a>Pdb介绍</h1><p><strong>网址</strong>:<a href="https://docs.python.org/3/library/pdb.html">pdb — The Python Debugger</a></p><p><strong>介绍</strong>:模块pdb为Python程序定义了一个交互式源代码调试器。它支持在源行级别设置（条件）断点和单步执行，检查堆栈帧，列出源代码，以及在任何堆栈帧的上下文中评估任意Python代码。它还支持死后调试，并且可以在程序控制下调用。</p><span id="more"></span><h1 id="Pdb用法"><a href="#Pdb用法" class="headerlink" title="Pdb用法"></a>Pdb用法</h1><ul><li><strong>非侵入式方法</strong>（不用额外修改源代码，在命令行下直接运行就能调试）</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python3 -m pdb filename.py</span><br></pre></td></tr></table></figure><ul><li><strong>侵入式方法</strong>（需要在被调试的代码中添加一行代码然后再正常运行代码）</li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pdb;pdb.set_trace()</span><br><span class="line"><span class="comment"># or</span></span><br><span class="line"><span class="keyword">import</span> pdb</span><br><span class="line">pdb.set_trace()</span><br></pre></td></tr></table></figure><h1 id="Pdb常用命令"><a href="#Pdb常用命令" class="headerlink" title="Pdb常用命令"></a>Pdb常用命令</h1><table><thead><tr><th align="left">完整命令</th><th align="left">命令</th><th align="left">解释</th><th>说明</th></tr></thead><tbody><tr><td align="left">list</td><td align="left">l</td><td align="left">查看当前行的代码段</td><td>查看当前位置前后11行源代码（多次会翻页），当前位置在代码中会用–&gt;这个符号标出来</td></tr><tr><td align="left"></td><td align="left">ll</td><td align="left">查看当前函数或框架的所有源代码</td><td></td></tr><tr><td align="left">break</td><td align="left">b</td><td align="left">设置断点</td><td></td></tr><tr><td align="left"></td><td align="left">b lineno</td><td align="left">在lineno行设置断点</td><td></td></tr><tr><td align="left"></td><td align="left">b filename:lineno</td><td align="left">filename文件名，在filename的lineno行设置断点</td><td></td></tr><tr><td align="left"></td><td align="left">b functionname</td><td align="left">在函数名执行的第一行设置断点</td><td></td></tr><tr><td align="left">tbreak</td><td align="left">\</td><td align="left">临时断点，执行一次后时自动删除（这就是它被称为临时断点的原因）</td><td>参数同break，b</td></tr><tr><td align="left">clear</td><td align="left">cl</td><td align="left">清除所有断点</td><td>1.不带参数用于清除所有断点，会提示确认（包括临时断点）<br/>2.带参数则清除指定文件行或当前文件指定序号的断点</td></tr><tr><td align="left"></td><td align="left">cl filename:lineno</td><td align="left">清除指定文件中的指定行号的断点</td><td>filename: 文件名<br/>lineno: 断点行号</td></tr><tr><td align="left"></td><td align="left">cl bpnumber [bpnumber …]</td><td align="left">清除当前文件中的一个或多个断点</td><td>bpnumber: break point number 断点行号。bpnumber 断点序号（多个以空格分隔）</td></tr><tr><td align="left">print</td><td align="left">p x</td><td align="left">打印变量x的值</td><td></td></tr><tr><td align="left">step</td><td align="left">s</td><td align="left">进入函数（能够进入函数体）（进入 for 循环用 next 而不是用 step）</td><td></td></tr><tr><td align="left">next</td><td align="left">n</td><td align="left">执行下一行（不会进入函数体）</td><td></td></tr><tr><td align="left">return</td><td align="left">r</td><td align="left">执行下一行（在函数中时会直接执行到函数返回处）</td><td></td></tr><tr><td align="left">continue</td><td align="left">c</td><td align="left">持续执行下去，直到遇到一个断点</td><td></td></tr><tr><td align="left">until</td><td align="left">unt lineno</td><td align="left">持续执行直到运行到指定行（或遇到断点）</td><td></td></tr><tr><td align="left">jump</td><td align="left">j lineno</td><td align="left">直接跳转到指定行（注意，被跳过的代码不执行）</td><td></td></tr><tr><td align="left">up</td><td align="left">u</td><td align="left">返回到上个调用点（不是上一行）</td><td></td></tr><tr><td align="left">args</td><td align="left">a</td><td align="left">在函数中时打印函数的参数和参数的值</td><td></td></tr><tr><td align="left">whatis</td><td align="left">whatis expression</td><td align="left">打印表达式的类型，常用来打印变量值</td><td></td></tr><tr><td align="left">where</td><td align="left">w</td><td align="left">打印堆栈信息，最新的帧在最底部。箭头表示当前帧。</td><td></td></tr><tr><td align="left">!</td><td align="left">\</td><td align="left">在pdb中执行语句</td><td></td></tr><tr><td align="left">exit</td><td align="left">q</td><td align="left">中止调试，退出程序</td><td></td></tr><tr><td align="left">help</td><td align="left">\</td><td align="left">帮助</td><td></td></tr></tbody></table>]]></content>
    
    
    <summary type="html">&lt;img src=&quot;/2023/09/09/2023-09-09-Python-1-Pdb%E8%B0%83%E8%AF%95%E5%99%A8/pdb.png&quot; class=&quot;&quot; title=&quot;Pdb&quot;&gt;

&lt;h1 id=&quot;Pdb介绍&quot;&gt;&lt;a href=&quot;#Pdb介绍&quot; class=&quot;headerlink&quot; title=&quot;Pdb介绍&quot;&gt;&lt;/a&gt;Pdb介绍&lt;/h1&gt;&lt;p&gt;&lt;strong&gt;网址&lt;/strong&gt;:&lt;a href=&quot;https://docs.python.org/3/library/pdb.html&quot;&gt;pdb — The Python Debugger&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;介绍&lt;/strong&gt;:模块pdb为Python程序定义了一个交互式源代码调试器。它支持在源行级别设置（条件）断点和单步执行，检查堆栈帧，列出源代码，以及在任何堆栈帧的上下文中评估任意Python代码。它还支持死后调试，并且可以在程序控制下调用。&lt;/p&gt;</summary>
    
    
    
    <category term="技术积累" scheme="http://example.com/categories/%E6%8A%80%E6%9C%AF%E7%A7%AF%E7%B4%AF/"/>
    
    
    <category term="Code" scheme="http://example.com/tags/Code/"/>
    
    <category term="Python" scheme="http://example.com/tags/Python/"/>
    
  </entry>
  
</feed>
