国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

2023年的深度學(xué)習(xí)入門指南(25) - 通義千問7b

這篇具有很好參考價(jià)值的文章主要介紹了2023年的深度學(xué)習(xí)入門指南(25) - 通義千問7b。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

2023年的深度學(xué)習(xí)入門指南(25) - 通義千問7b

最近發(fā)生的兩件事情都比較有意思,一個(gè)是連續(xù)開源了7b和13b模型的百川,對(duì)其53b閉源了;另一個(gè)是閉源項(xiàng)目通義千問開源了自己的7b模型。

下面我們就來(lái)研究下通義千問7b.

2023年的深度學(xué)習(xí)入門指南(25) - 通義千問7b,深度學(xué)習(xí),人工智能

使用通義千問7b

首先安裝依賴庫(kù):

pip install transformers==4.31.0 accelerate tiktoken einops transformers_stream_generator bitsandbytes

通義千問7b的開源做得還是不錯(cuò)的,不光在自家的魔搭平臺(tái)上可以用,而且也開放在了huggingface上,所以我們可以直接用huggingface的API來(lái)調(diào)用。

我們按照官方的三輪對(duì)話的例子:

from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.generation import GenerationConfig

tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen-7B-Chat", trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen-7B-Chat", device_map="auto", trust_remote_code=True, bf16=True).eval()
model.generation_config = GenerationConfig.from_pretrained("Qwen/Qwen-7B-Chat", trust_remote_code=True) # 可指定不同的生成長(zhǎng)度、top_p等相關(guān)超參

# 第一輪對(duì)話 1st dialogue turn
response, history = model.chat(tokenizer, "你好", history=None)
print(response)

# 第二輪對(duì)話 2nd dialogue turn
response, history = model.chat(tokenizer, "給我講一個(gè)年輕人奮斗創(chuàng)業(yè)最終取得成功的故事。", history=history)
print(response)

# 第三輪對(duì)話 3rd dialogue turn
response, history = model.chat(tokenizer, "給這個(gè)故事起一個(gè)標(biāo)題", history=history)
print(response)

運(yùn)行結(jié)果如下:

你好!很高興為你服務(wù)。
---
好的,這是一個(gè)關(guān)于一個(gè)年輕人奮斗創(chuàng)業(yè)最終取得成功的故事:

這是一個(gè)關(guān)于一個(gè)年輕人叫做杰克的故事。杰克是一個(gè)非常有抱負(fù)的年輕人,他一直夢(mèng)想著自己能夠成為一名成功的企業(yè)家。他從小就對(duì)商業(yè)和創(chuàng)業(yè)有著濃厚的興趣,而且非常勤奮,總是努力學(xué)習(xí)和探索新的知識(shí)和技能。

在大學(xué)里,杰克學(xué)習(xí)了商業(yè)管理和創(chuàng)業(yè)課程,并且積極參加各種商業(yè)競(jìng)賽和實(shí)習(xí)項(xiàng)目。他通過自己的努力和聰明才智,贏得了很多獎(jiǎng)項(xiàng)和機(jī)會(huì),得到了很多寶貴的經(jīng)驗(yàn)和知識(shí)。

畢業(yè)后,杰克決定開始自己的創(chuàng)業(yè)之路。他開始在市場(chǎng)上尋找機(jī)會(huì),發(fā)現(xiàn)了一個(gè)非常有潛力的行業(yè),并且決定在這個(gè)行業(yè)里創(chuàng)業(yè)。他面臨著很多挑戰(zhàn)和困難,但是他非常堅(jiān)韌和有決心,不斷努力和探索新的方法和思路,不斷地學(xué)習(xí)和進(jìn)步。

杰克和他的團(tuán)隊(duì)經(jīng)歷了許多困難和失敗,但是他們一直保持著樂觀和積極的態(tài)度,并且不斷地學(xué)習(xí)和改進(jìn)自己的方法和策略。最終,他們終于成功地推出了一款非常受歡迎的產(chǎn)品,并且在市場(chǎng)上獲得了巨大的成功。

杰克的成功不僅僅是因?yàn)樗穆斆鞑胖呛颓趭^努力,更重要的是因?yàn)樗哂袌?jiān)定的信念和不屈不撓的精神。他不斷地學(xué)習(xí)和進(jìn)步,不斷地嘗試新的方法和思路,不斷地克服困難和挑戰(zhàn),最終取得了成功。他的故事告訴我們,只要我們具有勇氣和決心,就可以在創(chuàng)業(yè)的道路上取得成功。
---
這個(gè)故事的標(biāo)題可以是:《杰克的創(chuàng)業(yè)之路》。

不知道千問7b所說的杰克,是不是姓馬?:)

gradio

千問7b的Web demo用的是Gradio來(lái)實(shí)現(xiàn)的。與Streamlit類似,Gradio也是包含了簡(jiǎn)單的Web封裝,加上前端的封裝。

我們先看一個(gè)最簡(jiǎn)單的例子:

import gradio as gr

def greet(name):
    return "Hello " + name + "!"

demo = gr.Interface(fn=greet, inputs="text", outputs="text")
    
if __name__ == "__main__":
    demo.launch() 

Gradio對(duì)Jupyter Notebook的支持相當(dāng)好,我們可以直接在Jupyter Notebook中運(yùn)行,既可以啟動(dòng)后端,也能展示前端。

2023年的深度學(xué)習(xí)入門指南(25) - 通義千問7b,深度學(xué)習(xí),人工智能

Gradio通過Markdown方法來(lái)書寫markdown文本,當(dāng)然也支持html標(biāo)簽:

    gr.Markdown("""<p align="center"><img src="https://modelscope.cn/api/v1/models/qwen/Qwen-7B-Chat/repo?Revision=master&FilePath=assets/logo.jpeg&View=true" style="height: 80px"/><p>""")
    gr.Markdown("""<center><font size=8>Qwen-7B-Chat Bot</center>""")
    gr.Markdown(
        """<center><font size=3>This WebUI is based on Qwen-7B-Chat, developed by Alibaba Cloud. (本W(wǎng)ebUI基于Qwen-7B-Chat打造,實(shí)現(xiàn)聊天機(jī)器人功能。)</center>"""
    )
    gr.Markdown(
        """<center><font size=4>Qwen-7B <a >?? <a> | <a >??</a>&nbsp | Qwen-7B-Chat <a >?? <a>| <a >??</a>&nbsp | &nbsp<a 
    )

我們來(lái)看下效果:

2023年的深度學(xué)習(xí)入門指南(25) - 通義千問7b,深度學(xué)習(xí),人工智能

Gradio支持TextBox用于輸入,Button用于點(diǎn)擊事件,而且支持ChatBot這樣的復(fù)雜控件。還可以用Row來(lái)橫向布局:

    chatbot = gr.Chatbot(lines=10, label='Qwen-7B-Chat', elem_classes="control-height")
    query = gr.Textbox(lines=2, label='Input')

    with gr.Row():
        emptyBtn = gr.Button("?? Clear History (清除歷史)")
        submitBtn = gr.Button("?? Submit (發(fā)送)")
        regenBtn = gr.Button("??? Regenerate (重試)")

效果如下:

2023年的深度學(xué)習(xí)入門指南(25) - 通義千問7b,深度學(xué)習(xí),人工智能

完整代碼如下,大家可以自己運(yùn)行一下:

import gradio as gr
with gr.Blocks() as demo:
    gr.Markdown("""<p align="center"><img src="https://modelscope.cn/api/v1/models/qwen/Qwen-7B-Chat/repo?Revision=master&FilePath=assets/logo.jpeg&View=true" style="height: 80px"/><p>""")
    gr.Markdown("""<center><font size=8>Qwen-7B-Chat Bot</center>""")
    gr.Markdown(
        """<center><font size=3>This WebUI is based on Qwen-7B-Chat, developed by Alibaba Cloud. (本W(wǎng)ebUI基于Qwen-7B-Chat打造,實(shí)現(xiàn)聊天機(jī)器人功能。)</center>"""
    )
    gr.Markdown(
        """<center><font size=4>Qwen-7B <a >?? <a> | <a >??</a>&nbsp | Qwen-7B-Chat <a >?? <a>| <a >??</a>&nbsp | &nbsp<a 
    )

    chatbot = gr.Chatbot(lines=10, label='Qwen-7B-Chat', elem_classes="control-height")
    query = gr.Textbox(lines=2, label='Input')

    with gr.Row():
        emptyBtn = gr.Button("?? Clear History (清除歷史)")
        submitBtn = gr.Button("?? Submit (發(fā)送)")
        regenBtn = gr.Button("??? Regenerate (重試)")

    gr.Markdown(
        """<font size=2>Note: This demo is governed by the original license of Qwen-7B. We strongly advise users not to knowingly generate or allow others to knowingly generate harmful content, including hate speech, violence, pornography, deception, etc. (注:本演示受Qwen-7B的許可協(xié)議限制。我們強(qiáng)烈建議,用戶不應(yīng)傳播及不應(yīng)允許他人傳播以下內(nèi)容,包括但不限于仇恨言論、暴力、色情、欺詐相關(guān)的有害信息。)"""
    )

if __name__ == "__main__":
    demo.launch()

再給三個(gè)Button配上響應(yīng)函數(shù),就可以響應(yīng)功能了:

    submitBtn.click(predict, [query, chatbot], [chatbot], show_progress=True)
    submitBtn.click(reset_user_input, [], [query])
    emptyBtn.click(reset_state, outputs=[chatbot], show_progress=True)
    regenBtn.click(regenerate, [chatbot], [chatbot], show_progress=True)

其中reset_state只更新下內(nèi)部狀態(tài)就好:

def reset_state():
    task_history.clear()
    return []

reset_user_input需要通過update函數(shù)來(lái)刷新下狀態(tài),寫過React的同學(xué)應(yīng)該很熟悉,這其實(shí)是個(gè)異步操作哈:

def reset_user_input():
    return gr.update(value="")

然后是需要處理下流狀態(tài)的predict函數(shù):

def predict(query, chatbot):
    print("User: " + parse_text(query))
    chatbot.append((parse_text(query), ""))
    fullResponse = ""

    for response in model.chat_stream(tokenizer, query, history=task_history):
        chatbot[-1] = (parse_text(query), parse_text(response))

        yield chatbot
        fullResponse = parse_text(response)

    task_history.append((query, fullResponse))
    print("Qwen-7B-Chat: " + parse_text(fullResponse))

注意yield的用法,chatbot就是我們用gr.ChatBot生成的對(duì)話框控件。

regenerate仍然要注意下yield:

def regenerate(chatbot):
    if not task_history:
        yield chatbot
        return
    item = task_history.pop(-1)
    chatbot.pop(-1)
    yield from predict(item[0], chatbot)

代碼超參數(shù)

下面我們來(lái)看下Qwen-7B-Chat的代碼。

首先是支持了哪些配置項(xiàng)和超參數(shù):

from transformers import PretrainedConfig


class QWenConfig(PretrainedConfig):
    model_type = "qwen"
    keys_to_ignore_at_inference = ["past_key_values"]
    attribute_map = {
        "hidden_size": "n_embd",
        "num_attention_heads": "n_head",
        "max_position_embeddings": "n_positions",
        "num_hidden_layers": "n_layer",
    }

    def __init__(
        self,
        vocab_size=151851,
        n_embd=4096,
        n_layer=32,
        n_head=32,
        n_inner=None,
        embd_pdrop=0.0,
        attn_pdrop=0.0,
        layer_norm_epsilon=1e-5,
        initializer_range=0.02,
        scale_attn_weights=True,
        use_cache=True,
        eos_token_id=151643,
        apply_residual_connection_post_layernorm=False,
        bf16=False,
        fp16=False,
        fp32=False,
        kv_channels=128,
        rotary_pct=1.0,
        rotary_emb_base=10000,
        use_dynamic_ntk=False,
        use_logn_attn=False,
        use_flash_attn=True,
        ffn_hidden_size=22016,
        no_bias=True,
        tie_word_embeddings=False,
        **kwargs,
    ):
        self.eos_token_id = eos_token_id
        super().__init__(
            eos_token_id=eos_token_id, tie_word_embeddings=tie_word_embeddings, **kwargs
        )

        self.vocab_size = vocab_size
        self.n_embd = n_embd
        self.n_layer = n_layer
        self.n_head = n_head
        self.n_inner = n_inner
        self.embd_pdrop = embd_pdrop
        self.attn_pdrop = attn_pdrop
        self.layer_norm_epsilon = layer_norm_epsilon
        self.initializer_range = initializer_range
        self.scale_attn_weights = scale_attn_weights
        self.use_cache = use_cache
        self.apply_residual_connection_post_layernorm = (
            apply_residual_connection_post_layernorm
        )
        self.bf16 = bf16
        self.fp16 = fp16
        self.fp32 = fp32
        self.kv_channels = kv_channels
        self.rotary_pct = rotary_pct
        self.rotary_emb_base = rotary_emb_base
        self.use_dynamic_ntk = use_dynamic_ntk
        self.use_logn_attn = use_logn_attn
        self.use_flash_attn = use_flash_attn
        self.ffn_hidden_size = ffn_hidden_size
        self.no_bias = no_bias
        self.tie_word_embeddings = tie_word_embeddings

