このドキュメントでは、フィルター、リコール、ソートのカスタムコンポーネントを作成し、PAI-Rec エンジンをカスタマイズする方法を説明します。
エンジンのインポート
go get github.com/alibaba/pairec/v2
フィルターのカスタマイズ
カスタムフィルターを追加するには、次の手順に従ってください:
1. IFilter インターフェイスを実装するカスタム型を作成します。このインターフェイスは PAI-Rec に事前定義されているため、実装を提供するだけで済みます。
// IFilter インターフェイス定義
type IFilter interface {
// Filter は、カスタムのフィルタリングロジックを実装します。filterData.Data 内のアイテムを受け取り、フィルタリング結果で置き換える必要があります。
Filter(filterData *FilterData) error
}package filter
import (
"fmt"
"github.com/alibaba/pairec/v2/filter"
"github.com/alibaba/pairec/v2/module"
)
type MyFilter struct {
}
func (f *MyFilter) Filter(data *filter.FilterData) error {
fmt.Println("my filter")
items := data.Data.([]*module.Item)
newItems := make([]*module.Item, 0)
// アイテムを処理し、条件を満たすものを newItems に追加します。
...
data.Data = newItems
return nil
}2. サーバーの起動前に、カスタムフィルターを登録します。
package main
import (
"pairec_base/src/controller"
myFilter "pairec_base/src/filter"
"github.com/alibaba/pairec/v2"
"github.com/alibaba/pairec/v2/filter"
)
func main() {
// フィルターを登録し、フックに追加します。
pairec.AddStartHook(func() error {
filter.RegisterFilter("myFilter", &myFilter.MyFilter{})
return nil
})
pairec.Route("/api/rec/feed", &controller.FeedController{})
pairec.Run()
}3. 設定を更新し、カスタムフィルターを使用します。default キーを使用すると、特定のシーンに適用することも、すべてのシーンに適用することもできます。
// myFilter はカスタムフィルターです。
"FilterNames": {"default": ["myFilter", "item_exposure_filter"]}リコールのカスタマイズ
カスタムフィルターの作成と同様に、Recall インターフェイスを実装する必要があります。このインターフェイスは事前定義されているため、実装を提供するだけで済みます。
// Recall インターフェイス定義
type Recall interface {
GetCandidateItems(user *module.User, context *context.RecommendContext) []*module.Item
}package recall
import (
"github.com/alibaba/pairec/v2/context"
"github.com/alibaba/pairec/v2/module"
)
type MyRecall struct {
}
func (r *MyRecall) GetCandidateItems(user *module.User, context *context.RecommendContext) []*module.Item {
ret := make([]*module.Item, 0)
// 具体的なリコールロジック。
...
return ret
}
次に、recall.RegisterRecall を呼び出してコンポーネントを登録します。
package main
import (
"pairec_base/src/controller"
recall2 "pairec_base/src/recall"
"github.com/alibaba/pairec/v2"
"github.com/alibaba/pairec/v2/service/recall"
)
func main() {
pairec.AddStartHook(func() error {
recall.RegisterRecall("myRecall", &recall2.MyRecall{})
return nil
})
pairec.Route("/api/rec/feed", &controller.FeedController{})
pairec.Run()
}
シーン設定で、リコールコンポーネントの名前を指定します。
"SceneConfs": {
"home_feed": {
"default": {
// myRecall に設定します。
"RecallNames": ["myRecall"]
}
}
}ソートのカスタマイズ
カスタムソートコンポーネントの作成手順は、フィルターの作成と同様です。
1. 事前定義されている ISort インターフェイスを実装する型を作成します。
type ISort interface {
// ソートインターフェイスの実装。データは sortData.Data に格納されています。
Sort(sortData *SortData) error
}package sort
import (
"fmt"
"github.com/alibaba/pairec/v2/module"
"github.com/alibaba/pairec/v2/sort"
)
type MySort struct {
}
func (s *MySort) Sort(data *sort.SortData) error {
fmt.Println("my sort")
items := data.Data.([]*module.Item)
// 具体的なソートロジック。
...
data.Data = items
return nil
}2. サーバーの起動前に、カスタムソートコンポーネントを登録します。
package main
import (
"pairec_base/src/controller"
sort2 "pairec_base/src/sort"
"github.com/alibaba/pairec/v2"
"github.com/alibaba/pairec/v2/sort"
)
func main() {
pairec.AddStartHook(func() error {
sort.RegisterSort("mySort", &sort2.MySort{})
return nil
})
pairec.Route("/api/rec/feed", &controller.FeedController{})
pairec.Run()
}
3. 設定を変更して、ソートコンポーネントを使用します。
"SortNames": {"default": ["mySort", "item_score"]}スコアブースティングのカスタマイズ
モデルスコアリング後に、カスタム戦略を適用してモデルスコアを調整できます。そのためには、boostFunc のシグネチャに一致する関数を定義します。
type boostFunc func(score float64, user *module.User, item *module.Item, context *context.RecommendContext) float64package rank
import (
"github.com/alibaba/pairec/v2/context"
"github.com/alibaba/pairec/v2/module"
)
// ユーザー定義関数
func BoostScore(score float64, user *module.User, item *module.Item, context *context.RecommendContext) float64 {
// ここにスコアブースティングロジックを実装します。
vTagId, err := item.IntProperty("vtag_id")
if err == nil && vTagId == 20214 {
return score * 1.3
}
return score
}登録:
package main
import(
prank "github.com/alibaba/pairec/v2/service/rank"
"pairec_base/src/rank"
"github.com/alibaba/pairec/v2"
)
func main(){
pairec.AddStartHook(func() error {
prank.SetBoostFunc(rank.BoostScore)
})
pairec.Route("/api/rec/feed", &controller.FeedController{})
pairec.Run()
}
特徴量ロードのカスタマイズ
特徴量ロードをカスタマイズできます。FeatureConfs を使用するだけでなく、カスタム関数を使用して特徴量をロードすることも可能です。
特徴量ロード関数のシグネチャは次のとおりです:
type LoadFeatureFunc func(user *module.User, items []*module.Item, context *context.RecommendContext)func RegisterLoadFeatureFunc(sceneName string, f LoadFeatureFunc) この関数を使用して、関数を登録します。登録はシーン単位です。
例:
package feature
import (
"github.com/alibaba/pairec/v2/context"
"github.com/alibaba/pairec/v2/module"
)
func LoadRealTimeFeatures(user *module.User, items []*module.Item, context *context.RecommendContext) {
// ユーザーにプロパティを追加します。
user.AddProperty("userAge", 30)
// 各アイテムにプロパティを追加します。
for _, item := range items {
item.AddProperty("count", 5)
}
}登録:
package main
import(
pfeature "github.com/alibaba/pairec/v2/service/feature"
"github.com/alibaba/pairec/v2"
)
func main(){
pairec.AddStartHook(func() error {
// home_feed はシーン名です。
pfeature.RegisterLoadFeatureFunc("home_feed", feature.LoadRealTimeFeatures)
return nil
})
pairec.Route("/api/rec/feed", &controller.FeedController{})
pairec.Run()
}特徴量エンジニアリングのカスタマイズ
特徴量ロード後は、新しい特徴量の生成、特徴量の組み合わせの作成、ユーザー特徴量とアイテム特徴量の統合処理など、特徴量エンジニアリングのタスクを実行することが一般的です。エンジンには、事前定義された特徴量処理オペレーターが用意されています。これらのオペレーターが要件を満たさない場合は、カスタムロジックを実装できます。
関数を使用してカスタムの特徴量エンジニアリングを実装することもできます。
特徴量エンジニアリング関数の定義は、次のとおりです:
type FeatureFunc func(user *module.User, items []*module.Item, context *context.RecommendContext) []*module.Itemfunc RegisterFeatureFunc(sceneName string, f FeatureFunc) この関数を使用して、関数を登録します。この登録もシーン単位です。
例:
特徴量処理関数を定義します:
package feature
import (
"github.com/alibaba/pairec/v2/context"
"github.com/alibaba/pairec/v2/module"
)
// ここで返されるアイテムの数を減らすこともできます。
func MyFeatureFunc(user *module.User, items []*module.Item, context *context.RecommendContext) []*module.Item {
if len(items) < 400 {
return items
}
// 特徴量処理ロジック。
return items[:400]
}main.go で関数を登録します:
package main
import(
pfeature "github.com/alibaba/pairec/v2/service/feature"
"github.com/alibaba/pairec/v2"
)
func main(){
pairec.AddStartHook(func() error {
// home_feed はシーン名です。
pfeature.RegisterFeatureFunc("home_feed", feature.MyFeatureFunc)
return nil
})
pairec.Route("/api/rec/feed", &controller.FeedController{})
pairec.Run()
}ランクのカスタマイズ
カスタムランクコンポーネントを組み込むには、IRank インターフェイスを実装します。このインターフェイスには、カスタムランキングの対象アイテムを選択する Filter と、選択したアイテムに対してランキングロジックを適用する Rank の 2 つのメソッドが必要です。
IRank インターフェイスは次のとおりです:
type IRank interface {
// Filter は、カスタムランクロジックをアイテムに適用するかどうかを判定します。
Filter(User *module.User, item *module.Item, context *context.RecommendContext) bool
// Rank は、選択されたアイテムにランキングロジックを適用します。
Rank(User *module.User, items []*module.Item, requestData []map[string]interface{}, context *context.RecommendContext)
}func RegisterRank(sceneName string, ranks ...IRank)この関数を使用して、シーンごとに異なるカスタムランクコンポーネントを登録します。
1 つのシーンに複数のランクコンポーネントを登録できます。これにより、異なるタイプのアイテムに対して異なるランキングアルゴリズムを適用できます。
次の例は、この仕組みを示しています:
type MyRank struct {
index int
}
func NewMyRank() *MyRank {
return &MyRank{
index: 0,
}
}
func (r *MyRank) Filter(User *module.User, item *module.Item, context *context.RecommendContext) bool {
r.index++
item.AddProperty("other", "other")
if r.index%2 == 0 {
item.AddProperty("index", r.index)
return true
}
return false
}
func (r *MyRank) Rank(User *module.User, items []*module.Item, requestData []map[string]interface{}, context *context.RecommendContext) {
fmt.Println("rank len", len(items))
for _, item := range items {
if f, err := item.FloatProperty("index"); err == nil {
item.Score = float64(f * 10)
}
}
r.index = 0
}アイテムがどのカスタムランクコンポーネントにも一致しない場合、エンジンは RankConf のモデル設定を使用します。
完全な例
recall.go ファイルには、カスタムリコールコンポーネントの例が含まれています。
package recall
import (
"fmt"
"math/rand"
"github.com/alibaba/pairec/v2/context"
"github.com/alibaba/pairec/v2/module"
)
type MyRecall struct {
}
func (r *MyRecall) GetCandidateItems(user *module.User, context *context.RecommendContext) []*module.Item {
fmt.Println("MyRecall is running!")
ret := make([]*module.Item, 0)
for i := 1; i < 100; i++ {
item := module.NewItem(fmt.Sprintf("item_%d", i))
item.Score = rand.Float64()
item.AddProperty("title", fmt.Sprintf("News_%d", i))
item.AddProperty("count", i)
item.RetrieveId = "myRecall"
ret = append(ret, item)
}
return ret
}
filter.go ファイルには、カスタムフィルターの例が含まれています。
package filter
import (
"fmt"
"github.com/alibaba/pairec/v2/filter"
"github.com/alibaba/pairec/v2/module"
)
type MyFilter struct {
}
func (f *MyFilter) Filter(data *filter.FilterData) error {
fmt.Println("MyFilter is running!")
items := data.Data.([]*module.Item)
newItems := make([]*module.Item, 0)
for _, item := range items {
if item.Score > 0.2 {
newItems = append(newItems, item)
}
}
fmt.Printf("MyFilter: kept %d items\n", len(newItems))
data.Data = newItems
return nil
}rank.go ファイルには、カスタムランクコンポーネントの例が含まれています。MyRank や MyRank1 のように、複数のランクコンポーネントを定義できます。
package rank
import (
"fmt"
"github.com/alibaba/pairec/v2/context"
"github.com/alibaba/pairec/v2/module"
)
type MyRank struct {
}
func (rank *MyRank) Filter(User *module.User, item *module.Item, context *context.RecommendContext) bool {
//fmt.Println("MyRank Filter ")
if item != nil && item.Score <= 0.5 {
return true
}
return false
}
func (rank *MyRank) Rank(User *module.User, items []*module.Item, requestData []map[string]interface{}, context *context.RecommendContext) {
fmt.Println("MyRank is running")
for _, item := range items {
item.Score = BoostScore(item.Score, User, item, context)
}
}
rank1.go
package rank
import (
"fmt"
"github.com/alibaba/pairec/v2/context"
"github.com/alibaba/pairec/v2/module"
)
type MyRank1 struct{}
func (r *MyRank1) Filter(User *module.User, item *module.Item, context *context.RecommendContext) bool {
//fmt.Println("MyRank1 Filter is running")
if item != nil && 0.5 < item.Score && item.Score < 1 {
return true
}
return false
}
func (r *MyRank1) Rank(User *module.User, items []*module.Item, requestData []map[string]interface{}, context *context.RecommendContext) {
fmt.Println("MyRank1 is running")
for _, item := range items {
if item.Score >= 0.9 {
item.Score *= 1.1
} else if item.Score >= 0.8 {
item.Score *= 1.2
} else if item.Score >= 0.7 {
item.Score *= 1.3
} else {
item.Score *= 1.4
}
}
}
boost.go ファイルには、ランクベースのブースト関数の例が含まれています。この関数は、カスタムランクコンポーネント内から手動で呼び出す必要があります。エンジンは、標準のランクコンポーネントに対しては自動的に呼び出します。
package rank
import (
"github.com/alibaba/pairec/v2/context"
"github.com/alibaba/pairec/v2/module"
)
func BoostScore(score float64, user *module.User, item *module.Item, ctx *context.RecommendContext) float64 {
//fmt.Println("BoostScore is running!")
return score * 1.5
}
feature.go ファイルには、カスタム特徴量関数の例が含まれています。
package feature
import (
"fmt"
"github.com/alibaba/pairec/v2/context"
"github.com/alibaba/pairec/v2/module"
)
// LoadRealTimeFeatures: カスタム特徴量ロード関数。
func LoadRealTimeFeatures(user *module.User, items []*module.Item, context *context.RecommendContext) {
// ユーザーにプロパティを追加します。
user.AddProperty("userAge", 30)
// 各アイテムにプロパティを追加します。
for _, item := range items {
item.AddProperty("count", 5)
}
}
// MyFeatureFunc: カスタム特徴量処理。
func MyFeatureFunc(user *module.User, items []*module.Item, context *context.RecommendContext) []*module.Item {
fmt.Println("MyFeatureFunc is running")
if len(items) < context.Size {
fmt.Printf("items less size :%d \n", len(items))
return items
}
item2 := make([]*module.Item, 0, len(items))
for _, item := range items {
if v, ok := item.Properties["userAge"]; ok {
if age, o := v.(int); o && age > 18 {
item2 = append(item2, item)
}
}
}
if len(item2) < context.Size {
fmt.Printf("item2 : %d \n", len(item2))
return item2
} else {
fmt.Printf("items[:context.Size] : %d \n", len(items))
return items[:context.Size]
}
}
main.go ファイルには、すべてのカスタムコンポーネントを登録する main 関数が記述されています。
package main
import (
"pairec_base/src/controller"
feature2 "pairec_base/src/feature"
filter2 "pairec_base/src/filter"
rank2 "pairec_base/src/rank"
recall2 "pairec_base/src/recall"
sort2 "pairec_base/src/sort"
"github.com/alibaba/pairec/v2"
"github.com/alibaba/pairec/v2/filter"
"github.com/alibaba/pairec/v2/service/feature"
"github.com/alibaba/pairec/v2/service/rank"
"github.com/alibaba/pairec/v2/service/recall"
"github.com/alibaba/pairec/v2/sort"
)
func main() {
pairec.AddStartHook(func() error {
recall.RegisterRecall("myRecall", &recall2.MyRecall{})
filter.RegisterFilter("myFilter", &filter2.MyFilter{})
sort.RegisterSort("mySort", &sort2.MySort{})
rank.RegisterRank("home_feed", &rank2.MyRank{}, &rank2.MyRank1{})
rank.SetBoostFunc(rank2.BoostScore)
feature.RegisterLoadFeatureFunc("home_feed", feature2.LoadRealTimeFeatures)
feature.RegisterFeatureFunc("home_feed", feature2.MyFeatureFunc)
return nil
})
pairec.Route("/api/rec/feed", &controller.FeedController{})
pairec.Run()
}
次のコードは、config.json ファイル内の設定例です。
{
"RunMode": "product",
"ListenConf": {
"HttpAddr": "",
"HttpPort": 8000
},
"FilterConfs": [
],
"RecallConfs": [
{
"Name": "mock_recall",
"RecallType": "MockRecall",
"RecallCount": 200
}
],
"SortNames": {
"default": [
"mySort"
]
},
"FilterNames": {
"default": [
"myFilter"
]
},
"AlgoConfs": [
],
"KafkaConfs": {
},
"RedisConfs": {
},
"SceneConfs": {
"home_feed": {
"default": {
"RecallNames": ["myRecall"],
"FilterNames": ["myFilter"],
"SortNames":["mySort"]
}
}
},
"LogConf": {
"RetensionDays": 3,
"DiskSize": 20,
"LogLevel": "INFO"
},
"RankConf": {
},
"FeatureConfs": {
}
}