首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

14.4  浏览和查询挖掘模型

创建和部署模型只是开始。当使用模型学习到的知识并且直接将这些知识嵌入到应用程序中时,真正的工作才开始。您可以推荐产品、管理存货、预测税收、验证数据以及执行无数的其他任务,这些任务只受到您的数据和想象力的限制。

14.4.1  使用ADOMD.NET来预测

让我们从使用ADOMD.NET的基本预测查询的示例开始。代码清单14-9示范了执行查询的典型示例。熟悉ADO.NET的读者将会注意到,迄今为止,API之间的唯一区别在于数据访问类的名称。事实上,使用ADO.NET类来针对Analysis Services执行简单的查询也同样是可能的,然而,ADOMD.NET得到了优化,以便使用Analysis Services服务器,从而可以利用额外的Analysis Services特性。

代码清单14-9  执行简单的单例预测查询

Private Sub SingleResultQuery()

' Create connection and command objects.

Dim cn As New AdomdConnection("location=localhost; " & _

"Initial Catalog=MovieClick")

Dim cmd As New AdomdCommand()

' Initialize command with query

cmd.Connection = cn

cmd.CommandText = "SELECT Predict(Generation) " & _

"FROM [Generation Trees] NATURAL PREDICTION JOIN " & _

"SELECT (SELECT 'HBO' AS Channel UNION " & _

"SELECT 'Showtime' AS Channel) as PayChannels as t"

' Open connection and write result to debug window

cn.Open()

Debug.WriteLine(cmd.ExecuteScalar().ToString())

' Close connection

cn.Close()

End Sub

提示:

当执行单例查询(该查询返回单行中的单列)时使用ExecuteScalar方法。

当执行返回多个列或者行的查询(如代码清单14-10所示)时使用ExecuteReader,该查询执行与代码清单14-9相同的预测,但是返回PredictHistogram的平坦化结果,所以我们可以看到所有可能的预测结果的似然估计。

代码清单14-10  对包含多行的结果进行迭代

Private Sub MultipleRowQuery()

' Create connection and command objects.

Dim cn As New AdomdConnection("location=localhost;" & _

"Initial Catalog=MovieClick")

Dim cmd As New AdomdCommand()

' Initialize command with query

cmd.Connection = cn

cmd.CommandText="SELECT FLATTENED PredictHistogram(Generation) " & _

"FROM [Generation Trees] NATURAL PREDICTION JOIN " & _

"SELECT (SELECT 'HBO' AS Channel UNION " & _

"SELECT 'Showtime' AS Channel) as PayChannels as t"

' Open connection and execute query

Dim reader As AdomdDataReader

cn.Open()

reader = cmd.ExecuteReader()

' Write field names to debug window

Dim i As Integer

For i = 0 To reader.FieldCount - 1

Debug.Write(reader.GetName(i) & "\t")

Next

Debug.WriteLine("")

' Iterate results to debug window

While reader.Read

For i = 0 To reader.FieldCount - 1

Debug.Write(reader.GetValue(i).ToString())

Next

Debug.WriteLine("")

End While

' Close reader and connection

reader.Close()

cn.Close()

End Sub

在最后一个示例中,为了方便迭代而将嵌套表查询的结果平坦化。然而,在一些情况下,对结果进行平坦化是不切实际的,例如,当查询返回多个嵌套表乃至嵌套表内的嵌套表时。代码清单14-11示范了如何在不含FLATTENED关键字的情况下对前面示例的结果进行迭代。

代码清单14-11  对嵌套的PredictHistogram结果的Attribute列进行迭代

Dim nestedreader As AdomdDataReader

While reader.Read()

nestedreader = reader.GetReader(0)

While nestedreader.Read()

Debug.WriteLine(nestedreader.GetValue(0).ToString())

End While

nestedreader.Close()' Be sure to close the nested readers!

End While

到目前为止,所有通过ADO.NET来执行的操作都执行完了,但还不够有效。下一步,学习通过以下方式来扩展应用程序的功能:使用参数化的查询来修改预测的输入。ADO.NET不支持命名参数,这些参数用于提供程序而不是SQL Server关系引擎。要在查询中使用命名参数,则必须使用ADOMD.NET。代码清单14-12示例了使用命名参数的数据挖掘查询。

代码清单14-12  使用命名参数的数据挖掘查询

' Initialize command with parameterized query

