├── .gitignore
├── Data
└── UserBehaviour.txt
├── Example
├── App.config
├── Example.csproj
├── MainForm.Designer.cs
├── MainForm.cs
├── MainForm.resx
├── Program.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── Settings.Designer.cs
│ └── Settings.settings
├── RecommenderTests.cs
└── icon.ico
├── LICENSE
├── README.md
├── User Behavior.sln
└── User Behavior
├── Abstractions
├── IClassifier.cs
├── IComparer.cs
├── IRater.cs
└── ISplitter.cs
├── Comparers
├── CoRatedCosineUserComparer.cs
├── CorrelationUserComparer.cs
├── CosineUserComparer.cs
├── RootMeanSquareUserComparer.cs
└── SimpleCountUserComparer.cs
├── Mathematics
├── Matrix.cs
├── SingularValueDecomposition.cs
└── SvdResult.cs
├── Objects
├── Article.cs
├── ArticleRating.cs
├── ArticleTag.cs
├── ArticleTagCounts.cs
├── ScoreResults.cs
├── Suggestion.cs
├── Tag.cs
├── TestResults.cs
├── User.cs
├── UserAction.cs
├── UserActionTag.cs
├── UserArticleRatings.cs
└── UserArticleRatingsTable.cs
├── Parsers
├── DaySplitter.cs
├── UserBehaviorDatabase.cs
├── UserBehaviorDatabaseParser.cs
└── UserBehaviorTransformer.cs
├── Properties
├── AssemblyInfo.cs
├── Resources.Designer.cs
├── Resources.resx
├── Settings.Designer.cs
└── Settings.settings
├── Raters
├── LinearRater.cs
├── SimpleRater.cs
└── WeightedRater.cs
├── Recommender.csproj
└── Recommenders
├── ClassifierExtensions.cs
├── HybridRecommender.cs
├── ItemCollaborativeFilterRecommender.cs
├── MatrixFactorizationRecommender.cs
├── RandomRecommender.cs
└── UserCollaborativeFilterRecommender.cs
/.gitignore:
--------------------------------------------------------------------------------
1 | *.cache
2 | *.suo
3 | *.manifest
4 | *.key
5 | bin/
6 | obj/
7 | packages/
8 | *.vspx
9 | *.diagsession
--------------------------------------------------------------------------------
/Example/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Example/Example.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {A27FF3EC-EE8E-4EE8-B6F2-60572619CEC7}
8 | WinExe
9 | Properties
10 | Example
11 | RecommenderExample
12 | v4.5.2
13 | 512
14 | true
15 |
16 |
17 | x64
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | x64
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 |
35 |
36 | icon.ico
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | Form
54 |
55 |
56 | MainForm.cs
57 |
58 |
59 |
60 |
61 |
62 | MainForm.cs
63 |
64 |
65 | ResXFileCodeGenerator
66 | Resources.Designer.cs
67 | Designer
68 |
69 |
70 | True
71 | Resources.resx
72 |
73 |
74 | SettingsSingleFileGenerator
75 | Settings.Designer.cs
76 |
77 |
78 | True
79 | Settings.settings
80 | True
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | {db5a3fef-d18f-4901-9b4f-959e403bd21b}
89 | Recommender
90 |
91 |
92 |
93 |
94 |
95 |
96 |
103 |
--------------------------------------------------------------------------------
/Example/MainForm.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace Example
2 | {
3 | partial class MainForm
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
32 | this.ofdGetUserBehaviors = new System.Windows.Forms.OpenFileDialog();
33 | this.bgWorker = new System.ComponentModel.BackgroundWorker();
34 | this.groupTrain = new System.Windows.Forms.GroupBox();
35 | this.btnLoadTrain = new System.Windows.Forms.Button();
36 | this.groupScore = new System.Windows.Forms.GroupBox();
37 | this.txtScoreArticle = new System.Windows.Forms.TextBox();
38 | this.label2 = new System.Windows.Forms.Label();
39 | this.txtScoreUser = new System.Windows.Forms.TextBox();
40 | this.label1 = new System.Windows.Forms.Label();
41 | this.btnScore = new System.Windows.Forms.Button();
42 | this.groupRecommend = new System.Windows.Forms.GroupBox();
43 | this.txtRecommendNum = new System.Windows.Forms.TextBox();
44 | this.label3 = new System.Windows.Forms.Label();
45 | this.txtRecommendUser = new System.Windows.Forms.TextBox();
46 | this.label4 = new System.Windows.Forms.Label();
47 | this.btnRecommend = new System.Windows.Forms.Button();
48 | this.rtbOutput = new System.Windows.Forms.RichTextBox();
49 | this.bgScore = new System.ComponentModel.BackgroundWorker();
50 | this.bgRecommend = new System.ComponentModel.BackgroundWorker();
51 | this.groupTrain.SuspendLayout();
52 | this.groupScore.SuspendLayout();
53 | this.groupRecommend.SuspendLayout();
54 | this.SuspendLayout();
55 | //
56 | // ofdGetUserBehaviors
57 | //
58 | this.ofdGetUserBehaviors.FileName = "UserBehaviour.txt";
59 | //
60 | // bgWorker
61 | //
62 | this.bgWorker.DoWork += new System.ComponentModel.DoWorkEventHandler(this.bgWorker_DoWork);
63 | this.bgWorker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.bgWorker_RunWorkerCompleted);
64 | //
65 | // groupTrain
66 | //
67 | this.groupTrain.Controls.Add(this.btnLoadTrain);
68 | this.groupTrain.Location = new System.Drawing.Point(12, 12);
69 | this.groupTrain.Name = "groupTrain";
70 | this.groupTrain.Size = new System.Drawing.Size(282, 49);
71 | this.groupTrain.TabIndex = 2;
72 | this.groupTrain.TabStop = false;
73 | this.groupTrain.Text = "Train the Classifier";
74 | //
75 | // btnLoadTrain
76 | //
77 | this.btnLoadTrain.Location = new System.Drawing.Point(6, 19);
78 | this.btnLoadTrain.Name = "btnLoadTrain";
79 | this.btnLoadTrain.Size = new System.Drawing.Size(270, 23);
80 | this.btnLoadTrain.TabIndex = 0;
81 | this.btnLoadTrain.Text = "Load Data and Train";
82 | this.btnLoadTrain.UseVisualStyleBackColor = true;
83 | this.btnLoadTrain.Click += new System.EventHandler(this.btnLoadTrain_Click);
84 | //
85 | // groupScore
86 | //
87 | this.groupScore.Controls.Add(this.txtScoreArticle);
88 | this.groupScore.Controls.Add(this.label2);
89 | this.groupScore.Controls.Add(this.txtScoreUser);
90 | this.groupScore.Controls.Add(this.label1);
91 | this.groupScore.Controls.Add(this.btnScore);
92 | this.groupScore.Enabled = false;
93 | this.groupScore.Location = new System.Drawing.Point(12, 67);
94 | this.groupScore.Name = "groupScore";
95 | this.groupScore.Size = new System.Drawing.Size(138, 101);
96 | this.groupScore.TabIndex = 2;
97 | this.groupScore.TabStop = false;
98 | this.groupScore.Text = "Score an Article";
99 | //
100 | // txtScoreArticle
101 | //
102 | this.txtScoreArticle.Location = new System.Drawing.Point(67, 45);
103 | this.txtScoreArticle.Name = "txtScoreArticle";
104 | this.txtScoreArticle.Size = new System.Drawing.Size(65, 20);
105 | this.txtScoreArticle.TabIndex = 4;
106 | //
107 | // label2
108 | //
109 | this.label2.AutoSize = true;
110 | this.label2.Location = new System.Drawing.Point(11, 49);
111 | this.label2.Name = "label2";
112 | this.label2.Size = new System.Drawing.Size(50, 13);
113 | this.label2.TabIndex = 3;
114 | this.label2.Text = "Article ID";
115 | //
116 | // txtScoreUser
117 | //
118 | this.txtScoreUser.Location = new System.Drawing.Point(67, 19);
119 | this.txtScoreUser.Name = "txtScoreUser";
120 | this.txtScoreUser.Size = new System.Drawing.Size(65, 20);
121 | this.txtScoreUser.TabIndex = 2;
122 | //
123 | // label1
124 | //
125 | this.label1.AutoSize = true;
126 | this.label1.Location = new System.Drawing.Point(18, 23);
127 | this.label1.Name = "label1";
128 | this.label1.Size = new System.Drawing.Size(43, 13);
129 | this.label1.TabIndex = 1;
130 | this.label1.Text = "User ID";
131 | //
132 | // btnScore
133 | //
134 | this.btnScore.Location = new System.Drawing.Point(6, 71);
135 | this.btnScore.Name = "btnScore";
136 | this.btnScore.Size = new System.Drawing.Size(126, 23);
137 | this.btnScore.TabIndex = 0;
138 | this.btnScore.Text = "Score";
139 | this.btnScore.UseVisualStyleBackColor = true;
140 | this.btnScore.Click += new System.EventHandler(this.btnScore_Click);
141 | //
142 | // groupRecommend
143 | //
144 | this.groupRecommend.Controls.Add(this.txtRecommendNum);
145 | this.groupRecommend.Controls.Add(this.label3);
146 | this.groupRecommend.Controls.Add(this.txtRecommendUser);
147 | this.groupRecommend.Controls.Add(this.label4);
148 | this.groupRecommend.Controls.Add(this.btnRecommend);
149 | this.groupRecommend.Enabled = false;
150 | this.groupRecommend.Location = new System.Drawing.Point(156, 67);
151 | this.groupRecommend.Name = "groupRecommend";
152 | this.groupRecommend.Size = new System.Drawing.Size(138, 101);
153 | this.groupRecommend.TabIndex = 2;
154 | this.groupRecommend.TabStop = false;
155 | this.groupRecommend.Text = "Recommend Articles";
156 | //
157 | // txtRecommendNum
158 | //
159 | this.txtRecommendNum.Location = new System.Drawing.Point(67, 45);
160 | this.txtRecommendNum.Name = "txtRecommendNum";
161 | this.txtRecommendNum.Size = new System.Drawing.Size(65, 20);
162 | this.txtRecommendNum.TabIndex = 4;
163 | //
164 | // label3
165 | //
166 | this.label3.AutoSize = true;
167 | this.label3.Location = new System.Drawing.Point(10, 49);
168 | this.label3.Name = "label3";
169 | this.label3.Size = new System.Drawing.Size(51, 13);
170 | this.label3.TabIndex = 3;
171 | this.label3.Text = "# Articles";
172 | //
173 | // txtRecommendUser
174 | //
175 | this.txtRecommendUser.Location = new System.Drawing.Point(67, 19);
176 | this.txtRecommendUser.Name = "txtRecommendUser";
177 | this.txtRecommendUser.Size = new System.Drawing.Size(65, 20);
178 | this.txtRecommendUser.TabIndex = 2;
179 | //
180 | // label4
181 | //
182 | this.label4.AutoSize = true;
183 | this.label4.Location = new System.Drawing.Point(18, 23);
184 | this.label4.Name = "label4";
185 | this.label4.Size = new System.Drawing.Size(43, 13);
186 | this.label4.TabIndex = 1;
187 | this.label4.Text = "User ID";
188 | //
189 | // btnRecommend
190 | //
191 | this.btnRecommend.Location = new System.Drawing.Point(6, 71);
192 | this.btnRecommend.Name = "btnRecommend";
193 | this.btnRecommend.Size = new System.Drawing.Size(126, 23);
194 | this.btnRecommend.TabIndex = 0;
195 | this.btnRecommend.Text = "Recommend";
196 | this.btnRecommend.UseVisualStyleBackColor = true;
197 | this.btnRecommend.Click += new System.EventHandler(this.btnRecommend_Click);
198 | //
199 | // rtbOutput
200 | //
201 | this.rtbOutput.Font = new System.Drawing.Font("Consolas", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
202 | this.rtbOutput.Location = new System.Drawing.Point(12, 174);
203 | this.rtbOutput.Name = "rtbOutput";
204 | this.rtbOutput.ReadOnly = true;
205 | this.rtbOutput.Size = new System.Drawing.Size(282, 170);
206 | this.rtbOutput.TabIndex = 3;
207 | this.rtbOutput.Text = "";
208 | //
209 | // bgScore
210 | //
211 | this.bgScore.DoWork += new System.ComponentModel.DoWorkEventHandler(this.bgScore_DoWork);
212 | this.bgScore.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.bgScore_RunWorkerCompleted);
213 | //
214 | // bgRecommend
215 | //
216 | this.bgRecommend.DoWork += new System.ComponentModel.DoWorkEventHandler(this.bgRecommend_DoWork);
217 | this.bgRecommend.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.bgRecommend_RunWorkerCompleted);
218 | //
219 | // MainForm
220 | //
221 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
222 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
223 | this.ClientSize = new System.Drawing.Size(306, 356);
224 | this.Controls.Add(this.rtbOutput);
225 | this.Controls.Add(this.groupRecommend);
226 | this.Controls.Add(this.groupScore);
227 | this.Controls.Add(this.groupTrain);
228 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
229 | this.Name = "MainForm";
230 | this.Text = "Recommender - Scott Clayton";
231 | this.groupTrain.ResumeLayout(false);
232 | this.groupScore.ResumeLayout(false);
233 | this.groupScore.PerformLayout();
234 | this.groupRecommend.ResumeLayout(false);
235 | this.groupRecommend.PerformLayout();
236 | this.ResumeLayout(false);
237 |
238 | }
239 |
240 | #endregion
241 | private System.Windows.Forms.OpenFileDialog ofdGetUserBehaviors;
242 | private System.ComponentModel.BackgroundWorker bgWorker;
243 | private System.Windows.Forms.GroupBox groupTrain;
244 | private System.Windows.Forms.Button btnLoadTrain;
245 | private System.Windows.Forms.GroupBox groupScore;
246 | private System.Windows.Forms.TextBox txtScoreArticle;
247 | private System.Windows.Forms.Label label2;
248 | private System.Windows.Forms.TextBox txtScoreUser;
249 | private System.Windows.Forms.Label label1;
250 | private System.Windows.Forms.Button btnScore;
251 | private System.Windows.Forms.GroupBox groupRecommend;
252 | private System.Windows.Forms.TextBox txtRecommendNum;
253 | private System.Windows.Forms.Label label3;
254 | private System.Windows.Forms.TextBox txtRecommendUser;
255 | private System.Windows.Forms.Label label4;
256 | private System.Windows.Forms.Button btnRecommend;
257 | private System.Windows.Forms.RichTextBox rtbOutput;
258 | private System.ComponentModel.BackgroundWorker bgScore;
259 | private System.ComponentModel.BackgroundWorker bgRecommend;
260 | }
261 | }
262 |
263 |
--------------------------------------------------------------------------------
/Example/MainForm.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Data;
5 | using System.Drawing;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 | using System.Windows.Forms;
11 | using UserBehavior.Abstractions;
12 | using UserBehavior.Comparers;
13 | using UserBehavior.Objects;
14 | using UserBehavior.Parsers;
15 | using UserBehavior.Raters;
16 | using UserBehavior.Recommenders;
17 |
18 | namespace Example
19 | {
20 | public partial class MainForm : Form
21 | {
22 | IRecommender recommender;
23 | string savedModel = "recommender.dat";
24 |
25 | public MainForm()
26 | {
27 | InitializeComponent();
28 |
29 | IRater rate = new LinearRater(-4, 2, 3, 1);
30 | IComparer compare = new CorrelationUserComparer();
31 |
32 | recommender = new UserCollaborativeFilterRecommender(compare, rate, 50);
33 |
34 | if (File.Exists(savedModel))
35 | {
36 | try
37 | {
38 | recommender.Load(savedModel);
39 | rtbOutput.Text = "Loaded model from file";
40 | EnableForm(true);
41 | }
42 | catch
43 | {
44 | rtbOutput.Text = "Saved model is corrupt";
45 | }
46 | }
47 |
48 | //RecommenderTests.TestAllRecommenders();
49 | //RecommenderTests.FindBestRaterWeights();
50 | }
51 |
52 | private void btnLoadTrain_Click(object sender, EventArgs e)
53 | {
54 | if (!bgWorker.IsBusy && ofdGetUserBehaviors.ShowDialog() == DialogResult.OK)
55 | {
56 | rtbOutput.Text = "Training...";
57 | bgWorker.RunWorkerAsync(ofdGetUserBehaviors.FileName);
58 | EnableForm(false);
59 | }
60 | }
61 |
62 | private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
63 | {
64 | UserBehaviorDatabaseParser parser = new UserBehaviorDatabaseParser();
65 | UserBehaviorDatabase db = parser.LoadUserBehaviorDatabase(e.Argument as string);
66 |
67 | recommender.Train(db);
68 | recommender.Save(savedModel);
69 | }
70 |
71 | private void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
72 | {
73 | rtbOutput.Text = "Finished training\r\nModel saved to file";
74 | EnableForm(true);
75 | }
76 |
77 | private void EnableForm(bool enabled)
78 | {
79 | groupRecommend.Enabled = enabled;
80 | groupScore.Enabled = enabled;
81 | groupTrain.Enabled = enabled;
82 | }
83 |
84 | private void btnScore_Click(object sender, EventArgs e)
85 | {
86 | if (!bgScore.IsBusy)
87 | {
88 | int userId;
89 | int articleId;
90 | int.TryParse(txtScoreUser.Text, out userId);
91 | int.TryParse(txtScoreArticle.Text, out articleId);
92 |
93 | if (userId >= 1 && userId <= 3000 && articleId >= 1 && articleId <= 3000)
94 | {
95 | bgScore.RunWorkerAsync(new GetRating { UserID = userId, ArticleID = articleId });
96 | EnableForm(false);
97 | }
98 | else
99 | {
100 | MessageBox.Show("Invalid User ID or Article ID!");
101 | }
102 | }
103 | }
104 |
105 | private void bgScore_DoWork(object sender, DoWorkEventArgs e)
106 | {
107 | GetRating args = e.Argument as GetRating;
108 | e.Result = recommender.GetRating(args.UserID, args.ArticleID);
109 | }
110 |
111 | private void bgScore_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
112 | {
113 | rtbOutput.Text = "Predicted rating: " + ((double)e.Result).ToString("0.00");
114 | EnableForm(true);
115 | }
116 |
117 | private void btnRecommend_Click(object sender, EventArgs e)
118 | {
119 | if (!bgRecommend.IsBusy)
120 | {
121 | int userId;
122 | int ratings;
123 | int.TryParse(txtRecommendUser.Text, out userId);
124 | int.TryParse(txtRecommendNum.Text, out ratings);
125 |
126 | if (userId >= 1 && userId <= 3000 && ratings >= 1 && ratings <= 100)
127 | {
128 | bgRecommend.RunWorkerAsync(new GetRecommendation { UserID = userId, Ratings = ratings });
129 | EnableForm(false);
130 | }
131 | else
132 | {
133 | MessageBox.Show("Invalid User ID or Recommendation Count!");
134 | }
135 | }
136 | }
137 |
138 | private void bgRecommend_DoWork(object sender, DoWorkEventArgs e)
139 | {
140 | GetRecommendation args = e.Argument as GetRecommendation;
141 | e.Result = recommender.GetSuggestions(args.UserID, args.Ratings);
142 | }
143 |
144 | private void bgRecommend_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
145 | {
146 | List suggestions = e.Result as List;
147 |
148 | rtbOutput.Text = "Recommendations:";
149 | foreach (Suggestion suggestion in suggestions)
150 | {
151 | rtbOutput.Text += "\r\n" + suggestion.ArticleID;
152 | }
153 |
154 | EnableForm(true);
155 | }
156 | }
157 |
158 | class GetRating
159 | {
160 | public int UserID { get; set; }
161 | public int ArticleID { get; set; }
162 | }
163 |
164 | class GetRecommendation
165 | {
166 | public int UserID { get; set; }
167 | public int Ratings { get; set; }
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/Example/MainForm.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | 17, 17
122 |
123 |
124 | 183, 17
125 |
126 |
127 | 289, 17
128 |
129 |
130 | 385, 17
131 |
132 |
133 |
134 |
135 | AAABAAMAMDAAAAEAIACoJQAANgAAACAgAAABACAAqBAAAN4lAAAQEAAAAQAgAGgEAACGNgAAKAAAADAA
136 | AABgAAAAAQAgAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
137 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABtxeCQXcXScD3FxRAdtbeQDb
138 | WpoA21qvANtaugDbWrsA21qxANtanAHbW30D3FxUBdxdKwbcXgsAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
139 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
140 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABtxeBgXcXS4B21tzAdtatQbc
141 | XuAV3mf1KOF0/j3kgf9I5Yn/XOiW/17ol/9L5or/P+SD/yrhdf4V3mj2B9xf4wHbW7oB21t6BNxdNAbc
142 | XggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
143 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfcXggE3F1FAdtbpQjc
144 | X+go4XT+YOmY/5nxvf/I99v+4/vt/vL99//4/vr+/f/+//7//v/4/vv+8/33/+X77v7J99z+n/LB/2Tp
145 | m/8q4XX/Ct1h7AHbW60D3FxNB9xeCwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
146 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI3GABBdxdNALb
147 | W6UM3WLxROWG/5rxvv/f++v//P/9////////////////////////////////////////////////////
148 | //////////////z//f/j++3+pPLE/0rliv8Q3WX1AdtbrwXcXT0I3F8DAAAAAAAAAAAAAAAAAAAAAAAA
149 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfc
150 | Xw4D3FxzBtxe5Drjf/+n88b/8P31////////////////////////////////////////////////////
151 | //////////////////////////////////////////////T9+P+w9Mz/QuSF/wfcX+oD3Fx/B9xeEgAA
152 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
153 | AAAAAAAABtxeHwLbXKYW3mj6e+yq/+b77/7/////////////////////////////////////////////
154 | ////////////////////////////////////////////////////////////////////////7Pzz/4bu
155 | sf8b32z8AttbsgbcXigAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
156 | AAAAAAAAAAAAAAAAAAAG3F4sA9xcwSrhdf+x9Mz//P/9////////////////////////////////////
157 | ////////////////////////////////////////////////////////////////////////////////
158 | //////////////3//v+89tT+M+J7/wLbXM0F3F43AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
159 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbcXi0C21zKOeN//8z43v//////////////////////////////
160 | ////////////////////////////////////////////////////////////////////////////////
161 | ////////////////////////////////////////1fnk/kfliP8E3F3VBdxeOAAAAAAAAAAAAAAAAAAA
162 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABtxeIQPcXMI643//0/ni/v//////////////////
163 | //////////////////////////////////////////////////////////////f++v/5/vv/////////
164 | /////////////////////////////////////////////////////////////+H77P9K5Yr/Attczwbc
165 | XiwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH3F4OAttbqCzhdv/O+N//////////
166 | ///////////////////6/vz+/f/9/////////////////////////////////////////////////936
167 | 6f6q88j+/P/9////////////////////////////////////////////////////////////////////
168 | ///X+eX+N+N9/wLbW7gG3F4WAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvdYgED3Fx3Ft5o+7H0
169 | zf////////////////////////////n++/+99tT+9v75////////////////////////////////////
170 | /////////////+z88v5J5Yr+svTN/v//////////////////////////////////////////////////
171 | ////////////////////////wfbX/h/gbv4C3FyJCNxfBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXc
172 | XTgH3F/mge2u//3//f///////////////////////////7321f527Kb+/P/9////////////////////
173 | //////////////////////////////v+/f9V55H+KuF1/8/44P7/////////////////////////////
174 | /////////////////////////////////////////////5Dvt/8J3GDuBdxdSAAAAAAAAAAAAAAAAAAA
175 | AAAAAAAAB9xeCQLbW6o+5IL/6fzx////////////////////////////7f30/0fliP5+7az+////////
176 | //////////////////////////////////////////////////997av+ANtZ/2vrn/77/vz/////////
177 | //////////////////////////////////////////////////////////////D99f9N5oz/Attbuwbc
178 | XhAAAAAAAAAAAAAAAAAAAAAABNxdSw3dYvOp88f/////////////////////////////////lPC6/gbc
179 | Xv+f8cH+//////////////////////////////////////////////////////////+t9Mr+X+iX/sPt
180 | 1f79/v3/////////////////////////////////////////////////////////////////////////
181 | //+89dP/Ft5o+QPcXF0AAAAAAAAAAAAAAAAG3F4HAdtbq0blh//x/fb/////////////////////////
182 | ///7/fz/rOzH/XTqpf7P+eD+////////////////////////////////////////////////////////
183 | ///j9Or+h72h/rfUxf7i/O3+5PHq/eny7f7/////////////////////////////////////////////
184 | ///////////////////5/vv/WuiU/wHbW70G3F4OAAAAAAAAAAAF3F01Cdxg66Dywv//////////////
185 | ///////////////////2+ff/aaaH/mGigf6iybX+2enh/vf6+f//////////////////////////////
186 | /////////v///8vg1f5LlW/+RpFr/s3v3P6A7639ttnG/U+Xcv7H3tL+/v/+////////////////////
187 | ////////////////////////////////////////s/TO/w/dZPME3F1FAAAAAAAAAAAB21t8K+F2/+L7
188 | 7P7///////////////////////////3//v/w/fX+b6uM/gBnMP8FbDj/HntM/0yWcP6Lu6L+xt7S/u/2
189 | 8v/+//7////////////z+PX/lsGr/iB8Tf8Kbjv/rs++/oXvsP495YL9ttrH/g1wPf8ee0v/mcOu/vX5
190 | 9///////////////////////////////////////////////////////7fzz/znjf/8B21uQAAAAAAbc
191 | Xg0B21u8Zemb//3//v///////////////////////////+j88P+O8bf+ocy2/gdtOf8Cazb/AWo1/wBp
192 | M/8CajX/EnRC/zmLYf5zrI/+1+jf/vT49v5io4L+B205/wBoMv9UmXb+yPLa/iHgb/845H//uN3J/RFz
193 | Qf4AaTT/Cm87/2Skg/7g7eb//////////////////////////////////////////////////////3rs
194 | qf8D3FzMBtxeFwXcXS8I3F/lofLC/////////////////////////////////6vzyP4x43r+u+XN/iB7
195 | Tf8BajX/A2s2/wNrNv8Dazb/Amo1/wBnMf82iF3+0+rd/t3t5fwfe0z9AGkz/xN0Q/+62Mj+a+yg/gHb
196 | W/8v43n+v+TQ/Rx5Sv8CajX/Ams1/wFqNf82iV/+uNbH/vz+/f//////////////////////////////
197 | /////////////7f10P8O3WPuBNxdQQLcXFsY3mn4zvjf/v//////////////////////////+P76/1Xn
198 | kf4J3WD/qu7G/kmSbf4AaTP/A2s2/wNrNv8Dazb/AWo0/yN9T/+328f+ifGz/Mbp1foifU/9AGgy/2ek
199 | hf628s/+Ft5o/wPcXP8j4XH+vObO/iJ8Tv8BajX/A2s2/wNrNv8AaTP/FnZF/6vOvP7/////////////
200 | /////////////////////////////9z66f4i4HD8AdtbcgHbW4Yu4nj/6Pzw/v//////////////////
201 | ////////0vni/hrfa/8B21v/gO6t/nuxlv4AaTP/A2s2/wNrNv8CajX/EnNC/6bMuP6E77D+KuJ2/sPp
202 | 1PshfE79GnhI/8Dfzv5R6I/+A9xc/wTcXf8h4XD/vOjP/SR9UP4BajT/A2s2/wJrNv8CajX/UZh0/t/s
203 | 5f////////////////////////////////////////////L99v495IH/ANtanQDbWqdE5Yb/9v75/v//
204 | ////////////////////////7fzz/13olv8C21v/SueL/qTOuP4Jbjr/A2s2/wJrNf8Ibjr/ibmh/qDx
205 | wf4Q3mX/I+Fx/8nu2f4tglf+eK6T/qDxwv4M3WL/Bdxe/wXcXf8W32n+uOzO/DOGXP4AaTT/Amo1/wVs
206 | N/9ko4P+4PDo/uT87v7+//////////////////////////////////////////z//f9X55L/ANtavgDb
207 | Wrxb6JX//f/+/////////////////////////////////+b87/9L5or+HuBu/7LkyP4hfE7/AWo0/wJr
208 | Nf9sp4n+t/HP/iHgb/8C21v/JeFy/8vv2/9anHv/vOPO/jnkf/8D3Fz/Btxe/wXcXv8R3mX/tO7M/juK
209 | Yv8AaDL/CW47/3Osj/7l9ez+g+6v/rH0zP7/////////////////////////////////////////////
210 | //9k6Zv/ANta0gDbWsdg6Zj//v/+///////////////////////////////////////d+un+SuaK/pzt
211 | vv5Kk27+AGcx/06Wcv7D7dX+OOR+/gPcXP8F3F3/HeBt/sbv2P2918r+hO2v/gbcXv8G3F7/Btxe/wXc
212 | Xv8P3mT/sO7K/TyLY/0Jbjr/iLig/uH36v5j6pv+KuF1/t/66v7/////////////////////////////
213 | //////////////////947Kj/Adtb3ADbWshg6Zj//v/+////////////////////////////////////
214 | ////////1/nl/6r0yP52r5L+L4RY/sPk0v5U6JH+Attc/wTcXP8B21v/Ed5l/sz33fzU9+L+JeFy/gPc
215 | XP8F3F3/Bdxd/wPcXP8E3F3/oO7A/GOigv6XwKv+1/fk/k7njf4B21v/YemZ/vz//f//////////////
216 | //////////////////////////////////987Kr/Attb3QDbWr9d6Jb//f/+////////////////////
217 | //////////////////////////////T/+P7C3M/+t9nH/m7sof4F3F3/Ed1l/yvhdv5V55H+iu6z/uv8
218 | 8v7j++3/pvLG/ZnxvfyV8Lv/lPC6/4Pur/x37Kf8yPbb/t7q5P7F9tn+OuSA/gPcXP8J3GD/qPPG/v//
219 | //////////////////////////////////////////////////5m6Zz+ANta1ADbWqtH5Yj/9/76/v//
220 | ///////////////////////////////////////////////////+/v7/v/bV/mjqnf6T8Ln+wvbY/uX7
221 | 7v76/vz///////////////////////////////////////////////////////r+/P9a6JT+Adtb/wTc
222 | Xf8o4XT+4Pvr/v////////////////////////////////////////////////3//v9b6JX/ANtawgDb
223 | Wowx4nr/6/zy/v/////////////////////////////////////////////////////+////9f75/vz/
224 | /f///////////////////////////////////////////////////////////////////////v7+//b4
225 | 9/2f8cH+Ct1g/wLbW/9i6Zn+/P/9//////////////////////////////////////////////////T9
226 | +P5B5IT/ANtapALbXGMb32v60/nj/v//////////////////////////////////////////////////
227 | ////////////////////////////////////////////////////////////////////////////////
228 | ////////+fz6/6fLuP3a9OX+NuN9/gncYP+t88r+////////////////////////////////////////
229 | /////////////+H76/4m4HP9AdtbegXcXTYK3WDpqvPI////////////////////////////////////
230 | ////////////////////////////////////////////////////////////////////////////////
231 | ////////////////////////+fv6/12gfv6+2Mr+jfC1/i3hd//k++7/////////////////////////
232 | /////////////////////////////7/21f8R3WXyA9xcSQbcXhEB21vFcOui////////////////////
233 | ////////////////////////////////////////////////////////////////////////////////
234 | ////////////////////////////////////////8vf1/jeKYP5ko4P+1fjj/o3vtf78//3/////////
235 | /////////////////////////////////////////////4XusP8E3F3UBtxeHQAAAAAB21uINeN8/+r8
236 | 8f//////////////////////////////////////////////////////////////////////////////
237 | ////////////////////////////////////////////////////////7vXy/zOHXP8aeEj/zuTZ/vT/
238 | +f7/////////////////////////////////////////////////////8v33/0Plhf8A21qcBtxeAgAA
239 | AAAE3F1ADt1j8bD0zP//////////////////////////////////////////////////////////////
240 | ////////////////////////////////////////////////////////////////////////4+7o/iR+
241 | UP4AaTP/fbKX/v//////////////////////////////////////////////////////////wfbX/xXe
242 | aPgD3FxRAAAAAAAAAAAG3F4MAdtbuVXnkf/3/vn/////////////////////////////////////////
243 | ////////////////////////////////////////////////////////////////////////////////
244 | ////////3Orj/x57S/8AaDL/Z6aG/u/89P/3/vr//v//////////////////////////////////////
245 | ///9//3/aOqd/wLbW8kG3F4UAAAAAAAAAAAAAAAABNxcWxLeZvm49dH/////////////////////////
246 | ////////////////////////////////////////////////////////////////////////////////
247 | ////////////////////////zOHW/RBzQf4he03/st7G/lDnjv5I5Yj+b+ui/prxvv7C9tf+9P34/v//
248 | ///////////////////L+N3/Ht9t/QLbXG4AAAAAAAAAAAAAAAAAAAAABtxeEALbW7tO5o3/8f32////
249 | ////////////////////////////////////////////////////////////////////////////////
250 | ////////////////////////////////////////w9zP/xByQP+JuqH+gO6t/gPcXP8C21v/Attb/wTc
251 | XP9e6Jf+8P31/v////////////////f++v9f6Jf/AttbywbcXhkAAAAAAAAAAAAAAAAAAAAAAAAAAATc
252 | XUoL3WHwlvC7////////////////////////////////////////////////////////////////////
253 | ////////////////////////////////////////////////////////r9C//UqTbv7B7dT+K+J2/gjc
254 | X/8F3F3/Attb/0Dkg/7e+un+/////////////////////6Xyxf8P3WT2BNxdWwAAAAAAAAAAAAAAAAAA
255 | AAAAAAAAAAAAAAfcXwQC3FyOIeBv/sX32v7/////////////////////////////////////////////
256 | ////////////////////////////////////////////////////////////////////////tdPE/rrW
257 | yP7w/vX+wvbY/q70yv6X8Lz+g+2v/tT54///////////////////////1Pnj/i3hd/8C21ugB9xfCQAA
258 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG3F4ZAttbvj3kgf/f+ur/////////////////////////
259 | ////////////////////////////////////////////////////////////////////////////////
260 | ////////7fTx/vn7+v/////////////////////////////////////////////////n/O/+TeaM/wPc
261 | XMwG3F4jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABdxeMgTcXdZP5o3/5Pvt/v//
262 | ////////////////////////////////////////////////////////////////////////////////
263 | /////////////////////////////////////////////////////////////////////////////+79
264 | 9P9j6Zr/Bdxd4QXcXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXc
265 | XUME3F3eUOaO/+H76///////////////////////////////////////////////////////////////
266 | ////////////////////////////////////////////////////////////////////////////////
267 | ////////5/zv/mPpmv8H3F/mBNxdUQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
268 | AAAAAAAAAAAAAAAAAAAF3F1DBNxd2EHkhP/K+N3+////////////////////////////////////////
269 | ////////////////////////////////////////////////////////////////////////////////
270 | ///////////////////V+eT+TeaM/wXcXeEE3F1RDd1jAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
271 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABdxeNALbXMIk4HL+m/G+//T9+P//////////////
272 | ////////////////////////////////////////////////////////////////////////////////
273 | ////////////////////////9/76/6vzyP8u4nj/A9xczQXcXUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
274 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbcXhsC21yUDN1i81Xn
275 | kf/E99n++v78////////////////////////////////////////////////////////////////////
276 | //////////////////////////////z//f/O+N//YOmY/xDdZfYC21yhBtxeIwAAAAAAAAAAAAAAAAAA
277 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
278 | AAAH3F8HBNxdUALbW8QY3mr7Yuma/7721f/y/ff/////////////////////////////////////////
279 | ///////////////////////////////////0/fj/w/bY/2zqoP8f3278A9xczATcXFsH3F8KAAAAAAAA
280 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
281 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbcXhQD3FxmAttbxRPeZvdC5IX/gu2u/7321P/h++z+9f74//3/
282 | /v///////////////////////f/+//b++f/k++7+wPbW/ojusv9G5Yf/Fd5o+QLbW8sC21xvBtxeGQAA
283 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
284 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABtxeEQPcXEwA21qYA9xc0hDd
285 | ZPIm4HP9QuSF/13olv9r6p/+gO2t/4Htrv9u66H+XeiW/0Xlhv8q4XX+Ed5l8wTcXdYB21qeA9xcUwbc
286 | XhUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
287 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
288 | AAAG3F4DBtxeGgPcXEcB21t5ANtapADbWsMB21rXAttb4QLbW+EB21vYANtaxgDbWqgB21t9A9xcSwbc
289 | Xh4G3F4EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
290 | AAAAAAAAAAAAAP//+B///wAA//+AAf//AAD//AAAP/8AAP/wAAAP/wAA/+AAAAf/AAD/gAAAAf8AAP8A
291 | AAAA/wAA/gAAAAB/AAD8AAAAAD8AAPgAAAAAHwAA+AAAAAAPAADwAAAAAA8AAOAAAAAABwAA4AAAAAAH
292 | AADAAAAAAAMAAMAAAAAAAwAAwAAAAAABAACAAAAAAAEAAIAAAAAAAQAAgAAAAAABAAAAAAAAAAAAAAAA
293 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAB
294 | AACAAAAAAAEAAIAAAAAAAQAAgAAAAAABAADAAAAAAAMAAMAAAAAAAwAA4AAAAAAHAADgAAAAAAcAAPAA
295 | AAAADwAA8AAAAAAPAAD4AAAAAB8AAPwAAAAAPwAA/gAAAAB/AAD/AAAAAP8AAP+AAAAB/wAA/8AAAAP/
296 | AAD/8AAAD/8AAP/8AAA//wAA//8AAP//AAD///AP//8AACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAA
297 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANlRDADa
298 | UzoB21hzCNxfpBLeZsQb32zTHN9s0xPeZsUJ3GCnAdtZdwDaVD0A2VEOAAAAAAAAAAAAAAAAAAAAAAAA
299 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANpUFgHb
300 | WWQY3mm5ReWH63fsp/yh8sL/u/XT/8r33f/L+N3/vPbU/6Pyw/967Kn9R+WI7Rrfa70C21lpANpUGQAA
301 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANtXBwDb
302 | WFUd323Iaeqe+7721f/x/fb////////////////////////////////////////////y/ff/w/fY/27r
303 | ofwh4G/OANtZXQDaVwkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADa
304 | VxUI3F+RTOaL9MD21v/7/vz/////////////////////////////////////////////////////////
305 | /////////P/9/8b32v9S54/2CtxgmgDaVxoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
306 | AAAA2lcdEN1kr3Xspv7r/PL/////////////////////////////////////////////////////////
307 | /////////////////////////////+/99f9+7av/E95muADbVyMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
308 | AAAAAAAAANtXFhDdZLCF7rD/9/76//////////////////////////////////////////////////j+
309 | +v/9//7///////////////////////////////////////r++/+S8Lj/FN5nuQDaVxwAAAAAAAAAAAAA
310 | AAAAAAAAAAAAAADbVwcH3F+Td+yn/vj++v////////////j++v/0/fj+////////////////////////
311 | ////////xPfZ/sz43v7///////////////////////////////////////////r+/P+B7a3/Ct1hngDa
312 | VwoAAAAAAAAAAAAAAAAAAAAAANtZWU/mjfXs/PL/////////////////s/TO/tb55P7/////////////
313 | ///////////////////E99n+TOaM/t/76v////////////////////////////////////////////H9
314 | 9v9Y55P4ANtZZQAAAAAAAAAAAAAAAADaVRge327KwvbY/////////////////9n65v9O5o3+4Pvr////
315 | /////////////////////////////+L77P845H/+qfLH/v//////////////////////////////////
316 | /////////////8v43f8l4HLUANpVHwAAAAAAAAAAANtZaWvqn/z8//3/////////////////tO7M/nHo
317 | ov70/vj/////////////////////////////////9Pz4/5XVsf7I5tb+6vfw/vH39P//////////////
318 | /////////////////////////v///3nsqP8B21t1AAAAAADZUg8Z32q9wfbX////////////////////
319 | //+Zw63+Tplz/pTAqv7N4tf/8/j1/////////////////+z08P99spf+Xp9+/pjqu/6X27T9YaGB/tDj
320 | 2v//////////////////////////////////////y/je/yDgb8gA2VIWANpUP0fliO3y/fb/////////
321 | ////////1/vm/pLOrf4JbTr/Amo1/xV2RP8/jmb/e7GV/tLk2/7a6eH+RpJr/xFyQP+Vza7+PueD/4bZ
322 | qv4WdET+JX9R/6XKt/75+/r////////////////////////////3/vr/U+eQ8gDaVUwA21l7fOyq/f//
323 | //////////////3//v9w7KP+fd+m/il/U/8BajT/Amo1/wBoMf8deUn/q9nA/Z3KsvsAaDP/WJ16/njp
324 | p/4K3mH/h96s/SF7Tv8AaTP/DXE+/2+qjP7p8u3///////////////////////////+K7rP/Attbigrd
325 | YKym8sb/////////////////4fvr/yPgcf9d5pX+Tphy/gBpM/8CajX/EHJA/4XEov5p7Z/+hcaj+xt3
326 | SP6T1bD+I+Fx/wbdXv+C4Kn+Jn1R/gFqNP8AaTP/M4Zb/9fn3/7//////////////////////////7P0
327 | zv8P3WS7Ft5ozML22P/////////////////y/ff/ZOmb/zXkff5wtJD+AWk0/wdtOf90s5L+XOeV/ibj
328 | dP+Py6r+bKqK/mDnmP4D3Fz/Bd1e/3vjpv0vg1n/AGgy/0COZv+14Mj+4vzs/v//////////////////
329 | ////////zPje/xvfbNke323bz/jg///////////////////////q/PH/a+yf/n/Oof4Nbz7/XqJ//nbn
330 | pf4H3V//IOFv/7Dfxf6P3bD+Et9m/wPcXP8C3Fz/dOOi/jSGXP5NlXD/ruXF/mDqmP7X+eX+////////
331 | ///////////////////Y+ub/JeBy5x7fbtzP+OD////////////////////////////n/PD/tufL/mim
332 | hv6C4ar+EN5l/wrcYP8x4nr+0vfh/Xfsp/4q4XX+LOF2/yDgb/5556f9lsCr/qPmwP4v43n/UueP/vn+
333 | +////////////////////////////9n65/8m4HPnGN5pzsT32f//////////////////////////////
334 | ///7/fz/zfHc/m3rof6B7a3+tPTO/t366f/6/vz/8P32/un88f7n/PD/4Pvr/uz88//Y+eX+KuJ2/wPc
335 | XP+b8b/+////////////////////////////////zfjf/xzfbNsL3WGwqvPI////////////////////
336 | ///////////////////4/vr/+P77/////////////////////////////////////////////Pz8/9rw
337 | 5P4/5IP/Ht9t/9n65v7///////////////////////////////+29dD/Ed1lwADbWoGB7a3+////////
338 | ////////////////////////////////////////////////////////////////////////////////
339 | ///x9/T/lL+p/o3qtP5d6Jf/+v78/////////////////////////////////4/vtv8D3FyRANpURk7m
340 | jPD1/vn/////////////////////////////////////////////////////////////////////////
341 | /////////////+z08P9DkGn+qNe9/sv53v7////////////////////////////////5/vv/WeiU9ADa
342 | VVMA2VITH99uxcr33P//////////////////////////////////////////////////////////////
343 | ////////////////////////5e/q/iN+T/5opof+/f7+/////////////////////////////////9P5
344 | 4/8l4HLPANlSGgAAAAAB21tzdeym/v7//v//////////////////////////////////////////////
345 | ///////////////////////////////////b6uL+GXhI/1qhfP7A9db+0/ni/uz88//6/vz/////////
346 | ////////g+6v/wPcXIAAAAAAAAAAAADaVR8k4HLUzPje////////////////////////////////////
347 | /////////////////////////////////////////////83i1/4zhlz+f9el/hffaf8W3mj/QuSF/tH5
348 | 4f7//////////9X55P8t4XfcANpVJwAAAAAAAAAAAAAAAADbWmZb6JX58v33////////////////////
349 | ////////////////////////////////////////////////////////xNzQ/pHAqP6C7q7+MuJ7/ybg
350 | c/+K77P++v78///////2/vn/Zemb/AHbWnMAAAAAAAAAAAAAAAAAAAAAANtXCwvdYaOG7rH//P/9////
351 | ///////////////////////////////////////////////////////////////////p8u3+7vXy//j+
352 | +//u/fT/5Pvu//j++////////f/+/5Lwuf8P3WSuANpXEAAAAAAAAAAAAAAAAAAAAAAAAAAAANpXHxbe
353 | aMCW8Lv//P/9////////////////////////////////////////////////////////////////////
354 | //////////////////////////////3//v+k8sT/G99syADaVyYAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
355 | AAAAAAAAANpXKRfeacGJ7rP/8/34////////////////////////////////////////////////////
356 | ///////////////////////////////////2/vn/kvC5/xzfbMkA21cwAAAAAAAAAAAAAAAAAAAAAAAA
357 | AAAAAAAAAAAAAAAAAAAAAAAAANpXIA3dYqZd6Jb60fjh//7//v//////////////////////////////
358 | ////////////////////////////////////////1/nl/2fqnfsQ3WSvANpXJgAAAAAAAAAAAAAAAAAA
359 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANpXDQHbWmso4XTZfu2r/tL54v/5/vv/////////
360 | ///////////////////////////////////6/vz/1fnk/4XusP8u4njeA9tbcwDaVxAAAAAAAAAAAAAA
361 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADaVSME3Fx7JOByzVjn
362 | k/SO77b/t/XR/8/44P/b+uj/3Prp/9D44P+59dL/kfC4/1volfYm4HPQBdxdgQDaVScA21kBAAAAAAAA
363 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADZ
364 | TgEA2VIYANpVUQTcXJAR3mXAHt9t3SjhdOop4XXqHt9u3hLeZsIE3FyTANpVVQDZUhsA2VEBAAAAAAAA
365 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//gf///AA///AAD//AAAP/gAAB/wAAAP4AAAB+AA
366 | AAfAAAADwAAAA4AAAAGAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAABgAAAAcAA
367 | AAHAAAAD4AAAB+AAAAfwAAAP+AAAH/wAAD//AAD//8AB///wD/8oAAAAEAAAACAAAAABACAAAAAAAAAE
368 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMQEAhLcYDJI5YmKc+uky4rus+eK77TndOulzUnl
369 | iY0T3WE0AMcMAwAAAAAAAAAAAAAAAAAAAAAAAAAAANZDDUTlhXec8b/h3Pro/vf++v/+//7//v/+//f+
370 | +v/d+un+n/HB40blh3wA10YPAAAAAAAAAAAAAAAAANZADlfnkpfJ99z7/v/+/////////////v////v+
371 | /P////////////7//v/M+N78XOiVnADWRBAAAAAAAIQAAUXlhnnK99z7+/78/+D76//+//7///////3/
372 | /v+49dH+6/zy/////////////////8343/1I5Yh/ALAAAgncYDSd8b/h/////9j25P+v78r+////////
373 | ////////qenE/snw2v75+/r/////////////////ovLD5Q/dZDpI5YmN3Pro//////+238j+S5hw/4O2
374 | nP/P4tj/utbH/2yyjf5616L+cKyN/tjo4P///////////+D76/9O5oyVdOulzvj++v/r/fL/ad+a/iB/
375 | Tf8KbTr/bb+S/lymgP1JxH7/Q9iB/ht5SP9FkWr/6vPu///////5/vv/euyp1Yzvter/////9f75/4zu
376 | tP5AnGv+QaZv/z7ggf55z5/+Idxu/zPXd/5Bkmj+c8aY/uf77////////////5HvuPCN77Xr/v/+////
377 | ///1/vj/tt/J/m/jn/597av+xPbY/onvs/+c7b7+g96q/l7pl//0/vj///////////+R8Ljwduyn0fj+
378 | +v////////////v//f/1/vn//v//////////////9/n4/4vhr/6X8bz////////////5/vv/fOyq10zm
379 | i5Lf+ur//////////////////////////////////////+Lu6P90sZH+6Pnv////////////4vvs/1Hm
380 | jpoO3WM5ofLC5f/////////////////////////////////////a6OH/WKt+/nTopP/C99f//////6by
381 | xegT3mY+ALEAAknliYDO+OD9////////////////////////////////6vLu/7rnzf6N8Lb/1vnk/9T5
382 | 4/5N5ouHAL8AAwAAAAAA10YRXeiWodD44P3+/////////////////////////////////////////9X5
383 | 5P1i6ZmmANhKEwAAAAAAAAAAAAAAAADXSRFK5YqCpPLE6OL77P/6/vz////////////6/vz/4/vt/6jz
384 | x+lO5oyHANhMEwAAAAAAAAAAAAAAAAAAAAAAAAAAAMsaBBjeZTxR5o6YfO2q2JPwufKT8Lnyfe2r2VLn
385 | j5sa3mc+AMwfBQAAAAAAAAAAAAAAAPgfAADwDwAAwAMAAMADAACAAQAAAAAAAAAAAAAAAAAAAAAAAAAA
386 | AAAAAAAAgAEAAIABAADAAwAA4AcAAPgfAAA=
387 |
388 |
389 |
--------------------------------------------------------------------------------
/Example/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using System.Windows.Forms;
6 |
7 | namespace Example
8 | {
9 | static class Program
10 | {
11 | ///
12 | /// The main entry point for the application.
13 | ///
14 | [STAThread]
15 | static void Main()
16 | {
17 | Application.EnableVisualStyles();
18 | Application.SetCompatibleTextRenderingDefault(false);
19 | Application.Run(new MainForm());
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Example/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("Example")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("Scott Clayton")]
12 | [assembly: AssemblyProduct("Recommender Example")]
13 | [assembly: AssemblyCopyright("Copyright © 2018")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("a27ff3ec-ee8e-4ee8-b6f2-60572619cec7")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/Example/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace Example.Properties
12 | {
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources
26 | {
27 |
28 | private static global::System.Resources.ResourceManager resourceMan;
29 |
30 | private static global::System.Globalization.CultureInfo resourceCulture;
31 |
32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
33 | internal Resources()
34 | {
35 | }
36 |
37 | ///
38 | /// Returns the cached ResourceManager instance used by this class.
39 | ///
40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
41 | internal static global::System.Resources.ResourceManager ResourceManager
42 | {
43 | get
44 | {
45 | if ((resourceMan == null))
46 | {
47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Example.Properties.Resources", typeof(Resources).Assembly);
48 | resourceMan = temp;
49 | }
50 | return resourceMan;
51 | }
52 | }
53 |
54 | ///
55 | /// Overrides the current thread's CurrentUICulture property for all
56 | /// resource lookups using this strongly typed resource class.
57 | ///
58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
59 | internal static global::System.Globalization.CultureInfo Culture
60 | {
61 | get
62 | {
63 | return resourceCulture;
64 | }
65 | set
66 | {
67 | resourceCulture = value;
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Example/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/Example/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace Example.Properties
12 | {
13 |
14 |
15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
18 | {
19 |
20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
21 |
22 | public static Settings Default
23 | {
24 | get
25 | {
26 | return defaultInstance;
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Example/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/RecommenderTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using UserBehavior.Comparers;
9 | using UserBehavior.Objects;
10 | using UserBehavior.Parsers;
11 | using UserBehavior.Raters;
12 | using UserBehavior.Recommenders;
13 |
14 | namespace Example
15 | {
16 | class RecommenderTests
17 | {
18 | public static void FindBestRaterWeights()
19 | {
20 | using (StreamWriter w = new StreamWriter("rater-weights.csv", true))
21 | {
22 | w.WriteLine("down,up,view,dl,rmse,solved,total,precision,rank");
23 | }
24 |
25 | var once = new User(0, "");
26 | var options = new List();
27 |
28 | for (double up = 0.0; up < 5.0; up += 1)
29 | {
30 | for (double down = -5.0; down <= 0.0; down += 1)
31 | {
32 | for (double dl = 0.0; dl < 5.0; dl += 1)
33 | {
34 | for (double view = 0.0; view < 5.0; view += 1)
35 | {
36 | options.Add(new { up, down, dl, view });
37 | }
38 | }
39 | }
40 | }
41 |
42 | var dbp = new UserBehaviorDatabaseParser();
43 | var db = dbp.LoadUserBehaviorDatabase("UserBehaviour.txt");
44 | var sp = new DaySplitter(db, 3);
45 | var cp = new CorrelationUserComparer();
46 |
47 | Parallel.ForEach(options, set =>
48 | {
49 | try
50 | {
51 | var rate = new LinearRater(set.down, set.up, set.view, set.dl);
52 | var mfr = new MatrixFactorizationRecommender(20, rate);
53 | //var mfr = new UserCollaborativeFilterRecommender(cp, rate, set.features);
54 |
55 | mfr.Train(sp.TrainingDB);
56 |
57 | var score = mfr.Score(sp.TestingDB, rate);
58 | var results = mfr.Test(sp.TestingDB, 100);
59 |
60 | lock (once)
61 | {
62 | using (StreamWriter w = new StreamWriter("rater-weights.csv", true))
63 | {
64 | w.WriteLine(set.down + "," + set.up + "," + set.view + "," + set.dl + "," + score.RootMeanSquareDifference + "," + results.UsersSolved + "," + results.TotalUsers + "," + results.AveragePrecision + "," + results.AverageRecall);
65 | }
66 | }
67 | }
68 | catch (Exception ex)
69 | {
70 | File.WriteAllText("errors.txt", ex.ToString());
71 | }
72 | });
73 | }
74 |
75 | public static void TestAllRecommenders()
76 | {
77 | UserBehaviorDatabaseParser dbp = new UserBehaviorDatabaseParser();
78 | UserBehaviorDatabase db = dbp.LoadUserBehaviorDatabase("UserBehaviour.txt");
79 |
80 | //var ubt = new UserBehaviorTransformer(db);
81 | //var uart = ubt.GetUserArticleRatingsTable();
82 | //uart.SaveSparcityVisual("sparcity.bmp");
83 | //uart.SaveUserRatingDistribution("distrib.csv");
84 | //uart.SaveArticleRatingDistribution("distriba.csv");
85 |
86 | var rate = new LinearRater();
87 | var sp = new DaySplitter(db, 5);
88 | var uc = new CorrelationUserComparer();
89 |
90 | //var rr = new RandomRecommender();
91 | //rr.Train(sp.TrainingDB);
92 | //ScoreResults scores5 = rr.Score(sp.TestingDB, rate);
93 | //TestResults results5 = rr.Test(sp.TestingDB, 30);
94 |
95 | var ubc = new UserCollaborativeFilterRecommender(uc, rate, 30);
96 | var mfr = new MatrixFactorizationRecommender(30, rate);
97 | var icf = new ItemCollaborativeFilterRecommender(uc, rate, 30);
98 | var hbr = new HybridRecommender(ubc, mfr, icf);
99 |
100 | hbr.Train(sp.TrainingDB);
101 | ScoreResults scores1 = hbr.Score(sp.TestingDB, rate);
102 | TestResults results1 = hbr.Test(sp.TestingDB, 30);
103 |
104 | ubc = new UserCollaborativeFilterRecommender(uc, rate, 30);
105 | mfr = new MatrixFactorizationRecommender(30, rate);
106 | icf = new ItemCollaborativeFilterRecommender(uc, rate, 30);
107 |
108 | ubc.Train(sp.TrainingDB);
109 | ScoreResults scores2 = ubc.Score(sp.TestingDB, rate);
110 | TestResults results2 = ubc.Test(sp.TestingDB, 30);
111 |
112 | mfr.Train(sp.TrainingDB);
113 | ScoreResults scores3 = mfr.Score(sp.TestingDB, rate);
114 | TestResults results3 = mfr.Test(sp.TestingDB, 30);
115 |
116 | icf.Train(sp.TrainingDB);
117 | ScoreResults scores4 = icf.Score(sp.TestingDB, rate);
118 | TestResults results4 = icf.Test(sp.TestingDB, 30);
119 |
120 | using (StreamWriter w = new StreamWriter("results.csv"))
121 | {
122 | w.WriteLine("model,rmse,users,user-solved,precision,recall");
123 | w.WriteLine("UCF," + scores2.RootMeanSquareDifference + "," + results2.TotalUsers + "," + results2.UsersSolved + "," + results2.AveragePrecision + "," + results2.AverageRecall);
124 | w.WriteLine("SVD," + scores3.RootMeanSquareDifference + "," + results3.TotalUsers + "," + results3.UsersSolved + "," + results3.AveragePrecision + "," + results3.AverageRecall);
125 | w.WriteLine("ICF," + scores4.RootMeanSquareDifference + "," + results4.TotalUsers + "," + results4.UsersSolved + "," + results4.AveragePrecision + "," + results4.AverageRecall);
126 | w.WriteLine("HR," + scores1.RootMeanSquareDifference + "," + results1.TotalUsers + "," + results1.UsersSolved + "," + results1.AveragePrecision + "," + results1.AverageRecall);
127 | }
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/Example/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skotz/cp-user-behavior/882eac66800af6c589fb20f094bb9d233bfbd8a2/Example/icon.ico
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Scott Clayton
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # C# Recommendation Engine
2 |
3 | This project and [corresponding article](https://www.codeproject.com/Articles/1232150/Building-an-Article-Recommendation-Engine) won first place in Code Project's 2018 Machine Learning and Artificial Intelligence Competition.
4 | It demonstrates user-based and item-based collaborative filtering with matrix factorization using
5 | the user behavior data provided by Code Project for the challenge.
6 |
7 | ### Usage
8 |
9 | ```C#
10 | IRater rate = new LinearRater(-4, 2, 3, 1);
11 | IComparer compare = new CorrelationUserComparer();
12 | IRecommender recommender = new UserCollaborativeFilterRecommender(compare, rate, 50);
13 |
14 | UserBehaviorDatabaseParser parser = new UserBehaviorDatabaseParser();
15 | UserBehaviorDatabase db = parser.LoadUserBehaviorDatabase("UserBehavior.txt");
16 | ISplitter split = new DaySplitter(db, 5);
17 |
18 | recommender.Train(split.TrainingDB);
19 |
20 | ScoreResults scores = recommender.Score(split.TestingDB, rate);
21 | TestResults results = recommender.Test(split.TestingDB, 30);
22 |
23 | List suggestions = recommender.GetSuggestions(someUserId, numberOfRecommendations);
24 | ```
25 |
--------------------------------------------------------------------------------
/User Behavior.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.25420.1
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Recommender", "User Behavior\Recommender.csproj", "{DB5A3FEF-D18F-4901-9B4F-959E403BD21B}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "Example\Example.csproj", "{A27FF3EC-EE8E-4EE8-B6F2-60572619CEC7}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {DB5A3FEF-D18F-4901-9B4F-959E403BD21B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {DB5A3FEF-D18F-4901-9B4F-959E403BD21B}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {DB5A3FEF-D18F-4901-9B4F-959E403BD21B}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {DB5A3FEF-D18F-4901-9B4F-959E403BD21B}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {A27FF3EC-EE8E-4EE8-B6F2-60572619CEC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {A27FF3EC-EE8E-4EE8-B6F2-60572619CEC7}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {A27FF3EC-EE8E-4EE8-B6F2-60572619CEC7}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {A27FF3EC-EE8E-4EE8-B6F2-60572619CEC7}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | EndGlobal
29 |
--------------------------------------------------------------------------------
/User Behavior/Abstractions/IClassifier.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using UserBehavior.Objects;
7 | using UserBehavior.Parsers;
8 |
9 | namespace UserBehavior.Recommenders
10 | {
11 | public interface IRecommender
12 | {
13 | void Train(UserBehaviorDatabase db);
14 |
15 | List GetSuggestions(int userId, int numSuggestions);
16 |
17 | double GetRating(int userId, int articleId);
18 |
19 | void Save(string file);
20 |
21 | void Load(string file);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/User Behavior/Abstractions/IComparer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace UserBehavior.Comparers
8 | {
9 | public interface IComparer
10 | {
11 | double CompareVectors(double[] userFeaturesOne, double[] userFeaturesTwo);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/User Behavior/Abstractions/IRater.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using UserBehavior.Objects;
7 |
8 | namespace UserBehavior.Abstractions
9 | {
10 | public interface IRater
11 | {
12 | double GetRating(List actions);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/User Behavior/Abstractions/ISplitter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace UserBehavior.Parsers
8 | {
9 | public interface ISplitter
10 | {
11 | UserBehaviorDatabase TrainingDB { get; }
12 |
13 | UserBehaviorDatabase TestingDB { get; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/User Behavior/Comparers/CoRatedCosineUserComparer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace UserBehavior.Comparers
8 | {
9 | public class CoRatedCosineUserComparer : IComparer
10 | {
11 | public double CompareVectors(double[] userFeaturesOne, double[] userFeaturesTwo)
12 | {
13 | double sumProduct = 0.0;
14 | double sumOneSquared = 0.0;
15 | double sumTwoSquared = 0.0;
16 |
17 | for (int i = 0; i < userFeaturesOne.Length; i++)
18 | {
19 | // Only compare articles rated by both users
20 | if (userFeaturesOne[i] != 0 && userFeaturesTwo[i] != 0)
21 | {
22 | sumProduct += userFeaturesOne[i] * userFeaturesTwo[i];
23 | sumOneSquared += Math.Pow(userFeaturesOne[i], 2);
24 | sumTwoSquared += Math.Pow(userFeaturesTwo[i], 2);
25 | }
26 | }
27 |
28 | if (sumOneSquared > 0 && sumTwoSquared > 0)
29 | {
30 | return sumProduct / (Math.Sqrt(sumOneSquared) * Math.Sqrt(sumTwoSquared));
31 | }
32 | else
33 | {
34 | return double.NegativeInfinity;
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/User Behavior/Comparers/CorrelationUserComparer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace UserBehavior.Comparers
8 | {
9 | public class CorrelationUserComparer : IComparer
10 | {
11 | public double CompareVectors(double[] userFeaturesOne, double[] userFeaturesTwo)
12 | {
13 | double average1 = 0.0;
14 | double average2 = 0.0;
15 | int count = 0;
16 |
17 | for (int i = 0; i < userFeaturesOne.Length; i++)
18 | {
19 | if (userFeaturesOne[i] != 0 && userFeaturesTwo[i] != 0)
20 | {
21 | average1 += userFeaturesOne[i];
22 | average2 += userFeaturesTwo[i];
23 | count++;
24 | }
25 | }
26 |
27 | average1 /= count;
28 | average2 /= count;
29 |
30 | double sum = 0.0;
31 | double squares1 = 0.0;
32 | double squares2 = 0.0;
33 |
34 | for (int i = 0; i < userFeaturesOne.Length; i++)
35 | {
36 | if (userFeaturesOne[i] != 0 && userFeaturesTwo[i] != 0)
37 | {
38 | sum += (userFeaturesOne[i] - average1) * (userFeaturesTwo[i] - average2);
39 | squares1 += Math.Pow(userFeaturesOne[i] - average1, 2);
40 | squares2 += Math.Pow(userFeaturesTwo[i] - average2, 2);
41 | }
42 | }
43 |
44 | return sum / Math.Sqrt(squares1 * squares2);
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/User Behavior/Comparers/CosineUserComparer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace UserBehavior.Comparers
8 | {
9 | public class CosineUserComparer : IComparer
10 | {
11 | public double CompareVectors(double[] userFeaturesOne, double[] userFeaturesTwo)
12 | {
13 | double sumProduct = 0.0;
14 | double sumOneSquared = 0.0;
15 | double sumTwoSquared = 0.0;
16 |
17 | for (int i = 0; i < userFeaturesOne.Length; i++)
18 | {
19 | sumProduct += userFeaturesOne[i] * userFeaturesTwo[i];
20 | sumOneSquared += Math.Pow(userFeaturesOne[i], 2);
21 | sumTwoSquared += Math.Pow(userFeaturesTwo[i], 2);
22 | }
23 |
24 | return sumProduct / (Math.Sqrt(sumOneSquared) * Math.Sqrt(sumTwoSquared));
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/User Behavior/Comparers/RootMeanSquareUserComparer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace UserBehavior.Comparers
8 | {
9 | public class RootMeanSquareUserComparer : IComparer
10 | {
11 | public double CompareVectors(double[] userFeaturesOne, double[] userFeaturesTwo)
12 | {
13 | double score = 0.0;
14 |
15 | for (int i = 0; i < userFeaturesOne.Length; i++)
16 | {
17 | score += Math.Pow(userFeaturesOne[i] - userFeaturesTwo[i], 2);
18 | }
19 |
20 | // Higher numbers indicate closer similarity
21 | return -Math.Sqrt(score / userFeaturesOne.Length);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/User Behavior/Comparers/SimpleCountUserComparer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace UserBehavior.Comparers
8 | {
9 | public class SimpleCountUserComparer : IComparer
10 | {
11 | public double CompareVectors(double[] userFeaturesOne, double[] userFeaturesTwo)
12 | {
13 | double count = 0.0;
14 | for (int i = 0; i < userFeaturesOne.Length; i++)
15 | {
16 | if (userFeaturesOne[i] != 0 && userFeaturesTwo[i] != 0)
17 | {
18 | count++;
19 | }
20 | }
21 | return count;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/User Behavior/Mathematics/Matrix.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace UserBehavior.Mathematics
8 | {
9 | class Matrix
10 | {
11 | ///
12 | /// Calculate the dot product between two vectors
13 | ///
14 | public static double GetDotProduct(double[] matrixOne, double[] matrixTwo)
15 | {
16 | return matrixOne.Zip(matrixTwo, (a, b) => a * b).Sum();
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/User Behavior/Mathematics/SingularValueDecomposition.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using UserBehavior.Objects;
8 |
9 | namespace UserBehavior.Mathematics
10 | {
11 | class SingularValueDecomposition
12 | {
13 | private double averageGlobalRating;
14 |
15 | private int learningIterations;
16 | private double learningRate;
17 | private double learningDescent = 0.99;
18 | private double regularizationTerm = 0.02;
19 |
20 | private int numUsers;
21 | private int numArticles;
22 | private int numFeatures;
23 |
24 | private double[] userBiases;
25 | private double[] articleBiases;
26 | private double[][] userFeatures;
27 | private double[][] articleFeatures;
28 |
29 | public SingularValueDecomposition()
30 | : this(20, 100)
31 | {
32 | }
33 |
34 | public SingularValueDecomposition(int features, int iterations)
35 | : this(features, iterations, 0.005)
36 | {
37 | }
38 |
39 | public SingularValueDecomposition(int features, int iterations, double learningSpeed)
40 | {
41 | numFeatures = features;
42 | learningIterations = iterations;
43 | learningRate = learningSpeed;
44 | }
45 |
46 | private void Initialize(UserArticleRatingsTable ratings)
47 | {
48 | numUsers = ratings.Users.Count;
49 | numArticles = ratings.Users[0].ArticleRatings.Length;
50 |
51 | Random rand = new Random();
52 |
53 | userFeatures = new double[numUsers][];
54 | for (int userIndex = 0; userIndex < numUsers; userIndex++)
55 | {
56 | userFeatures[userIndex] = new double[numFeatures];
57 |
58 | for (int featureIndex = 0; featureIndex < numFeatures; featureIndex++)
59 | {
60 | userFeatures[userIndex][featureIndex] = rand.NextDouble();
61 | }
62 | }
63 |
64 | articleFeatures = new double[numArticles][];
65 | for (int articleIndex = 0; articleIndex < numArticles; articleIndex++)
66 | {
67 | articleFeatures[articleIndex] = new double[numFeatures];
68 |
69 | for (int featureIndex = 0; featureIndex < numFeatures; featureIndex++)
70 | {
71 | articleFeatures[articleIndex][featureIndex] = rand.NextDouble();
72 | }
73 | }
74 |
75 | userBiases = new double[numUsers];
76 | articleBiases = new double[numArticles];
77 | }
78 |
79 | public SvdResult FactorizeMatrix(UserArticleRatingsTable ratings)
80 | {
81 | Initialize(ratings);
82 |
83 | double squaredError;
84 | int count;
85 | List rmseAll = new List();
86 |
87 | averageGlobalRating = GetAverageRating(ratings);
88 |
89 | for (int i = 0; i < learningIterations; i++)
90 | {
91 | squaredError = 0.0;
92 | count = 0;
93 |
94 | for (int userIndex = 0; userIndex < numUsers; userIndex++)
95 | {
96 | for (int articleIndex = 0; articleIndex < numArticles; articleIndex++)
97 | {
98 | if (ratings.Users[userIndex].ArticleRatings[articleIndex] != 0)
99 | {
100 | double predictedRating = averageGlobalRating + userBiases[userIndex] + articleBiases[articleIndex] + Matrix.GetDotProduct(userFeatures[userIndex], articleFeatures[articleIndex]);
101 |
102 | double error = ratings.Users[userIndex].ArticleRatings[articleIndex] - predictedRating;
103 |
104 | if (double.IsNaN(predictedRating))
105 | {
106 | throw new Exception("Encountered a non-number while factorizing a matrix! Try decreasing the learning rate.");
107 | }
108 |
109 | squaredError += Math.Pow(error, 2);
110 | count++;
111 |
112 | averageGlobalRating += learningRate * (error - regularizationTerm * averageGlobalRating);
113 | userBiases[userIndex] += learningRate * (error - regularizationTerm * userBiases[userIndex]);
114 | articleBiases[articleIndex] += learningRate * (error - regularizationTerm * articleBiases[articleIndex]);
115 |
116 | for (int featureIndex = 0; featureIndex < numFeatures; featureIndex++)
117 | {
118 | userFeatures[userIndex][featureIndex] += learningRate * (error * articleFeatures[articleIndex][featureIndex] - regularizationTerm * userFeatures[userIndex][featureIndex]);
119 | articleFeatures[articleIndex][featureIndex] += learningRate * (error * userFeatures[userIndex][featureIndex] - regularizationTerm * articleFeatures[articleIndex][featureIndex]);
120 | }
121 | }
122 | }
123 | }
124 |
125 | squaredError = Math.Sqrt(squaredError / count);
126 | rmseAll.Add(squaredError);
127 |
128 | learningRate *= learningDescent;
129 | }
130 |
131 | //using (StreamWriter w = new StreamWriter("rmse.csv"))
132 | //{
133 | // w.WriteLine("epoc,rmse");
134 | // for (int i = 0; i < rmseAll.Count; i++)
135 | // {
136 | // w.WriteLine((i + 1) + "," + rmseAll[i]);
137 | // }
138 | //}
139 |
140 | return new SvdResult(averageGlobalRating, userBiases, articleBiases, userFeatures, articleFeatures);
141 | }
142 |
143 | ///
144 | /// Get the average rating of non-zero values across the entire user-article matrix
145 | ///
146 | private double GetAverageRating(UserArticleRatingsTable ratings)
147 | {
148 | double sum = 0.0;
149 | int count = 0;
150 |
151 | for (int userIndex = 0; userIndex < numUsers; userIndex++)
152 | {
153 | for (int articleIndex = 0; articleIndex < numArticles; articleIndex++)
154 | {
155 | // If the given user rated the given item, add it to our average
156 | if (ratings.Users[userIndex].ArticleRatings[articleIndex] != 0)
157 | {
158 | sum += ratings.Users[userIndex].ArticleRatings[articleIndex];
159 | count++;
160 | }
161 | }
162 | }
163 |
164 | return sum / count;
165 | }
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/User Behavior/Mathematics/SvdResult.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace UserBehavior.Mathematics
8 | {
9 | class SvdResult
10 | {
11 | public double AverageGlobalRating { get; private set; }
12 | public double[] UserBiases { get; private set; }
13 | public double[] ArticleBiases { get; private set; }
14 | public double[][] UserFeatures { get; private set; }
15 | public double[][] ArticleFeatures { get; private set; }
16 |
17 | public SvdResult(double averageGlobalRating, double[] userBiases, double[] articleBiases, double[][] userFeatures, double[][] articleFeatures)
18 | {
19 | AverageGlobalRating = averageGlobalRating;
20 | UserBiases = userBiases;
21 | ArticleBiases = articleBiases;
22 | UserFeatures = userFeatures;
23 | ArticleFeatures = articleFeatures;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/User Behavior/Objects/Article.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace UserBehavior.Objects
6 | {
7 | [Serializable]
8 | public class Article
9 | {
10 | public int ArticleID { get; set; }
11 |
12 | public string Name { get; set; }
13 |
14 | public List Tags { get; set; }
15 |
16 | public Article(int id, string name, List tags)
17 | {
18 | ArticleID = id;
19 | Name = name;
20 | Tags = tags;
21 | }
22 |
23 | public override string ToString()
24 | {
25 | return ArticleID + "," + Name + "," + Tags.Select(x => x.Name).Aggregate((c, n) => c + "," + n);
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/User Behavior/Objects/ArticleRating.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace UserBehavior.Objects
8 | {
9 | public class ArticleRating
10 | {
11 | public int ArticleID { get; set; }
12 |
13 | public double Rating { get; set; }
14 |
15 | public ArticleRating(int articleId, double rating)
16 | {
17 | ArticleID = articleId;
18 | Rating = rating;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/User Behavior/Objects/ArticleTag.cs:
--------------------------------------------------------------------------------
1 | namespace UserBehavior.Objects
2 | {
3 | public class ArticleTag
4 | {
5 | public int ArticleID { get; set; }
6 |
7 | public string TagName { get; set; }
8 |
9 | public ArticleTag(int articleid, string tag)
10 | {
11 | ArticleID = articleid;
12 | TagName = tag;
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/User Behavior/Objects/ArticleTagCounts.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace UserBehavior.Objects
8 | {
9 | public class ArticleTagCounts
10 | {
11 | public int ArticleID { get; set; }
12 |
13 | public double[] TagCounts { get; set; }
14 |
15 | public ArticleTagCounts(int articleId, int numTags)
16 | {
17 | ArticleID = articleId;
18 | TagCounts = new double[numTags];
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/User Behavior/Objects/ScoreResults.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace UserBehavior.Objects
8 | {
9 | public class ScoreResults
10 | {
11 | public double RootMeanSquareDifference { get; set; }
12 |
13 | public ScoreResults(double rmsd)
14 | {
15 | RootMeanSquareDifference = rmsd;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/User Behavior/Objects/Suggestion.cs:
--------------------------------------------------------------------------------
1 | namespace UserBehavior.Objects
2 | {
3 | public class Suggestion
4 | {
5 | public int UserID { get; set; }
6 |
7 | public int ArticleID { get; set; }
8 |
9 | public double Rating { get; set; }
10 |
11 | public Suggestion(int userId, int articleId, double assurance)
12 | {
13 | UserID = userId;
14 | ArticleID = articleId;
15 | Rating = assurance;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/User Behavior/Objects/Tag.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace UserBehavior.Objects
4 | {
5 | [Serializable]
6 | public class Tag
7 | {
8 | public string Name { get; set; }
9 |
10 | public Tag(string name)
11 | {
12 | Name = name;
13 | }
14 |
15 | public override string ToString()
16 | {
17 | return Name;
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/User Behavior/Objects/TestResults.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace UserBehavior.Objects
8 | {
9 | public class TestResults
10 | {
11 | public int TotalUsers { get; set; }
12 |
13 | public int UsersSolved { get; set; }
14 |
15 | public double AverageRecall { get; set; }
16 |
17 | public double AveragePrecision { get; set; }
18 |
19 | public TestResults(int totalUsers, int usersSolved, double averageRecall, double averagePrecision)
20 | {
21 | TotalUsers = totalUsers;
22 | UsersSolved = usersSolved;
23 | AverageRecall = averageRecall;
24 | AveragePrecision = averagePrecision;
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/User Behavior/Objects/User.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace UserBehavior.Objects
4 | {
5 | [Serializable]
6 | public class User
7 | {
8 | public int UserID { get; set; }
9 |
10 | public string Name { get; set; }
11 |
12 | public User(int id, string name)
13 | {
14 | UserID = id;
15 | Name = name;
16 | }
17 |
18 | public override string ToString()
19 | {
20 | return UserID + "," + Name;
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/User Behavior/Objects/UserAction.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace UserBehavior.Objects
4 | {
5 | [Serializable]
6 | public class UserAction
7 | {
8 | public int Day { get; set; }
9 |
10 | public string Action { get; set; }
11 |
12 | public int UserID { get; set; }
13 |
14 | public string UserName { get; set; }
15 |
16 | public int ArticleID { get; set; }
17 |
18 | public string ArticleName { get; set; }
19 |
20 | public UserAction(int day, string action, int userid, string username, int articleid, string articlename)
21 | {
22 | Day = day;
23 | Action = action;
24 | UserID = userid;
25 | UserName = username;
26 | ArticleID = articleid;
27 | ArticleName = articlename;
28 | }
29 |
30 | public override string ToString()
31 | {
32 | return Day + "," + Action + "," + UserID + "," + UserName + "," + ArticleID + "," + ArticleName;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/User Behavior/Objects/UserActionTag.cs:
--------------------------------------------------------------------------------
1 | namespace UserBehavior.Objects
2 | {
3 | public class UserActionTag
4 | {
5 | public int UserID { get; set; }
6 |
7 | public double[] ActionTagData { get; set; }
8 |
9 | public double Score { get; set; }
10 |
11 | public UserActionTag(int userId, int actionTagCount)
12 | {
13 | UserID = userId;
14 | ActionTagData = new double[actionTagCount];
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/User Behavior/Objects/UserArticleRatings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace UserBehavior.Objects
5 | {
6 | public class UserArticleRatings
7 | {
8 | public int UserID { get; set; }
9 |
10 | public double[] ArticleRatings { get; set; }
11 |
12 | public double Score { get; set; }
13 |
14 | public UserArticleRatings(int userId, int articlesCount)
15 | {
16 | UserID = userId;
17 | ArticleRatings = new double[articlesCount];
18 | }
19 |
20 | public void AppendRatings(double[] ratings)
21 | {
22 | List allRatings = new List();
23 |
24 | allRatings.AddRange(ArticleRatings);
25 | allRatings.AddRange(ratings);
26 |
27 | ArticleRatings = allRatings.ToArray();
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/User Behavior/Objects/UserArticleRatingsTable.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace UserBehavior.Objects
10 | {
11 | public class UserArticleRatingsTable
12 | {
13 | public List Users { get; set; }
14 |
15 | public List UserIndexToID { get; set; }
16 |
17 | public List ArticleIndexToID { get; set; }
18 |
19 | public UserArticleRatingsTable()
20 | {
21 | Users = new List();
22 | UserIndexToID = new List();
23 | ArticleIndexToID = new List();
24 | }
25 |
26 | public void AppendUserFeatures(double[][] userFeatures)
27 | {
28 | for (int i = 0; i < UserIndexToID.Count; i++)
29 | {
30 | Users[i].AppendRatings(userFeatures[i]);
31 | }
32 | }
33 |
34 | public void AppendArticleFeatures(double[][] articleFeatures)
35 | {
36 | for (int f = 0; f < articleFeatures[0].Length; f++)
37 | {
38 | UserArticleRatings newFeature = new UserArticleRatings(int.MaxValue, ArticleIndexToID.Count);
39 |
40 | for (int a = 0; a < ArticleIndexToID.Count; a++)
41 | {
42 | newFeature.ArticleRatings[a] = articleFeatures[a][f];
43 | }
44 |
45 | Users.Add(newFeature);
46 | }
47 | }
48 |
49 | internal void AppendArticleFeatures(List articleTags)
50 | {
51 | double[][] features = new double[articleTags.Count][];
52 |
53 | for (int a = 0; a < articleTags.Count; a++)
54 | {
55 | features[a] = new double[articleTags[a].TagCounts.Length];
56 |
57 | for (int f = 0; f < articleTags[a].TagCounts.Length; f++)
58 | {
59 | features[a][f] = articleTags[a].TagCounts[f];
60 | }
61 | }
62 |
63 | AppendArticleFeatures(features);
64 | }
65 |
66 | public void SaveSparcityVisual(string file)
67 | {
68 | double min = Users.Min(x => x.ArticleRatings.Min());
69 | double max = Users.Max(x => x.ArticleRatings.Max());
70 |
71 | Bitmap b = new Bitmap(ArticleIndexToID.Count, UserIndexToID.Count);
72 | int numPixels = 0;
73 |
74 | for (int x = 0; x < ArticleIndexToID.Count; x++)
75 | {
76 | for (int y = 0; y < UserIndexToID.Count; y++)
77 | {
78 | //int brightness = 255 - (int)(((UserArticleRatings[y].ArticleRatings[x] - min) / (max - min)) * 255);
79 | //brightness = Math.Max(0, Math.Min(255, brightness));
80 |
81 | int brightness = Users[y].ArticleRatings[x] == 0 ? 255 : 0;
82 |
83 | Color c = Color.FromArgb(brightness, brightness, brightness);
84 |
85 | b.SetPixel(x, y, c);
86 |
87 | numPixels += Users[y].ArticleRatings[x] != 0 ? 1 : 0;
88 | }
89 | }
90 |
91 | double sparcity = (double)numPixels / (ArticleIndexToID.Count * UserIndexToID.Count);
92 |
93 | b.Save(file);
94 | }
95 |
96 | ///
97 | /// Generate a CSV report of users and how many ratings they've given
98 | ///
99 | public void SaveUserRatingDistribution(string file)
100 | {
101 | int bucketSize = 4;
102 | int maxRatings = Users.Max(x => x.ArticleRatings.Count(y => y != 0));
103 | List buckets = new List();
104 |
105 | for (int i = 0; i <= Math.Floor((double)maxRatings / bucketSize); i++)
106 | {
107 | buckets.Add(0);
108 | }
109 |
110 | foreach (UserArticleRatings ratings in Users)
111 | {
112 | buckets[(int)Math.Floor((double)ratings.ArticleRatings.Count(x => x != 0) / bucketSize)]++;
113 | }
114 |
115 | using (StreamWriter w = new StreamWriter(file))
116 | {
117 | w.WriteLine("numArticlesRead,numUsers");
118 |
119 | for (int i = 0; i <= Math.Floor((double)maxRatings / bucketSize); i++)
120 | {
121 | w.WriteLine("=\"" + (i * bucketSize) + "-" + (((i + 1) * bucketSize) - 1) + "\"," + buckets[i / bucketSize]);
122 | }
123 | }
124 | }
125 |
126 | ///
127 | /// Generate a CSV report of articles and how many ratings they've gotten
128 | ///
129 | public void SaveArticleRatingDistribution(string file)
130 | {
131 | int bucketSize = 2;
132 | int maxRatings = 3000;
133 | List buckets = new List();
134 |
135 | for (int i = 0; i <= Math.Floor((double)maxRatings / bucketSize); i++)
136 | {
137 | buckets.Add(0);
138 | }
139 |
140 | for (int i = 0; i < ArticleIndexToID.Count; i ++)
141 | {
142 | int readers = Users.Select(x => x.ArticleRatings[i]).Count(x => x != 0);
143 | buckets[(int)Math.Floor((double)readers / bucketSize)]++;
144 | }
145 |
146 | while (buckets[buckets.Count - 1] == 0)
147 | {
148 | buckets.RemoveAt(buckets.Count - 1);
149 | }
150 |
151 | using (StreamWriter w = new StreamWriter(file))
152 | {
153 | w.WriteLine("numReaders,numArticles");
154 |
155 | for (int i = 0; i < buckets.Count; i++)
156 | {
157 | w.WriteLine("=\"" + (i * bucketSize) + "-" + (((i + 1) * bucketSize) - 1) + "\"," + buckets[i]);
158 | }
159 | }
160 | }
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/User Behavior/Parsers/DaySplitter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace UserBehavior.Parsers
8 | {
9 | public class DaySplitter : ISplitter
10 | {
11 | public UserBehaviorDatabase TrainingDB { get; private set; }
12 |
13 | public UserBehaviorDatabase TestingDB { get; private set; }
14 |
15 | public DaySplitter(UserBehaviorDatabase db, int daysForTesting)
16 | {
17 | int splitDay = db.UserActions.Max(x => x.Day) - daysForTesting;
18 |
19 | TrainingDB = db.Clone();
20 | TrainingDB.UserActions.RemoveAll(x => x.Day > splitDay);
21 |
22 | TestingDB = db.Clone();
23 | TestingDB.UserActions.RemoveAll(x => x.Day <= splitDay);
24 |
25 | // Remove any user-article pairs from the testing set that are also in the training set
26 | TestingDB.UserActions.RemoveAll(test => TrainingDB.UserActions.Any(train => train.UserID == test.UserID && train.ArticleID == test.ArticleID));
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/User Behavior/Parsers/UserBehaviorDatabase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Runtime.Serialization.Formatters.Binary;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using UserBehavior.Objects;
9 |
10 | namespace UserBehavior.Parsers
11 | {
12 | [Serializable]
13 | public class UserBehaviorDatabase
14 | {
15 | public List Tags { get; set; }
16 |
17 | public List Articles { get; set; }
18 |
19 | public List Users { get; set; }
20 |
21 | public List UserActions { get; set; }
22 |
23 | public UserBehaviorDatabase()
24 | {
25 | Tags = new List();
26 | Articles = new List();
27 | Users = new List();
28 | UserActions = new List();
29 | }
30 |
31 | public List GetArticleTagLinkingTable()
32 | {
33 | List articleTags = new List();
34 |
35 | foreach (Article article in Articles)
36 | {
37 | foreach (Tag tag in article.Tags)
38 | {
39 | articleTags.Add(new ArticleTag(article.ArticleID, tag.Name));
40 | }
41 | }
42 |
43 | return articleTags;
44 | }
45 |
46 | public UserBehaviorDatabase Clone()
47 | {
48 | using (MemoryStream ms = new MemoryStream())
49 | {
50 | BinaryFormatter formatter = new BinaryFormatter();
51 | formatter.Serialize(ms, this);
52 | ms.Position = 0;
53 | return (UserBehaviorDatabase)formatter.Deserialize(ms);
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/User Behavior/Parsers/UserBehaviorDatabaseParser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using UserBehavior.Objects;
8 |
9 | namespace UserBehavior.Parsers
10 | {
11 | public class UserBehaviorDatabaseParser
12 | {
13 | private const string HeaderTags = "# Tags";
14 | private const string HeaderArticles = "# Articles";
15 | private const string HeaderUsers = "# Users";
16 | private const string HeaderUserActions = "# User actions";
17 |
18 | public UserBehaviorDatabaseParser()
19 | {
20 |
21 | }
22 |
23 | public UserBehaviorDatabase LoadUserBehaviorDatabase(string file)
24 | {
25 | UserBehaviorDatabase db = new UserBehaviorDatabase();
26 |
27 | List lines = File.ReadAllLines(file).ToList();
28 |
29 | // Get the indexes of each data section
30 | int tagsIndex = lines.FindIndex(x => x.StartsWith(HeaderTags));
31 | int articlesIndex = lines.FindIndex(x => x.StartsWith(HeaderArticles));
32 | int usersIndex = lines.FindIndex(x => x.StartsWith(HeaderUsers));
33 | int userActionsIndex = lines.FindIndex(x => x.StartsWith(HeaderUserActions));
34 |
35 | // Parse out the tags
36 | for (int i = tagsIndex; i < articlesIndex; i++)
37 | {
38 | if (!lines[i].Trim().StartsWith("#") && lines[i].Trim().Length > 0)
39 | {
40 | foreach (string s in lines[i].Split(','))
41 | {
42 | db.Tags.Add(new Tag(s.Trim()));
43 | }
44 | }
45 | }
46 |
47 | // Parse out the articles
48 | for (int i = articlesIndex; i < usersIndex; i++)
49 | {
50 | if (!lines[i].Trim().StartsWith("#") && lines[i].Trim().Length > 0)
51 | {
52 | string[] cols = lines[i].Split(',');
53 | List tags = new List();
54 |
55 | for (int c = 2; c < cols.Length; c++)
56 | {
57 | tags.Add(new Tag(cols[c].Trim()));
58 | }
59 |
60 | db.Articles.Add(new Article(int.Parse(cols[0].Trim()), cols[1].Trim(), tags));
61 | }
62 | }
63 |
64 | // Parse out the users
65 | for (int i = usersIndex; i < userActionsIndex; i++)
66 | {
67 | if (!lines[i].Trim().StartsWith("#") && lines[i].Trim().Length > 0)
68 | {
69 | string[] cols = lines[i].Split(',');
70 | db.Users.Add(new User(int.Parse(cols[0].Trim()), cols[1].Trim()));
71 | }
72 | }
73 |
74 | // Parse out the user actions
75 | for (int i = userActionsIndex; i < lines.Count; i++)
76 | {
77 | if (!lines[i].Trim().StartsWith("#") && lines[i].Trim().Length > 0)
78 | {
79 | string[] cols = lines[i].Split(',');
80 | db.UserActions.Add(new UserAction(int.Parse(cols[0].Trim()), cols[1].Trim(), int.Parse(cols[2].Trim()), cols[3].Trim(), int.Parse(cols[4].Trim()), cols[5].Trim()));
81 | }
82 | }
83 |
84 | return db;
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/User Behavior/Parsers/UserBehaviorTransformer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using UserBehavior.Abstractions;
8 | using UserBehavior.Objects;
9 |
10 | namespace UserBehavior.Parsers
11 | {
12 | public class UserBehaviorTransformer
13 | {
14 | private UserBehaviorDatabase db;
15 |
16 | public UserBehaviorTransformer(UserBehaviorDatabase database)
17 | {
18 | db = database;
19 | }
20 |
21 | ///
22 | /// Get a list of all users and their ratings on every article
23 | ///
24 | public UserArticleRatingsTable GetUserArticleRatingsTable(IRater rater)
25 | {
26 | UserArticleRatingsTable table = new UserArticleRatingsTable();
27 |
28 | table.UserIndexToID = db.Users.OrderBy(x => x.UserID).Select(x => x.UserID).Distinct().ToList();
29 | table.ArticleIndexToID = db.Articles.OrderBy(x => x.ArticleID).Select(x => x.ArticleID).Distinct().ToList();
30 |
31 | foreach (int userId in table.UserIndexToID)
32 | {
33 | table.Users.Add(new UserArticleRatings(userId, table.ArticleIndexToID.Count));
34 | }
35 |
36 | var userArticleRatingGroup = db.UserActions
37 | .GroupBy(x => new { x.UserID, x.ArticleID })
38 | .Select(g => new { g.Key.UserID, g.Key.ArticleID, Rating = rater.GetRating(g.ToList()) })
39 | .ToList();
40 |
41 | foreach (var userAction in userArticleRatingGroup)
42 | {
43 | int userIndex = table.UserIndexToID.IndexOf(userAction.UserID);
44 | int articleIndex = table.ArticleIndexToID.IndexOf(userAction.ArticleID);
45 |
46 | table.Users[userIndex].ArticleRatings[articleIndex] = userAction.Rating;
47 | }
48 |
49 | return table;
50 | }
51 |
52 | ///
53 | /// Get a table of all articles as rows and all tags as columns
54 | ///
55 | public List GetArticleTagCounts()
56 | {
57 | List articleTags = new List();
58 |
59 | foreach (Article article in db.Articles)
60 | {
61 | ArticleTagCounts articleTag = new ArticleTagCounts(article.ArticleID, db.Tags.Count);
62 |
63 | for (int tag = 0; tag < db.Tags.Count; tag++)
64 | {
65 | articleTag.TagCounts[tag] = article.Tags.Any(x => x.Name == db.Tags[tag].Name) ? 1.0 : 0.0;
66 | }
67 |
68 | articleTags.Add(articleTag);
69 | }
70 |
71 | return articleTags;
72 | }
73 |
74 | ///
75 | /// Get a list of all users and the number of times they viewed articles with a specific tag
76 | ///
77 | [Obsolete]
78 | public List GetUserTags()
79 | {
80 | List userData = new List();
81 | List articleTags = db.GetArticleTagLinkingTable();
82 |
83 | int numFeatures = db.Tags.Count;
84 |
85 | // Create a dataset that links every user action to a list of tags associated with the article that action was for, then
86 | // group them by user, action, and tag so we can get a count of the number of times each user performed a action on an article with a specific tag
87 | var userActionTags = db.UserActions
88 | .Join(articleTags, u => u.ArticleID, t => t.ArticleID, (u, t) => new { u.UserID, t.TagName })
89 | .GroupBy(x => new { x.UserID, x.TagName })
90 | .Select(g => new { g.Key.UserID, g.Key.TagName, Count = g.Count() })
91 | .OrderBy(x => x.UserID).ThenBy(x => x.TagName)
92 | .ToList();
93 |
94 | int totalUserActions = userActionTags.Count();
95 | int lastFoundIndex = 0;
96 |
97 | // Get action-tag data
98 | foreach (User user in db.Users)
99 | {
100 | int dataCol = 0;
101 | UserActionTag uat = new UserActionTag(user.UserID, numFeatures);
102 |
103 | foreach (Tag tag in db.Tags)
104 | {
105 | // Count the number of times this user interacted with an article with this tag
106 | // We can loop through like this since the list is sorted
107 | int tagActionCount = 0;
108 | for (int i = lastFoundIndex; i < totalUserActions; i++)
109 | {
110 | if (userActionTags[i].UserID == user.UserID && userActionTags[i].TagName == tag.Name)
111 | {
112 | lastFoundIndex = i;
113 | tagActionCount = userActionTags[i].Count;
114 | break;
115 | }
116 | }
117 |
118 | uat.ActionTagData[dataCol++] = tagActionCount;
119 | }
120 |
121 | // Normalize data to values between 0 and 5
122 | double upperLimit = 5.0;
123 | double max = uat.ActionTagData.Max();
124 | if (max > 0)
125 | {
126 | for (int i = 0; i < uat.ActionTagData.Length; i++)
127 | {
128 | uat.ActionTagData[i] = (uat.ActionTagData[i] / max) * upperLimit;
129 | }
130 | }
131 |
132 | userData.Add(uat);
133 | }
134 |
135 | return userData;
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/User Behavior/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("User Behavior")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("Scott Clayton")]
12 | [assembly: AssemblyProduct("User Behavior")]
13 | [assembly: AssemblyCopyright("Copyright © 2018")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("db5a3fef-d18f-4901-9b4f-959e403bd21b")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/User Behavior/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace UserBehavior.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UserBehavior.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/User Behavior/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/User Behavior/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace UserBehavior.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/User Behavior/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/User Behavior/Raters/LinearRater.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using UserBehavior.Abstractions;
7 | using UserBehavior.Objects;
8 |
9 | namespace UserBehavior.Raters
10 | {
11 | public class LinearRater : IRater
12 | {
13 | private double downVoteWeight;
14 | private double upVoteWeight;
15 | private double viewWeight;
16 | private double downloadWeight;
17 |
18 | private double minWeight;
19 | private double maxWeight;
20 |
21 | public LinearRater()
22 | : this (-5.0, 1.0, 3.0, 0.5, 5.0)
23 | {
24 | }
25 |
26 | public LinearRater(double downVote, double upvote, double view, double download)
27 | : this (downVote, upvote, view, download, 5.0)
28 | {
29 | }
30 |
31 | public LinearRater(double downVote, double upVote, double view, double download, double max)
32 | {
33 | downVoteWeight = downVote;
34 | upVoteWeight = upVote;
35 | viewWeight = view;
36 | downloadWeight = download;
37 |
38 | minWeight = 0.1;
39 | maxWeight = max;
40 | }
41 |
42 | public double GetRating(List actions)
43 | {
44 | int up = actions.Count(x => x.Action == "UpVote");
45 | int down = actions.Count(x => x.Action == "DownVote");
46 | int view = actions.Count(x => x.Action == "View");
47 | int dl = actions.Count(x => x.Action == "Download");
48 |
49 | double rating = up * upVoteWeight + down * downVoteWeight + view * viewWeight + dl * downloadWeight;
50 |
51 | return Math.Min(maxWeight, Math.Max(minWeight, rating));
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/User Behavior/Raters/SimpleRater.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using UserBehavior.Abstractions;
7 | using UserBehavior.Objects;
8 |
9 | namespace UserBehavior.Raters
10 | {
11 | ///
12 | /// Return 0 if the article was downvoted, otherwise provide a rating of 1.
13 | ///
14 | public class SimpleRater : IRater
15 | {
16 | public double GetRating(List actions)
17 | {
18 | if (actions.Any(x => x.Action == "DownVote"))
19 | {
20 | return 0.0;
21 | }
22 |
23 | return 1.0;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/User Behavior/Raters/WeightedRater.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using UserBehavior.Abstractions;
7 | using UserBehavior.Objects;
8 |
9 | namespace UserBehavior.Raters
10 | {
11 | public class WeightedRater : IRater
12 | {
13 | private double downVoteWeight;
14 | private double baseWeight;
15 | private double upVoteWeight;
16 | private double viewWeight;
17 | private double downloadWeight;
18 | private double maxWeight;
19 |
20 | public WeightedRater()
21 | : this (0.1, 3.0, 1.0, 0.5, 0.5, 5.0)
22 | {
23 | }
24 |
25 | public WeightedRater(double downVote, double baseVal, double upvote, double view, double download)
26 | : this (downVote, baseVal, upvote, view, download, 5.0)
27 | {
28 | }
29 |
30 | public WeightedRater(double downVote, double baseVal, double upvote, double view, double download, double max)
31 | {
32 | downVoteWeight = downVote;
33 | baseWeight = baseVal;
34 | upVoteWeight = upvote;
35 | viewWeight = view;
36 | downloadWeight = download;
37 | maxWeight = max;
38 | }
39 |
40 | public double GetRating(List actions)
41 | {
42 | double rating;
43 | string lastVote = actions.LastOrDefault(x => x.Action == "DownVote" || x.Action == "UpVote")?.Action ?? "";
44 | int viewCount = actions.Count(x => x.Action == "View");
45 | bool downloaded = actions.Any(x => x.Action == "Download");
46 |
47 | if (lastVote == "DownVote")
48 | {
49 | rating = downVoteWeight;
50 | }
51 | else
52 | {
53 | rating = baseWeight;
54 |
55 | rating += lastVote == "UpVote" ? upVoteWeight : 0.0;
56 | rating += viewCount * viewWeight;
57 | rating += downloaded ? downloadWeight : 0.0;
58 | }
59 |
60 | return Math.Min(maxWeight, Math.Max(0.0, rating));
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/User Behavior/Recommender.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {DB5A3FEF-D18F-4901-9B4F-959E403BD21B}
8 | Library
9 | Properties
10 | UserBehavior
11 | ScottClayton.Recommendation
12 | v4.5.2
13 | 512
14 | true
15 |
16 |
17 | AnyCPU
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | AnyCPU
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | ResXFileCodeGenerator
95 | Resources.Designer.cs
96 | Designer
97 |
98 |
99 | True
100 | Resources.resx
101 | True
102 |
103 |
104 | SettingsSingleFileGenerator
105 | Settings.Designer.cs
106 |
107 |
108 | True
109 | Settings.settings
110 | True
111 |
112 |
113 |
114 |
121 |
--------------------------------------------------------------------------------
/User Behavior/Recommenders/ClassifierExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using UserBehavior.Abstractions;
7 | using UserBehavior.Objects;
8 | using UserBehavior.Parsers;
9 | using UserBehavior.Raters;
10 |
11 | namespace UserBehavior.Recommenders
12 | {
13 | public static class ClassifierExtensions
14 | {
15 | public static TestResults Test(this IRecommender classifier, UserBehaviorDatabase db, int numSuggestions)
16 | {
17 | // We're only using the ratings to check for existence of a rating, so we can use a simple rater for everything
18 | SimpleRater rater = new SimpleRater();
19 | UserBehaviorTransformer ubt = new UserBehaviorTransformer(db);
20 | UserArticleRatingsTable ratings = ubt.GetUserArticleRatingsTable(rater);
21 |
22 | int correctUsers = 0;
23 | double averagePrecision = 0.0;
24 | double averageRecall = 0.0;
25 |
26 | // Get a list of users in this database who interacted with an article for the first time
27 | List distinctUsers = db.UserActions.Select(x => x.UserID).Distinct().ToList();
28 |
29 | var distinctUserArticles = db.UserActions.GroupBy(x => new { x.UserID, x.ArticleID });
30 |
31 | // Now get suggestions for each of these users
32 | foreach (int user in distinctUsers)
33 | {
34 | List suggestions = classifier.GetSuggestions(user, numSuggestions);
35 | bool foundOne = false;
36 | int userIndex = ratings.UserIndexToID.IndexOf(user);
37 |
38 | int userCorrectArticles = 0;
39 | int userTotalArticles = distinctUserArticles.Count(x => x.Key.UserID == user);
40 |
41 | foreach (Suggestion s in suggestions)
42 | {
43 | int articleIndex = ratings.ArticleIndexToID.IndexOf(s.ArticleID);
44 |
45 | // If one of the top N suggestions is what the user ended up reading, then we're golden
46 | if (ratings.Users[userIndex].ArticleRatings[articleIndex] != 0)
47 | {
48 | userCorrectArticles++;
49 |
50 | if (!foundOne)
51 | {
52 | correctUsers++;
53 | foundOne = true;
54 | }
55 | }
56 | }
57 |
58 | averagePrecision += (double)userCorrectArticles / numSuggestions;
59 | averageRecall += (double)userCorrectArticles / userTotalArticles;
60 | }
61 |
62 | averagePrecision /= distinctUsers.Count;
63 | averageRecall /= distinctUsers.Count;
64 |
65 | return new TestResults(distinctUsers.Count, correctUsers, averageRecall, averagePrecision);
66 | }
67 |
68 | public static ScoreResults Score(this IRecommender classifier, UserBehaviorDatabase db, IRater rater)
69 | {
70 | UserBehaviorTransformer ubt = new UserBehaviorTransformer(db);
71 | UserArticleRatingsTable actualRatings = ubt.GetUserArticleRatingsTable(rater);
72 |
73 | var distinctUserArticlePairs = db.UserActions.GroupBy(x => new { x.UserID, x.ArticleID }).ToList();
74 |
75 | double score = 0.0;
76 | int count = 0;
77 |
78 | foreach (var userArticle in distinctUserArticlePairs)
79 | {
80 | int userIndex = actualRatings.UserIndexToID.IndexOf(userArticle.Key.UserID);
81 | int articleIndex = actualRatings.ArticleIndexToID.IndexOf(userArticle.Key.ArticleID);
82 |
83 | double actualRating = actualRatings.Users[userIndex].ArticleRatings[articleIndex];
84 |
85 | if (actualRating != 0)
86 | {
87 | double predictedRating = classifier.GetRating(userArticle.Key.UserID, userArticle.Key.ArticleID);
88 |
89 | score += Math.Pow(predictedRating - actualRating, 2);
90 | count++;
91 | }
92 | }
93 |
94 | if (count > 0)
95 | {
96 | score = Math.Sqrt(score / count);
97 | }
98 |
99 | return new ScoreResults(score);
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/User Behavior/Recommenders/HybridRecommender.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using UserBehavior.Objects;
8 | using UserBehavior.Parsers;
9 |
10 | namespace UserBehavior.Recommenders
11 | {
12 | public class HybridRecommender : IRecommender
13 | {
14 | private List classifiers;
15 |
16 | public HybridRecommender()
17 | {
18 | classifiers = new List();
19 | }
20 |
21 | public HybridRecommender(params IRecommender[] recommenders)
22 | {
23 | classifiers = recommenders.ToList();
24 | }
25 |
26 | public void Add(IRecommender classifier)
27 | {
28 | classifiers.Add(classifier);
29 | }
30 |
31 | public void Train(UserBehaviorDatabase db)
32 | {
33 | foreach (IRecommender classifier in classifiers)
34 | {
35 | classifier.Train(db);
36 | }
37 | }
38 |
39 | public double GetRating(int userId, int articleId)
40 | {
41 | return classifiers.Select(classifier => classifier.GetRating(userId, articleId)).Average();
42 | }
43 |
44 | public List GetSuggestions(int userId, int numSuggestions)
45 | {
46 | List suggestions = new List();
47 | int numSuggestionsEach = (int)Math.Ceiling((double)numSuggestions / classifiers.Count);
48 |
49 | foreach (IRecommender classifier in classifiers)
50 | {
51 | suggestions.AddRange(classifier.GetSuggestions(userId, numSuggestionsEach));
52 | }
53 |
54 | suggestions.Sort((c, n) => n.Rating.CompareTo(c.Rating));
55 |
56 | return suggestions.Take(numSuggestions).ToList();
57 | }
58 |
59 | private List GetCommonSuggestions(int userId, int numSuggestions)
60 | {
61 | int internalSuggestions = 100;
62 |
63 | List> suggestions = new List>();
64 | foreach (IRecommender classifier in classifiers)
65 | {
66 | suggestions.Add(classifier.GetSuggestions(userId, internalSuggestions));
67 | }
68 |
69 | List> final = new List>();
70 |
71 | foreach (List list in suggestions)
72 | {
73 | foreach (Suggestion suggestion in list)
74 | {
75 | int existingIndex = final.FindIndex(x => x.Item1.ArticleID == suggestion.ArticleID);
76 |
77 | if (existingIndex >= 0)
78 | {
79 | Suggestion highestRated = final[existingIndex].Item1.Rating > suggestion.Rating ? final[existingIndex].Item1 : suggestion;
80 | final[existingIndex] = new Tuple(highestRated, final[existingIndex].Item2 + 1);
81 | }
82 | else
83 | {
84 | final.Add(new Tuple(suggestion, 1));
85 | }
86 | }
87 | }
88 |
89 | final = final.OrderByDescending(x => x.Item2).ThenByDescending(x => x.Item1.Rating).ToList();
90 |
91 | return final.Select(x => x.Item1).Take(numSuggestions).ToList();
92 | }
93 |
94 | public void Save(string file)
95 | {
96 | int n = 1;
97 | foreach (IRecommender classifier in classifiers)
98 | {
99 | classifier.Save(file + ".rm" + (n++));
100 | }
101 | }
102 |
103 | public void Load(string file)
104 | {
105 | int n = 1;
106 | foreach (IRecommender classifier in classifiers)
107 | {
108 | classifier.Load(file + ".rm" + (n++));
109 | }
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/User Behavior/Recommenders/ItemCollaborativeFilterRecommender.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.IO.Compression;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using UserBehavior.Abstractions;
9 | using UserBehavior.Comparers;
10 | using UserBehavior.Mathematics;
11 | using UserBehavior.Objects;
12 | using UserBehavior.Parsers;
13 |
14 | namespace UserBehavior.Recommenders
15 | {
16 | public class ItemCollaborativeFilterRecommender : IRecommender
17 | {
18 | private IComparer comparer;
19 | private IRater rater;
20 | private UserArticleRatingsTable ratings;
21 | private double[][] transposedRatings;
22 |
23 | private int neighborCount;
24 |
25 | public ItemCollaborativeFilterRecommender(IComparer itemComparer, IRater implicitRater, int numberOfNeighbors)
26 | {
27 | comparer = itemComparer;
28 | rater = implicitRater;
29 | neighborCount = numberOfNeighbors;
30 | }
31 |
32 | private void FillTransposedRatings()
33 | {
34 | int features = ratings.Users.Count;
35 | transposedRatings = new double[ratings.ArticleIndexToID.Count][];
36 |
37 | // Precompute a transposed ratings matrix where each row becomes an article and each column a user or tag
38 | for (int a = 0; a < ratings.ArticleIndexToID.Count; a++)
39 | {
40 | transposedRatings[a] = new double[features];
41 |
42 | for (int f = 0; f < features; f++)
43 | {
44 | transposedRatings[a][f] = ratings.Users[f].ArticleRatings[a];
45 | }
46 | }
47 | }
48 |
49 | private List GetHighestRatedArticlesForUser(int userIndex)
50 | {
51 | List> items = new List>();
52 |
53 | for (int articleIndex = 0; articleIndex < ratings.ArticleIndexToID.Count; articleIndex++)
54 | {
55 | // Create a list of every article this user has viewed
56 | if (ratings.Users[userIndex].ArticleRatings[articleIndex] != 0)
57 | {
58 | items.Add(new Tuple(articleIndex, ratings.Users[userIndex].ArticleRatings[articleIndex]));
59 | }
60 | }
61 |
62 | // Sort the articles by rating
63 | items.Sort((c, n) => n.Item2.CompareTo(c.Item2));
64 |
65 | return items.Select(x => x.Item1).ToList();
66 | }
67 |
68 | public void Train(UserBehaviorDatabase db)
69 | {
70 | UserBehaviorTransformer ubt = new UserBehaviorTransformer(db);
71 | ratings = ubt.GetUserArticleRatingsTable(rater);
72 |
73 | List articleTags = ubt.GetArticleTagCounts();
74 | ratings.AppendArticleFeatures(articleTags);
75 |
76 | FillTransposedRatings();
77 | }
78 |
79 | public double GetRating(int userId, int articleId)
80 | {
81 | int userIndex = ratings.UserIndexToID.IndexOf(userId);
82 | int articleIndex = ratings.ArticleIndexToID.IndexOf(articleId);
83 |
84 | var userRatings = ratings.Users[userIndex].ArticleRatings.Where(x => x != 0);
85 | var articleRatings = ratings.Users.Select(x => x.ArticleRatings[articleIndex]).Where(x => x != 0);
86 |
87 | double averageUser = userRatings.Count() > 0 ? userRatings.Average() : 0;
88 | double averageArticle = articleRatings.Count() > 0 ? articleRatings.Average() : 0;
89 |
90 | // For now, just return the average rating across this user and article
91 | return averageArticle > 0 && averageUser > 0 ? (averageUser + averageArticle) / 2.0 : Math.Max(averageUser, averageArticle);
92 | }
93 |
94 | public List GetSuggestions(int userId, int numSuggestions)
95 | {
96 | int userIndex = ratings.UserIndexToID.IndexOf(userId);
97 | List articles = GetHighestRatedArticlesForUser(userIndex).Take(5).ToList();
98 | List suggestions = new List();
99 |
100 | foreach (int articleIndex in articles)
101 | {
102 | int articleId = ratings.ArticleIndexToID[articleIndex];
103 | List neighboringArticles = GetNearestNeighbors(articleId, neighborCount);
104 |
105 | foreach (ArticleRating neighbor in neighboringArticles)
106 | {
107 | int neighborArticleIndex = ratings.ArticleIndexToID.IndexOf(neighbor.ArticleID);
108 |
109 | double averageArticleRating = 0.0;
110 | int count = 0;
111 | for (int userRatingIndex = 0; userRatingIndex < ratings.UserIndexToID.Count; userRatingIndex++)
112 | {
113 | if (transposedRatings[neighborArticleIndex][userRatingIndex] != 0)
114 | {
115 | averageArticleRating += transposedRatings[neighborArticleIndex][userRatingIndex];
116 | count++;
117 | }
118 | }
119 | if (count > 0)
120 | {
121 | averageArticleRating /= count;
122 | }
123 |
124 | suggestions.Add(new Suggestion(userId, neighbor.ArticleID, averageArticleRating));
125 | }
126 | }
127 |
128 | suggestions.Sort((c, n) => n.Rating.CompareTo(c.Rating));
129 |
130 | return suggestions.Take(numSuggestions).ToList();
131 | }
132 |
133 | private List GetNearestNeighbors(int articleId, int numArticles)
134 | {
135 | List neighbors = new List();
136 | int mainArticleIndex = ratings.ArticleIndexToID.IndexOf(articleId);
137 |
138 | for (int articleIndex = 0; articleIndex < ratings.ArticleIndexToID.Count; articleIndex++)
139 | {
140 | int searchArticleId = ratings.ArticleIndexToID[articleIndex];
141 |
142 | double score = comparer.CompareVectors(transposedRatings[mainArticleIndex], transposedRatings[articleIndex]);
143 |
144 | neighbors.Add(new ArticleRating(searchArticleId, score));
145 | }
146 |
147 | neighbors.Sort((c, n) => n.Rating.CompareTo(c.Rating));
148 |
149 | return neighbors.Take(numArticles).ToList();
150 | }
151 |
152 | public void Save(string file)
153 | {
154 | using (FileStream fs = new FileStream(file, FileMode.Create))
155 | using (GZipStream zip = new GZipStream(fs, CompressionMode.Compress))
156 | using (StreamWriter w = new StreamWriter(zip))
157 | {
158 | w.WriteLine(ratings.Users.Count);
159 | w.WriteLine(ratings.Users[0].ArticleRatings.Length);
160 |
161 | foreach (UserArticleRatings t in ratings.Users)
162 | {
163 | w.WriteLine(t.UserID);
164 |
165 | foreach (double v in t.ArticleRatings)
166 | {
167 | w.WriteLine(v);
168 | }
169 | }
170 |
171 | w.WriteLine(ratings.UserIndexToID.Count);
172 |
173 | foreach (int i in ratings.UserIndexToID)
174 | {
175 | w.WriteLine(i);
176 | }
177 |
178 | w.WriteLine(ratings.ArticleIndexToID.Count);
179 |
180 | foreach (int i in ratings.ArticleIndexToID)
181 | {
182 | w.WriteLine(i);
183 | }
184 | }
185 | }
186 |
187 | public void Load(string file)
188 | {
189 | ratings = new UserArticleRatingsTable();
190 |
191 | using (FileStream fs = new FileStream(file, FileMode.Open))
192 | using (GZipStream zip = new GZipStream(fs, CompressionMode.Decompress))
193 | using (StreamReader r = new StreamReader(zip))
194 | {
195 | long total = long.Parse(r.ReadLine());
196 | int features = int.Parse(r.ReadLine());
197 |
198 | for (long i = 0; i < total; i++)
199 | {
200 | int userId = int.Parse(r.ReadLine());
201 | UserArticleRatings uat = new UserArticleRatings(userId, features);
202 |
203 | for (int x = 0; x < features; x++)
204 | {
205 | uat.ArticleRatings[x] = double.Parse(r.ReadLine());
206 | }
207 |
208 | ratings.Users.Add(uat);
209 | }
210 |
211 | total = int.Parse(r.ReadLine());
212 |
213 | for (int i = 0; i < total; i++)
214 | {
215 | ratings.UserIndexToID.Add(int.Parse(r.ReadLine()));
216 | }
217 |
218 | total = int.Parse(r.ReadLine());
219 |
220 | for (int i = 0; i < total; i++)
221 | {
222 | ratings.ArticleIndexToID.Add(int.Parse(r.ReadLine()));
223 | }
224 | }
225 |
226 | FillTransposedRatings();
227 | }
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/User Behavior/Recommenders/MatrixFactorizationRecommender.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.IO.Compression;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using UserBehavior.Abstractions;
9 | using UserBehavior.Mathematics;
10 | using UserBehavior.Objects;
11 | using UserBehavior.Parsers;
12 |
13 | namespace UserBehavior.Recommenders
14 | {
15 | public class MatrixFactorizationRecommender : IRecommender
16 | {
17 | private UserArticleRatingsTable ratings;
18 | private SvdResult svd;
19 | private IRater rater;
20 |
21 | private int numUsers;
22 | private int numArticles;
23 |
24 | private int numFeatures;
25 | private int learningIterations;
26 |
27 | public MatrixFactorizationRecommender(IRater implicitRater)
28 | : this(20, implicitRater)
29 | {
30 | }
31 |
32 | public MatrixFactorizationRecommender(int features, IRater implicitRater)
33 | {
34 | numFeatures = features;
35 | learningIterations = 100;
36 | rater = implicitRater;
37 | }
38 |
39 | public void Train(UserBehaviorDatabase db)
40 | {
41 | UserBehaviorTransformer ubt = new UserBehaviorTransformer(db);
42 | ratings = ubt.GetUserArticleRatingsTable(rater);
43 |
44 | SingularValueDecomposition factorizer = new SingularValueDecomposition(numFeatures, learningIterations);
45 | svd = factorizer.FactorizeMatrix(ratings);
46 |
47 | numUsers = ratings.UserIndexToID.Count;
48 | numArticles = ratings.ArticleIndexToID.Count;
49 | }
50 |
51 | public double GetRating(int userId, int articleId)
52 | {
53 | int userIndex = ratings.UserIndexToID.IndexOf(userId);
54 | int articleIndex = ratings.ArticleIndexToID.IndexOf(articleId);
55 |
56 | return GetRatingForIndex(userIndex, articleIndex);
57 | }
58 |
59 | private double GetRatingForIndex(int userIndex, int articleIndex)
60 | {
61 | return svd.AverageGlobalRating + svd.UserBiases[userIndex] + svd.ArticleBiases[articleIndex] + Matrix.GetDotProduct(svd.UserFeatures[userIndex], svd.ArticleFeatures[articleIndex]);
62 | }
63 |
64 | public List GetSuggestions(int userId, int numSuggestions)
65 | {
66 | int userIndex = ratings.UserIndexToID.IndexOf(userId);
67 | UserArticleRatings user = ratings.Users[userIndex];
68 | List suggestions = new List();
69 |
70 | for (int articleIndex = 0; articleIndex < ratings.ArticleIndexToID.Count; articleIndex++)
71 | {
72 | // If the user in question hasn't rated the given article yet
73 | if (user.ArticleRatings[articleIndex] == 0)
74 | {
75 | double rating = GetRatingForIndex(userIndex, articleIndex);
76 |
77 | suggestions.Add(new Suggestion(userId, ratings.ArticleIndexToID[articleIndex], rating));
78 | }
79 | }
80 |
81 | suggestions.Sort((c, n) => n.Rating.CompareTo(c.Rating));
82 |
83 | return suggestions.Take(numSuggestions).ToList();
84 | }
85 |
86 | public void Save(string file)
87 | {
88 | using (FileStream fs = new FileStream(file, FileMode.Create))
89 | using (GZipStream zip = new GZipStream(fs, CompressionMode.Compress))
90 | using (StreamWriter w = new StreamWriter(zip))
91 | {
92 | w.WriteLine(numUsers);
93 | w.WriteLine(numArticles);
94 | w.WriteLine(numFeatures);
95 |
96 | w.WriteLine(svd.AverageGlobalRating);
97 |
98 | for (int userIndex = 0; userIndex < numUsers; userIndex++)
99 | {
100 | w.WriteLine(svd.UserBiases[userIndex]);
101 | }
102 |
103 | for (int articleIndex = 0; articleIndex < numArticles; articleIndex++)
104 | {
105 | w.WriteLine(svd.ArticleBiases[articleIndex]);
106 | }
107 |
108 | for (int userIndex = 0; userIndex < numUsers; userIndex++)
109 | {
110 | for (int featureIndex = 0; featureIndex < numFeatures; featureIndex++)
111 | {
112 | w.WriteLine(svd.UserFeatures[userIndex][featureIndex]);
113 | }
114 | }
115 |
116 | for (int articleIndex = 0; articleIndex < numUsers; articleIndex++)
117 | {
118 | for (int featureIndex = 0; featureIndex < numFeatures; featureIndex++)
119 | {
120 | w.WriteLine(svd.ArticleFeatures[articleIndex][featureIndex]);
121 | }
122 | }
123 |
124 | foreach (UserArticleRatings t in ratings.Users)
125 | {
126 | w.WriteLine(t.UserID);
127 |
128 | foreach (double v in t.ArticleRatings)
129 | {
130 | w.WriteLine(v);
131 | }
132 | }
133 |
134 | foreach (int i in ratings.UserIndexToID)
135 | {
136 | w.WriteLine(i);
137 | }
138 |
139 | foreach (int i in ratings.ArticleIndexToID)
140 | {
141 | w.WriteLine(i);
142 | }
143 | }
144 | }
145 |
146 | public void Load(string file)
147 | {
148 | ratings = new UserArticleRatingsTable();
149 |
150 | using (FileStream fs = new FileStream(file, FileMode.Open))
151 | using (GZipStream zip = new GZipStream(fs, CompressionMode.Decompress))
152 | using (StreamReader r = new StreamReader(zip))
153 | {
154 | numUsers = int.Parse(r.ReadLine());
155 | numArticles = int.Parse(r.ReadLine());
156 | numFeatures = int.Parse(r.ReadLine());
157 |
158 | double averageGlobalRating = double.Parse(r.ReadLine());
159 |
160 | double[] userBiases = new double[numUsers];
161 | for (int userIndex = 0; userIndex < numUsers; userIndex++)
162 | {
163 | userBiases[userIndex] = double.Parse(r.ReadLine());
164 | }
165 |
166 | double[] articleBiases = new double[numArticles];
167 | for (int articleIndex = 0; articleIndex < numArticles; articleIndex++)
168 | {
169 | articleBiases[articleIndex] = double.Parse(r.ReadLine());
170 | }
171 |
172 | double[][] userFeatures = new double[numUsers][];
173 | for (int userIndex = 0; userIndex < numUsers; userIndex++)
174 | {
175 | userFeatures[userIndex] = new double[numFeatures];
176 |
177 | for (int featureIndex = 0; featureIndex < numFeatures; featureIndex++)
178 | {
179 | userFeatures[userIndex][featureIndex] = double.Parse(r.ReadLine());
180 | }
181 | }
182 |
183 | double[][] articleFeatures = new double[numArticles][];
184 | for (int articleIndex = 0; articleIndex < numUsers; articleIndex++)
185 | {
186 | articleFeatures[articleIndex] = new double[numFeatures];
187 |
188 | for (int featureIndex = 0; featureIndex < numFeatures; featureIndex++)
189 | {
190 | articleFeatures[articleIndex][featureIndex] = double.Parse(r.ReadLine());
191 | }
192 | }
193 |
194 | svd = new SvdResult(averageGlobalRating, userBiases, articleBiases, userFeatures, articleFeatures);
195 |
196 | for (int i = 0; i < numUsers; i++)
197 | {
198 | int userId = int.Parse(r.ReadLine());
199 | UserArticleRatings uat = new UserArticleRatings(userId, numArticles);
200 |
201 | for (int x = 0; x < numArticles; x++)
202 | {
203 | uat.ArticleRatings[x] = double.Parse(r.ReadLine());
204 | }
205 |
206 | ratings.Users.Add(uat);
207 | }
208 |
209 | for (int i = 0; i < numUsers; i++)
210 | {
211 | ratings.UserIndexToID.Add(int.Parse(r.ReadLine()));
212 | }
213 |
214 | for (int i = 0; i < numArticles; i++)
215 | {
216 | ratings.ArticleIndexToID.Add(int.Parse(r.ReadLine()));
217 | }
218 | }
219 | }
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/User Behavior/Recommenders/RandomRecommender.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using UserBehavior.Objects;
7 | using UserBehavior.Parsers;
8 |
9 | namespace UserBehavior.Recommenders
10 | {
11 | public class RandomRecommender : IRecommender
12 | {
13 | private Random rand;
14 |
15 | public RandomRecommender()
16 | {
17 | rand = new Random();
18 | }
19 |
20 | public void Train(UserBehaviorDatabase db)
21 | {
22 | }
23 |
24 | public double GetRating(int userId, int articleId)
25 | {
26 | return rand.NextDouble() * 5.0;
27 | }
28 |
29 | public List GetSuggestions(int userId, int numSuggestions)
30 | {
31 | List suggestions = new List();
32 |
33 | for (int i = 0; i < numSuggestions; i++)
34 | {
35 | suggestions.Add(new Suggestion(userId, rand.Next(1, 3000), rand.NextDouble() * 5.0));
36 | }
37 |
38 | return suggestions;
39 | }
40 |
41 | public void Load(string file)
42 | {
43 | throw new NotImplementedException();
44 | }
45 |
46 | public void Save(string file)
47 | {
48 | throw new NotImplementedException();
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/User Behavior/Recommenders/UserCollaborativeFilterRecommender.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.IO.Compression;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using UserBehavior.Abstractions;
9 | using UserBehavior.Comparers;
10 | using UserBehavior.Mathematics;
11 | using UserBehavior.Objects;
12 | using UserBehavior.Parsers;
13 |
14 | namespace UserBehavior.Recommenders
15 | {
16 | public class UserCollaborativeFilterRecommender : IRecommender
17 | {
18 | private IComparer comparer;
19 | private IRater rater;
20 | private UserArticleRatingsTable ratings;
21 |
22 | private int neighborCount;
23 | private int latentUserFeatureCount;
24 |
25 | public UserCollaborativeFilterRecommender(IComparer userComparer, IRater implicitRater, int numberOfNeighbors)
26 | : this(userComparer, implicitRater, numberOfNeighbors, 20)
27 | {
28 | }
29 |
30 | public UserCollaborativeFilterRecommender(IComparer userComparer, IRater implicitRater, int numberOfNeighbors, int latentFeatures)
31 | {
32 | comparer = userComparer;
33 | rater = implicitRater;
34 | neighborCount = numberOfNeighbors;
35 | latentUserFeatureCount = latentFeatures;
36 | }
37 |
38 | public void Train(UserBehaviorDatabase db)
39 | {
40 | UserBehaviorTransformer ubt = new UserBehaviorTransformer(db);
41 | ratings = ubt.GetUserArticleRatingsTable(rater);
42 |
43 | if (latentUserFeatureCount > 0)
44 | {
45 | SingularValueDecomposition svd = new SingularValueDecomposition(latentUserFeatureCount, 100);
46 | SvdResult results = svd.FactorizeMatrix(ratings);
47 |
48 | ratings.AppendUserFeatures(results.UserFeatures);
49 | }
50 | }
51 |
52 | public double GetRating(int userId, int articleId)
53 | {
54 | UserArticleRatings user = ratings.Users.FirstOrDefault(x => x.UserID == userId);
55 | List neighbors = GetNearestNeighbors(user, neighborCount);
56 |
57 | return GetRating(user, neighbors, articleId);
58 | }
59 |
60 | private double GetRating(UserArticleRatings user, List neighbors, int articleId)
61 | {
62 | int articleIndex = ratings.ArticleIndexToID.IndexOf(articleId);
63 |
64 | var nonZero = user.ArticleRatings.Where(x => x != 0);
65 | double avgUserRating = nonZero.Count() > 0 ? nonZero.Average() : 0.0;
66 |
67 | double score = 0.0;
68 | int count = 0;
69 | for (int u = 0; u < neighbors.Count; u++)
70 | {
71 | var nonZeroRatings = neighbors[u].ArticleRatings.Where(x => x != 0);
72 | double avgRating = nonZeroRatings.Count() > 0 ? nonZeroRatings.Average() : 0.0;
73 |
74 | if (neighbors[u].ArticleRatings[articleIndex] != 0)
75 | {
76 | score += neighbors[u].ArticleRatings[articleIndex] - avgRating;
77 | count++;
78 | }
79 | }
80 | if (count > 0)
81 | {
82 | score /= count;
83 | score += avgUserRating;
84 | }
85 |
86 | return score;
87 | }
88 |
89 | public List GetSuggestions(int userId, int numSuggestions)
90 | {
91 | int userIndex = ratings.UserIndexToID.IndexOf(userId);
92 | UserArticleRatings user = ratings.Users[userIndex];
93 | List suggestions = new List();
94 |
95 | var neighbors = GetNearestNeighbors(user, neighborCount);
96 |
97 | for (int articleIndex = 0; articleIndex < ratings.ArticleIndexToID.Count; articleIndex++)
98 | {
99 | // If the user in question hasn't rated the given article yet
100 | if (user.ArticleRatings[articleIndex] == 0)
101 | {
102 | double score = 0.0;
103 | int count = 0;
104 | for (int u = 0; u < neighbors.Count; u++)
105 | {
106 | if (neighbors[u].ArticleRatings[articleIndex] != 0)
107 | {
108 | // Calculate the weighted score for this article
109 | score += neighbors[u].ArticleRatings[articleIndex] - ((u + 1.0) / 100.0);
110 | count++;
111 | }
112 | }
113 | if (count > 0)
114 | {
115 | score /= count;
116 | }
117 |
118 | suggestions.Add(new Suggestion(userId, ratings.ArticleIndexToID[articleIndex], score));
119 | }
120 | }
121 |
122 | suggestions.Sort((c, n) => n.Rating.CompareTo(c.Rating));
123 |
124 | return suggestions.Take(numSuggestions).ToList();
125 | }
126 |
127 | private List GetNearestNeighbors(UserArticleRatings user, int numUsers)
128 | {
129 | List neighbors = new List();
130 |
131 | for (int i = 0; i < ratings.Users.Count; i++)
132 | {
133 | if (ratings.Users[i].UserID == user.UserID)
134 | {
135 | ratings.Users[i].Score = double.NegativeInfinity;
136 | }
137 | else
138 | {
139 | ratings.Users[i].Score = comparer.CompareVectors(ratings.Users[i].ArticleRatings, user.ArticleRatings);
140 | }
141 | }
142 |
143 | var similarUsers = ratings.Users.OrderByDescending(x => x.Score);
144 |
145 | return similarUsers.Take(numUsers).ToList();
146 | }
147 |
148 | public void Save(string file)
149 | {
150 | using (FileStream fs = new FileStream(file, FileMode.Create))
151 | using (GZipStream zip = new GZipStream(fs, CompressionMode.Compress))
152 | using (StreamWriter w = new StreamWriter(zip))
153 | {
154 | w.WriteLine(ratings.Users.Count);
155 | w.WriteLine(ratings.Users[0].ArticleRatings.Length);
156 |
157 | foreach (UserArticleRatings t in ratings.Users)
158 | {
159 | w.WriteLine(t.UserID);
160 |
161 | foreach (double v in t.ArticleRatings)
162 | {
163 | w.WriteLine(v);
164 | }
165 | }
166 |
167 | w.WriteLine(ratings.UserIndexToID.Count);
168 |
169 | foreach (int i in ratings.UserIndexToID)
170 | {
171 | w.WriteLine(i);
172 | }
173 |
174 | w.WriteLine(ratings.ArticleIndexToID.Count);
175 |
176 | foreach (int i in ratings.ArticleIndexToID)
177 | {
178 | w.WriteLine(i);
179 | }
180 | }
181 | }
182 |
183 | public void Load(string file)
184 | {
185 | ratings = new UserArticleRatingsTable();
186 |
187 | using (FileStream fs = new FileStream(file, FileMode.Open))
188 | using (GZipStream zip = new GZipStream(fs, CompressionMode.Decompress))
189 | using (StreamReader r = new StreamReader(zip))
190 | {
191 | long total = long.Parse(r.ReadLine());
192 | int features = int.Parse(r.ReadLine());
193 |
194 | for (long i = 0; i < total; i++)
195 | {
196 | int userId = int.Parse(r.ReadLine());
197 | UserArticleRatings uat = new UserArticleRatings(userId, features);
198 |
199 | for (int x = 0; x < features; x++)
200 | {
201 | uat.ArticleRatings[x] = double.Parse(r.ReadLine());
202 | }
203 |
204 | ratings.Users.Add(uat);
205 | }
206 |
207 | total = int.Parse(r.ReadLine());
208 |
209 | for (int i = 0; i < total; i++)
210 | {
211 | ratings.UserIndexToID.Add(int.Parse(r.ReadLine()));
212 | }
213 |
214 | total = int.Parse(r.ReadLine());
215 |
216 | for (int i = 0; i < total; i++)
217 | {
218 | ratings.ArticleIndexToID.Add(int.Parse(r.ReadLine()));
219 | }
220 | }
221 | }
222 | }
223 | }
224 |
--------------------------------------------------------------------------------