内容
- 概述
- 1. 如何在交易中運用該方法
- 2. FP-Growth 算法實作
- 2.1. 樹的節點類實作
- 2.2. 關聯規則挖掘類的實作
- 3. 測試
- 結束語
- 參考文獻清單
- 本文中用到的程式
概述
在前一篇文章中,我們開始學習隸屬無監督學習方法的關聯規則挖掘算法。 我們研究了兩種算法來解決這類問題:Apriori 和 FP-Growth。 Apriori 算法的瓶頸在于其大量資料庫的調用,旨在判定對于頻繁形态候選者的支援度。 FP-Growth 方法則通過建構包含整個資料庫的樹來解決這個問題。 所有更進一步的操作都是依 FP 樹執行的,無需通路資料庫。 由于 FP 樹位于記憶體當中,這提高了問題解決速度。通路它比完整疊代資料庫要快捷得多。
1. 如何在交易中運用該方法
在繼續利用 MQL5 建構關聯規則挖掘算法之前,我們來研究一下如何在交易中運用它們。
建立關聯規則挖掘算法來搜尋資料庫中二進制特征之間的穩定依賴關系。 是以,這些算法可用來尋找各種特征之間的穩定關系。 這可以是由多個名額和/或工具組成的各種形态。 算法不關心每個單獨的特征是否代表不同的度量,或者它是不同時間段中相同度量的值。 算法評估時把每個特征均視為獨立的。 是以,我們可以嘗試将此算法與監督學習方法的開發結合起來。 我們在曆史資料的訓練樣本中添加一些目标特性。 算法應該搜尋關聯規則,這将導緻我們的目标值形成。
我們已有了一個算法,和如何運用它來解決實際問題的思路。 我們來看看如何利用 MQL5 實作它。 然後我們将在實踐中檢測這個思路。
2. FP-Growth 算法實作
為了實作上一篇文章中研究的 FP-Growth 算法,我們要記住其構造是基于決策樹的。 MQL5 标準庫擁有建構二叉樹的 CTree 類。 不幸的是,二叉樹選項對我們來說并不完全友善,因為一棵 FP 樹的一個節點的分支數可以超過 2 個(二進制實作中的最大可用分支數)。 是以,在建構算法本身之前,我們先建立 CMyTreeNode 類來實作擁有多個分支的樹節點。
2.1. 樹的節點類實作
該類将從動态對象數組 CArrayObj 的标準 MQL5 類派生而來。 這個類之是以被選為父類,是因為它擁有與建立和維護動态對象數組相關的所需功能,在我們的例子中,分支節點其實就是動态對象數組。
此外,為了實作算法所需的功能,類中已加入了三個新變量:
- m_cParent — 指向上一個父節點對象的指針。 對應樹根,它将為空
- m_iIndex — 源資料庫中特征的索引;用于識别特征
- m_dSupport — 接收特征支援度值的變量
class CMyTreeNode : public CArrayObj
{
protected:
CMyTreeNode *m_cParent;
ulong m_iIndex;
double m_dSupport;
public:
CMyTreeNode();
~CMyTreeNode();
}
在類構造函數中,設定變量的初始值,并清除動态數組。 将類析構函數保留為空。
CMyTreeNode::CMyTreeNode() : m_iIndex(ULONG_MAX),
m_dSupport(0)
{
Clear();
}
為了操控隐藏的類變量,我們将建立一些方法,這些方法将在 FP-Growth 算法的建立過程中用到。 我還會提供方法目的解釋及其用途。
class CMyTreeNode : public CArrayObj
{
........
public:
........
//--- methods of access to protected data
CMyTreeNode* Parent(void) const { return(m_cParent); }
void Parent(CMyTreeNode *node) { m_cParent = node; }
void IncreaseSupport(double support) { m_dSupport += support; }
double GetSupport(void) { return m_dSupport; }
void SetSupport(double support) { m_dSupport = support; }
ulong ID(void) { return m_iIndex; }
void ID(ulong ID) { m_iIndex = ID; }
};
為了計算置信水準,我們建立 GetConfidence 方法。 在其中,我們首先檢查指向前置節點的指針,如果它有效,則将目前節點支援度除以父節點支援度。
注意,FP 樹建構算法的組織方式是,任何節點的支援度都不能大于父節點的支援度。 是以,方法操作的結果将始終為正值,并且不會大于 1。
由于樹節點是基于現有業務添加的,是以我們沒有除零檢查。 是以,如果一個節點在樹中,那麼它的特征在資料庫中至少出現過一次,且它的支援度最低。
double CMyTreeNode::GetConfidence(void)
{
CMyTreeNode *parent = Parent();
if(!parent)
return 1;
//---
double result = m_dSupport / parent.GetSupport();
return result;
}
此外,我們添加了一個建立新分支節點的 AddNode 方法。 在方法參數中,我們傳遞訓練樣本源資料庫中的特征 ID,和節點的支援度。 該方法傳回指向所建立對象的指針。
在方法實體中,我們建立樹節點的新執行個體,并立即檢查操作結果。 如果發生錯誤,則傳回無效的對象指針。
接下來,我們指定所建立節點的 ID,并将指向目前對象的指針傳遞給它,作為父對象。
将新對象添加到目前節點的動态數組中,并檢查操作結果。 如果往數組中添加對象時出錯,則删除所建立對象,并退出方法,同時傳回無效指針。
在方法結束時,将參數中指定的支援度儲存到新對象當中,然後退出該方法。
CMyTreeNode *CMyTreeNode::AddNode(const ulong ID, double support = 0)
{
CMyTreeNode *node = new CMyTreeNode();
if(!node)
return node;
node.ID(ID);
if(!Add(node))
{
delete node;
return node;
}
node.Parent(GetPointer(this));
//---
if(support > 0)
node.SetSupport(support);
return node;
}
一旦我們建立了一個新對象,我們應該能夠删除它。 父類中已存在依據動态數組中的索引删除對象的方法。 為了擴充功能,我們建立 DeleteNode 方法,并按功能 ID 删除節點。
該方法接收要删除的功能 ID,并傳回操作的布爾結果。
在方法實體中,實作循環以在目前節點的動态數組中查找比對指定 ID 的節點。 循環将疊代周遊從 0 到 m_data_total 變量值範圍内的元素。 該變量包含動态數組的活動元素數量,并由父類管控。
在方法實體中,從動态數組中提取下一個元素,并驗證指針。 通過調用父類的 Delete 方法(含有指向欲删除元素的索引),可以立即删除含有無效指針的元素。
注意,Delete 方法傳回操作的布爾結果。 如果成功地從動态數組中删除了一個元素,則減少循環疊代的計數器,并移動到下一個數組。 我們隻減少循環疊代計數器,而不更改 m_data_total 變量值。 這是因為它的值在 Delete 父類的方法中已經更改。
如果在從動态數組中删除無效元素時發生錯誤,隻需移動到數組的下一個元素即可。 我們不會終止方法時傳回 false 結果,因為方法任務不是從無效對象中清除動态數組。 這隻是一個輔助功能。 該方法的主要任務是删除特定元素。 是以,我們繼續執行方法,直至發現所需的元素。
當找到動态數組的所需元素後,調用前面提到的父類的 Delete 方法。 這一次,我們在退出方法的同時傳回對象删除結果。
如果疊代周遊動态數組的所有元素後找不到該元素,請采用 false 結果退出該方法。
bool CMyTreeNode::DeleteNode(const ulong ID)
{
for(int i = 0; i < m_data_total; i++)
{
CMyTreeNode *temp = m_data[i];
if(!temp)
{
if(!Delete(i))
continue;
return DeleteNode(ID);
}
if(temp.ID() != ID)
continue;
return Delete(i);
}
//---
return false;
}
進一步讨論樹節點 CMyTreeNode 新類的有關方法,我想提請您注意 Mining 方法。 此方法負責在 FP 樹中查找我們正在分析的特征的路徑。 在我們繼續講述方法的算法之前,我必須說,它在建立時已考慮到交易中的預期用途。 是以,它稍微偏離了基本算法。
首先,我們不會判定所有特性的關聯規則,而隻為目标值判定關聯規則。 是以,在建構規則樹時,我們很可能會遇到這樣的情況,即所需的特征不是葉子,而是包含路徑中後續元素的節點。 但我們不能忽略後續節點,因為它們會增加目标結果的可能性。 是以,在選擇所分析特征的路徑時,應考慮它們。
構造此方法時,我曾注意的另一點如下。 根據該算法,我們首先需要在 FP 樹中找到所分析特征的所有路徑。 然後,我們可以計算標明路徑中每個特征的支援度值。 我決定在一個方法中執行這兩個子任務。
請注意,為了建構 FP 樹,僅計劃采用 CMyTreeNode 類執行個體。 是以,為了執行深度優先搜尋,我們将采用遞歸方法調用。
現在,我們看看在類的 Mining 方法中如何實作這些任務。 在方法參數中,我們傳遞指向一個向量的指針,該向量是為了寫入元素支援度值,一個為了寫入路徑的矩陣,所分析特征的辨別符,以及最小置信級别。 該方法将傳回操作的布爾結果。
在方法實體中,首先檢查欲分析節點是否是期望的特征。 為此,取節點 ID,并與參數中接收到的期望節點 ID 進行比較。 如果它們相等,則檢查目前分支中節點的置信級别。 該級别是調用前面研究的 GetConfidence 方法來判定的。 置信水準不得小于允許的最小值。 否則,以 true 結果退出方法。
bool CMyTreeNode::Mining(vector &supports, matrix &paths, const ulong ID, double min_conf)
{
if(ID == m_iIndex)
if(GetConfidence() < min_conf)
return true;
下一個子產品實作全樹的進一步深度搜尋。 在此,首先将目前節點的支援度值儲存到一個局部變量當中。 然後,運作一個循環,從目前節點到全樹深度周遊所有分支。 該方法遞歸調用搜尋所有分支。
注意,采用遞歸方法,我們隻傳遞期望的辨別符,直到找到相應的節點。 此後,我們将 ULONG_MAX 常量傳遞到全樹深度,替代期望的辨別符。 這是因為由于 FP 樹構造的特殊性,在我們找到期望項的路徑之前,形态置信度可能小于 100%。 随着我們沿着這條路徑繼續前進,期望特征的機率将是 100%。 否則,我們将建構一條不同的路徑,繞過期望的節點。
當然,當我們采用自定義算法時,這種情況就被排除在外。 當判定所有特征的規則時,我們開始處理 FP 樹中任何節點的時候,它将是一個沒有後續節點的葉片。 這是因為所有支援度較低的特征都将被處理,并從樹中删除。
是以,當我們偏離算法時,我們必須評估該變化對整個過程的影響,并對算法進行相應調整。 在這種情況下,我們必須将包含期望特征的所有路徑添加到清單中。 這是從期望特征到樹根的路徑,以及從任何後續節點路過期望特征至樹根的所有路徑。 為此目的,我們需要通知其它節點,在節點和樹根之間找到了期望的特征。 當期望特征的 ID 更改為 ULONG_MAX 常量時,會出現此類标志。
在遞歸方法得到肯定結果之後,對于下一個節點,我們從循環之前建立的局部變量中減去目前節點的支援度值。 如果下一個節點I D 等于期望 ID,則删除已處理的節點。
double support = m_dSupport;
for(int i = 0; i < m_data_total; i++)
{
CMyTreeNode *temp = m_data[i];
if(!temp)
{
if(Delete(i))
i--;
continue;
}
if(!temp.Mining(supports, paths, (ID == m_iIndex ? ULONG_MAX : ID), min_conf))
return false;
support -= temp.GetSupport();
if(temp.ID() == ID)
if(Delete(i))
i--;
}
您可看到,在前面的子產品中,我們隻針對後續節點遞歸調用了相同的方法,但我們沒有儲存找到的路徑。 儲存動作将在下一個方法子產品中執行。 對于擁有期望屬性的節點和後續節點,将執行該子產品。 為此,我們需要檢查目前節點的 ID,和參數中接收到的 ID。 此外,遞歸方法執行後對目前節點的支援度必須大于 “0”。 此外,目前節點不能是根節點。 這意味着它必須至少有一個前置節點。 這是因為我們需要用到一些東西作為規則的先行詞。
如果控件成功傳遞,則将路徑矩陣的大小增加 1 行,并在添加的行中填入零值。
接下來,實作從目前節點到樹根的擴散循環。 取我們的路徑線中目前節點的剩餘支援度,配置設定給目前和所有前置節點。 此外,在相應項的累積支援度向量中添加相同的值。
父疊代完成後,退出方法,并傳回肯定結果。
if(ID == m_iIndex || ID == ULONG_MAX)
if(support > 0 && !!m_cParent)
{
CMyTreeNode *parent = m_cParent;
ulong row = paths.Rows();
if(!paths.Resize(row + 1, paths.Cols()))
return false;
if(!paths.Row(vector::Zeros(paths.Cols()), row))
return false;
supports[m_iIndex] += support;
while(!!parent)
{
if(parent.ID() != ULONG_MAX)
{
supports[parent.ID()] += support;
paths[row, parent.ID()] = support;
}
parent = parent.Parent();
}
}
//---
return true;
}
我用一個小例子解釋一下這個方法是如何工作的,因為它的構造稍微超出了上面講述的 PF-Growth 算法的範圍。 假設源資料庫内含有以下業務: "AB" 重複兩次,一次 "ABC","ABCD" 重複三次,以及一次 "ABCDE". 結果就是,FP 樹中形成了以下路徑: "A7-B7-C5-D4-E1"。 當分析項目 “C” 時,我們需要從樹中恢複包含此項目的所有路徑。
我們開始先從根元素 “a” 調用一個方法,并指導它去查找 "C"。 在此我們遞歸調用節點的方法 "B"。 繼續直到葉片 "E"。 鑒于 "E" 葉片沒有後續節點,那麼從方法的子產品 2 開始處理,并寫入路徑。 此處,我們首先儲存 "ABCDE" 路徑,并為所有結點寫入支援度 1。 這意味着源資料庫中有 1 條這樣的路徑。 然後退出方法,将控制權轉交給更進階别。
在 "D" 節點級别儲存路徑 "ABCD"。 從 "D" 節點的支援度,減去 "E" 葉片的支援度 (4-1=3)。 為這條路徑上的所有項,登記該結果值作為支援度。 如您所見,這對應于初始資料,其中我們在訓練樣本中有 3 筆相同的業務。 我們用項目支援度值,來替代重複業務三次。
同樣,儲存路徑 “ABC”,支援度等于 1。 路徑 "AB" 未儲存,因為它不包含分析的特征。
在後附的檔案 MyTreeNode.mqh 中查找所有類方法的完整代碼。
2.2. 關聯規則挖掘類的實作
我們繼續建構 FP-Growth 關聯規則挖掘算法。 主要功能将在另一個類 CAssocRules 中實作。 這個類的結構如下所示。 正如您所看到的,大多數方法都隐藏在“引擎蓋下”。 但首要之事依然是首要。
class CAssocRules : public CObject
{
protected:
CMyTreeNode m_cRoot;
CMyTreeNode m_cBuyRules;
CMyTreeNode m_cSellRules;
vector m_vMin;
vector m_vStep;
int m_iSections;
matrix m_mPositions;
matrix m_BuyPositions;
matrix m_SellPositions;
//---
bool NewPath(CMyTreeNode *root, matrix &path);
CMyTreeNode *CheckPath(CMyTreeNode *root, vector &path);
//---
bool PrepareData(matrix &data, matrix &bin_data,
vector &buy, vector &sell,
const int sections = 10, const double min_sup = 0.03);
matrix CreatePath(vector &bin_data, matrix &positions);
matrix CreatePositions(vector &support, const double min_sup = 0.03);
bool GrowsTree(CMyTreeNode *root, matrix &bin_data, matrix &positions);
double Probability(CMyTreeNode *root, vector &data, matrix &positions);
public:
CAssocRules();
~CAssocRules();
//---
bool CreateRules(matrix &data, vector &buy,
vector &sell, int sections = 10,
double min_freq = 0.03,
double min_prob = 0.3);
bool Probability(vector &data, double &buy, double &sell);
//--- methods for working with files
virtual bool Save(const int file_handle);
virtual bool Load(const int file_handle);
virtual bool Save(const string file_name);
virtual bool Load(const string file_name);
};
在類變量中,有上述樹節點的三個執行個體:
- m_cRoot — 我們将用它來記錄我們的 FP 樹
- m_cBuyRules — 這個将被用來記錄購買規則
- m_cSellRules — 這個用于賣出規則
矩陣 m_mPositions、m_BuyPositions 和 m_SellPositions 将包含按降序排序的特征,及其支援度值。
在測試所有之前的模型時,我們檢查了在形态第三根蠟燭形成之前判定分形的可能性。 是以,我将依據兩個規則挖掘樹來定義買入和賣出分形。 如果根據您的問題,您需要為大量的目标特征定義規則,那麼您将需要建立更多的專有規則樹。
例如,您可能有多個目标買入和賣出級别。 不幸的是,關聯規則挖掘算法隻依據二進制表進行操作。 是以,您必須為每個目标級别建立單獨的項,并為其查找關聯規則。 您可用動态數組排除大量專有變量。
構造函數和析構函數留白,因為在這個類中,我沒有使用指向樹建構對象的動态指針。
如上所述,關聯規則挖掘算法僅适用于二進制矩陣。 但有關行情狀況的資料很難歸類。 是以,它們在使用前需要進行預處理。
為了簡化類的進一步使用,該算法不需要預先處理來自使用者的資料。 取而代之,它是在 PrepareData 方法中實作的。 在參數中,該方法接收指向 2 個矩陣、2 個向量和 2 個常量的指針。 一個矩陣包含原始資料,第二個矩陣用于寫入處理後的二進制資料。 矢量則包含目标值。 在我們的案例中,它們本就以二進制資料表示,是以不需要預處理。 需要對源資料進行預處理。
為了将源資料的标量機關轉換為二進制值,我們将取每個特征的數值範圍,并将其劃分為幾個區間。 區間數量由 “sections” 參數設定。 每個特征的最小值和步長将儲存在相應的矢量 m_vMin 和 m_vStep 當中。 在實際運用過程中,矢量将用于将源資料轉換為二進制值。
在此我們來準備二進制矩陣,設定所需的大小,并以零值填充。 我們還可以指定目标特征的辨別符,稍後将其添加到矩陣中的最後一列。
bool CAssocRules::PrepareData(matrix &data,
matrix &bin_data,
vector &buy,
vector &sell,
const int sections = 10,
const double min_sup = 0.03)
{
//---
m_iSections = sections;
m_vMin = data.Min(0);
vector max = data.Max(0);
vector delt = max - m_vMin;
m_vStep = delt / sections + 1e-8;
m_cBuyRules.ID(data.Cols() * m_iSections);
m_cSellRules.ID(m_cBuyRules.ID() + 1);
bin_data = matrix::Zeros(data.Rows(), m_cSellRules.ID() + 1);
接下來,執行循環,周遊輸入資料矩陣的所有行。 在循環實體中,從每行減去最小值的矢量,然後将結果除以步長。 為了排除超出範圍的資料,我們要限制矢量元素的下限和上限值。 作為這些操作的結果,向量的每項中數字的整數部分訓示我們需要将源資料的相應項包含到哪個數值範圍。 我們的二進制矩陣的每個範圍都是一個單獨的特征。
我們運作一個嵌套循環,并填充二進制矩陣的相關行。 如果該特征處于活動狀态,則将其值更改為 “1”。 未激活特征則保留零值。
for(ulong r = 0; r < data.Rows(); r++)
{
vector pos = (data.Row(r) - m_vMin) / m_vStep;
if(!pos.Clip(0, m_iSections - 1))
return false;
for(ulong c = 0; c < pos.Size(); c++)
bin_data[r, c * sections + (int)pos[c]] = 1;
}
if(!bin_data.Col(buy, m_cBuyRules.ID()) ||
!bin_data.Col(sell, m_cSellRules.ID()))
return false;
填充二進制矩陣後,我們可以立即計算每個特征的支援度,并在 CreatePositions 方法中按降序排序。 排序後,以肯定結果退出方法。
vector supp = bin_data.Sum(0) / bin_data.Rows();
m_mPositions = CreatePositions(supp, min_sup);
//---
return true;
}
既然我們提到了 CreatePositions 特征排序方法,我們就來研究一下它的算法。 該方法在參數中接收支援度向量,和最小支援度級别。
方法實體将包含一些準備工作。 這是因為收到的支援度值由向量表示,其中的項目值包含支援度值。 項目的索引訓示特征。 針對矢量項進行簡單排序,我們将失去與源資料特征的聯系。 是以,我們需要建立“ 特特征 id - 支援度” 資料對。 配對資料将儲存到矩陣當中。
為此,首先建立一個辨別矩陣,該矩陣有 2 列,行數等于原始樣本中的特征數量。 然後按列計算項目的累計和,并将結果矩陣的值減少 “1”。 由此,我們得到一個矩陣,其中的列包含從 “0” 開始的升序值,與行索引對應。 我們隻需要用生成的支援度向量替換一列。 是以,我們得到了一個矩陣:每行将包含一個特征辨別符,和一個與之對應的支援度值。
matrix CAssocRules::CreatePositions(vector &support, const double min_sup = 0.03)
{
matrix result = matrix::Ones(support.Size(), 2);
result = result.CumSum(0) - 1;
if(!result.Col(support, 1))
return matrix::Zeros(0, 0);
我們隻需要按支援度降序對矩陣的行進行排序。 為此,采用冒泡排序算法實作一個循環。
bool change = false;
do
{
change = false;
ulong total = result.Rows() - 1;
for(ulong i = 0; i < total; i++)
{
if(result[i, 1] >= result[i + 1, 1])
continue;
if(result.SwapRows(i, i + 1))
change = true;
}
}
while(change);
退出循環後,我們将得到一個擁有特征排序的矩陣。 我們隻需要從該矩陣中删除不滿足最低支援度要求的特征。 為此,找到低于最低支援度級别的第一個特征,并“切斷”低于此級别的所有特征。
int i = 0;
while(result[i, 1] >= min_sup)
i++;
if(!result.Resize(i, 2))
return matrix::Zeros(0, 0);
//---
return result;
}
成功調整矩陣大小之後,退出方法,并傳回結果矩陣。
在繼續探讨公開方法之前,我們再檢視一些方法,其中部分會執行重複功能。 我們需要建立一個路徑,從二進制資料轉換到 FP 樹。 此功能将在 CreatePath 方法中執行。 該方法将接收指向二進制資料矢量和排序已特征矩陣的指針。 然後,它将傳回一個路徑矩陣,其中包含要添加到 FP 樹中的 “特征 id - 支援度” 對。
請注意已排序特征矩陣之間的差異 ,其一是我們在準備資料時獲得的,另一個矩陣是為了添加路徑的 FP 樹。 這兩個矩陣都将包含“特征辨別符 - 支援度” 對。 但第一個矩陣包含源資料中提供的所有特征,及其在訓練樣本中的支援度。 而路徑矩陣僅包含所分析業務中存在的特征,以及二進制矩陣中表示的來自該業務的支援度。
好了,因為我們處理的是二進制矩陣,是以每筆業務中的特征支援度值必須相同。 稍後,我們将采用相同的方法來建構專有規則樹。 早前,我們在講述 CMyTreeNode::Mining 方法時研究過一個示例。 我們在該示例中多次采用支援度級别,取代重複一條路徑。 是以,為了統一操作,我們将在 2 個子流程中調用 1 個方法。 在這種情況下,引入支援度級别将非常有用。
在該方法伊始,我們将源資料向量的大小和所分析特征的數量儲存在局部變量之中,它小于由支援度低于最小值的随機特征構成的源資料向量大小。
此外,我們還準備了一個矩陣來寫出結果。 它不能大于分析特征矩陣。 此外,我們還引入了一個變量來訓示路徑的尺寸。 在這個階段,它等于 “0”。
接下來,我們按照支援度的降序循環周遊所有已分析特性。 在循環體中,我們提取下一個選中特征的辨別符。 檢查二進制源資料矢量中的值。 如果該特征未激活,則轉到下一個特征。
如果特征處于活動狀态,則将二進制源資料矢量中的特征 id 及其支援度添加到路徑矩陣之中。 之後,我們增加路徑尺寸變量。
退出循環後,将路徑矩陣的尺寸減至已填充元素的數量,然後退出方法。
matrix CAssocRules::CreatePath(vector &bin_data, matrix &positions)
{
ulong size = bin_data.Size();
//---
ulong total = positions.Rows();
int vect_pos = 0;
matrix path = matrix::Zeros(2, total);
for(ulong c = 0; c < total; c++)
{
ulong pos = (ulong)positions[c, 0];
if(pos >= size)
continue;
if(bin_data[pos] == 0)
continue;
path[0, vect_pos] = (double)pos;
path[1, vect_pos] = bin_data[pos];
vect_pos++;
}
if(!path.Resize(2, vect_pos))
return matrix::Zeros(0, 0);
//---
return path;
}
另一種我們需要的方法是将路徑添加到 FP 樹:NewPath。 該方法接收指向樹根節點和先前所建立路徑矩陣的指針。 然後,它将傳回操作的布爾結果。 在方法體中,我們首先檢查結果路徑的尺寸。 它應該大于 0。 然後,我們增加對根節點的支援度,并運作循環周遊路徑的所有項。
在循環體中,檢查是否存在含有所需 ID 的下一個節點,如有必要,建立一個新節點。 然後增加節點支援度大小。 然後繼續搜尋路徑中的下一個節點。
周遊路徑的所有項後,退出該方法。
bool CAssocRules::NewPath(CMyTreeNode *root, matrix &path)
{
ulong total = path.Cols();
if(total <= 0)
return false;
CMyTreeNode *parent = root;
root.IncreaseSupport(path[1, 0]);
for(ulong i = 0; i < total; i++)
{
CMyTreeNode *temp = parent.GetNext((ulong)path[0, i]);
if(!temp)
{
temp = parent.AddNode((int)path[0, i], 0);
if(!temp)
return false;
}
temp.IncreaseSupport(path[1, i]);
parent = temp;
}
//---
return true;
}
最後,我們繼續讨論生成FP 樹的方法:GrowsTree。 它在參數中接收指向樹根節點的指針、源資料的二進制矩陣、和經過排序的已分析特征矩陣。 在方法内部,運作循環周遊源資料裡的所有行。
在循環體中,從訓練樣本中捕獲下一筆業務,并調用 CreatePath 方法建立一個路徑,添加到樹中。 檢查并確定接收的部分大于 0。 然後調用 NewPath 方法把所建立路徑加進我們的 FP 樹。 不要忘記檢查操作結果。
成功疊源資料中的所有業務之後,以肯定結果退出該方法,。
bool CAssocRules::GrowsTree(CMyTreeNode * root, matrix & bin_data, matrix &positions)
{
ulong rows = bin_data.Rows();
for(ulong r = 0; r < rows; r++)
{
matrix path = CreatePath(bin_data.Row(r), positions);
ulong size = path.Cols();
if(size <= 0)
continue;
if(!NewPath(root, path))
return false;
}
//---
return true;
}
現在,我們将上述所有方法合并到公開方法 CreateRules 當中。 在方法參數中,我們傳遞源資料标量(非二進制)的矩陣、目标值的二進制向量、把标量值轉換為二進制值的區間數量、最小支援度和最小置信度。
在方法實體中,我們首先檢查接收到的源資料。 我們主要檢查獲得的矩陣向量的維數與區間數量的對應關系,區間數必須大于 0。
在子產品檢查之後,将源資料标量轉換為二進制形似。 這是調用上述 PrepareData 方法完成的。
bool CAssocRules::CreateRules(matrix &data,
vector &buy,
vector &sell,
int sections = 10,
double min_sup = 0.03,
double min_conf = 0.3)
{
if(data.Rows() <= 0 || data.Cols() <= 0 || sections <= 0 ||
data.Rows() != buy.Size() || data.Rows() != sell.Size())
return false;
//---
matrix binary_data;
if(!PrepareData(data, binary_data, buy, sell, sections))
return false;
進而,為了移動到相對機關平面,将二進制矩陣值除以訓練樣本中的業務數量,并調用 GrowsTree 方法建構 FP 樹。
double k = 1.0 / (double)(binary_data.Rows());
if(!GrowsTree(GetPointer(m_cRoot), binary_data * k, m_mPositions))
return false;
在建構 FP 樹之後,我們就能夠繼續建立規則。 首先,準備一個記錄支援度的向量,和一個記錄路徑的矩陣。 然後調用 Mining 方法,在 FP 樹裡查找所有含有買入特征的路徑。
vector supports = vector::Zeros(binary_data.Cols());
binary_data = matrix::Zeros(0, binary_data.Cols());
if(!m_cRoot.Mining(supports, binary_data, m_cBuyRules.ID(),min_conf))
return false;
成功提取所有路徑後,重置購買特征的支援度,進而将其從所有路徑的過程裡删除。 并按降序對專有支援度進行排序。 調用 CreatePositions 方法,并把結果記錄到 m_BuyPositions 矩陣當中。 如果,特征排序之後,我們仍然能夠建構規則(排序矩陣仍然有特征用作規則的先行項),那麼調用樹成長方法,并将先前選擇的路徑輸入到該方法之中。
作為這些操作的結果,我們将在 m_cBuyRules 節點獲得一個含有根的專有規則樹。
supports[m_cBuyRules.ID()] = 0;
m_BuyPositions = CreatePositions(supports, min_sup);
if(m_BuyPositions.Rows() > 0)
if(!GrowsTree(GetPointer(m_cBuyRules), binary_data, m_BuyPositions))
return false;
與此類似,為賣出特征建立一棵規則樹。
supports = vector::Zeros(binary_data.Cols());
binary_data = matrix::Zeros(0, binary_data.Cols());
if(!m_cRoot.Mining(supports, binary_data, m_cSellRules.ID(),min_conf))
return false;
supports[m_cSellRules.ID()] = 0;
m_SellPositions = CreatePositions(supports, min_sup);
if(m_SellPositions.Rows() > 0)
if(!GrowsTree(GetPointer(m_cSellRules), binary_data, m_SellPositions))
return false;
//---
m_cRoot.Clear();
//---
return true;
}
選擇所有規則後,清除源 FP 樹對象,以便釋放計算機資源。 然後以肯定結果退出方法。
“Probability” 方法是為實際應用而建立的。 作為參數,該方法接收源資料的标量向量,和指向兩個雙精度型變量的指針,這兩個變量是為了存儲特定的形态置信度。 該方法的算法用到上述所有方法。 您可以在附件中看到它們。
附件中提供了所有類和方法的完整代碼。
3. 測試
我已建立了一個智能系統(assocrules.mq5),并基于實際資料來測試這個類。 EA 的測試完全相容之前測試中所用的所有參數。 我不能說這個方法能判定所有的分形,且無錯誤。 但建立的 EA 展現出有趣的結果,如下面的螢幕截圖所示。
結束語
在本文中,我們研究了采用非監督學習方法解決另一類問題:關聯規則挖掘。 我們已建立了一個類,是為了利用 FP-Growth 算法建構關聯規則。 以智能系統形式對其進行了測試,結果很有趣。 由此,可以得出結論,這些算法可用來解決交易中的實際問題。