我們來(lái)解釋下這些參數(shù):

  • vocab_size:詞匯表大小,即模型可以處理的不同單詞的數(shù)量,默認(rèn)為 151851
  • n_embd: 嵌入層的維度,即每個(gè)單詞或位置的向量表示的長(zhǎng)度,默認(rèn)為 4096
  • n_layer: 編碼器層的數(shù)量,即模型中重復(fù)堆疊的自注意力層和前饋層的數(shù)量,默認(rèn)為 32
  • n_head=32: 注意力頭的數(shù)量,即每個(gè)編碼器層中分割后的多頭自注意力機(jī)制的數(shù)量,默認(rèn)為 32
  • n_inner: 前饋層的內(nèi)部維度,即每個(gè)編碼器層中全連接層的隱藏單元數(shù),默認(rèn)為 None,表示與嵌入層維度相同
  • embd_pdrop: 嵌入層的丟棄概率,即在嵌入層后應(yīng)用丟棄正則化時(shí)隨機(jī)置零單元的概率,默認(rèn)為 0.0,表示不使用丟棄正則化
  • attn_pdrop: 注意力層的丟棄概率,即在注意力層后應(yīng)用丟棄正則化時(shí)隨機(jī)置零單元的概率,默認(rèn)為 0.0,表示不使用丟棄正則化
  • layer_norm_epsilon: 層歸一化的 epsilon 值,即在計(jì)算層歸一化時(shí)加到分母上的小量,防止除以零,默認(rèn)為 1e-5
  • initializer_range: 初始化范圍,即在初始化模型參數(shù)時(shí)使用的均勻分布的上下界,默認(rèn)為 0.02
  • scale_attn_weights: 是否縮放注意力權(quán)重,即在計(jì)算多頭自注意力機(jī)制時(shí)是否除以注意力頭數(shù)的平方根,默認(rèn)為 True
  • use_cache: 是否使用緩存,即在解碼時(shí)是否保存前面計(jì)算過的隱藏狀態(tài)和注意力鍵值對(duì),默認(rèn)為 True
  • eos_token_id:結(jié)束符號(hào)的 ID,即表示序列結(jié)束的特殊單詞對(duì)應(yīng)的整數(shù)編號(hào),默認(rèn)為 151643
  • apply_residual_connection_post_layernorm:是否在層歸一化后應(yīng)用殘差連接,即在每個(gè)編碼器層中是否先進(jìn)行層歸一化再加上輸入,默認(rèn)為 False
  • bf16:是否使用 bf16 格式,即是否使用 16 位浮點(diǎn)數(shù)來(lái)存儲(chǔ)模型參數(shù)和計(jì)算梯度,默認(rèn)為 False
  • fp16:是否使用 fp16 格式,即是否使用 16 位浮點(diǎn)數(shù)來(lái)存儲(chǔ)模型參數(shù)和計(jì)算梯度,默認(rèn)為 False
  • fp32:是否使用 fp32 格式,即是否使用 32 位浮點(diǎn)數(shù)來(lái)存儲(chǔ)模型參數(shù)和計(jì)算梯度,默認(rèn)為 False
  • kv_channels: 鍵值通道數(shù),即在計(jì)算注意力鍵值對(duì)時(shí)使用的線性變換的輸出維度,默認(rèn)為 128
  • rotary_pct: 旋轉(zhuǎn)百分比,即在嵌入層中使用旋轉(zhuǎn)位置編碼的比例,默認(rèn)為 1.0,表示全部使用旋轉(zhuǎn)位置編碼
  • rotary_emb_base: 旋轉(zhuǎn)嵌入基數(shù),即在計(jì)算旋轉(zhuǎn)位置編碼時(shí)使用的基數(shù),默認(rèn)為 10000
  • use_dynamic_ntk:是否使用動(dòng)態(tài) NTK,即是否在計(jì)算注意力權(quán)重時(shí)使用動(dòng)態(tài)神經(jīng)切線核方法,默認(rèn)為 False
  • use_logn_attn: 是否使用對(duì)數(shù)注意力,即是否在計(jì)算注意力權(quán)重時(shí)使用對(duì)數(shù)函數(shù)來(lái)加速和壓縮,默認(rèn)為 False
  • use_flash_attn: 是否使用閃存注意力,即是否在計(jì)算注意力權(quán)重時(shí)使用閃存變換來(lái)降低復(fù)雜度,默認(rèn)為 True
  • ffn_hidden_size: 前饋層的隱藏大小,即每個(gè)編碼器層中全連接層的輸出維度,默認(rèn)為 22016
  • no_bias: 是否不使用偏置,即在模型中的所有線性變換中是否不添加偏置向量,默認(rèn)為 True
  • tie_word_embeddings: 是否綁定詞嵌入,即在模型中是否共享輸入和輸出的詞嵌入矩陣,默認(rèn)為 False
  • kwargs: 其他參數(shù),用于接收額外的配置信息或覆蓋上面的默認(rèn)值

Flash Attention

千問7b建議使用flash attention來(lái)進(jìn)行加速。
Flash Attention 是一種新型的注意力算法,它可以快速和內(nèi)存高效地計(jì)算精確的注意力權(quán)重,而不需要近似或壓縮。它的主要思想是利用 GPU 的層次化內(nèi)存結(jié)構(gòu),通過分塊和重用的方法,減少?gòu)母邘拑?nèi)存(HBM)到片上靜態(tài)隨機(jī)存儲(chǔ)器(SRAM)的讀寫次數(shù),從而提高計(jì)算速度和節(jié)省內(nèi)存空間。Flash Attention 還可以擴(kuò)展到塊稀疏注意力,進(jìn)一步降低計(jì)算復(fù)雜度和內(nèi)存消耗。

Flash Attention 的主要優(yōu)勢(shì)有:

  • 它可以實(shí)現(xiàn)與標(biāo)準(zhǔn)注意力相同的模型質(zhì)量和精度,而不犧牲任何信息或引入任何噪聲。
  • 它可以在不同的序列長(zhǎng)度、批量大小、模型大小和硬件配置下,都能達(dá)到顯著的加速和內(nèi)存節(jié)省效果。
  • 它可以與其他優(yōu)化技術(shù)如混合精度訓(xùn)練、激活檢查點(diǎn)等兼容,進(jìn)一步提升性能。
  • 它可以支持更長(zhǎng)的上下文長(zhǎng)度,從而提高模型在長(zhǎng)文本任務(wù)上的表現(xiàn)。

具體原理我們后面會(huì)分析到其論文和代碼。
代碼在:https://github.com/Dao-AILab/flash-attention。論文在:https://arxiv.org/abs/2205.14135

這里我們先看在千問7b中如何使用flash attention。

首先要把Flash attention的庫(kù)加載進(jìn)來(lái):

