LLM | Tokenization 从原理与代码了解GPT的分词器

news2024/11/24 10:44:49

声明:以上内容全是学习Andrej Karpathy油管教学视频的总结。

---------------------------------------------------------------------------------------------------------------------------------

大家好。在今天我们学习llm中的Tokenization,即分词器部分。许多人可能之前对于这个过程没有太多的重视。但是实际上,LLM中许多奇怪的问题都可以追溯到Tokenization的过程中

  • 无法拼写单词
  • 无法倒写单词
  • 处理"<|endoftext|>"之类特殊字符时易出现混乱
  • 为什么LLM相比于JSON而言,面对YAML文件更加友好。
  • ...

这一切的根源都是在于模型的Tokenization部分。下面我为大家进行一一讲解。在讲解之前,我需要首先给大家简单的讲解一下,什么是模型的Tokenization?

原理

这里我引入State of GPT文章中的一些插图。文章里详细的解释了ChatGPT等LLM的技术路线,建议大家去看一下原视频。

如上图所示,在LLM的基础模型训练过程中,模型的输入是 (B, T) 的Token序列。而模型所得到的输出也即是预测下一个Token出现的概率分布。而这些数字,则是通过Tokenizer得到的。其生成流程如下。

这里我们看见,我们把文本按照一种奇怪的方式分割成了一个又一个的 sub word。目前大家只需要理解 Tokens部分一个颜色的块则对应模型训练的一个Token,而对于每一个Token,模型内部会有一个字典 vocab 对应一个 int 整数。这就是Tokenazition的过程。简而言之,Tokenization可以理解为把一串字符串转换成整数的列表

现在我们理解了Tokenization的基本原理,我们对于上面的四个问题先给予一下简单的答复。

1. 无法拼写单词?

这里我们以一个单词 .DefaultCellStyle 为例子。在GPT4的分词器中,将这一长串文本分为了一个Token 98518。因此单词里的所有信息被压缩在了一个Token中。

因此若你询问GPT4有多少个l,会得到怎样的结果?如图...

2. 无法倒写单词

同理,如果我问如果反过来拼写 .DefaultCellStyle, 他会回答我一个奇怪的答案。

3. 处理"<|endoftext|>"之类特殊字符时易出现混乱

这是因为这些特殊字符有时在模型中具有其意义。因此某些时候存在问题。在Karpathy的视频里是存在问题的,不过目前我现在GPT是4o,貌似效果还好。

4.为什么LLM相比于JSON而言,面对YAML文件更加友好

这是因为相同内容的JSON和YAML文件,YAML文件的Token数更少,这是相当大的改进。Token少可以减少上下文长度。

代码

训练流程

在这里我们需要了解一个核心数据压缩算法BPE (字节对编码, Byte Pair Encoder) 。 简单的文字叙述可能不好理解,这里我直接以代码案例帮助大家理解。

text = "aaabdaaabac"
ids = list(text.encode(encoding="utf-8", errors="replace"))
print(ids)
# [97, 97, 97, 98, 100, 97, 97, 97, 98, 97, 99]

这里我们list的作用是把utf-8的字节流转换成int,并且处理成int的形式。那么接下来我们就要统计这一个ids列表里面出现的字节对的次数。

def get_stats(ids: list, count=None) -> dict:
    """
    找到字节对的统计次数
    Example: [1, 2, 1, 2, 3] -> {(1, 2): 2, (2, 1): 1, (2, 3): 1}

    :params ids: list, 字节流
    :return count: dict, 字节对的统计次数
    """

    count = {} if count is None else count
    for (p0, p1) in zip(ids, ids[1:]):
        count[(p0, p1)] = count.get((p0, p1), 0) + 1
    
    return count

counts = get_stats(ids)
print(counts)   
# {(97, 97): 4, (97, 98): 2, (98, 100): 1, (100, 97): 1, (98, 97): 1, (97, 99): 1}

这里我们就知道97,97是出现了4次的。那么接下来,我们就要把(97, 97)这个字节对用一个新的id来替代。

