- 實作注意機制嗎?
- 從 Python API 中詢問網路內部層的維度嗎?
- 簡介或檢查或列出模型輸入變數?
- 從 Python API 到 C++ API 的 1D 輸入到 1D 輸出的埠投影?
- 將 LSTM NDL 基本類型移植到 Python?
- 將預測限制為限定的間隔?
- 從 Python 設定詳細資訊或 traceLevel?
- 從先前的 V1 實作公開 V2 Python 中的新運算元?
實作注意機制
實作注意機制需要計算動態軸的 softmax。 執行此動作的其中一種方式是迴圈。 Python 中的符號週期需要一些時間才能使用。 若要讓專案具體化,讓我們看看如何實作採用查詢和候選答案的模型,並計算其標記法的余弦相似度。 首先,我們假設查詢和答案已由管線處理,如下所示
q_lstm = Sequential([ Embedding(500), BiRecurrence(LSTM(300), LSTM(300)), Dense(200)])
a_lstm = Sequential([ Embedding(500), BiRecurrence(LSTM(300), LSTM(300)), Dense(200)])
q_embed = q_lstm(question)
a_embed = a_lstm(answer)
其中 BiRecurrence 是一個便利函式,您可以在 本教學課程的第三個工作解決方案中找到。 它會向前執行一個 LSTM,另一個 LSTM 往後執行,並串連結果。 在此前置處理之後,我們有 200 維度向量的可變長度序列供查詢使用,而另一個變數長度序列則為 200 維度向量供答案使用。
若要實作注意機制,我們需要計算每個位置的純量值,並使用適當的更正來計算其指數值,使其指數的總和等於 1。
w_q = C.parameter((200,1), init=C.glorot_normal())
w_a = C.parameter((200,1), init=C.glorot_normal())
zq = C.times(q_embed, w_q)
za = C.times(a_embed, w_a)
現在我們需要計算適當的更正,這是指數總和的記錄。 這可以使用另一個週期來完成。
p = C.placeholder((1))
prev_zq_or_tiny = C.element_select(C.sequence.is_first(zq), -1e+30, C.sequence.past_value(p))
log_cumsum_exp = C.log_add_exp(zq, prev_zq_or_tiny)
actual_log_cumsum_exp = log_cumsum_exp.replace_placeholders({p:log_cumsum_exp.output})
log_sum_exp = C.sequence.last(actual_log_cumsum_exp)
attn_q = C.exp(zq - C.sequence.broadcast_as(log_sum_exp , zq))
要瞭解的最困難部分是 呼叫 replace_placeholders 。
在此呼叫計算圖表的這個部分之前未包含迴圈:我們正在查看 zq 或過去的 值 p 。 一旦呼叫replace_placeholders我們關閉迴圈,並指向 p 它用來定義的運算式輸出!
注意權數 attn_a 可以使用相同的方式取得。 最後,我們可以將參與內嵌之間的余弦距離計算為:
attended_q = C.sequence.reduce_sum(attn_q * q_embed)
attended_a = C.sequence.reduce_sum(attn_a * a_embed)
cosine_dst = C.cosine_distance(attended_q, attended_a)
從 Python API 中詢問網路內部層的維度
這取決於您使用 API 的方式。 如果您使用圖層 API,則模型如下所示:
model = Sequential([
Embedding(emb_dim),
Recurrence(LSTM(hidden_dim),
Dense(num_labels)
])
可以像這樣詢問:
print(len(model.layers))
print(model.layers[0].E.shape)
print(model.layers[2].b.value)
亦即,您必須知道用於內嵌 (E 的張量名稱,b 表示偏差,W 表示權數) 。 不過,您可以使用一些反映來復原這些專案。
簡介或檢查或列出模型輸入變數
如果我使用一些input_variable然後從定型器建立模型,我需要input_variable名稱,但只有模型,如何從input_variables取得簡介?
例如,您會像這樣設定定型器:
SetupTrainer():
input = cntk.input_variable((input_dim), np.float32)
label = cntk.input_variable((num_output_classes), np.float32)
z = model(input) # (features) -> (prediction as unnormalized log prob)
ce = cross_entropy_with_softmax(z, label)
errs = classification_error(z, label)
criterion = combine ([ce, errs]) # (features, labels) -> (loss, metric)
trainer = Trainer(model, criterion.outputs[0], criterion.outputs[1], learner)
return trainer, criterion
之後,您需要進行簡介,才能使用 引數來傳回名稱輸入和標籤:
# train the model
trainer, criterion = SetupTrainer()
trainer.train_minibatch({criterion.arguments[0]: features, criterion.arguments[1]: labels})
從 Python API 到 C++ API 的 1D 輸入到 1D 輸出的埠投影
在CNTK C++ API中,順位 1 張量表示資料行向量,而張量會以資料行主要格式儲存 (亦即,軸 0 是最快的變更維度,後面接著座標軸 1,依此類) 。 在 Python 中,為了符合 NumPy 所建立的一般接受標準,順位 1 張量表示資料列向量,而張量會以資料列主要格式儲存 (亦即,軸 0 是最慢的變更維度,後面接著座標軸 1,依此類) 。
採用下列 Python 程式碼片段:
input = input_variable((input_dim), np.float32)
times_param = parameter(shape=(input.shape[0], output_dim))
t = times(input, times_param)
若要將 dim inputDim 的 1D 輸入投影到 dim outputDim 的 1D 輸出,您需要在 C++ 中設定如下所示的專案:
input = InputVariable({ inputDim }, DataType::Float);
timesParam = CNTK::Parameter({ outputDim, input.Shape()[0] });
t = Times(timesParam, input);
請注意,與 Python 程式碼相比,張量圖形和運算元 Times 對作業的順序如何反轉。
我們在內部執行 SWIG 層所需的圖形轉換,以正確地將 Python 圖形和作業對應至 C++ 實作。
將 LSTM NDL 基本類型移植到 Python
如何?尋找下列 NDL LSTM 基本類型對 Python 的支援:
延遲
如何在網路稍後定義的變數延遲中傳遞引數? 例如,對於 Peep 孔 LSTM,稍後會定義資料格狀態變數,但需要延遲才能取得 t-1 資料格狀態。 Python 不允許先使用變數,稍後再加以定義。
答:一個需要使用
placeholder和 更新版本的 呼叫replace_placeholders。 以下是 簡單的範例。
RowStack、 RowSlice
這些基本類型是否有任何替代專案? 如果不是如何在 Python 中實作它們? 我們是否可以在變數上運作,就像它們是 numpy 陣列一樣?
答案:使用 接合
DiagTime 與 ElementTimes
向量元素乘法之間是否有任何差異? Python 也支援 DiagTimes 嗎?
使用 元素乘法
參數初始化
如何從 Python 中的檔案初始化參數,並將 設定
computeGradient為 false。使用 常數。 您可以透過 NumPy 陣列指定初始值。 有許多方法可將文字 (或其他) 檔案載入 NumPy 陣列。
將預測限制為限定間隔
您可以使用 clip。 例如,如果您使用圖層 API
z = Sequential([ Dense(500, activation=relu),
Dense(4, activation=None),
clip(Placeholder(), 0, 224) ])
將會建立具有 relu 的一層和具有線性啟用之一層的網路。 後者有四個輸出,其預測限制在間隔 [0,224]。 這可用來預測大小為 224 x 224 的影像周框方塊。
從 Python 設定詳細資訊或 traceLevel
import _cntk_py
_cntk_py.set_computation_network_trace_level(1)
從先前的 V1 實作公開 V2 Python 中的新運算元
有數個步驟將 V1 程式庫中可用的函式公開至 Python 中的 V2:
步驟 1:定義 Python 介面
步驟 2:定義 C++ 介面,並將介面管接至 V1 CPP
步驟 3:在 SWIG 層中的 Python 和 C++ API 上建立黏附層
步驟 1:Python 介面
在此步驟中,您會定義要公開的運算元介面。 在下列範例中,我們有 和 運算元,其採用 2 個輸入,這些輸入 (對應至 x 輸入資料,並從 y 輸入資料繪製) ,以及其他兩個使用者指定的輸入 (shift 和 num_negative_samples) 為整數參數。 函式會傳回陣列清單,這些陣列相依 num_negative_samples 于 和 minibatch (中的樣本數目,如定義) 時 x 使用者所指定。
注意:這是一種令人理解,而且其中一個可能會有不同的輸入變數組合、不可學習的參數和傳回輸出的結構。
注意2:如果您預期輸出中有空白行,請參閱下列程式碼,以瞭解應該如何指定空白行。 請確定間距完全相同。
您可以 doctest 執行:來執行。 pytest __init__.py 在 中 C.func_name ,C 是 (參考檔案) 的 .py 別名 import cntk as C 。
在 更新 中
//bindings/python/cntk/ops__init__.py
@typemap
def cosine_distance_with_negative_samples(x, y, shift, num_negative_samples, name=''):
'''
Give a description
Example:
>>> qry = # Create some data with numpy
>>> doc = # Create some data with numpy
>>> x = input_variable(shape=(4))
>>> y = input_variable(shape=(4))
>>> model = C.cosine_distance_with_negative_samples(x, y, shift=1, num_negative_samples=2)
>>> np.round(model.eval({x: qry, y: doc}), decimals=4)
array([[[ 1. , 0.5, 0. ]],
<BLANKLINE>
[[ 1. , 0.5, 0.5]],
<BLANKLINE>
[[ 1. , 0. , 0.5]]], dtype=float32)
Args:
Describe the individual variables
Returns:
:class:`~cntk.ops.functions.Function`
'''
from cntk.cntk_py import cosine_distance_with_negative_samples
dtype = get_data_type(x, y)
x = sanitize_input(x, dtype)
y = sanitize_input(y, dtype)
return cosine_distance_with_negative_samples(x, y, shift, num_negative_samples, name)
在對應的
//bindings/python/cntk/ops/tests目錄中新增測試
def test_cosine_distance_with_negative_samples():
a = #Create some fake data with Numpy
b = #Create some fake data with Numpy
qry = input_variable(shape=(5))
doc = input_variable(shape=(5))
num_neg_samples = 2
model = cosine_distance_with_negative_samples(qry, doc, shift=1, num_negative_samples=num_neg_samples)
result = model.eval({qry:[a], doc:[b]})
# Add tests that assert model shape and the returned values
步驟 2:在 V2 C++ API 中公開運算子
更新
CNTKLibrary.h) 中的//Source/CNTKv2LibraryDll/API(:新增您想要公開的簽章。 這應該會鏡像您在 Python API 中擁有的簽章
///
/// Create an instance of the CNTK built-in operation to compute the cosine distance
/// with negative samples for the specified input operands.
///
CNTK_API FunctionPtr CosineDistanceWithNegativeSamples(const Variable& leftOperand,
const Variable& rightOperand,
size_t shiftWindow,
size_t numberOfNegativeSamples,
const std::wstring& name = L"");
) 中的
//Source/CNTKv2LibraryDll/API更新CNTKLibraryInternals.h(:如果 V2 API 簽章與 V1 API 中提供的簽章不同,您應該在這裡指定內部 API。
CNTK_API FunctionPtr CosineDistanceWithNegativeSamples(const Variable& leftOperand,
const Variable& rightOperand,
const Variable& shiftWindow,
const Variable& numberOfNegativeSamples,
const std::wstring& name = L"");
在 //Source/CNTKv2LibraryDll) 中更新
Function.cpp(:此檔案必須具有 V2 C++ API 在 中呼叫的CNTKLibary.hAPI。 在此實例中,會建立複合共用物件。 請注意,size_t變數會轉換成常數,因為 V1 API 會將對應的參數公開為變數。 如果傳入的參數具有大於整數的維度,我們必須記得確保 V1 程式庫函式擲回例外狀況。
FunctionPtr CosineDistanceWithNegativeSamples(const Variable& leftOperand,
const Variable& rightOperand,
size_t shiftWindow,
size_t numberOfNegativeSamples,
const std::wstring& name)
{
std::vector<Variable> operands = {leftOperand,
rightOperand,
Constant::Scalar((float) shiftWindow),
Constant::Scalar((float) numberOfNegativeSamples) };
return AsComposite(MakeSharedObject<PrimitiveFunction>(
PrimitiveOpType::CosDistanceWithNegativeSamples,
operands, Dictionary(), name), name);
}
更新
PrimitiveOpType.h//Source/CNTKv2LibraryDll) 中的 (:為新的運算元提供列舉值。
CosDistanceWithNegativeSamples = 67,
// New op types should only be appended to the end of this list
更新
PrimitiveFunction.h//Source/CNTKv2LibraryDll) 中的 (:新增字串命名運算子
{PrimitiveOpType::CosDistanceWithNegativeSamples, L"CosDistanceWithNegativeSamples"},
或者,如果 EditDistanceErroradditionalProperties 函式需要傳遞,則必須將這些屬性宣告至 PrimitiveFunction 類別
{PrimitiveOpType::EditDistanceError, L"EditDistanceError" },
和 會定義新的屬性名稱。
static const std::wstring AttributeNameSubstitutionPenalty;
static const std::wstring AttributeNameDeletionPenalty;
static const std::wstring AttributeNameInsertionPenalty;
static const std::wstring AttributeNameSquashInputs;
static const std::wstring AttributeNameSamplesToIgnore;
更新
PrimitiveOpType.cpp//Source/CNTKv2LibraryDll) 中的 (:
在某些其他建構中,其他屬性會從 V1 API 公開,其中一個可能會實作函式呼叫,如下所示:
FunctionPtr EditDistanceError(const Variable& prediction,
const Variable& labels,
float subPen, float delPen, float insPen,
bool squashInputs,
const vector<size_t>& samplesToIgnore,
const std::wstring& name)
{
auto additionalProperties = Dictionary();
additionalProperties[PrimitiveFunction::AttributeNameSubstitutionPenalty] = subPen;
additionalProperties[PrimitiveFunction::AttributeNameDeletionPenalty] = delPen;
additionalProperties[PrimitiveFunction::AttributeNameInsertionPenalty] = insPen;
additionalProperties[PrimitiveFunction::AttributeNameSquashInputs] = squashInputs;
additionalProperties[PrimitiveFunction::AttributeNameSamplesToIgnore] = AsDictionaryValueVector(samplesToIgnore);
return BinaryOp(PrimitiveOpType::EditDistanceError, prediction, labels, std::move(additionalProperties), name);
}
CompositeFunction.cpp更新 //Source/CNTKv2LibraryDll) 中的 (:這裡會建構CosDistanceWithNegativeSamplesNode物件來保存要公開之函式的實作。
case PrimitiveOpType::CosDistanceWithNegativeSamples:
computationNodePtr = New<CosDistanceWithNegativeSamplesNode<ElementType>>(network->GetDeviceId(), internalNodeName);
在另一個範例中,參數會直接從呼叫端傳入,運算元實作看起來會像這樣
case PrimitiveOpType::EditDistanceError:
{
auto subPen = functionConfig[PrimitiveFunction::AttributeNameSubstitutionPenalty].Value<float>();
auto delPen = functionConfig[PrimitiveFunction::AttributeNameDeletionPenalty].Value<float>();
auto insPen = functionConfig[PrimitiveFunction::AttributeNameInsertionPenalty].Value<float>();
auto squashInputs = functionConfig[PrimitiveFunction::AttributeNameSquashInputs].Value<bool>();
auto samplesToIgnore = AsVector<size_t>(functionConfig[PrimitiveFunction::AttributeNameSamplesToIgnore].Value<std::vector<DictionaryValue>>());
computationNodePtr = New<EditDistanceErrorNode<ElementType>>(network->GetDeviceId(), subPen, delPen, insPen, squashInputs, samplesToIgnore, internalNodeName);
break;
}
更新
BackCompat.cpp//Source/CNTKv2LibraryDll) 中的更新 (:視函式參數傳遞方式而定,變更可能簡單如下:
else if (node->OperationName() == OperationNameOf(CosDistanceWithNegativeSamplesNode))
{
opType = PrimitiveOpType::CosDistanceWithNegativeSamples;
}
或者,如果要傳入其他參數,則下列程式碼片段更適合
else if (node->OperationName() == OperationNameOf(EditDistanceErrorNode))
{
auto edNode = node->As<EditDistanceErrorNode<ElementType>>();
primitiveFunctionConfigParameters[PrimitiveFunction::AttributeNameInsertionPenalty] = edNode->InsertionPenalty();
primitiveFunctionConfigParameters[PrimitiveFunction::AttributeNameDeletionPenalty] = edNode->DeletionPenalty();
primitiveFunctionConfigParameters[PrimitiveFunction::AttributeNameSubstitutionPenalty] = edNode->SubstitutionPenalty();
primitiveFunctionConfigParameters[PrimitiveFunction::AttributeNameSquashInputs] = edNode->SquashInputs();
primitiveFunctionConfigParameters[PrimitiveFunction::AttributeNameSamplesToIgnore] = AsDictionaryValueVector(edNode->SamplesToIgnore());
opType = PrimitiveOpType::EditDistanceError;
}
V1 操作員簽章的可能更新。 在 CosineDistanceWithNegativeSamples 的情況下,我們會將 API 中公開的 int 轉換成 V1 程式庫中公開的變數類型。 因此,這兩個參數需要驗證。 V1 功能的實作位於
LinearAlgebraNodes.h//Source/ComputationNetworkLib) 的 (中:我們會驗證所提供參數的形狀是否如預期般為常數純量。
auto input3AsLearnableParameterNode = Input(3)->template As<LearnableParameter<ElemType>>();
if (isFinalValidationPass && (!input3AsLearnableParameterNode || input3AsLearnableParameterNode->GetLearningRateMultiplier() != 0) || (Input(3)->GetSampleLayout().GetNumElements() != 1))
LogicError("%ls %ls operation expects a constant scalar for Input(3) which corresponds to number of negative samples.", NodeName().c_str(), OperationName().c_str());
對於從 Python API 傳遞其他參數的其他函式,則需要修改實作來源的建構函式。 例如,公開 EditDistanceError 將懲罰傳遞至建構函式的一些程式碼變更的位置,如下所示。
EditDistanceErrorNode(DEVICEID_TYPE deviceId, float subPen, float delPen, float insPen, bool squashInputs, std::vector<size_t> samplesToIgnore, const wstring & name)
: Base(deviceId, name), m_subPen(subPen), m_delPen(delPen), m_insPen(insPen), m_squashInputs(squashInputs), m_SamplesToIgnore(samplesToIgnore)
{
}
EditDistanceErrorNode(const ScriptableObjects::IConfigRecordPtr configp)
: EditDistanceErrorNode(configp->Get(L"deviceId"), configp->Get(L"subPen"), configp->Get(L"delPen"), configp->Get(L"insPen"), configp->Get(L"squashInputs"), configp->Get(L"samplesToIgnore"), L"<placeholder>")
{
AttachInputsFromConfig(configp, this->GetExpectedNumInputs());
}
可能需要對傳遞的變數進行一些額外的管接,並將明確複製到個別節點。
更新
SeriealizationTest.cpp//Tests/UnitTests/V2LibraryTests) 中的 (:檢查唯一識別碼判斷提示
static_cast<size_t>(PrimitiveOpType::CosDistanceWithNegativeSamples) == 67,
步驟 3:SWIG 的更新:
某些編譯器警告會被忽略。
cntk_py.i更新 //bindings/python/cntk) 中的 (:
%ignore CNTK::Internal::CosineDistanceWithNegativeSamples;
cntk_cs.i更新 //bindings/csharp/Swig) 中的 (:
%ignore CNTK::Internal::CosineDistanceWithNegativeSamples;
%ignore_function CNTK::Internal::CosineDistanceWithNegativeSamples;
選擇性地公開 BrainScript V2 中的功能
更新
ComputationNetworkBuilder.h//Source/ComputationNetworkLib) 中的 (:
ComputationNodePtr EditDistanceError(const ComputationNodePtr a, const ComputationNodePtr b, float subPen, float delPen, float insPen, bool squashInputs, vector<size_t> samplesToIgnore, const std::wstring nodeName = L"");
並建立新的節點並附加輸入
else if (nodeType == OperationNameOf(EditDistanceErrorNode))
return New<EditDistanceErrorNode<ElemType>>(forward<_Types>(_Args)...);
template <class ElemType>
shared_ptr<ComputationNode<ElemType>> ComputationNetworkBuilder<ElemType>::EditDistanceError(const ComputationNodePtr a, const ComputationNodePtr b, float subPen, float delPen, float insPen, bool squashInputs, vector<size_t> samplesToIgnore, const std::wstring nodeName)
{
return net.AddNodeToNetAndAttachInputs(New<EditDistanceErrorNode<ElemType>>(net.GetDeviceId(), subPen, delPen, insPen, squashInputs, samplesToIgnore, nodeName), { a, b });
}