def _import_flash_attn():
    global apply_rotary_emb_func, rms_norm, flash_attn_unpadded_func
    try:
        from flash_attn.layers.rotary import apply_rotary_emb_func as __apply_rotary_emb_func
        apply_rotary_emb_func = __apply_rotary_emb_func
    except ImportError:
        logger.warn(
            "Warning: import flash_attn rotary fail, please install FlashAttention rotary to get higher efficiency "
            "https://github.com/Dao-AILab/flash-attention/tree/main/csrc/rotary"
        )

    try:
        from flash_attn.ops.rms_norm import rms_norm as __rms_norm
        rms_norm = __rms_norm
    except ImportError:
        logger.warn(
            "Warning: import flash_attn rms_norm fail, please install FlashAttention layer_norm to get higher efficiency "
            "https://github.com/Dao-AILab/flash-attention/tree/main/csrc/layer_norm"
        )

    try:
        import flash_attn
        if not hasattr(flash_attn, '__version__'):
            from flash_attn.flash_attn_interface import flash_attn_unpadded_func as __flash_attn_unpadded_func
        else:
            if int(flash_attn.__version__.split(".")[0]) >= 2:
                from flash_attn.flash_attn_interface import flash_attn_varlen_func as __flash_attn_unpadded_func
            else:
                from flash_attn.flash_attn_interface import flash_attn_unpadded_func as __flash_attn_unpadded_func
        flash_attn_unpadded_func = __flash_attn_unpadded_func
    except ImportError:
        logger.warn(
            "Warning: import flash_attn fail, please install FlashAttention to get higher efficiency "
            "https://github.com/Dao-AILab/flash-attention"
        )

然后我們實(shí)現(xiàn)一個(gè)使用Flash Attention的自注意力模塊:

class FlashSelfAttention(torch.nn.Module):
    def __init__(
        self,
        causal=False,
        softmax_scale=None,
        attention_dropout=0.0,
    ):
        super().__init__()
        assert flash_attn_unpadded_func is not None, (
            "Please install FlashAttention first, " "e.g., with pip install flash-attn"
        )
        assert (
            rearrange is not None
        ), "Please install einops first, e.g., with pip install einops"
        self.causal = causal
        self.softmax_scale = softmax_scale
        self.dropout_p = attention_dropout

    def forward(self, q, k, v):
        assert all((i.dtype in [torch.float16, torch.bfloat16] for i in (q, k, v)))
        assert all((i.is_cuda for i in (q, k, v)))
        batch_size, seqlen_q = q.shape[0], q.shape[1]
        seqlen_k = k.shape[1]
        q, k, v = [rearrange(x, "b s ... -> (b s) ...") for x in [q, k, v]]
        cu_seqlens_q = torch.arange(
            0,
            (batch_size + 1) * seqlen_q,
            step=seqlen_q,
            dtype=torch.int32,
            device=q.device,
        )

        if self.training:
            assert seqlen_k == seqlen_q

            is_causal = self.causal
            cu_seqlens_k = cu_seqlens_q
        else:
            is_causal = seqlen_q == seqlen_k
            cu_seqlens_k = torch.arange(
                0,
                (batch_size + 1) * seqlen_k,
                step=seqlen_k,
                dtype=torch.int32,
                device=q.device,
            )
            self.dropout_p = 0
        output = flash_attn_unpadded_func(
            q,
            k,
            v,
            cu_seqlens_q,
            cu_seqlens_k,
            seqlen_q,
            seqlen_k,
            self.dropout_p,
            softmax_scale=self.softmax_scale,
            causal=is_causal,
        )

        output = rearrange(output, "(b s) ... -> b s ...", b=batch_size)
        return output

其主要步驟如下:

  • 首先,檢查q, k, v的數(shù)據(jù)類型是否為torch.float16或torch.bfloat16,以及是否在CUDA設(shè)備上運(yùn)行。
  • 然后,使用einops庫(kù)的rearrange函數(shù),將q, k, v的形狀從"b s …“變?yōu)椤?b s) …",其中b是批次大小,s是序列長(zhǎng)度。
  • 接著,根據(jù)q和k的序列長(zhǎng)度,生成兩個(gè)整數(shù)張量cu_seqlens_q和cu_seqlens_k,它們表示每個(gè)批次中每個(gè)序列的起始位置。
  • 再然后,根據(jù)是否處于訓(xùn)練模式和是否使用因果掩碼,設(shè)置cu_seqlens_k和is_causal的值,以及注意力的dropout概率。
  • 核心的Flash Attention來(lái)了,調(diào)用flash_attn_unpadded_func函數(shù),它是FlashAttention庫(kù)提供的一個(gè)核心函數(shù),它可以快速計(jì)算未填充的自注意力矩陣,并返回輸出張量。
  • 最后,將輸出張量的形狀從"(b s) …“變回"b s …”,并返回。

RMSNorm層

通義千問的RMSNorm跟之前講的基本一樣,這里就不多解釋了:

class RMSNorm(torch.nn.Module):
    def __init__(self, dim: int, eps: float = 1e-6):
        super().__init__()
        self.eps = eps
        self.weight = nn.Parameter(torch.ones(dim))

    def _norm(self, x):
        return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)

    def forward(self, x):
        if rms_norm is not None and x.is_cuda:
            return rms_norm(x, self.weight, self.eps)
        else:
            output = self._norm(x.float()).type_as(x)
            return output * self.weight

位置編碼

千問7b的位置編碼是標(biāo)準(zhǔn)的Rotary Position Embedding。來(lái)自論文《RoFormer: Enhanced Transformer with Rotary Position Embedding》。

class RotaryEmbedding(torch.nn.Module):
    def __init__(self, dim, base=10000):
        super().__init__()
        self.dim = dim
        self.base = base
        self.inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2).float() / dim))
        if importlib.util.find_spec("einops") is None:
            raise RuntimeError("einops is required for Rotary Embedding")

        self._rotary_pos_emb_cache = None
        self._seq_len_cached = 0
        self._ntk_alpha_cached = 1.0

    def update_rotary_pos_emb_cache(self, max_seq_len, offset=0, ntk_alpha=1.0):
        seqlen = max_seq_len + offset
        if seqlen > self._seq_len_cached or ntk_alpha != self._ntk_alpha_cached:
            base = self.base * ntk_alpha ** (self.dim / (self.dim - 2))
            self.inv_freq = 1.0 / (
                base
                ** (
                    torch.arange(0, self.dim, 2, device=self.inv_freq.device).float()
                    / self.dim
                )
            )
            self._seq_len_cached = max(2 * seqlen, 16)
            self._ntk_alpha_cached = ntk_alpha
            seq = torch.arange(self._seq_len_cached, device=self.inv_freq.device)
            freqs = torch.outer(seq.type_as(self.inv_freq), self.inv_freq)
            emb = torch.cat((freqs, freqs), dim=-1)
            from einops import rearrange

            self._rotary_pos_emb_cache = rearrange(emb, "n d -> 1 n 1 d")

    def forward(self, max_seq_len, offset=0, ntk_alpha=1.0):
        self.update_rotary_pos_emb_cache(max_seq_len, offset, ntk_alpha)
        return self._rotary_pos_emb_cache[:, offset : offset + max_seq_len]