def merge(ids: list, pair: tuple, idx: int) -> list:
    """
    将字节对用最新idx替换
    Example: ids=[1, 2, 3, 1, 2], pair=(1, 2), idx = 4 -> [4, 3, 4]

    :params ids: list, 原字节串
    :params pair: tuple, 原字节对
    :params idx: int, 新索引
    :return new_ids: list, 新字节串
    """

    new_ids = []

    i = 0
    while i < len(ids):
        if i < len(ids) - 1 and ids[i] == pair[0] and ids[i + 1] == pair[1]:
            new_ids.append(idx)
            i = i + 2
        else:
            new_ids.append(ids[i])
            i = i + 1
    
    return new_ids

new_id = merge(ids, (97, 97), 256)
print(new_id)
# [256, 97, 98, 100, 256, 97, 98, 97, 99]

以上的流程就完成了一次字节对的替换。同时我们的词汇也增加了一。这里大家可能疑惑词汇是什么。这里需要解释一下,由于utf-8编码是字节的变长排序。所以在训练的过程中,我们一般会把0-255的字节默认存储在字典中。同时替换一次,我们的词汇就会增加一。替换次数越多,词汇量越多,压缩的就越多,训练过程中能承载的原始文本原理上可以更多。

但是,词汇量并不是越多越好,了解Transformer结构的同学应该能理解,词汇量增多的话,会出现几个问题:

  • 出现许多低频词汇,学习变得困难
  • 参数量增多,增加计算成本
  • 过拟合
  • ...

因此,这个词汇量也是在模型训练过程中需要权衡的一个点。要既能捕捉复杂语言的细微差别,也要权衡上面的因素

上面的流程只是一次字节对替换的流程,接下来我把完整的训练流程以及中间变量尽可能详细的给大家通过代码展示出来。

vocab_size = 256 + 3   # 词汇量大小
num_merges = vocab_size - 256   # merge次数

idx = 256
vocab = {i: bytes([i]) for i in range(256)} # 初始的词汇
merges = {} # (int, int) -> int

text = "aaabdaaabac"
ids = list(text.encode(encoding="utf-8", errors="replace"))

# 进行字节对的替换
for i in range(num_merges):
    new_id = idx + i # 字节对的新编号
    stats = get_stats(ids)
    pair = max(stats, key=stats.get)    # 出现次数最多的字节对
    ids = merge(ids, pair, new_id)  # 替换字节对

    vocab[new_id] = vocab[pair[0]] + vocab[pair[1]]
    merges[pair] = new_id
    print(f"{pair} -> {new_id} {vocab[new_id].decode("utf-8")}")

# (97, 97) -> 256 aa
# (256, 97) -> 257 aaa
# (257, 98) -> 258 aaab

接下来,我们还需要编写两个重要的函数 encoder 与 decoder。作用当然大家也清楚:完成文本与ids之间的转换。

def decode(ids):
    tokens = b"".join(vocab[idx] for idx in ids)
    text = tokens.decode("utf-8", errors="replace")
    return text

# 这里要注意BPE的合并顺序
def encode(text):
    tokens = list(text.encode("utf-8"))
    while len(tokens) >= 2:
        stats = get_stats(tokens)
        pair = min(stats, key=lambda p: merges.get(p, float("inf")))
        if pair not in merges:
            break
        tokens = merge(tokens, pair, merges[pair])

    return tokens

大家可以自己尝试一下。

以上即为一次完整的训练过程。当然这相比于GPT的部分还是缺少了一些东西。不过不要紧,在下面我会以下面的内容为基础,为大家构建简单的Tokenizer。

Base

这里我们在文件夹下的base.py中创建一个用于继承的基础类,完成一些基本函数,制定标准。

import unicodedata


def get_stats(ids: list, count=None) -> dict:
    """
    找到字节对的统计次数
    Example: [1, 2, 1, 2, 3] -> {(1, 2): 2, (2, 1): 1, (2, 3): 1}

    :params ids: list, 字节流
    :return count: dict, 字节对的统计次数
    """

    count = {} if count is None else count
    for (p0, p1) in zip(ids, ids[1:]):
        count[(p0, p1)] = count.get((p0, p1), 0) + 1
    
    return count


def merge(ids: list, pair: tuple, idx: int) -> list:
    """
    将字节对用最新idx替换
    Example: ids=[1, 2, 3, 1, 2], pair=(1, 2), idx = 4 -> [4, 3, 4]

    :params ids: list, 原字节串
    :params pair: tuple, 原字节对
    :params idx: int, 新索引
    :return new_ids: list, 新字节串
    """

    new_ids = []

    i = 0
    while i < len(ids):
        if i < len(ids) - 1 and ids[i] == pair[0] and ids[i + 1] == pair[1]:
            new_ids.append(idx)
            i = i + 2
        else:
            new_ids.append(ids[i])
            i = i + 1
    
    return new_ids


def replace_control_charactors(s: str) -> str:
    """
    去除字符串中的控制字符, 如"\n" 用unicode码表示
    Example: "hello \n world" -> "hello \u000a world"

    :params s: str, 原字符串
    :return : str, 新字符串
    """

    chars = []
    for ch in s:
        if unicodedata.category(ch)[0] == "C":
            chars.append(f"\\u{ord(ch):04x}")
        else:
            chars.append(ch)
    
    return "".join(chars)


def render_token(t: bytes) -> str:
    """
    将字节流转换成str 并去除控制字符
    Example: 0x68 0x65 0x6c 0x6c 0x6f 0x20 0x0a 0x20 0x77 0x6f 0x72 0x6c 0x64 -> hello \u000a world

    :params t: bytes 字节流
    :return s: str 字符串
    """

    s = t.decode(encoding="utf-8", errors="replace")
    s = replace_control_charactors(s)
    
    return s


class Tokenizer():
    """Base class for Tokenizers"""


    def __init__(self):
        """
        Attributes:
            merges (dict): 存储合并的对和新ID的映射。
            vocab (dict): 存储字典,包含字符及其对应的字节表示。
            special_tokens(dict): 特殊字符
            pattern(str): 模式
        """
        self.merges = {}  # (int, int) -> int
        self.pattern = "" # str
        self.special_tokens = {} # str -> int, e.g. {{'<|endoftext|>': 100257}}
        self.vocab = self._build_vocab() # int -> bytes
    
    def train(self, text, vocab_size, verbose=False):
        raise NotImplementedError
    
    def encode(self, text):
        raise NotImplementedError
        
    def decode(self, ids):
        raise NotImplementedError

    def _build_vocab(self):
        vocab = {idx: bytes(idx) for idx in range(256)}
        for (p0, p1), idx in self.merges.items():
            vocab[idx] = vocab[p0] + vocab[p1]
        for special, idx in self.special_tokens.items():
            vocab[idx] = special.encode("utf-8", errors="replace")
        return vocab
    
    def save(self, file_prefix):
        # 保存模型文件,用于导入 
        model_file = file_prefix + ".model"
        with open(model_file, 'w') as f:
            f.write("minbpe v1\n")
            f.write(f"{self.pattern}\n")
            f.write(f"{len(self.special_tokens.items())}\n")
            
            for special, idx in self.special_tokens.items():
                f.write(f"{special} {idx}\n")
            for idx1, idx2 in self.merges:
                f.write(f"{idx1} {idx2}\n")
        
        # 保存vocab 用于人工检查
        vocab_file = file_prefix + ".vocab"
        inverted_merges = {idx: pair for pair, idx in self.merges.items()}
        with open(vocab_file, 'w', encoding="utf-8") as f:
            for idx, token in self.vocab.items():
                s = render_token(token)

                if idx in inverted_merges:
                    idx0, idx1 = inverted_merges[idx]
                    s0 = render_token(self.vocab[idx0])
                    s1 = render_token(self.vocab[idx1])
                    f.write(f"[{s0}][{s1}] -> [{s}] {idx}\n")
                else:
                    f.write(f"[{s}] {idx}\n")

    def load(self, model_file):
        assert model_file.endswith(".model")

        # 读取model文件
        merges = {}
        special_tokens = {}
        idx = 256

        with open(model_file, 'r') as f:
            version = f.readline().strip()
            assert version == "minbpe v1"
            
            self.pattern = f.readline().strip()
            
            num_special = int(f.readline().strip())
            for _ in range(num_special):
                special, special_idx = f.readline().strip().split()
                special_tokens[special] = int(special_idx)
            
            for line in f:
                idx1, idx2 = map(int, line.split())
                merges[(idx1, idx2)] = idx
                idx += 1
        
        self.merges = merges
        self.special_tokens = special_tokens
        self.vocab = self._build_vocab()