cmd.CommandText = "SELECT PredictHistogram(Generation) " & _

"FROM [Generation Trees] NATURAL PREDICTION JOIN " & _

"SELECT (SELECT @Channel1 AS Channel UNION " & _

"SELECT @Channel2 AS Channel) as PayChannels as t"

' Initialize parameters and add to command

Dim Channel1 As New AdomdParameter()

Dim Channel2 As New AdomdParameter()

Channel1.ParameterName = "@Channel1"

Channel2.ParameterName = "@Channel2"

cmd.Parameters.Add(Channel1)

cmd.Parameters.Add(Channel2)

' Set parameter values

cmd.Parameters("@Channel1").Value = "HBO"

cmd.Parameters("@Channel2").Value = "Showtime"

代码清单14-12假定我们知道只允许和需要两个频道来执行该预测。显然,情况并不总是这样。ADOMD.NET允许使用参数来将整个表作为输入数据源传入。通过这一点,我们可以比较容易地使用数据来执行预测,这些数据在客户端上或者对服务器不可用。代码清单14-13示范了使用整形(shaped)后的表参数来作为预测的输入。

代码清单14-13  使用表参数的数据挖掘查询

' Create table for case

Dim caseTable as new DataTable

caseTable.Columns.Add("CustID", System.Type.GetType("System.Int32"))

caseTable.Rows.Add(1)

' Create nested table

Dim nestedTable as new DataTable

nestedTable.Columns.Add("CustID", _

System.Type.GetType("System.Int32"))

nestedTable.Columns.Add("Channel", _

System.Type.GetType("System.String"))

nestedTable.Rows.Add(1,"HBO")

nestedTable.Rows.Add(1,"Showtime")

' Initialize command with parameterized query

cmd.CommandText = "SELECT PredictHistogram(Generation) " & _

"FROM [Generation Trees] NATURAL PREDICTION JOIN " & _

"SHAPE { @CaseTable } " & _

"APPEND ({ @NestedTable } " & _

"RELATE CustID to CustID) AS Channels " & _

"as t"

' Initialize parameters and add to command

Dim caseParam As New AdomdParameter()

Dim nestedParam As New AdomdParameter()

caseParam.ParameterName = "@CaseTable"

nestedParam.ParameterName = "@NestedTable"

cmd.Parameters.Add(caseParam)

cmd.Parameters.Add(NestedParam)

' Set parameter values

cmd.Parameters("@CaseTable").Value = caseTable

cmd.Parameters("@NestedTable").Value = nestedTable

14.4.2  浏览模型

正如在第2章中所描述的,所有的模型元数据和内容都可以通过模式行集来访问。然而,使用ADOMD.NET,可以改为使用丰富的对象模型来浏览服务器和模型。图14-2显示了ADOMD.NET的主要的数据挖掘对象。

图14-2  在ADOMD.NET中的数据挖掘对象层次

正如从对象模型中可以看到的,不需要求助于模式查询就可以连接到服务器,然后在数据挖掘对象上进行迭代。这为应用程序开发人员带来的好处是,如果已连接的用户没有访问特定对象的权限,则该对象将不会出现在它的集合中,正如它不存在一样。

通过使用ADOMD.NET对象模型所获得的最重要的能力是以自然的、层次化的方式来使用对象迭代模型内容的能力,而不是试图拆开平坦的模式行集表。使用该对象模型,使得编写复杂的程序来浏览内容或者将内容显示给用户变得容易。例如,Microsoft决策树算法重要的问题是:给定一个属性,查找在该属性上包含一个拆分的所有树。

代码清单14-14示范了使用内容对象模型来浏览树,以查找给定属性上的拆分。首先,对代表树的根的所有子节点进行识别,然后递归地检查树的子节点,以了解它们的边缘规则是否包含所请求的属性。通过查看节点类型,而不是使用的算法,该函数将针对包含树的任何模型进行处理,而不管它是使用Microsoft决策树算法、Microsoft时序算法,还是任何的第三方基于树的算法。

代码清单14-14  使用ADOMD.NET来浏览模型内容

' Identify all the attributes that split

' on a specified attribute.

Sub FindSplits(ByVal cn As AdomdConnection, _

ByVal ModelName As String, ByVal AttributeName As String)

' Find the specified model.

Dim model As MiningModel

model = cn.MiningModels(ModelName)

If IsDBNull(model) Then Return

' Look for the attribute in all model trees.

