色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
电子发烧友
开通电子发烧友VIP会员 尊享10大特权
海量资料免费下载
精品直播免费看
优质内容免费畅学
课程9折专享价
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

自制深度學習推理框架之計算圖中的表達式

jf_pmFSk4VX ? 來源:GiantPandaCV ? 2023-02-16 10:33 ? 次閱讀

什么是表達式

表達式就是一個計算過程,類似于如下:

output_mid=input1+input2
output=output_mid*input3

用圖形來表達就是這樣的.

1d1dd988-ad17-11ed-bfe3-dac502259ad0.png

但是在PNNX的Expession Layer中給出的是一種抽象表達式,會對計算過程進行折疊,消除中間變量. 并且將具體的輸入張量替換為抽象輸入@0,@1等.對于上面的計算過程,PNNX生成的抽象表達式是這樣的.

add(@0,mul(@1,@2))抽象的表達式重新變回到一個方便后端執行的計算過程(抽象語法樹來表達,在推理的時候我們會把它轉成逆波蘭式)。

其中add和mul表示我們上一節中說到的RuntimeOperator, @0和@1表示我們上一節課中說道的RuntimeOperand. 這個抽象表達式看起來比較簡單,但是實際上情況會非常復雜,我們給出一個復雜的例子:

add(add(mul(@0,@1),mul(@2,add(add(add(@0,@2),@3),@4))),@5)

這就要求我們需要一個魯棒的表達式解析和語法樹構建功能.

我們的工作

詞法解析