Basic

接下来我们按照上面的BPE算法,不考虑特殊字符与正则化分割,创建一个最基本的Tokenizer类。

from base import Tokenizer, get_stats, merge


class BasicTokenizer(Tokenizer):
    """
    最简单的BPE进行分词

    
    """

    def __init__(self):
        super().__init__()
    
    def train(self, text, vocab_size, verbose=False):
        """
        对text进行训练,通过BPE得到merge

        params text: str, 文本训练内容
        params vocab_size: int(>=256), 得到merge个数为(vocab_size - 256)
        params verbose: bool, 是否打印
        """

        assert vocab_size >= 256
        num_merges = vocab_size - 256
        idx = 256
        
        text_bytes = text.encode("utf-8", "replace")
        ids = list(text_bytes)

        vocab = {i : bytes([i]) for i in range(256)}
        merges = {}
        for i in range(num_merges):
            new_id = idx + i
            stats = get_stats(ids)

            pair = max(stats, key=stats.get)

            merges[(pair)] = new_id
            ids = merge(ids, pair, new_id)
            vocab[new_id] = vocab[pair[0]] + vocab[pair[1]]

            if verbose:
                print(f"merge {i + 1}/{num_merges}:{pair} -> {idx}erges: {pair} -> {idx} ({vocab[idx]}) had {stats[pair]} occurrences")

        self.merges = merges
        self.vocab = vocab

    def encode(self, s):
        s_bytes = s.encode(encoding="utf-8", errors="replace")
        ids = list(s_bytes)

        while len(ids) >= 2:
            stats = get_stats(ids)
            # merge需要按照训练时的先后顺序

            pair = min(stats, key=lambda p: self.merges.get(p, float("inf")))
            if pair not in self.merges:
                break

            ids = merge(ids, pair, self.merges[pair])

        return ids
    
    def decode(self, ids):
        t = b"".join(self.vocab[i] for i in ids)
        return t.decode(encoding="utf-8", errors="replace")
    

if __name__ == "__main__":
    tokenizer = BasicTokenizer()
    text = "aaabdaaabac"
    tokenizer.train(text, 256+3)
    print(tokenizer.encode(text))
    print(tokenizer.decode([258, 100, 258, 97, 99]))
    tokenizer.save("toy")

regex

接下来又是一个重要的知识点。即在BPE算法中,我们希望一些字节对永远不要出现,因而我们需要利用regex库提前对于regex进行分割。同时需要对于特殊字符进行一定的处理。分割原理如下:

import regex as re
gpt2pat = re.compile(r"""'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+""")

print(re.findall(gpt2pat, "Hello've world123 how's are you!!!?"))
# ['Hello', "'ve", ' world', '123', ' how', "'s", ' are', ' you', '!!!?']

然后同样的,我们则是多了一个循环,对于每一个块进行统计与字节对替换的操作。以此为基础创建regex类。

import regex as re
from base import Tokenizer, get_stats, merge


# the main GPT text split patterns, see
# https://github.com/openai/tiktoken/blob/main/tiktoken_ext/openai_public.py
GPT2_SPLIT_PATTERN = r"""'(?:[sdmt]|ll|ve|re)| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+"""
GPT4_SPLIT_PATTERN = r"""'(?i:[sdmt]|ll|ve|re)|[^\r\n\p{L}\p{N}]?+\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]++[\r\n]*|\s*[\r\n]|\s+(?!\S)|\s+"""