Dim node As MiningContentNode

For Each node In model.Content

If node.Type = MiningNodeType.Tree Then

FindSplits(node, AttributeName)

End If

Next

End Sub

' Recursively search for the attribute among content nodes

' Return when children exhausted or attribute is found

Sub FindSplits(ByVal node As MiningContentNode, _

ByVal AttributeName As String)

' Check for the attribute in the MarginalRule.

If node.MarginalRule.Contains(AttributeName) Then

' The attribute column contains the

' name of the tree.

Debug.WriteLine(node.Attribute)

Return

End If

' Recurse over child nodes

Dim childNode As MiningContentNode

For Each childNode In node.Children

FindSplits(childNode, AttributeName)

Next

End Sub

通过使用PredictNodeId函数,也可以使用模型内容来找出预测的理由。例如,可以使用以下查询:

SELECT Predict(Generation), PredictNodeId(Generation) ...

来检索用于生成预测结果的节点ID,然后将预测结果输入到类似如下的函数中:

Function GetPredictionReason(ByVal model As MiningModel, _

ByVal NodeID As String) As String

Dim node As MiningContentNode

node = model.GetNodeFromUniqueName(NodeID)

If IsDBNull(node) Then Throw New System.Exception("Node not found")

return node.Description;

End Function

14.4.3  存储过程

ADOMD.NET提供了一个优秀的对象模型,用于访问服务器对象和浏览模型内容。然而,该对象模型存在一些主要的缺点。对于在代码清单14-14中的FindSplits方法,必须将整个模型内容从服务器传送到客户端来确定拆分列表。包含1000棵树并且每棵树都包含1000个节点的模型需要的行数超过1 000 000,即使只有少数的树引用了所需要的属性。同样,在GetPredictionReason函数中,即使可以使用GetNodeFromUniqueName直接访问需要的节点,也会导致服务器在每次调用时发生往返操作;不推荐以批处理的方式来执行该操作。