千問7b的_rotate_half使用了einops庫(kù)來(lái)加速:

def _rotate_half(x):
    from einops import rearrange

    x = rearrange(x, "... (j d) -> ... j d", j=2)
    x1, x2 = x.unbind(dim=-2)
    return torch.cat((-x2, x1), dim=-1)

最后是apply_rotary_pos_emb的實(shí)現(xiàn),使用了apply_rotary_emb_func來(lái)進(jìn)行加速。

def apply_rotary_pos_emb(t, freqs):
    if apply_rotary_emb_func is not None and t.is_cuda:
        t_ = t.float()
        freqs = freqs.squeeze(0).squeeze(1)
        cos = freqs[:, : freqs.shape[-1] // 2].cos()
        sin = freqs[:, : freqs.shape[-1] // 2].sin()
        output = apply_rotary_emb_func(t_, cos, sin).type_as(t)
        return output
    else:
        rot_dim = freqs.shape[-1]
        t_, t_pass_ = t[..., :rot_dim], t[..., rot_dim:]
        t_ = t_.float()
        t_pass_ = t_pass_.float()
        t_ = (t_ * freqs.cos()) + (_rotate_half(t_) * freqs.sin())
        return torch.cat((t_, t_pass_), dim=-1).type_as(t)

千問7b的注意力結(jié)構(gòu)

首先還是一堆變量定義:

class QWenAttention(nn.Module):
    def __init__(self, config, layer_number=None):
        super().__init__()

        max_positions = config.max_position_embeddings
        self.register_buffer(
            "bias",
            torch.tril(
                torch.ones((max_positions, max_positions), dtype=torch.bool)
            ).view(1, 1, max_positions, max_positions),
            persistent=False,
        )
        self.register_buffer("masked_bias", torch.tensor(-1e4), persistent=False)
        self.layer_number = max(1, layer_number)
        self.params_dtype = config.params_dtype
        self.seq_length = config.seq_length

        self.hidden_size = config.hidden_size
        self.split_size = config.hidden_size
        self.num_heads = config.num_attention_heads
        self.head_dim = self.hidden_size // self.num_heads

        self.use_flash_attn = config.use_flash_attn
        self.scale_attn_weights = True

        self.layer_idx = None

        self.projection_size = config.kv_channels * config.num_attention_heads

        assert self.projection_size % config.num_attention_heads == 0
        self.hidden_size_per_attention_head = (
            self.projection_size // config.num_attention_heads
        )

        self.c_attn = nn.Linear(config.hidden_size, 3 * self.projection_size)

        self.c_proj = nn.Linear(
            config.hidden_size, self.projection_size, bias=not config.no_bias
        )

        self.is_fp32 = not (config.bf16 or config.fp16)
        if (
            self.use_flash_attn
            and flash_attn_unpadded_func is not None
            and not self.is_fp32
        ):
            self.core_attention_flash = FlashSelfAttention(
                causal=True, attention_dropout=config.attn_pdrop
            )

        self.bf16 = config.bf16

        if config.rotary_pct == 1.0:
            self.rotary_ndims = None
        else:
            assert config.rotary_pct < 1
            self.rotary_ndims = int(
                self.hidden_size_per_attention_head * config.rotary_pct
            )
        dim = (
            self.rotary_ndims
            if self.rotary_ndims is not None
            else self.hidden_size_per_attention_head
        )
        self.rotary_emb = RotaryEmbedding(dim, base=config.rotary_emb_base)

        self.use_dynamic_ntk = config.use_dynamic_ntk
        self.use_logn_attn = config.use_logn_attn

        logn_list = [
            math.log(i, self.seq_length) if i > self.seq_length else 1
            for i in range(1, 32768)
        ]
        self.logn_tensor = torch.tensor(logn_list)[None, :, None, None]
        self._ntk_cached = 1.0

        self.attn_dropout = nn.Dropout(config.attn_pdrop)

大致介紹一下這些變量,具體的含義我們?cè)诤竺娲a可以講到:

  • max_positions 定義了模型可以處理的最大位置數(shù),它來(lái)自配置對(duì)象
  • bias 是一個(gè)下三角矩陣,大小為 (max_positions, max_positions),用于實(shí)現(xiàn)自注意力的屏蔽。它被注冊(cè)為一個(gè)不需要持久化的緩沖區(qū)
  • masked_bias 是一個(gè)具有大負(fù)值(-1e4)的張量,用于在注意力得分中屏蔽某些位置
  • layer_number 是當(dāng)前層的層數(shù),至少為1
  • params_dtype 是模型參數(shù)的數(shù)據(jù)類型
  • seq_length 是輸入序列的長(zhǎng)度
  • hidden_size、split_size、num_heads、head_dim 分別為隱藏層大小,分割大小,注意力頭數(shù)和每個(gè)注意力頭的維度
  • use_flash_attn 是一個(gè)布爾標(biāo)志,表示是否使用 Flash Attention
  • scale_attn_weights 是一個(gè)布爾標(biāo)志,表示是否對(duì)注意力權(quán)重進(jìn)行縮放
  • projection_size 定義了投影的大小,它等于 kv_channels 和 num_attention_heads 的乘積
  • c_attn 和 c_proj 是兩個(gè)線性層,用于計(jì)算注意力得分
  • core_attention_flash 是一個(gè) FlashSelfAttention 對(duì)象,只有在使用 Flash Attention 并且數(shù)據(jù)類型不是 fp32 時(shí)才會(huì)創(chuàng)建
  • bf16 是一個(gè)布爾標(biāo)志,表示是否使用 bf16 數(shù)據(jù)類型
  • rotary_emb 是一個(gè) RotaryEmbedding 對(duì)象,用于實(shí)現(xiàn)旋轉(zhuǎn)位置編碼
  • use_dynamic_ntk 是一個(gè)布爾標(biāo)志,表示是否使用動(dòng)態(tài) NTK
  • use_logn_attn 是一個(gè)布爾標(biāo)志,表示是否使用 logn 注意力
  • logn_tensor 是一個(gè)張量,包含了一些預(yù)計(jì)算的 logn 值
  • attn_dropout 是一個(gè) Dropout 層,用于在注意力計(jì)算中添加隨機(jī)性

下面我們來(lái)看注意力的計(jì)算:

    def _attn(self, query, key, value, attention_mask=None, head_mask=None):
        attn_weights = torch.matmul(query, key.transpose(-1, -2))

        if self.scale_attn_weights:
            attn_weights = attn_weights / torch.full(
                [],
                value.size(-1) ** 0.5,
                dtype=attn_weights.dtype,
                device=attn_weights.device,
            )

        query_length, key_length = query.size(-2), key.size(-2)
        causal_mask = self.bias[
            :, :, key_length - query_length : key_length, :key_length
        ]
        mask_value = torch.finfo(attn_weights.dtype).min
        mask_value = torch.full([], mask_value, dtype=attn_weights.dtype).to(
            attn_weights.device
        )
        attn_weights = torch.where(
            causal_mask, attn_weights.to(attn_weights.dtype), mask_value
        )

        attn_weights = nn.functional.softmax(attn_weights, dim=-1)

        attn_weights = attn_weights.type(value.dtype)
        attn_weights = self.attn_dropout(attn_weights)

        if head_mask is not None:
            attn_weights = attn_weights * head_mask

        attn_output = torch.matmul(attn_weights, value)
        attn_output = attn_output.transpose(1, 2)

        return attn_output, attn_weights

其主要步驟如下:

  • 使用 torch.matmul 計(jì)算查詢(query)和鍵(key)的點(diǎn)積,得到注意力權(quán)重 attn_weights。
  • 如果 self.scale_attn_weights 為 True,則將注意力權(quán)重除以值(value)的最后一個(gè)維度的平方根,這是一種常見的縮放操作,用于控制注意力權(quán)重的大小。
  • 創(chuàng)建一個(gè)因果屏蔽 causal_mask,該屏蔽用于確保在自注意力計(jì)算中,任何位置只能注意到其之前的位置。其中 mask_value 是一個(gè)非常小的數(shù),用于在注意力得分中屏蔽某些位置。
  • 使用 torch.where 應(yīng)用因果屏蔽。如果 causal_mask 中的某一位置為 True,那么在對(duì)應(yīng)的 attn_weights 位置保持原值,否則用 mask_value 替換。
  • 對(duì)注意力權(quán)重應(yīng)用 softmax 函數(shù),使得所有權(quán)重之和為1,這樣可以將它們解釋為概率。
  • 使用 attn_dropout 對(duì)注意力權(quán)重應(yīng)用 dropout 操作,以增加模型的泛化能力。
  • 如果提供了 head_mask,則將其應(yīng)用到注意力權(quán)重上,這可以用于屏蔽某些注意力頭。
  • 使用注意力權(quán)重和值(value)計(jì)算注意力輸出 attn_output,并將其張量的第1維和第2維進(jìn)行轉(zhuǎn)置,以滿足后續(xù)操作的需要。

為了提高計(jì)算精度,還有另一個(gè)Attention的計(jì)算函數(shù):

    def _upcast_and_reordered_attn(
        self, query, key, value, attention_mask=None, head_mask=None
    ):
        bsz, num_heads, q_seq_len, dk = query.size()
        _, _, k_seq_len, _ = key.size()

        attn_weights = torch.empty(
            bsz * num_heads,
            q_seq_len,
            k_seq_len,
            dtype=torch.float32,
            device=query.device,
        )

        scale_factor = 1.0
        if self.scale_attn_weights:
            scale_factor /= float(value.size(-1)) ** 0.5

        with autocast(enabled=False):
            q, k = query.reshape(-1, q_seq_len, dk), key.transpose(-1, -2).reshape(
                -1, dk, k_seq_len
            )
            attn_weights = torch.baddbmm(
                attn_weights, q.float(), k.float(), beta=0, alpha=scale_factor
            )
            attn_weights = attn_weights.reshape(bsz, num_heads, q_seq_len, k_seq_len)

        query_length, key_length = query.size(-2), key.size(-2)
        causal_mask = self.bias[
            :, :, key_length - query_length : key_length, :key_length
        ]
        mask_value = torch.finfo(attn_weights.dtype).min
        mask_value = torch.tensor(mask_value, dtype=attn_weights.dtype).to(
            attn_weights.device
        )
        attn_weights = torch.where(causal_mask, attn_weights, mask_value)

        if attention_mask is not None:
            attn_weights = attn_weights + attention_mask

        attn_weights = nn.functional.softmax(attn_weights, dim=-1)

        if attn_weights.dtype != torch.float32:
            raise RuntimeError(
                "Error with upcasting, attn_weights does not have dtype torch.float32"
            )
        attn_weights = attn_weights.type(value.dtype)
        attn_weights = self.attn_dropout(attn_weights)

        if head_mask is not None:
            attn_weights = attn_weights * head_mask

        attn_output = torch.matmul(attn_weights, value)

        return attn_output, attn_weights

_upcast_and_reordered_attn注意力權(quán)重計(jì)算使用float32精度。將query和key reshape成2D矩陣,然后使用torch.baddbmm進(jìn)行高效的矩陣乘法。計(jì)算得到的attn_weights再reshape回原始的4D形狀。同樣應(yīng)用因果遮掩矩陣和attention mask。

在softmax之前校驗(yàn)attn_weights是否是float32,如果不是會(huì)報(bào)錯(cuò)。softmax后再將attn_weights轉(zhuǎn)回value的dtype。

最后得到attention輸出和權(quán)重矩陣。

還有對(duì)頭的拆分和組裝的兩個(gè)輔助函數(shù):

    def _split_heads(self, tensor, num_heads, attn_head_size):
        new_shape = tensor.size()[:-1] + (num_heads, attn_head_size)
        tensor = tensor.view(new_shape)
        return tensor

    def _merge_heads(self, tensor, num_heads, attn_head_size):
        tensor = tensor.contiguous()
        new_shape = tensor.size()[:-2] + (num_heads * attn_head_size,)
        return tensor.view(new_shape)

_split_heads 函數(shù)的作用是將輸入張量的最后一個(gè)維度分割成兩個(gè)維度,其中一個(gè)是注意力頭的數(shù)量(num_heads),另一個(gè)是每個(gè)注意力頭的大?。╝ttn_head_size)。函數(shù)首先創(chuàng)建了新的形狀 new_shape,然后使用 view 函數(shù)將輸入張量變形為這個(gè)新的形狀。