詞法解析的目的就是將add(@0,mul(@1,@2))拆分為多個token,token依次為add ( @0 , mul等.代碼如下:

enumclassTokenType{
TokenUnknown=-1,
TokenInputNumber=0,
TokenComma=1,
TokenAdd=2,
TokenMul=3,
TokenLeftBracket=4,
TokenRightBracket=5,
};

structToken{
TokenTypetoken_type=TokenType::TokenUnknown;
int32_tstart_pos=0;//詞語開始的位置
int32_tend_pos=0;//詞語結束的位置
Token(TokenTypetoken_type,int32_tstart_pos,int32_tend_pos):token_type(token_type),start_pos(start_pos),end_pos(end_pos){

}
};

我們在TokenType中規定了Token的類型,類型有輸入、加法、乘法以及左右括號等.Token類中記錄了類型以及Token在字符串的起始和結束位置.

如下的代碼是具體的解析過程,我們將輸入存放在statement_中,首先是判斷statement_是否為空, 隨后刪除表達式中的所有空格和制表符.

if(!need_retoken&&!this->tokens_.empty()){
return;
}

CHECK(!statement_.empty())<

下面的代碼中,我們先遍歷表達式輸入

for(int32_ti=0;i

char c是當前的字符,當c等于字符a的時候,我們的詞法規定在token中以a作為開始的情況只有add. 所以我們判斷接下來的兩個字符必須是d和 d.如果不是的話就報錯,如果是i的話就初始化一個新的token并進行保存.

舉個簡單的例子只有可能是add,沒有可能是axc之類的組合.

elseif(c=='m'){
CHECK(i+1

同理當c等于字符m的時候,我們的語法規定token中以m作為開始的情況只有mul. 所以我們判斷接下來的兩個字必須是u和l. 如果不是的話,就報錯,是的話就初始化一個mul token進行保存.

}elseif(c=='@'){
CHECK(i+1

當輸入為ant時候,我們對ant之后的所有數字進行讀取,如果其之后不是操作數,則報錯.當字符等于(或者,的時候就直接保存為對應的token,不需要對往后的字符進行探查, 直接保存為對應類型的Token.

語法解析

當得到token數組之后,我們對語法進行分析,并得到最終產物抽象語法樹(不懂的請自己百度,這是編譯原理中的概念).語法解析的過程是遞歸向下的,定義在Generate_函數中.

structTokenNode{
int32_tnum_index=-1;
std::shared_ptrleft=nullptr;
std::shared_ptrright=nullptr;
TokenNode(int32_tnum_index,std::shared_ptrleft,std::shared_ptrright);
TokenNode()=default;
};

抽象語法樹由一個二叉樹組成,其中存儲它的左子節點和右子節點以及對應的操作編號num_index. num_index為正, 則表明是輸入的編號,例如@0,@1中的num_index依次為1和2. 如果num_index為負數則表明當前的節點是mul或者add等operator.

std::shared_ptrExpressionParser::Generate_(int32_t&index){
CHECK(indextokens_.size());
constautocurrent_token=this->tokens_.at(index);
CHECK(current_token.token_type==TokenType::TokenInputNumber
||current_token.token_type==TokenType::TokenAdd||current_token.token_type==TokenType::TokenMul);

因為是一個遞歸函數,所以index指向token數組中的當前處理位置.current_token表示當前處理的token,它作為當前遞歸層的第一個Token, 必須是以下類型的一種.

TokenInputNumber=0,
TokenAdd=2,
TokenMul=3,

如果當前token類型是輸入數字類型, 則直接返回一個操作數token作為一個葉子節點,不再向下遞歸, 也就是在add(@0,@1)中的@0和@1,它們在前面的詞法分析中被歸類為TokenInputNumber類型.

if(current_token.token_type==TokenType::TokenInputNumber){
uint32_tstart_pos=current_token.start_pos+1;
uint32_tend_pos=current_token.end_pos;
CHECK(end_pos>start_pos);
CHECK(end_pos<=?this->statement_.length());
conststd::string&str_number=
std::string(this->statement_.begin()+start_pos,this->statement_.begin()+end_pos);
returnstd::make_shared(std::stoi(str_number),nullptr,nullptr);

}
elseif(current_token.token_type==TokenType::TokenMul||current_token.token_type==TokenType::TokenAdd){
std::shared_ptrcurrent_node=std::make_shared();
current_node->num_index=-int(current_token.token_type);

index+=1;
CHECK(indextokens_.size());
//判斷add之后是否有(leftbracket
CHECK(this->tokens_.at(index).token_type==TokenType::TokenLeftBracket);

index+=1;
CHECK(indextokens_.size());
constautoleft_token=this->tokens_.at(index);
//判斷當前需要處理的lefttoken是不是合法類型
if(left_token.token_type==TokenType::TokenInputNumber
||left_token.token_type==TokenType::TokenAdd||left_token.token_type==TokenType::TokenMul){
//(之后進行向下遞歸得到@0
current_node->left=Generate_(index);
}else{
LOG(FATAL)<

如果當前Token類型是mul或者add. 那么我們需要向下遞歸構建對應的左子節點和右子節點.

例如對于add(@1,@2),再遇到add之后,我們需要先判斷是否存在left bracket, 然后再向下遞歸得到@1, 但是@1所代表的 數字類型,不會再繼續向下遞歸.

當左子樹構建完畢之后,我們將左子樹連接到current_node的left指針中,隨后我們開始構建右子樹.此處描繪的過程體現在current_node->left = Generate_(index);中.

index+=1;
//當前的index指向add(@1,@2)中的逗號
CHECK(indextokens_.size());
//判斷是否是逗號
CHECK(this->tokens_.at(index).token_type==TokenType::TokenComma);

index+=1;
CHECK(indextokens_.size());
//current_node->right=Generate_(index);構建右子樹
constautoright_token=this->tokens_.at(index);
if(right_token.token_type==TokenType::TokenInputNumber
||right_token.token_type==TokenType::TokenAdd||right_token.token_type==TokenType::TokenMul){
current_node->right=Generate_(index);
}else{
LOG(FATAL)<tokens_.size());
CHECK(this->tokens_.at(index).token_type==TokenType::TokenRightBracket);
returncurrent_node;

例如對于add(@1,@2),index當前指向逗號的位置,所以我們需要先判斷是否存在comma, 隨后開始構建右子樹.右子樹中的向下遞歸分析中得到了@2. 當右子樹構建完畢后,我們將它(Generate_返回的節點,此處返回的是一個葉子節點,其中的數據是@2) 放到current_node的right指針中.

串聯起來的例子

簡單來說,我們復盤一下add(@0,@1)這個例子.輸入到Generate_函數中, 是一個token數組.

add

(

@0

,

@1

)

Generate_數組首先檢查第一個輸入是否為add,mul或者是input number中的一種.

CHECK(current_token.token_type==TokenType::TokenInputNumber||
current_token.token_type==TokenType::TokenAdd||current_token.token_type==TokenType::TokenMul);

第一個輸入add,所以我們需要判斷其后是否是left bracket來判斷合法性, 如果合法則構建左子樹.

elseif(current_token.token_type==TokenType::TokenMul||current_token.token_type==TokenType::TokenAdd){
std::shared_ptrcurrent_node=std::make_shared();
current_node->num_index=-int(current_token.token_type);

index+=1;
CHECK(indextokens_.size());
CHECK(this->tokens_.at(index).token_type==TokenType::TokenLeftBracket);

index+=1;
CHECK(indextokens_.size());
constautoleft_token=this->tokens_.at(index);

if(left_token.token_type==TokenType::TokenInputNumber
||left_token.token_type==TokenType::TokenAdd||left_token.token_type==TokenType::TokenMul){
current_node->left=Generate_(index);
}

處理下一個token, 構建左子樹.

if(current_token.token_type==TokenType::TokenInputNumber){
uint32_tstart_pos=current_token.start_pos+1;
uint32_tend_pos=current_token.end_pos;
CHECK(end_pos>start_pos);
CHECK(end_pos<=?this->statement_.length());
conststd::string&str_number=
std::string(this->statement_.begin()+start_pos,this->statement_.begin()+end_pos);
returnstd::make_shared(std::stoi(str_number),nullptr,nullptr);

}

遞歸進入左子樹后,判斷是TokenType::TokenInputNumber則返回一個新的TokenNode到add token成為左子樹.

檢查下一個token是否為逗號,也就是在add(@0,@1)的@0是否為,

CHECK(this->tokens_.at(index).token_type==TokenType::TokenComma);

index+=1;
CHECK(indextokens_.size());

下一步是構建add token的右子樹

index+=1;
CHECK(indextokens_.size());
constautoright_token=this->tokens_.at(index);
if(right_token.token_type==TokenType::TokenInputNumber
||right_token.token_type==TokenType::TokenAdd||right_token.token_type==TokenType::TokenMul){
current_node->right=Generate_(index);
}else{
LOG(FATAL)<tokens_.size());
CHECK(this->tokens_.at(index).token_type==TokenType::TokenRightBracket);
returncurrent_node;
current_node->right=Generate_(index);///構建add(@0,@1)中的右子樹

Generate_(index)遞歸進入后遇到的token是@1 token,因為是Input Number類型所在構造TokenNode后返回.

if(current_token.token_type==TokenType::TokenInputNumber){
uint32_tstart_pos=current_token.start_pos+1;
uint32_tend_pos=current_token.end_pos;
CHECK(end_pos>start_pos);
CHECK(end_pos<=?this->statement_.length());
conststd::string&str_number=
std::string(this->statement_.begin()+start_pos,this->statement_.begin()+end_pos);
returnstd::make_shared(std::stoi(str_number),nullptr,nullptr);

}

至此, add語句的抽象語法樹構建完成.

structTokenNode{
int32_tnum_index=-1;
std::shared_ptrleft=nullptr;
std::shared_ptrright=nullptr;
TokenNode(int32_tnum_index,std::shared_ptrleft,std::shared_ptrright);
TokenNode()=default;
};

在上述結構中, left存放的是@0表示的節點, right存放的是@1表示的節點.

一個復雜點的例子

我們提出這個例子是為了讓同學更加透徹的理解Expression Layer, 我們舉一個復雜點的例子:

add(mul(@0,@1),@2),我們將以人工分析的方式去還原詞法和語法分析的過程.

例子中的詞法分析

我們將以上的這個輸入劃分為多個token,多個token分別為

add | left bracket| |mul|left bracket|@0|comma|@1|right bracket| @2 |right bracket

例子中的語法分析

在ExpressionParser::Generate_函數對例子add(mul(@0,@1),@2),如下的列表為token 數組.

add

(

mul

(

@0

,

@1

)

,

@2

)

index = 0, 當前遇到的token為add, 調用層為1

index = 1, 根據以上的流程,我們期待add token之后的token為left bracket, 否則就報錯. 調用層為1

**開始遞歸調用,構建add的左子樹.**從層1進入層2

index = 2, 遇到了mul token. 調用層為2.

index = 3, 根據以上的流程,我們期待mul token之后的token是第二個left bracket. 調用層為2.

開始遞歸調用用來構建mul token的左子樹.

index = 4, 遇到@0,進入遞歸調用,進入層3, 但是因為操作數都是葉子節點,構建好之后就直接返回了,得到mul token的左子節點.放在mul token的left 指針上.

index = 5, 我們希望遇到一個逗號,否則就報錯mul(@0,@1)中中間的逗號.調用層為2.

index = 6, 遇到@2,進入遞歸調用,進入層3, 但是因為操作數是葉子節點, 構建好之后就直接返回到2,得到mul token的右子節點.

index = 7, 我們希望遇到一個右括號,就是mul(@1,@2)中的右括號.調用層為2.

到現在為止mul token已經構建完畢,返回形成add token的左子節點,add token的left指針指向構建完畢的mul樹. 返回到調用層1.

...

add token開始構建right token,但是因為@2是一個輸入操作數,所以直接遞歸就返回了,至此得到add的右子樹,并用right指針指向.

所以構建好的抽象語法樹如圖:

1d2c6584-ad17-11ed-bfe3-dac502259ad0.png

實驗部分

需要完成test/tet_expression.cpp下的expression3函數

TEST(test_expression,expression3){
usingnamespacekuiper_infer;
conststd::string&statement="add(@0,div(@1,@2))";
ExpressionParserparser(statement);
constauto&node_tokens=parser.Generate();
ShowNodes(node_tokens);
}
staticvoidShowNodes(conststd::shared_ptr&node){
if(!node){
return;
}
ShowNodes(node->left);
if(node->num_indexnum_index==-int(kuiper_infer::TokenAdd)){
LOG(INFO)<num_index==-int(kuiper_infer::TokenMul)){
LOG(INFO)<num_index;
}
ShowNodes(node->right);
}

TEST(test_expression,expression1){
usingnamespacekuiper_infer;
conststd::string&statement="add(mul(@0,@1),@2)";
ExpressionParserparser(statement);
constauto&node_tokens=parser.Generate();
ShowNodes(node_tokens);
}

最后會打印抽象語法樹的中序遍歷:

Couldnotcreateloggingfile:Nosuchfileordirectory
COULDNOTCREATEALOGGINGFILE20230115-223854.21496!I202301152254.86322621496test_main.cpp:13]Starttest...
I202301152254.86348021496test_expression.cpp:23]NUM:0
I202301152254.86348821496test_expression.cpp:20]MUL
I202301152254.86349221496test_expression.cpp:23]NUM:1
I202301152254.86349721496test_expression.cpp:18]ADD
I202301152254.86350221496test_expression.cpp:23]NUM:2

如果語句是一個更復雜的表達式 add(mul(@0,@1),mul(@2,@3))

1d3b036e-ad17-11ed-bfe3-dac502259ad0.png

我們的單元測試輸出為:

I202301152222.08662723767test_expression.cpp:23]NUM:0
I202301152222.08663523767test_expression.cpp:20]MUL
I202301152222.08663923767test_expression.cpp:23]NUM:1
I202301152222.08664423767test_expression.cpp:18]ADD
I202301152222.08664923767test_expression.cpp:23]NUM:2
I202301152222.08665323767test_expression.cpp:20]MUL
I202301152222.08665823767test_expression.cpp:23]NUM:3