class RegexTokenizer(Tokenizer):
    """
    添加正则化和特殊令牌的分词器

    """

    def __init__(self, pattern=None):
        super().__init__()
        self.pattern = GPT4_SPLIT_PATTERN if pattern is None else pattern
        self.compiled_pattern = re.compile(pattern=self.pattern)
        self.special_tokens = {}    # str -> int, example: {'<|endoftext|>': 100257}
        self.inverse_special_tokens = {}

    def train(self, text, vocab_size, verbose=False):
        ids_chunks = [list(ck.encode("utf-8", errors="replace") for ck in self.compiled_pattern.findall(text))]

        vocab_size = 256 + 3
        num_merges = vocab_size - 256
        vocab = {i: bytes([i]) for i in range(256)}
        merges = {}

        idx = 256

        for i in range(num_merges):
            new_id = idx + i
            stats = {}
            for chunk in ids_chunks:
                if len(chunk) >= 2:
                    stats = get_stats(chunk, stats)

            pair = max(stats, key=stats.get)
            ids_chunks = [merge(chunk, pair, new_id) for chunk in ids_chunks]

            merges[pair] = new_id
            vocab[new_id] = vocab[pair[0]] + vocab[pair[1]]
            
            if verbose:
                print(f"{pair} -> {new_id}")

        self.merges = merges
        self.vocab = vocab

    def register_special_tokens(self, special_tokens):
        self.special_tokens = special_tokens
        self.inverse_special_tokens = {v: k for k, v in special_tokens.items()}

    def decode(self, ids):
        part_bytes = []
        for idx in ids:
            if idx in self.vocab:
                part_bytes.append(self.vocab[idx])
            elif idx in self.inverse_special_tokens:
                part_bytes.append(self.inverse_special_tokens[idx].encode(encoding="utf-8", errors="replace"))
            else:
                raise ValueError(f"invalid token id: {idx}")
        
        text_bytes = b"".join(part_bytes)
        text = text_bytes.decode(encoding="utf-8", errors="replace")
        return text
    
    def _encode_chunk(self, text_bytes):
        """
        就是正常的encode,只不过这里没有对于special tokens的处理
        """
        ids = list(text_bytes)
        while len(ids) >= 2:
            stats = get_stats(ids)
            pair = min(stats, key=lambda p: self.merges.get(p, float("inf")))
            if pair not in self.merges:
                break

            idx = self.merges[pair]
            ids = merge(ids, pair, idx)
        return ids
    
    def encode_ordinary(self, text):
        """Encoding that ignores any special tokens."""
        text_chunks = re.findall(self.compiled_pattern, text)
        ids = []
        for chunk in text_chunks:
            chunk_bytes = chunk.encode("utf-8") # raw bytes
            chunk_ids = self._encode_chunk(chunk_bytes)
            ids.extend(chunk_ids)
        return ids
    
    def encode(self, text, allowed_special="none_raise"):
        # decode the user desire w.r.t. handling of special tokens
        special = None
        if allowed_special == "all":
            special = self.special_tokens
        elif allowed_special == "none":
            special = {}
        elif allowed_special == "none_raise":
            special = {}
            assert all(token not in text for token in self.special_tokens)
        elif isinstance(allowed_special, set):
            special = {k: v for k, v in self.special_tokens.items() if k in allowed_special}
        else:
            raise ValueError(f"allowed_special={allowed_special} not understood")
        if not special:
            # shortcut: if no special tokens, just use the ordinary encoding
            return self.encode_ordinary(text)
        special_pattern = "(" + "|".join(re.escape(k) for k in special) + ")"
        special_chunks = re.split(special_pattern, text)

        ids = []
        for part in special_chunks:
            if part in special:
                ids.append(special[part])
            else:
                ids.extend(self.encode_ordinary(part))
        return ids
    

    

regex基本实现了GPT4的简易功能。当然还是有一些不同,欢迎大家去Karpathy的Github仓库看一下他的代码。我太懒了,最后的gpt4tokenizer没有实现。