_merge_heads 函數(shù)的作用是將 _split_heads 函數(shù)處理后的張量回歸到原始的維度。首先,它會(huì)調(diào)用 contiguous 函數(shù)確保張量在內(nèi)存中是連續(xù)的,這是因?yàn)樵谀承┣闆r下,view 函數(shù)需要輸入張量在內(nèi)存中是連續(xù)的。然后,它創(chuàng)建了新的形狀 new_shape,并使用 view 函數(shù)將輸入張量變形為這個(gè)新的形狀。

最后是前向計(jì)算。主要分為十步:

  • 輸入?yún)?shù):hidden_states是輸入的隱藏狀態(tài),layer_past是上一層的輸出,attention_mask和head_mask分別是注意力掩碼和頭掩碼,encoder_hidden_states和encoder_attention_mask是在編碼器-解碼器架構(gòu)中使用的,output_attentions決定是否輸出注意力權(quán)重,use_cache決定是否使用緩存。
  • 計(jì)算 query、key 和 value:通過self.c_attn(hidden_states)計(jì)算混合層,然后將其拆分為查詢、鍵和值。拆分后的大小是self.split_size。
  • 分割多頭注意力:使用_split_heads()函數(shù)對(duì) query、key 和 value 進(jìn)行拆分,將最后一個(gè)維度拆分為self.num_heads和self.head_dim。
  • 處理旋轉(zhuǎn)位置嵌入:根據(jù)kv_seq_len和ntk_alpha計(jì)算旋轉(zhuǎn)位置嵌入。然后,對(duì) query 和 key 應(yīng)用旋轉(zhuǎn)位置嵌入。
  • 處理 past layer:如果layer_past存在,將其與當(dāng)前的 key 和 value 連接起來(lái)。
  • 處理緩存:如果use_cache為 True,則將當(dāng)前的 key 和 value 存儲(chǔ)到present中。
  • 應(yīng)用對(duì)數(shù)注意力:如果use_logn_attn為 True,并且當(dāng)前不處于訓(xùn)練模式,那么將對(duì) query 應(yīng)用對(duì)數(shù)注意力。
  • 應(yīng)用 Flash Attention 或常規(guī)注意力:如果use_flash_attn為 True,并且滿足一些其他條件,那么使用 Flash Attention 對(duì) query、key 和 value 進(jìn)行處理。否則,使用常規(guī)的注意力機(jī)制,并且將 query、key 和 value 的維度重新排列以符合_attn()函數(shù)的要求。
  • 計(jì)算注意力輸出并進(jìn)行投影:使用self.c_proj()將注意力輸出進(jìn)行投影。
  • 生成輸出:如果output_attentions為 True,那么在輸出中加入注意力權(quán)重。
    def forward(
        self,
        hidden_states: Optional[Tuple[torch.FloatTensor]],
        layer_past: Optional[Tuple[torch.Tensor]] = None,
        attention_mask: Optional[torch.FloatTensor] = None,
        head_mask: Optional[torch.FloatTensor] = None,
        encoder_hidden_states: Optional[torch.Tensor] = None,
        encoder_attention_mask: Optional[torch.FloatTensor] = None,
        output_attentions: Optional[bool] = False,
        use_cache: Optional[bool] = False,
    ):

        mixed_x_layer = self.c_attn(hidden_states)
        query, key, value = mixed_x_layer.split(self.split_size, dim=2)

        query = self._split_heads(query, self.num_heads, self.head_dim)
        key = self._split_heads(key, self.num_heads, self.head_dim)
        value = self._split_heads(value, self.num_heads, self.head_dim)

        kv_seq_len = hidden_states.size()[1]
        if layer_past:
            # layer past[0] shape: bs * seq_len * head_num * dim
            kv_seq_len += layer_past[0].shape[1]
        if (
            self.use_dynamic_ntk
            and kv_seq_len == hidden_states.size()[1]
            and not self.training
        ):
            context_value = math.log(kv_seq_len / self.seq_length, 2) + 1
            ntk_alpha = 2 ** math.ceil(context_value) - 1
            ntk_alpha = max(ntk_alpha, 1)
            self._ntk_cached = ntk_alpha
        else:
            ntk_alpha = self._ntk_cached
        rotary_pos_emb = self.rotary_emb(kv_seq_len, ntk_alpha=ntk_alpha).to(
            hidden_states.device
        )

        if rotary_pos_emb is not None:
            if isinstance(rotary_pos_emb, tuple):
                rotary_pos_emb = rotary_pos_emb
            else:
                rotary_pos_emb = (rotary_pos_emb,) * 2

        if rotary_pos_emb is not None:
            q_pos_emb, k_pos_emb = rotary_pos_emb
            # Slice the pos emb for current inference
            cur_len = query.shape[1]
            q_pos_emb = q_pos_emb[:, -cur_len:, :, :]
            k_pos_emb = k_pos_emb[:, -cur_len:, :, :]
            query = apply_rotary_pos_emb(query, q_pos_emb)
            key = apply_rotary_pos_emb(key, k_pos_emb)

        if layer_past is not None:
            past_key, past_value = layer_past[0], layer_past[1]
            key = torch.cat((past_key, key), dim=1)
            value = torch.cat((past_value, value), dim=1)

        if use_cache:
            present = (key, value)
        else:
            present = None

        if self.use_logn_attn and not self.training:
            if self.logn_tensor.device != query.device or self.logn_tensor.dtype != query.dtype:
                self.logn_tensor = self.logn_tensor.to(query.device).type_as(query)
            seq_start = key.size(1) - query.size(1)
            seq_end = key.size(1)
            logn_tensor = self.logn_tensor[:, seq_start:seq_end, :, :]
            query = query * logn_tensor.expand_as(query)

        if (
            self.use_flash_attn
            and flash_attn_unpadded_func is not None
            and not self.is_fp32
            and query.is_cuda
        ):
            q, k, v = query, key, value
            context_layer = self.core_attention_flash(q, k, v)

            context_layer = rearrange(
                context_layer, "b s h d -> b s (h d)"
            ).contiguous()
        else:
            query = query.permute(0, 2, 1, 3)
            key = key.permute(0, 2, 1, 3)
            value = value.permute(0, 2, 1, 3)
            attn_output, attn_weight = self._attn(
                query, key, value, attention_mask, head_mask
            )
            context_layer = self._merge_heads(
                attn_output, self.num_heads, self.head_dim
            )

        attn_output = self.c_proj(context_layer)
        outputs = (attn_output, present)
        if output_attentions:
            if (
                self.use_flash_attn
                and flash_attn_unpadded_func is not None
                and not self.is_fp32
            ):
                raise ValueError("Cannot output attentions while using flash-attn")
            else:
                outputs += (attn_weight,)

        return outputs