对于这些问题存在解决方案。在SQL Server 2005中的Analysis Services支持存储过程,可以通过任何托管的语言(例如C#、VB.NET或者托管的C++)来编写存储过程。对象模型ADOMD+与ADOMD.NET几乎一样,它使得两个模型之间的转换变得容易。ADOMD+比较明显的优点是所有的内容都可以在服务器上获得,并且可以只将需要的信息返回给服务器。可以使用CALL语法或者将UDF作为DMX查询的一部分来单独调用UDF。例如,以下查询:

CALL MySprocs.TreeHelpers.FindSplits('Generation Trees','HBO')

直接调用存储过程,而且只是返回结果,然而下面这个查询:

SELECT Predict(Generation),

MySprocs.TreeHelpers.GetPredictionReason(PredictNodeId(Generation))

...

对于从预测查询中返回的每一行都调用存储过程。在这种情况下,查询将返回预测的结果以及对每一行结果的解释。

将VBA和Excel的函数作为存储过程来调用

如果在安装Analysis Services服务器的相同机器上安装了Microsoft Office,则可以将Visual Basic for Applications(VBA)和Excel的函数作为DMX查询内部的存储过程来利用。

例如,可以将预测的输出转换为小写形式,如下所示:

SELECT LCase(Predict(MyModel.[Home Ownership]) FROM MyModel

PREDICTION JOIN ....

如果函数同时存在于Excel和VBA中,则必须相应地使用Excel!和VBA!作为函数名的前缀。例如,要获得来自Excel的预测结果的以10为底的对数,以及来自VBA的预测结果的自然对数,则使用如下所示的查询:

SELECT Excel!Log(Predict(Sales)), VBA!Log(Predict(Sales))

From MyModel ....

如果Excel或者VBA函数也存在于MDX或者DMX中,或者该函数包含$字符,则必须用方括号([])转义函数名。例如,要将预测的格式设置为货币格式,例如$20.56,则使用如下所示的查询:

SELECT [Format](Predict(Sales), '$d.dd') FROM MyModel ....

在附录B中列出了所支持的VBA函数和Excel函数。

14.4.4  编写存储过程

在引用了需要的程序集——Microsoft.AnalysisServices.AdomdServer之后,可以访问称为Context的全局对象。该对象与ADOMD.NET连接对象相似,包含主要对象(例如MiningModels)的所有集合,在存储过程中可以访问这些对象。存储过程也可以利用Context对象的CurrentMiningModel属性来引用作为查询主体的模型。

存储过程可以采用任何简单的类型作为参数,并且可以在响应中返回简单的类型,甚至可以返回DataTable或者DataSet。如果客户端使用CALL来调用返回简单类型的存储过程,则尽管存储过程将会执行,也不会获得值。如果客户端调用预测查询内部的存储过程,并且该过程返回DataTable或者DataSet,则客户端将会获得包含返回行的嵌套表。

1. 存储过程与调用准备

当编写在服务器上执行的存储过程时,在准备调用期间,必须知道什么时候会调用存储过程来返回结果,以及什么时候调用存储过程只是为了收集模式信息。另外,在准备操作期间,必须指出,调用您的存储过程是安全的,并且调用它不会带来任何不需要的副作用,例如,您不希望两次都创建相同的对象。

将复杂的类型发送给存储过程

如果必须将复杂的类型(例如结构或者数组)发送给存储过程,则可以使用在客户端上的XMLSerializer来对它们进行串行化,然后将它们作为XML字符串发送出去。在服务器一方,反串行化结构或者数组,然后使用感兴趣的复杂类型来调用重载函数。例如,可能会有需要使用下列类型的数组作为参数的函数:

Public Structure MyType

Public a As Integer

Public b As String

End Structure

可以编写以下函数来将数组串行化为XML字符串,然后将该字符串作为参数发送给存储过程:

Function SerializeMyType(ByVal MyArray As MyType()) As String

Dim s As New

System.Xml.Serialization.XmlSerializer(MyArray.GetType())

Dim sw = New System.IO.StringWriter()

Dim str As String

s.Serialize(sw, MyArray)

Return sw.ToString()

End Function

在服务器一方,要复制类型的定义,然后编写一个存根函数来对数组进行反串行化,最后再调用实际的函数。

Public Function MySproc(ByVal xmlString As String) As DataTable

Dim MyArray() As MyType

Dim s As New

System.Xml.Serialization.XmlSerializer(MyArray.GetType())

Dim sr = New System.IO.StringReader(xmlString)

MyArray = s.Deserialize(sr)

Return MySproc(MyArray)

End Function

Function MySproc(ByVal MyArray As MyType()) As DataTable

... ' Function body

End Function

该策略将支持传送复杂的类型,而且为未来的版本(该版本可能支持自然地传送复杂的类型)做好了准备。

Context对象包含ExecuteForPrepare属性,可以在执行存储过程中的任何耗时操作之前对该属性进行检查。如果返回的是DataTable或者DataSet,则应该对这些对象进行定义,然后将空数据返回给它们,从而客户端将会知道模式。一般来说,在准备期间不应该发生错误,特别是缺少对象这类错误,因为准备调用可以在批处理查询期间调用,这些对象可能在调用该存储过程来返回结果之前都一直存在。

为了指出过程没有任何不期望的副作用,必须增加自定义的属性SafeToPrepare。

2. 存储过程示例

代码清单14-15示范了用VB.NET来编写的存储过程。其中的方法与代码清单14-13和代码清单14-14中的方法是相同的,但是对它进行了修改,用以在服务器上执行。此外,代码清单14-15考虑了Context对象的存在,同时考虑了怎样恰当处理在准备操作期间调用存储过程的场合。

代码清单14-15  数据挖掘存储过程

Imports Microsoft.AnalysisServices.AdomdServer

Imports System.Data

Public Class TreeHelper

<SafeToPrepare(True)> _

Public Function FindSplits(ByVal ModelID As String, _

ByVal AttributeName As String) As DataTable

' Create the result table and add a column.

' for the attribute

Dim tblResult As New DataTable()

tblResult.Columns.Add("Attribute", _

System.Type.GetType("System.String"))

' If this is a prepare statement, return the empty

' table for schema information.

If Context.ExecuteForPrepare Then Return tblResult

' Access the model and throw an exception if not found.

' Error text will be propagated to the client.

Dim model As MiningModel

model = Context.MiningModels(ModelID)

If IsDBNull(model) Then Throw _

New System.Exception("Model not found")

' Look for the attribute in all model trees.

If model.Content.Count > 0 Then

Dim node As MiningContentNode

For Each node In model.Content(0).Children

If node.Type = MiningNodeType.Tree Then

FindSplits(node, AttributeName, tblResult)

End If

Next

End If

' Return the table containing the result.

Return tblResult

End Function

Private Function FindSplits(ByVal node As MiningContentNode, _

ByVal AttributeName As String, _

ByRef tblResult As DataTable) As Boolean

' Check for the attribute in the MarginalRule

' and add row to the table if found

If node.MarginalRule.Contains(AttributeName) Then

Dim row() As String = {node.Attribute.Name}

tblResult.Rows.Add(row)

Return True

End If

' Recurse over child nodes

Dim childNode As MiningContentNode

For Each childNode In node.Children

If (FindSplits(childNode, AttributeName, tblResult)) Then

Return True

End If

Next

Return False

End Function

<SafeToPrepare(True)> _

Public Function GetPredictionReason( _

ByVal NodeID As String) As String

' Return immediately if executing for prepare

If Context.ExecuteForPrepare Then Return ""

' Return the node description.

Return Context.CurrentMiningModel. _

GetNodeFromUniqueName(NodeID).Description

End Function

End Class

3. 在存储过程内部执行查询

存储过程一个常见的用途是封装查询,从而可以容易地重用该查询。例如,如果应用程序必须预测Generation,但是我们需要具有这样的灵活性:修改当前使用的模型或者增加其他业务逻辑,则可以通过编写存储过程来实现,该存储过程不需要修改应用层就可以根据需要来执行查询和重新部署过程。

Server ADOMD允许通过使用ADOMD.NET中使用的相同对象来执行DMX查询,唯一的区别在于不需要指定连接,因为已经连接上了。可以将来自查询的结果复制到DataTable,也可以只返回由ExecuteReader返回的DataReader。代码清单14-16示范了来自代码清单14-9的查询(该查询是作为UDF来实现的)。

代码清单14-16  执行存储过程内部的DMX查询

Imports Microsoft.AnalysisServices.AdomdServer

Imports System.Data

Public Class MyClass

<SafeToPrepare(True)> _

Public Function PredictGeneration() as AdomdDataReader

Dim cmd As New AdomdCommand()

' Initialize command with query

cmd.CommandText = "SELECT Predict(Generation) " & _

"FROM [Generation Trees] NATURAL PREDICTION JOIN " & _

"SELECT (SELECT 'HBO' AS Channel UNION " & _

"SELECT 'Showtime' AS Channel) as PayChannels as t"

' Return result to client

Return cmd.ExecuteReader()

End Sub

在这个示例中,如果希望修改正在执行预测的模型,则要修改在存储过程内部的查询,而不需要修改嵌入在应用程序内部的查询。当然,也可以参数化查询,如代码清单14-12所示。

注意:

存储过程不可以用来实现Analysis Serivices中的安全性。当前用户的安全上下文用来确定对Analysis Services服务器内部对象的访问。也就是说,任何调用过程来查询挖掘模型的用户如果在该模型上没有读的权限,则该用户将接收到权限错误消息。类似地,如果调用代码清单14-15中的GetPredictionReason UDF的用户在该模型上没有浏览权限,则也将接收到权限错误消息。

4. 部署和调试存储过程程序集

在编译和构建完存储过程之后,必须将存储过程部署到Analysis Server,从而可以在DMX中调用该存储过程。要将.NET程序集加入到Analysis Services项目中,则需要在解决方案资源管理器中右击“程序集”文件夹,然后选择“新建程序集”。

当部署项目时,会对程序集进行编码,然后将它发送给分析服务器,从而在项目数据库中可以使用该程序集。当必须更新程序集时,只需重新部署它。如果正在使用活跃的项目,则程序集会立刻部署在服务器上。如要在活跃的项目中更新程序集,则删除程序集,然后再将它添加回项目中。

如果您有通用的程序集,您希望在服务器上的所有数据库中都可以访问该程序集,则可以在服务器级别使用SQL Management Studio来部署该程序集。在对象资源管理器中,右击服务器的“程序集”集合。在该集合上,可以选择“新建程序集”,然后选择要增加的程序集。

最好是在同一台机器上运行服务器和客户端时对程序集进行调试;为了达到该目的,可以使用SQL Server的开发许可证。要在Visual Studio中调试程序集,则从“调试”菜单中选择“附加到进程”。从列表中选择可执行的msmdsrv.exe,然后确保对话框显示了与“附加到”相邻的Common Language Runtime。一旦遵循了这些步骤,就可以在存储过程中设置断点。

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论