以上则是个人总结的所有内容。欢迎大家交流讨论~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2209127.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

快收藏!超实用标签title属性重写,让同事对你刮目相看

原生title属性的弊端 日常开发中&#xff0c;我们经常会遇到hover文本&#xff0c;显示其全部内容的需求。但是原生的title属性有两个很大的缺点 样式丑陋&#xff0c;无法更改 windows下的样式 mac下的样式 不够智能&#xff0c;属性显影只能人为控制 只要写了title属性&a…

使用Provide和Inject设计Vue3插件

使用provide和inject的Vue依赖项注入非常适合构建Vue3插件或避免prop多层传递。 尽管不经常使用它&#xff0c;但是您可以仅使用两个内置方法来实现依赖项注入&#xff1a;provide和inject。 查看Composition API文档&#xff0c;在Vue 3.0中&#xff0c;使用Provide和Inject进…

【笔记】Day2.5.1查询运费模板列表(未完

&#xff08;一&#xff09;代码编写 1.阅读需求&#xff0c;确保理解其中的每一个要素&#xff1a; 获取全部运费模板&#xff1a;这意味着我需要从数据库中查询所有运费模板数据。按创建时间倒序排序&#xff1a;这意味着查询结果需要根据模板的创建时间进行排序&#xff0…

汉语言文学做大数据七年实际工作经验分享普通人快来围观

&#xff08;一&#xff09;没有人带你 社会上&#xff0c;都很现实。就是进了公司&#xff0c;有师傅&#xff0c;师傅也没空带你&#xff0c;最多就是有空的时候帮你解决问题。 无论是做啥工作&#xff0c;都要靠自己努力。努力不会成为笑话&#xff0c;不努力就是笑话。就…

Crypto虐狗记---”你“和小鱼(五)

前言&#xff1a;剧情五 提示&#xff1a; 一种食物&#xff1f; 一种食物——培根&#xff1a;&#xff08;A B 也暗示是培根加密&#xff09; cyberpeace{attackanddefenceworldisinteresting} 密码学笔记——培根密码 - ILK - 博客园 (cnblogs.com)

Windows如何手动编辑右键上下文菜单 - 注册表通用方法

通过注册表编辑右键菜单的方法 文章目录 前言文件夹空白位置右键列表文件夹选中右键列表&#xff0c;有两个不同的路径&#xff1a;单个文件选中右键列表如何手动创建新的右键快捷按键&#xff1a; 前言 右键菜单有三类&#xff08;具体可以自己分别按下面的类型点击尝试&…

uniapp 设置 tabbar 的 midButton 按钮

效果展示&#xff1a; 中间的国际化没生效&#xff08;忽略就行&#xff09; 示例代码&#xff1a; 然后在 App.vue 中进行监听&#xff1a; <script>export default {onLaunch(e) {// #ifdef APPuni.onTabBarMidButtonTap(()>{console.log("中间按钮点击回调…

禁用微软的windos安全中心

目录 一、为什么禁用 二、WDControl_1.5.0程序禁用windows安全中心 步骤1--- 步骤2--- 三、禁用widows安全中心成功 一、为什么禁用 描述&#xff1a;下载第三方软件常常会收到病毒防护秒杀&#xff0c; 第1---直接无法下载 第2---提前下载在U盘解压会被干掉程序文件 …

SMU Autumn 2024 div2 1st

文章目录 The First Week一、前言二、算法1.逆序对<1>&#xff08;2024牛客国庆集训派对day2 I&#xff09; 2.图论<1>&#xff08;2024牛客国庆集训派对day2 F&#xff09; 3. 二分<1>&#xff08;AcWing 102. 最佳牛围栏&#xff09;<2>&#xff08;…

第17课-C++【模板进阶】

&#x1f307;前言 模板作为搭建STL的关键工具以及泛型编程思想的核心体现&#xff0c;对提高程序灵活性和推动高效迭代开发具有重要意义。除了基本的类型替换功能外&#xff0c;模板还具备如非类型模板参数、全特化、偏特化等高级操作。同时&#xff0c;模板声明与定义不能分…

聚类分析 | AP近邻传播聚类算法

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 AP近邻传播聚类算法 AP&#xff08;Affinity Propagation&#xff09;近邻传播聚类算法是一种基于数据点之间的相似度矩阵来进行聚类的算法。该算法不需要事先设定聚类簇的个数&#xff0c;而是通过在数据点之间传播…

JavaScript 第7章:字符串处理

第7章&#xff1a;字符串处理 在 JavaScript 中&#xff0c;字符串是一个非常常用的数据类型&#xff0c;用于表示文本信息。JavaScript 提供了许多内置的方法来处理字符串&#xff0c;包括操作、搜索、替换和格式化等。 一、字符串操作方法 1. charAt charAt(index) 方法返…

Java面向对象编程--高级

目录 一、static关键字 1.1 静态变量 1.2 静态内存解析 1.3 static的应用与练习 二、单例设计模式 2.1 单例模式 2.2 如何实现单例模式 三、代码块 3.1 详解 3.2 练习&#xff0c;测试 四、final关键字 五、抽象类与抽象方法 5.1 abstract 5.2 练习 六、接口 6.…

基于机器视觉的水果品质检测研究进展

摘 要&#xff1a;水果品质检测关系到水果的包装运输贮藏和销售的效果和收益。传统的外观品质检测主要是利用分级机械&#xff0c;其存在很多不足之处&#xff0c;因此提出了利用机器视觉进行无损检测的技术。利用机器视觉技术主要是检测水果的大小、形状、颜色和表面缺陷四个…

106. 从中序与后序遍历序列构造二叉树【 力扣(LeetCode) 】

文章目录 零、LeetCode 原题一、题目描述二、测试用例三、解题思路四、参考代码 零、LeetCode 原题 106. 从中序与后序遍历序列构造二叉树 一、题目描述 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵…

Static修饰不同对象

目录 Static修饰局部变量 Static修饰全局变量 Static修饰函数 Static修饰成员 Static修饰成员变量 Static修饰成员函数 Static修饰成员的特性&#xff1a; 静态成员变量和静态成员函数的使用案例&#xff1a; 案例1&#xff1a;求1 2 3...n 案例2&#xff1a;单例模…

【学术会议投稿链接】React前端框架:构建现代Web应用的强大工具

【即将截稿】第五届经济管理与大数据应用国际学术会议&#xff08;ICEMBDA 2024&#xff09;_艾思科蓝_学术一站式服务平台 更多学术会议请看&#xff1a;https://ais.cn/u/nuyAF3 目录 引言 一、React简介 二、React的核心概念 1. 组件化 2. 虚拟DOM&#xff08;Virtua…

LOID:有效提升遮挡条件下的车道检测精度

1.论文信息 论文标题&#xff1a;LOID: Lane Occlusion Inpainting and Detection for Enhanced Autonomous Driving Systems 作者&#xff1a;Aayush Agrawal, Ashmitha Jaysi Sivakumar, Ibrahim Kaif∗, Chayan Banerjee† 作者单位&#xff1a;印度马德拉斯印度理工学院&…

数学建模算法与应用 第12章 现代优化算法

目录 12.1 粒子群优化算法 Matlab代码示例&#xff1a;粒子群优化算法求解函数最小值 12.2 遗传算法 Matlab代码示例&#xff1a;遗传算法求解函数最小值 12.3 蚁群算法 Matlab代码示例&#xff1a;蚁群算法求解旅行商问题 12.4 Matlab 遗传算法工具 使用遗传算法工具箱…

java的LinkedList

java的LinkedList 什么是LinkedListLinkedList的模拟实现LinkedList的使用ArrayList和LinkedList的区别 什么是LinkedList LinkedList的官方文档 LinkedList的底层是双向链表结构&#xff0c;由于链表没有将元素存储在连续的空间中&#xff0c;元素存储在单独的结点中&#xf…