小結(jié)

千問7b的代碼比較長(zhǎng),實(shí)現(xiàn)的接口也較多,下一節(jié)我們繼續(xù)介紹將自注意力模塊和組裝成模型的代碼。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-639584.html

到了這里,關(guān)于2023年的深度學(xué)習(xí)入門指南(25) - 通義千問7b的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 2023年的深度學(xué)習(xí)入門指南(2) - 給openai API寫前端

    2023年的深度學(xué)習(xí)入門指南(2) - 給openai API寫前端

    上一篇我們說了,目前的大規(guī)模預(yù)訓(xùn)練模型技術(shù)還避免不了回答問題時(shí)出現(xiàn)低級(jí)錯(cuò)誤。 但是其實(shí),人類犯的邏輯錯(cuò)誤也是層出不窮。 比如,有人就認(rèn)為要想學(xué)好chatgpt,就要先學(xué)好Python。 其隱含的推理過程可能是這樣的: TensorFlow需要使用Python PyTorch需要使用Python Scikit-Learn需

    2023年04月08日
    瀏覽(27)
  • 2023年的深度學(xué)習(xí)入門指南(9) - SIMD和通用GPU編程

    2023年的深度學(xué)習(xí)入門指南(9) - SIMD和通用GPU編程

    深度學(xué)習(xí)從一開始就跟GPU有不解之緣,因?yàn)樗懔κ巧疃葘W(xué)習(xí)不可或缺的一部分。 時(shí)至今日,雖然多任務(wù)編程早已經(jīng)深入人心,但是很多同學(xué)還沒有接觸過CPU上的SIMD指令,更不用說GPGPU的編程。這一篇我們先給SIMD和GPU編程掃個(gè)盲,讓大家以后用到的時(shí)候有個(gè)感性認(rèn)識(shí)。 從多線

    2024年02月02日
    瀏覽(27)
  • 2023年的深度學(xué)習(xí)入門指南(6) - 在你的電腦上運(yùn)行大模型

    2023年的深度學(xué)習(xí)入門指南(6) - 在你的電腦上運(yùn)行大模型

    上一篇我們介紹了大模型的基礎(chǔ),自注意力機(jī)制以及其實(shí)現(xiàn)Transformer模塊。因?yàn)門ransformer被PyTorch和TensorFlow等框架所支持,所以我們只要能夠配置好框架的GPU或者其他加速硬件的支持,就可以運(yùn)行起來(lái)了。 而想運(yùn)行大模型,恐怕就沒有這么容易了,很有可能你需要一臺(tái)Linux電腦

    2024年02月16日
    瀏覽(24)
  • 測(cè)牛學(xué)堂:2023軟件測(cè)試入門學(xué)習(xí)指南(測(cè)試方法之邊界值法)

    邊界值分析法 邊界值:輸入數(shù)據(jù)是一個(gè)有序的集合或者范圍的時(shí)候,處于集合范圍的邊界上的值。 邊界值的幾個(gè)常用的概念: 上點(diǎn):邊界上的點(diǎn)。比如條件是(1,9)那么上點(diǎn)就是2和9 離點(diǎn):開區(qū)間的離點(diǎn),就是反方向去取。(1,9) 的離點(diǎn),就是2和8 內(nèi)點(diǎn):范圍內(nèi)除了上點(diǎn)和

    2023年04月25日
    瀏覽(21)
  • 手把手帶你入門深度學(xué)習(xí)(一):保姆級(jí)Anaconda和PyTorch環(huán)境配置指南

    手把手帶你入門深度學(xué)習(xí)(一):保姆級(jí)Anaconda和PyTorch環(huán)境配置指南

    B站:馬上就更?。?!_bilibili CSDN:手把手帶你入門深度學(xué)習(xí)(一):保姆級(jí)Anaconda和PyTorch環(huán)境配置指南_百年后封筆-CSDN博客 Github:封筆 公眾號(hào):百年后封筆 你好,我是封筆! 如今深度學(xué)習(xí)技術(shù)的不斷演進(jìn),我們的生活發(fā)生著翻天覆地的變化,無(wú)論是計(jì)算機(jī)視覺、自然語(yǔ)言處

    2024年02月08日
    瀏覽(96)
  • kotlin入門教程指南(2023最新)

    kotlin入門教程指南(2023最新)

    Kotlin 是一個(gè)基于 JVM 的新的編程語(yǔ)言,目前在國(guó)外非?;馃幔⑶以谝徊讲阶呦驀?guó)內(nèi)市場(chǎng) Kotlin有以下好處: 強(qiáng)大的IDE。而且是JetBrains第一方支持,不是3年更新一次的第三方插件; 庫(kù)多生態(tài)強(qiáng)。Kotlin的設(shè)計(jì)者非常重視和Java的互操作,所以Kotlin號(hào)稱可以無(wú)縫銜接所有Java庫(kù)。

    2024年02月14日
    瀏覽(40)
  • 立體匹配入門指南(8):視差圖、深度圖、點(diǎn)云

    立體匹配入門指南(8):視差圖、深度圖、點(diǎn)云

    本篇是比較簡(jiǎn)單的基礎(chǔ)概念,剛?cè)腴T的朋友可能是需要的。 視差圖 三維點(diǎn)云 首先,我們要介紹下這三個(gè)概念。 視差(disparity) 視差 d d d 等于同名點(diǎn)對(duì)在左視圖的列坐標(biāo)減去在右視圖上的列坐標(biāo),是 像素單位 d = x l ? x r d=x_l-x_r d = x l ? ? x r ? 立體視覺里,視差概念在極

    2023年04月08日
    瀏覽(61)
  • 【機(jī)器學(xué)習(xí)學(xué)習(xí)】第一天:入門指南

    引言 當(dāng)今社會(huì),機(jī)器學(xué)習(xí)技術(shù)已經(jīng)被廣泛應(yīng)用于許多領(lǐng)域,如自然語(yǔ)言處理、圖像處理和金融分析等。然而,機(jī)器學(xué)習(xí)這一領(lǐng)域需要掌握大量的數(shù)學(xué)知識(shí)和編程技能,因此對(duì)于初學(xué)者來(lái)說,可能會(huì)感到非常困難。本文將為初學(xué)者提供一份機(jī)器學(xué)習(xí)入門指南,幫助他們了解機(jī)器

    2024年02月02日
    瀏覽(26)
  • 一位計(jì)科學(xué)長(zhǎng)寫給 2023 級(jí)計(jì)算機(jī)類和人工智能專業(yè)的同學(xué)們的程序設(shè)計(jì)入門指南

    一位計(jì)科學(xué)長(zhǎng)寫給 2023 級(jí)計(jì)算機(jī)類和人工智能專業(yè)的同學(xué)們的程序設(shè)計(jì)入門指南

    本指南內(nèi)容較多,但你們?nèi)裟苣托淖x完,你們將收獲很多…… 歡迎訪問作者的主頁(yè):Xi Xu’s Home Page 什么是程序設(shè)計(jì)和程序設(shè)計(jì)語(yǔ)言? 程序設(shè)計(jì) 1 (programming),或稱編程,是給程序解決出特定問題的過程,軟件開發(fā)過程中的重要步驟。程序設(shè)計(jì)方法往往以某種程序設(shè)計(jì)語(yǔ)言

    2024年02月16日
    瀏覽(25)
  • 前端學(xué)習(xí)路線指南:從入門到精通【①】

    作為一個(gè)前端開發(fā)者,學(xué)習(xí)前端技術(shù)是必不可少的。然而,由于前端領(lǐng)域的廣闊和不斷演進(jìn)的技術(shù)棧,對(duì)于初學(xué)者來(lái)說可能會(huì)感到困惑。本篇文章將為你提供一個(gè)清晰的前端學(xué)習(xí)路線,幫助你系統(tǒng)地掌握前端開發(fā)技能,并成為一名優(yōu)秀的前端工程師。 HTML和CSS基礎(chǔ) 在開始前端

    2024年02月08日
    瀏覽(28)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包