審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 編程語法
    +關注

    關注

    0

    文章

    7

    瀏覽量

    6968
  • 深度學習
    +關注

    關注

    73

    文章

    5550

    瀏覽量

    122376
  • Index
    +關注

    關注

    0

    文章

    5

    瀏覽量

    3803

原文標題:自制深度學習推理框架-第7節-計算圖中的表達式

文章出處:【微信號:GiantPandaCV,微信公眾號:GiantPandaCV】歡迎添加關注!文章轉載請注明出處。

收藏 0人收藏

    評論

    相關推薦
    熱點推薦

    教您快速學習python程序設計中正則表達式的運用

    正則表達式:通用的字符串表達框架;簡潔表達一組字符串的表達式;針對字符串表達“簡潔”和“特征”思
    的頭像 發表于 11-21 08:10 ?5255次閱讀
    教您快速<b class='flag-5'>學習</b>python程序設計中正則<b class='flag-5'>表達式</b>的運用

    什么是正則表達式?正則表達式如何工作?哪些語法規則適用正則表達式

    正則表達式又稱規則表達式(Regular Expression,在代碼中常簡寫為 regex、regexp 或 RE),是一種用于匹配、查找、替換文本的強大工具。它能夠以特定的模式匹配字符串,從而
    的頭像 發表于 11-03 14:41 ?4900次閱讀
    什么是正則<b class='flag-5'>表達式</b>?正則<b class='flag-5'>表達式</b>如何工作?哪些語法規則適用正則<b class='flag-5'>表達式</b>?

    前面板輸入公式的表達式計算vi

    公式節點只能在程序框圖中使用,不方便在前面板進行改動,假期無聊,于是自己自制一個前面板表達式節點vi,分享之。
    發表于 11-05 15:53

    表達式節點

    labview我用表達式節點求sin(x),跟計算器結果不同,是出自哪里的問題?求解
    發表于 03-25 10:30

    shell正則表達式學習

    正則表達式計算機科學中,是指一個用來描述或者匹配一系列符合某個句法規則的字符串的單個字符串。在很多文本編輯器或其他工具里,正則表達式通常被用來檢索和/或替換那些符合某個模式的文本內容。許多
    發表于 07-25 17:18

    請問labview如何計算字符串的正表達式

    labview如何計算字符串的正表達式,如:SHORT_OK(65535 Kohm,1000 Kohm):pass,他的正表達式是什么,是怎么計算的,怎么分析出來的,有相關資料嗎?
    發表于 01-06 22:16

    防范表達式的失控

    在C 語言中,表達式是最重要的組成部分之一,幾乎所有的代碼都由表達式構成。表達式的使用如此廣泛,讀者也許會產生這樣的疑問,像+ 、- 、3 、/ 、& & 這樣簡單的運算也會出現
    發表于 04-22 16:57 ?13次下載

    正則表達式學習心得

    正則表達式學習心得
    發表于 10-30 08:41 ?8次下載
    正則<b class='flag-5'>表達式</b><b class='flag-5'>學習</b>心得

    Python正則表達式學習指南

    本文介紹了Python對于正則表達式的支持,包括正則表達式基礎以及Python正則表達式標準庫的完整介紹及使用示例。本文的內容不包括如何編寫高效的正則表達式、如何優化正則
    發表于 09-15 08:00 ?0次下載
    Python正則<b class='flag-5'>表達式</b>的<b class='flag-5'>學習</b>指南

    Python正則表達式指南

    本文介紹了Python對于正則表達式的支持,包括正則表達式基礎以及Python正則表達式標準庫的完整介紹及使用示例。本文的內容不包括如何編寫高效的正則表達式、如何優化正則
    發表于 03-26 09:13 ?10次下載
    Python正則<b class='flag-5'>表達式</b>指南

    Lambda表達式詳解

    C++11中的Lambda表達式用于 **定義并創建匿名的函數對象** ,以簡化編程工作。下面看一下Lambda表達式的基本構成。
    的頭像 發表于 02-09 11:28 ?1507次閱讀

    表達式與邏輯門之間的關系

    邏輯表達式是指表示一個表示邏輯運算關系的式子,是一個抽象的類似數學表達式,下面我們重點說明下其表達式與邏輯門之間的關系。
    的頭像 發表于 02-15 14:54 ?2008次閱讀
    <b class='flag-5'>表達式</b>與邏輯門之間的關系

    C語言的表達式

    在C語言中,表達式是由操作符和操作數組成。表達式可以由一個或者多個操作數組成,不同的操作符與操作數組成不同的表達式,因此,表達式才是C語言的基本。
    的頭像 發表于 02-21 15:09 ?1733次閱讀
    C語言的<b class='flag-5'>表達式</b>

    一文詳解Verilog表達式

    表達式由操作符和操作數構成,其目的是根據操作符的意義得到一個計算結果。表達式可以在出現數值的任何地方使用。
    的頭像 發表于 05-29 16:23 ?3116次閱讀
    一文詳解Verilog<b class='flag-5'>表達式</b>

    zabbix觸發器表達式 基本RS觸發器表達式 rs觸發器的邏輯表達式

    zabbix觸發器表達式 基本RS觸發器表達式 rs觸發器的邏輯表達式? Zabbix是一款開源的監控軟件,它能通過監控指標來實時監測服務器和網絡的運行狀態,同時還能提供警報和報告等功能來幫助管理員
    的頭像 發表于 08-24 15:50 ?1840次閱讀
    主站蜘蛛池模板: 9国产露脸精品国产麻豆 | 伊人久久精品线影院 | 国内精品久久久久影院亚洲 | 少妇性饥渴BBBBBBBBB | 国产麻豆精品人妻无码A片 国产麻豆精品久久一二三 国产麻豆精品传媒AV国产在线 | 寂寞夜晚视频在线观看 | 欧美特级特黄a大片免费 | 97国产在线观看 | 久久久久琪琪精品色 | 鲁一鲁亚洲无线码 | 久久高清免费视频 | 99精品视频免费观看 | 午夜家庭影院 | 国产成人精品免费视频软件 | 亚洲蜜桃AV永久无码精品放毛片 | 国产嫩草影院精品免费网址 | 久久99国产精品一区二区 | 国产电影一区二区三区 | 精品国产午夜福利在线观看蜜月 | 长篇高h肉爽文丝袜 | 日本xxxx裸体xxxx | 国产传媒麻豆剧精品AV | 欧美黑人经典片免费观看 | 久久精品亚洲精品国产欧美 | 蜜臀AV久久国产午夜福利软件 | 免费人成网站在线观看10分钟 | 同时和两老师双飞 | 午夜在线观看免费观看 视频 | 男男腐文污高干嗯啊快点1V1 | 两个人的视频免费 | japanese幼儿videos| 果冻传媒完整免费网站在线观看 | 国产精品久久久久久久久爆乳 | 九九在线精品亚洲国产 | gogo亚洲肉体艺术照片9090 | 无码国产成人777爽死 | 久久艹综合 | 毛片999| 思思久99久女女精品 | 国产高清亚洲日韩字幕一区 | 亚洲国语在线视频手机在线 |

    電子發燒友

    中國電子工程師最喜歡的網站

    • 2931785位工程師會員交流學習
    • 獲取您個性化的科技前沿技術信息
    • 參加活動獲取豐厚的禮品