1、作為安全從業者,以前搞逆向、挖漏洞、幹滲透全靠人工推進,缺點很明顯:
- 無法自動化,甚至也無法半自動化,效率低(後續可以開發agent解決)
- 知識面有限,存在很多知識盲點,導致遇到部分問題無法解決(可以透過增加知識庫,然後rag檢索或微調大模型解決)
嘗試了一些線上的大模型(chatGPT4、copilot),挖掘 https://www.cnblogs.com/theseventhson/p/13933230.html 這個例子的棧溢位漏洞,效果還行, 能找到漏洞;離線部署的LLM中嘗試了secGPT、openbuddy等,同樣的程式碼也能準確找到漏洞所在,比如:
說明基於現有大資料訓練得到的模型是能夠發現並解決已知問題的,這條路別人已經走通了!
2、 訓練語料
(1)眾所周知,機器學習分監督學習於非監督學習。不論哪種,訓練資料都是必須的,所以先整理一下我自己的部落格園技術總結:
huggingface上大模型sft微調一般的資料格式為:
{ "instruction": "Please perform static application security testing on the above code", "input": "static void goodG2B1()\n{\n int data;\n /* Initialize data */\n data = -1;\n if(0)\n {\n /* INCIDENTAL: CWE 561 Dead Code, the code below will never run */\n printLine(\"Benign, fixed string\");\n }\n else\n {\n /* FIX: Set data to a relatively small number greater than zero */\n data = 20;\n }\n {\n size_t i;\n int *intPointer;\n /* POTENTIAL FLAW: if data * sizeof(int) > SIZE_MAX, overflows to a small value\n * so that the for loop doing the initialization causes a buffer overflow */\n intPointer = (int*)malloc(data * sizeof(int));\n if (intPointer == NULL) {exit(-1);}\n for (i = 0; i < (size_t)data; i++)\n {\n intPointer[i] = 0; /* Potentially writes beyond the boundary of intPointer */\n }\n printIntLine(intPointer[0]);\n free(intPointer);\n }\n}\n", "output": "this code does not violate any security encoding standards" }
這類資料需要標註,耗時耗力,而部落格園的技術文章都是沒有標註的,所以sft暫時不考慮!如果非要把部落格園的文章搞成instrct格式,可以嘗試把文章內容作為input,標題或主要內容作為output,instruction改成自己關注的問題就好!
(2)我因為沒時間標註,直接把文章內容挨個放入json檔案;為了減少訓練時記憶體佔用,也為了GPT2的max_length=1024的限制,把文章按照1024的長度截斷,分別存入json檔案;一共準備了19個json檔案;
用於微調的樣本資料:
3、模型選擇:huggingface上模型已多大66w,這麼多模型怎麼選合適的?
(1)模型引數量:N = l * 12* d^2; l是transformer block的個數;d 是 embedding的維度;
(2)模型總的計算量:C=6*ND; N是模型引數量,D是訓練集的token總數
(3)模型BP需要儲存::16byte*N + FFN(d、樣本length、batch_size等)
由於token數量只有200w,如果選擇引數大的模型,肯定欠擬合(參考scaling laws,我這點token量都達不到人家嘗試資料量的下限),所以只能選擇引數小的模型;這裡最終選擇GPT2嘗試!
4、微調的方式有很多種,這裡選擇截至目前最優的lora嘗試:
1 import logging 2 import torch 3 from transformers import GPT2Tokenizer, GPT2LMHeadModel, Trainer, TrainingArguments 4 from datasets import load_dataset 5 from peft import get_peft_model, LoraConfig, TaskType 6 import os 7 8 logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO) 9 10 11 def preprocess_function(examples): 12 inputs = tokenizer(examples["text"], truncation=True, padding="max_length", max_length=1024) 13 inputs["labels"] = inputs["input_ids"].copy() 14 return inputs 15 16 if __name__ == '__main__': 17 18 model_name = "gpt2" 19 tokenizer = GPT2Tokenizer.from_pretrained(model_name) 20 model = GPT2LMHeadModel.from_pretrained(model_name) 21 logging.info("Model loaded successfully") 22 23 24 tokenizer.pad_token = tokenizer.eos_token 25 26 27 training_args = TrainingArguments( 28 output_dir="/root/huggingface/GPT2/Lora", 29 overwrite_output_dir=True, 30 num_train_epochs=1, 31 per_device_train_batch_size=20, 32 save_steps=10_000, 33 save_total_limit=2, 34 logging_dir="/root/huggingface/GPT2/logs", 35 save_strategy="epoch", 36 ) 37 38 lora_config = LoraConfig( 39 task_type=TaskType.CAUSAL_LM, 40 r=16, 41 lora_alpha=32, 42 lora_dropout=0.1, 43 target_modules=["attn.c_proj", "attn.c_attn"] 44 ) 45 46 47 model = get_peft_model(model, lora_config) 48 model.print_trainable_parameters() # 列印可訓練引數 49 50 last_checkpoint = None 51 checkpoint_prefix = "checkpoint" 52 53 # 檢查是否存在之前的檢查點 54 for i in range(19, 0, -1): 55 checkpoint_dir = f"/root/huggingface/GPT2/{checkpoint_prefix}-{i}" 56 if os.path.exists(checkpoint_dir): 57 last_checkpoint = checkpoint_dir 58 logging.info(f"last_checkpoint:{last_checkpoint}") 59 break 60 61 logging.info(f"last_checkpoint:{last_checkpoint}") 62 # 從上一個檢查點繼續訓練 63 start_file_index = 1 if last_checkpoint is None else int(last_checkpoint.split('-')[-1]) + 1 64 65 # 遍歷每個資料檔案並進行訓練 66 for i in range(start_file_index, 20): 67 data_file = f'/root/huggingface/data/cyber_security{i}.json' 68 dataset = load_dataset('json', data_files=data_file, split='train') 69 logging.info(f"Dataset {data_file} loaded successfully") 70 print(f"Dataset size: {len(dataset)}") 71 tokenized_dataset = dataset.map(preprocess_function, batched=True) 72 print(f"Tokenized dataset size: {len(tokenized_dataset)}") 73 74 # 建立Trainer物件 75 trainer = Trainer( 76 model=model, 77 args=training_args, 78 train_dataset=tokenized_dataset, 79 ) 80 81 logging.info(f"Before training on {data_file}") 82 83 if last_checkpoint: 84 logging.info(f"Loading from checkpoint {last_checkpoint}") 85 trainer.train(resume_from_checkpoint=last_checkpoint) 86 last_checkpoint = None # 只在第一次載入檢查點 87 else: 88 # 訓練模型 89 trainer.train() 90 91 logging.info(f"After training on {data_file}") 92 93 # 儲存中間模型和檢查點 94 model.save_pretrained(f"/root/huggingface/GPT2/fine-tuned-model-{i}") 95 tokenizer.save_pretrained(f"/root/huggingface/GPT2/fine-tuned-model-{i}") 96 trainer.save_model(output_dir=f"/root/huggingface/GPT2/{checkpoint_prefix}-{i}") 97 logging.info(f"Model saved successfully after training on {data_file}") 98 99 # 最終儲存模型 100 model.save_pretrained("/root/huggingface/GPT2/fine-tuned-model-final") 101 tokenizer.save_pretrained("/root/huggingface/GPT2/fine-tuned-model-final") 102 logging.info("Final model saved successfully")
為了測試不同的引數的效果,我這裡分別使用了r=8和r=16兩個引數分別測試:
r=8的trainable:
cross entropy的loss:
r=16的trainable:
cross entropy的loss:
微調完成後推理:中文效果非常差,感覺完全沒理解語義
英語效果也好不到哪去:
原因:
- GPT2訓練的中文語料少,對中文支援不足
- 微調的語料太少,每批訓練語料的epoch也只有1,loss最小也在3;
總結:
1、transformer架構,基礎的block塊核心由兩部分構成:attention和FFN;FFN就是深度神經網路,十幾年前就有了,沒啥新的,所以attention是較大創新;
- 在attention之前的embedding加上了位置編碼,完美記錄了token的位置,更好地表達語義;
- attention本身透過q和k的向量內積提取token之間的相似度,找到每個token重要的context
- q*k的乘積作為權重調整v的值,更好地表達token在當前context中的值;
比如“apple”這個token,初始的embedding都是一樣的,無法區分是水果的apple,還是電子產品的apple,只能根據不同的context確定apple這個詞的語義;如果apple周圍出現大量的pear、bananer、peach等,大機率指的是水果apple,理論上講multihead中水果類head轉換的v值會被q*k的內積增加,而電子產品類head轉換的v值會被q*k的內積減小;所以,attention最核心的功能:根據context調整token的v值,讓同一token在不同的context有不同的v值,更利於後續進一步的語義理解!目的感覺和網際網路搜廣推的“千人千面”很像啊!所以使用模型時處理token長度的max_length值不能太小(普通問答1024夠了,專業的技術文章建議至少10240),否則提取的context資訊有限,影響token v值的正確調整!
2、transformer每個block都幹同樣的事:
- attention:根據context調整token的v值
- FFN:空間轉換提取特徵
理論上講:只要訓練語料足夠多,block數量足夠多,經過層層變換,總能精準得到每個token的v值和語義資訊,所以transformer的核心是“大力出奇跡”!
附:
1、知識庫rag和微調的優劣對比: